diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 1bfa7a41ccc9..ffc0150ebac5 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,53 +1,18 @@ -#------------------------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. -#------------------------------------------------------------------------------------------------------------- +FROM mcr.microsoft.com/devcontainers/typescript-node:22-bookworm -FROM node:8 +RUN apt-get install -y wget bzip2 -# Avoid warnings by switching to noninteractive -ENV DEBIAN_FRONTEND=noninteractive +# Run in silent mode and save downloaded script as anaconda.sh. +# Run with /bin/bash and run in silent mode to /opt/conda. +# Also get rid of installation script after finishing. +RUN wget --quiet https://repo.anaconda.com/archive/Anaconda3-2023.07-1-Linux-x86_64.sh -O ~/anaconda.sh && \ + /bin/bash ~/anaconda.sh -b -p /opt/conda && \ + rm ~/anaconda.sh -# The node image comes with a base non-root 'node' user which this Dockerfile -# gives sudo access. However, for Linux, this user's GID/UID must match your local -# user UID/GID to avoid permission issues with bind mounts. Update USER_UID / USER_GID -# if yours is not 1000. See https://aka.ms/vscode-remote/containers/non-root-user. -ARG USER_UID=1000 -ARG USER_GID=$USER_UID +ENV PATH="/opt/conda/bin:$PATH" + +# Sudo apt update needs to run in order for installation of fish to work . +RUN sudo apt update && \ + sudo apt install fish -y -# Configure apt and install packages -RUN apt-get update \ - && apt-get -y install --no-install-recommends apt-utils dialog 2>&1 \ - # - # Verify git and needed tools are installed - && apt-get -y install git iproute2 procps \ - # - # Remove outdated yarn from /opt and install via package - # so it can be easily updated via apt-get upgrade yarn - && rm -rf /opt/yarn-* \ - && rm -f /usr/local/bin/yarn \ - && rm -f /usr/local/bin/yarnpkg \ - && apt-get install -y curl apt-transport-https lsb-release \ - && curl -sS https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/pubkey.gpg | apt-key add - 2>/dev/null \ - && echo "deb https://dl.yarnpkg.com/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/ stable main" | tee /etc/apt/sources.list.d/yarn.list \ - && apt-get update \ - && apt-get -y install --no-install-recommends yarn \ - # - # Install tslint and typescript globally - && npm install -g tslint typescript \ - # - # [Optional] Update a non-root user to match UID/GID - see https://aka.ms/vscode-remote/containers/non-root-user. - && if [ "$USER_GID" != "1000" ]; then groupmod node --gid $USER_GID; fi \ - && if [ "$USER_UID" != "1000" ]; then usermod --uid $USER_UID node; fi \ - # [Optional] Add add sudo support for non-root user - && apt-get install -y sudo \ - && echo node ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/node \ - && chmod 0440 /etc/sudoers.d/node \ - # - # Clean up - && apt-get autoremove -y \ - && apt-get clean -y \ - && rm -rf /var/lib/apt/lists/* -# Switch back to dialog for any ad-hoc use of apt-get -ENV DEBIAN_FRONTEND= diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 17c0189dcafd..67a8833d30cf 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,26 +1,30 @@ -// For format details, see https://aka.ms/vscode-remote/devcontainer.json or the definition README at -// https://github.com/microsoft/vscode-dev-containers/tree/master/containers/typescript-node-8 +// For format details, see https://aka.ms/devcontainer.json. { - "name": "Node.js 8 & TypeScript", - "dockerFile": "Dockerfile", - - // Use 'settings' to set *default* container specific settings.json values on container create. - // You can edit these settings after create using File > Preferences > Settings > Remote. - "settings": { - "terminal.integrated.shell.linux": "/bin/bash" + "name": "VS Code Python Dev Container", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "build": { + "dockerfile": "./Dockerfile", + "context": ".." }, + "customizations": { + "vscode": { + "extensions": [ + "charliermarsh.ruff", + "editorconfig.editorconfig", + "esbenp.prettier-vscode", + "dbaeumer.vscode-eslint", + "ms-python.python", + "ms-python.vscode-pylance", + "ms-python.debugpy" + ] + } + }, + // Commands to execute on container creation,start. + "postCreateCommand": "bash scripts/postCreateCommand.sh", + "onCreateCommand": "bash scripts/onCreateCommand.sh", - // Uncomment the next line if you want to publish any ports. - // "appPort": [], - - // Uncomment the next line to run commands after the container is created. - // "postCreateCommand": "yarn install", - - // Uncomment the next line to use a non-root user. On Linux, this will prevent - // new files getting created as root, but you may need to update the USER_UID - // and USER_GID in .devcontainer/Dockerfile to match your user if not 1000. - // "runArgs": [ "-u", "node" ], + "containerEnv": { + "CI_PYTHON_PATH": "/workspaces/vscode-python/.venv/bin/python" + } - // Add the IDs of extensions you want installed when the container is created in the array below. - "extensions": ["ms-vscode.vscode-typescript-tslint-plugin"] } diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index c11f159e3eae..000000000000 --- a/.eslintignore +++ /dev/null @@ -1,1503 +0,0 @@ -# Disable ESLint on all existing js, jsx, ts and tsx files in build/ and src/, -# except for src/client/pythonEnvironments and src/test/pythonEnvironments. - -build/constants.js -build/util.js - -build/ci/postInstall.js -build/ci/scripts/runFunctionalTests.js -build/ci/performance/checkPerformanceResults.js -build/ci/performance/createNewPerformanceBenchmark.js -build/ci/performance/savePerformanceResults.js - -build/webpack/webpack.datascience-ui.config.js -build/webpack/webpack.extension.config.js -build/webpack/webpack.datascience-ui-notebooks.config.js -build/webpack/plugins/less-plugin-base64.js -build/webpack/webpack.datascience-ui.config.builder.js -build/webpack/pdfkit.js -build/webpack/webpack.extension.dependencies.config.js -build/webpack/common.js -build/webpack/webpack.datascience-ui-viewers.config.js -build/webpack/webpack.datascience-ui-renderers.config.js -build/webpack/loaders/fixNodeFetch.js -build/webpack/loaders/remarkLoader.js -build/webpack/loaders/jsonloader.js -build/webpack/loaders/externalizeDependencies.js - -build/tslint-rules/messagesMustBeLocalizedRule.js -build/tslint-rules/baseRuleWalker.js - -build/debug/replaceWithWebBrowserPanel.js - -src/test/mocks/mementos.ts -src/test/mocks/process.ts -src/test/mocks/moduleInstaller.ts -src/test/mocks/proc.ts -src/test/mocks/autoSelector.ts -src/test/mocks/vsc/extHostedTypes.ts -src/test/mocks/vsc/uuid.ts -src/test/mocks/vsc/strings.ts -src/test/mocks/vsc/charCode.ts -src/test/mocks/vsc/htmlContent.ts -src/test/mocks/vsc/selection.ts -src/test/mocks/vsc/position.ts -src/test/mocks/vsc/uri.ts -src/test/mocks/vsc/telemetryReporter.ts -src/test/mocks/vsc/range.ts -src/test/mocks/vsc/index.ts -src/test/mocks/vsc/arrays.ts - -src/test/smoke/common.ts -src/test/smoke/datascience.smoke.test.ts -src/test/smoke/runInTerminal.smoke.test.ts -src/test/smoke/languageServer.smoke.test.ts - -src/test/analysisEngineTest.ts -src/test/ciConstants.ts -src/test/common.ts -src/test/constants.ts -src/test/core.ts -src/test/debuggerTest.ts -src/test/extension-version.functional.test.ts -src/test/fixtures.ts -src/test/index.ts -src/test/initialize.ts -src/test/mockClasses.ts -src/test/multiRootTest.ts -src/test/performanceTest.ts -src/test/proc.ts -src/test/serviceRegistry.ts -src/test/smokeTest.ts -src/test/standardTest.ts -src/test/startupTelemetry.unit.test.ts -src/test/sourceMapSupport.test.ts -src/test/sourceMapSupport.unit.test.ts -src/test/testBootstrap.ts -src/test/testLogger.ts -src/test/testRunner.ts -src/test/textUtils.ts -src/test/unittests.ts -src/test/vscode-mock.ts - -src/test/interpreters/mocks.ts -src/test/interpreters/interpreterVersion.unit.test.ts -src/test/interpreters/virtualEnvs/index.unit.test.ts -src/test/interpreters/virtualEnvs/condaInheritEnvPrompt.unit.test.ts -src/test/interpreters/virtualEnvs/virtualEnvPrompt.unit.test.ts -src/test/interpreters/autoSelection/proxy.unit.test.ts -src/test/interpreters/autoSelection/interpreterSecurity/interpreterSecurityStorage.unit.test.ts -src/test/interpreters/autoSelection/interpreterSecurity/interpreterEvaluation.unit.test.ts -src/test/interpreters/autoSelection/interpreterSecurity/interpreterSecurityService.unit.test.ts -src/test/interpreters/autoSelection/index.unit.test.ts -src/test/interpreters/autoSelection/rules/settings.unit.test.ts -src/test/interpreters/autoSelection/rules/cached.unit.test.ts -src/test/interpreters/autoSelection/rules/winRegistry.unit.test.ts -src/test/interpreters/autoSelection/rules/base.unit.test.ts -src/test/interpreters/autoSelection/rules/currentPath.unit.test.ts -src/test/interpreters/autoSelection/rules/workspaceEnv.unit.test.ts -src/test/interpreters/autoSelection/rules/system.unit.test.ts -src/test/interpreters/virtualEnvManager.unit.test.ts -src/test/interpreters/pythonPathUpdaterFactory.unit.test.ts -src/test/interpreters/interpreterService.unit.test.ts -src/test/interpreters/activation/service.unit.test.ts -src/test/interpreters/activation/wrapperEnvironmentActivationService.unit.test.ts -src/test/interpreters/activation/preWarmVariables.unit.test.ts -src/test/interpreters/activation/terminalEnvironmentActivationService.unit.test.ts -src/test/interpreters/helpers.unit.test.ts -src/test/interpreters/serviceRegistry.unit.test.ts -src/test/interpreters/currentPathService.unit.test.ts -src/test/interpreters/display/progressDisplay.unit.test.ts -src/test/interpreters/display/interpreterSelectionTip.unit.test.ts -src/test/interpreters/display.unit.test.ts - -src/test/configuration/interpreterSelector/interpreterSelector.unit.test.ts -src/test/configuration/interpreterSelector/commands/resetInterpreter.unit.test.ts -src/test/configuration/interpreterSelector/commands/setInterpreter.unit.test.ts - -src/test/install/channelManager.channels.test.ts -src/test/install/channelManager.messages.test.ts - -src/test/terminals/serviceRegistry.unit.test.ts -src/test/terminals/activation.unit.test.ts -src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts -src/test/terminals/codeExecution/helper.test.ts -src/test/terminals/codeExecution/codeExecutionManager.unit.test.ts -src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts - -src/test/markdown/restTextConverter.test.ts - -src/test/languageServers/jedi/autocomplete/base.test.ts -src/test/languageServers/jedi/autocomplete/pep526.test.ts -src/test/languageServers/jedi/autocomplete/pep484.test.ts -src/test/languageServers/jedi/signature/signature.jedi.test.ts -src/test/languageServers/jedi/completionSource.unit.test.ts -src/test/languageServers/jedi/symbolProvider.unit.test.ts -src/test/languageServers/jedi/pythonSignatureProvider.unit.test.ts -src/test/languageServers/jedi/definitions/parallel.jedi.test.ts -src/test/languageServers/jedi/definitions/navigation.test.ts -src/test/languageServers/jedi/definitions/hover.jedi.test.ts - -src/test/providers/foldingProvider.test.ts -src/test/providers/importSortProvider.unit.test.ts -src/test/providers/terminal.unit.test.ts -src/test/providers/repl.unit.test.ts -src/test/providers/codeActionProvider/main.unit.test.ts -src/test/providers/codeActionProvider/pythonCodeActionsProvider.unit.test.ts -src/test/providers/codeActionProvider/launchJsonCodeActionProvider.unit.test.ts -src/test/providers/shebangCodeLenseProvider.unit.test.ts -src/test/providers/serviceRegistry.unit.test.ts - -src/test/activation/aaTesting.unit.test.ts -src/test/activation/activationService.unit.test.ts -src/test/activation/languageServer/downloadChannelRules.unit.test.ts -src/test/activation/languageServer/platformData.unit.test.ts -src/test/activation/languageServer/languageServer.unit.test.ts -src/test/activation/languageServer/languageServerFolderService.unit.test.ts -src/test/activation/languageServer/languageServerPackageRepository.unit.test.ts -src/test/activation/languageServer/languageServerPackageService.test.ts -src/test/activation/languageServer/manager.unit.test.ts -src/test/activation/languageServer/analysisOptions.unit.test.ts -src/test/activation/languageServer/languageServerCompatibilityService.unit.test.ts -src/test/activation/languageServer/activator.unit.test.ts -src/test/activation/languageServer/downloader.unit.test.ts -src/test/activation/languageServer/languageServerExtension.unit.test.ts -src/test/activation/languageServer/languageClientFactory.unit.test.ts -src/test/activation/languageServer/outputChannel.unit.test.ts -src/test/activation/languageServer/languageServerPackageService.unit.test.ts -src/test/activation/activeResource.unit.test.ts -src/test/activation/serviceRegistry.unit.test.ts -src/test/activation/node/languageServerFolderService.unit.test.ts -src/test/activation/node/languageServerChangeHandler.unit.test.ts -src/test/activation/node/activator.unit.test.ts -src/test/activation/extensionSurvey.unit.test.ts -src/test/activation/activationManager.unit.test.ts - -src/test/utils/interpreters.ts -src/test/utils/fs.ts - -src/test/language/braceCounter.unit.test.ts -src/test/language/textRangeCollection.unit.test.ts -src/test/language/characterStream.unit.test.ts -src/test/language/languageConfiguration.unit.test.ts -src/test/language/textIterator.unit.test.ts -src/test/language/textBuilder.unit.test.ts -src/test/language/textRange.unit.test.ts -src/test/language/tokenizer.unit.test.ts - -src/test/api.functional.test.ts - -src/test/testing/argsService.test.ts -src/test/testing/mocks.ts -src/test/testing/debugger.test.ts -src/test/testing/serviceRegistry.ts -src/test/testing/unittest/unittest.test.ts -src/test/testing/unittest/unittest.discovery.unit.test.ts -src/test/testing/unittest/unittest.diagnosticService.unit.test.ts -src/test/testing/unittest/unittest.discovery.test.ts -src/test/testing/unittest/unittest.run.test.ts -src/test/testing/unittest/unittest.argsService.unit.test.ts -src/test/testing/unittest/unittest.unit.test.ts -src/test/testing/codeLenses/testFiles.unit.test.ts -src/test/testing/nosetest/nosetest.test.ts -src/test/testing/nosetest/nosetest.discovery.unit.test.ts -src/test/testing/nosetest/nosetest.disovery.test.ts -src/test/testing/nosetest/nosetest.argsService.unit.test.ts -src/test/testing/nosetest/nosetest.run.test.ts -src/test/testing/pytest/pytest.testMessageService.test.ts -src/test/testing/pytest/pytest_unittest_parser_data.ts -src/test/testing/pytest/pytest.discovery.test.ts -src/test/testing/pytest/pytest.test.ts -src/test/testing/pytest/pytest_run_tests_data.ts -src/test/testing/pytest/pytest.argsService.unit.test.ts -src/test/testing/pytest/pytest.run.test.ts -src/test/testing/pytest/services/discoveryService.unit.test.ts -src/test/testing/configurationFactory.unit.test.ts -src/test/testing/rediscover.test.ts -src/test/testing/helper.ts -src/test/testing/navigation/fileNavigator.unit.test.ts -src/test/testing/navigation/functionNavigator.unit.test.ts -src/test/testing/navigation/suiteNavigator.unit.test.ts -src/test/testing/navigation/serviceRegistry.unit.test.ts -src/test/testing/navigation/commandHandlers.unit.test.ts -src/test/testing/navigation/helper.unit.test.ts -src/test/testing/navigation/symbolNavigator.unit.test.ts -src/test/testing/configuration.unit.test.ts -src/test/testing/explorer/testTreeViewItem.unit.test.ts -src/test/testing/explorer/treeView.unit.test.ts -src/test/testing/explorer/testExplorerCommandHandler.unit.test.ts -src/test/testing/explorer/failedTestHandler.unit.test.ts -src/test/testing/explorer/testTreeViewProvider.unit.test.ts -src/test/testing/explorer/explorerTestData.ts -src/test/testing/stoppingDiscoverAndTest.test.ts -src/test/testing/banners/proposeNewLanguageServerBanner.unit.test.ts -src/test/testing/common/argsHelper.unit.test.ts -src/test/testing/common/trackEnablement.unit.test.ts -src/test/testing/common/debugLauncher.unit.test.ts -src/test/testing/common/managers/baseTestManager.unit.test.ts -src/test/testing/common/managers/testConfigurationManager.unit.test.ts -src/test/testing/common/xUnitParser.unit.test.ts -src/test/testing/common/testUtils.unit.test.ts -src/test/testing/common/testVisitors/resultResetVisitor.unit.test.ts -src/test/testing/common/services/discoveredTestParser.unit.test.ts -src/test/testing/common/services/storageService.unit.test.ts -src/test/testing/common/services/testStatusService.unit.test.ts -src/test/testing/common/services/testResultsService.unit.test.ts -src/test/testing/common/services/discovery.unit.test.ts -src/test/testing/common/services/configSettingService.unit.test.ts -src/test/testing/common/services/contextService.unit.test.ts -src/test/testing/main.unit.test.ts -src/test/testing/results.ts -src/test/testing/display/picker.functional.test.ts -src/test/testing/display/main.unit.test.ts -src/test/testing/display/picker.unit.test.ts - -src/test/common/exitCIAfterTestReporter.ts -src/test/common/crypto.unit.test.ts -src/test/common/configuration/service.test.ts -src/test/common/configuration/service.unit.test.ts -src/test/common/net/fileDownloader.unit.test.ts -src/test/common/net/httpClient.unit.test.ts -src/test/common/moduleInstaller.test.ts -src/test/common/terminals/activator/index.unit.test.ts -src/test/common/terminals/activator/base.unit.test.ts -src/test/common/terminals/activator/powerShellFailedHandler.unit.test.ts -src/test/common/terminals/activation.conda.unit.test.ts -src/test/common/terminals/shellDetector.unit.test.ts -src/test/common/terminals/activation.bash.unit.test.ts -src/test/common/terminals/commandPrompt.unit.test.ts -src/test/common/terminals/service.unit.test.ts -src/test/common/terminals/synchronousTerminalService.unit.test.ts -src/test/common/terminals/serviceRegistry.unit.test.ts -src/test/common/terminals/helper.unit.test.ts -src/test/common/terminals/activation.commandPrompt.unit.test.ts -src/test/common/terminals/factory.unit.test.ts -src/test/common/terminals/pyenvActivationProvider.unit.test.ts -src/test/common/terminals/activation.unit.test.ts -src/test/common/terminals/shellDetectors/shellDetectors.unit.test.ts -src/test/common/terminals/environmentActivationProviders/pipEnvActivationProvider.unit.test.ts -src/test/common/terminals/environmentActivationProviders/terminalActivation.testvirtualenvs.ts -src/test/common/misc.test.ts -src/test/common/socketStream.test.ts -src/test/common/configSettings.test.ts -src/test/common/experiments/service.unit.test.ts -src/test/common/experiments/manager.unit.test.ts -src/test/common/experiments/telemetry.unit.test.ts -src/test/common/platform/filesystem.unit.test.ts -src/test/common/platform/pathUtils.functional.test.ts -src/test/common/platform/errors.unit.test.ts -src/test/common/platform/platformService.test.ts -src/test/common/platform/utils.ts -src/test/common/platform/fs-temp.unit.test.ts -src/test/common/platform/fs-temp.functional.test.ts -src/test/common/platform/serviceRegistry.unit.test.ts -src/test/common/platform/filesystem.functional.test.ts -src/test/common/platform/fs-paths.unit.test.ts -src/test/common/platform/fs-paths.functional.test.ts -src/test/common/platform/filesystem.test.ts -src/test/common/utils/async.unit.test.ts -src/test/common/utils/text.unit.test.ts -src/test/common/utils/regexp.unit.test.ts -src/test/common/utils/cacheUtils.unit.test.ts -src/test/common/utils/decorators.unit.test.ts -src/test/common/utils/localize.functional.test.ts -src/test/common/utils/version.unit.test.ts -src/test/common/utils/workerPool.functional.test.ts -src/test/common/configSettings/configSettings.pythonPath.unit.test.ts -src/test/common/configSettings/configSettings.unit.test.ts -src/test/common/featureDeprecationManager.unit.test.ts -src/test/common/dotnet/compatibilityService.unit.test.ts -src/test/common/dotnet/serviceRegistry.unit.test.ts -src/test/common/dotnet/services/linuxCompatibilityService.unit.test.ts -src/test/common/dotnet/services/winCompatibilityService.unit.test.ts -src/test/common/dotnet/services/unknownOsCompatibilityService.unit.test.ts -src/test/common/dotnet/services/macCompatibilityService.unit.test.ts -src/test/common/serviceRegistry.unit.test.ts -src/test/common/extensions.unit.test.ts -src/test/common/variables/envVarsService.functional.test.ts -src/test/common/variables/envVarsService.test.ts -src/test/common/variables/envVarsService.unit.test.ts -src/test/common/variables/serviceRegistry.unit.test.ts -src/test/common/variables/environmentVariablesProvider.unit.test.ts -src/test/common/variables/envVarsProvider.multiroot.test.ts -src/test/common/nuget/nugetService.unit.test.ts -src/test/common/nuget/azureBobStoreRepository.functional.test.ts -src/test/common/nuget/nugetRepository.unit.test.ts -src/test/common/nuget/azureBobStoreRepository.unit.test.ts -src/test/common/helpers.test.ts -src/test/common/application/commands/reloadCommand.unit.test.ts -src/test/common/installer/channelManager.unit.test.ts -src/test/common/installer/condaInstaller.unit.test.ts -src/test/common/installer/installer.unit.test.ts -src/test/common/installer/pipInstaller.unit.test.ts -src/test/common/installer/installer.invalidPath.unit.test.ts -src/test/common/installer/moduleInstaller.unit.test.ts -src/test/common/installer/pipEnvInstaller.unit.test.ts -src/test/common/installer/productPath.unit.test.ts -src/test/common/installer/serviceRegistry.unit.test.ts -src/test/common/installer/poetryInstaller.unit.test.ts -src/test/common/installer/extensionBuildInstaller.unit.test.ts -src/test/common/socketCallbackHandler.test.ts -src/test/common/installer.test.ts -src/test/common/process/decoder.test.ts -src/test/common/process/pythonDaemonPool.unit.test.ts -src/test/common/process/processFactory.unit.test.ts -src/test/common/process/pythonToolService.unit.test.ts -src/test/common/process/proc.observable.test.ts -src/test/common/process/currentProcess.test.ts -src/test/common/process/serviceRegistry.unit.test.ts -src/test/common/process/pythonProc.simple.multiroot.test.ts -src/test/common/process/execFactory.test.ts -src/test/common/process/pythonEnvironment.unit.test.ts -src/test/common/process/logger.unit.test.ts -src/test/common/process/pythonDaemonPool.functional.test.ts -src/test/common/process/proc.exec.test.ts -src/test/common/process/pythonDaemon.functional.test.ts -src/test/common/process/pythonProcess.unit.test.ts -src/test/common/process/pythonExecutionFactory.unit.test.ts -src/test/common/process/proc.unit.test.ts -src/test/common/asyncDump.ts -src/test/common/interpreterPathService.unit.test.ts -src/test/common/insidersBuild/downloadChannelRules.unit.test.ts -src/test/common/insidersBuild/insidersExtensionPrompt.unit.test.ts -src/test/common/insidersBuild/downloadChannelService.unit.test.ts -src/test/common/insidersBuild/insidersExtensionService.unit.test.ts - -src/test/pythonFiles/formatting/dummy.ts - -src/test/format/extension.dispatch.test.ts -src/test/format/extension.format.native.vscode.test.ts -src/test/format/extension.onTypeFormat.test.ts -src/test/format/extension.lineFormatter.test.ts -src/test/format/extension.sort.test.ts -src/test/format/extension.onEnterFormat.test.ts -src/test/format/extension.format.test.ts -src/test/format/format.helper.test.ts -src/test/format/formatter.unit.test.ts - -src/test/debugger/extension/configuration/debugConfigurationService.unit.test.ts -src/test/debugger/extension/configuration/providers/moduleLaunch.unit.test.ts -src/test/debugger/extension/configuration/providers/pyramidLaunch.unit.test.ts -src/test/debugger/extension/configuration/providers/flaskLaunch.unit.test.ts -src/test/debugger/extension/configuration/providers/fileLaunch.unit.test.ts -src/test/debugger/extension/configuration/providers/djangoLaunch.unit.test.ts -src/test/debugger/extension/configuration/providers/providerFactory.unit.test.ts -src/test/debugger/extension/configuration/providers/remoteAttach.unit.test.ts -src/test/debugger/extension/configuration/providers/pidAttach.unit.test.ts -src/test/debugger/extension/configuration/resolvers/base.unit.test.ts -src/test/debugger/extension/configuration/resolvers/common.ts -src/test/debugger/extension/configuration/resolvers/attach.unit.test.ts -src/test/debugger/extension/configuration/resolvers/launch.unit.test.ts -src/test/debugger/extension/configuration/launch.json/updaterServer.unit.test.ts -src/test/debugger/extension/configuration/launch.json/completionProvider.unit.test.ts -src/test/debugger/extension/configuration/launch.json/interpreterPathCommand.unit.test.ts -src/test/debugger/extension/banner.unit.test.ts -src/test/debugger/extension/adapter/adapter.test.ts -src/test/debugger/extension/adapter/outdatedDebuggerPrompt.unit.test.ts -src/test/debugger/extension/adapter/remoteLaunchers.unit.test.ts -src/test/debugger/extension/adapter/factory.unit.test.ts -src/test/debugger/extension/adapter/activator.unit.test.ts -src/test/debugger/extension/adapter/logging.unit.test.ts -src/test/debugger/extension/serviceRegistry.unit.test.ts -src/test/debugger/extension/hooks/childProcessAttachHandler.unit.test.ts -src/test/debugger/extension/hooks/childProcessAttachService.unit.test.ts -src/test/debugger/extension/attachQuickPick/provider.unit.test.ts -src/test/debugger/extension/attachQuickPick/wmicProcessParser.unit.test.ts -src/test/debugger/extension/attachQuickPick/factory.unit.test.ts -src/test/debugger/extension/attachQuickPick/psProcessParser.unit.test.ts -src/test/debugger/utils.ts -src/test/debugger/common/constants.ts -src/test/debugger/common/protocolparser.test.ts -src/test/debugger/envVars.test.ts - -src/test/startPage/startPage.unit.test.ts -src/test/startPage/startPage.functional.test.tsx - -src/test/telemetry/index.unit.test.ts -src/test/telemetry/importTracker.unit.test.ts -src/test/telemetry/envFileTelemetry.unit.test.ts -src/test/telemetry/extensionInstallTelemetry.unit.test.ts - -src/test/linters/pylint.unit.test.ts -src/test/linters/mypy.unit.test.ts -src/test/linters/lint.provider.test.ts -src/test/linters/lint.unit.test.ts -src/test/linters/linter.availability.unit.test.ts -src/test/linters/common.ts -src/test/linters/lintengine.test.ts -src/test/linters/lint.multilinter.test.ts -src/test/linters/lint.test.ts -src/test/linters/linterinfo.unit.test.ts -src/test/linters/serviceRegistry.unit.test.ts -src/test/linters/pylint.test.ts -src/test/linters/lint.manager.unit.test.ts -src/test/linters/lint.args.test.ts -src/test/linters/linterCommands.unit.test.ts -src/test/linters/lint.functional.test.ts -src/test/linters/linterManager.unit.test.ts -src/test/linters/lint.multiroot.test.ts - -src/test/application/diagnostics/checks/macPythonInterpreter.unit.test.ts -src/test/application/diagnostics/checks/upgradeCodeRunner.unit.test.ts -src/test/application/diagnostics/checks/pythonPathDeprecated.unit.test.ts -src/test/application/diagnostics/checks/pythonInterpreter.unit.test.ts -src/test/application/diagnostics/checks/invalidLaunchJsonDebugger.unit.test.ts -src/test/application/diagnostics/checks/updateTestSettings.unit.test.ts -src/test/application/diagnostics/checks/powerShellActivation.unit.test.ts -src/test/application/diagnostics/checks/invalidPythonPathInDebugger.unit.test.ts -src/test/application/diagnostics/checks/envPathVariable.unit.test.ts -src/test/application/diagnostics/checks/lsNotSupported.unit.test.ts -src/test/application/diagnostics/applicationDiagnostics.unit.test.ts -src/test/application/diagnostics/promptHandler.unit.test.ts -src/test/application/diagnostics/sourceMapSupportService.unit.test.ts -src/test/application/diagnostics/serviceRegistry.unit.test.ts -src/test/application/diagnostics/filter.unit.test.ts -src/test/application/diagnostics/commands/ignore.unit.test.ts -src/test/application/diagnostics/commands/launchBrowser.unit.test.ts -src/test/application/diagnostics/commands/execVSCCommands.unit.test.ts -src/test/application/diagnostics/commands/factory.unit.test.ts -src/test/application/misc/joinMailingListPrompt.unit.test.ts - -src/test/performance/load.perf.test.ts - -src/test/datascience/mockLanguageServerCache.ts -src/test/datascience/debugLocationTracker.unit.test.ts -src/test/datascience/mockLiveShare.ts -src/test/datascience/liveshare.functional.test.tsx -src/test/datascience/mountedWebViewFactory.ts -src/test/datascience/data-viewing/dataViewerPDependencyService.unit.test.ts -src/test/datascience/data-viewing/dataViewer.unit.test.ts -src/test/datascience/mockPythonService.ts -src/test/datascience/testHelpersCore.ts -src/test/datascience/shiftEnterBanner.unit.test.ts -src/test/datascience/executionServiceMock.ts -src/test/datascience/mockJupyterManager.ts -src/test/datascience/mockCommandManager.ts -src/test/datascience/mockCustomEditorService.ts -src/test/datascience/mockInputBox.ts -src/test/datascience/reactHelpers.ts -src/test/datascience/helpers.ts -src/test/datascience/jupyterUriProviderRegistration.unit.test.ts -src/test/datascience/mockDocumentManager.ts -src/test/datascience/errorHandler.unit.test.ts -src/test/datascience/cellMatcher.unit.test.ts -src/test/datascience/crossProcessLock.unit.test.ts -src/test/datascience/uiTests/helpers.ts -src/test/datascience/uiTests/webBrowserPanel.ts -src/test/datascience/uiTests/notebookUi.ts -src/test/datascience/uiTests/webBrowserPanelProvider.ts -src/test/datascience/uiTests/recorder.ts -src/test/datascience/uiTests/notebookHelpers.ts -src/test/datascience/uiTests/ipywidget.ui.functional.test.ts -src/test/datascience/mockWorkspaceConfiguration.ts -src/test/datascience/mockTextEditor.ts -src/test/datascience/mockLanguageServerAnalysisOptions.ts -src/test/datascience/mockLanguageServerProxy.ts -src/test/datascience/interactiveWindowCommandListener.unit.test.ts -src/test/datascience/trustedNotebooks.functional.test.tsx -src/test/datascience/mockPythonSettings.ts -src/test/datascience/progress/progressReporter.unit.test.ts -src/test/datascience/progress/decorators.unit.test.ts -src/test/datascience/kernel-launcher/kernelDaemonPool.unit.test.ts -src/test/datascience/kernel-launcher/kernelDaemonPoolPreWarmer.unit.test.ts -src/test/datascience/kernel-launcher/kernelLauncherDaemon.unit.test.ts -src/test/datascience/ipywidgets/ipyWidgetScriptSourceProvider.unit.test.ts -src/test/datascience/ipywidgets/cdnWidgetScriptSourceProvider.unit.test.ts -src/test/datascience/ipywidgets/localWidgetScriptSourceProvider.unit.test.ts -src/test/datascience/ipywidgets/incompatibleWidgetHandler.unit.test.ts -src/test/datascience/mockKernelFinder.ts -src/test/datascience/datascienceSurveyBanner.unit.test.ts -src/test/datascience/intellisense.functional.test.tsx -src/test/datascience/nativeEditor.toolbar.functional.test.tsx -src/test/datascience/mockDocument.ts -src/test/datascience/raw-kernel/rawKernelTestHelpers.ts -src/test/datascience/raw-kernel/rawKernel.functional.test.ts -src/test/datascience/color.test.ts -src/test/datascience/nativeEditorViewTracker.unit.test.ts -src/test/datascience/mockCode2ProtocolConverter.ts -src/test/datascience/mockFileSystem.ts -src/test/datascience/interactive-common/trustService.unit.test.ts -src/test/datascience/interactive-common/notebookProvider.unit.test.ts -src/test/datascience/interactive-common/notebookServerProvider.unit.test.ts -src/test/datascience/interactive-common/trustCommandHandler.unit.test.ts -src/test/datascience/mockStatusProvider.ts -src/test/datascience/extensionapi/exampleextension/ms-ai-tools-test/webpack.config.js -src/test/datascience/extensionapi/exampleextension/ms-ai-tools-test/.eslintrc.js -src/test/datascience/extensionapi/exampleextension/ms-ai-tools-test/src/typings/python.d.ts -src/test/datascience/extensionapi/exampleextension/ms-ai-tools-test/src/extension.ts -src/test/datascience/extensionapi/exampleextension/ms-ai-tools-test/src/serverPicker.ts -src/test/datascience/common.unit.test.ts -src/test/datascience/testexecutionLogger.ts -src/test/datascience/interactiveWindow.functional.test.tsx -src/test/datascience/mockQuickPick.ts -src/test/datascience/dsTestSetup.ts -src/test/datascience/mockLanguageServer.ts -src/test/datascience/debugger.functional.test.tsx -src/test/datascience/testInteractiveWindowProvider.ts -src/test/datascience/dataScienceIocContainer.ts -src/test/datascience/dataviewer.functional.test.tsx -src/test/datascience/jupyterUtils.unit.test.ts -src/test/datascience/preWarmVariables.unit.test.ts -src/test/datascience/mockJupyterNotebook.ts -src/test/datascience/remoteTestHelpers.ts -src/test/datascience/mockWorkspaceFolder.ts -src/test/datascience/variableexplorer.functional.test.tsx -src/test/datascience/mockJupyterSession.ts -src/test/datascience/jupyterUriProviderRegistration.functional.test.ts -src/test/datascience/mockJupyterRequest.ts -src/test/datascience/inputHistory.unit.test.ts -src/test/datascience/jupyterHelpers.ts -src/test/datascience/mockJupyterServer.ts -src/test/datascience/mockJupyterManagerFactory.ts -src/test/datascience/mainState.unit.test.ts -src/test/datascience/mockDebugService.ts -src/test/datascience/nativeEditorTestHelpers.tsx -src/test/datascience/datascience.unit.test.ts -src/test/datascience/kernelLauncher.functional.test.ts -src/test/datascience/interactive-ipynb/nativeEditorStorage.unit.test.ts -src/test/datascience/interactive-ipynb/nativeEditorProvider.functional.test.ts -src/test/datascience/kernelFinder.unit.test.ts -src/test/datascience/plotViewer.functional.test.tsx -src/test/datascience/mockExtensions.ts -src/test/datascience/mockProtocol2CodeConverter.ts -src/test/datascience/editor-integration/helpers.ts -src/test/datascience/editor-integration/cellhashprovider.unit.test.ts -src/test/datascience/editor-integration/gotocell.functional.test.ts -src/test/datascience/editor-integration/codelensprovider.unit.test.ts -src/test/datascience/editor-integration/codewatcher.unit.test.ts -src/test/datascience/jupyterPasswordConnect.unit.test.ts -src/test/datascience/commands/serverSelector.unit.test.ts -src/test/datascience/commands/commandRegistry.unit.test.ts -src/test/datascience/commands/notebookCommands.functional.test.ts -src/test/datascience/testHelpers.tsx -src/test/datascience/notebook.functional.test.ts -src/test/datascience/mockLanguageClient.ts -src/test/datascience/errorHandler.functional.test.tsx -src/test/datascience/notebook/notebookStorage.unit.test.ts -src/test/datascience/notebook/notebookTrust.native.vscode.test.ts -src/test/datascience/notebook/rendererExtensionDownloader.unit.test.ts -src/test/datascience/notebook/survey.unit.test.ts -src/test/datascience/notebook/interrupRestart.native.vscode.test.ts -src/test/datascience/notebook/contentProvider.native.vscode.test.ts -src/test/datascience/notebook/helper.ts -src/test/datascience/notebook/contentProvider.unit.test.ts -src/test/datascience/notebook/edit.native.vscode.test.ts -src/test/datascience/notebook/rendererExension.unit.test.ts -src/test/datascience/notebook/saving.native.vscode.test.ts -src/test/datascience/notebook/notebookEditorProvider.native.vscode.test.ts -src/test/datascience/notebook/helpers.unit.test.ts -src/test/datascience/notebook/executionService.native.vscode.test.ts -src/test/datascience/notebook/cellOutput.native.vscode.test.ts -src/test/datascience/interactiveWindowTestHelpers.tsx -src/test/datascience/export/exportUtil.test.ts -src/test/datascience/export/exportToHTML.test.ts -src/test/datascience/export/exportToPython.test.ts -src/test/datascience/export/exportFileOpener.unit.test.ts -src/test/datascience/export/exportManager.test.ts -src/test/datascience/intellisense.unit.test.ts -src/test/datascience/nativeEditor.functional.test.tsx -src/test/datascience/markdownManipulation.unit.test.ts -src/test/datascience/interactivePanel.functional.test.tsx -src/test/datascience/variableTestHelpers.ts -src/test/datascience/activation.unit.test.ts -src/test/datascience/testPersistentStateFactory.ts -src/test/datascience/jupyter/interpreter/jupyterInterpreterSubCommandExecutionService.unit.test.ts -src/test/datascience/jupyter/interpreter/jupyterInterpreterDependencyService.unit.test.ts -src/test/datascience/jupyter/interpreter/jupyterInterpreterStateStore.unit.test.ts -src/test/datascience/jupyter/interpreter/jupyterInterpreterService.unit.test.ts -src/test/datascience/jupyter/interpreter/jupyterInterpreterSelectionCommand.unit.test.ts -src/test/datascience/jupyter/interpreter/jupyterInterpreterSelector.unit.test.ts -src/test/datascience/jupyter/serverSelector.unit.test.ts -src/test/datascience/jupyter/kernels/kernelSelector.unit.test.ts -src/test/datascience/jupyter/kernels/kernelSelections.unit.test.ts -src/test/datascience/jupyter/kernels/kernelDependencyService.unit.test.ts -src/test/datascience/jupyter/kernels/kernelSwitcher.unit.test.ts -src/test/datascience/jupyter/kernels/kernelService.unit.test.ts -src/test/datascience/jupyter/jupyterCellOutputMimeTypeTracker.unit.test.ts -src/test/datascience/jupyter/jupyterSession.unit.test.ts -src/test/datascience/jupyter/jupyterConnection.unit.test.ts -src/test/datascience/jupyter/serverCache.unit.test.ts -src/test/datascience/mockWorkspaceConfig.ts -src/test/datascience/mountedWebView.ts -src/test/datascience/execution.unit.test.ts -src/test/datascience/mockProcessService.ts -src/test/datascience/testNativeEditorProvider.ts -src/test/datascience/cellFactory.unit.test.ts - -src/test/refactor/extension.refactor.extract.method.test.ts -src/test/refactor/extension.refactor.extract.var.test.ts -src/test/refactor/rename.test.ts - -src/test/workspaceSymbols/provider.unit.test.ts -src/test/workspaceSymbols/common.ts -src/test/workspaceSymbols/main.unit.test.ts -src/test/workspaceSymbols/generator.unit.test.ts - -src/ipywidgets/types/require.js.d.ts -src/ipywidgets/types/index.d.ts -src/ipywidgets/webpack.config.js -src/ipywidgets/scripts/copyfiles.js -src/ipywidgets/scripts/clean.js -src/ipywidgets/src/manager.ts -src/ipywidgets/src/widgetLoader.ts -src/ipywidgets/src/libembed.ts -src/ipywidgets/src/index.ts -src/ipywidgets/src/embed.ts -src/ipywidgets/src/signal.ts -src/ipywidgets/src/documentContext.ts - -src/datascience-ui/native-editor/index.tsx -src/datascience-ui/native-editor/nativeCell.tsx -src/datascience-ui/native-editor/addCellLine.tsx -src/datascience-ui/native-editor/toolbar.tsx -src/datascience-ui/native-editor/nativeEditor.tsx -src/datascience-ui/native-editor/redux/mapping.ts -src/datascience-ui/native-editor/redux/actions.ts -src/datascience-ui/native-editor/redux/reducers/movement.ts -src/datascience-ui/native-editor/redux/reducers/index.ts -src/datascience-ui/native-editor/redux/reducers/creation.ts -src/datascience-ui/native-editor/redux/reducers/execution.ts -src/datascience-ui/native-editor/redux/reducers/effects.ts -src/datascience-ui/native-editor/redux/store.ts -src/datascience-ui/renderers/index.tsx -src/datascience-ui/renderers/webviewApi.d.ts -src/datascience-ui/renderers/constants.ts -src/datascience-ui/renderers/render.tsx -src/datascience-ui/plot/index.tsx -src/datascience-ui/plot/testSvg.ts -src/datascience-ui/plot/toolbar.tsx -src/datascience-ui/plot/mainPanel.tsx -src/datascience-ui/ipywidgets/manager.ts -src/datascience-ui/ipywidgets/container.tsx -src/datascience-ui/ipywidgets/types.ts -src/datascience-ui/ipywidgets/index.ts -src/datascience-ui/ipywidgets/kernel.ts -src/datascience-ui/ipywidgets/requirejsRegistry.ts -src/datascience-ui/ipywidgets/incompatibleWidgetHandler.ts -src/datascience-ui/interactive-common/trimmedOutputLink.tsx -src/datascience-ui/interactive-common/trustMessage.tsx -src/datascience-ui/interactive-common/variableExplorerRowRenderer.tsx -src/datascience-ui/interactive-common/variableExplorerHeaderCellFormatter.tsx -src/datascience-ui/interactive-common/code.tsx -src/datascience-ui/interactive-common/buildSettingsCss.ts -src/datascience-ui/interactive-common/markdown.tsx -src/datascience-ui/interactive-common/editor.tsx -src/datascience-ui/interactive-common/mainState.ts -src/datascience-ui/interactive-common/collapseButton.tsx -src/datascience-ui/interactive-common/utils.ts -src/datascience-ui/interactive-common/images.d.ts -src/datascience-ui/interactive-common/tokenizer.ts -src/datascience-ui/interactive-common/cellInput.tsx -src/datascience-ui/interactive-common/variableExplorerEmptyRows.tsx -src/datascience-ui/interactive-common/variablePanel.tsx -src/datascience-ui/interactive-common/jupyterInfo.tsx -src/datascience-ui/interactive-common/executionCount.tsx -src/datascience-ui/interactive-common/handlers.ts -src/datascience-ui/interactive-common/variableExplorer.tsx -src/datascience-ui/interactive-common/intellisenseProvider.ts -src/datascience-ui/interactive-common/variableExplorerButtonCellFormatter.tsx -src/datascience-ui/interactive-common/markdownManipulation.ts -src/datascience-ui/interactive-common/variableExplorerCellFormatter.tsx -src/datascience-ui/interactive-common/cellOutput.tsx -src/datascience-ui/interactive-common/informationMessages.tsx -src/datascience-ui/interactive-common/redux/helpers.ts -src/datascience-ui/interactive-common/redux/reducers/helpers.ts -src/datascience-ui/interactive-common/redux/reducers/monaco.ts -src/datascience-ui/interactive-common/redux/reducers/transfer.ts -src/datascience-ui/interactive-common/redux/reducers/types.ts -src/datascience-ui/interactive-common/redux/reducers/variables.ts -src/datascience-ui/interactive-common/redux/reducers/commonEffects.ts -src/datascience-ui/interactive-common/redux/reducers/kernel.ts -src/datascience-ui/interactive-common/redux/postOffice.ts -src/datascience-ui/interactive-common/redux/store.ts -src/datascience-ui/interactive-common/transforms.tsx -src/datascience-ui/interactive-common/contentPanel.tsx -src/datascience-ui/interactive-common/inputHistory.ts -src/datascience-ui/history-react/index.tsx -src/datascience-ui/history-react/interactivePanel.tsx -src/datascience-ui/history-react/interactiveCell.tsx -src/datascience-ui/history-react/redux/mapping.ts -src/datascience-ui/history-react/redux/actions.ts -src/datascience-ui/history-react/redux/reducers/index.ts -src/datascience-ui/history-react/redux/reducers/creation.ts -src/datascience-ui/history-react/redux/reducers/execution.ts -src/datascience-ui/history-react/redux/reducers/effects.ts -src/datascience-ui/history-react/redux/store.ts -src/datascience-ui/react-common/arePathsSame.ts -src/datascience-ui/react-common/imageButton.tsx -src/datascience-ui/react-common/monacoHelpers.ts -src/datascience-ui/react-common/svgViewer.tsx -src/datascience-ui/react-common/relativeImage.tsx -src/datascience-ui/react-common/progress.tsx -src/datascience-ui/react-common/styleInjector.tsx -src/datascience-ui/react-common/reduxUtils.ts -src/datascience-ui/react-common/monacoEditor.tsx -src/datascience-ui/react-common/textMeasure.ts -src/datascience-ui/react-common/flyout.tsx -src/datascience-ui/react-common/logger.ts -src/datascience-ui/react-common/svgList.tsx -src/datascience-ui/react-common/constants.ts -src/datascience-ui/react-common/settingsReactSide.ts -src/datascience-ui/react-common/locReactSide.ts -src/datascience-ui/react-common/button.tsx -src/datascience-ui/react-common/themeDetector.ts -src/datascience-ui/react-common/image.tsx -src/datascience-ui/react-common/event.ts -src/datascience-ui/react-common/codicon/codicon.ts -src/datascience-ui/react-common/postOffice.ts -src/datascience-ui/react-common/errorBoundary.tsx -src/datascience-ui/common/main.ts -src/datascience-ui/common/cellFactory.ts -src/datascience-ui/common/index.ts -src/datascience-ui/startPage/index.tsx -src/datascience-ui/startPage/startPage.tsx -src/datascience-ui/data-explorer/index.tsx -src/datascience-ui/data-explorer/cellFormatter.tsx -src/datascience-ui/data-explorer/globalJQueryImports.ts -src/datascience-ui/data-explorer/emptyRowsView.tsx -src/datascience-ui/data-explorer/progressBar.tsx -src/datascience-ui/data-explorer/testData.ts -src/datascience-ui/data-explorer/reactSlickGridFilterBox.tsx -src/datascience-ui/data-explorer/mainPanel.tsx -src/datascience-ui/data-explorer/reactSlickGrid.tsx - -src/client/interpreter/interpreterService.ts -src/client/interpreter/configuration/interpreterComparer.ts -src/client/interpreter/configuration/interpreterSelector/commands/base.ts -src/client/interpreter/configuration/interpreterSelector/commands/setInterpreter.ts -src/client/interpreter/configuration/interpreterSelector/commands/resetInterpreter.ts -src/client/interpreter/configuration/interpreterSelector/commands/setShebangInterpreter.ts -src/client/interpreter/configuration/interpreterSelector/interpreterSelector.ts -src/client/interpreter/configuration/pythonPathUpdaterService.ts -src/client/interpreter/configuration/pythonPathUpdaterServiceFactory.ts -src/client/interpreter/configuration/types.ts -src/client/interpreter/configuration/services/globalUpdaterService.ts -src/client/interpreter/configuration/services/workspaceUpdaterService.ts -src/client/interpreter/configuration/services/workspaceFolderUpdaterService.ts -src/client/interpreter/serviceRegistry.ts -src/client/interpreter/helpers.ts -src/client/interpreter/virtualEnvs/condaInheritEnvPrompt.ts -src/client/interpreter/virtualEnvs/types.ts -src/client/interpreter/virtualEnvs/virtualEnvPrompt.ts -src/client/interpreter/virtualEnvs/index.ts -src/client/interpreter/autoSelection/interpreterSecurity/interpreterSecurityStorage.ts -src/client/interpreter/autoSelection/interpreterSecurity/interpreterEvaluation.ts -src/client/interpreter/autoSelection/interpreterSecurity/interpreterSecurityService.ts -src/client/interpreter/autoSelection/types.ts -src/client/interpreter/autoSelection/constants.ts -src/client/interpreter/autoSelection/proxy.ts -src/client/interpreter/autoSelection/rules/baseRule.ts -src/client/interpreter/autoSelection/rules/winRegistry.ts -src/client/interpreter/autoSelection/rules/settings.ts -src/client/interpreter/autoSelection/rules/currentPath.ts -src/client/interpreter/autoSelection/rules/cached.ts -src/client/interpreter/autoSelection/rules/workspaceEnv.ts -src/client/interpreter/autoSelection/rules/system.ts -src/client/interpreter/autoSelection/index.ts -src/client/interpreter/interpreterVersion.ts -src/client/interpreter/contracts.ts -src/client/interpreter/activation/wrapperEnvironmentActivationService.ts -src/client/interpreter/activation/terminalEnvironmentActivationService.ts -src/client/interpreter/activation/preWarmVariables.ts -src/client/interpreter/activation/types.ts -src/client/interpreter/activation/service.ts -src/client/interpreter/locators/types.ts -src/client/interpreter/display/shebangCodeLensProvider.ts -src/client/interpreter/display/index.ts -src/client/interpreter/display/progressDisplay.ts -src/client/interpreter/display/interpreterSelectionTip.ts - -src/client/api.ts -src/client/constants.ts -src/client/extension.ts -src/client/extensionActivation.ts -src/client/extensionInit.ts -src/client/sourceMapSupport.ts -src/client/startupTelemetry.ts - -src/client/typeFormatters/blockFormatProvider.ts -src/client/typeFormatters/contracts.ts -src/client/typeFormatters/codeBlockFormatProvider.ts -src/client/typeFormatters/onEnterFormatter.ts -src/client/typeFormatters/dispatcher.ts - -src/client/terminals/serviceRegistry.ts -src/client/terminals/activation.ts -src/client/terminals/types.ts -src/client/terminals/codeExecution/helper.ts -src/client/terminals/codeExecution/djangoShellCodeExecution.ts -src/client/terminals/codeExecution/repl.ts -src/client/terminals/codeExecution/terminalCodeExecution.ts -src/client/terminals/codeExecution/codeExecutionManager.ts -src/client/terminals/codeExecution/djangoContext.ts - -src/client/providers/objectDefinitionProvider.ts -src/client/providers/serviceRegistry.ts -src/client/providers/symbolProvider.ts -src/client/providers/completionSource.ts -src/client/providers/renameProvider.ts -src/client/providers/hoverProvider.ts -src/client/providers/itemInfoSource.ts -src/client/providers/formatProvider.ts -src/client/providers/importSortProvider.ts -src/client/providers/replProvider.ts -src/client/providers/codeActionProvider/main.ts -src/client/providers/codeActionProvider/launchJsonCodeActionProvider.ts -src/client/providers/codeActionProvider/pythonCodeActionProvider.ts -src/client/providers/types.ts -src/client/providers/docStringFoldingProvider.ts -src/client/providers/linterProvider.ts -src/client/providers/providerUtilities.ts -src/client/providers/simpleRefactorProvider.ts -src/client/providers/completionProvider.ts -src/client/providers/jediProxy.ts -src/client/providers/definitionProvider.ts -src/client/providers/referenceProvider.ts -src/client/providers/terminalProvider.ts -src/client/providers/signatureProvider.ts - -src/client/activation/serviceRegistry.ts -src/client/activation/languageServer/manager.ts -src/client/activation/languageServer/languageServerExtension.ts -src/client/activation/languageServer/languageServerProxy.ts -src/client/activation/languageServer/languageClientFactory.ts -src/client/activation/languageServer/platformData.ts -src/client/activation/languageServer/languageServerCompatibilityService.ts -src/client/activation/languageServer/languageServerPackageRepository.ts -src/client/activation/languageServer/languageServerFolderService.ts -src/client/activation/languageServer/outputChannel.ts -src/client/activation/languageServer/languageServerPackageService.ts -src/client/activation/languageServer/analysisOptions.ts -src/client/activation/languageServer/activator.ts -src/client/activation/commands.ts -src/client/activation/activationManager.ts -src/client/activation/progress.ts -src/client/activation/extensionSurvey.ts -src/client/activation/types.ts -src/client/activation/common/languageServerChangeHandler.ts -src/client/activation/common/activatorBase.ts -src/client/activation/common/languageServerFolderService.ts -src/client/activation/common/languageServerPackageService.ts -src/client/activation/common/downloader.ts -src/client/activation/common/packageRepository.ts -src/client/activation/common/analysisOptions.ts -src/client/activation/common/downloadChannelRules.ts -src/client/activation/aaTesting.ts -src/client/activation/refCountedLanguageServer.ts -src/client/activation/jedi.ts -src/client/activation/languageClientMiddleware.ts -src/client/activation/activationService.ts -src/client/activation/node/manager.ts -src/client/activation/node/cancellationUtils.ts -src/client/activation/node/languageServerProxy.ts -src/client/activation/node/languageClientFactory.ts -src/client/activation/node/languageServerPackageRepository.ts -src/client/activation/node/languageServerFolderService.ts -src/client/activation/node/languageServerPackageService.ts -src/client/activation/node/analysisOptions.ts -src/client/activation/node/activator.ts -src/client/activation/none/activator.ts - -src/client/formatters/blackFormatter.ts -src/client/formatters/serviceRegistry.ts -src/client/formatters/helper.ts -src/client/formatters/dummyFormatter.ts -src/client/formatters/autoPep8Formatter.ts -src/client/formatters/lineFormatter.ts -src/client/formatters/types.ts -src/client/formatters/yapfFormatter.ts -src/client/formatters/baseFormatter.ts - -src/client/language/languageConfiguration.ts -src/client/language/characters.ts -src/client/language/textRangeCollection.ts -src/client/language/tokenizer.ts -src/client/language/characterStream.ts -src/client/language/textIterator.ts -src/client/language/types.ts -src/client/language/iterableTextRange.ts -src/client/language/braceCounter.ts -src/client/language/unicode.ts -src/client/language/textBuilder.ts - -src/client/testing/serviceRegistry.ts -src/client/testing/unittest/main.ts -src/client/testing/unittest/helper.ts -src/client/testing/unittest/testConfigurationManager.ts -src/client/testing/unittest/socketServer.ts -src/client/testing/unittest/runner.ts -src/client/testing/unittest/services/parserService.ts -src/client/testing/unittest/services/argsService.ts -src/client/testing/unittest/services/discoveryService.ts -src/client/testing/codeLenses/main.ts -src/client/testing/codeLenses/testFiles.ts -src/client/testing/nosetest/main.ts -src/client/testing/nosetest/testConfigurationManager.ts -src/client/testing/nosetest/runner.ts -src/client/testing/nosetest/services/parserService.ts -src/client/testing/nosetest/services/argsService.ts -src/client/testing/nosetest/services/discoveryService.ts -src/client/testing/main.ts -src/client/testing/pytest/main.ts -src/client/testing/pytest/testConfigurationManager.ts -src/client/testing/pytest/runner.ts -src/client/testing/pytest/services/argsService.ts -src/client/testing/pytest/services/discoveryService.ts -src/client/testing/pytest/services/testMessageService.ts -src/client/testing/configurationFactory.ts -src/client/testing/navigation/serviceRegistry.ts -src/client/testing/navigation/symbolProvider.ts -src/client/testing/navigation/helper.ts -src/client/testing/navigation/commandHandler.ts -src/client/testing/navigation/suiteNavigator.ts -src/client/testing/navigation/types.ts -src/client/testing/navigation/functionNavigator.ts -src/client/testing/navigation/fileNavigator.ts -src/client/testing/explorer/testTreeViewItem.ts -src/client/testing/explorer/testTreeViewProvider.ts -src/client/testing/explorer/commandHandlers.ts -src/client/testing/explorer/failedTestHandler.ts -src/client/testing/explorer/treeView.ts -src/client/testing/types.ts -src/client/testing/common/argumentsHelper.ts -src/client/testing/common/enablementTracker.ts -src/client/testing/common/debugLauncher.ts -src/client/testing/common/managers/testConfigurationManager.ts -src/client/testing/common/managers/baseTestManager.ts -src/client/testing/common/types.ts -src/client/testing/common/constants.ts -src/client/testing/common/testUtils.ts -src/client/testing/common/xUnitParser.ts -src/client/testing/common/updateTestSettings.ts -src/client/testing/common/testVisitors/visitor.ts -src/client/testing/common/testVisitors/flatteningVisitor.ts -src/client/testing/common/testVisitors/resultResetVisitor.ts -src/client/testing/common/runner.ts -src/client/testing/common/services/discoveredTestParser.ts -src/client/testing/common/services/contextService.ts -src/client/testing/common/services/testResultsService.ts -src/client/testing/common/services/storageService.ts -src/client/testing/common/services/types.ts -src/client/testing/common/services/unitTestDiagnosticService.ts -src/client/testing/common/services/testsStatusService.ts -src/client/testing/common/services/discovery.ts -src/client/testing/common/services/configSettingService.ts -src/client/testing/common/services/testManagerService.ts -src/client/testing/common/services/workspaceTestManagerService.ts -src/client/testing/display/main.ts -src/client/testing/display/picker.ts -src/client/testing/configuration.ts - -src/client/common/configuration/service.ts -src/client/common/serviceRegistry.ts -src/client/common/helpers.ts -src/client/common/net/browser.ts -src/client/common/net/fileDownloader.ts -src/client/common/net/httpClient.ts -src/client/common/net/socket/socketCallbackHandler.ts -src/client/common/net/socket/socketServer.ts -src/client/common/net/socket/SocketStream.ts -src/client/common/asyncDisposableRegistry.ts -src/client/common/editor.ts -src/client/common/contextKey.ts -src/client/common/markdown/restTextConverter.ts -src/client/common/featureDeprecationManager.ts -src/client/common/experiments/manager.ts -src/client/common/experiments/groups.ts -src/client/common/experiments/telemetry.ts -src/client/common/experiments/service.ts -src/client/common/refBool.ts -src/client/common/open.ts -src/client/common/platform/serviceRegistry.ts -src/client/common/platform/errors.ts -src/client/common/platform/fs-temp.ts -src/client/common/platform/fs-paths.ts -src/client/common/platform/platformService.ts -src/client/common/platform/types.ts -src/client/common/platform/constants.ts -src/client/common/platform/fileSystem.ts -src/client/common/platform/registry.ts -src/client/common/platform/pathUtils.ts -src/client/common/persistentState.ts -src/client/common/terminal/activator/base.ts -src/client/common/terminal/activator/powershellFailedHandler.ts -src/client/common/terminal/activator/index.ts -src/client/common/terminal/helper.ts -src/client/common/terminal/syncTerminalService.ts -src/client/common/terminal/factory.ts -src/client/common/terminal/types.ts -src/client/common/terminal/commandPrompt.ts -src/client/common/terminal/service.ts -src/client/common/terminal/shellDetector.ts -src/client/common/terminal/shellDetectors/userEnvironmentShellDetector.ts -src/client/common/terminal/shellDetectors/vscEnvironmentShellDetector.ts -src/client/common/terminal/shellDetectors/terminalNameShellDetector.ts -src/client/common/terminal/shellDetectors/settingsShellDetector.ts -src/client/common/terminal/shellDetectors/baseShellDetector.ts -src/client/common/terminal/environmentActivationProviders/pipEnvActivationProvider.ts -src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts -src/client/common/terminal/environmentActivationProviders/condaActivationProvider.ts -src/client/common/terminal/environmentActivationProviders/commandPrompt.ts -src/client/common/terminal/environmentActivationProviders/bash.ts -src/client/common/terminal/environmentActivationProviders/pyenvActivationProvider.ts -src/client/common/utils/decorators.ts -src/client/common/utils/enum.ts -src/client/common/utils/async.ts -src/client/common/utils/text.ts -src/client/common/utils/localize.ts -src/client/common/utils/regexp.ts -src/client/common/utils/platform.ts -src/client/common/utils/multiStepInput.ts -src/client/common/utils/stopWatch.ts -src/client/common/utils/random.ts -src/client/common/utils/serializers.ts -src/client/common/utils/icons.ts -src/client/common/utils/sysTypes.ts -src/client/common/utils/version.ts -src/client/common/utils/misc.ts -src/client/common/utils/logging.ts -src/client/common/utils/cacheUtils.ts -src/client/common/utils/workerPool.ts -src/client/common/crypto.ts -src/client/common/extensions.ts -src/client/common/dotnet/compatibilityService.ts -src/client/common/dotnet/serviceRegistry.ts -src/client/common/dotnet/types.ts -src/client/common/dotnet/services/unknownOsCompatibilityService.ts -src/client/common/dotnet/services/macCompatibilityService.ts -src/client/common/dotnet/services/linuxCompatibilityService.ts -src/client/common/dotnet/services/windowsCompatibilityService.ts -src/client/common/types.ts -src/client/common/logger.ts -src/client/common/configSettings.ts -src/client/common/constants.ts -src/client/common/variables/serviceRegistry.ts -src/client/common/variables/environment.ts -src/client/common/variables/types.ts -src/client/common/variables/environmentVariablesProvider.ts -src/client/common/variables/sysTypes.ts -src/client/common/variables/systemVariables.ts -src/client/common/nuget/azureBlobStoreNugetRepository.ts -src/client/common/nuget/nugetRepository.ts -src/client/common/nuget/types.ts -src/client/common/nuget/nugetService.ts -src/client/common/cancellation.ts -src/client/common/interpreterPathService.ts -src/client/common/startPage/startPage.ts -src/client/common/startPage/types.ts -src/client/common/startPage/startPageMessageListener.ts -src/client/common/application/customEditorService.ts -src/client/common/application/commands.ts -src/client/common/application/applicationShell.ts -src/client/common/application/languageService.ts -src/client/common/application/notebook.ts -src/client/common/application/clipboard.ts -src/client/common/application/workspace.ts -src/client/common/application/debugSessionTelemetry.ts -src/client/common/application/extensions.ts -src/client/common/application/types.ts -src/client/common/application/activeResource.ts -src/client/common/application/commandManager.ts -src/client/common/application/documentManager.ts -src/client/common/application/webPanels/webPanelProvider.ts -src/client/common/application/webPanels/webPanel.ts -src/client/common/application/debugService.ts -src/client/common/application/commands/reloadCommand.ts -src/client/common/application/terminalManager.ts -src/client/common/application/applicationEnvironment.ts -src/client/common/errors/errorUtils.ts -src/client/common/errors/moduleNotInstalledError.ts -src/client/common/installer/serviceRegistry.ts -src/client/common/installer/productNames.ts -src/client/common/installer/condaInstaller.ts -src/client/common/installer/extensionBuildInstaller.ts -src/client/common/installer/productInstaller.ts -src/client/common/installer/channelManager.ts -src/client/common/installer/moduleInstaller.ts -src/client/common/installer/types.ts -src/client/common/installer/poetryInstaller.ts -src/client/common/installer/pipEnvInstaller.ts -src/client/common/installer/productService.ts -src/client/common/installer/pipInstaller.ts -src/client/common/installer/productPath.ts -src/client/common/process/currentProcess.ts -src/client/common/process/processFactory.ts -src/client/common/process/serviceRegistry.ts -src/client/common/process/pythonDaemon.ts -src/client/common/process/pythonToolService.ts -src/client/common/process/internal/python.ts -src/client/common/process/internal/scripts/testing_tools.ts -src/client/common/process/internal/scripts/vscode_datascience_helpers.ts -src/client/common/process/internal/scripts/index.ts -src/client/common/process/pythonDaemonPool.ts -src/client/common/process/pythonDaemonFactory.ts -src/client/common/process/types.ts -src/client/common/process/logger.ts -src/client/common/process/baseDaemon.ts -src/client/common/process/constants.ts -src/client/common/process/pythonProcess.ts -src/client/common/process/proc.ts -src/client/common/process/pythonEnvironment.ts -src/client/common/process/decoder.ts -src/client/common/process/pythonExecutionFactory.ts -src/client/common/insidersBuild/insidersExtensionPrompt.ts -src/client/common/insidersBuild/insidersExtensionService.ts -src/client/common/insidersBuild/types.ts -src/client/common/insidersBuild/downloadChannelService.ts -src/client/common/insidersBuild/downloadChannelRules.ts - -src/client/debugger/extension/configuration/providers/moduleLaunch.ts -src/client/debugger/extension/configuration/providers/flaskLaunch.ts -src/client/debugger/extension/configuration/providers/fileLaunch.ts -src/client/debugger/extension/configuration/providers/remoteAttach.ts -src/client/debugger/extension/configuration/providers/djangoLaunch.ts -src/client/debugger/extension/configuration/providers/providerFactory.ts -src/client/debugger/extension/configuration/providers/pyramidLaunch.ts -src/client/debugger/extension/configuration/providers/pidAttach.ts -src/client/debugger/extension/configuration/resolvers/base.ts -src/client/debugger/extension/configuration/resolvers/helper.ts -src/client/debugger/extension/configuration/resolvers/launch.ts -src/client/debugger/extension/configuration/resolvers/attach.ts -src/client/debugger/extension/configuration/types.ts -src/client/debugger/extension/configuration/debugConfigurationService.ts -src/client/debugger/extension/configuration/launch.json/updaterService.ts -src/client/debugger/extension/configuration/launch.json/interpreterPathCommand.ts -src/client/debugger/extension/configuration/launch.json/completionProvider.ts -src/client/debugger/extension/banner.ts -src/client/debugger/extension/serviceRegistry.ts -src/client/debugger/extension/adapter/remoteLaunchers.ts -src/client/debugger/extension/adapter/outdatedDebuggerPrompt.ts -src/client/debugger/extension/adapter/factory.ts -src/client/debugger/extension/adapter/types.ts -src/client/debugger/extension/adapter/activator.ts -src/client/debugger/extension/adapter/logging.ts -src/client/debugger/extension/types.ts -src/client/debugger/extension/hooks/eventHandlerDispatcher.ts -src/client/debugger/extension/hooks/types.ts -src/client/debugger/extension/hooks/constants.ts -src/client/debugger/extension/hooks/childProcessAttachHandler.ts -src/client/debugger/extension/hooks/childProcessAttachService.ts -src/client/debugger/extension/attachQuickPick/wmicProcessParser.ts -src/client/debugger/extension/attachQuickPick/factory.ts -src/client/debugger/extension/attachQuickPick/types.ts -src/client/debugger/extension/attachQuickPick/psProcessParser.ts -src/client/debugger/extension/attachQuickPick/provider.ts -src/client/debugger/extension/attachQuickPick/picker.ts -src/client/debugger/extension/helpers/protocolParser.ts -src/client/debugger/types.ts -src/client/debugger/constants.ts - -src/client/languageServices/jediProxyFactory.ts -src/client/languageServices/proposeLanguageServerBanner.ts - -src/client/telemetry/types.ts -src/client/telemetry/importTracker.ts -src/client/telemetry/constants.ts -src/client/telemetry/index.ts -src/client/telemetry/envFileTelemetry.ts -src/client/telemetry/extensionInstallTelemetry.ts - -src/client/linters/pydocstyle.ts -src/client/linters/serviceRegistry.ts -src/client/linters/linterAvailability.ts -src/client/linters/lintingEngine.ts -src/client/linters/prospector.ts -src/client/linters/pycodestyle.ts -src/client/linters/linterInfo.ts -src/client/linters/bandit.ts -src/client/linters/linterCommands.ts -src/client/linters/flake8.ts -src/client/linters/errorHandlers/baseErrorHandler.ts -src/client/linters/errorHandlers/errorHandler.ts -src/client/linters/errorHandlers/notInstalled.ts -src/client/linters/errorHandlers/standard.ts -src/client/linters/types.ts -src/client/linters/mypy.ts -src/client/linters/baseLinter.ts -src/client/linters/constants.ts -src/client/linters/linterManager.ts -src/client/linters/pylama.ts -src/client/linters/pylint.ts - -src/client/application/serviceRegistry.ts -src/client/application/types.ts -src/client/application/diagnostics/surceMapSupportService.ts -src/client/application/diagnostics/base.ts -src/client/application/diagnostics/applicationDiagnostics.ts -src/client/application/diagnostics/serviceRegistry.ts -src/client/application/diagnostics/filter.ts -src/client/application/diagnostics/checks/upgradeCodeRunner.ts -src/client/application/diagnostics/checks/powerShellActivation.ts -src/client/application/diagnostics/checks/envPathVariable.ts -src/client/application/diagnostics/checks/invalidLaunchJsonDebugger.ts -src/client/application/diagnostics/checks/pythonPathDeprecated.ts -src/client/application/diagnostics/checks/lsNotSupported.ts -src/client/application/diagnostics/checks/macPythonInterpreter.ts -src/client/application/diagnostics/checks/invalidPythonPathInDebugger.ts -src/client/application/diagnostics/checks/pythonInterpreter.ts -src/client/application/diagnostics/promptHandler.ts -src/client/application/diagnostics/types.ts -src/client/application/diagnostics/constants.ts -src/client/application/diagnostics/commands/base.ts -src/client/application/diagnostics/commands/ignore.ts -src/client/application/diagnostics/commands/factory.ts -src/client/application/diagnostics/commands/execVSCCommand.ts -src/client/application/diagnostics/commands/types.ts -src/client/application/diagnostics/commands/launchBrowser.ts -src/client/application/misc/joinMailingListPrompt.ts - -src/client/datascience/baseJupyterSession.ts -src/client/datascience/data-viewing/jupyterVariableDataProviderFactory.ts -src/client/datascience/data-viewing/dataViewerMessageListener.ts -src/client/datascience/data-viewing/jupyterVariableDataProvider.ts -src/client/datascience/data-viewing/types.ts -src/client/datascience/data-viewing/dataViewer.ts -src/client/datascience/data-viewing/dataViewerDependencyService.ts -src/client/datascience/data-viewing/dataViewerFactory.ts -src/client/datascience/shiftEnterBanner.ts -src/client/datascience/dataScienceSurveyBanner.ts -src/client/datascience/serviceRegistry.ts -src/client/datascience/gather/gatherLogger.ts -src/client/datascience/gather/gatherListener.ts -src/client/datascience/webViewHost.ts -src/client/datascience/context/activeEditorContext.ts -src/client/datascience/progress/progressReporter.ts -src/client/datascience/progress/messages.ts -src/client/datascience/progress/types.ts -src/client/datascience/progress/decorator.ts -src/client/datascience/codeCssGenerator.ts -src/client/datascience/kernel-launcher/helpers.ts -src/client/datascience/kernel-launcher/kernelFinder.ts -src/client/datascience/kernel-launcher/kernelProcess.ts -src/client/datascience/kernel-launcher/types.ts -src/client/datascience/kernel-launcher/kernelDaemonPool.ts -src/client/datascience/kernel-launcher/kernelLauncherDaemon.ts -src/client/datascience/kernel-launcher/kernelDaemon.ts -src/client/datascience/kernel-launcher/kernelDaemonPreWarmer.ts -src/client/datascience/kernel-launcher/kernelLauncher.ts -src/client/datascience/ipywidgets/localWidgetScriptSourceProvider.ts -src/client/datascience/ipywidgets/ipyWidgetScriptSourceProvider.ts -src/client/datascience/ipywidgets/ipyWidgetScriptSource.ts -src/client/datascience/ipywidgets/cdnWidgetScriptSourceProvider.ts -src/client/datascience/ipywidgets/types.ts -src/client/datascience/ipywidgets/remoteWidgetScriptSourceProvider.ts -src/client/datascience/ipywidgets/constants.ts -src/client/datascience/ipywidgets/ipyWidgetMessageDispatcher.ts -src/client/datascience/ipywidgets/ipywidgetHandler.ts -src/client/datascience/ipywidgets/ipyWidgetMessageDispatcherFactory.ts -src/client/datascience/themeFinder.ts -src/client/datascience/multiplexingDebugService.ts -src/client/datascience/interactive-window/identity.ts -src/client/datascience/interactive-window/interactiveWindow.ts -src/client/datascience/interactive-window/interactiveWindowCommandListener.ts -src/client/datascience/interactive-window/interactiveWindowProvider.ts -src/client/datascience/datascience.ts -src/client/datascience/liveshare/liveshare.ts -src/client/datascience/liveshare/serviceProxy.ts -src/client/datascience/liveshare/liveshareProxy.ts -src/client/datascience/liveshare/postOffice.ts -src/client/datascience/jupyterUriProviderRegistration.ts -src/client/datascience/messages.ts -src/client/datascience/raw-kernel/rawJupyterSession.ts -src/client/datascience/raw-kernel/rawNotebookProvider.ts -src/client/datascience/raw-kernel/rawNotebookSupportedService.ts -src/client/datascience/raw-kernel/liveshare/guestRawNotebookProvider.ts -src/client/datascience/raw-kernel/liveshare/hostRawNotebookProvider.ts -src/client/datascience/raw-kernel/rawKernel.ts -src/client/datascience/raw-kernel/rawSession.ts -src/client/datascience/raw-kernel/rawSocket.ts -src/client/datascience/raw-kernel/rawNotebookProviderWrapper.ts -src/client/datascience/crossProcessLock.ts -src/client/datascience/debugLocationTrackerFactory.ts -src/client/datascience/preWarmVariables.ts -src/client/datascience/common.ts -src/client/datascience/kernelSocketWrapper.ts -src/client/datascience/jupyterDebugService.ts -src/client/datascience/utils.ts -src/client/datascience/interactive-common/serialization.ts -src/client/datascience/interactive-common/showPlotListener.ts -src/client/datascience/interactive-common/debugListener.ts -src/client/datascience/interactive-common/interactiveBase.ts -src/client/datascience/interactive-common/types.ts -src/client/datascience/interactive-common/linkProvider.ts -src/client/datascience/interactive-common/notebookUsageTracker.ts -src/client/datascience/interactive-common/interactiveWindowTypes.ts -src/client/datascience/interactive-common/synchronization.ts -src/client/datascience/interactive-common/notebookProvider.ts -src/client/datascience/interactive-common/interactiveWindowMessageListener.ts -src/client/datascience/interactive-common/intellisense/wordHelper.ts -src/client/datascience/interactive-common/intellisense/intellisenseDocument.ts -src/client/datascience/interactive-common/intellisense/intellisenseLine.ts -src/client/datascience/interactive-common/intellisense/conversion.ts -src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts -src/client/datascience/interactive-common/notebookServerProvider.ts -src/client/datascience/activation.ts -src/client/datascience/jupyterUriProviderWrapper.ts -src/client/datascience/types.ts -src/client/datascience/errorHandler/errorHandler.ts -src/client/datascience/cellMatcher.ts -src/client/datascience/notebookStorage/notebookModel.ts -src/client/datascience/notebookStorage/notebookModelEditEvent.ts -src/client/datascience/notebookStorage/notebookStorageProvider.ts -src/client/datascience/notebookStorage/nativeEditorStorage.ts -src/client/datascience/notebookStorage/factory.ts -src/client/datascience/notebookStorage/types.ts -src/client/datascience/notebookStorage/nativeEditorProvider.ts -src/client/datascience/notebookStorage/vscNotebookModel.ts -src/client/datascience/notebookStorage/baseModel.ts -src/client/datascience/debugLocationTracker.ts -src/client/datascience/plotting/plotViewerMessageListener.ts -src/client/datascience/plotting/types.ts -src/client/datascience/plotting/plotViewer.ts -src/client/datascience/plotting/plotViewerProvider.ts -src/client/datascience/constants.ts -src/client/datascience/monacoMessages.ts -src/client/datascience/interactive-ipynb/nativeEditorRunByLineListener.ts -src/client/datascience/interactive-ipynb/nativeEditorViewTracker.ts -src/client/datascience/interactive-ipynb/trustCommandHandler.ts -src/client/datascience/interactive-ipynb/nativeEditorOldWebView.ts -src/client/datascience/interactive-ipynb/nativeEditorSynchronizer.ts -src/client/datascience/interactive-ipynb/nativeEditor.ts -src/client/datascience/interactive-ipynb/trustService.ts -src/client/datascience/interactive-ipynb/digestStorage.ts -src/client/datascience/interactive-ipynb/nativeEditorProviderOld.ts -src/client/datascience/interactive-ipynb/nativeEditorCommandListener.ts -src/client/datascience/interactive-ipynb/autoSaveService.ts -src/client/datascience/editor-integration/hoverProvider.ts -src/client/datascience/editor-integration/codeLensFactory.ts -src/client/datascience/editor-integration/codewatcher.ts -src/client/datascience/editor-integration/decorator.ts -src/client/datascience/editor-integration/codelensprovider.ts -src/client/datascience/editor-integration/cellhashprovider.ts -src/client/datascience/commands/commandLineSelector.ts -src/client/datascience/commands/notebookCommands.ts -src/client/datascience/commands/exportCommands.ts -src/client/datascience/commands/commandRegistry.ts -src/client/datascience/commands/serverSelector.ts -src/client/datascience/cellFactory.ts -src/client/datascience/notebook/contentProvider.ts -src/client/datascience/notebook/notebookDisposeService.ts -src/client/datascience/notebook/serviceRegistry.ts -src/client/datascience/notebook/notebookEditor.ts -src/client/datascience/notebook/notebookEditorCompatibilitySupport.ts -src/client/datascience/notebook/kernelProvider.ts -src/client/datascience/notebook/integration.ts -src/client/datascience/notebook/types.ts -src/client/datascience/notebook/notebookEditorProvider.ts -src/client/datascience/notebook/constants.ts -src/client/datascience/notebook/notebookEditorProviderWrapper.ts -src/client/datascience/notebook/renderer.ts -src/client/datascience/notebook/rendererExtensionDownloader.ts -src/client/datascience/notebook/helpers/multiCancellationToken.ts -src/client/datascience/notebook/helpers/helpers.ts -src/client/datascience/notebook/helpers/executionHelpers.ts -src/client/datascience/notebook/survey.ts -src/client/datascience/notebook/rendererExtension.ts -src/client/datascience/export/exportToHTML.ts -src/client/datascience/export/exportToPython.ts -src/client/datascience/export/exportUtil.ts -src/client/datascience/export/exportManager.ts -src/client/datascience/export/types.ts -src/client/datascience/export/exportToPDF.ts -src/client/datascience/export/exportManagerFilePicker.ts -src/client/datascience/export/exportBase.ts -src/client/datascience/export/exportDependencyChecker.ts -src/client/datascience/export/exportFileOpener.ts -src/client/datascience/notebookAndInteractiveTracker.ts -src/client/datascience/statusProvider.ts -src/client/datascience/jupyter/interpreter/jupyterInterpreterSelectionCommand.ts -src/client/datascience/jupyter/interpreter/jupyterCommand.ts -src/client/datascience/jupyter/interpreter/jupyterInterpreterStateStore.ts -src/client/datascience/jupyter/interpreter/jupyterInterpreterDependencyService.ts -src/client/datascience/jupyter/interpreter/jupyterInterpreterOldCacheStateStore.ts -src/client/datascience/jupyter/interpreter/jupyterInterpreterSubCommandExecutionService.ts -src/client/datascience/jupyter/interpreter/jupyterInterpreterSelector.ts -src/client/datascience/jupyter/interpreter/jupyterInterpreterService.ts -src/client/datascience/jupyter/kernels/kernelSelector.ts -src/client/datascience/jupyter/kernels/jupyterKernelPromiseFailedError.ts -src/client/datascience/jupyter/kernels/helpers.ts -src/client/datascience/jupyter/kernels/kernelExecution.ts -src/client/datascience/jupyter/kernels/kernelSwitcher.ts -src/client/datascience/jupyter/kernels/kernelService.ts -src/client/datascience/jupyter/kernels/kernelProvider.ts -src/client/datascience/jupyter/kernels/types.ts -src/client/datascience/jupyter/kernels/kernelSelections.ts -src/client/datascience/jupyter/kernels/jupyterKernelSpec.ts -src/client/datascience/jupyter/kernels/kernelDependencyService.ts -src/client/datascience/jupyter/kernels/kernel.ts -src/client/datascience/jupyter/kernels/cellExecution.ts -src/client/datascience/jupyter/jupyterNotebook.ts -src/client/datascience/jupyter/jupyterExecutionFactory.ts -src/client/datascience/jupyter/jupyterSession.ts -src/client/datascience/jupyter/serverPreload.ts -src/client/datascience/jupyter/jupyterRequest.ts -src/client/datascience/jupyter/jupyterNotebookProvider.ts -src/client/datascience/jupyter/commandLineSelector.ts -src/client/datascience/jupyter/jupyterVariables.ts -src/client/datascience/jupyter/jupyterDebugger.ts -src/client/datascience/jupyter/liveshare/hostJupyterServer.ts -src/client/datascience/jupyter/liveshare/roleBasedFactory.ts -src/client/datascience/jupyter/liveshare/guestJupyterNotebook.ts -src/client/datascience/jupyter/liveshare/hostJupyterNotebook.ts -src/client/datascience/jupyter/liveshare/responseQueue.ts -src/client/datascience/jupyter/liveshare/hostJupyterExecution.ts -src/client/datascience/jupyter/liveshare/liveShareParticipantMixin.ts -src/client/datascience/jupyter/liveshare/utils.ts -src/client/datascience/jupyter/liveshare/guestJupyterSessionManager.ts -src/client/datascience/jupyter/liveshare/guestJupyterSessionManagerFactory.ts -src/client/datascience/jupyter/liveshare/types.ts -src/client/datascience/jupyter/liveshare/guestJupyterServer.ts -src/client/datascience/jupyter/liveshare/serverCache.ts -src/client/datascience/jupyter/liveshare/guestJupyterExecution.ts -src/client/datascience/jupyter/kernelVariables.ts -src/client/datascience/jupyter/jupyterSessionManagerFactory.ts -src/client/datascience/jupyter/jupyterDebuggerPortBlockedError.ts -src/client/datascience/jupyter/jupyterConnectError.ts -src/client/datascience/jupyter/jupyterExecution.ts -src/client/datascience/jupyter/debuggerVariableRegistration.ts -src/client/datascience/jupyter/jupyterDebuggerRemoteNotSupported.ts -src/client/datascience/jupyter/jupyterConnection.ts -src/client/datascience/jupyter/jupyterPasswordConnect.ts -src/client/datascience/jupyter/jupyterDebuggerNotInstalledError.ts -src/client/datascience/jupyter/jupyterSelfCertsError.ts -src/client/datascience/jupyter/jupyterDebuggerPortNotAvailableError.ts -src/client/datascience/jupyter/jupyterWebSocket.ts -src/client/datascience/jupyter/jupyterServer.ts -src/client/datascience/jupyter/jupyterInvalidKernelError.ts -src/client/datascience/jupyter/jupyterExporter.ts -src/client/datascience/jupyter/notebookStarter.ts -src/client/datascience/jupyter/jupyterZMQBinariesNotFoundError.ts -src/client/datascience/jupyter/serverSelector.ts -src/client/datascience/jupyter/jupyterUtils.ts -src/client/datascience/jupyter/jupyterSessionManager.ts -src/client/datascience/jupyter/jupyterDataRateLimitError.ts -src/client/datascience/jupyter/variableScriptLoader.ts -src/client/datascience/jupyter/jupyterServerWrapper.ts -src/client/datascience/jupyter/jupyterImporter.ts -src/client/datascience/jupyter/jupyterInstallError.ts -src/client/datascience/jupyter/oldJupyterVariables.ts -src/client/datascience/jupyter/jupyterInterruptError.ts -src/client/datascience/jupyter/invalidNotebookFileError.ts -src/client/datascience/jupyter/jupyterWaitForIdleError.ts -src/client/datascience/jupyter/jupyterCellOutputMimeTypeTracker.ts -src/client/datascience/jupyter/debuggerVariables.ts -src/client/datascience/dataScienceFileSystem.ts -src/client/logging/levels.ts -src/client/logging/transports.ts -src/client/logging/_global.ts -src/client/logging/logger.ts -src/client/logging/util.ts -src/client/logging/index.ts -src/client/logging/formatters.ts -src/client/logging/trace.ts -src/client/ioc/serviceManager.ts -src/client/ioc/container.ts -src/client/ioc/types.ts -src/client/ioc/index.ts -src/client/refactor/proxy.ts -src/client/workspaceSymbols/main.ts -src/client/workspaceSymbols/contracts.ts -src/client/workspaceSymbols/generator.ts -src/client/workspaceSymbols/parser.ts -src/client/workspaceSymbols/provider.ts - diff --git a/.eslintplugin/no-bad-gdpr-comment.js b/.eslintplugin/no-bad-gdpr-comment.js new file mode 100644 index 000000000000..786259683ff6 --- /dev/null +++ b/.eslintplugin/no-bad-gdpr-comment.js @@ -0,0 +1,51 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +var noBadGDPRComment = { + create: function (context) { + var _a; + return _a = {}, + _a['Program'] = function (node) { + for (var _i = 0, _a = node.comments; _i < _a.length; _i++) { + var comment = _a[_i]; + if (comment.type !== 'Block' || !comment.loc) { + continue; + } + if (!comment.value.includes('__GDPR__')) { + continue; + } + var dataStart = comment.value.indexOf('\n'); + var data = comment.value.substring(dataStart); + var gdprData = void 0; + try { + var jsonRaw = "{ ".concat(data, " }"); + gdprData = JSON.parse(jsonRaw); + } + catch (e) { + context.report({ + loc: { start: comment.loc.start, end: comment.loc.end }, + message: 'GDPR comment is not valid JSON', + }); + } + if (gdprData) { + var len = Object.keys(gdprData).length; + if (len !== 1) { + context.report({ + loc: { start: comment.loc.start, end: comment.loc.end }, + message: "GDPR comment must contain exactly one key, not ".concat(Object.keys(gdprData).join(', ')), + }); + } + } + } + }, + _a; + }, +}; +module.exports = { + rules: { + 'no-bad-gdpr-comment': noBadGDPRComment, // Ensure correct structure + }, +}; diff --git a/.eslintplugin/no-bad-gdpr-comment.ts b/.eslintplugin/no-bad-gdpr-comment.ts new file mode 100644 index 000000000000..1eba899a7de3 --- /dev/null +++ b/.eslintplugin/no-bad-gdpr-comment.ts @@ -0,0 +1,55 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as eslint from 'eslint'; +const noBadGDPRComment: eslint.Rule.RuleModule = { + create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { + return { + ['Program'](node) { + for (const comment of (node as eslint.AST.Program).comments) { + if (comment.type !== 'Block' || !comment.loc) { + continue; + } + if (!comment.value.includes('__GDPR__')) { + continue; + } + + const dataStart = comment.value.indexOf('\n'); + const data = comment.value.substring(dataStart); + + let gdprData: { [key: string]: object } | undefined; + + try { + const jsonRaw = `{ ${data} }`; + gdprData = JSON.parse(jsonRaw); + } catch (e) { + context.report({ + loc: { start: comment.loc.start, end: comment.loc.end }, + message: 'GDPR comment is not valid JSON', + }); + } + + if (gdprData) { + const len = Object.keys(gdprData).length; + if (len !== 1) { + context.report({ + loc: { start: comment.loc.start, end: comment.loc.end }, + message: `GDPR comment must contain exactly one key, not ${Object.keys(gdprData).join( + ', ', + )}`, + }); + } + } + } + }, + }; + }, +}; + +module.exports = { + rules: { + 'no-bad-gdpr-comment': noBadGDPRComment, // Ensure correct structure + }, +}; diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 75d90e364335..000000000000 --- a/.eslintrc +++ /dev/null @@ -1,120 +0,0 @@ -{ - "env": { - "node": true, - "es6": true, - "mocha": true - }, - "parser": "@typescript-eslint/parser", - "plugins": ["@typescript-eslint"], - "extends": [ - "airbnb", - "plugin:@typescript-eslint/recommended", - "plugin:import/errors", - "plugin:import/warnings", - "plugin:import/typescript" - ], - "rules": { - // Overriding ESLint rules with Typescript-specific ones - "@typescript-eslint/ban-ts-comment": [ - "error", - { - "ts-ignore": "allow-with-description" - } - ], - "no-bitwise": "off", - "no-dupe-class-members": "off", - "@typescript-eslint/no-dupe-class-members": "error", - "no-empty-function": "off", - "@typescript-eslint/no-empty-function": ["error"], - "@typescript-eslint/no-empty-interface": "off", - "@typescript-eslint/no-non-null-assertion": "off", - "no-unused-vars": "off", - "@typescript-eslint/no-unused-vars": "error", - "no-use-before-define": "off", - "@typescript-eslint/no-use-before-define": [ - "error", - { - "functions": false - } - ], - "no-useless-constructor": "off", - "@typescript-eslint/no-useless-constructor": "error", - "@typescript-eslint/no-var-requires": "off", - // Other rules - "func-names": "off", - "import/extensions": "off", - "import/namespace": "off", - "import/no-extraneous-dependencies": "off", - "import/no-unresolved": [ - "error", - { - "ignore": ["monaco-editor", "vscode"] - } - ], - "import/prefer-default-export": "off", - "indent": [ - "error", - 4, - { - "SwitchCase": 1 - } - ], - "linebreak-style": "off", - "max-len": [ - "warn", - { - "code": 120, - "ignorePattern": "^import\\s.+\\sfrom\\s.+;$", - "ignoreStrings": true, - "ignoreTemplateLiterals": true, - "ignoreUrls": true - } - ], - "no-await-in-loop": "off", - "no-confusing-arrow": [ - "error", - { - "allowParens": true - } - ], - "no-console": "off", - "no-control-regex": "off", - "no-extend-native": "off", - "no-multi-str": "off", - "no-param-reassign": "off", - "no-prototype-builtins": "off", - "no-restricted-syntax": [ - "error", - { - "selector": "ForInStatement", - "message": "for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array." - }, - - { - "selector": "LabeledStatement", - "message": "Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand." - }, - { - "selector": "WithStatement", - "message": "`with` is disallowed in strict mode because it makes code impossible to predict and optimize." - } - ], - "no-template-curly-in-string": "off", - "no-underscore-dangle": "off", - "no-useless-escape": "off", - "no-void": [ - "error", - { - "allowAsStatement": true - } - ], - "operator-assignment": "off", - "react/jsx-filename-extension": [ - 1, - { - "extensions": [".tsx"] - } - ], - "strict": "off" - } -} diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000000..e2c2a50781b9 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,15 @@ +# Prettier +2b6a8f2d439fe9d5e66665ea46d8b690ac9b2c39 +649156a09ccdc51c0d20f7cd44540f1918f9347b +4f774d94bf4fbf87bb417b2b2b8e79e334eb3536 +61b179b2092050709e3c373a6738abad8ce581c4 +c33617b0b98daeb4d72040b48c5850b476d6256c +db8e1e2460e9754ec0672d958789382b6d15c5aa +08bc9ad3bee5b19f02fa756fbc53ab32f1b39920 +# Black +a58eeffd1b64498e2afe5f11597888dfd1c8699c +5cd8f539f4d2086b718c8f11f823c0ac12fc2c49 +9ec9e9eaebb25adc6d942ac19d4d6c128abb987f +c4af91e090057d20d7a633b3afa45eaa13ece76f +# Ruff +e931bed3efbede7b05113316506958ecd7506777 diff --git a/.github/ISSUE_TEMPLATE/1_ds_bug_report.md b/.github/ISSUE_TEMPLATE/1_ds_bug_report.md deleted file mode 100644 index f9f2a490d336..000000000000 --- a/.github/ISSUE_TEMPLATE/1_ds_bug_report.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -name: Bug report for Notebook Editor, Interactive Window, Python Editor cells -about: Create a report to help us improve -labels: type-bug, data science ---- - -# Bug: Notebook Editor, Interactive Window, Editor cells - - - -## Steps to cause the bug to occur - -1. - -## Actual behavior - -## Expected behavior - - - -### Your Jupyter and/or Python environment - -_Please provide as much info as you readily know_ - -- **Jupyter server running:** Local | Remote | N/A -- **Extension version:** 20YY.MM.#####-xxx -- **VS Code version:** #.## -- **Setting python.jediEnabled:** true | false -- **Setting python.languageServer:** Jedi | Microsoft | None -- **Python and/or Anaconda version:** #.#.# -- **OS:** Windows | Mac | Linux (distro): -- **Virtual environment:** conda | venv | virtualenv | N/A | ... - -## Python Output - - - -Microsoft Data Science for VS Code Engineering Team: @rchiodo, @IanMatthewHuff, @DavidKutu, @DonJayamanne, @greazer, @joyceerhl diff --git a/.github/ISSUE_TEMPLATE/2_bug_report.md b/.github/ISSUE_TEMPLATE/2_bug_report.md deleted file mode 100644 index 63ebddf67b47..000000000000 --- a/.github/ISSUE_TEMPLATE/2_bug_report.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -name: General bug report -about: Create a report to help us improve -labels: classify, type-bug ---- - - - -## Environment data - -- VS Code version: XXX -- Extension version (available under the Extensions sidebar): XXX -- OS and version: XXX -- Python version (& distribution if applicable, e.g. Anaconda): XXX -- Type of virtual environment used (N/A | venv | virtualenv | conda | ...): XXX -- Relevant/affected Python packages and their versions: XXX -- Relevant/affected Python-related VS Code extensions and their versions: XXX -- Value of the `python.languageServer` setting: XXX - -[**NOTE**: If you suspect that your issue is related to the Microsoft Python Language Server (`python.languageServer: 'Microsoft'`), please download our new language server [Pylance](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance) from the VS Code marketplace to see if that fixes your issue] - -## Expected behaviour - -XXX - -## Actual behaviour - -XXX - -## Steps to reproduce: - -[**NOTE**: Self-contained, minimal reproducing code samples are **extremely** helpful and will expedite addressing your issue] - -1. XXX - - - -## Logs - -
- -Output for Python in the Output panel (ViewOutput, change the drop-down the upper-right of the Output panel to Python) - - -

- -``` -XXX -``` - -

-
diff --git a/.github/ISSUE_TEMPLATE/3_ds_feature_request.md b/.github/ISSUE_TEMPLATE/3_ds_feature_request.md deleted file mode 100644 index 71876c9f3aad..000000000000 --- a/.github/ISSUE_TEMPLATE/3_ds_feature_request.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: Feature request for Notebook Editor, Interactive Window, Editor cells -about: Suggest an idea for this project -labels: type-enhancement, data science ---- - -# Feature: Notebook Editor, Interactive Window, Python Editor cells - - - -## Description - -Microsoft Data Science for VS Code Engineering Team: @rchiodo, @IanMatthewHuff, @DavidKutu, @DonJayamanne, @greazer, @joyceerhl diff --git a/.github/ISSUE_TEMPLATE/3_feature_request.md b/.github/ISSUE_TEMPLATE/3_feature_request.md new file mode 100644 index 000000000000..d13a5e94e700 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/3_feature_request.md @@ -0,0 +1,7 @@ +--- +name: Feature request +about: Request for the Python extension, not supporting/sibling extensions +labels: classify, feature-request +--- + + diff --git a/.github/ISSUE_TEMPLATE/4_feature_request.md b/.github/ISSUE_TEMPLATE/4_feature_request.md deleted file mode 100644 index e98872ab0d10..000000000000 --- a/.github/ISSUE_TEMPLATE/4_feature_request.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -name: General feature request -about: Suggest an idea for this project -labels: classify, type-enhancement ---- - - - - diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index ef0e2aae348f..c966f6bde856 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,17 @@ blank_issues_enabled: false contact_links: - - name: Stack Overflow - url: https://stackoverflow.com/questions/tagged/visual-studio-code+python - about: Please ask questions here. + - name: 'Bug 🐜' + url: https://aka.ms/pvsc-bug + about: 'Use the `Python: Report Issue...` command (follow the link for instructions)' + - name: 'Pylance' + url: https://github.com/microsoft/pylance-release/issues + about: 'For issues relating to the Pylance language server extension' + - name: 'Jupyter' + url: https://github.com/microsoft/vscode-jupyter/issues + about: 'For issues relating to the Jupyter extension (including the interactive window)' + - name: 'Python Debugger' + url: https://github.com/microsoft/vscode-python-debugger/issues + about: 'For issues relating to the Python debugger' + - name: Help/Support + url: https://github.com/microsoft/vscode-python/discussions/categories/q-a + about: 'Having trouble with the extension? Need help getting something to work?' diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index c1b633b61d84..000000000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,17 +0,0 @@ -For # - - - -- [ ] Pull request represents a single change (i.e. not fixing disparate/unrelated things in a single PR). -- [ ] Title summarizes what is changing. -- [ ] Has a [news entry](https://github.com/Microsoft/vscode-python/tree/main/news) file (remember to thank yourself!). -- [ ] Appropriate comments and documentation strings in the code. -- [ ] Has sufficient logging. -- [ ] Has telemetry for enhancements. -- [ ] Unit tests & system/integration tests are added/updated. -- [ ] [Test plan](https://github.com/Microsoft/vscode-python/blob/main/.github/test_plan.md) is updated as appropriate. -- [ ] [`package-lock.json`](https://github.com/Microsoft/vscode-python/blob/main/package-lock.json) has been regenerated by running `npm install` (if dependencies have changed). -- [ ] The wiki is updated with any design decisions/details. diff --git a/.github/actions/build-vsix/action.yml b/.github/actions/build-vsix/action.yml index 787bb999022e..912ff2c34a74 100644 --- a/.github/actions/build-vsix/action.yml +++ b/.github/actions/build-vsix/action.yml @@ -1,34 +1,101 @@ name: 'Build VSIX' description: "Build the extension's VSIX" -outputs: - path: - description: 'Path to the VSIX' - value: 'ms-python-insiders.vsix' +inputs: + node_version: + description: 'Version of Node to install' + required: true + vsix_name: + description: 'Name to give the final VSIX' + required: true + artifact_name: + description: 'Name to give the artifact containing the VSIX' + required: true + cargo_target: + description: 'Cargo build target for the native build' + required: true + vsix_target: + description: 'vsix build target for the native build' + required: true runs: using: 'composite' steps: + - name: Install Node + uses: actions/setup-node@v6 + with: + node-version: ${{ inputs.node_version }} + cache: 'npm' + + - name: Rust Tool Chain setup + uses: dtolnay/rust-toolchain@stable + + # Jedi LS depends on dataclasses which is not in the stdlib in Python 3.7. + - name: Use Python 3.10 for JediLSP + uses: actions/setup-python@v6 + with: + python-version: '3.10' + cache: 'pip' + cache-dependency-path: | + requirements.txt + python_files/jedilsp_requirements/requirements.txt + + - name: Upgrade Pip + run: python -m pip install -U pip + shell: bash + # For faster/better builds of sdists. - - run: python -m pip install wheel + - name: Install build pre-requisite + run: python -m pip install wheel nox + shell: bash + + - name: Install Python Extension dependencies (jedi, etc.) + run: nox --session install_python_libs + shell: bash + + - name: Add Rustup target + run: rustup target add "${CARGO_TARGET}" shell: bash + env: + CARGO_TARGET: ${{ inputs.cargo_target }} - - run: python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade -r requirements.txt + - name: Build Native Binaries + run: nox --session native_build shell: bash + env: + CARGO_TARGET: ${{ inputs.cargo_target }} - - run: | - python -m pip --disable-pip-version-check install -r build/debugger-install-requirements.txt - python ./pythonFiles/install_debugpy.py + - name: Run npm ci + run: npm ci --prefer-offline shell: bash - - run: npm ci --prefer-offline + - name: Update optional extension dependencies + run: npm run addExtensionPackDependencies shell: bash - # Use the GITHUB_RUN_ID environment variable to update the build number. - # GITHUB_RUN_ID is a unique number for each run within a repository. - # This number does not change if you re-run the workflow run. - - run: npm run updateBuildNumber -- --buildNumber $GITHUB_RUN_ID + - name: Build Webpack + run: | + npx gulp clean + npx gulp prePublishBundle shell: bash - - run: npm run package + - name: Build VSIX + run: npx vsce package --target "${VSIX_TARGET}" --out ms-python-insiders.vsix --pre-release shell: bash + env: + VSIX_TARGET: ${{ inputs.vsix_target }} + + - name: Rename VSIX + # Move to a temp name in case the specified name happens to match the default name. + run: mv ms-python-insiders.vsix ms-python-temp.vsix && mv ms-python-temp.vsix "${VSIX_NAME}" + shell: bash + env: + VSIX_NAME: ${{ inputs.vsix_name }} + + - name: Upload VSIX + uses: actions/upload-artifact@v7 + with: + name: ${{ inputs.artifact_name }} + path: ${{ inputs.vsix_name }} + if-no-files-found: error + retention-days: 7 diff --git a/.github/actions/lint/action.yml b/.github/actions/lint/action.yml new file mode 100644 index 000000000000..0bd5a2d8e1e2 --- /dev/null +++ b/.github/actions/lint/action.yml @@ -0,0 +1,50 @@ +name: 'Lint' +description: 'Lint TypeScript and Python code' + +inputs: + node_version: + description: 'Version of Node to install' + required: true + +runs: + using: 'composite' + steps: + - name: Install Node + uses: actions/setup-node@v6 + with: + node-version: ${{ inputs.node_version }} + cache: 'npm' + + - name: Install Node dependencies + run: npm ci --prefer-offline + shell: bash + + - name: Run `gulp prePublishNonBundle` + run: npx gulp prePublishNonBundle + shell: bash + + - name: Check dependencies + run: npm run checkDependencies + shell: bash + + - name: Lint TypeScript code + run: npm run lint + shell: bash + + - name: Check TypeScript format + run: npm run format-check + shell: bash + + - name: Install Python + uses: actions/setup-python@v6 + with: + python-version: '3.x' + cache: 'pip' + + - name: Run Ruff + run: | + python -m pip install -U "ruff" + python -m ruff check . + python -m ruff format --check + working-directory: python_files + shell: bash diff --git a/.github/actions/smoke-tests/action.yml b/.github/actions/smoke-tests/action.yml new file mode 100644 index 000000000000..0531ef5d42a3 --- /dev/null +++ b/.github/actions/smoke-tests/action.yml @@ -0,0 +1,66 @@ +name: 'Smoke tests' +description: 'Run smoke tests' + +inputs: + node_version: + description: 'Version of Node to install' + required: true + artifact_name: + description: 'Name of the artifact containing the VSIX' + required: true + +runs: + using: 'composite' + steps: + - name: Install Node + uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.node_version }} + cache: 'npm' + + - name: Install Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + cache: 'pip' + cache-dependency-path: | + build/test-requirements.txt + requirements.txt + + - name: Install dependencies (npm ci) + run: npm ci --prefer-offline + shell: bash + + - name: Install Python requirements + uses: brettcannon/pip-secure-install@92f400e3191171c1858cc0e0d9ac6320173fdb0c # v1.0.0 + with: + options: '-t ./python_files/lib/python --implementation py' + + - name: pip install system test requirements + run: | + python -m pip install --upgrade -r build/test-requirements.txt + shell: bash + + # Bits from the VSIX are reused by smokeTest.ts to speed things up. + - name: Download VSIX + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.artifact_name }} + + - name: Prepare for smoke tests + run: npx tsc -p ./ + shell: bash + + - name: Set CI_PYTHON_PATH and CI_DISABLE_AUTO_SELECTION + run: | + echo "CI_PYTHON_PATH=python" >> $GITHUB_ENV + echo "CI_DISABLE_AUTO_SELECTION=1" >> $GITHUB_ENV + shell: bash + + - name: Run smoke tests + env: + DISPLAY: 10 + INSTALL_JUPYTER_EXTENSION: true + uses: GabrielBB/xvfb-action@b706e4e27b14669b486812790492dc50ca16b465 # v1.7 + with: + run: node --no-force-async-hooks-checks ./out/test/smokeTest.js diff --git a/.github/commands.json b/.github/commands.json new file mode 100644 index 000000000000..2fb6684a7ee6 --- /dev/null +++ b/.github/commands.json @@ -0,0 +1,157 @@ +[ + { + "type": "label", + "name": "*question", + "action": "close", + "reason": "not_planned", + "comment": "We closed this issue because it is a question about using the Python extension for VS Code rather than an issue or feature request. We recommend browsing resources such as our [Python documentation](https://code.visualstudio.com/docs/languages/python) and our [Discussions page](https://github.com/microsoft/vscode-python/discussions). You may also find help on [StackOverflow](https://stackoverflow.com/questions/tagged/vscode-python), where the community has already answered thousands of similar questions. \n\nHappy Coding!" + }, + { + "type": "label", + "name": "*dev-question", + "action": "close", + "reason": "not_planned", + "comment": "We have a great extension developer community over on [GitHub discussions](https://github.com/microsoft/vscode-discussions/discussions) and [Slack](https://vscode-dev-community.slack.com/) where extension authors help each other. This is a great place for you to ask questions and find support.\n\nHappy Coding!" + }, + { + "type": "label", + "name": "*extension-candidate", + "action": "close", + "reason": "not_planned", + "comment": "We try to keep the Python extension lean and we think the functionality you're asking for is great for a VS Code extension. You might be able to find one that suits you in the [VS Code Marketplace](https://aka.ms/vscodemarketplace) already. If not, in a few simple steps you can get started [writing your own extension](https://aka.ms/vscodewritingextensions) or leverage our [tool extension template](https://github.com/microsoft/vscode-python-tools-extension-template) to get started. In addition, check out the [vscode-python-environments](https://github.com/microsoft/vscode-python-environments) as this may be the right spot for your request. \n\nHappy Coding!" + }, + { + "type": "label", + "name": "*not-reproducible", + "action": "close", + "reason": "not_planned", + "comment": "We closed this issue because we are unable to reproduce the problem with the steps you describe. Chances are we've already fixed your problem in a recent version of the Python extension, so we recommend updating to the latest version and trying again. If you continue to experience this issue, please ask us to reopen the issue and provide us with more detail.\n\nHappy Coding!" + }, + { + "type": "label", + "name": "*out-of-scope", + "action": "close", + "reason": "not_planned", + "comment": "We closed this issue because we [don't plan to address it](https://github.com/microsoft/vscode-python/wiki/Issue-Management#criteria-for-closing-out-of-scope-feature-requests) in the foreseeable future. If you disagree and feel that this issue is crucial: we are happy to listen and to reconsider.\n\nIf you wonder what we are up to, please see our [roadmap](https://aka.ms/pythonvscoderoadmap) and [issue reporting guidelines]( https://github.com/microsoft/vscode-python/wiki/Issue-Management).\n\nThanks for your understanding, and happy coding!" + }, + { + "type": "label", + "name": "wont-fix", + "action": "close", + "reason": "not_planned", + "comment": "We closed this issue because we [don't plan to address it](https://github.com/microsoft/vscode/wiki/Issue-Grooming#wont-fix-bugs).\n\nThanks for your understanding, and happy coding!" + }, + { + "type": "label", + "name": "*caused-by-extension", + "action": "close", + "reason": "not_planned", + "comment": "This issue is caused by an extension, please file it with the repository (or contact) the extension has linked in its overview in VS Code or the [marketplace](https://aka.ms/vscodemarketplace) for VS Code. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting). If you don't know which extension is causing the problem, you can run `Help: Start extension bisect` from the command palette (F1) to help identify the problem extension.\n\nHappy Coding!" + }, + { + "type": "label", + "name": "*as-designed", + "action": "close", + "reason": "not_planned", + "comment": "The described behavior is how it is expected to work. If you disagree, please explain what is expected and what is not in more detail. See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!" + }, + { + "type": "label", + "name": "L10N", + "assign": [ + "csigs", + "TylerLeonhardt" + ] + }, + { + "type": "label", + "name": "*duplicate", + "action": "close", + "reason": "not_planned", + "comment": "Thanks for creating this issue! We figured it's covering the same as another one we already have. Thus, we closed this one as a duplicate. You can search for [similar existing issues](${duplicateQuery}). See also our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!" + }, + { + "type": "comment", + "name": "verified", + "allowUsers": [ + "@author" + ], + "action": "updateLabels", + "addLabel": "verified", + "removeLabel": "author-verification-requested", + "requireLabel": "author-verification-requested", + "disallowLabel": "unreleased" + }, + { + "type": "comment", + "name": "confirm", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "updateLabels", + "addLabel": "confirmed", + "removeLabel": "confirmation-pending" + }, + { + "type": "label", + "name": "*off-topic", + "action": "close", + "reason": "not_planned", + "comment": "Thanks for creating this issue. We think this issue is unactionable or unrelated to the goals of this project. Please follow our [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nHappy Coding!" + }, + { + "type": "comment", + "name": "gifPlease", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "comment", + "addLabel": "info-needed", + "comment": "Thanks for reporting this issue! Unfortunately, it's hard for us to understand what issue you're seeing. Please help us out by providing a screen recording showing exactly what isn't working as expected. While we can work with most standard formats, `.gif` files are preferred as they are displayed inline on GitHub. You may find https://gifcap.dev helpful as a browser-based gif recording tool.\n\nIf the issue depends on keyboard input, you can help us by enabling screencast mode for the recording (`Developer: Toggle Screencast Mode` in the command palette). Lastly, please attach this file via the GitHub web interface as emailed responses will strip files out from the issue.\n\nHappy coding!" + }, + { + "type": "label", + "name": "*workspace-trust-docs", + "action": "close", + "reason": "not_planned", + "comment": "This issue appears to be the result of the new workspace trust feature shipped in June 2021. This security-focused feature has major impact on the functionality of VS Code. Due to the volume of issues, we ask that you take some time to review our [comprehensive documentation](https://aka.ms/vscode-workspace-trust) on the feature. If your issue is still not resolved, please let us know." + }, + { + "type": "label", + "name": "~verification-steps-needed", + "action": "updateLabels", + "addLabel": "verification-steps-needed", + "removeLabel": "~verification-steps-needed", + "comment": "Friendly ping! Looks like this issue requires some further steps to be verified. Please provide us with the steps necessary to verify this issue." + }, + { + "type": "label", + "name": "~info-needed", + "action": "updateLabels", + "addLabel": "info-needed", + "removeLabel": "~info-needed", + "comment": "Thanks for creating this issue! We figured it's missing some basic information or in some other way doesn't follow our [issue reporting guidelines](https://aka.ms/pvsc-bug). Please take the time to review these and update the issue or even open a new one with the Report Issue command in VS Code (**Help > Report Issue**) to have all the right information collected for you.\n\nHappy Coding!" + }, + { + "type": "label", + "name": "~version-info-needed", + "action": "updateLabels", + "addLabel": "info-needed", + "removeLabel": "~version-info-needed", + "comment": "Thanks for creating this issue! We figured it's missing some basic information, such as a version number, or in some other way doesn't follow our issue reporting guidelines. Please take the time to review these and update the issue or even open a new one with the Report Issue command in VS Code (**Help > Report Issue**) to have all the right information collected for you.\n\nHappy Coding!" + }, + { + "type": "label", + "name": "~confirmation-needed", + "action": "updateLabels", + "addLabel": "info-needed", + "removeLabel": "~confirmation-needed", + "comment": "Please diagnose the root cause of the issue by running the command `F1 > Help: Troubleshoot Issue` and following the instructions. Once you have done that, please update the issue with the results.\n\nHappy Coding!" + } +] diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3b264d060014..14c8e18d475d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,27 +5,45 @@ updates: schedule: interval: daily labels: - - 'skip news' + - 'no-changelog' - # Not skipping the news for Python dependencies in case it's actually useful to communicate to users. + - package-ecosystem: 'github-actions' + directory: .github/actions/build-vsix + schedule: + interval: daily + labels: + - 'no-changelog' + + - package-ecosystem: 'github-actions' + directory: .github/actions/lint + schedule: + interval: daily + labels: + - 'no-changelog' + + - package-ecosystem: 'github-actions' + directory: .github/actions/smoke-test + schedule: + interval: daily + labels: + - 'no-changelog' + + # Not skipping the news for some Python dependencies in case it's actually useful to communicate to users. - package-ecosystem: 'pip' directory: / schedule: interval: daily ignore: + - dependency-name: prospector # Due to Python 2.7 and #14477. - dependency-name: pytest # Due to Python 2.7 and #13776. - dependency-name: py # Due to Python 2.7. - - - package-ecosystem: 'pip' - directory: /news - schedule: - interval: monthly + - dependency-name: jedi-language-server labels: - - 'skip news' + - 'no-changelog' # Activate when we feel ready to keep up with frequency. # - package-ecosystem: 'npm' # directory: / # schedule: # interval: daily # default_labels: - # - "skip news" + # - "no-changelog" diff --git a/.github/instructions/learning.instructions.md b/.github/instructions/learning.instructions.md new file mode 100644 index 000000000000..28b085f486ce --- /dev/null +++ b/.github/instructions/learning.instructions.md @@ -0,0 +1,34 @@ +--- +applyTo: '**' +description: This document describes how to deal with learnings that you make. (meta instruction) +--- + +This document describes how to deal with learnings that you make. +It is a meta-instruction file. + +Structure of learnings: + +- Each instruction file has a "Learnings" section. +- Each learning has a counter that indicates how often that learning was useful (initially 1). +- Each learning has a 1 sentence description of the learning that is clear and concise. + +Example: + +```markdown +## Learnings + +- Prefer `const` over `let` whenever possible (1) +- Avoid `any` type (3) +``` + +When the user tells you "learn!", you should: + +- extract a learning from the recent conversation + _ identify the problem that you created + _ identify why it was a problem + _ identify how you were told to fix it/how the user fixed it + _ generate only one learning (1 sentence) that helps to summarize the insight gained +- then, add the reflected learning to the "Learnings" section of the most appropriate instruction file + +Important: Whenever a learning was really useful, increase the counter!! +When a learning was not useful and just caused more problems, decrease the counter. diff --git a/.github/instructions/pytest-json-test-builder.instructions.md b/.github/instructions/pytest-json-test-builder.instructions.md new file mode 100644 index 000000000000..436bce0c9cd8 --- /dev/null +++ b/.github/instructions/pytest-json-test-builder.instructions.md @@ -0,0 +1,126 @@ +--- +applyTo: 'python_files/tests/pytestadapter/test_discovery.py' +description: 'A guide for adding new tests for pytest discovery and JSON formatting in the test_pytest_collect suite.' +--- + +# How to Add New Pytest Discovery Tests + +This guide explains how to add new tests for pytest discovery and JSON formatting in the `test_pytest_collect` suite. Follow these steps to ensure your tests are consistent and correct. + +--- + +## 1. Add Your Test File + +- Place your new test file/files in the appropriate subfolder under: + ``` + python_files/tests/pytestadapter/.data/ + ``` +- Organize folders and files to match the structure you want to test. For example, to test nested folders, create the corresponding directory structure. +- In your test file, mark each test function with a comment: + ```python + def test_function(): # test_marker--test_function + ... + ``` + +**Root Node Matching:** + +- The root node in your expected output must match the folder or file you pass to pytest discovery. For example, if you run discovery on a subfolder, the root `"name"`, `"path"`, and `"id_"` in your expected output should be that subfolder, not the parent `.data` folder. +- Only use `.data` as the root if you are running discovery on the entire `.data` folder. + +**Example:** +If you run: + +```python +helpers.runner([os.fspath(TEST_DATA_PATH / "myfolder"), "--collect-only"]) +``` + +then your expected output root should be: + +```python +{ + "name": "myfolder", + "path": os.fspath(TEST_DATA_PATH / "myfolder"), + "type_": "folder", + ... +} +``` + +--- + +## 2. Update `expected_discovery_test_output.py` + +- Open `expected_discovery_test_output.py` in the same test suite. +- Add a new expected output dictionary for your test file, following the format of existing entries. +- Use the helper functions and path conventions: + - Use `os.fspath()` for all paths. + - Use `find_test_line_number("function_name", file_path)` for the `lineno` field. + - Use `get_absolute_test_id("relative_path::function_name", file_path)` for `id_` and `runID`. + - Always use current path concatenation (e.g., `TEST_DATA_PATH / "your_folder" / "your_file.py"`). + - Create new constants as needed to keep the code clean and maintainable. + +**Important:** + +- Do **not** read the entire `expected_discovery_test_output.py` file if you only need to add or reference a single constant. This file is very large; prefer searching for the relevant section or appending to the end. + +**Example:** +If you run discovery on a subfolder: + +```python +helpers.runner([os.fspath(TEST_DATA_PATH / "myfolder"), "--collect-only"]) +``` + +then your expected output root should be: + +```python +myfolder_path = TEST_DATA_PATH / "myfolder" +my_expected_output = { + "name": "myfolder", + "path": os.fspath(myfolder_path), + "type_": "folder", + ... +} +``` + +- Add a comment above your dictionary describing the structure, as in the existing examples. + +--- + +## 3. Add Your Test to `test_discovery.py` + +- In `test_discovery.py`, add your new test as a parameterized case to the main `test_pytest_collect` function. Do **not** create a standalone test function for new discovery cases. +- Reference your new expected output constant from `expected_discovery_test_output.py`. + +**Example:** + +```python +@pytest.mark.parametrize( + ("file", "expected_const"), + [ + ("myfolder", my_expected_output), + # ... other cases ... + ], +) +def test_pytest_collect(file, expected_const): + ... +``` + +--- + +## 4. Run and Verify + +- Run the test suite to ensure your new test is discovered and passes. +- If the test fails, check your expected output dictionary for path or structure mismatches. + +--- + +## 5. Tips + +- Always use the helper functions for line numbers and IDs. +- Match the folder/file structure in `.data` to the expected JSON structure. +- Use comments to document the expected output structure for clarity. +- Ensure all `"path"` and `"id_"` fields in your expected output match exactly what pytest returns, including absolute paths and root node structure. + +--- + +**Reference:** +See `expected_discovery_test_output.py` for more examples and formatting. Use search or jump to the end of the file to avoid reading the entire file when possible. diff --git a/.github/instructions/python-quality-checks.instructions.md b/.github/instructions/python-quality-checks.instructions.md new file mode 100644 index 000000000000..48f37529dfbc --- /dev/null +++ b/.github/instructions/python-quality-checks.instructions.md @@ -0,0 +1,97 @@ +--- +applyTo: 'python_files/**' +description: Guide for running and fixing Python quality checks (Ruff and Pyright) that run in CI +--- + +# Python Quality Checks — Ruff and Pyright + +Run the same Python quality checks that run in CI. All checks target `python_files/` and use config from `python_files/pyproject.toml`. + +## Commands + +```bash +npm run check-python # Run both Ruff and Pyright +npm run check-python:ruff # Linting and formatting only +npm run check-python:pyright # Type checking only +``` + +## Fixing Ruff Errors + +**Auto-fix most issues:** + +```bash +cd python_files +python -m ruff check . --fix +python -m ruff format +npm run check-python:ruff # Verify +``` + +**Manual fixes:** + +- Ruff shows file, line number, rule code (e.g., `F841`), and description +- Open the file, read the error, fix the code +- Common: line length (100 char max), import sorting, unused variables + +## Fixing Pyright Errors + +**Common patterns and fixes:** + +- **Undefined variable/import**: Add the missing import +- **Type mismatch**: Correct the type or add type annotations +- **Missing return type**: Add `-> ReturnType` to function signatures + ```python + def my_function() -> str: # Add return type + return "result" + ``` + +**Verify:** + +```bash +npm run check-python:pyright +``` + +## Configuration + +- **Ruff**: Line length 100, Python 3.9+, 40+ rule families (flake8, isort, pyupgrade, etc.) +- **Pyright**: Version 1.1.308 (or whatever is found in the environment), ignores `lib/` and 15+ legacy files +- Config: `python_files/pyproject.toml` sections `[tool.ruff]` and `[tool.pyright]` + +## Troubleshooting + +**"Module not found" in Pyright**: Install dependencies + +```bash +python -m pip install --upgrade -r build/test-requirements.txt +nox --session install_python_libs +``` + +**Import order errors**: Auto-fix with `ruff check . --fix` + +**Type errors in ignored files**: Legacy files in `pyproject.toml` ignore list—fix if working on them + +## When Writing Tests + +**Always format your test files before committing:** + +```bash +cd python_files +ruff format tests/ # Format all test files +# or format specific files: +ruff format tests/unittestadapter/test_utils.py +``` + +**Best practice workflow:** + +1. Write your test code +2. Run `ruff format` on the test files +3. Run the tests to verify they pass +4. Run `npm run check-python` to catch any remaining issues + +This ensures your tests pass both functional checks and quality checks in CI. + +## Learnings + +- Always run `npm run check-python` before pushing to catch CI failures early (1) +- Use `ruff check . --fix` to auto-fix most linting issues before manual review (1) +- Pyright version must match CI (1.1.308) to avoid inconsistent results between local and CI runs (1) +- Always run `ruff format` on test files after writing them to avoid formatting CI failures (1) diff --git a/.github/instructions/testing-workflow.instructions.md b/.github/instructions/testing-workflow.instructions.md new file mode 100644 index 000000000000..844946404328 --- /dev/null +++ b/.github/instructions/testing-workflow.instructions.md @@ -0,0 +1,581 @@ +--- +applyTo: '**/test/**' +--- + +# AI Testing Workflow Guide: Write, Run, and Fix Tests + +This guide provides comprehensive instructions for AI agents on the complete testing workflow: writing tests, running them, diagnosing failures, and fixing issues. Use this guide whenever working with test files or when users request testing tasks. + +## Complete Testing Workflow + +This guide covers the full testing lifecycle: + +1. **📝 Writing Tests** - Create comprehensive test suites +2. **▶️ Running Tests** - Execute tests using VS Code tools +3. **🔍 Diagnosing Issues** - Analyze failures and errors +4. **🛠️ Fixing Problems** - Resolve compilation and runtime issues +5. **✅ Validation** - Ensure coverage and resilience + +### When to Use This Guide + +**User Requests Testing:** + +- "Write tests for this function" +- "Run the tests" +- "Fix the failing tests" +- "Test this code" +- "Add test coverage" + +**File Context Triggers:** + +- Working in `**/test/**` directories +- Files ending in `.test.ts` or `.unit.test.ts` +- Test failures or compilation errors +- Coverage reports or test output analysis + +## Test Types + +When implementing tests as an AI agent, choose between two main types: + +### Unit Tests (`*.unit.test.ts`) + +- **Fast isolated testing** - Mock all external dependencies +- **Use for**: Pure functions, business logic, data transformations +- **Execute with**: `runTests` tool with specific file patterns +- **Mock everything** - VS Code APIs automatically mocked via `/src/test/unittests.ts` + +### Extension Tests (`*.test.ts`) + +- **Full VS Code integration** - Real environment with actual APIs +- **Use for**: Command registration, UI interactions, extension lifecycle +- **Execute with**: VS Code launch configurations or `runTests` tool +- **Slower but comprehensive** - Tests complete user workflows + +## 🤖 Agent Tool Usage for Test Execution + +### Primary Tool: `runTests` + +Use the `runTests` tool to execute tests programmatically rather than terminal commands for better integration and result parsing: + +```typescript +// Run specific test files +await runTests({ + files: ['/absolute/path/to/test.unit.test.ts'], + mode: 'run', +}); + +// Run tests with coverage +await runTests({ + files: ['/absolute/path/to/test.unit.test.ts'], + mode: 'coverage', + coverageFiles: ['/absolute/path/to/source.ts'], +}); + +// Run specific test names +await runTests({ + files: ['/absolute/path/to/test.unit.test.ts'], + testNames: ['should handle edge case', 'should validate input'], +}); +``` + +### Compilation Requirements + +Before running tests, ensure compilation. Always start compilation with `npm run watch-tests` before test execution to ensure TypeScript files are built. Recompile after making import/export changes before running tests, as stubs won't work if they're applied to old compiled JavaScript that doesn't have the updated imports: + +```typescript +// Start watch mode for auto-compilation +await run_in_terminal({ + command: 'npm run watch-tests', + isBackground: true, + explanation: 'Start test compilation in watch mode', +}); + +// Or compile manually +await run_in_terminal({ + command: 'npm run compile-tests', + isBackground: false, + explanation: 'Compile TypeScript test files', +}); +``` + +### Alternative: Terminal Execution + +For targeted test runs when `runTests` tool is unavailable. Note: When a targeted test run yields 0 tests, first verify the compiled JS exists under `out/test` (rootDir is `src`); absence almost always means the test file sits outside `src` or compilation hasn't run yet: + +```typescript +// Run specific test suite +await run_in_terminal({ + command: 'npm run unittest -- --grep "Suite Name"', + isBackground: false, + explanation: 'Run targeted unit tests', +}); +``` + +## 🔍 Diagnosing Test Failures + +### Common Failure Patterns + +**Compilation Errors:** + +```typescript +// Missing imports +if (error.includes('Cannot find module')) { + await addMissingImports(testFile); +} + +// Type mismatches +if (error.includes("Type '" && error.includes("' is not assignable"))) { + await fixTypeIssues(testFile); +} +``` + +**Runtime Errors:** + +```typescript +// Mock setup issues +if (error.includes('stub') || error.includes('mock')) { + await fixMockConfiguration(testFile); +} + +// Assertion failures +if (error.includes('AssertionError')) { + await analyzeAssertionFailure(error); +} +``` + +### Systematic Failure Analysis + +Fix test issues iteratively - run tests, analyze failures, apply fixes, repeat until passing. When unit tests fail with VS Code API errors like `TypeError: X is not a constructor` or `Cannot read properties of undefined (reading 'Y')`, check if VS Code APIs are properly mocked in `/src/test/unittests.ts` - add missing APIs following the existing pattern. + +```typescript +interface TestFailureAnalysis { + type: 'compilation' | 'runtime' | 'assertion' | 'timeout'; + message: string; + location: { file: string; line: number; col: number }; + suggestedFix: string; +} + +function analyzeFailure(failure: TestFailure): TestFailureAnalysis { + if (failure.message.includes('Cannot find module')) { + return { + type: 'compilation', + message: failure.message, + location: failure.location, + suggestedFix: 'Add missing import statement', + }; + } + // ... other failure patterns +} +``` + +### Agent Decision Logic for Test Type Selection + +**Choose Unit Tests (`*.unit.test.ts`) when analyzing:** + +- Functions with clear inputs/outputs and no VS Code API dependencies +- Data transformation, parsing, or utility functions +- Business logic that can be isolated with mocks +- Error handling scenarios with predictable inputs + +**Choose Extension Tests (`*.test.ts`) when analyzing:** + +- Functions that register VS Code commands or use `vscode.*` APIs +- UI components, tree views, or command palette interactions +- File system operations requiring workspace context +- Extension lifecycle events (activation, deactivation) + +**Agent Implementation Pattern:** + +```typescript +function determineTestType(functionCode: string): 'unit' | 'extension' { + if ( + functionCode.includes('vscode.') || + functionCode.includes('commands.register') || + functionCode.includes('window.') || + functionCode.includes('workspace.') + ) { + return 'extension'; + } + return 'unit'; +} +``` + +## 🎯 Step 1: Automated Function Analysis + +As an AI agent, analyze the target function systematically: + +### Code Analysis Checklist + +```typescript +interface FunctionAnalysis { + name: string; + inputs: string[]; // Parameter types and names + outputs: string; // Return type + dependencies: string[]; // External modules/APIs used + sideEffects: string[]; // Logging, file system, network calls + errorPaths: string[]; // Exception scenarios + testType: 'unit' | 'extension'; +} +``` + +### Analysis Implementation + +1. **Read function source** using `read_file` tool +2. **Identify imports** - look for `vscode.*`, `child_process`, `fs`, etc. +3. **Map data flow** - trace inputs through transformations to outputs +4. **Catalog dependencies** - external calls that need mocking +5. **Document side effects** - logging, file operations, state changes + +### Test Setup Differences + +#### Unit Test Setup (\*.unit.test.ts) + +```typescript +// Mock VS Code APIs - handled automatically by unittests.ts +import * as sinon from 'sinon'; +import * as workspaceApis from '../../common/workspace.apis'; // Wrapper functions + +// Stub wrapper functions, not VS Code APIs directly +// Always mock wrapper functions (e.g., workspaceApis.getConfiguration()) instead of +// VS Code APIs directly to avoid stubbing issues +const mockGetConfiguration = sinon.stub(workspaceApis, 'getConfiguration'); +``` + +#### Extension Test Setup (\*.test.ts) + +```typescript +// Use real VS Code APIs +import * as vscode from 'vscode'; + +// Real VS Code APIs available - no mocking needed +const config = vscode.workspace.getConfiguration('python'); +``` + +## 🎯 Step 2: Generate Test Coverage Matrix + +Based on function analysis, automatically generate comprehensive test scenarios: + +### Coverage Matrix Generation + +```typescript +interface TestScenario { + category: 'happy-path' | 'edge-case' | 'error-handling' | 'side-effects'; + description: string; + inputs: Record; + expectedOutput?: any; + expectedSideEffects?: string[]; + shouldThrow?: boolean; +} +``` + +### Automated Scenario Creation + +1. **Happy Path**: Normal execution with typical inputs +2. **Edge Cases**: Boundary conditions, empty/null inputs, unusual but valid data +3. **Error Scenarios**: Invalid inputs, dependency failures, exception paths +4. **Side Effects**: Verify logging calls, file operations, state changes + +### Agent Pattern for Scenario Generation + +```typescript +function generateTestScenarios(analysis: FunctionAnalysis): TestScenario[] { + const scenarios: TestScenario[] = []; + + // Generate happy path for each input combination + scenarios.push(...generateHappyPathScenarios(analysis)); + + // Generate edge cases for boundary conditions + scenarios.push(...generateEdgeCaseScenarios(analysis)); + + // Generate error scenarios for each dependency + scenarios.push(...generateErrorScenarios(analysis)); + + return scenarios; +} +``` + +## 🗺️ Step 3: Plan Your Test Coverage + +### Create a Test Coverage Matrix + +#### Main Flows + +- ✅ **Happy path scenarios** - normal expected usage +- ✅ **Alternative paths** - different configuration combinations +- ✅ **Integration scenarios** - multiple features working together + +#### Edge Cases + +- 🔸 **Boundary conditions** - empty inputs, missing data +- 🔸 **Error scenarios** - network failures, permission errors +- 🔸 **Data validation** - invalid inputs, type mismatches + +#### Real-World Scenarios + +- ✅ **Fresh install** - clean slate +- ✅ **Existing user** - migration scenarios +- ✅ **Power user** - complex configurations +- 🔸 **Error recovery** - graceful degradation + +### Example Test Plan Structure + +```markdown +## Test Categories + +### 1. Configuration Migration Tests + +- No legacy settings exist +- Legacy settings already migrated +- Fresh migration needed +- Partial migration required +- Migration failures + +### 2. Configuration Source Tests + +- Global search paths +- Workspace search paths +- Settings precedence +- Configuration errors + +### 3. Path Resolution Tests + +- Absolute vs relative paths +- Workspace folder resolution +- Path validation and filtering + +### 4. Integration Scenarios + +- Combined configurations +- Deduplication logic +- Error handling flows +``` + +## 🔧 Step 4: Set Up Your Test Infrastructure + +### Test File Structure + +```typescript +// 1. Imports - group logically +import assert from 'node:assert'; +import * as sinon from 'sinon'; +import { Uri } from 'vscode'; +import * as logging from '../../../common/logging'; +import * as pathUtils from '../../../common/utils/pathUtils'; +import * as workspaceApis from '../../../common/workspace.apis'; + +// 2. Function under test +import { getAllExtraSearchPaths } from '../../../managers/common/nativePythonFinder'; + +// 3. Mock interfaces +interface MockWorkspaceConfig { + get: sinon.SinonStub; + inspect: sinon.SinonStub; + update: sinon.SinonStub; +} +``` + +### Mock Setup Strategy + +Create minimal mock objects with only required methods and use TypeScript type assertions (e.g., `mockApi as PythonEnvironmentApi`) to satisfy interface requirements instead of implementing all interface methods when only specific methods are needed for the test. Simplify mock setup by only mocking methods actually used in tests and use `as unknown as Type` for TypeScript compatibility. + +```typescript +suite('Function Integration Tests', () => { + // 1. Declare all mocks + let mockGetConfiguration: sinon.SinonStub; + let mockGetWorkspaceFolders: sinon.SinonStub; + let mockTraceLog: sinon.SinonStub; + let mockTraceError: sinon.SinonStub; + let mockTraceWarn: sinon.SinonStub; + + // 2. Mock complex objects + let pythonConfig: MockWorkspaceConfig; + let envConfig: MockWorkspaceConfig; + + setup(() => { + // 3. Initialize all mocks + mockGetConfiguration = sinon.stub(workspaceApis, 'getConfiguration'); + mockGetWorkspaceFolders = sinon.stub(workspaceApis, 'getWorkspaceFolders'); + mockTraceLog = sinon.stub(logging, 'traceLog'); + mockTraceError = sinon.stub(logging, 'traceError'); + mockTraceWarn = sinon.stub(logging, 'traceWarn'); + + // 4. Set up default behaviors + mockGetWorkspaceFolders.returns(undefined); + + // 5. Create mock configuration objects + // When fixing mock environment creation, use null to truly omit + // properties rather than undefined + pythonConfig = { + get: sinon.stub(), + inspect: sinon.stub(), + update: sinon.stub(), + }; + + envConfig = { + get: sinon.stub(), + inspect: sinon.stub(), + update: sinon.stub(), + }; + }); + + teardown(() => { + sinon.restore(); // Always clean up! + }); +}); +``` + +## Step 4: Write Tests Using Mock → Run → Assert Pattern + +### The Three-Phase Pattern + +#### Phase 1: Mock (Set up the scenario) + +```typescript +test('Description of what this tests', async () => { + // Mock → Clear description of the scenario + pythonConfig.inspect.withArgs('venvPath').returns({ globalValue: '/path' }); + envConfig.inspect.withArgs('globalSearchPaths').returns({ globalValue: [] }); + mockGetWorkspaceFolders.returns([{ uri: Uri.file('/workspace') }]); +``` + +#### Phase 2: Run (Execute the function) + +```typescript +// Run +const result = await getAllExtraSearchPaths(); +``` + +#### Phase 3: Assert (Verify the behavior) + +```typescript + // Assert - Use set-based comparison for order-agnostic testing + const expected = new Set(['/expected', '/paths']); + const actual = new Set(result); + assert.strictEqual(actual.size, expected.size, 'Should have correct number of paths'); + assert.deepStrictEqual(actual, expected, 'Should contain exactly the expected paths'); + + // Verify side effects + // Use sinon.match() patterns for resilient assertions that don't break on minor output changes + assert(mockTraceLog.calledWith(sinon.match(/completion/i)), 'Should log completion'); +}); +``` + +## Step 6: Make Tests Resilient + +### Use Order-Agnostic Comparisons + +```typescript +// ❌ Brittle - depends on order +assert.deepStrictEqual(result, ['/path1', '/path2', '/path3']); + +// ✅ Resilient - order doesn't matter +const expected = new Set(['/path1', '/path2', '/path3']); +const actual = new Set(result); +assert.strictEqual(actual.size, expected.size, 'Should have correct number of paths'); +assert.deepStrictEqual(actual, expected, 'Should contain exactly the expected paths'); +``` + +### Use Flexible Error Message Testing + +```typescript +// ❌ Brittle - exact text matching +assert(mockTraceError.calledWith('Error during legacy python settings migration:')); + +// ✅ Resilient - pattern matching +assert(mockTraceError.calledWith(sinon.match.string, sinon.match.instanceOf(Error)), 'Should log migration error'); + +// ✅ Resilient - key terms with regex +assert(mockTraceError.calledWith(sinon.match(/migration.*error/i)), 'Should log migration error'); +``` + +### Handle Complex Mock Scenarios + +```typescript +// For functions that call the same mock multiple times +envConfig.inspect.withArgs('globalSearchPaths').returns({ globalValue: [] }); +envConfig.inspect + .withArgs('globalSearchPaths') + .onSecondCall() + .returns({ + globalValue: ['/migrated/paths'], + }); + +// Testing async functions with child processes: +// Call the function first to get a promise, then use setTimeout to emit mock events, +// then await the promise - this ensures proper timing of mock setup versus function execution + +// Cannot stub internal function calls within the same module after import - stub external +// dependencies instead (e.g., stub childProcessApis.spawnProcess rather than trying to stub +// helpers.isUvInstalled when testing helpers.shouldUseUv) because intra-module calls use +// direct references, not module exports +``` + +## 🧪 Step 7: Test Categories and Patterns + +### Configuration Tests + +- Test different setting combinations +- Test setting precedence (workspace > user > default) +- Test configuration errors and recovery +- Always use dynamic path construction with Node.js `path` module when testing functions that resolve paths against workspace folders to ensure cross-platform compatibility + +### Data Flow Tests + +- Test how data moves through the system +- Test transformations (path resolution, filtering) +- Test state changes (migrations, updates) + +### Error Handling Tests + +- Test graceful degradation +- Test error logging +- Test fallback behaviors + +### Integration Tests + +- Test multiple features together +- Test real-world scenarios +- Test edge case combinations + +## 📊 Step 8: Review and Refine + +### Test Quality Checklist + +- [ ] **Clear naming** - test names describe the scenario and expected outcome +- [ ] **Good coverage** - main flows, edge cases, error scenarios +- [ ] **Resilient assertions** - won't break due to minor changes +- [ ] **Readable structure** - follows Mock → Run → Assert pattern +- [ ] **Isolated tests** - each test is independent +- [ ] **Fast execution** - tests run quickly with proper mocking + +### Common Anti-Patterns to Avoid + +- ❌ Testing implementation details instead of behavior +- ❌ Brittle assertions that break on cosmetic changes +- ❌ Order-dependent tests that fail due to processing changes +- ❌ Tests that don't clean up mocks properly +- ❌ Overly complex test setup that's hard to understand + +## 🔄 Reviewing and Improving Existing Tests + +### Quick Review Process + +1. **Read test files** - Check structure and mock setup +2. **Run tests** - Establish baseline functionality +3. **Apply improvements** - Use patterns below. When reviewing existing tests, focus on behavior rather than implementation details in test names and assertions +4. **Verify** - Ensure tests still pass + +### Common Fixes + +- Over-complex mocks → Minimal mocks with only needed methods +- Brittle assertions → Behavior-focused with error messages +- Vague test names → Clear scenario descriptions (transform "should return X when Y" into "should [expected behavior] when [scenario context]") +- Missing structure → Mock → Run → Assert pattern +- Untestable Node.js APIs → Create proxy abstraction functions (use function overloads to preserve intelligent typing while making functions mockable) + +## 🧠 Agent Learnings + +- When mocking `testController.createTestItem()` in unit tests, use `typemoq.It.isAny()` for parameters when testing handler behavior (not ID/label generation logic), but consider using specific matchers (e.g., `It.is((id: string) => id.startsWith('_error_'))`) when the actual values being passed are important for correctness - this balances test precision with maintainability (2) +- Remove unused variables from test code immediately - leftover tracking variables like `validationCallCount` that aren't referenced indicate dead code that should be simplified (1) +- Use `Uri.file(path).fsPath` for both sides of path comparisons in tests to ensure cross-platform compatibility - Windows converts forward slashes to backslashes automatically (1) +- When tests fail with "Cannot stub non-existent property", the method likely moved to a different class during refactoring - find the class that owns the method and test that class directly instead of stubbing on the original class (1) diff --git a/.github/instructions/testing_feature_area.instructions.md b/.github/instructions/testing_feature_area.instructions.md new file mode 100644 index 000000000000..a4e11523d7c8 --- /dev/null +++ b/.github/instructions/testing_feature_area.instructions.md @@ -0,0 +1,263 @@ +--- +applyTo: 'src/client/testing/**' +--- + +# Testing feature area — Discovery, Run, Debug, and Results + +This document maps the testing support in the extension: discovery, execution (run), debugging, result reporting and how those pieces connect to the codebase. It's written for contributors and agents who need to navigate, modify, or extend test support (both `unittest` and `pytest`). + +## Overview + +- Purpose: expose Python tests in the VS Code Test Explorer (TestController), support discovery, run, debug, and surface rich results and outputs. +- Scope: provider-agnostic orchestration + provider-specific adapters, TestController mapping, IPC with Python-side scripts, debug launch integration, and configuration management. + +## High-level architecture + +- Controller / UI bridge: orchestrates TestController requests and routes them to workspace adapters. +- Workspace adapter: provider-agnostic coordinator that translates TestController requests to provider adapters and maps payloads back into TestItems/TestRuns. +- Provider adapters: implement discovery/run/debug for `unittest` and `pytest` by launching Python scripts and wiring named-pipe IPC. +- Result resolver: translates Python-side JSON/IPCPayloads into TestController updates (start/pass/fail/output/attachments). +- Debug launcher: prepares debug sessions and coordinates the debugger attach flow with the Python runner. + +## Key components (files and responsibilities) + +- Entrypoints + - `src/client/testing/testController/controller.ts` — `PythonTestController` (main orchestrator). + - `src/client/testing/serviceRegistry.ts` — DI/wiring for testing services. +- Workspace orchestration + - `src/client/testing/testController/workspaceTestAdapter.ts` — `WorkspaceTestAdapter` (provider-agnostic entry used by controller). +- **Project-based testing (multi-project workspaces)** + - `src/client/testing/testController/common/testProjectRegistry.ts` — `TestProjectRegistry` (manages project lifecycle, discovery, and nested project handling). + - `src/client/testing/testController/common/projectAdapter.ts` — `ProjectAdapter` interface (represents a single Python project with its own test infrastructure). + - `src/client/testing/testController/common/projectUtils.ts` — utilities for project ID generation, display names, and shared adapter creation. +- Provider adapters + - Unittest + - `src/client/testing/testController/unittest/testDiscoveryAdapter.ts` + - `src/client/testing/testController/unittest/testExecutionAdapter.ts` + - Pytest + - `src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts` + - `src/client/testing/testController/pytest/pytestExecutionAdapter.ts` +- Result resolution and helpers + - `src/client/testing/testController/common/resultResolver.ts` — `PythonResultResolver` (maps payload -> TestController updates). + - `src/client/testing/testController/common/testItemUtilities.ts` — helpers for TestItem lifecycle. + - `src/client/testing/testController/common/types.ts` — `ITestDiscoveryAdapter`, `ITestExecutionAdapter`, `ITestResultResolver`, `ITestDebugLauncher`. + - `src/client/testing/testController/common/debugLauncher.ts` — debug session creation helper. + - `src/client/testing/testController/common/utils.ts` — named-pipe helpers and command builders (`startDiscoveryNamedPipe`, etc.). +- Configuration + - `src/client/testing/common/testConfigurationManager.ts` — per-workspace test settings. + - `src/client/testing/configurationFactory.ts` — configuration service factory. +- Utilities & glue + - `src/client/testing/utils.ts` — assorted helpers used by adapters. + - Python-side scripts: `python_files/unittestadapter/*`, `python_files/pytestadapter/*` — discovery/run code executed by adapters. + +## Python subprocess runners (what runs inside Python) + +The adapters in the extension don't implement test discovery/run logic themselves — they spawn a Python subprocess that runs small helper scripts located under `python_files/` and stream structured events back to the extension over the named-pipe IPC. This is a central part of the feature area; changes here usually require coordinated edits in both the TypeScript adapters and the Python scripts. + +- Unittest helpers (folder: `python_files/unittestadapter`) + + - `discovery.py` — performs `unittest` discovery and emits discovery payloads (test suites, cases, locations) on the IPC channel. + - `execution.py` / `django_test_runner.py` — run tests for `unittest` and, where applicable, Django test runners; emit run events (start, stdout/stderr, pass, fail, skip, teardown) and attachment info. + - `pvsc_utils.py`, `django_handler.py` — utility helpers used by the runners for environment handling and Django-specific wiring. + - The adapter TypeScript files (`testDiscoveryAdapter.ts`, `testExecutionAdapter.ts`) construct the command line, start a named-pipe listener, and spawn these Python scripts using the extension's ExecutionFactory (activated interpreter) so the scripts execute inside the user's selected environment. + +- Pytest helpers (folder: `python_files/vscode_pytest`) + + - `_common.py` — shared helpers for pytest runner scripts. + - `run_pytest_script.py` — the primary pytest runner used for discovery and execution; emits the same structured IPC payloads the extension expects (discovery events and run events). + - The `pytest` execution adapter (`pytestExecutionAdapter.ts`) and discovery adapter build the CLI to run `run_pytest_script.py`, start the pipe, and translate incoming payloads via `PythonResultResolver`. + +- IPC contract and expectations + + - Adapters rely on a stable JSON payload contract emitted by the Python scripts: identifiers for tests, event types (discovered, collected, started, passed, failed, skipped), timings, error traces, and optional attachments (logs, captured stdout/stderr, file links). + - The extension maps these payloads to `TestItem`/`TestRun` updates via `PythonResultResolver` (`src/client/testing/testController/common/resultResolver.ts`). If you change payload shape, update the resolver and tests concurrently. + +- How the subprocess is started + - Execution adapters use the extension's `ExecutionFactory` (preferred) to get an activated interpreter and then spawn a child process that runs the helper script. The adapter will set up environment variables and command-line args (including the pipe name / run-id) so the Python runner knows where to send events and how to behave (discovery vs run vs debug). + - For debug sessions a debug-specific entry argument/port is passed and `common/debugLauncher.ts` coordinates starting a VS Code debug session that will attach to the Python process. + +## Core functionality (what to change where) + +- Discovery + - Entry: `WorkspaceTestAdapter.discoverTests` → provider discovery adapter. Adapter starts a named-pipe listener, spawns the discovery script in an activated interpreter, forwards discovery events to `PythonResultResolver` which creates/updates TestItems. + - Files: `workspaceTestAdapter.ts`, `*DiscoveryAdapter.ts`, `resultResolver.ts`, `testItemUtilities.ts`. +- Run / Execution + - Entry: `WorkspaceTestAdapter.executeTests` → provider execution adapter. Adapter spawns runner in an activated env, runner streams run events to the pipe, `PythonResultResolver` updates a `TestRun` with start/pass/fail and attachments. + - Files: `workspaceTestAdapter.ts`, `*ExecutionAdapter.ts`, `resultResolver.ts`. +- Debugging + - Flow: debug request flows like a run but goes through `debugLauncher.ts` to create a VS Code debug session with prepared ports/pipes. The Python runner coordinates attach/continue with the debugger. + - Files: `*ExecutionAdapter.ts`, `common/debugLauncher.ts`, `common/types.ts`. +- Result reporting + - `resultResolver.ts` is the canonical place to change how JSON payloads map to TestController constructs (messages, durations, error traces, attachments). + +## Typical workflows (short) + +- Full discovery + + 1. `PythonTestController` triggers discovery -> `WorkspaceTestAdapter.discoverTests`. + 2. Provider discovery adapter starts pipe and launches Python discovery script. + 3. Discovery events -> `PythonResultResolver` -> TestController tree updated. + +- Run tests + + 1. Controller collects TestItems -> creates `TestRun`. + 2. `WorkspaceTestAdapter.executeTests` delegates to execution adapter which launches the runner. + 3. Runner events arrive via pipe -> `PythonResultResolver` updates `TestRun`. + 4. On process exit the run is finalized. + +- Debug a test + 1. Debug request flows to execution adapter. + 2. Adapter prepares ports and calls `debugLauncher` to start a VS Code debug session with the run ID. + 3. Runner coordinates with the debugger; `PythonResultResolver` still receives and applies run events. + +## Tests and examples to inspect + +- Unit/integration tests for adapters and orchestration under `src/test/` (examples): + - `src/test/testing/common/testingAdapter.test.ts` + - `src/test/testing/testController/workspaceTestAdapter.unit.test.ts` + - `src/test/testing/testController/unittest/testExecutionAdapter.unit.test.ts` + - Adapter tests demonstrate expected telemetry, debug-launch payloads and result resolution. + +## History & evolution (brief) + +- Migration to TestController API: the code organizes around VS Code TestController, mapping legacy adapter behaviour into TestItems/TestRuns. +- Named-pipe IPC: discovery/run use named-pipe IPC to stream events from Python runner scripts (`python_files/*`) which enables richer, incremental updates and debug coordination. +- Environment activation: adapters prefer the extension ExecutionFactory (activated interpreter) to run discovery and test scripts. + +## Pointers for contributors (practical) + +- To extend discovery output: update the Python discovery script in `python_files/*` and `resultResolver.ts` to parse new payload fields. +- To change run behaviour (args/env/timouts): update the provider execution adapter (`*ExecutionAdapter.ts`) and add/update tests under `src/test/`. +- To change debug flow: edit `common/debugLauncher.ts` and adapters' debug paths; update tests that assert launch argument shapes. + +## Django support (how it works) + +- The extension supports Django projects by delegating discovery and execution to Django-aware Python helpers under `python_files/unittestadapter`. + - `python_files/unittestadapter/django_handler.py` contains helpers that invoke `manage.py` for discovery or execute Django test runners inside the project context. + - `python_files/unittestadapter/django_test_runner.py` provides `CustomDiscoveryTestRunner` and `CustomExecutionTestRunner` which integrate with the extension by using the same IPC contract (they use `UnittestTestResult` and `send_post_request` to emit discovery/run payloads). +- How adapters pass Django configuration: + - Execution adapters set environment variables (e.g. `MANAGE_PY_PATH`) and modify `PYTHONPATH` so Django code and the custom test runner are importable inside the spawned subprocess. + - For discovery the adapter may run the discovery helper which calls `manage.py test` with a custom test runner that emits discovery payloads instead of executing tests. +- Practical notes for contributors: + - Changes to Django discovery/execution often require edits in both `django_test_runner.py`/`django_handler.py` and the TypeScript adapters (`testDiscoveryAdapter.ts` / `testExecutionAdapter.ts`). + - The Django test runner expects `TEST_RUN_PIPE` environment variable to be present to send IPC events (see `django_test_runner.py`). + +## Settings referenced by this feature area + +- The extension exposes several `python.testing.*` settings used by adapters and configuration code (declared in `package.json`): + - `python.testing.pytestEnabled`, `python.testing.unittestEnabled` — enable/disable frameworks. + - `python.testing.pytestPath`, `python.testing.pytestArgs`, `python.testing.unittestArgs` — command path and CLI arguments used when spawning helper scripts. + - `python.testing.cwd` — optional working directory used when running discovery/runs. + - `python.testing.autoTestDiscoverOnSaveEnabled`, `python.testing.autoTestDiscoverOnSavePattern` — control automatic discovery on save. + - `python.testing.debugPort` — default port used for debug runs. + - `python.testing.promptToConfigure` — whether to prompt users to configure tests when potential test folders are found. +- Where to look in the code: + - Settings are consumed by `src/client/testing/common/testConfigurationManager.ts`, `src/client/testing/configurationFactory.ts`, and adapters under `src/client/testing/testController/*` which read settings to build CLI args and env for subprocesses. + - The setting definitions and descriptions are in `package.json` and localized strings in `package.nls.json`. + +## Project-based testing (multi-project workspaces) + +Project-based testing enables multi-project workspace support where each Python project gets its own test tree root with its own Python environment. + +### Architecture + +- **TestProjectRegistry** (`testProjectRegistry.ts`): Central registry that: + + - Discovers Python projects via the Python Environments API + - Creates and manages `ProjectAdapter` instances per workspace + - Computes nested project relationships and configures ignore lists + - Falls back to "legacy" single-adapter mode when API unavailable + +- **ProjectAdapter** (`projectAdapter.ts`): Interface representing a single project with: + - Project identity (ID, name, URI from Python Environments API) + - Python environment with execution details + - Test framework adapters (discovery/execution) + - Nested project ignore paths (for parent projects) + +### How it works + +1. **Activation**: When the extension activates, `PythonTestController` checks if the Python Environments API is available. +2. **Project discovery**: `TestProjectRegistry.discoverAndRegisterProjects()` queries the API for all Python projects in each workspace. +3. **Nested handling**: `configureNestedProjectIgnores()` identifies child projects and adds their paths to parent projects' ignore lists. +4. **Test discovery**: For each project, the controller calls `project.discoveryAdapter.discoverTests()` with the project's URI. The adapter sets `PROJECT_ROOT_PATH` environment variable for the Python runner. +5. **Python side**: + - For pytest: `get_test_root_path()` in `vscode_pytest/__init__.py` returns `PROJECT_ROOT_PATH` (if set) or falls back to `cwd`. + - For unittest: `discovery.py` uses `PROJECT_ROOT_PATH` as `top_level_dir` and `project_root_path` to root the test tree at the project directory. +6. **Test tree**: Each project gets its own root node in the Test Explorer, with test IDs scoped by project ID using the `@@vsc@@` separator (defined in `projectUtils.ts`). + +### Nested project handling: pytest vs unittest + +**pytest** supports the `--ignore` flag to exclude paths during test collection. When nested projects are detected, parent projects automatically receive `--ignore` flags for child project paths. This ensures each test appears under exactly one project in the test tree. + +**unittest** does not support path exclusion during `discover()`. Therefore, tests in nested project directories may appear under multiple project roots (both the parent and the child project). This is **expected behavior** for unittest: + +- Each project discovers and displays all tests it finds within its directory structure +- There is no deduplication or collision detection +- Users may see the same test file under multiple project roots if their project structure has nesting + +This approach was chosen because: + +1. unittest's `TestLoader.discover()` has no built-in path exclusion mechanism +2. Implementing custom exclusion would add significant complexity with minimal benefit +3. The existing approach is transparent and predictable - each project shows what it finds + +### Empty projects and root nodes + +If a project discovers zero tests, its root node will still appear in the Test Explorer as an empty folder. This ensures consistent behavior and makes it clear which projects were discovered, even if they have no tests yet. + +### Logging prefix + +All project-based testing logs use the `[test-by-project]` prefix for easy filtering in the output channel. + +### Key files + +- Python side: + - `python_files/vscode_pytest/__init__.py` — `get_test_root_path()` function and `PROJECT_ROOT_PATH` environment variable for pytest. + - `python_files/unittestadapter/discovery.py` — `discover_tests()` with `project_root_path` parameter and `PROJECT_ROOT_PATH` handling for unittest discovery. + - `python_files/unittestadapter/execution.py` — `run_tests()` with `project_root_path` parameter and `PROJECT_ROOT_PATH` handling for unittest execution. +- TypeScript: `testProjectRegistry.ts`, `projectAdapter.ts`, `projectUtils.ts`, and the discovery/execution adapters. + +### Tests + +- `src/test/testing/testController/common/testProjectRegistry.unit.test.ts` — TestProjectRegistry tests +- `src/test/testing/testController/common/projectUtils.unit.test.ts` — Project utility function tests +- `python_files/tests/pytestadapter/test_discovery.py` — pytest PROJECT_ROOT_PATH tests (see `test_project_root_path_env_var()` and `test_symlink_with_project_root_path()`) +- `python_files/tests/unittestadapter/test_discovery.py` — unittest `project_root_path` / PROJECT_ROOT_PATH discovery tests +- `python_files/tests/unittestadapter/test_execution.py` — unittest `project_root_path` / PROJECT_ROOT_PATH execution tests +- `src/test/testing/testController/unittest/testDiscoveryAdapter.unit.test.ts` — unittest discovery adapter PROJECT_ROOT_PATH tests +- `src/test/testing/testController/unittest/testExecutionAdapter.unit.test.ts` — unittest execution adapter PROJECT_ROOT_PATH tests + +## Coverage support (how it works) + +- Coverage is supported by running the Python helper scripts with coverage enabled and then collecting a coverage payload from the runner. + - Pytest-side coverage logic lives in `python_files/vscode_pytest/__init__.py` (checks `COVERAGE_ENABLED`, imports `coverage`, computes per-file metrics and emits a `CoveragePayloadDict`). + - Unittest adapters enable coverage by setting environment variable(s) (e.g. `COVERAGE_ENABLED`) when launching the subprocess; adapters and `resultResolver.ts` handle the coverage profile kind (`TestRunProfileKind.Coverage`). +- Flow summary: + 1. User starts a Coverage run via Test Explorer (profile kind `Coverage`). + 2. Controller/adapters set `COVERAGE_ENABLED` (or equivalent) in the subprocess env and invoke the runner script. + 3. The Python runner collects coverage (using `coverage` or `pytest-cov`), builds a file-level coverage map, and sends a coverage payload back over the IPC. + 4. `PythonResultResolver` (`src/client/testing/testController/common/resultResolver.ts`) receives the coverage payload and stores `detailedCoverageMap` used by the TestController profile to show file-level coverage details. +- Tests that exercise coverage flows are under `src/test/testing/*` and `python_files/tests/*` (see `testingAdapter.test.ts` and adapter unit tests that assert `COVERAGE_ENABLED` is set appropriately). + +## Interaction with the VS Code API + +- TestController API + - The feature area is built on VS Code's TestController/TestItem/TestRun APIs (`vscode.tests.createTestController` / `tests.createTestController` in the code). The controller creates a `TestController` in `src/client/testing/testController/controller.ts` and synchronizes `TestItem` trees with discovery payloads. + - `PythonResultResolver` maps incoming JSON events to VS Code API calls: `testRun.appendOutput`, `testRun.passed/failed/skipped`, `testRun.end`, and `TestItem` updates (labels, locations, children). +- Debug API + - Debug runs use the Debug API to start an attach/launch session. The debug launcher implementation is in `src/client/testing/testController/common/debugLauncher.ts` which constructs a debug configuration and calls the VS Code debug API to start a session (e.g. `vscode.debug.startDebugging`). + - Debug adapter/resolver code in the extension's debugger modules may also be used when attaching to Django or test subprocesses. +- Commands and configuration + - The Test Controller wires commands that appear in the Test Explorer and editor context menus (see `package.json` contributes `commands`) and listens to configuration changes filtered by `python.testing` in `src/client/testing/main.ts`. +- The "Copy Test ID" command (`python.copyTestId`) can be accessed from both the Test Explorer context menu (`testing/item/context`) and the editor gutter icon context menu (`testing/item/gutter`). This command copies test identifiers to the clipboard in the appropriate format for the active test framework (pytest path format or unittest module.class.method format). +- Execution factory & activated environments + - Adapters use the extension `ExecutionFactory` to spawn subprocesses in an activated interpreter (so the user's venv/conda is used). This involves the extension's internal environment execution APIs and sometimes `envExt` helpers when the external environment extension is present. + +## Learnings + +- Never await `showErrorMessage()` calls in test execution adapters as it blocks the test UI thread and freezes the Test Explorer (1) +- VS Code test-related context menus are contributed to using both `testing/item/context` and `testing/item/gutter` menu locations in package.json for full coverage (1) + +``` + +``` diff --git a/.github/lock.yml b/.github/lock.yml deleted file mode 100644 index 16fbb5233be9..000000000000 --- a/.github/lock.yml +++ /dev/null @@ -1,2 +0,0 @@ -daysUntilLock: 7 -lockComment: false diff --git a/.github/prompts/extract-impl-instructions.prompt.md b/.github/prompts/extract-impl-instructions.prompt.md new file mode 100644 index 000000000000..c2fb08b443c7 --- /dev/null +++ b/.github/prompts/extract-impl-instructions.prompt.md @@ -0,0 +1,79 @@ +--- +mode: edit +--- + +Analyze the specified part of the VS Code Python Extension codebase to generate or update implementation instructions in `.github/instructions/.instructions.md`. + +## Task + +Create concise developer guidance focused on: + +### Implementation Essentials + +- **Core patterns**: How this component is typically implemented and extended +- **Key interfaces**: Essential classes, services, and APIs with usage examples +- **Integration points**: How this component interacts with other extension parts +- **Common tasks**: Typical development scenarios with step-by-step guidance + +### Content Structure + +````markdown +--- +description: 'Implementation guide for the part of the Python Extension' +--- + +# Implementation Guide + +## Overview + +Brief description of the component's purpose and role in VS Code Python Extension. + +## Key Concepts + +- Main abstractions and their responsibilities +- Important interfaces and base classes + +## Common Implementation Patterns + +### Pattern 1: [Specific Use Case] + +```typescript +// Code example showing typical implementation +``` +```` + +### Pattern 2: [Another Use Case] + +```typescript +// Another practical example +``` + +## Integration Points + +- How this component connects to other VS Code Python Extension systems +- Required services and dependencies +- Extension points and contribution models + +## Essential APIs + +- Key methods and interfaces developers need +- Common parameters and return types + +## Gotchas and Best Practices + +- Non-obvious behaviors to watch for +- Performance considerations +- Common mistakes to avoid + +``` + +## Guidelines +- **Be specific**: Use actual class names, method signatures, and file paths +- **Show examples**: Include working code snippets from the codebase +- **Target implementation**: Focus on how to build with/extend this component +- **Keep it actionable**: Every section should help developers accomplish tasks + +Source conventions from existing `.github/instructions/*.instructions.md`, `CONTRIBUTING.md`, and codebase patterns. + +If `.github/instructions/.instructions.md` exists, intelligently merge new insights with existing content. +``` diff --git a/.github/prompts/extract-usage-instructions.prompt.md b/.github/prompts/extract-usage-instructions.prompt.md new file mode 100644 index 000000000000..ea48f162a220 --- /dev/null +++ b/.github/prompts/extract-usage-instructions.prompt.md @@ -0,0 +1,30 @@ +--- +mode: edit +--- + +Analyze the user requested part of the codebase (use a suitable ) to generate or update `.github/instructions/.instructions.md` for guiding developers and AI coding agents. + +Focus on practical usage patterns and essential knowledge: + +- How to use, extend, or integrate with this code area +- Key architectural patterns and conventions specific to this area +- Common implementation patterns with code examples +- Integration points and typical interaction patterns with other components +- Essential gotchas and non-obvious behaviors + +Source existing conventions from `.github/instructions/*.instructions.md`, `CONTRIBUTING.md`, and `README.md`. + +Guidelines: + +- Write concise, actionable instructions using markdown structure +- Document discoverable patterns with concrete examples +- If `.github/instructions/.instructions.md` exists, merge intelligently +- Target developers who need to work with or extend this code area + +Update `.github/instructions/.instructions.md` with header: + +``` +--- +description: "How to work with the part of the codebase" +--- +``` diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 000000000000..0058580e92e0 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,19 @@ +changelog: + exclude: + labels: + - 'no-changelog' + authors: + - 'dependabot' + + categories: + - title: Enhancements + labels: + - 'feature-request' + + - title: Bug Fixes + labels: + - 'bug' + + - title: Code Health + labels: + - 'debt' diff --git a/.github/release_plan.md b/.github/release_plan.md index 295c8f733cdd..091ed559825b 100644 --- a/.github/release_plan.md +++ b/.github/release_plan.md @@ -1,92 +1,138 @@ -# Prerequisites - -- Python 3.7 and higher -- run `python3 -m pip install --user -r news/requirements.txt` - -# Release candidate (Monday, XXX XX) - -- [ ] Announce the code freeze on both Teams and e-mail, leave enough time for teams to surface any last minute issues that need to get in before freeze. Make sure debugger and Language Server teams are looped in as well. -- [ ] Update `main` for the release - - [ ] Create a branch against `main` for a pull request - - [ ] Change the version in [`package.json`](https://github.com/Microsoft/vscode-python/blob/main/package.json) from a `-dev` suffix to `-rc` (🤖) - - [ ] Run `npm install` to make sure [`package-lock.json`](https://github.com/Microsoft/vscode-python/blob/main/package.json) is up-to-date (🤖) - - [ ] Extension will pick up latest version of `debugpy`. If you need to pin to a particular version see `install_debugpy.py`. - - [ ] Update `languageServerVersion` in `package.json` to point to the latest version of the [Language Server](https://github.com/Microsoft/python-language-server). Check with the language server team if this needs updating. - - [ ] Update [`CHANGELOG.md`](https://github.com/Microsoft/vscode-python/blob/main/CHANGELOG.md) (🤖) - - [ ] Run [`news`](https://github.com/Microsoft/vscode-python/tree/main/news) (typically `python news --final --update CHANGELOG.md | code-insiders -`) - - [ ] Copy over the "Thanks" section from the previous release into the "Thanks" section for the new release - - [ ] Make sure the "Thanks" section is up-to-date (e.g. compare to versions in [`requirements.txt`](https://github.com/microsoft/vscode-python/blob/main/requirements.txt)) - - [ ] Touch up news entries (e.g. add missing periods) - - [ ] Check the Markdown rendering to make sure everything looks good - - [ ] Add any relevant news entries for `debugpy` and the language server if they were updated - - [ ] Update [`ThirdPartyNotices-Distribution.txt`](https://github.com/Microsoft/vscode-python/blob/main/ThirdPartyNotices-Distribution.txt) by using https://tools.opensource.microsoft.com/notice (Notes for this process are in the Team OneNote under Python VS Code → Dev Process → Third-Party Notices / TPN file) - - [ ] Update [`ThirdPartyNotices-Repository.txt`](https://github.com/Microsoft/vscode-python/blob/main/ThirdPartyNotices-Repository.txt) as appropriate. This file is manually edited so you can check with the teams if anything needs to be added here. - - [ ] Create a pull request against `main` (🤖) - - [ ] Merge pull request into `main` -- [ ] Update the [`release` branch](https://github.com/microsoft/vscode-python/branches) - - [ ] If there are `release` branches that are two versions old (e.g. release-2020.[current month - 2]) you can delete them at this time - - [ ] Create a new `release-YYYY.MM` branch from `main` -- [ ] Update `main` post-release (🤖) - - [ ] Bump the version number to the next monthly ("YYYY.MM.0-dev") release in the `main` branch - - [ ] `package.json` - - [ ] `package-lock.json` - - [ ] Create a pull request against `main` - - [ ] Merge pull request into `main` -- [ ] Announce the code freeze is over on the same channels -- [ ] Update Component Governance (Notes are in the team OneNote under Python VS Code → Dev Process → Component Governance). - - [ ] Make sure there are no active alerts - - [ ] Manually add any repository/embedded/CG-incompatible dependencies -- [ ] GDPR bookkeeping (@brettcannon) (🤖; Notes in OneNote under Python VS Code → Dev Process → GDPR) -- [ ] Open appropriate [documentation issues](https://github.com/microsoft/vscode-docs/issues?q=is%3Aissue+is%3Aopen+label%3Apython) - - new features - - settings changes - - etc. (ask the team) -- [ ] Schedule a bug bash. Aim for close after freeze so there is still time to fix release bugs before release. Ask teams before bash for specific areas that need testing. -- [ ] Begin drafting a [blog](http://aka.ms/pythonblog) post. Contact the PM team for this. -- [ ] Ask CTI to test the release candidate - -# Final (Monday, XXX XX) - -## Preparation - -- [ ] Make sure the [appropriate pull requests](https://github.com/microsoft/vscode-docs/pulls) for the [documentation](https://code.visualstudio.com/docs/python/python-tutorial) -- including the [WOW](https://code.visualstudio.com/docs/languages/python) page -- are ready -- [ ] Final updates to the `release-YYYY.MM` branch - - [ ] Create a branch against `release-YYYY.MM` for a pull request - - [ ] Update the version in [`package.json`](https://github.com/Microsoft/vscode-python/blob/main/package.json) to remove the `-rc` (🤖) - - [ ] Run `npm install` to make sure [`package-lock.json`](https://github.com/Microsoft/vscode-python/blob/main/package.json) is up-to-date (the only update should be the version number if `package-lock.json` has been kept up-to-date) (🤖) - - [ ] Update [`CHANGELOG.md`](https://github.com/Microsoft/vscode-python/blob/main/CHANGELOG.md) (🤖) - - [ ] Update version and date for the release section - - [ ] Run [`news`](https://github.com/Microsoft/vscode-python/tree/main/news) and copy-and-paste new entries (typically `python news --final | code-insiders -`; quite possibly nothing new to add) - - [ ] Update [`ThirdPartyNotices-Distribution.txt`](https://github.com/Microsoft/vscode-python/blob/main/ThirdPartyNotices-Distribution.txt) by using https://tools.opensource.microsoft.com/notice (🤖; see team notes) - - [ ] Update [`ThirdPartyNotices-Repository.txt`](https://github.com/Microsoft/vscode-python/blob/main/ThirdPartyNotices-Repository.txt) manually if necessary - - [ ] Create pull request against `release-YYYY.MM` (🤖) - - [ ] Merge pull request into `release-YYYY.MM` -- [ ] Make sure component governance is happy - -## Release - -- [ ] Publish the release via Azure DevOps - - [ ] Make sure [CI](https://github.com/Microsoft/vscode-python/blob/main/CONTRIBUTING.md) is passing. Try a re-run on any failing CI test stages. If tests still won't pass contact the owning team. - - [ ] On Azure DevOps on the page for the CI run after it succeeds there will now be a "Releases" tab which is populated with a release entry. Click that entry to go to the release page, which shows the "Upload" and "Publish" stages - - [ ] Click the deploy button on the "Upload" stage and make sure that it succeeds - - [ ] Make sure no extraneous files are being included in the `.vsix` file (make sure to check for hidden files) - - [ ] Click the deploy button on the "Publish" stage, this will push out the release to the public - - [ ] From a VSCode instance uninstall the python extension. After the publish see if the new version is available from the extensions tab. Download it and quick sanity check to make sure the extension loads. -- [ ] Create a [GitHub release](https://github.com/microsoft/vscode-python/releases) - - [ ] The previous publish step should have created a release here, but it needs to be edited - - [ ] Edit the tag to match the version of the released extension - - [ ] Copy the changelog entry into the release as the description -- [ ] Publish [documentation changes](https://github.com/Microsoft/vscode-docs/pulls?q=is%3Apr+is%3Aopen+label%3Apython) -- [ ] Publish the [blog](http://aka.ms/pythonblog) post -- [ ] Determine if a hotfix is needed -- [ ] Merge `release-YYYY.MM` back into `main`. Don't overwrite the `-dev` version in package.json. (🤖) - -## Clean up after _this_ release - -- [ ] Go through [`info needed` issues](https://github.com/Microsoft/vscode-python/issues?q=is%3Aopen+label%3A%22info+needed%22+-label%3A%22data+science%22+sort%3Aupdated-asc) and close any that have no activity for over a month (🤖) -- [ ] GDPR bookkeeping (🤖) +### General Notes +All dates should align with VS Code's [iteration](https://github.com/microsoft/vscode/labels/iteration-plan) and [endgame](https://github.com/microsoft/vscode/labels/endgame-plan) plans. + +Feature freeze is Monday @ 17:00 America/Vancouver, XXX XX. At that point, commits to `main` should only be in response to bugs found during endgame testing until the release candidate is ready. + +
+ Release Primary and Secondary Assignments for the 2025 Calendar Year + +| Month and version number | Primary | Secondary | +|------------|----------|-----------| +| January v2025.0.0 | Eleanor | Karthik | +| February v2025.2.0 | Anthony | Eleanor | +| March v2025.4.0 | Karthik | Anthony | +| April v2025.6.0 | Eleanor | Karthik | +| May v2025.8.0 | Anthony | Eleanor | +| June v2025.10.0 | Karthik | Anthony | +| July v2025.12.0 | Eleanor | Karthik | +| August v2025.14.0 | Anthony | Eleanor | +| September v2025.16.0 | Karthik | Anthony | +| October v2025.18.0 | Eleanor | Karthik | +| November v2025.20.0 | Anthony | Eleanor | +| December v2025.22.0 | Karthik | Anthony | + +
+ + +# Release candidate (Thursday, XXX XX) +NOTE: This Thursday occurs during TESTING week. Branching should be done during this week to freeze the release with only the correct changes. Any last minute fixes go in as candidates into the release branch and will require team approval. + +Other: +NOTE: Third Party Notices are automatically added by our build pipelines using https://tools.opensource.microsoft.com/notice. +NOTE: the number of this release is in the issue title and can be substituted in wherever you see [YYYY.minor]. + + +### Step 1: +##### Bump the version of `main` to be a release candidate (also updating third party notices, and package-lock.json).❄️ (steps with ❄️ will dictate this step happens while main is frozen 🥶) + +- [ ] checkout to `main` on your local machine and run `git fetch` to ensure your local is up to date with the remote repo. +- [ ] Create a new branch called **`bump-release-[YYYY.minor]`**. +- [ ] Update `pet`: + - [ ] Go to the [pet](https://github.com/microsoft/python-environment-tools) repo and check `main` and latest `release/*` branch. If there are new changes in `main` then create a branch called `release/YYYY.minor` (matching python extension release `major.minor`). + - [ ] Update `build\azure-pipeline.stable.yml` to point to the latest `release/YYYY.minor` for `python-environment-tools`. +- [ ] Change the version in `package.json` to the next **even** number. (🤖) +- [ ] Run `npm install` to make sure `package-lock.json` is up-to-date _(you should now see changes to the `package.json` and `package-lock.json` at this point which update the version number **only**)_. (🤖) +- [ ] Update `ThirdPartyNotices-Repository.txt` as appropriate. You can check by looking at the [commit history](https://github.com/microsoft/vscode-python/commits/main) and scrolling through to see if there's anything listed there which might have pulled in some code directly into the repository from somewhere else. If you are still unsure you can check with the team. +- [ ] Create a PR from your branch **`bump-release-[YYYY.minor]`** to `main`. Add the `"no change-log"` tag to the PR so it does not show up on the release notes before merging it. + +NOTE: this PR will fail the test in our internal release pipeline called `VS Code (pre-release)` because the version specified in `main` is (temporarily) an invalid pre-release version. This is expected as this will be resolved below. + + +### Step 2: Creating your release branch ❄️ +- [ ] Create a release branch by creating a new branch called **`release/YYYY.minor`** branch from `main`. This branch is now the candidate for our release which will be the base from which we will release. + +NOTE: If there are release branches that are two versions old you can delete them at this time. + +### Step 3 Create a draft GitHub release for the release notes (🤖) ❄️ + +- [ ] Create a new [GitHub release](https://github.com/microsoft/vscode-python/releases/new). +- [ ] Specify a new tag called `YYYY.minor.0`. +- [ ] Have the `target` for the github release be your release branch called **`release/YYYY.minor`**. +- [ ] Create the release notes by specifying the previous tag for the last stable release and click `Generate release notes`. Quickly check that it only contain notes from what is new in this release. +- [ ] Click `Save draft`. + +### Step 4: Return `main` to dev and unfreeze (❄️ ➡ 💧) +NOTE: The purpose of this step is ensuring that main always is on a dev version number for every night's 🌃 pre-release. Therefore it is imperative that you do this directly after the previous steps to reset the version in main to a dev version **before** a pre-release goes out. +- [ ] Create a branch called **`bump-dev-version-YYYY.[minor+1]`**. +- [ ] Bump the minor version number in the `package.json` to the next `YYYY.[minor+1]` which will be an odd number, and add `-dev`.(🤖) +- [ ] Run `npm install` to make sure `package-lock.json` is up-to-date _(you should now see changes to the `package.json` and `package-lock.json` only relating to the new version number)_ . (🤖) +- [ ] Create a PR from this branch against `main` and merge it. + +NOTE: this PR should make all CI relating to `main` be passing again (such as the failures stemming from step 1). + +### Step 5: Notifications and Checks on External Release Factors +- [ ] Check [Component Governance](https://dev.azure.com/monacotools/Monaco/_componentGovernance/192726?_a=alerts&typeId=11825783&alerts-view-option=active) to make sure there are no active alerts. +- [ ] Manually add/fix any 3rd-party licenses as appropriate based on what the internal build pipeline detects. +- [ ] Open appropriate [documentation issues](https://github.com/microsoft/vscode-docs/issues?q=is%3Aissue+is%3Aopen+label%3Apython). +- [ ] Contact the PM team to begin drafting a blog post. +- [ ] Announce to the development team that `main` is open again. + + +# Release (Wednesday, XXX XX) + +### Step 6: Take the release branch from a candidate to the finalized release +- [ ] Make sure the [appropriate pull requests](https://github.com/microsoft/vscode-docs/pulls) for the [documentation](https://code.visualstudio.com/docs/python/python-tutorial) -- including the [WOW](https://code.visualstudio.com/docs/languages/python) page -- are ready. +- [ ] Check to make sure any final updates to the **`release/YYYY.minor`** branch have been merged. + +### Step 7: Execute the Release +- [ ] Make sure CI is passing for **`release/YYYY.minor`** release branch (🤖). +- [ ] Run the [CD](https://dev.azure.com/monacotools/Monaco/_build?definitionId=299) pipeline on the **`release/YYYY.minor`** branch. + - [ ] Click `run pipeline`. + - [ ] for `branch/tag` select the release branch which is **`release/YYYY.minor`**. + - NOTE: Please opt to release the python extension close to when VS Code is released to align when release notes go out. When we bump the VS Code engine number, our extension will not go out to stable until the VS Code stable release but this only occurs when we bump the engine number. +- [ ] 🧍🧍 Get approval on the release on the [CD](https://dev.azure.com/monacotools/Monaco/_build?definitionId=299). +- [ ] Click "approve" in the publish step of [CD](https://dev.azure.com/monacotools/Monaco/_build?definitionId=299) to publish the release to the marketplace. 🎉 +- [ ] Take the Github release out of draft. +- [ ] Publish documentation changes. +- [ ] Contact the PM team to publish the blog post. +- [ ] Determine if a hotfix is needed. +- [ ] Merge the release branch **`release/YYYY.minor`** back into `main`. (This step is only required if changes were merged into the release branch. If the only change made on the release branch is the version, this is not necessary. Overall you need to ensure you DO NOT overwrite the version on the `main` branch.) + + +## Steps for Point Release (if necessary) +- [ ] checkout to `main` on your local machine and run `git fetch` to ensure your local is up to date with the remote repo. +- [ ] checkout to the `release/YYY.minor` and check to make sure all necessary changes for the point release have been cherry-picked into the release branch. If not, contact the owner of the changes to do so. +- [ ] Create a branch against **`release/YYYY.minor`** called **`release-[YYYY.minor.point]`**. +- [ ] Bump the point version number in the `package.json` to the next `YYYY.minor.point` +- [ ] Run `npm install` to make sure `package-lock.json` is up-to-date _(you should now see changes to the `package.json` and `package-lock.json` only relating to the new version number)_ . (🤖) +- [ ] If Point Release is due to an issue in `pet`. Update `build\azure-pipeline.stable.yml` to point to the branch `release/YYYY.minor` for `python-environment-tools` with the fix or decided by the team. +- [ ] Create a PR from this branch against `release/YYYY.minor` +- [ ] **Rebase** and merge this PR into the release branch +- [ ] Create a draft GitHub release for the release notes (🤖) ❄️ + - [ ] Create a new [GitHub release](https://github.com/microsoft/vscode-python/releases/new). + - [ ] Specify a new tag called `vYYYY.minor.point`. + - [ ] Have the `target` for the github release be your release branch called **`release/YYYY.minor`**. + - [ ] Create the release notes by specifying the previous tag as the previous version of stable, so the minor release **`vYYYY.minor`** for the last stable release and click `Generate release notes`. + - [ ] Check the generated notes to ensure that all PRs for the point release are included so users know these new changes. + - [ ] Click `Save draft`. +- [ ] Publish the point release + - [ ] Make sure CI is passing for **`release/YYYY.minor`** release branch (🤖). + - [ ] Run the [CD](https://dev.azure.com/monacotools/Monaco/_build?definitionId=299) pipeline on the **`release/YYYY.minor`** branch. + - [ ] Click `run pipeline`. + - [ ] for `branch/tag` select the release branch which is **`release/YYYY.minor`**. + - [ ] 🧍🧍 Get approval on the release on the [CD](https://dev.azure.com/monacotools/Monaco/_build?definitionId=299) and publish the release to the marketplace. 🎉 + - [ ] Take the Github release out of draft. + +## Steps for contributing to a point release +- [ ] Work with team to decide if point release is necessary +- [ ] Work with team or users to verify the fix is correct and solves the problem without creating any new ones +- [ ] Create PR/PRs and merge then each into main as usual +- [ ] Make sure to still mark if the change is "bug" or "no-changelog" +- [ ] Cherry-pick all PRs to the release branch and check that the changes are in before the package is bumped +- [ ] Notify the release champ that your changes are in so they can trigger a point-release + ## Prep for the _next_ release -- [ ] Create a new [release plan](https://raw.githubusercontent.com/microsoft/vscode-python/main/.github/release_plan.md) (🤖) -- [ ] [(Un-)pin](https://help.github.com/en/articles/pinning-an-issue-to-your-repository) [release plan issues](https://github.com/Microsoft/vscode-python/labels/release%20plan) (🤖) +- [ ] Create a new [release plan](https://raw.githubusercontent.com/microsoft/vscode-python/main/.github/release_plan.md). (🤖) +- [ ] [(Un-)pin](https://help.github.com/en/articles/pinning-an-issue-to-your-repository) [release plan issues](https://github.com/Microsoft/vscode-python/labels/release-plan) (🤖) diff --git a/.github/test_plan.md b/.github/test_plan.md deleted file mode 100644 index 8183c3dda071..000000000000 --- a/.github/test_plan.md +++ /dev/null @@ -1,637 +0,0 @@ -# Test plan - -## Environment - -- OS: XXX (Windows, macOS, latest Ubuntu LTS) - - Shell: XXX (Command Prompt, PowerShell, bash, fish) -- Python - - Distribution: XXX (CPython, miniconda) - - Version: XXX (2.7, latest 3.x) -- VS Code: XXX (Insiders) - -## Tests - -**ALWAYS**: - -- Check the `Output` window under `Python` for logged errors -- Have `Developer Tools` open to detect any errors -- Consider running the tests in a multi-folder workspace -- Focus on in-development features (i.e. experimental debugger and language server) - -
- Scenarios - -### [Environment](https://code.visualstudio.com/docs/python/environments) - -#### Interpreters - -- [ ] Interpreter is [shown in the status bar](https://code.visualstudio.com/docs/python/environments#_choosing-an-environment) -- [ ] An interpreter can be manually specified using the [`Select Interpreter` command](https://code.visualstudio.com/docs/python/environments#_choosing-an-environment) -- [ ] Detected system-installed interpreters -- [ ] Detected an Anaconda installation -- [ ] (Linux/macOS) Detected all interpreters installed w/ [pyenv](https://github.com/pyenv/pyenv) detected -- [ ] [`"python.pythonPath"`](https://code.visualstudio.com/docs/python/environments#_manually-specifying-an-interpreter) triggers an update in the status bar -- [ ] `Run Python File in Terminal` -- [ ] `Run Selection/Line in Python Terminal` - - [ ] Right-click - - [ ] Command - - [ ] `Shift+Enter` - -#### Terminal - -Sample file: - -```python -import requests -request = requests.get("https://drive.google.com/uc?export=download&id=1_9On2-nsBQIw3JiY43sWbrF8EjrqrR4U") -with open("survey2017.zip", "wb") as file: - file.write(request.content) -import zipfile -with zipfile.ZipFile('survey2017.zip') as zip: - zip.extractall('survey2017') -import shutil, os -shutil.move('survey2017/survey_results_public.csv','survey2017.csv') -shutil.rmtree('survey2017') -os.remove('survey2017.zip') -``` - -- [ ] _Shift+Enter_ to send selected code in sample file to terminal works - -#### Virtual environments - -**ALWAYS**: - -- Use the latest version of Anaconda -- Realize that `conda` is slow -- Create an environment with a space in their path somewhere as well as upper and lowercase characters -- Make sure that you do not have `python.pythonPath` specified in your `settings.json` when testing automatic detection -- Do note that the `Select Interpreter` drop-down window scrolls - -- [ ] Detected a single virtual environment at the top-level of the workspace folder on Mac when when `python` command points to default Mac Python installation or `python` command fails in the terminal. - - [ ] Appropriate suffix label specified in status bar (e.g. `(venv)`) -- [ ] Detected a single virtual environment at the top-level of the workspace folder on Windows when `python` fails in the terminal. - - [ ] Appropriate suffix label specified in status bar (e.g. `(venv)`) -- [ ] Detected a single virtual environment at the top-level of the workspace folder - - [ ] Appropriate suffix label specified in status bar (e.g. `(venv)`) - - [ ] [`Create Terminal`](https://code.visualstudio.com/docs/python/environments#_activating-an-environment-in-the-terminal) works - - [ ] Steals focus - - [ ] `"python.terminal.activateEnvironment": false` deactivates automatically running the activation script in the terminal - - [ ] After the language server downloads it is able to complete its analysis of the environment w/o requiring a restart -- [ ] Detect multiple virtual environments contained in the directory specified in `"python.venvPath"` -- [ ] Detected all [conda environments created with an interpreter](https://code.visualstudio.com/docs/python/environments#_conda-environments) - - [ ] Appropriate suffix label specified in status bar (e.g. `(condaenv)`) - - [ ] Prompted to install Pylint - - [ ] Asked whether to install using conda or pip - - [ ] Installs into environment - - [ ] [`Create Terminal`](https://code.visualstudio.com/docs/python/environments#_activating-an-environment-in-the-terminal) works - - [ ] `"python.terminal.activateEnvironment": false` deactivates automatically running the activation script in the terminal - - [ ] After the language server downloads it is able to complete its analysis of the environment w/o requiring a restart -- [ ] (Linux/macOS until [`-m` is supported](https://github.com/Microsoft/vscode-python/issues/978)) Detected the virtual environment created by [pipenv](https://docs.pipenv.org/) - - [ ] Appropriate suffix label specified in status bar (e.g. `(pipenv)`) - - [ ] Prompt to install Pylint uses `pipenv install --dev` - - [ ] [`Create Terminal`](https://code.visualstudio.com/docs/python/environments#_activating-an-environment-in-the-terminal) works - - [ ] `"python.terminal.activateEnvironment": false` deactivates automatically running the activation script in the terminal - - [ ] After the language server downloads it is able to complete its analysis of the environment w/o requiring a restart -- [ ] (Linux/macOS) Virtual environments created under `{workspaceFolder}/.direnv/python-{python_version}` are detected (for [direnv](https://direnv.net/) and its [`layout python3`](https://github.com/direnv/direnv/blob/master/stdlib.sh) support) - - [ ] Appropriate suffix label specified in status bar (e.g. `(venv)`) - -#### [Environment files](https://code.visualstudio.com/docs/python/environments#_environment-variable-definitions-file) - -Sample files: - -```python -# example.py -import os -print('Hello,', os.environ.get('WHO'), '!') -``` - -``` -# .env -WHO=world -PYTHONPATH=some/path/somewhere -SPAM='hello ${WHO}' -``` - -**ALWAYS**: - -- Make sure to use `Reload Window` between tests to reset your environment -- Note that environment files only apply under the debugger and Jedi - -- [ ] Environment variables in a `.env` file are exposed when running under the debugger -- [ ] `"python.envFile"` allows for specifying an environment file manually (e.g. Jedi picks up `PYTHONPATH` changes) -- [ ] `envFile` in a `launch.json` configuration works -- [ ] simple variable substitution works - -#### [Debugging](https://code.visualstudio.com/docs/python/environments#_python-interpreter-for-debugging) - -- [ ] `pythonPath` setting in your `launch.json` overrides your `python.pythonPath` default setting - -### [Linting](https://code.visualstudio.com/docs/python/linting) - -**ALWAYS**: - -- Check under the `Problems` tab to see e.g. if a linter is raising errors - -#### Language server - -- [ ] LS is downloaded using HTTP (no SSL) when the "http.proxyStrictSSL" setting is false -- [ ] An item with a cloud icon appears in the status bar indicating progress while downloading the language server -- [ ] Installing [`requests`](https://pypi.org/project/requests/) in virtual environment is detected - - [ ] Import of `requests` without package installed is flagged as unresolved - - [ ] Create a virtual environment - - [ ] Install `requests` into the virtual environment - -#### Pylint/default linting - -[Prompting to install Pylint is covered under `Environments` above] - -For testing the disablement of the default linting rules for Pylint: - -```ini -# pylintrc -[MESSAGES CONTROL] -enable=bad-names -``` - -```python3 -# example.py -foo = 42 # Marked as a disallowed name. -``` - -- [ ] Installation via the prompt installs Pylint as appropriate - - [ ] Uses `--user` for system-install of Python - - [ ] Installs into a virtual environment environment directly -- [ ] Pylint works -- [ ] `"python.linting.pylintUseMinimalCheckers": false` turns off the default rules w/ no `pylintrc` file present -- [ ] The existence of a `pylintrc` file turns off the default rules - -#### Other linters - -**Note**: - -- You can use the `Run Linting` command to run a newly installed linter -- When the extension installs a new linter, it turns off all other linters - -- [ ] flake8 works - - [ ] `Select linter` lists the linter and installs it if necessary -- [ ] mypy works - - [ ] `Select linter` lists the linter and installs it if necessary -- [ ] pycodestyle works - - [ ] `Select linter` lists the linter and installs it if necessary -- [ ] prospector works - - [ ] `Select linter` lists the linter and installs it if necessary -- [ ] pydocstyle works - - [ ] `Select linter` lists the linter and installs it if necessary -- [ ] pylama works - - [ ] `Select linter` lists the linter and installs it if necessary -- [ ] 3 or more linters work simultaneously (make sure you have turned on the linters in your `settings.json`) - - [ ] `Run Linting` runs all activated linters - - [ ] `"python.linting.enabled": false` disables all linters - - [ ] The `Enable Linting` command changes `"python.linting.enabled"` -- [ ] `"python.linting.lintOnSave` works - -### [Editing](https://code.visualstudio.com/docs/python/editing) - -#### [IntelliSense](https://code.visualstudio.com/docs/python/editing#_autocomplete-and-intellisense) - -Please also test for general accuracy on the most "interesting" code you can find. - -- [ ] `"python.autoComplete.extraPaths"` works -- [ ] `"python.autocomplete.addBrackets": true` causes auto-completion of functions to append `()` -- [ ] Auto-completions works - -#### [Formatting](https://code.visualstudio.com/docs/python/editing#_formatting) - -Sample file: - -```python -# There should be _some_ change after running `Format Document`. -import os,sys; -def foo():pass -``` - -- [ ] Prompted to install a formatter if none installed and `Format Document` is run - - [ ] Installing `autopep8` works - - [ ] Installing `black` works - - [ ] Installing `yapf` works -- [ ] Formatters work with default settings (i.e. `"python.formatting.provider"` is specified but not matching `*Path`or `*Args` settings) - - [ ] autopep8 - - [ ] black - - [ ] yapf -- [ ] Formatters work when appropriate `*Path` and `*Args` settings are specified (use absolute paths; use `~` if possible) - - [ ] autopep8 - - [ ] black - - [ ] yapf -- [ ] `"editor.formatOnType": true` works and has expected results - -#### [Refactoring](https://code.visualstudio.com/docs/python/editing#_refactoring) - -- [ ] [`Extract Variable`](https://code.visualstudio.com/docs/python/editing#_extract-variable) works - - [ ] You are prompted to install `rope` if it is not already available -- [ ] [`Extract method`](https://code.visualstudio.com/docs/python/editing#_extract-method) works - - [ ] You are prompted to install `rope` if it is not already available -- [ ] [`Sort Imports`](https://code.visualstudio.com/docs/python/editing#_sort-imports) works - -### [Debugging](https://code.visualstudio.com/docs/python/debugging) - -- [ ] [Configurations](https://code.visualstudio.com/docs/python/debugging#_debugging-specific-app-types) work (see [`package.json`](https://github.com/Microsoft/vscode-python/blob/main/package.json) and the `"configurationSnippets"` section for all of the possible configurations) -- [ ] Running code from start to finish w/ no special debugging options (e.g. no breakpoints) -- [ ] Breakpoint-like things - - [ ] Breakpoint - - [ ] Set - - [ ] Hit - - [ ] Conditional breakpoint - - [ ] Expression - - [ ] Set - - [ ] Hit - - [ ] Hit count - - [ ] Set - - [ ] Hit - - [ ] Logpoint - - [ ] Set - - [ ] Hit -- [ ] Stepping - - [ ] Over - - [ ] Into - - [ ] Out -- [ ] Can inspect variables - - [ ] Through hovering over variable in code - - [ ] `Variables` section of debugger sidebar -- [ ] [Remote debugging](https://code.visualstudio.com/docs/python/debugging#_remote-debugging) works - - [ ] ... over SSH - - [ ] ... on other branches -- [ ] [App Engine](https://code.visualstudio.com/docs/python/debugging#_google-app-engine-debugging) - -### [Unit testing](https://code.visualstudio.com/docs/python/unit-testing) - -#### [`unittest`](https://code.visualstudio.com/docs/python/unit-testing#_unittest-configuration-settings) - -```python -import unittest - -MODULE_SETUP = False - - -def setUpModule(): - global MODULE_SETUP - MODULE_SETUP = True - - -class PassingSetupTests(unittest.TestCase): - CLASS_SETUP = False - METHOD_SETUP = False - - @classmethod - def setUpClass(cls): - cls.CLASS_SETUP = True - - def setUp(self): - self.METHOD_SETUP = True - - def test_setup(self): - self.assertTrue(MODULE_SETUP) - self.assertTrue(self.CLASS_SETUP) - self.assertTrue(self.METHOD_SETUP) - - -class PassingTests(unittest.TestCase): - - def test_passing(self): - self.assertEqual(42, 42) - - def test_passing_still(self): - self.assertEqual("silly walk", "silly walk") - - -class FailingTests(unittest.TestCase): - - def test_failure(self): - self.assertEqual(42, -13) - - def test_failure_still(self): - self.assertEqual("I'm right!", "no, I am!") -``` - -- [ ] `Run All Unit Tests` triggers the prompt to configure the test runner -- [ ] Tests are discovered (as shown by code lenses on each test) - - [ ] Code lens for a class runs all tests for that class - - [ ] Code lens for a method runs just that test - - [ ] `Run Test` works - - [ ] `Debug Test` works - - [ ] Module/suite setup methods are also run (run the `test_setup` method to verify) -- [ ] while debugging tests, an uncaught exception in a test does not - cause `debugpy` to raise `SystemExit` exception. - -#### [`pytest`](https://code.visualstudio.com/docs/python/unit-testing#_pytest-configuration-settings) - -```python -def test_passing(): - assert 42 == 42 - -def test_failure(): - assert 42 == -13 -``` - -- [ ] `Run All Unit Tests` triggers the prompt to configure the test runner - - [ ] `pytest` gets installed -- [ ] Tests are discovered (as shown by code lenses on each test) - - [ ] `Run Test` works - - [ ] `Debug Test` works -- [ ] A `Diagnostic` is shown in the problems pane for each failed/skipped test - - [ ] The `Diagnostic`s are organized according to the file the test was executed from (not necessarily the file it was defined in) - - [ ] The appropriate `DiagnosticRelatedInformation` is shown for each `Diagnostic` - - [ ] The `DiagnosticRelatedInformation` reflects the traceback for the test - -#### [`nose`](https://code.visualstudio.com/docs/python/unit-testing#_nose-configuration-settings) - -```python -def test_passing(): - assert 42 == 42 - -def test_failure(): - assert 42 == -13 -``` - -- [ ] `Run All Unit Tests` triggers the prompt to configure the test runner - - [ ] Nose gets installed -- [ ] Tests are discovered (as shown by code lenses on each test) - - [ ] `Run Test` works - - [ ] `Debug Test` works - -#### General - -- [ ] Code lenses appears - - [ ] `Run Test` lens works (and status bar updates as appropriate) - - [ ] `Debug Test` lens works - - [ ] Appropriate ✔/❌ shown for each test -- [ ] Status bar is functioning - - [ ] Appropriate test results displayed - - [ ] `Run All Unit Tests` works - - [ ] `Discover Unit Tests` works (resets tests result display in status bar) - - [ ] `Run Unit Test Method ...` works - - [ ] `View Unit Test Output` works - - [ ] After having at least one failure, `Run Failed Tests` works -- [ ] `Configure Unit Tests` works - - [ ] quick pick for framework (and its settings) - - [ ] selected framework enabled in workspace settings - - [ ] framework's config added (and old config removed) - - [ ] other frameworks disabled in workspace settings -- [ ] `Configure Unit Tests` does not close if it loses focus -- [ ] Cancelling configuration does not leave incomplete settings -- [ ] The first `"request": "test"` entry in launch.json is used for running unit tests - -### [Data Science](https://code.visualstudio.com/docs/python/jupyter-support) - -#### P0 Test Scenarios - -- [ ] Start and connect to local Jupyter server - 1. Open the file src/test/datascience/manualTestFiles/manualTestFile.py in VSCode - 1. At the top of the file it will list the things that you need installed in your Python environment - 1. On the first cell click `Run Below` - 1. Interactive Window should open, show connection information, and execute cells - 1. The first thing in the window should have a line like this: `Jupyter Server URI: http://localhost:[port number]/?token=[token value]` -- [ ] Verify Basic Notebook Editor - 1. Create a new file in VS code with the extension .ipynb - 1. Open the file - 1. The Notebook Editor should open - 1. Verify that there is a single cell in the notebook editor - 1. Add `print('bar')` to that cell - 1. Run the cell - 1. Verify that `bar` shows up below the input - 1. Add a cell with the topmost hover bar - 1. Verify the cell appears above all others - 1. Add a cell at the bottom with the bottom most hover bar - 1. Verify the cell appears below all cells - 1. Select a cell - 1. Add a cell with the plus button on the cell - 1. Verify cell appears below - 1. Repeat with the topmost toolbar -- [ ] Verify basic outputs - 1. Run all the cells in manualTestFile.py - 1. Check to make sure that no outputs have errors - 1. Verify that graphs and progress bars are shown -- [ ] Verify export / import - 1. With the results from `Start and connect to local server` open click the `Export as Jupyter Notebook` button in the Interactive Window - 1. Choose a file location and save the generated .ipynb file - 1. When the prompt comes up in the lower right choose to open the file in the browser - 1. The file should open in the web browser and contain the output from the Interactive Window - 1. Try the same steps and choose to open the file in the ipynb editor. - 1. The file should open in the Notebook Editor. -- [ ] Verify text entry - 1. In the Interactive Window type in some new code `print('testing')` and submit it to the Interactive Windows - 1. Verify the output from what you added -- [ ] Verify dark and light main themes - 1. Repeat the `Start and connect to local server` and `Verify basic outputs` steps using `Default Dark+` and `Default Light+` themes -- [ ] Verify Variable Explorer - 1. After manualTestFile.py has been run drop down the Variables section at the top of the Interactive Window - 1. In the Variables list there should be an entry for all variables created. These variables might change as more is added to manualTestFile.py. - 1. Check that variables have expected values. They will be truncated for longer items - 1. Sort the list ascending and descending by Type. Also sort the list ascending and descending by Count. Values like (X, Y) use the first X value for Count sort ordering - 1. Check that list, Series, ndarray, and DataFrame types have a button to "Show variable in data viewer" on the right - 1. In the Interactive Window input box add a new variable. Verify that it is added into the Variable Explorer - 1. Export the contents of the interactive window as a notebook and open the notebook editor - 1. Find the first cell and click on the Run Below button - 1. Open the variable explorer and verify the same variables are there - 1. Add a new cell with a variable in it. - 1. Run the cell and verify the variable shows up in the variable explorer -- [ ] Verify Data Explorer - 1. From the listed types in the Variable explorer open up the Data Viewer by clicking the button or double clicking the row - 1. Inspect the data in the Data Viewer for the expected values - [ ] Verify Sorting and Filtering - 1. Open up the myDataFrame item - 1. Sort the name column ascending and descending - 1. Sort one of the numerical columns ascending and descending - 1. Click the Filter Rows button - 1. In the name filter box input 'a' to filter to just name with an a in them - 1. In one of the numerical columns input a number 1 - 9 to filter to just that column - 1. Open the myList variable in the explorer - 1. Make sure that you can scroll all the way to the end of the entries - [ ] Verify notebook outputs - 1. Open the src/test/datascience/manualTestFiles/manualTestFile.py in VSCode. - 1. Run all of the cells in the file. - 1. Interactive Window should open - 1. Export the cells in the interactive window and open the notebook editor - 1. Run all the cells in the notebook editor and verify the same outputs appear as in the interactive window -- [ ] Verify Notebook Editor Intellisense - 1. Open the src/test/datascience/manualTestFiles/manualTestFile.py in VSCode. - 1. Run all of the cells in the file. - 1. Interactive Window should open - 1. Export the cells in the interactive window and open the notebook editor - 1. Hover over each cell in the notebook editor and verify you get hover intellisense - 1. Add a new cell in between cells - 1. Add `import sys` and `sys.executable` to the cell - 1. Move the cell around and verify intellisense hover still works on the `import sys` - 1. Delete and readd the cell and verify intellisense hover still works. -- [ ] Verify Notebook Keyboard Shortcuts - 1. Using the notebook generated from the manualTestFile.py, do the following - 1. Select a cell by clicking on it - 1. Move selection up and down with j,k and arrow keys. - 1. Focus a cell by double clicking on it or hitting the enter key when selected - 1. Move selection through the code with the arrow keys. - 1. Verify selection travels between focused cells - 1. Hit escape when a cell has focus and verify it becomes selected instead of focused - 1. Hit `y` on a markdown cell when not focused and see that it becomes a code cell - 1. Hit `m` on a code cell when not focused and see that it becomes a markdown cell - 1. Hit `l` on a code cell and verify line numbers appear - 1. Hit `o` on a code cell with output and verify that outputs disappear - 1. Hit `d` + `d` and verify a cell is deleted. - 1. Hit `z` and verify a deleted cell reappears - 1. Hit `a` and verify the selected cell moves up - 1. Hit `b` and verify the selected cell moves down - 1. Hit `shift+enter` and verify a cell runs and selection moves to the next cell - 1. Hit `alt+enter` and verify a cell runs and a new cell is added below - 1. Hit `ctrl+enter` and verify a cell runs and selection does not change -- [ ] Verify debugging - 1. Open the file src/test/datascience/manualTestFiles/manualTestFile.py in VSCode - 1. On the first cell click `Run Below` - 1. Interactive Window should open, show connection information, and execute cells - 1. Go back to the first cell and click `Debug Cell` - 1. Debugger should start and have an ip indicator on the first line of the cell - 1. Step through the debugger. - 1. Verify the variables tab of the debugger shows variables. - 1. Verify the variables explorer window shows output not available while debugging - 1. When you get to the end of the cell, the debugger should stop - 1. Output from the cell should show up in the Interactive Window (sometimes you have to finish debugging the cell first) -- [ ] Verify installing ipykernel in a new environment - 1. Create a brand new folder on your machine - 1. Create a new venv in that folder via command line / terminal `python3 -m venv .newEnv` - 1. Open that folder in VS Code and copy the manual test file there - 1. Select the newly created venv by running Ctrl+Shift+P, typing 'Python: Select Interpreter' into the VS Code command palette, and selecting the new venv from the dropdown. If the new venv doesn't appear in the quickpick you may need to reload VS Code and reattempt this step. - 1. Execute the manual test file, you should be prompted to install ipykernel in `.newEnv` - 1. After ipykernel is installed execution of the file should continue successfully - -#### P1 Test Scenarios - -- [ ] Connect to a `remote` server - 1. Open up a valid python command prompt that can run `jupyter notebook` (a default Anaconda prompt works well) - 1. Run `jupyter notebook` to start up a local Jupyter server - 1. In the command window that launched Jupyter look for the server / token name like so: http://localhost:8888/?token=bf9eae43641cd75015df9104f814b8763ef0e23ffc73720d - 1. Run the command `Python: Select Jupyter server URI` then `Type in the URI to connect to a running jupyter server` - 1. Input the server / token name here - 1. Now run the cells in the manualTestFile.py - 1. Verify that you see the server name in the initial connection message - 1. Verify the outputs of the cells -- [ ] Interactive Window commands - - [ ] Verify per-cell commands - 1. Expand and collapse the input area of a cell - 1. Use the `X` button to remove a cell - 1. Use the `Goto Code` button to jump to the part of the .py file that submitted the code - - [ ] Verify top menu commands - 1. Use `X` to delete all cells - 1. Undo the delete action with `Undo` - 1. Redo the delete action with `Redo` - 1. In manualTestFile.py modify the trange command in the progress bar from 100 to 2000. Run the Cell. As the cell is running hit the `Interrupt iPython Kernel` button - 1. The progress bar should be interrupted and you should see a KeyboardInterrupt error message in the output - 1. Test the `Restart iPython kernel` command. Kernel should be restarted and you should see a status output message for the kernel restart - 1. Use the expand all input and collapse all input commands to collapse all cell inputs -- [ ] Verify theming works - 1. Start Python Interactive window - 1. Add a cell with some comments - 1. Switch VS Code theme to something else - 1. Check that the cell you just added updates the comment color - 1. Switch back and forth between a 'light' and a 'dark' theme - 1. Check that the cell switches colors - 1. Check that the buttons on the top change to their appropriate 'light' or 'dark' versions - 1. Enable the 'ignoreVscodeTheme' setting - 1. Close the Python Interactive window and reopen it. The theme in just the 'Python Interactive' window should be light - 1. Switch to a dark theme. Make sure the interactive window remains in the light theme. -- [ ] Verify code lenses - 1. Check that `Run Cell` `Run Above` and `Run Below` all do the correct thing -- [ ] Verify context menu navigation commands - 1. Check the `Run Current Cell` and `Run Current Cell And Advance` context menu commands - 1. If run on the last cell of the file `Run Current Cell And Advance` should create a new empty cell and advance to it -- [ ] Verify command palette commands - 1. Close the Interactive Window then pick `Python: Show Interactive Window` - 1. Restart the kernel and pick `Python: Run Current File In Python Interactive Window` it should run the whole file again -- [ ] Verify shift-enter - 1. Move to the top cell in the .py file - 1. Shift-enter should run each cell and advance to the next - 1. Shift-enter on the final cell should create a new cell and move to it -- [ ] Verify file without cells - 1. Open the manualTestFileNoCells.py file - 1. Select a chunk of code, shift-enter should send it to the terminal - 1. Open VSCode settings, change `Send Selection To Interactive Window` to true - 1. Select a chunk of code, shift-enter should send that selection to the Interactive Windows - 1. Move your cursor to a line, but don't select anything. Shift-enter should send that line to the Interactive Window -- [ ] Multiple installs - 1. Close and re-open VSCode to make sure that all jupyter servers are closed - 1. Also make sure you are set to locally launch Jupyter and not to connect to an existing URI - 1. In addition to your main testing environment install a new python or miniconda install (conda won't work as it has Jupyter by default) - 1. In VS code change the python interpreter to the new install - 1. Try `Run Cell` - 1. You should get a message that Jupyter was not found and that it is defaulting back to launch on the python instance that has Jupyter -- [ ] LiveShare Support - 1. Install the LiveShare VSCode Extension - 1. Open manualTestFile.py in VSCode - 1. Run the first cell in the file - 1. Switch to the `Live Share` tab in VS Code and start a session - - [ ] Verify server start - 1. Jupyter server instance should appear in the live share tab - 1. Open another window of VSCode - 1. Connect the second instance of VSCode as a Guest to the first Live Share session - 1. After the workspace opens, open the manualTestFile.py on the Guest instance - 1. On the Guest instance run a cell from the file, both via the codelens and via the command palette `Run Cell` command - - [ ] Verify results - 1. Output should show up on the Guest Interactive Window - 1. Same output should show up in the Host Interactive Window - 1. On the Host instance run a cell from the file, both via the codelens and via the command palette - - [ ] Verify results - 1. Output should show up on the Guest Interactive Window - 1. Same output should show up in the Host Interactive Window - 1. Export the file to a notebook - 1. Open the notebook editor on the host - 1. Run a cell on the host - 1. Verify the editor opens on the guest and the cell is run there too -- [ ] Jupyter Hub support - - 1. Windows install instructions - - 1. Install Docker Desktop onto a machine - 1. Create a folder with a file 'Dockerfile' in it. - 1. Mark the file to look like so: - - ``` - ARG BASE_CONTAINER=jupyterhub/jupyterhub - FROM $BASE_CONTAINER - - USER root - - USER $NB_UID - ``` - - 1. From a command prompt (in the same folder as the Dockerfile), run `docker build -t jupyterhubcontainer:1.0 .` - 1. Run `docker container create --name jupyterhub jupyterhubcontainer:1.0 jupyterhub` - 1. From the docker desktop app, start the jupyterhub container. - 1. From the docker desktop app, run the CLI - - 1. OR Mac / Linux install instructions - 1. Install docker - 1. From the terminal `docker run -p 8000:8000 -d --name jupyterhub jupyterhub/jupyterhub jupyterhub` - 1. Open a terminal in the docker container with `docker exec -it jupyterhub bash` - 1. From that terminal run `python3 -m pip install notebook` - 1. From the new command prompt, run `adduser testuser` - 1. Follow the series of prompts to add a password for this user - 1. Open VS code - 1. Open a folder with a python file in it. - 1. Run the `Python: Specify local or remote Jupyter server for connections` command. - 1. Pick 'Existing' - 1. Enter `http://localhost:8000` (assuming the jupyter hub container was successful in launching) - 1. Reload VS code and reopen this folder. - 1. Run a cell in a python file. - [ ] Verify results 1. Verify you are asked first for a user name and then a password. 1. Verify a cell runs once you enter the user name and password 1. Verify that the python that is running in the interactive window is from the docker container (if on windows it should show a linux path) - -#### P2 Test Scenarios - -- [ ] Directory change - - [ ] Verify directory change in export - 1. Follow the previous steps for export, but export the ipynb to a directory outside of the current workspace - 1. Open the file in the browser, you should get an initial cell added to change directory back to your workspace directory - - [ ] Verify directory change in import - 1. Follow the previous steps for import, but import an ipynb that is located outside of your current workspace - 1. Open the file in the editor. There should be python code at the start to change directory to the previous location of the .ipynb file -- [ ] Interactive Window input history history - 1. Start up an Interactive Window session - 1. Input several lines into the Interactive Window terminal - 1. Press up to verify that those previously entered lines show in the Interactive Window terminal history -- [ ] Extra themes 1. Try several of the themes that come with VSCode that are not the default Dark+ and Light+ -
diff --git a/.github/workflows/assignIssue.yml b/.github/workflows/assignIssue.yml deleted file mode 100644 index 624ebedbd99c..000000000000 --- a/.github/workflows/assignIssue.yml +++ /dev/null @@ -1,123 +0,0 @@ -name: Assign DS issue to someone -on: - issues: - types: [opened, labeled] -jobs: - assignIssue: - name: Assign Issue to Someone - runs-on: ubuntu-latest - if: github.repository == 'microsoft/vscode-python' - steps: - - name: Created internally - id: internal - env: - ISSUE_OWNER: ${{github.event.issue.owner.login}} - run: | - echo ::set-output name=result::$(node -p -e "['rchiodo', 'greazer', 'joyceerhl', 'DavidKutu', 'claudiaregio', 'IanMatthewHuff', 'DonJayamanne'].filter(item => process.env.ISSUE_OWNER.toLowerCase() === item.toLowerCase()).length > 0 ? 1 : 0") - shell: bash - - name: Should we proceed - id: proceed - env: - ISSUE_LABELS: ${{toJson(github.event.issue.labels)}} - ISSUE_ASSIGNEES: ${{toJson(github.event.issue.assignees)}} - ISSUE_IS_INTERNAL: ${{steps.internal.outputs.result}} - run: | - echo ::set-output name=result::$(node -p -e "process.env.ISSUE_IS_INTERNAL === '0' && JSON.parse(process.env.ISSUE_ASSIGNEES).length === 0 && JSON.parse(process.env.ISSUE_LABELS).filter(item => item.name.indexOf('data science') >= 0).length === 1 ? 1 : 0") - shell: bash - - uses: actions/checkout@v2 - if: steps.proceed.outputs.result == 1 - - name: Day of week - if: steps.proceed.outputs.result == 1 - id: day - run: | - echo ::set-output name=number::$(node -p -e "new Date().getDay()") - shell: bash - - name: Hour of day - if: steps.proceed.outputs.result == 1 - id: hour - run: | - echo ::set-output name=hour::$(node -p -e "(new Date().getUTCHours() - 7)%24") - shell: bash - - name: Week Number - if: steps.proceed.outputs.result == 1 - id: week - run: | - echo ::set-output name=odd::$(node .github/workflows/week.js) - shell: bash - - name: Print day and week - if: steps.proceed.outputs.result == 1 - run: | - echo ${{steps.day.outputs.number}} - echo ${{steps.week.outputs.odd}} - echo ${{steps.hour.outputs.hour}} - shell: bash - - name: Even late friday (David) - if: steps.proceed.outputs.result == 1 && steps.week.outputs.odd == 0 && steps.day.outputs.number == 5 && steps.hour.outputs.hour >= 16 - uses: actions/github@v1.0.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - args: assign DavidKutu - - name: Odd late friday (Joyce) - if: steps.proceed.outputs.result == 1 && steps.week.outputs.odd == 1 && steps.day.outputs.number == 5 && steps.hour.outputs.hour >= 16 - uses: actions/github@v1.0.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - args: assign joyceerhl - - name: Even weekends (David) - if: steps.proceed.outputs.result == 1 && steps.week.outputs.odd == 0 && (steps.day.outputs.number == 6 || steps.day.outputs.number == 0) - uses: actions/github@v1.0.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - args: assign DavidKutu - - name: Odd weekends (Joyce) - if: steps.proceed.outputs.result == 1 && steps.week.outputs.odd == 1 && (steps.day.outputs.number == 6 || steps.day.outputs.number == 0) - uses: actions/github@v1.0.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - args: assign joyceerhl - - name: Odd Monday (David) - if: steps.proceed.outputs.result == 1 && steps.week.outputs.odd == 1 && steps.day.outputs.number == 1 && steps.hour.outputs.hour < 16 - uses: actions/github@v1.0.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - args: assign DavidKutu - - name: Even Monday (Joyce) - if: steps.proceed.outputs.result == 1 && steps.week.outputs.odd == 0 && steps.day.outputs.number == 1 && steps.hour.outputs.hour < 16 - uses: actions/github@v1.0.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - args: assign joyceerhl - - name: Tuesday (Ian) - if: steps.proceed.outputs.result == 1 && (steps.day.outputs.number == 1 && steps.hour.outputs.hour >= 16) || (steps.day.outputs.number == 2 && steps.hour.outputs.hour < 16) - uses: actions/github@v1.0.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - args: assign IanMatthewHuff - - name: Wednesday (Rich) - if: steps.proceed.outputs.result == 1 && (steps.day.outputs.number == 2 && steps.hour.outputs.hour >= 16) || (steps.day.outputs.number == 3 && steps.hour.outputs.hour < 16) - uses: actions/github@v1.0.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - args: assign rchiodo - - name: Thursday (Don) - if: steps.proceed.outputs.result == 1 && (steps.day.outputs.number == 3 && steps.hour.outputs.hour >= 16) || (steps.day.outputs.number == 4 && steps.hour.outputs.hour < 16) - uses: actions/github@v1.0.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - args: assign DonJayamanne - - name: Friday (Claudia) - if: steps.proceed.outputs.result == 1 && (steps.day.outputs.number == 4 && steps.hour.outputs.hour >= 16) || (steps.day.outputs.number == 5 && steps.hour.outputs.hour < 16) - uses: actions/github@v1.0.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - args: assign claudiaregio diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000000..09d019dec4a7 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,449 @@ +name: Build + +on: + push: + branches: + - 'main' + - 'release' + - 'release/*' + - 'release-*' + +permissions: {} + +env: + NODE_VERSION: 22.21.1 + PYTHON_VERSION: '3.10' # YML treats 3.10 the number as 3.1, so quotes around 3.10 + # Force a path with spaces and to test extension works in these scenarios + # Unicode characters are causing 2.7 failures so skip that for now. + special-working-directory: './path with spaces' + special-working-directory-relative: 'path with spaces' + # Use the mocha-multi-reporters and send output to both console (spec) and JUnit (mocha-junit-reporter). + # Also enables a reporter which exits the process running the tests if it haven't already. + MOCHA_REPORTER_JUNIT: true + +jobs: + setup: + name: Set up + if: github.repository == 'microsoft/vscode-python' + runs-on: ubuntu-latest + defaults: + run: + shell: python + outputs: + vsix_basename: ${{ steps.vsix_names.outputs.vsix_basename }} + vsix_name: ${{ steps.vsix_names.outputs.vsix_name }} + vsix_artifact_name: ${{ steps.vsix_names.outputs.vsix_artifact_name }} + steps: + - name: VSIX names + id: vsix_names + run: | + import os + if os.environ["GITHUB_REF"].endswith("/main"): + vsix_type = "insiders" + else: + vsix_type = "release" + print(f"::set-output name=vsix_name::ms-python-{vsix_type}.vsix") + print(f"::set-output name=vsix_basename::ms-python-{vsix_type}") + print(f"::set-output name=vsix_artifact_name::ms-python-{vsix_type}-vsix") + + build-vsix: + name: Create VSIX + if: github.repository == 'microsoft/vscode-python' + needs: setup + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: windows-latest + target: x86_64-pc-windows-msvc + vsix-target: win32-x64 + - os: windows-latest + target: aarch64-pc-windows-msvc + vsix-target: win32-arm64 + - os: ubuntu-latest + target: x86_64-unknown-linux-musl + vsix-target: linux-x64 + # - os: ubuntu-latest + # target: aarch64-unknown-linux-gnu + # vsix-target: linux-arm64 + # - os: ubuntu-latest + # target: arm-unknown-linux-gnueabihf + # vsix-target: linux-armhf + # - os: macos-latest + # target: x86_64-apple-darwin + # vsix-target: darwin-x64 + # - os: macos-14 + # target: aarch64-apple-darwin + # vsix-target: darwin-arm64 + - os: ubuntu-latest + target: x86_64-unknown-linux-musl + vsix-target: alpine-x64 + # - os: ubuntu-latest + # target: aarch64-unknown-linux-musl + # vsix-target: alpine-arm64 + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Checkout Python Environment Tools + uses: actions/checkout@v6 + with: + repository: 'microsoft/python-environment-tools' + path: 'python-env-tools' + persist-credentials: false + sparse-checkout: | + crates + Cargo.toml + Cargo.lock + sparse-checkout-cone-mode: false + + - name: Build VSIX + uses: ./.github/actions/build-vsix + with: + node_version: ${{ env.NODE_VERSION}} + vsix_name: ${{ needs.setup.outputs.vsix_basename }}-${{ matrix.vsix-target }}.vsix + artifact_name: ${{ needs.setup.outputs.vsix_artifact_name }}-${{ matrix.vsix-target }} + cargo_target: ${{ matrix.target }} + vsix_target: ${{ matrix.vsix-target }} + + lint: + name: Lint + if: github.repository == 'microsoft/vscode-python' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Lint + uses: ./.github/actions/lint + with: + node_version: ${{ env.NODE_VERSION }} + + check-types: + name: Check Python types + if: github.repository == 'microsoft/vscode-python' + runs-on: ubuntu-latest + steps: + - name: Use Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v6 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Checkout + uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Install core Python requirements + uses: brettcannon/pip-secure-install@92f400e3191171c1858cc0e0d9ac6320173fdb0c # v1.0.0 + with: + options: '-t ./python_files/lib/python --no-cache-dir --implementation py' + + - name: Install Jedi requirements + uses: brettcannon/pip-secure-install@92f400e3191171c1858cc0e0d9ac6320173fdb0c # v1.0.0 + with: + requirements-file: './python_files/jedilsp_requirements/requirements.txt' + options: '-t ./python_files/lib/jedilsp --no-cache-dir --implementation py' + + - name: Install other Python requirements + run: | + python -m pip install --upgrade -r build/test-requirements.txt + + - name: Run Pyright + uses: jakebailey/pyright-action@8ec14b5cfe41f26e5f41686a31eb6012758217ef # v3.0.2 + with: + version: 1.1.308 + working-directory: 'python_files' + + python-tests: + name: Python Tests + # The value of runs-on is the OS of the current job (specified in the strategy matrix below) instead of being hardcoded. + runs-on: ${{ matrix.os }} + defaults: + run: + working-directory: ${{ env.special-working-directory }} + strategy: + fail-fast: false + matrix: + # We're not running CI on macOS for now because it's one less matrix entry to lower the number of runners used, + # macOS runners are expensive, and we assume that Ubuntu is enough to cover the Unix case. + os: [ubuntu-latest, windows-latest] + # Run the tests on the oldest and most recent versions of Python. + python: ['3.10', '3.x', '3.13'] + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + path: ${{ env.special-working-directory-relative }} + persist-credentials: false + + - name: Use Python ${{ matrix.python }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python }} + + - name: Install base Python requirements + uses: brettcannon/pip-secure-install@92f400e3191171c1858cc0e0d9ac6320173fdb0c # v1.0.0 + with: + requirements-file: '"${{ env.special-working-directory-relative }}/requirements.txt"' + options: '-t "${{ env.special-working-directory-relative }}/python_files/lib/python" --no-cache-dir --implementation py' + + - name: Install test requirements + run: python -m pip install --upgrade -r build/test-requirements.txt + + - name: Run Python unit tests + run: python python_files/tests/run_all.py + + tests: + name: Tests + if: github.repository == 'microsoft/vscode-python' + runs-on: ${{ matrix.os }} + defaults: + run: + working-directory: ${{ env.special-working-directory }} + strategy: + fail-fast: false + matrix: + # We're not running CI on macOS for now because it's one less matrix + # entry to lower the number of runners used, macOS runners are expensive, + # and we assume that Ubuntu is enough to cover the UNIX case. + os: [ubuntu-latest, windows-latest] + python: ['3.x'] + test-suite: [ts-unit, venv, single-workspace, multi-workspace, debugger, functional] + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + path: ${{ env.special-working-directory-relative }} + persist-credentials: false + + - name: Checkout Python Environment Tools + uses: actions/checkout@v6 + with: + repository: 'microsoft/python-environment-tools' + path: ${{ env.special-working-directory-relative }}/python-env-tools + persist-credentials: false + sparse-checkout: | + crates + Cargo.toml + Cargo.lock + sparse-checkout-cone-mode: false + + - name: Install Node + uses: actions/setup-node@v6 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + cache-dependency-path: ${{ env.special-working-directory-relative }}/package-lock.json + + - name: Install dependencies (npm ci) + run: npm ci + + - name: Compile + run: npx gulp prePublishNonBundle + + - name: Localization + run: npx @vscode/l10n-dev@latest export ./src + + - name: Install Python ${{ matrix.python }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python }} + + - name: Upgrade Pip + run: python -m pip install -U pip + + # For faster/better builds of sdists. + - name: Install build pre-requisite + run: python -m pip install wheel nox + + - name: Install Python Extension dependencies (jedi, etc.) + run: nox --session install_python_libs + + - name: Install test requirements + run: python -m pip install --upgrade -r build/test-requirements.txt + + - name: Rust Tool Chain setup + uses: dtolnay/rust-toolchain@stable + + - name: Build Native Binaries + run: nox --session native_build + shell: bash + + - name: Install functional test requirements + run: python -m pip install --upgrade -r ./build/functional-test-requirements.txt + if: matrix.test-suite == 'functional' + + - name: Prepare pipenv for venv tests + env: + TEST_FILES_SUFFIX: testvirtualenvs + PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' + shell: pwsh + if: matrix.test-suite == 'venv' + run: | + python -m pip install pipenv + python -m pipenv run python ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} pipenvPath + + - name: Prepare poetry for venv tests + env: + TEST_FILES_SUFFIX: testvirtualenvs + shell: pwsh + if: matrix.test-suite == 'venv' + run: | + python -m pip install poetry + Move-Item -Path ".\build\ci\pyproject.toml" -Destination . + poetry env use python + + - name: Prepare virtualenv for venv tests + env: + TEST_FILES_SUFFIX: testvirtualenvs + PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' + shell: pwsh + if: matrix.test-suite == 'venv' + run: | + python -m pip install virtualenv + python -m virtualenv .virtualenv/ + if ('${{ matrix.os }}' -match 'windows-latest') { + & ".virtualenv/Scripts/python.exe" ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} virtualEnvPath + } else { + & ".virtualenv/bin/python" ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} virtualEnvPath + } + + - name: Prepare venv for venv tests + env: + TEST_FILES_SUFFIX: testvirtualenvs + PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' + shell: pwsh + if: matrix.test-suite == 'venv' && startsWith(matrix.python, 3.) + run: | + python -m venv .venv + if ('${{ matrix.os }}' -match 'windows-latest') { + & ".venv/Scripts/python.exe" ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} venvPath + } else { + & ".venv/bin/python" ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} venvPath + } + + - name: Prepare conda for venv tests + env: + TEST_FILES_SUFFIX: testvirtualenvs + PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' + shell: pwsh + if: matrix.test-suite == 'venv' + run: | + # 1. For `*.testvirtualenvs.test.ts` + if ('${{ matrix.os }}' -match 'windows-latest') { + $condaPythonPath = Join-Path -Path $Env:CONDA -ChildPath python.exe + $condaExecPath = Join-Path -Path $Env:CONDA -ChildPath Scripts | Join-Path -ChildPath conda + } else{ + $condaPythonPath = Join-Path -Path $Env:CONDA -ChildPath bin | Join-Path -ChildPath python + $condaExecPath = Join-Path -Path $Env:CONDA -ChildPath bin | Join-Path -ChildPath conda + } + & $condaPythonPath ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} condaExecPath $condaExecPath + & $condaPythonPath ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} condaPath + & $condaExecPath init --all + + - name: Set CI_PYTHON_PATH and CI_DISABLE_AUTO_SELECTION + run: | + echo "CI_PYTHON_PATH=python" >> $GITHUB_ENV + echo "CI_DISABLE_AUTO_SELECTION=1" >> $GITHUB_ENV + shell: bash + if: matrix.test-suite != 'ts-unit' + + # Run TypeScript unit tests only for Python 3.X. + - name: Run TypeScript unit tests + run: npm run test:unittests + if: matrix.test-suite == 'ts-unit' && startsWith(matrix.python, '3.') + + # The virtual environment based tests use the `testSingleWorkspace` set of tests + # with the environment variable `TEST_FILES_SUFFIX` set to `testvirtualenvs`, + # which is set in the "Prepare environment for venv tests" step. + # We also use a third-party GitHub Action to install xvfb on Linux, + # run tests and then clean up the process once the tests ran. + # See https://github.com/GabrielBB/xvfb-action + - name: Run venv tests + env: + TEST_FILES_SUFFIX: testvirtualenvs + CI_PYTHON_VERSION: ${{ matrix.python }} + uses: GabrielBB/xvfb-action@b706e4e27b14669b486812790492dc50ca16b465 # v1.7 + with: + run: npm run testSingleWorkspace + working-directory: ${{ env.special-working-directory }} + if: matrix.test-suite == 'venv' && matrix.os == 'ubuntu-latest' + + - name: Run single-workspace tests + env: + CI_PYTHON_VERSION: ${{ matrix.python }} + uses: GabrielBB/xvfb-action@b706e4e27b14669b486812790492dc50ca16b465 # v1.7 + with: + run: npm run testSingleWorkspace + working-directory: ${{ env.special-working-directory }} + if: matrix.test-suite == 'single-workspace' + + - name: Run multi-workspace tests + env: + CI_PYTHON_VERSION: ${{ matrix.python }} + uses: GabrielBB/xvfb-action@b706e4e27b14669b486812790492dc50ca16b465 # v1.7 + with: + run: npm run testMultiWorkspace + working-directory: ${{ env.special-working-directory }} + if: matrix.test-suite == 'multi-workspace' + + - name: Run debugger tests + env: + CI_PYTHON_VERSION: ${{ matrix.python }} + uses: GabrielBB/xvfb-action@b706e4e27b14669b486812790492dc50ca16b465 # v1.7 + with: + run: npm run testDebugger + working-directory: ${{ env.special-working-directory }} + if: matrix.test-suite == 'debugger' + + # Run TypeScript functional tests + - name: Run TypeScript functional tests + run: npm run test:functional + if: matrix.test-suite == 'functional' + + smoke-tests: + name: Smoke tests + if: github.repository == 'microsoft/vscode-python' + runs-on: ${{ matrix.os }} + needs: [setup, build-vsix] + strategy: + fail-fast: false + matrix: + # We're not running CI on macOS for now because it's one less matrix entry to lower the number of runners used, + # macOS runners are expensive, and we assume that Ubuntu is enough to cover the UNIX case. + include: + - os: windows-latest + vsix-target: win32-x64 + - os: ubuntu-latest + vsix-target: linux-x64 + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Checkout Python Environment Tools + uses: actions/checkout@v6 + with: + repository: 'microsoft/python-environment-tools' + path: ${{ env.special-working-directory-relative }}/python-env-tools + persist-credentials: false + sparse-checkout: | + crates + Cargo.toml + Cargo.lock + sparse-checkout-cone-mode: false + + - name: Smoke tests + uses: ./.github/actions/smoke-tests + with: + node_version: ${{ env.NODE_VERSION }} + artifact_name: ${{ needs.setup.outputs.vsix_artifact_name }}-${{ matrix.vsix-target }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000000..5528fbbe9c0a --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,70 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +name: 'CodeQL' + +on: + push: + branches: + - main + - release-* + - release/* + pull_request: + # The branches below must be a subset of the branches above + branches: [main] + schedule: + - cron: '0 3 * * 0' + +permissions: + security-events: write + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + # Override automatic language detection by changing the below list + # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] + language: ['javascript', 'python'] + # Learn more... + # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + persist-credentials: false + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + #- name: Autobuild + # uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 diff --git a/.github/workflows/community-feedback-auto-comment.yml b/.github/workflows/community-feedback-auto-comment.yml new file mode 100644 index 000000000000..27f93400a023 --- /dev/null +++ b/.github/workflows/community-feedback-auto-comment.yml @@ -0,0 +1,28 @@ +name: Community Feedback Auto Comment + +on: + issues: + types: + - labeled +jobs: + add-comment: + if: github.event.label.name == 'needs community feedback' + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Check For Existing Comment + uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0 + id: finder + with: + issue-number: ${{ github.event.issue.number }} + comment-author: 'github-actions[bot]' + body-includes: 'Thanks for the feature request! We are going to give the community' + + - name: Add Community Feedback Comment + if: steps.finder.outputs.comment-id == '' + uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 + with: + issue-number: ${{ github.event.issue.number }} + body: | + Thanks for the feature request! We are going to give the community 60 days from when this issue was created to provide 7 👍 upvotes on the opening comment to gauge general interest in this idea. If there's enough upvotes then we will consider this feature request in our future planning. If there's unfortunately not enough upvotes then we will close this issue. diff --git a/.github/workflows/gen-issue-velocity.yml b/.github/workflows/gen-issue-velocity.yml new file mode 100644 index 000000000000..41d79e4074d0 --- /dev/null +++ b/.github/workflows/gen-issue-velocity.yml @@ -0,0 +1,34 @@ +name: Issues Summary + +on: + schedule: + - cron: '0 0 * * 2' # Runs every Tuesday at midnight + workflow_dispatch: + +permissions: + issues: read + +jobs: + generate-summary: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.x' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install requests + + - name: Run summary script + run: python scripts/issue_velocity_summary_script.py + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/info-needed-closer.yml b/.github/workflows/info-needed-closer.yml new file mode 100644 index 000000000000..46892a58e800 --- /dev/null +++ b/.github/workflows/info-needed-closer.yml @@ -0,0 +1,33 @@ +name: Info-Needed Closer +on: + schedule: + - cron: 20 12 * * * # 5:20am Redmond + repository_dispatch: + types: [trigger-needs-more-info] + workflow_dispatch: + +permissions: + issues: write + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v6 + with: + repository: 'microsoft/vscode-github-triage-actions' + path: ./actions + persist-credentials: false + ref: stable + - name: Install Actions + run: npm install --production --prefix ./actions + - name: Run info-needed Closer + uses: ./actions/needs-more-info-closer + with: + token: ${{secrets.GITHUB_TOKEN}} + label: info-needed + closeDays: 30 + closeComment: "Because we have not heard back with the information we requested, we are closing this issue for now. If you are able to provide the info later on, then we will be happy to re-open this issue to pick up where we left off. \n\nHappy Coding!" + pingDays: 30 + pingComment: "Hey @${assignee}, this issue might need further attention.\n\n@${author}, you can help us out by closing this issue if the problem no longer exists, or adding more information." diff --git a/.github/workflows/insiders.yml b/.github/workflows/insiders.yml deleted file mode 100644 index 77b7fa3c9091..000000000000 --- a/.github/workflows/insiders.yml +++ /dev/null @@ -1,505 +0,0 @@ -name: Insiders Build - -on: - push: - branches: - - main - -env: - PYTHON_VERSION: 3.8 - MOCHA_REPORTER_JUNIT: true # Use the mocha-multi-reporters and send output to both console (spec) and JUnit (mocha-junit-reporter). Also enables a reporter which exits the process running the tests if it haven't already. - CACHE_NPM_DEPS: cache-npm - CACHE_OUT_DIRECTORY: cache-out-directory - CACHE_PIP_DEPS: cache-pip - # Key for the cache created at the end of the the 'Cache ./pythonFiles/lib/python' step. - CACHE_PYTHONFILES: cache-pvsc-pythonFiles - ARTIFACT_NAME_VSIX: ms-python-insiders-vsix - VSIX_NAME: ms-python-insiders.vsix - COVERAGE_REPORTS: tests-coverage-reports - TEST_RESULTS_DIRECTORY: . - -jobs: - build-vsix: - name: Build VSIX - runs-on: ubuntu-latest - if: github.repository == 'microsoft/vscode-python' - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Cache pip files - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{runner.os}}-${{env.CACHE_PIP_DEPS}}-${{env.PYTHON_VERSION}}-${{hashFiles('requirements.txt')}}-${{hashFiles('build/debugger-install-requirements.txt')}} - - - name: Cache npm files - uses: actions/cache@v2 - with: - path: ~/.npm - key: ${{runner.os}}-${{env.CACHE_NPM_DEPS}}-${{hashFiles('package-lock.json')}} - - - name: Use Python ${{env.PYTHON_VERSION}} - uses: actions/setup-python@v2 - with: - python-version: ${{env.PYTHON_VERSION}} - - - name: Upgrade pip - run: python -m pip install -U pip - - - name: Build VSIX - uses: ./.github/actions/build-vsix - id: build-vsix - - - name: Rename VSIX - if: steps.build-vsix.outputs.path != env.VSIX_NAME - run: mv ${{ steps.build-vsix.outputs.path }} ${{ env.VSIX_NAME }} - - - uses: actions/upload-artifact@v2 - with: - name: ${{env.ARTIFACT_NAME_VSIX}} - path: ${{env.VSIX_NAME}} - - lint: - name: Lint - runs-on: ubuntu-latest - if: github.repository == 'microsoft/vscode-python' - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Cache pip files - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{runner.os}}-${{env.CACHE_PIP_DEPS}}-${{env.PYTHON_VERSION}}-${{hashFiles('requirements.txt')}}-${{hashFiles('build/debugger-install-requirements.txt')}} - - - name: Cache npm files - uses: actions/cache@v2 - with: - path: ~/.npm - key: ${{runner.os}}-${{env.CACHE_NPM_DEPS}}-${{hashFiles('package-lock.json')}} - - - name: Install dependencies (npm ci) - run: npm ci --prefer-offline - - - name: Run gulp prePublishNonBundle - run: npx gulp prePublishNonBundle - - - name: Cache the out/ directory - uses: actions/cache@v2 - with: - path: ./out - key: ${{runner.os}}-${{env.CACHE_OUT_DIRECTORY}}-${{hashFiles('src/**')}} - - - name: Check dependencies - run: npm run checkDependencies - - - name: Run linting on TypeScript code - run: npx tslint --project tsconfig.json - - - name: Run prettier on TypeScript code - run: npx prettier 'src/**/*.ts*' --check - - - name: Run prettier on JavaScript code - run: npx prettier 'build/**/*.js' --check - - - name: Use Python ${{env.PYTHON_VERSION}} - uses: actions/setup-python@v2 - with: - python-version: ${{env.PYTHON_VERSION}} - - - name: Run Black on Python code - run: | - python -m pip install -U black - python -m black . --check - working-directory: pythonFiles - - ### Non-smoke tests - tests: - name: Tests - # The value of runs-on is the OS of the current job (specified in the strategy matrix below) instead of being hardcoded. - runs-on: ${{ matrix.os }} - env: - # Something in Node 12.16.0 breaks the TS debug adapter, and ubuntu-latest bundles Node 12.16.1. - # We can remove this when we switch over to the python-based DA in https://github.com/microsoft/vscode-python/issues/7136. - # See https://github.com/microsoft/ptvsd/issues/2068 - # At this point pinning is only needed for consistency. We no longer have TS debug adapter. - NODE_VERSION: 12.15.0 - # Force a path with spaces and to test extension works in these scenarios - # Unicode characters are causing 2.7 failures so skip that for now. - special-working-directory: './path with spaces' - special-working-directory-relative: 'path with spaces' - defaults: - run: - working-directory: ${{env.special-working-directory}} - if: github.repository == 'microsoft/vscode-python' - strategy: - fail-fast: false - matrix: - # We're not running CI on macOS for now because it's one less matrix entry to lower the number of runners used, - # macOS runners are expensive, and we assume that Ubuntu is enough to cover the UNIX case. - os: [ubuntu-latest, windows-latest] - # Run the tests on the oldest and most recent versions of Python. - python: [2.7, 3.8] - test-suite: [ts-unit, python-unit, venv, single-workspace, multi-workspace, debugger, functional] - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - path: ${{env.special-working-directory-relative}} - - - name: Cache pip files - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{runner.os}}-${{env.CACHE_PIP_DEPS}}-${{env.PYTHON_VERSION}}-${{hashFiles('requirements.txt')}}-${{hashFiles('build/debugger-install-requirements.txt')}} - - - name: Cache npm files - uses: actions/cache@v2 - with: - path: ~/.npm - key: ${{runner.os}}-${{env.CACHE_NPM_DEPS}}-${{hashFiles('package-lock.json')}} - - - name: Cache compiled TS files - # Use an id for this step so that its cache-hit output can be accessed and checked in the next step. - id: out-cache - uses: actions/cache@v2 - with: - path: ${{env.special-working-directory}}/out - key: ${{runner.os}}-${{env.CACHE_OUT_DIRECTORY}}-${{hashFiles('src/**')}} - - - name: Install dependencies (npm ci) - run: npm ci - - - name: Compile if not cached - run: npx gulp prePublishNonBundle - if: steps.out-cache.outputs.cache-hit == false - - - name: Use Python ${{matrix.python}} - uses: actions/setup-python@v2 - with: - python-version: ${{matrix.python}} - - - name: Use Node ${{env.NODE_VERSION}} - uses: actions/setup-node@v2.1.1 - with: - node-version: ${{env.NODE_VERSION}} - - - name: Install Python requirements - run: | - python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade -r requirements.txt --no-user - # We need to have debugpy so that tests relying on it keep passing, but we don't need install_debugpy's logic in the test phase. - python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade --pre debugpy - - - name: Install test requirements - run: python -m pip install --upgrade -r build/test-requirements.txt - - - name: pip install ipython requirements - run: | - python -m pip install numpy - python -m pip install --upgrade -r ./build/ipython-test-requirements.txt - if: matrix.test-suite == 'python-unit' - - - name: Install debugpy wheels (python 3.8) - run: | - python -m pip install wheel - python -m pip --disable-pip-version-check install -r build/debugger-install-requirements.txt - python ./pythonFiles/install_debugpy.py - shell: bash - if: matrix.test-suite == 'debugger' && matrix.python == 3.8 - - - name: Install debugpy (python 2.7) - run: | - python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade --pre debugpy - shell: bash - if: matrix.test-suite == 'debugger' && matrix.python == 2.7 - - - name: Install functional test requirements - run: | - python -m pip install numpy - python -m pip install --upgrade -r ./build/functional-test-requirements.txt - if: matrix.test-suite == 'functional' - - - name: Prepare pipenv for venv tests - env: - TEST_FILES_SUFFIX: testvirtualenvs - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - shell: pwsh - if: matrix.test-suite == 'venv' - run: | - python -m pip install pipenv - python -m pipenv run python ./build/ci/addEnvPath.py ${{env.PYTHON_VIRTUAL_ENVS_LOCATION}} pipenvPath - - - name: Prepare virtualenv for venv tests - env: - TEST_FILES_SUFFIX: testvirtualenvs - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - shell: pwsh - if: matrix.test-suite == 'venv' - run: | - python -m pip install virtualenv - python -m virtualenv .virtualenv/ - if ('${{matrix.os}}' -match 'windows-latest') { - & ".virtualenv/Scripts/python.exe" ./build/ci/addEnvPath.py ${{env.PYTHON_VIRTUAL_ENVS_LOCATION}} virtualEnvPath - } else { - & ".virtualenv/bin/python" ./build/ci/addEnvPath.py ${{env.PYTHON_VIRTUAL_ENVS_LOCATION}} virtualEnvPath - } - - - name: Prepare venv for venv tests - env: - TEST_FILES_SUFFIX: testvirtualenvs - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - shell: pwsh - if: matrix.test-suite == 'venv' && startsWith(matrix.python, 3.) - run: | - python -m venv .venv - if ('${{matrix.os}}' -match 'windows-latest') { - & ".venv/Scripts/python.exe" ./build/ci/addEnvPath.py ${{env.PYTHON_VIRTUAL_ENVS_LOCATION}} venvPath - } else { - & ".venv/bin/python" ./build/ci/addEnvPath.py ${{env.PYTHON_VIRTUAL_ENVS_LOCATION}} venvPath - } - - - name: Prepare conda for venv tests - env: - TEST_FILES_SUFFIX: testvirtualenvs - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - shell: pwsh - if: matrix.test-suite == 'venv' - run: | - # 1. For `terminalActivation.testvirtualenvs.test.ts` - if ('${{matrix.os}}' -match 'windows-latest') { - $condaPythonPath = Join-Path -Path $Env:CONDA -ChildPath python.exe - $condaExecPath = Join-Path -Path $Env:CONDA -ChildPath Scripts | Join-Path -ChildPath conda - } else{ - $condaPythonPath = Join-Path -Path $Env:CONDA -ChildPath bin | Join-Path -ChildPath python - $condaExecPath = Join-Path -Path $Env:CONDA -ChildPath bin | Join-Path -ChildPath conda - } - & $condaPythonPath ./build/ci/addEnvPath.py ${{env.PYTHON_VIRTUAL_ENVS_LOCATION}} condaExecPath $condaExecPath - & $condaPythonPath ./build/ci/addEnvPath.py ${{env.PYTHON_VIRTUAL_ENVS_LOCATION}} condaPath - - # 2. For `interpreterLocatorService.testvirtualenvs.ts` - - & $condaExecPath create -n "test_env1" -y python - & $condaExecPath create -p "./test_env2" -y python - & $condaExecPath create -p "~/test_env3" -y python - - - name: Set CI_PYTHON_PATH and CI_DISABLE_AUTO_SELECTION - run: | - echo "::set-env name=CI_PYTHON_PATH::python" - echo "::set-env name=CI_DISABLE_AUTO_SELECTION::1" - shell: bash - if: matrix.test-suite != 'ts-unit' - - # Run TypeScript unit tests only for Python 3.X. - - name: Run TypeScript unit tests - run: npm run test:unittests:cover - if: matrix.test-suite == 'ts-unit' && startsWith(matrix.python, 3.) - - # Upload unit test coverage reports for later use in the "reports" job. - - name: Upload unit test coverage reports - uses: actions/upload-artifact@v1 - with: - name: ${{runner.os}}-${{env.COVERAGE_REPORTS}} - path: .nyc_output - if: matrix.test-suite == 'ts-unit' && startsWith(matrix.python, 3.) - - # Run the Python and IPython tests in our codebase. - - name: Run Python and IPython unit tests - run: | - python pythonFiles/tests/run_all.py - python -m IPython pythonFiles/tests/run_all.py - if: matrix.test-suite == 'python-unit' - - # The virtual environment based tests use the `testSingleWorkspace` set of tests - # with the environment variable `TEST_FILES_SUFFIX` set to `testvirtualenvs`, - # which is set in the "Prepare environment for venv tests" step. - # We also use a third-party GitHub Action to install xvfb on Linux, - # run tests and then clean up the process once the tests ran. - # See https://github.com/GabrielBB/xvfb-action - - name: Run venv tests - env: - TEST_FILES_SUFFIX: testvirtualenvs - CI_PYTHON_VERSION: ${{matrix.python}} - uses: GabrielBB/xvfb-action@v1.4 - with: - run: npm run testSingleWorkspace - working-directory: ${{env.special-working-directory}} - if: matrix.test-suite == 'venv' - - - name: Run single-workspace tests - env: - CI_PYTHON_VERSION: ${{matrix.python}} - uses: GabrielBB/xvfb-action@v1.4 - with: - run: npm run testSingleWorkspace - working-directory: ${{env.special-working-directory}} - if: matrix.test-suite == 'single-workspace' - - - name: Run multi-workspace tests - env: - CI_PYTHON_VERSION: ${{matrix.python}} - uses: GabrielBB/xvfb-action@v1.4 - with: - run: npm run testMultiWorkspace - working-directory: ${{env.special-working-directory}} - if: matrix.test-suite == 'multi-workspace' - - - name: Run debugger tests - env: - CI_PYTHON_VERSION: ${{matrix.python}} - uses: GabrielBB/xvfb-action@v1.4 - with: - run: npm run testDebugger - working-directory: ${{env.special-working-directory}} - if: matrix.test-suite == 'debugger' - - - name: Run functional tests - run: npm run test:functional - if: matrix.test-suite == 'functional' - - smoke-tests: - name: Smoke tests - # The value of runs-on is the OS of the current job (specified in the strategy matrix below) instead of being hardcoded. - runs-on: ${{ matrix.os }} - if: github.repository == 'microsoft/vscode-python' - needs: [build-vsix] - strategy: - fail-fast: false - matrix: - # We're not running CI on macOS for now because it's one less matrix entry to lower the number of runners used, - # macOS runners are expensive, and we assume that Ubuntu is enough to cover the UNIX case. - os: [ubuntu-latest, windows-latest] - python: [3.8] - steps: - # Need the source to have the tests available. - - name: Checkout - uses: actions/checkout@v2 - - - name: Cache pip files - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{runner.os}}-${{env.CACHE_PIP_DEPS}}-${{env.PYTHON_VERSION}}-${{hashFiles('requirements.txt')}}-${{hashFiles('build/debugger-install-requirements.txt')}} - - - name: Cache npm files - uses: actions/cache@v2 - with: - path: ~/.npm - key: ${{runner.os}}-${{env.CACHE_NPM_DEPS}}-${{hashFiles('package-lock.json')}} - - - name: Use Python ${{matrix.python}} - uses: actions/setup-python@v2 - with: - python-version: ${{matrix.python}} - - - name: Install dependencies (npm ci) - run: npm ci --prefer-offline - - - name: pip install system test requirements - run: | - python -m pip install --upgrade -r build/test-requirements.txt - python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade -r requirements.txt - python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade --pre debugpy - shell: bash - - - name: pip install ipython requirements - run: | - python -m pip install numpy - python -m pip install --upgrade -r ./build/ipython-test-requirements.txt - - - name: pip install jupyter - run: | - python -m pip install --upgrade jupyter - - # Save time by reusing bits from the VSIX. - - name: Download VSIX - uses: actions/download-artifact@v2 - with: - name: ${{env.ARTIFACT_NAME_VSIX}} - - # Compile the test files. - - name: Prepare for smoke tests - run: npx tsc -p ./ - shell: bash - - - name: Set CI_PYTHON_PATH and CI_DISABLE_AUTO_SELECTION - run: | - echo "::set-env name=CI_PYTHON_PATH::python" - echo "::set-env name=CI_DISABLE_AUTO_SELECTION::1" - shell: bash - - - name: Run smoke tests - env: - DISPLAY: 10 - uses: GabrielBB/xvfb-action@v1.4 - with: - run: node --no-force-async-hooks-checks ./out/test/smokeTest.js - - coverage: - name: Coverage reports upload - runs-on: ubuntu-latest - if: github.repository == 'microsoft/vscode-python' - needs: [tests, smoke-tests] - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Cache npm files - uses: actions/cache@v2 - with: - path: ~/.npm - key: ${{runner.os}}-${{env.CACHE_NPM_DEPS}}-${{hashFiles('package-lock.json')}} - - - name: Install dependencies (npm ci) - run: npm ci --prefer-offline - - # It isn't possible to specify a regex for artifact names, so we have to download each artifact manually. - # The name pattern is ${{runner.os}}-${{env.COVERAGE_REPORTS}}, and possible values for runner.os are `Linux`, `Windows`, or `macOS`. - # See https://help.github.com/en/actions/reference/contexts-and-expression-syntax-for-github-actions#runner-context - - name: Download Ubuntu test coverage artifacts - uses: actions/download-artifact@v1 - with: - name: Linux-${{env.COVERAGE_REPORTS}} - - - name: Extract Ubuntu coverage artifacts to ./nyc_output - run: | - mkdir .nyc_output - mv Linux-${{env.COVERAGE_REPORTS}}/* .nyc_output - rm -r Linux-${{env.COVERAGE_REPORTS}} - - - name: Generate coverage reports - run: npm run test:cover:report - continue-on-error: true - - - name: Upload coverage to codecov - uses: codecov/codecov-action@v1 - with: - token: ${{ secrets.CODECOV_TOKEN }} - file: ./coverage/cobertura-coverage.xml - - upload: - name: Upload VSIX to Azure Blob Storage - runs-on: ubuntu-latest - if: github.repository == 'microsoft/vscode-python' - needs: [tests, smoke-tests, build-vsix] - env: - BLOB_CONTAINER_NAME: extension-builds - BLOB_NAME: ms-python-insiders.vsix - - steps: - - name: Download VSIX - uses: actions/download-artifact@v2 - with: - name: ${{ env.ARTIFACT_NAME_VSIX }} - - - name: Azure Login - uses: azure/login@v1 - with: - creds: ${{ secrets.AZURE_CREDENTIALS }} - - - name: Upload to Blob Storage - run: az storage blob upload --file ${{ env.VSIX_NAME }} --account-name pvsc --container-name ${{ env.BLOB_CONTAINER_NAME }} --name ${{ env.BLOB_NAME }} --auth-mode login - - - name: Get URL to uploaded VSIX - run: az storage blob url --account-name pvsc --container-name ${{ env.BLOB_CONTAINER_NAME }} --name ${{ env.BLOB_NAME }} --auth-mode login diff --git a/.github/workflows/issue-labels.yml b/.github/workflows/issue-labels.yml new file mode 100644 index 000000000000..dcbd114086e2 --- /dev/null +++ b/.github/workflows/issue-labels.yml @@ -0,0 +1,34 @@ +name: Issue labels + +on: + issues: + types: [opened, reopened] + +env: + TRIAGERS: '["karthiknadig","eleanorjboyd","anthonykim1"]' + +permissions: + issues: write + +jobs: + # From https://github.com/marketplace/actions/github-script#apply-a-label-to-an-issue. + add-classify-label: + name: "Add 'triage-needed' and remove assignees" + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v6 + with: + repository: 'microsoft/vscode-github-triage-actions' + ref: stable + path: ./actions + persist-credentials: false + + - name: Install Actions + run: npm install --production --prefix ./actions + + - name: "Add 'triage-needed' and remove assignees" + uses: ./actions/python-issue-labels + with: + triagers: ${{ env.TRIAGERS }} + token: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/lock-issues.yml b/.github/workflows/lock-issues.yml new file mode 100644 index 000000000000..544d04ee185e --- /dev/null +++ b/.github/workflows/lock-issues.yml @@ -0,0 +1,24 @@ +name: 'Lock Issues' + +on: + schedule: + - cron: '0 0 * * *' + workflow_dispatch: + +permissions: + issues: write + +concurrency: + group: lock + +jobs: + lock-issues: + runs-on: ubuntu-latest + steps: + - name: 'Lock Issues' + uses: dessant/lock-threads@7266a7ce5c1df01b1c6db85bf8cd86c737dadbe7 # v6.0.0 + with: + github-token: ${{ github.token }} + issue-inactive-days: '30' + process-only: 'issues' + log-output: true diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml new file mode 100644 index 000000000000..c8a6f2dd416e --- /dev/null +++ b/.github/workflows/pr-check.yml @@ -0,0 +1,689 @@ +name: PR/CI Check + +on: + pull_request: + push: + branches-ignore: + - main + - release* + +permissions: {} + +env: + NODE_VERSION: 22.21.1 + PYTHON_VERSION: '3.10' # YML treats 3.10 the number as 3.1, so quotes around 3.10 + MOCHA_REPORTER_JUNIT: true # Use the mocha-multi-reporters and send output to both console (spec) and JUnit (mocha-junit-reporter). Also enables a reporter which exits the process running the tests if it haven't already. + ARTIFACT_NAME_VSIX: ms-python-insiders-vsix + TEST_RESULTS_DIRECTORY: . + # Force a path with spaces and to test extension works in these scenarios + # Unicode characters are causing 2.7 failures so skip that for now. + special-working-directory: './path with spaces' + special-working-directory-relative: 'path with spaces' + +jobs: + build-vsix: + name: Create VSIX + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: windows-latest + target: x86_64-pc-windows-msvc + vsix-target: win32-x64 + - os: windows-latest + target: aarch64-pc-windows-msvc + vsix-target: win32-arm64 + - os: ubuntu-latest + target: x86_64-unknown-linux-musl + vsix-target: linux-x64 + # - os: ubuntu-latest + # target: aarch64-unknown-linux-gnu + # vsix-target: linux-arm64 + # - os: ubuntu-latest + # target: arm-unknown-linux-gnueabihf + # vsix-target: linux-armhf + # - os: macos-latest + # target: x86_64-apple-darwin + # vsix-target: darwin-x64 + # - os: macos-14 + # target: aarch64-apple-darwin + # vsix-target: darwin-arm64 + - os: ubuntu-latest + target: x86_64-unknown-linux-musl + vsix-target: alpine-x64 + # - os: ubuntu-latest + # target: aarch64-unknown-linux-musl + # vsix-target: alpine-arm64 + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Checkout Python Environment Tools + uses: actions/checkout@v6 + with: + repository: 'microsoft/python-environment-tools' + path: 'python-env-tools' + persist-credentials: false + sparse-checkout: | + crates + Cargo.toml + Cargo.lock + sparse-checkout-cone-mode: false + + - name: Build VSIX + uses: ./.github/actions/build-vsix + with: + node_version: ${{ env.NODE_VERSION}} + vsix_name: 'ms-python-insiders-${{ matrix.vsix-target }}.vsix' + artifact_name: '${{ env.ARTIFACT_NAME_VSIX }}-${{ matrix.vsix-target }}' + cargo_target: ${{ matrix.target }} + vsix_target: ${{ matrix.vsix-target }} + + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Lint + uses: ./.github/actions/lint + with: + node_version: ${{ env.NODE_VERSION }} + + check-types: + name: Check Python types + runs-on: ubuntu-latest + steps: + - name: Use Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v6 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Checkout + uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Checkout Python Environment Tools + uses: actions/checkout@v6 + with: + repository: 'microsoft/python-environment-tools' + path: 'python-env-tools' + persist-credentials: false + sparse-checkout: | + crates + Cargo.toml + Cargo.lock + sparse-checkout-cone-mode: false + + - name: Install base Python requirements + uses: brettcannon/pip-secure-install@92f400e3191171c1858cc0e0d9ac6320173fdb0c # v1.0.0 + with: + options: '-t ./python_files/lib/python --no-cache-dir --implementation py' + + - name: Install Jedi requirements + uses: brettcannon/pip-secure-install@92f400e3191171c1858cc0e0d9ac6320173fdb0c # v1.0.0 + with: + requirements-file: './python_files/jedilsp_requirements/requirements.txt' + options: '-t ./python_files/lib/jedilsp --no-cache-dir --implementation py' + + - name: Install other Python requirements + run: | + python -m pip install --upgrade -r build/test-requirements.txt + + - name: Run Pyright + uses: jakebailey/pyright-action@8ec14b5cfe41f26e5f41686a31eb6012758217ef # v3.0.2 + with: + version: 1.1.308 + working-directory: 'python_files' + + python-tests: + name: Python Tests + # The value of runs-on is the OS of the current job (specified in the strategy matrix below) instead of being hardcoded. + runs-on: ${{ matrix.os }} + defaults: + run: + working-directory: ${{ env.special-working-directory }} + strategy: + fail-fast: false + matrix: + # We're not running CI on macOS for now because it's one less matrix entry to lower the number of runners used, + # macOS runners are expensive, and we assume that Ubuntu is enough to cover the Unix case. + os: [ubuntu-latest, windows-latest] + # Run the tests on the oldest and most recent versions of Python. + python: ['3.10', '3.x', '3.13'] # run for 3 pytest versions, most recent stable, oldest version supported and pre-release + pytest-version: ['pytest', 'pytest@pre-release', 'pytest==6.2.0'] + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + path: ${{ env.special-working-directory-relative }} + persist-credentials: false + + - name: Use Python ${{ matrix.python }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python }} + + - name: Install specific pytest version + if: matrix.pytest-version == 'pytest@pre-release' + run: | + python -m pip install --pre pytest + + - name: Install specific pytest version + if: matrix.pytest-version != 'pytest@pre-release' + run: | + python -m pip install "${{ matrix.pytest-version }}" + + - name: Install specific pytest version + run: python -m pytest --version + - name: Install base Python requirements + uses: brettcannon/pip-secure-install@92f400e3191171c1858cc0e0d9ac6320173fdb0c # v1.0.0 + with: + requirements-file: '"${{ env.special-working-directory-relative }}/requirements.txt"' + options: '-t "${{ env.special-working-directory-relative }}/python_files/lib/python" --no-cache-dir --implementation py' + + - name: Install test requirements + run: python -m pip install --upgrade -r build/test-requirements.txt + + - name: Run Python unit tests + run: python python_files/tests/run_all.py + + tests: + name: Tests + # The value of runs-on is the OS of the current job (specified in the strategy matrix below) instead of being hardcoded. + runs-on: ${{ matrix.os }} + defaults: + run: + working-directory: ${{ env.special-working-directory }} + strategy: + fail-fast: false + matrix: + # We're not running CI on macOS for now because it's one less matrix entry to lower the number of runners used, + # macOS runners are expensive, and we assume that Ubuntu is enough to cover the Unix case. + os: [ubuntu-latest, windows-latest] + # Run the tests on the oldest and most recent versions of Python. + python: ['3.x'] + test-suite: [ts-unit, venv, single-workspace, debugger, functional] + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + path: ${{ env.special-working-directory-relative }} + persist-credentials: false + + - name: Checkout Python Environment Tools + uses: actions/checkout@v6 + with: + repository: 'microsoft/python-environment-tools' + path: ${{ env.special-working-directory-relative }}/python-env-tools + persist-credentials: false + sparse-checkout: | + crates + Cargo.toml + Cargo.lock + sparse-checkout-cone-mode: false + + - name: Install Node + uses: actions/setup-node@v6 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + cache-dependency-path: ${{ env.special-working-directory-relative }}/package-lock.json + + - name: Install dependencies (npm ci) + run: npm ci + + - name: Compile + run: npx gulp prePublishNonBundle + + - name: Localization + run: npx @vscode/l10n-dev@latest export ./src + + - name: Use Python ${{ matrix.python }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python }} + + - name: Upgrade Pip + run: python -m pip install -U pip + + # For faster/better builds of sdists. + - name: Install build pre-requisite + run: python -m pip install wheel nox + + - name: Install Python Extension dependencies (jedi, etc.) + run: nox --session install_python_libs + + - name: Install test requirements + run: python -m pip install --upgrade -r build/test-requirements.txt + + - name: Rust Tool Chain setup + uses: dtolnay/rust-toolchain@stable + + - name: Build Native Binaries + run: nox --session native_build + shell: bash + + - name: Install functional test requirements + run: python -m pip install --upgrade -r ./build/functional-test-requirements.txt + if: matrix.test-suite == 'functional' + + - name: Prepare pipenv for venv tests + env: + TEST_FILES_SUFFIX: testvirtualenvs + PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' + shell: pwsh + if: matrix.test-suite == 'venv' + run: | + python -m pip install pipenv + python -m pipenv run python ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} pipenvPath + + - name: Prepare poetry for venv tests + env: + TEST_FILES_SUFFIX: testvirtualenvs + shell: pwsh + if: matrix.test-suite == 'venv' + run: | + python -m pip install poetry + Move-Item -Path ".\build\ci\pyproject.toml" -Destination . + poetry env use python + + - name: Prepare virtualenv for venv tests + env: + TEST_FILES_SUFFIX: testvirtualenvs + PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' + shell: pwsh + if: matrix.test-suite == 'venv' + run: | + python -m pip install virtualenv + python -m virtualenv .virtualenv/ + if ('${{ matrix.os }}' -match 'windows-latest') { + & ".virtualenv/Scripts/python.exe" ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} virtualEnvPath + } else { + & ".virtualenv/bin/python" ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} virtualEnvPath + } + + - name: Prepare venv for venv tests + env: + TEST_FILES_SUFFIX: testvirtualenvs + PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' + shell: pwsh + if: matrix.test-suite == 'venv' && startsWith(matrix.python, 3.) + run: | + python -m venv .venv + if ('${{ matrix.os }}' -match 'windows-latest') { + & ".venv/Scripts/python.exe" ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} venvPath + } else { + & ".venv/bin/python" ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} venvPath + } + + - name: Prepare conda for venv tests + env: + TEST_FILES_SUFFIX: testvirtualenvs + PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' + shell: pwsh + if: matrix.test-suite == 'venv' + run: | + # 1. For `*.testvirtualenvs.test.ts` + if ('${{ matrix.os }}' -match 'windows-latest') { + $condaPythonPath = Join-Path -Path $Env:CONDA -ChildPath python.exe + $condaExecPath = Join-Path -Path $Env:CONDA -ChildPath Scripts | Join-Path -ChildPath conda + } else{ + $condaPythonPath = Join-Path -Path $Env:CONDA -ChildPath bin | Join-Path -ChildPath python + $condaExecPath = Join-Path -Path $Env:CONDA -ChildPath bin | Join-Path -ChildPath conda + } + & $condaPythonPath ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} condaExecPath $condaExecPath + & $condaPythonPath ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} condaPath + & $condaExecPath init --all + + - name: Set CI_PYTHON_PATH and CI_DISABLE_AUTO_SELECTION + run: | + echo "CI_PYTHON_PATH=python" >> $GITHUB_ENV + echo "CI_DISABLE_AUTO_SELECTION=1" >> $GITHUB_ENV + shell: bash + if: matrix.test-suite != 'ts-unit' + + # Run TypeScript unit tests only for Python 3.X. + - name: Run TypeScript unit tests + run: npm run test:unittests + if: matrix.test-suite == 'ts-unit' && startsWith(matrix.python, 3.) + + # The virtual environment based tests use the `testSingleWorkspace` set of tests + # with the environment variable `TEST_FILES_SUFFIX` set to `testvirtualenvs`, + # which is set in the "Prepare environment for venv tests" step. + # We also use a third-party GitHub Action to install xvfb on Linux, + # run tests and then clean up the process once the tests ran. + # See https://github.com/GabrielBB/xvfb-action + - name: Run venv tests + env: + TEST_FILES_SUFFIX: testvirtualenvs + CI_PYTHON_VERSION: ${{ matrix.python }} + uses: GabrielBB/xvfb-action@b706e4e27b14669b486812790492dc50ca16b465 # v1.7 + with: + run: npm run testSingleWorkspace + working-directory: ${{ env.special-working-directory }} + if: matrix.test-suite == 'venv' + + - name: Run single-workspace tests + env: + CI_PYTHON_VERSION: ${{ matrix.python }} + uses: GabrielBB/xvfb-action@b706e4e27b14669b486812790492dc50ca16b465 # v1.7 + with: + run: npm run testSingleWorkspace + working-directory: ${{ env.special-working-directory }} + if: matrix.test-suite == 'single-workspace' + + - name: Run debugger tests + env: + CI_PYTHON_VERSION: ${{ matrix.python }} + uses: GabrielBB/xvfb-action@b706e4e27b14669b486812790492dc50ca16b465 # v1.7 + with: + run: npm run testDebugger + working-directory: ${{ env.special-working-directory }} + if: matrix.test-suite == 'debugger' + + # Run TypeScript functional tests + - name: Run TypeScript functional tests + run: npm run test:functional + if: matrix.test-suite == 'functional' + + native-tests: + name: Native Tests + # The value of runs-on is the OS of the current job (specified in the strategy matrix below) instead of being hardcoded. + runs-on: ${{ matrix.os }} + defaults: + run: + working-directory: ${{ env.special-working-directory }} + strategy: + fail-fast: false + matrix: + # We're not running CI on macOS for now because it's one less matrix entry to lower the number of runners used, + # macOS runners are expensive, and we assume that Ubuntu is enough to cover the Unix case. + os: [ubuntu-latest, windows-latest] + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + path: ${{ env.special-working-directory-relative }} + persist-credentials: false + + - name: Checkout Python Environment Tools + uses: actions/checkout@v6 + with: + repository: 'microsoft/python-environment-tools' + path: ${{ env.special-working-directory-relative }}/python-env-tools + persist-credentials: false + sparse-checkout: | + crates + Cargo.toml + Cargo.lock + sparse-checkout-cone-mode: false + + - name: Python Environment Tools tests + run: cargo test -- --nocapture + working-directory: ${{ env.special-working-directory }}/python-env-tools + + smoke-tests: + name: Smoke tests + # The value of runs-on is the OS of the current job (specified in the strategy matrix below) instead of being hardcoded. + runs-on: ${{ matrix.os }} + needs: [build-vsix] + strategy: + fail-fast: false + matrix: + # We're not running CI on macOS for now because it's one less matrix entry to lower the number of runners used, + # macOS runners are expensive, and we assume that Ubuntu is enough to cover the UNIX case. + include: + - os: windows-latest + vsix-target: win32-x64 + - os: ubuntu-latest + vsix-target: linux-x64 + + steps: + # Need the source to have the tests available. + - name: Checkout + uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Checkout Python Environment Tools + uses: actions/checkout@v6 + with: + repository: 'microsoft/python-environment-tools' + path: python-env-tools + persist-credentials: false + sparse-checkout: | + crates + Cargo.toml + Cargo.lock + sparse-checkout-cone-mode: false + + - name: Smoke tests + uses: ./.github/actions/smoke-tests + with: + node_version: ${{ env.NODE_VERSION }} + artifact_name: '${{ env.ARTIFACT_NAME_VSIX }}-${{ matrix.vsix-target }}' + + ### Coverage run + coverage: + name: Coverage + # TEMPORARILY DISABLED - hanging in CI, needs investigation + if: false + # The value of runs-on is the OS of the current job (specified in the strategy matrix below) instead of being hardcoded. + runs-on: ${{ matrix.os }} + needs: [lint, check-types, python-tests, tests, native-tests] + strategy: + fail-fast: false + matrix: + # Only run coverage on linux for PRs + os: [ubuntu-latest] + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Checkout Python Environment Tools + uses: actions/checkout@v6 + with: + repository: 'microsoft/python-environment-tools' + path: python-env-tools + persist-credentials: false + sparse-checkout: | + crates + Cargo.toml + Cargo.lock + sparse-checkout-cone-mode: false + + - name: Install Node + uses: actions/setup-node@v6 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + + - name: Install dependencies (npm ci) + run: npm ci + + - name: Compile + run: npx gulp prePublishNonBundle + + - name: Localization + run: npx @vscode/l10n-dev@latest export ./src + + - name: Use Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v6 + with: + python-version: ${{ env.PYTHON_VERSION }} + cache: 'pip' + cache-dependency-path: | + requirements.txt + python_files/jedilsp_requirements/requirements.txt + build/test-requirements.txt + build/functional-test-requirements.txt + + - name: Install base Python requirements + uses: brettcannon/pip-secure-install@92f400e3191171c1858cc0e0d9ac6320173fdb0c # v1.0.0 + with: + options: '-t ./python_files/lib/python --implementation py' + + - name: Install Jedi requirements + uses: brettcannon/pip-secure-install@92f400e3191171c1858cc0e0d9ac6320173fdb0c # v1.0.0 + with: + requirements-file: './python_files/jedilsp_requirements/requirements.txt' + options: '-t ./python_files/lib/jedilsp --implementation py' + + - name: Install build pre-requisite + run: python -m pip install wheel nox + shell: bash + + - name: Rust Tool Chain setup + uses: dtolnay/rust-toolchain@stable + + - name: Build Native Binaries + run: nox --session native_build + shell: bash + + - name: Install test requirements + run: python -m pip install --upgrade -r build/test-requirements.txt + + - name: Install functional test requirements + run: python -m pip install --upgrade -r ./build/functional-test-requirements.txt + + - name: Prepare pipenv for venv tests + env: + TEST_FILES_SUFFIX: testvirtualenvs + PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' + shell: pwsh + run: | + python -m pip install pipenv + python -m pipenv run python ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} pipenvPath + + - name: Prepare poetry for venv tests + env: + TEST_FILES_SUFFIX: testvirtualenvs + shell: pwsh + run: | + python -m pip install poetry + Move-Item -Path ".\build\ci\pyproject.toml" -Destination . + poetry env use python + + - name: Prepare virtualenv for venv tests + env: + TEST_FILES_SUFFIX: testvirtualenvs + PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' + shell: pwsh + run: | + python -m pip install virtualenv + python -m virtualenv .virtualenv/ + if ('${{ matrix.os }}' -match 'windows-latest') { + & ".virtualenv/Scripts/python.exe" ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} virtualEnvPath + } else { + & ".virtualenv/bin/python" ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} virtualEnvPath + } + + - name: Prepare venv for venv tests + env: + TEST_FILES_SUFFIX: testvirtualenvs + PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' + shell: pwsh + run: | + python -m venv .venv + if ('${{ matrix.os }}' -match 'windows-latest') { + & ".venv/Scripts/python.exe" ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} venvPath + } else { + & ".venv/bin/python" ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} venvPath + } + + - name: Prepare conda for venv tests + env: + TEST_FILES_SUFFIX: testvirtualenvs + PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' + shell: pwsh + run: | + # 1. For `*.testvirtualenvs.test.ts` + if ('${{ matrix.os }}' -match 'windows-latest') { + $condaPythonPath = Join-Path -Path $Env:CONDA -ChildPath python.exe + $condaExecPath = Join-Path -Path $Env:CONDA -ChildPath Scripts | Join-Path -ChildPath conda + } else{ + $condaPythonPath = Join-Path -Path $Env:CONDA -ChildPath bin | Join-Path -ChildPath python + $condaExecPath = Join-Path -Path $Env:CONDA -ChildPath bin | Join-Path -ChildPath conda + } + & $condaPythonPath ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} condaExecPath $condaExecPath + & $condaPythonPath ./build/ci/addEnvPath.py ${{ env.PYTHON_VIRTUAL_ENVS_LOCATION }} condaPath + & $condaExecPath init --all + + - name: Run TypeScript unit tests + run: npm run test:unittests:cover + + - name: Run Python unit tests + run: | + python python_files/tests/run_all.py + + # The virtual environment based tests use the `testSingleWorkspace` set of tests + # with the environment variable `TEST_FILES_SUFFIX` set to `testvirtualenvs`, + # which is set in the "Prepare environment for venv tests" step. + # We also use a third-party GitHub Action to install xvfb on Linux, + # run tests and then clean up the process once the tests ran. + # See https://github.com/GabrielBB/xvfb-action + - name: Run venv tests + env: + TEST_FILES_SUFFIX: testvirtualenvs + CI_PYTHON_VERSION: ${{ env.PYTHON_VERSION }} + CI_DISABLE_AUTO_SELECTION: 1 + uses: GabrielBB/xvfb-action@b706e4e27b14669b486812790492dc50ca16b465 # v1.7 + with: + run: npm run testSingleWorkspace:cover + + - name: Run single-workspace tests + env: + CI_PYTHON_VERSION: ${{ env.PYTHON_VERSION }} + CI_DISABLE_AUTO_SELECTION: 1 + uses: GabrielBB/xvfb-action@b706e4e27b14669b486812790492dc50ca16b465 # v1.7 + with: + run: npm run testSingleWorkspace:cover + + # Enable these tests when coverage is setup for multiroot workspace tests + # - name: Run multi-workspace tests + # env: + # CI_PYTHON_VERSION: ${{ env.PYTHON_VERSION }} + # CI_DISABLE_AUTO_SELECTION: 1 + # uses: GabrielBB/xvfb-action@b706e4e27b14669b486812790492dc50ca16b465 # v1.7 + # with: + # run: npm run testMultiWorkspace:cover + + # Enable these tests when coverage is setup for debugger tests + # - name: Run debugger tests + # env: + # CI_PYTHON_VERSION: ${{ env.PYTHON_VERSION }} + # CI_DISABLE_AUTO_SELECTION: 1 + # uses: GabrielBB/xvfb-action@b706e4e27b14669b486812790492dc50ca16b465 # v1.7 + # with: + # run: npm run testDebugger:cover + + # Run TypeScript functional tests + - name: Run TypeScript functional tests + env: + CI_PYTHON_VERSION: ${{ env.PYTHON_VERSION }} + CI_DISABLE_AUTO_SELECTION: 1 + run: npm run test:functional:cover + + - name: Generate coverage reports + run: npm run test:cover:report + + - name: Upload HTML report + uses: actions/upload-artifact@v7 + with: + name: ${{ runner.os }}-coverage-report-html + path: ./coverage + retention-days: 1 diff --git a/.github/workflows/pr-file-check.yml b/.github/workflows/pr-file-check.yml new file mode 100644 index 000000000000..6364e5fa744e --- /dev/null +++ b/.github/workflows/pr-file-check.yml @@ -0,0 +1,44 @@ +name: PR files + +on: + pull_request: + types: + - 'opened' + - 'reopened' + - 'synchronize' + - 'labeled' + - 'unlabeled' + +permissions: {} + +jobs: + changed-files-in-pr: + name: 'Check for changed files' + runs-on: ubuntu-latest + steps: + - name: 'package-lock.json matches package.json' + uses: brettcannon/check-for-changed-files@871d7b8b5917a4f6f06662e2262e8ffc51dff6d1 # v1.2.1 + with: + prereq-pattern: 'package.json' + file-pattern: 'package-lock.json' + skip-label: 'skip package*.json' + failure-message: '${prereq-pattern} was edited but ${file-pattern} was not (the ${skip-label} label can be used to pass this check)' + + - name: 'package.json matches package-lock.json' + uses: brettcannon/check-for-changed-files@871d7b8b5917a4f6f06662e2262e8ffc51dff6d1 # v1.2.1 + with: + prereq-pattern: 'package-lock.json' + file-pattern: 'package.json' + skip-label: 'skip package*.json' + failure-message: '${prereq-pattern} was edited but ${file-pattern} was not (the ${skip-label} label can be used to pass this check)' + + - name: 'Tests' + uses: brettcannon/check-for-changed-files@871d7b8b5917a4f6f06662e2262e8ffc51dff6d1 # v1.2.1 + with: + prereq-pattern: src/**/*.ts + file-pattern: | + src/**/*.test.ts + src/**/*.testvirtualenvs.ts + .github/test_plan.md + skip-label: 'skip tests' + failure-message: 'TypeScript code was edited without also editing a ${file-pattern} file; see the Testing page in our wiki on testing guidelines (the ${skip-label} label can be used to pass this check)' diff --git a/.github/workflows/pr-issue-check.yml b/.github/workflows/pr-issue-check.yml new file mode 100644 index 000000000000..5587227d2848 --- /dev/null +++ b/.github/workflows/pr-issue-check.yml @@ -0,0 +1,31 @@ +name: PR issue check + +on: + pull_request: + types: + - 'opened' + - 'reopened' + - 'synchronize' + - 'labeled' + - 'unlabeled' + +permissions: {} + +jobs: + check-for-attached-issue: + name: 'Check for attached issue' + runs-on: ubuntu-latest + steps: + - name: 'Ensure PR has an associated issue' + uses: actions/github-script@v9 + with: + script: | + const labels = context.payload.pull_request.labels.map(label => label.name); + if (!labels.includes('skip-issue-check')) { + const prBody = context.payload.pull_request.body || ''; + const issueLink = prBody.match(/https:\/\/github\.com\/\S+\/issues\/\d+/); + const issueReference = prBody.match(/#\d+/); + if (!issueLink && !issueReference) { + core.setFailed('No associated issue found in the PR description.'); + } + } diff --git a/.github/workflows/pr-labels.yml b/.github/workflows/pr-labels.yml new file mode 100644 index 000000000000..af24ac10772c --- /dev/null +++ b/.github/workflows/pr-labels.yml @@ -0,0 +1,24 @@ +name: 'PR labels' +on: + pull_request: + types: + - 'opened' + - 'reopened' + - 'labeled' + - 'unlabeled' + - 'synchronize' + +jobs: + classify: + name: 'Classify PR' + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - name: 'PR impact specified' + uses: mheap/github-action-required-labels@0ac283b4e65c1fb28ce6079dea5546ceca98ccbe # v5.5.2 + with: + mode: exactly + count: 1 + labels: 'bug, debt, feature-request, no-changelog' diff --git a/.github/workflows/pr_datascience.yml b/.github/workflows/pr_datascience.yml deleted file mode 100644 index 481340e18add..000000000000 --- a/.github/workflows/pr_datascience.yml +++ /dev/null @@ -1,156 +0,0 @@ -name: Pull Request DataScience - -on: - pull_request: - branches: - - main - check_run: - types: [rerequested, requested_action] - -env: - NODE_VERSION: 12.15.0 - PYTHON_VERSION: 3.8 - MOCHA_REPORTER_JUNIT: true # Use the mocha-multi-reporters and send output to both console (spec) and JUnit (mocha-junit-reporter). Also enables a reporter which exits the process running the tests if it haven't already. - CACHE_NPM_DEPS: cache-npm - CACHE_OUT_DIRECTORY: cache-out-directory - CACHE_PIP_DEPS: cache-pip - # Key for the cache created at the end of the the 'Cache ./pythonFiles/lib/python' step. - CACHE_PYTHONFILES: cache-pvsc-pythonFiles - COVERAGE_REPORTS: tests-coverage-reports - CI_PYTHON_PATH: python - TEST_RESULTS_DIRECTORY: . - TEST_RESULTS_GLOB: '**/test-results*.xml' - -jobs: - tests: - name: Functional Jupyter Tests - runs-on: ${{ matrix.os }} - if: github.repository == 'microsoft/vscode-python' - strategy: - fail-fast: false - matrix: - # We're not running CI on macOS for now because it's one less matrix entry to lower the number of runners used, - # macOS runners are expensive, and we assume that Ubuntu is enough to cover the UNIX case. - os: [ubuntu-latest] - python: [3.8] - test-suite: [group1, group2, group3, group4] - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Use Python ${{matrix.python}} - uses: actions/setup-python@v2 - with: - python-version: ${{matrix.python}} - - - name: Upgrade pip - run: python -m pip install -U pip - - - name: Use Node ${{env.NODE_VERSION}} - uses: actions/setup-node@v2.1.1 - with: - node-version: ${{env.NODE_VERSION}} - - # Start caching - - # Cache Python Dependencies. - # Caching (https://github.com/actions/cache/blob/main/examples.md#python---pip - - name: Cache pip on linux - uses: actions/cache@v2 - if: matrix.os == 'ubuntu-latest' - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{env.PYTHON_VERSION}}-${{ hashFiles('requirements.txt') }}-${{hashFiles('build/debugger-install-requirements.txt')}}-${{hashFiles('test-requirements.txt')}}-${{hashFiles('ipython-test-requirements.txt')}}-${{hashFiles('functional-test-requirements.txt')}}-${{hashFiles('conda-functional-requirements.txt')}} - restore-keys: | - ${{ runner.os }}-pip-${{env.PYTHON_VERSION}}- - - - name: Cache pip on mac - uses: actions/cache@v2 - if: matrix.os == 'macos-latest' - with: - path: ~/Library/Caches/pip - key: ${{ runner.os }}-pip-${{env.PYTHON_VERSION}}-${{ hashFiles('requirements.txt') }}-${{hashFiles('build/debugger-install-requirements.txt')}}-${{hashFiles('test-requirements.txt')}}-${{hashFiles('ipython-test-requirements.txt')}}-${{hashFiles('functional-test-requirements.txt')}}-${{hashFiles('conda-functional-requirements.txt')}} - restore-keys: | - ${{ runner.os }}-pip-${{env.PYTHON_VERSION}}- - - - name: Cache pip on windows - uses: actions/cache@v2 - if: matrix.os == 'windows-latest' - with: - path: ~\AppData\Local\pip\Cache - key: ${{ runner.os }}-pip-${{env.PYTHON_VERSION}}-${{ hashFiles('requirements.txt') }}-${{hashFiles('build/debugger-install-requirements.txt')}}-${{hashFiles('test-requirements.txt')}}-${{hashFiles('ipython-test-requirements.txt')}}-${{hashFiles('functional-test-requirements.txt')}}-${{hashFiles('conda-functional-requirements.txt')}} - restore-keys: | - ${{ runner.os }}-pip-${{env.PYTHON_VERSION}}- - - # Caching of npm packages (https://github.com/actions/cache/blob/main/examples.md#node---npm) - - name: Cache npm on linux/mac - uses: actions/cache@v2 - if: matrix.os != 'windows-latest' - with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- - - - name: Get npm cache directory - if: matrix.os == 'windows-latest' - id: npm-cache - run: | - echo "::set-output name=dir::$(npm config get cache)" - - name: Cache npm on windows - uses: actions/cache@v2 - if: matrix.os == 'windows-latest' - with: - path: ${{ steps.npm-cache.outputs.dir }} - key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- - - - name: Cache compiled TS files - id: out-cache - uses: actions/cache@v2 - with: - path: ./out - key: ${{runner.os}}-${{env.CACHE_OUT_DIRECTORY}}-${{hashFiles('src/**')}} - - # For faster/better builds of sdists. - - run: python -m pip install wheel - shell: bash - - # debugpy is not shipped, only installed for local tests. - # In production, we get debugpy from python extension. - - name: Install functional test requirements - run: | - python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade -r ./requirements.txt - python -m pip --disable-pip-version-check install -r build/debugger-install-requirements.txt - python ./pythonFiles/install_debugpy.py - python -m pip install numpy - python -m pip install --upgrade jupyter - python -m pip install --upgrade -r build/test-requirements.txt - python -m pip install --upgrade -r ./build/ipython-test-requirements.txt - python -m pip install --upgrade -r ./build/conda-functional-requirements.txt - python -m ipykernel install --user - # This step is slow. - - - name: Install dependencies (npm ci) - run: npm ci --prefer-offline - # This step is slow. - - - name: Compile if not cached - run: npx gulp prePublishNonBundle - - - name: Run functional tests - run: npm run test:functional:parallel -- --${{matrix.test-suite}} - env: - VSCODE_PYTHON_ROLLING: 1 - VSC_PYTHON_FORCE_LOGGING: 1 - id: test_functional_group - - - name: Publish Test Report - uses: scacap/action-surefire-report@v1 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - report_paths: ${{ env.TEST_RESULTS_GLOB }} - check_name: Functional Test Report - if: steps.test_functional_group.outcome == 'failure' && failure() - diff --git a/.github/workflows/python27-issue-response.yml b/.github/workflows/python27-issue-response.yml new file mode 100644 index 000000000000..9db84bca1a23 --- /dev/null +++ b/.github/workflows/python27-issue-response.yml @@ -0,0 +1,16 @@ +on: + issues: + types: [opened] + +jobs: + python27-issue-response: + runs-on: ubuntu-latest + permissions: + issues: write + if: "contains(github.event.issue.body, 'Python version (& distribution if applicable, e.g. Anaconda): 2.7')" + steps: + - name: Check for Python 2.7 string + run: | + response="We're sorry, but we no longer support Python 2.7. If you need to work with Python 2.7, you will have to pin to 2022.2.* version of the extension, which was the last version that had the debugger (debugpy) with support for python 2.7, and was tested with `2.7`. Thank you for your understanding! \n ![https://user-images.githubusercontent.com/51720070/80000627-39dacc00-8472-11ea-9755-ac7ba0acbb70.gif](https://user-images.githubusercontent.com/51720070/80000627-39dacc00-8472-11ea-9755-ac7ba0acbb70.gif)" + gh issue comment ${{ github.event.issue.number }} --body "$response" + gh issue close ${{ github.event.issue.number }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 71eb74d3213a..000000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,397 +0,0 @@ -name: Release Build - -on: - push: - branches: - - 'release' - - 'release/*' - - 'release-*' - -env: - PYTHON_VERSION: 3.8 - MOCHA_REPORTER_JUNIT: false # Use the mocha-multi-reporters and send output to both console (spec) and JUnit (mocha-junit-reporter). Also enables a reporter which exits the process running the tests if it hasn't already. - CACHE_NPM_DEPS: cache-npm - CACHE_OUT_DIRECTORY: cache-out-directory - CACHE_PIP_DEPS: cache-pip - # Key for the cache created at the end of the the 'Cache ./pythonFiles/lib/python' step. - CACHE_PYTHONFILES: cache-pvsc-pythonFiles - ARTIFACT_NAME_VSIX: ms-python-release-vsix - VSIX_NAME: ms-python-release.vsix - COVERAGE_REPORTS: tests-coverage-reports - TEST_RESULTS_DIRECTORY: . - -jobs: - build-vsix: - name: Build VSIX - runs-on: ubuntu-latest - if: github.repository == 'microsoft/vscode-python' - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Cache pip files - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{runner.os}}-${{env.CACHE_PIP_DEPS}}-${{env.PYTHON_VERSION}}-${{hashFiles('requirements.txt')}}-${{hashFiles('build/debugger-install-requirements.txt')}} - - - name: Cache npm files - uses: actions/cache@v2 - with: - path: ~/.npm - key: ${{runner.os}}-${{env.CACHE_NPM_DEPS}}-${{hashFiles('package-lock.json')}} - - - name: Use Python ${{env.PYTHON_VERSION}} - uses: actions/setup-python@v2 - with: - python-version: ${{env.PYTHON_VERSION}} - - - name: Upgrade pip - run: python -m pip install -U pip - - - name: Build VSIX - uses: ./.github/actions/build-vsix - id: build-vsix - - - name: Rename VSIX - if: steps.build-vsix.outputs.path != env.VSIX_NAME - run: mv ${{ steps.build-vsix.outputs.path }} ${{ env.VSIX_NAME }} - - - uses: actions/upload-artifact@v2 - with: - name: ${{ env.ARTIFACT_NAME_VSIX }} - path: ${{ env.VSIX_NAME }} - - lint: - # Unlike for the insiders build, we skip linting file formatting as that - # will be caught when we merge back into 'main'. - name: Lint - runs-on: ubuntu-latest - if: github.repository == 'microsoft/vscode-python' - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Cache npm files - uses: actions/cache@v2 - with: - path: ~/.npm - key: ${{runner.os}}-${{env.CACHE_NPM_DEPS}}-${{hashFiles('package-lock.json')}} - - - name: Install dependencies (npm ci) - run: npm ci --prefer-offline - - - name: Run gulp prePublishNonBundle - run: npx gulp prePublishNonBundle - - - name: Cache the out/ directory - uses: actions/cache@v2 - with: - path: ./out - key: ${{runner.os}}-${{env.CACHE_OUT_DIRECTORY}}-${{hashFiles('src/**')}} - - - name: Check dependencies - run: npm run checkDependencies - - - name: Run linting on TypeScript code - run: npx tslint --project tsconfig.json - - ### Non-smoke tests - tests: - name: Tests - # The value of runs-on is the OS of the current job (specified in the strategy matrix below) instead of being hardcoded. - runs-on: ${{ matrix.os }} - if: github.repository == 'microsoft/vscode-python' - strategy: - fail-fast: false - matrix: - # We're not running CI on macOS for now because it's one less matrix entry to lower the number of runners used, - # macOS runners are expensive, and we assume that Ubuntu is enough to cover the UNIX case. - os: [ubuntu-latest, windows-latest] - # Run the tests on the oldest and most recent versions of Python. - python: [2.7, 3.8] - test-suite: [ts-unit, python-unit, venv, single-workspace, multi-workspace, debugger, functional] - env: - # Something in Node 12.16.0 breaks the TS debug adapter, and ubuntu-latest bundles Node 12.16.1. - # We can remove this when we switch over to the python-based DA in https://github.com/microsoft/vscode-python/issues/7136. - # See https://github.com/microsoft/ptvsd/issues/2068 - # At this point pinning is only needed for consistency. We no longer have TS debug adapter. - NODE_VERSION: 12.15.0 - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Cache pip files - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{runner.os}}-${{env.CACHE_PIP_DEPS}}-${{env.PYTHON_VERSION}}-${{hashFiles('requirements.txt')}}-${{hashFiles('build/debugger-install-requirements.txt')}} - - - name: Cache npm files - uses: actions/cache@v2 - with: - path: ~/.npm - key: ${{runner.os}}-${{env.CACHE_NPM_DEPS}}-${{hashFiles('package-lock.json')}} - - - name: Cache compiled TS files - # Use an id for this step so that its cache-hit output can be accessed and checked in the next step. - id: out-cache - uses: actions/cache@v2 - with: - path: ./out - key: ${{runner.os}}-${{env.CACHE_OUT_DIRECTORY}}-${{hashFiles('src/**')}} - - - name: Install dependencies (npm ci) - run: npm ci - - - name: Compile if not cached - run: npx gulp prePublishNonBundle - if: steps.out-cache.outputs.cache-hit == false - - - name: Use Python ${{matrix.python}} - uses: actions/setup-python@v2 - with: - python-version: ${{matrix.python}} - - - name: Use Node ${{env.NODE_VERSION}} - uses: actions/setup-node@v2.1.1 - with: - node-version: ${{env.NODE_VERSION}} - - - name: Install Python requirements - run: | - python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade -r requirements.txt - # We need to have debugpy so that tests relying on it keep passing, but we don't need install_debugpy's logic in the test phase. - python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade --pre debugpy - - - name: Install test requirements - run: python -m pip install --upgrade -r build/test-requirements.txt - - - name: pip install ipython requirements - run: | - python -m pip install numpy - python -m pip install --upgrade -r ./build/ipython-test-requirements.txt - if: matrix.test-suite == 'python-unit' - - - name: Install debugpy wheels (python 3.8) - run: | - python -m pip install wheel - python -m pip --disable-pip-version-check install -r build/debugger-install-requirements.txt - python ./pythonFiles/install_debugpy.py - shell: bash - if: matrix.test-suite == 'debugger' && matrix.python == 3.8 - - - name: Install debugpy (python 2.7) - run: | - python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade --pre debugpy - shell: bash - if: matrix.test-suite == 'debugger' && matrix.python == 2.7 - - - name: Install functional test requirements - run: | - python -m pip install numpy - python -m pip install --upgrade -r ./build/functional-test-requirements.txt - if: matrix.test-suite == 'functional' - - - name: Prepare pipenv for venv tests - env: - TEST_FILES_SUFFIX: testvirtualenvs - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - shell: pwsh - if: matrix.test-suite == 'venv' - run: | - python -m pip install pipenv - python -m pipenv run python ./build/ci/addEnvPath.py ${{env.PYTHON_VIRTUAL_ENVS_LOCATION}} pipenvPath - - - name: Prepare virtualenv for venv tests - env: - TEST_FILES_SUFFIX: testvirtualenvs - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - shell: pwsh - if: matrix.test-suite == 'venv' - run: | - python -m pip install virtualenv - python -m virtualenv .virtualenv/ - if ('${{matrix.os}}' -match 'windows-latest') { - & ".virtualenv/Scripts/python.exe" ./build/ci/addEnvPath.py ${{env.PYTHON_VIRTUAL_ENVS_LOCATION}} virtualEnvPath - } else { - & ".virtualenv/bin/python" ./build/ci/addEnvPath.py ${{env.PYTHON_VIRTUAL_ENVS_LOCATION}} virtualEnvPath - } - - - name: Prepare venv for venv tests - env: - TEST_FILES_SUFFIX: testvirtualenvs - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - shell: pwsh - if: matrix.test-suite == 'venv' && startsWith(matrix.python, 3.) - run: | - python -m venv .venv - if ('${{matrix.os}}' -match 'windows-latest') { - & ".venv/Scripts/python.exe" ./build/ci/addEnvPath.py ${{env.PYTHON_VIRTUAL_ENVS_LOCATION}} venvPath - } else { - & ".venv/bin/python" ./build/ci/addEnvPath.py ${{env.PYTHON_VIRTUAL_ENVS_LOCATION}} venvPath - } - - - name: Prepare conda for venv tests - env: - TEST_FILES_SUFFIX: testvirtualenvs - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - shell: pwsh - if: matrix.test-suite == 'venv' - run: | - # 1. For `terminalActivation.testvirtualenvs.test.ts` - if ('${{matrix.os}}' -match 'windows-latest') { - $condaPythonPath = Join-Path -Path $Env:CONDA -ChildPath python.exe - $condaExecPath = Join-Path -Path $Env:CONDA -ChildPath Scripts | Join-Path -ChildPath conda - } else{ - $condaPythonPath = Join-Path -Path $Env:CONDA -ChildPath bin | Join-Path -ChildPath python - $condaExecPath = Join-Path -Path $Env:CONDA -ChildPath bin | Join-Path -ChildPath conda - } - & $condaPythonPath ./build/ci/addEnvPath.py ${{env.PYTHON_VIRTUAL_ENVS_LOCATION}} condaExecPath $condaExecPath - & $condaPythonPath ./build/ci/addEnvPath.py ${{env.PYTHON_VIRTUAL_ENVS_LOCATION}} condaPath - - # 2. For `interpreterLocatorService.testvirtualenvs.ts` - - & $condaExecPath create -n "test_env1" -y python - & $condaExecPath create -p "./test_env2" -y python - & $condaExecPath create -p "~/test_env3" -y python - - - name: Set CI_PYTHON_PATH and CI_DISABLE_AUTO_SELECTION - run: | - echo "::set-env name=CI_PYTHON_PATH::python" - echo "::set-env name=CI_DISABLE_AUTO_SELECTION::1" - shell: bash - if: matrix.test-suite != 'ts-unit' - - # Run TypeScript unit tests only for Python 3.X. - - name: Run TypeScript unit tests - run: npm run test:unittests:cover - if: matrix.test-suite == 'ts-unit' && startsWith(matrix.python, 3.) - - # Run the Python and IPython tests in our codebase. - - name: Run Python and IPython unit tests - run: | - python pythonFiles/tests/run_all.py - python -m IPython pythonFiles/tests/run_all.py - if: matrix.test-suite == 'python-unit' - - # The virtual environment based tests use the `testSingleWorkspace` set of tests - # with the environment variable `TEST_FILES_SUFFIX` set to `testvirtualenvs`, - # which is set in the "Prepare environment for venv tests" step. - # We also use a third-party GitHub Action to install xvfb on Linux, - # run tests and then clean up the process once the tests ran. - # See https://github.com/GabrielBB/xvfb-action - - name: Run venv tests - env: - TEST_FILES_SUFFIX: testvirtualenvs - CI_PYTHON_VERSION: ${{matrix.python}} - uses: GabrielBB/xvfb-action@v1.4 - with: - run: npm run testSingleWorkspace - if: matrix.test-suite == 'venv' - - - name: Run single-workspace tests - env: - CI_PYTHON_VERSION: ${{matrix.python}} - uses: GabrielBB/xvfb-action@v1.4 - with: - run: npm run testSingleWorkspace - if: matrix.test-suite == 'single-workspace' - - - name: Run multi-workspace tests - env: - CI_PYTHON_VERSION: ${{matrix.python}} - uses: GabrielBB/xvfb-action@v1.4 - with: - run: npm run testMultiWorkspace - if: matrix.test-suite == 'multi-workspace' - - - name: Run debugger tests - env: - CI_PYTHON_VERSION: ${{matrix.python}} - uses: GabrielBB/xvfb-action@v1.4 - with: - run: npm run testDebugger - if: matrix.test-suite == 'debugger' - - - name: Run functional tests - run: npm run test:functional - if: matrix.test-suite == 'functional' - - smoke-tests: - name: Smoke tests - # The value of runs-on is the OS of the current job (specified in the strategy matrix below) instead of being hardcoded. - runs-on: ${{ matrix.os }} - if: github.repository == 'microsoft/vscode-python' - needs: [build-vsix] - strategy: - fail-fast: false - matrix: - # We're not running CI on macOS for now because it's one less matrix entry to lower the number of runners used, - # macOS runners are expensive, and we assume that Ubuntu is enough to cover the UNIX case. - os: [ubuntu-latest, windows-latest] - python: [3.8] - steps: - # Need the source to have the tests available. - - name: Checkout - uses: actions/checkout@v2 - - - name: Cache pip files - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{runner.os}}-${{env.CACHE_PIP_DEPS}}-${{env.PYTHON_VERSION}}-${{hashFiles('requirements.txt')}}-${{hashFiles('build/debugger-install-requirements.txt')}} - - - name: Cache npm files - uses: actions/cache@v2 - with: - path: ~/.npm - key: ${{runner.os}}-${{env.CACHE_NPM_DEPS}}-${{hashFiles('package-lock.json')}} - - - name: Use Python ${{matrix.python}} - uses: actions/setup-python@v2 - with: - python-version: ${{matrix.python}} - - - name: Install dependencies (npm ci) - run: npm ci --prefer-offline - - - name: pip install system test requirements - run: | - python -m pip install --upgrade -r build/test-requirements.txt - python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade -r requirements.txt - python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade --pre debugpy - shell: bash - - - name: pip install ipython requirements - run: | - python -m pip install numpy - python -m pip install --upgrade -r ./build/ipython-test-requirements.txt - - - name: pip install jupyter - run: | - python -m pip install --upgrade jupyter - - # Save time by reusing bits from the VSIX. - - name: Download VSIX - uses: actions/download-artifact@v2 - with: - name: ${{env.ARTIFACT_NAME_VSIX}} - - # Compile the test files. - - name: Prepare for smoke tests - run: npx tsc -p ./ - shell: bash - - - name: Set CI_PYTHON_PATH and CI_DISABLE_AUTO_SELECTION - run: | - echo "::set-env name=CI_PYTHON_PATH::python" - echo "::set-env name=CI_DISABLE_AUTO_SELECTION::1" - shell: bash - - - name: Run smoke tests - env: - DISPLAY: 10 - uses: GabrielBB/xvfb-action@v1.4 - with: - run: node --no-force-async-hooks-checks ./out/test/smokeTest.js diff --git a/.github/workflows/remove-needs-labels.yml b/.github/workflows/remove-needs-labels.yml new file mode 100644 index 000000000000..24352526d0d8 --- /dev/null +++ b/.github/workflows/remove-needs-labels.yml @@ -0,0 +1,20 @@ +name: 'Remove Needs Label' +on: + issues: + types: [closed] + +jobs: + classify: + name: 'Remove needs labels on issue closing' + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: 'Removes needs labels on issue close' + uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0 # v1.3.0 + with: + labels: | + needs PR + needs spike + needs community feedback + needs proposal diff --git a/.github/workflows/test-plan-item-validator.yml b/.github/workflows/test-plan-item-validator.yml new file mode 100644 index 000000000000..57db4a3e18a7 --- /dev/null +++ b/.github/workflows/test-plan-item-validator.yml @@ -0,0 +1,30 @@ +name: Test Plan Item Validator +on: + issues: + types: [edited, labeled] + +permissions: + issues: write + +jobs: + main: + runs-on: ubuntu-latest + if: contains(github.event.issue.labels.*.name, 'testplan-item') || contains(github.event.issue.labels.*.name, 'invalid-testplan-item') + steps: + - name: Checkout Actions + uses: actions/checkout@v6 + with: + repository: 'microsoft/vscode-github-triage-actions' + path: ./actions + persist-credentials: false + ref: stable + + - name: Install Actions + run: npm install --production --prefix ./actions + + - name: Run Test Plan Item Validator + uses: ./actions/test-plan-item-validator + with: + label: testplan-item + invalidLabel: invalid-testplan-item + comment: Invalid test plan item. See errors below and the [test plan item spec](https://github.com/microsoft/vscode/wiki/Writing-Test-Plan-Items) for more information. This comment will go away when the issues are resolved. diff --git a/.github/workflows/triage-info-needed.yml b/.github/workflows/triage-info-needed.yml new file mode 100644 index 000000000000..c7a37ba0c78d --- /dev/null +++ b/.github/workflows/triage-info-needed.yml @@ -0,0 +1,57 @@ +name: Triage "info-needed" label + +on: + issue_comment: + types: [created] + +env: + TRIAGERS: '["karrtikr","karthiknadig","paulacamargo25","eleanorjboyd", "brettcannon","anthonykim1"]' + +jobs: + add_label: + if: contains(github.event.issue.labels.*.name, 'triage-needed') && !contains(github.event.issue.labels.*.name, 'info-needed') + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Checkout Actions + uses: actions/checkout@v6 + with: + repository: 'microsoft/vscode-github-triage-actions' + ref: stable + path: ./actions + persist-credentials: false + + - name: Install Actions + run: npm install --production --prefix ./actions + + - name: Add "info-needed" label + uses: ./actions/python-triage-info-needed + with: + triagers: ${{ env.TRIAGERS }} + action: 'add' + token: ${{secrets.GITHUB_TOKEN}} + + remove_label: + if: contains(github.event.issue.labels.*.name, 'info-needed') && contains(github.event.issue.labels.*.name, 'triage-needed') + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Checkout Actions + uses: actions/checkout@v6 + with: + repository: 'microsoft/vscode-github-triage-actions' + ref: stable + path: ./actions + persist-credentials: false + + - name: Install Actions + run: npm install --production --prefix ./actions + + - name: Remove "info-needed" label + uses: ./actions/python-triage-info-needed + with: + triagers: ${{ env.TRIAGERS }} + action: 'remove' + token: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/week.js b/.github/workflows/week.js deleted file mode 100644 index b8a11013283b..000000000000 --- a/.github/workflows/week.js +++ /dev/null @@ -1,29 +0,0 @@ -/* For a given date, get the ISO week number - * - * Based on information at: - * - * http://www.merlyn.demon.co.uk/weekcalc.htm#WNR - * - * Algorithm is to find nearest thursday, it's year - * is the year of the week number. Then get weeks - * between that date and the first day of that year. - * - * Note that dates in one year can be weeks of previous - * or next year, overlap is up to 3 days. - * - * e.g. 2014/12/29 is Monday in week 1 of 2015 - * 2012/1/1 is Sunday in week 52 of 2011 - */ -function getWeekNumber(d) { - // Copy date so don't modify original - d = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())); - // Set to nearest Thursday: current date + 4 - current day number - // Make Sunday's day number 7 - d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7)); - // Get first day of year - var yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); - // Calculate full weeks to nearest Thursday - return Math.ceil(((d - yearStart) / 86400000 + 1) / 7); -} -// Whether it is an odd or event week. -console.log(getWeekNumber(new Date()) % 2); diff --git a/.gitignore b/.gitignore index a343e9929c81..2fa056f84fa6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ log.log **/node_modules *.pyc *.vsix +envVars.txt **/.vscode/.ropeproject/** **/testFiles/**/.cache/** *.noseids @@ -22,7 +23,8 @@ cucumber-report.json **/.venv*/ port.txt precommit.hook -pythonFiles/lib/** +python_files/lib/** +python_files/get-pip.py debug_coverage*/** languageServer/** languageServer.*/** @@ -41,3 +43,16 @@ debugpy*.log pydevd*.log nodeLanguageServer/** nodeLanguageServer.*/** +dist/** +# translation files +*.xlf +package.nls.*.json +l10n/ +python-env-tools/** +# coverage files produced as test output +python_files/tests/*/.data/.coverage* +python_files/tests/*/.data/*/.coverage* +src/testTestingRootWkspc/coverageWorkspace/.coverage + +# ignore ai artifacts generated and placed in this folder +ai-artifacts/* diff --git a/.nvmrc b/.nvmrc index 764b945d130f..c6a66a6e6a68 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v12.15.0 +v22.21.1 diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 953832c3207c..000000000000 --- a/.prettierignore +++ /dev/null @@ -1,3 +0,0 @@ -# Ignore the pythonEnvironments/ folder because we use ESLint there instead -src/client/pythonEnvironments/* -src/test/pythonEnvironments/* diff --git a/.prettierrc.js b/.prettierrc.js index e584661edd38..87a94b7bf466 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -3,19 +3,13 @@ module.exports = { printWidth: 120, tabWidth: 4, endOfLine: 'auto', - trailingComma: 'none', + trailingComma: 'all', overrides: [ { files: ['*.yml', '*.yaml'], options: { tabWidth: 2 } - }, - { - files: ['**/datascience/serviceRegistry.ts'], - options: { - printWidth: 240 - } } ] }; diff --git a/.sonarcloud.properties b/.sonarcloud.properties index e20640d4b551..9e466689a90a 100644 --- a/.sonarcloud.properties +++ b/.sonarcloud.properties @@ -1,5 +1,4 @@ -sonar.sources=src/client,src/datascience-ui -sonar.exclusions=src/datascience-ui/**/codicon*.* +sonar.sources=src/client sonar.tests=src/test sonar.cfamily.build-wrapper-output.bypass=true -sonar.cpd.exclusions=src/datascience-ui/**/redux/actions.ts,src/client/**/raw-kernel/rawKernel.ts,src/client/datascience/jupyter/*ariable*.ts,src/client/activation/**/*.ts +sonar.cpd.exclusions=src/client/activation/**/*.ts diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 508eae4c5ddc..15e6aada1d50 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,8 +2,11 @@ // See https://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format "recommendations": [ - "ms-vscode.vscode-typescript-tslint-plugin", + "charliermarsh.ruff", "editorconfig.editorconfig", - "esbenp.prettier-vscode" + "esbenp.prettier-vscode", + "dbaeumer.vscode-eslint", + "ms-python.python", + "ms-python.vscode-pylance" ] } diff --git a/.vscode/launch.json b/.vscode/launch.json index 67a42917fef6..1e983413c8d4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,7 +8,6 @@ "request": "launch", "runtimeExecutable": "${execPath}", "args": ["--extensionDevelopmentPath=${workspaceFolder}"], - "stopOnEntry": false, "smartStep": true, "sourceMaps": true, "outFiles": ["${workspaceFolder}/out/**/*", "!${workspaceFolder}/**/node_modules**/*"], @@ -25,49 +24,17 @@ "XDEBUGPY_LOG_DIR": "${workspaceRoot}/tmp/Debug_Output_Ex" } }, - { - "name": "Extension (DS UI in Browser)", - "type": "extensionHost", - "request": "launch", - "runtimeExecutable": "${execPath}", - "args": ["--extensionDevelopmentPath=${workspaceFolder}"], - "stopOnEntry": false, - "smartStep": true, - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/out/**/*", "!${workspaceFolder}/**/node_modules**/*"], - "preLaunchTask": "Inject DS WebBrowser UI", - "env": { - "VSC_PYTHON_DS_UI_PROMPT": "1" - }, - "skipFiles": ["/**"] - }, { "name": "Extension inside container", "type": "extensionHost", "request": "launch", "runtimeExecutable": "${execPath}", "args": ["--extensionDevelopmentPath=${workspaceFolder}", "${workspaceFolder}/data"], - "stopOnEntry": false, "smartStep": true, "sourceMaps": true, "outFiles": ["${workspaceFolder}/out/**/*", "!${workspaceFolder}/**/node_modules**/*"], "preLaunchTask": "Compile" }, - { - "name": "Python: Current File with iPython", - "type": "python", - "request": "launch", - "module": "IPython", - "console": "integratedTerminal", - "args": ["${file}"] // Additional args should be prefixed with a '--' first. - }, - { - "name": "Python: Current File", - "type": "python", - "request": "launch", - "program": "${file}", - "console": "integratedTerminal" - }, { "name": "Tests (Debugger, VS Code, *.test.ts)", "type": "extensionHost", @@ -79,7 +46,6 @@ "--extensionDevelopmentPath=${workspaceFolder}", "--extensionTestsPath=${workspaceFolder}/out/test" ], - "stopOnEntry": false, "sourceMaps": true, "smartStep": true, "outFiles": ["${workspaceFolder}/out/**/*", "!${workspaceFolder}/**/node_modules**/*"], @@ -103,9 +69,10 @@ "--extensionTestsPath=${workspaceFolder}/out/test" ], "env": { - "VSC_PYTHON_CI_TEST_GREP": "Smoke Test" + "VSC_PYTHON_CI_TEST_GREP": "Smoke Test", + "VSC_PYTHON_SMOKE_TEST": "1", + "TEST_FILES_SUFFIX": "smoke.test" }, - "stopOnEntry": false, "sourceMaps": true, "outFiles": ["${workspaceFolder}/out/**/*.js", "!${workspaceFolder}/**/node_modules**/*"], "preLaunchTask": "Compile", @@ -125,7 +92,6 @@ "env": { "VSC_PYTHON_CI_TEST_GREP": "" // Modify this to run a subset of the single workspace tests }, - "stopOnEntry": false, "sourceMaps": true, "outFiles": ["${workspaceFolder}/out/**/*.js", "!${workspaceFolder}/**/node_modules**/*"], "preLaunchTask": "Compile", @@ -145,67 +111,11 @@ "env": { "VSC_PYTHON_CI_TEST_GREP": "Language Server:" }, - "stopOnEntry": false, "sourceMaps": true, "outFiles": ["${workspaceFolder}/out/**/*.js", "!${workspaceFolder}/**/node_modules**/*"], "preLaunchTask": "preTestJediLSP", "skipFiles": ["/**"] }, - { - "name": "Tests (DataScience, *.native.vscode.test.ts)", - "type": "extensionHost", - "request": "launch", - "runtimeExecutable": "${execPath}", - "args": [ - "${workspaceFolder}/src/test/datascience", - "--disable-extensions", - "--enable-proposed-api", - "--extensionDevelopmentPath=${workspaceFolder}", - "--extensionTestsPath=${workspaceFolder}/out/test" - ], - "env": { - "VSC_PYTHON_CI_TEST_GREP": "", // Modify this to run a subset of the single workspace tests - "VSC_PYTHON_CI_TEST_INVERT_GREP": "", // Initialize this to invert the grep (exclude tests with value defined in grep). - "CI_PYTHON_PATH": "", // Initialize this to invert the grep (exclude tests with value defined in grep). - - "VSC_PYTHON_LOAD_EXPERIMENTS_FROM_FILE": "true", - "TEST_FILES_SUFFIX": "native.vscode.test" - }, - "stopOnEntry": false, - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/out/**/*.js", "!${workspaceFolder}/**/node_modules**/*"], - "preLaunchTask": "Compile", - "skipFiles": ["/**"] - }, - { - "name": "DataScience tests in VS Code (*.vscode.ts)", - "type": "extensionHost", - "request": "launch", - "runtimeExecutable": "${execPath}", - "args": [ - "${workspaceFolder}/src/test/datascience", - "--disable-extensions", - "--extensionDevelopmentPath=${workspaceFolder}", - "--extensionTestsPath=${workspaceFolder}/out/test" - ], - "env": { - "VSC_PYTHON_CI_TEST_GREP": "", // Modify this to run a subset of the single workspace tests - "VSC_PYTHON_CI_TEST_INVERT_GREP": "", // Initialize this to invert the grep (exclude tests with value defined in grep). - "CI_PYTHON_PATH": "", // Initialize this to invert the grep (exclude tests with value defined in grep). - "VSC_PYTHON_LOAD_EXPERIMENTS_FROM_FILE": "true", - "TEST_FILES_SUFFIX": "vscode.test" - }, - "stopOnEntry": false, - "sourceMaps": true, - "outFiles": [ - "${workspaceFolder}/out/**/*.js", - "!${workspaceFolder}/**/node_modules**/*" - ], - "preLaunchTask": "Compile", - "skipFiles": [ - "/**" - ] - }, { "name": "Tests (Multiroot, VS Code, *.test.ts)", "type": "extensionHost", @@ -217,7 +127,9 @@ "--extensionDevelopmentPath=${workspaceFolder}", "--extensionTestsPath=${workspaceFolder}/out/test" ], - "stopOnEntry": false, + "env": { + "VSC_PYTHON_CI_TEST_GREP": "" // Modify this to run a subset of the single workspace tests + }, "sourceMaps": true, "smartStep": true, "outFiles": ["${workspaceFolder}/out/**/*", "!${workspaceFolder}/**/node_modules**/*"], @@ -292,38 +204,9 @@ // Remove `X` prefix and update path to test with real python interpreter (for DS functional tests). "XCI_PYTHON_PATH": "", // Remove 'X' prefix to dump output for debugger. Directory has to exist prior to launch - "XDEBUGPY_LOG_DIR": "${workspaceRoot}/tmp/Debug_Output" - }, - "outFiles": ["${workspaceFolder}/out/**/*.js", "!${workspaceFolder}/**/node_modules**/*"], - "preLaunchTask": "Compile", - "skipFiles": ["/**"] - }, - { - "name": "Functional DS UI Tests (without VS Code, *.ui.functional.test.ts)", - "type": "node", - "request": "launch", - "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", - "stopOnEntry": false, - "sourceMaps": true, - "args": [ - "./out/test/**/*.ui.functional.test.js", - "--require=out/test/unittests.js", - "--ui=tdd", - "--recursive", - "--colors", - //"--grep", "", - "--timeout=300000", - "--fast" - ], - "env": { - // Remove `X` prefix to test with real browser to host DS ui (for DS functional tests). - "XVSC_PYTHON_DS_UI_BROWSER": "1", - // Remove `X` prefix to test with real python (for DS functional tests). - "XVSCODE_PYTHON_ROLLING": "1", - // Remove 'X' to turn on all logging in the debug output - "XVSC_PYTHON_FORCE_LOGGING": "1", - // Remove `X` prefix and update path to test with real python interpreter (for DS functional tests). - "XCI_PYTHON_PATH": "" + "XDEBUGPY_LOG_DIR": "${workspaceRoot}/tmp/Debug_Output", + // Remove 'X' prefix to dump webview redux action log + "XVSC_PYTHON_WEBVIEW_LOG_FILE": "${workspaceRoot}/test-webview.log" }, "outFiles": ["${workspaceFolder}/out/**/*.js", "!${workspaceFolder}/**/node_modules**/*"], "preLaunchTask": "Compile", @@ -341,19 +224,39 @@ "name": "Node: Current File", "program": "${file}", "request": "launch", - "skipFiles": [ - "/**" - ], - "type": "pwa-node" + "skipFiles": ["/**"], + "type": "node" }, { "name": "Python: Current File", - "type": "python", + "type": "debugpy", "justMyCode": true, "request": "launch", "program": "${file}", "console": "integratedTerminal", "cwd": "${workspaceFolder}" + }, + { + "name": "Python: Attach Listen", + "type": "debugpy", + "request": "attach", + "listen": { "host": "localhost", "port": 5678 }, + "justMyCode": true + }, + { + "name": "Debug pytest plugin tests", + + "type": "debugpy", + "request": "launch", + "module": "pytest", + "args": ["${workspaceFolder}/python_files/tests/pytestadapter"], + "justMyCode": true + } + ], + "compounds": [ + { + "name": "Debug Python and Extension", + "configurations": ["Python: Attach Listen", "Extension"] } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index b2dc13efe26e..01de0d907706 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,7 @@ { "files.exclude": { "out": true, // set this to true to hide the "out" folder with the compiled JS files + "dist": true, "**/*.pyc": true, ".nyc_output": true, "obj": true, @@ -10,11 +11,11 @@ "**/node_modules": true, ".vscode-test": false, ".vscode test": false, - "**/.mypy_cache/**": true, - "**/.ropeproject/**": true + "**/.mypy_cache/**": true }, "search.exclude": { "out": true, // set this to false to include "out" folder in search results + "dist": true, "**/node_modules": true, "coverage": true, "languageServer*/**": true, @@ -22,39 +23,56 @@ ".vscode test": true }, "[python]": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit", + "source.organizeImports.isort": "explicit" + }, + "editor.defaultFormatter": "charliermarsh.ruff", + }, + "[rust]": { + "editor.defaultFormatter": "rust-lang.rust-analyzer", "editor.formatOnSave": true }, "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true }, "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[JSON]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[YAML]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true }, "typescript.tsdk": "./node_modules/typescript/lib", // we want to use the TS server from our node_modules folder to control its version - "tslint.enable": true, - "python.linting.enabled": false, - "python.testing.promptToConfigure": false, - "python.workspaceSymbols.enabled": false, - "python.formatting.provider": "black", "typescript.preferences.quoteStyle": "single", "javascript.preferences.quoteStyle": "single", - "typescriptHero.imports.stringQuoteStyle": "'", "prettier.printWidth": 120, "prettier.singleQuote": true, "editor.codeActionsOnSave": { - "source.fixAll.eslint": true, - "source.fixAll.tslint": true - }, - "python.languageServer": "Microsoft", - "python.analysis.logLevel": "Trace", - "python.analysis.downloadChannel": "beta", - "python.linting.pylintEnabled": false, - "python.linting.flake8Enabled": true, - "cucumberautocomplete.skipDocStringsFormat": true, - "python.linting.flake8Args": [ - // Match what black does. - "--max-line-length=88" - ], + "source.fixAll.eslint": "explicit" + }, + "python.languageServer": "Default", "typescript.preferences.importModuleSpecifier": "relative", - "debug.javascript.usePreview": false + // Branch name suggestion. + "git.branchProtectionPrompt": "alwaysCommitToNewBranch", + "git.branchRandomName.enable": true, + "git.branchProtection": ["main", "release/*"], + "git.pullBeforeCheckout": true, + // Open merge editor for resolving conflicts. + "git.mergeEditor": true, + "python.testing.pytestArgs": [ + "python_files/tests" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, + "rust-analyzer.linkedProjects": [ + ".\\python-env-tools\\Cargo.toml" + ] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 95e759d089ec..c5a054ed43cf 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -12,32 +12,12 @@ "type": "npm", "script": "compile", "isBackground": true, - "problemMatcher": [ - "$tsc-watch", - { - "base": "$tslint5", - "fileLocation": "relative" - } - ], + "problemMatcher": ["$tsc-watch"], "group": { "kind": "build", "isDefault": true } }, - { - "label": "Compile Web Views", - "type": "npm", - "script": "compile-webviews-watch", - "isBackground": true, - "group": { - "kind": "build", - "isDefault": true - }, - "problemMatcher": [ - "$tsc-watch", - "$ts-checker-webpack-watch" - ] - }, { "label": "Run Unit Tests", "type": "npm", @@ -47,20 +27,36 @@ "isDefault": true } }, - { - "label": "Inject DS WebBrowser UI", - "type": "shell", - "command": "node", - "args": [ - "build/debug/replaceWithWebBrowserPanel.js" - ], - "problemMatcher": [] - }, { "type": "npm", "script": "preTestJediLSP", "problemMatcher": [], "label": "preTestJediLSP" + }, + { + "type": "npm", + "script": "check-python", + "problemMatcher": ["$python"], + "label": "npm: check-python", + "detail": "npm run check-python:ruff && npm run check-python:pyright" + }, + { + "label": "npm: check-python (venv)", + "type": "shell", + "command": "bash", + "args": ["-lc", "source .venv/bin/activate && npm run check-python"], + "problemMatcher": [], + "detail": "Activates the repo .venv first (avoids pyenv/shim Python) then runs: npm run check-python", + "windows": { + "command": "pwsh", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + ".\\.venv\\Scripts\\Activate.ps1; npm run check-python" + ] + } } ] } diff --git a/.vscodeignore b/.vscodeignore index 5e685b54c307..d636ab48f361 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,55 +1,36 @@ **/*.map **/*.analyzer.html -!out/datascience-ui/common/node_modules/**/* -!out/datascience-ui/notebook/commons.initial.bundle.js.map -!out/datascience-ui/notebook/interactiveWindow.js.map -!out/datascience-ui/notebook/nativeEditor.js.map -!out/datascience-ui/viewers/commons.initial.bundle.js.map -!out/datascience-ui/viewers/dataExplorer.js.map -!out/datascience-ui/viewers/plotViewer.js.map +**/.env *.vsix -.appveyor.yml .editorconfig .env .eslintrc +.eslintignore .gitattributes .gitignore .gitmodules -.huskyrc.json +.git* .npmrc .nvmrc .nycrc -.travis.yml CODE_OF_CONDUCT.md CODING_STANDARDS.md CONTRIBUTING.md -CONTRIBUTING - LANGUAGE SERVER.md -coverconfig.json -cucumber-report.json gulpfile.js -package.datascience-ui.dependencies.json package-lock.json -packageExtension.cmd -pvsc-dev-ext.py -pvsc.code-workspace -PYTHON_INTERACTIVE_TROUBLESHOOTING.md requirements.in sprint-planning.github-issues test.ipynb -travis*.log tsconfig*.json tsfmt.json -tslint.json -typings.json -vsc-extension-quickstart.md vscode-python-signing.* -webpack.config.js -webpack.datascience-*.config.js +noxfile.py -.devcontainer/** +.config/** .github/** .mocha-reporter/** .nvm/** +.nox/** .nyc_output .prettierrc.js .sonarcloud.properties @@ -70,42 +51,39 @@ debug_coverage*/** images/**/*.gif images/**/*.png ipywidgets/** -news/** +i18n/** node_modules/** obj/** out/**/*.stats.json out/client/**/*.analyzer.html out/coverconfig.json -out/datascience-ui/**/*.analyzer.html -out/datascience-ui/common/** -out/datascience-ui/**/*.js.LICENSE -out/datascience-ui/data-explorer/** -out/datascience-ui/datascience-ui/** -out/datascience-ui/history-react/** -out/datascience-ui/interactive-common/** -out/datascience-ui/ipywidgets/** -out/datascience-ui/native-editor/** -out/datascience-ui/notebook/datascience-ui/** -out/datascience-ui/notebook/ipywidgets/** -out/datascience-ui/notebook/index.*.html -out/datascience-ui/plot/** -out/datascience-ui/react-common/** -out/datascience-ui/viewers/datascience-ui/** -out/datascience-ui/viewers/ipywidgets/** -out/datascience-ui/viewers/index.*.html -out/pythonFiles/** +out/python_files/** out/src/** out/test/** out/testMultiRootWkspc/** precommit.hook -pythonFiles/**/*.pyc -pythonFiles/lib/**/*.dist-info/** -pythonFiles/lib/**/*.egg-info/** -pythonFiles/lib/python/bin/** -pythonFiles/tests/** +python_files/**/*.pyc +python_files/lib/**/*.egg-info/** +python_files/lib/jedilsp/bin/** +python_files/lib/python/bin/** +python_files/tests/** scripts/** src/** test/** tmp/** typings/** types/** +**/__pycache__/** +**/.devcontainer/** + +python-env-tools/.gitignore +python-env-tools/bin/.gitignore +python-env-tools/.github/** +python-env-tools/.vscode/** +python-env-tools/crates/** +python-env-tools/target/** +python-env-tools/Cargo.* +python-env-tools/.cargo/** + +python-env-tools/**/*.md +pythonExtensionApi/** diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ce03027896b..56c1f7697ad7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,3735 @@ # Changelog +**Please see https://github.com/microsoft/vscode-python/releases for the latest release notes. The notes below have been kept for historical purposes.** + +## 2022.10.1 (14 July 2022) + +### Code Health + +- Update app insights key by [karthiknadig](https://github.com/karthiknadig) in ([#19463](https://github.com/microsoft/vscode-python/pull/19463)). + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Pylance](https://github.com/microsoft/pylance-release) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [poetry](https://pypi.org/project/poetry/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2022.10.0 (7 July 2022) + +### Enhancements + +- Add `breakpoint` support for `django-html` & `django-txt` by [Lakshmikanth2001](https://github.com/Lakshmikanth2001) in ([#19288](https://github.com/microsoft/vscode-python/pull/19288)). +- Fix `unittest` discovery issue with experimental component by [ksy7588](https://github.com/ksy7588) in ([#19324](https://github.com/microsoft/vscode-python/pull/19324)). +- Trigger refresh when using `Select Interpreter` command if no envs were found previously by [karrtikr](https://github.com/karrtikr) in ([#19361](https://github.com/microsoft/vscode-python/pull/19361)). +- Update `debugpy` to 1.6.2. + +### Bug Fixes + +- Fix variable name for `flake8Path`'s description by [usta](https://github.com/usta) in ([#19313](https://github.com/microsoft/vscode-python/pull/19313)). +- Ensure we dispose objects on deactivate by [karthiknadig](https://github.com/karthiknadig) in ([#19341](https://github.com/microsoft/vscode-python/pull/19341)). +- Ensure we can change interpreters after trusting a workspace by [karrtikr](https://github.com/karrtikr) in ([#19353](https://github.com/microsoft/vscode-python/pull/19353)). +- Fix for `::::` in node id for `pytest` by [karthiknadig](https://github.com/karthiknadig) in ([#19356](https://github.com/microsoft/vscode-python/pull/19356)). +- Ensure we register for interpreter change when moving from untrusted to trusted. by [karthiknadig](https://github.com/karthiknadig) in ([#19351](https://github.com/microsoft/vscode-python/pull/19351)). + +### Code Health + +- Update CI for using GitHub Actions for release notes by [brettcannon](https://github.com/brettcannon) in ([#19273](https://github.com/microsoft/vscode-python/pull/19273)). +- Add missing translations by [paulacamargo25](https://github.com/paulacamargo25) in ([#19305](https://github.com/microsoft/vscode-python/pull/19305)). +- Delete the `news` directory by [brettcannon](https://github.com/brettcannon) in ([#19308](https://github.com/microsoft/vscode-python/pull/19308)). +- Fix interpreter discovery related telemetry by [karrtikr](https://github.com/karrtikr) in ([#19319](https://github.com/microsoft/vscode-python/pull/19319)). +- Simplify and merge async dispose and dispose by [karthiknadig](https://github.com/karthiknadig) in ([#19348](https://github.com/microsoft/vscode-python/pull/19348)). +- Updating required packages by [karthiknadig](https://github.com/karthiknadig) in ([#19375](https://github.com/microsoft/vscode-python/pull/19375)). +- Update the issue notebook by [brettcannon](https://github.com/brettcannon) in ([#19388](https://github.com/microsoft/vscode-python/pull/19388)). +- Remove `notebookeditor` proposed API by [karthiknadig](https://github.com/karthiknadig) in ([#19392](https://github.com/microsoft/vscode-python/pull/19392)). + +**Full Changelog**: https://github.com/microsoft/vscode-python/compare/2022.8.1...2022.10.0 + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Pylance](https://github.com/microsoft/pylance-release) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [poetry](https://pypi.org/project/poetry/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2022.8.1 (28 June 2022) + +### Code Health + +1. Update vscode `extension-telemetry` package. + ([#19375](https://github.com/microsoft/vscode-python/pull/19375)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Pylance](https://github.com/microsoft/pylance-release) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [poetry](https://pypi.org/project/poetry/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2022.8.0 (9 June 2022) + +### Enhancements + +1. Make cursor focus switch automatically to the terminal after launching a python process with configuration option. (Thanks [djplt](https://github.com/djplt)) + ([#14851](https://github.com/Microsoft/vscode-python/issues/14851)) +1. Enable localization using vscode-nls. + ([#18286](https://github.com/Microsoft/vscode-python/issues/18286)) +1. Add support for referencing multiroot-workspace folders in settings using `${workspaceFolder:}`. + ([#18650](https://github.com/Microsoft/vscode-python/issues/18650)) +1. Ensure conda envs lacking an interpreter which do not use a valid python binary are also discovered and is selectable, so that `conda env list` matches with what the extension reports. + ([#18934](https://github.com/Microsoft/vscode-python/issues/18934)) +1. Improve information collected by the `Python: Report Issue` command. + ([#19067](https://github.com/Microsoft/vscode-python/issues/19067)) +1. Only trigger auto environment discovery if a user attempts to choose a different interpreter, or when a particular scope (a workspace folder or globally) is opened for the first time. + ([#19102](https://github.com/Microsoft/vscode-python/issues/19102)) +1. Added a proposed API to report progress of environment discovery in two phases. + ([#19103](https://github.com/Microsoft/vscode-python/issues/19103)) +1. Update to latest LS client (v8.0.0) and server (v8.0.0). + ([#19114](https://github.com/Microsoft/vscode-python/issues/19114)) +1. Update to latest LS client (v8.0.1) and server (v8.0.1) that contain the race condition fix around `LangClient.stop`. + ([#19139](https://github.com/Microsoft/vscode-python/issues/19139)) + +### Fixes + +1. Do not use `--user` flag when installing in a virtual environment. + ([#14327](https://github.com/Microsoft/vscode-python/issues/14327)) +1. Fix error `No such file or directory` on conda activate, and simplify the environment activation code. + ([#18989](https://github.com/Microsoft/vscode-python/issues/18989)) +1. Add proposed async execution API under environments. + ([#19079](https://github.com/Microsoft/vscode-python/issues/19079)) + +### Code Health + +1. Capture whether environment discovery was triggered using Quickpick UI. + ([#19077](https://github.com/Microsoft/vscode-python/issues/19077)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Pylance](https://github.com/microsoft/pylance-release) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [poetry](https://pypi.org/project/poetry/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2022.6.0 (5 May 2022) + +### Enhancements + +1. Rewrite support for unittest test discovery. + ([#17242](https://github.com/Microsoft/vscode-python/issues/17242)) +1. Do not require a reload when swapping between language servers. + ([#18509](https://github.com/Microsoft/vscode-python/issues/18509)) + +### Fixes + +1. Do not show inherit env prompt for conda envs when running "remotely". + ([#18510](https://github.com/Microsoft/vscode-python/issues/18510)) +1. Fixes invalid regular expression logging error occurs when file paths contain special characters. + (Thanks [sunyinqi0508](https://github.com/sunyinqi0508)) + ([#18829](https://github.com/Microsoft/vscode-python/issues/18829)) +1. Do not prompt to select new virtual envrionment if it has already been selected. + ([#18915](https://github.com/Microsoft/vscode-python/issues/18915)) +1. Disable isort when using isort extension. + ([#18945](https://github.com/Microsoft/vscode-python/issues/18945)) +1. Remove `process` check from browser specific entry point for the extension. + ([#18974](https://github.com/Microsoft/vscode-python/issues/18974)) +1. Use built-in test refresh button. + ([#19012](https://github.com/Microsoft/vscode-python/issues/19012)) +1. Update vscode-telemetry-extractor to @vscode/telemetry-extractor@1.9.7. + (Thanks [Quan Zhuo](https://github.com/quanzhuo)) + ([#19036](https://github.com/Microsoft/vscode-python/issues/19036)) +1. Ensure 64-bit interpreters are preferred over 32-bit when auto-selecting. + ([#19042](https://github.com/Microsoft/vscode-python/issues/19042)) + +### Code Health + +1. Update Jedi minimum to python 3.7. + ([#18324](https://github.com/Microsoft/vscode-python/issues/18324)) +1. Stop using `--live-stream` when using `conda run` (see https://github.com/conda/conda/issues/11209 for details). + ([#18511](https://github.com/Microsoft/vscode-python/issues/18511)) +1. Remove prompt to recommend users in old insiders program to switch to pre-release. + ([#18809](https://github.com/Microsoft/vscode-python/issues/18809)) +1. Update requirements to remove python 2.7 version restrictions. + ([#19060](https://github.com/Microsoft/vscode-python/issues/19060)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Pylance](https://github.com/microsoft/pylance-release) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [poetry](https://pypi.org/project/poetry/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2022.4.1 (7 April 2022) + +### Fixes + +1. Ensure `conda info` command isn't run multiple times during startup when large number of conda interpreters are present. + ([#18200](https://github.com/Microsoft/vscode-python/issues/18200)) +1. If a conda environment is not returned via the `conda env list` command, consider it as unknown env type. + ([#18530](https://github.com/Microsoft/vscode-python/issues/18530)) +1. Wrap file paths containing an ampersand in double quotation marks for running commands in a shell. + ([#18722](https://github.com/Microsoft/vscode-python/issues/18722)) +1. Fixes regression with support for python binaries not following the standard names. + ([#18835](https://github.com/Microsoft/vscode-python/issues/18835)) +1. Fix launch of Python Debugger when using conda environments. + ([#18847](https://github.com/Microsoft/vscode-python/issues/18847)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Pylance](https://github.com/microsoft/pylance-release) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [poetry](https://pypi.org/project/poetry/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2022.4.0 (30 March 2022) + +### Enhancements + +1. Use new pre-release mechanism to install insiders. + ([#18144](https://github.com/Microsoft/vscode-python/issues/18144)) +1. Add support for detection and selection of conda environments lacking a python interpreter. + ([#18357](https://github.com/Microsoft/vscode-python/issues/18357)) +1. Retains the state of the TensorBoard webview. + ([#18591](https://github.com/Microsoft/vscode-python/issues/18591)) +1. Move interpreter info status bar item to the right. + ([#18710](https://github.com/Microsoft/vscode-python/issues/18710)) +1. `debugpy` updated to version `v1.6.0`. + ([#18795](https://github.com/Microsoft/vscode-python/issues/18795)) + +### Fixes + +1. Properly dismiss the error popup dialog when having a linter error. (Thanks [Virgil Sisoe](https://github.com/sisoe24)) + ([#18553](https://github.com/Microsoft/vscode-python/issues/18553)) +1. Python files are no longer excluded from Pytest arguments during test discovery. + (thanks [Marc Mueller](https://github.com/cdce8p/)) + ([#18562](https://github.com/Microsoft/vscode-python/issues/18562)) +1. Fixes regression caused due to using `conda run` for executing files. + ([#18634](https://github.com/Microsoft/vscode-python/issues/18634)) +1. Use `conda run` to get the activated environment variables instead of activation using shell scripts. + ([#18698](https://github.com/Microsoft/vscode-python/issues/18698)) + +### Code Health + +1. Remove old settings migrator. + ([#14334](https://github.com/Microsoft/vscode-python/issues/14334)) +1. Remove old language server setting migration. + ([#14337](https://github.com/Microsoft/vscode-python/issues/14337)) +1. Remove dependency on other file system watchers. + ([#18381](https://github.com/Microsoft/vscode-python/issues/18381)) +1. Update TypeScript version to 4.5.5. + ([#18602](https://github.com/Microsoft/vscode-python/issues/18602)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Pylance](https://github.com/microsoft/pylance-release) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [poetry](https://pypi.org/project/poetry/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2022.2.0 (3 March 2022) + +### Enhancements + +1. Implement a "New Python File" command + ([#18376](https://github.com/Microsoft/vscode-python/issues/18376)) +1. Use `conda run` for conda environments for running python files and installing modules. + ([#18479](https://github.com/Microsoft/vscode-python/issues/18479)) +1. Better filename patterns for pip-requirements. + (thanks [Baptiste Darthenay](https://github.com/batisteo)) + ([#18498](https://github.com/Microsoft/vscode-python/issues/18498)) + +### Fixes + +1. Ensure clicking "Discovering Python Interpreters" in the status bar shows the current discovery progress. + ([#18443](https://github.com/Microsoft/vscode-python/issues/18443)) +1. Fixes Pylama output parsing with MyPy. (thanks [Nicola Marella](https://github.com/nicolamarella)) + ([#15609](https://github.com/Microsoft/vscode-python/issues/15609)) +1. Fix CPU load issue caused by poetry plugin by not watching directories which do not exist. + ([#18459](https://github.com/Microsoft/vscode-python/issues/18459)) +1. Explicitly add `"justMyCode": "true"` to all `launch.json` configurations. + (Thanks [Matt Bogosian](https://github.com/posita)) + ([#18471](https://github.com/Microsoft/vscode-python/issues/18471)) +1. Identify base conda environments inside pyenv correctly. + ([#18500](https://github.com/Microsoft/vscode-python/issues/18500)) +1. Fix for a crash when loading environments with no info. + ([#18594](https://github.com/Microsoft/vscode-python/issues/18594)) + +### Code Health + +1. Remove dependency on `ts-mock-imports`. + ([#14757](https://github.com/Microsoft/vscode-python/issues/14757)) +1. Update `vsce` to `v2.6.6`. + ([#18411](https://github.com/Microsoft/vscode-python/issues/18411)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Pylance](https://github.com/microsoft/pylance-release) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [poetry](https://pypi.org/project/poetry/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2022.0.1 (8 February 2022) + +### Fixes + +1. Fix `invalid patch string` error when using conda. + ([#18455](https://github.com/Microsoft/vscode-python/issues/18455)) +1. Revert to old way of running debugger if conda version less than 4.9.0. + ([#18436](https://github.com/Microsoft/vscode-python/issues/18436)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Pylance](https://github.com/microsoft/pylance-release) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [poetry](https://pypi.org/project/poetry/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2022.0.0 (3 February 2022) + +### Enhancements + +1. Add support for conda run without output, using `--no-capture-output` flag. + ([#7696](https://github.com/Microsoft/vscode-python/issues/7696)) +1. Add an option to clear interpreter setting for all workspace folders in multiroot scenario. + ([#17693](https://github.com/Microsoft/vscode-python/issues/17693)) +1. Public API for environments (proposed). + ([#17905](https://github.com/Microsoft/vscode-python/issues/17905)) +1. Group interpreters in interpreter quick picker using separators. + ([#17944](https://github.com/Microsoft/vscode-python/issues/17944)) +1. Add support for pylint error ranges. Requires Python 3.8 and pylint 2.12.2 or higher. (thanks [Marc Mueller](https://github.com/cdce8p)) + ([#18068](https://github.com/Microsoft/vscode-python/issues/18068)) +1. Move pinned interpreter status bar item towards the right behind `pythonInterpreterInfoPinned` experiment. + ([#18282](https://github.com/Microsoft/vscode-python/issues/18282)) +1. Move interpreter status bar item into the `Python` language status item behind `pythonInterpreterInfoUnpinned` experiment. + ([#18283](https://github.com/Microsoft/vscode-python/issues/18283)) +1. Update Jedi language server to latest. + ([#18325](https://github.com/Microsoft/vscode-python/issues/18325)) + +### Fixes + +1. Update zh-tw translations. (thanks [ted1030](https://github.com/ted1030)) + ([#17991](https://github.com/Microsoft/vscode-python/issues/17991)) +1. Support selecting conda environments with python `3.10`. + ([#18128](https://github.com/Microsoft/vscode-python/issues/18128)) +1. Fixes to telemetry handler in language server middleware. + ([#18188](https://github.com/Microsoft/vscode-python/issues/18188)) +1. Resolve system variables in `python.defaultInterpreterPath`. + ([#18207](https://github.com/Microsoft/vscode-python/issues/18207)) +1. Ensures interpreters are discovered even when running `interpreterInfo.py` script prints more than just the script output. + ([#18234](https://github.com/Microsoft/vscode-python/issues/18234)) +1. Remove restrictions on using `purpose` in debug configuration. + ([#18248](https://github.com/Microsoft/vscode-python/issues/18248)) +1. Ensure Python Interpreter information in the status bar is updated if Interpreter information changes. + ([#18257](https://github.com/Microsoft/vscode-python/issues/18257)) +1. Fix "Run Selection/Line in Python Terminal" for Python < 3.8 when the code includes decorators. + ([#18258](https://github.com/Microsoft/vscode-python/issues/18258)) +1. Ignore notebook cells for pylance. Jupyter extension is handling notebooks. + ([#18259](https://github.com/Microsoft/vscode-python/issues/18259)) +1. Fix for UriError when using python.interpreterPath command in tasks. + ([#18285](https://github.com/Microsoft/vscode-python/issues/18285)) +1. Ensure linting works under `conda run` (work-around for https://github.com/conda/conda/issues/10972). + ([#18364](https://github.com/Microsoft/vscode-python/issues/18364)) +1. Ensure items are removed from the array in reverse order when using array indices. + ([#18382](https://github.com/Microsoft/vscode-python/issues/18382)) +1. Log experiments only after we finish updating active experiments list. + ([#18393](https://github.com/Microsoft/vscode-python/issues/18393)) + +### Code Health + +1. Improve unit tests for envVarsService, in particular the variable substitution logic (Thanks [Keshav Kini](https://github.com/kini)) + ([#17747](https://github.com/Microsoft/vscode-python/issues/17747)) +1. Remove `python.pythonPath` setting and `pythonDeprecatePythonPath` experiment. + ([#17977](https://github.com/Microsoft/vscode-python/issues/17977)) +1. Remove `pythonTensorboardExperiment` and `PythonPyTorchProfiler` experiments. + ([#18074](https://github.com/Microsoft/vscode-python/issues/18074)) +1. Reduce direct dependency on IOutputChannel. + ([#18132](https://github.com/Microsoft/vscode-python/issues/18132)) +1. Upgrade to Node 14 LTS (v14.18.2). + ([#18148](https://github.com/Microsoft/vscode-python/issues/18148)) +1. Switch `jedils_requirements.txt` to `requirements.txt` under `pythonFiles/jedilsp_requirements/`. + ([#18185](https://github.com/Microsoft/vscode-python/issues/18185)) +1. Removed `experiments.json` file. + ([#18235](https://github.com/Microsoft/vscode-python/issues/18235)) +1. Fixed typescript and namespace errors. (Thanks [Harry-Hopkinson](https://github.com/Harry-Hopkinson)) + ([#18345](https://github.com/Microsoft/vscode-python/issues/18345)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Pylance](https://github.com/microsoft/pylance-release) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [poetry](https://pypi.org/project/poetry/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.12.0 (9 December 2021) + +### Enhancements + +1. Python extension should activate on onDebugInitialConfigurations. + (thanks [Nayana Vinod](https://github.com/nayana-vinod) and [Jessica Jolly](https://github.com/JessieJolly)). + ([#9557](https://github.com/Microsoft/vscode-python/issues/9557)) +1. Declare limited support when running in virtual workspaces by only supporting language servers. + ([#17519](https://github.com/Microsoft/vscode-python/issues/17519)) +1. Add a "Do not show again" option to the formatter installation prompt. + ([#17937](https://github.com/Microsoft/vscode-python/issues/17937)) +1. Add the ability to install `pip` if missing, when installing missing packages from the `Jupyter Extension`. + ([#17975](https://github.com/Microsoft/vscode-python/issues/17975)) +1. Declare limited support for untrusted workspaces by only supporting Pylance. + ([#18031](https://github.com/Microsoft/vscode-python/issues/18031)) +1. Update to latest jedi language server. + ([#18051](https://github.com/Microsoft/vscode-python/issues/18051)) +1. Add language status item indicating that extension works partially in virtual and untrusted workspaces. + ([#18059](https://github.com/Microsoft/vscode-python/issues/18059)) + +### Fixes + +1. Partial fix for using the same directory as discovery when running tests. + (thanks [Brian Rutledge](https://github.com/bhrutledge)) + ([#9553](https://github.com/Microsoft/vscode-python/issues/9553)) +1. Handle decorators properly when using the `Run Selection/Line in Python Terminal` command. + ([#15058](https://github.com/Microsoft/vscode-python/issues/15058)) +1. Don't interpret `--rootdir` as a test folder for `pytest`. + (thanks [Brian Rutledge](https://github.com/bhrutledge)) + ([#16079](https://github.com/Microsoft/vscode-python/issues/16079)) +1. Ensure debug configuration env variables overwrite env variables defined in .env file. + ([#16984](https://github.com/Microsoft/vscode-python/issues/16984)) +1. Fix for `pytest` run all tests when using `pytest.ini` and `cwd`. + (thanks [Brian Rutledge](https://github.com/bhrutledge)) + ([#17546](https://github.com/Microsoft/vscode-python/issues/17546)) +1. When parsing pytest node ids with parameters, use native pytest information to separate out the parameter decoration rather than try and parse the nodeid as text. + (thanks [Martijn Pieters](https://github.com/mjpieters)) + ([#17676](https://github.com/Microsoft/vscode-python/issues/17676)) +1. Do not process system Python 2 installs on macOS Monterey. + ([#17870](https://github.com/Microsoft/vscode-python/issues/17870)) +1. Remove duplicate "Clear Workspace Interpreter Setting" command from the command palette. + ([#17890](https://github.com/Microsoft/vscode-python/issues/17890)) +1. Ensure that path towards extenal tools like linters are not synched between + machines. (thanks [Sorin Sbarnea](https://github.com/ssbarnea)) + ([#18008](https://github.com/Microsoft/vscode-python/issues/18008)) +1. Increase timeout for activation of conda environments from 30s to 60s. + ([#18017](https://github.com/Microsoft/vscode-python/issues/18017)) + +### Code Health + +1. Removing experiments for refresh and failed tests buttons. + ([#17868](https://github.com/Microsoft/vscode-python/issues/17868)) +1. Remove caching debug configuration experiment only. + ([#17895](https://github.com/Microsoft/vscode-python/issues/17895)) +1. Remove "join mailing list" notification experiment. + ([#17904](https://github.com/Microsoft/vscode-python/issues/17904)) +1. Remove dependency on `winston` logger. + ([#17921](https://github.com/Microsoft/vscode-python/issues/17921)) +1. Bump isort from 5.9.3 to 5.10.0. + ([#17923](https://github.com/Microsoft/vscode-python/issues/17923)) +1. Remove old discovery code and discovery experiments. + ([#17962](https://github.com/Microsoft/vscode-python/issues/17962)) +1. Remove dependency on `azure-storage`. + ([#17972](https://github.com/Microsoft/vscode-python/issues/17972)) +1. Ensure telemetry correctly identifies when users set linter paths. + ([#18019](https://github.com/Microsoft/vscode-python/issues/18019)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [poetry](https://pypi.org/project/poetry/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.11.0 (4 November 2021) + +### Enhancements + +1. Improve setting description for enabling A/B tests. (Thanks [Thi Le](https://github.com/thi-lee)) + ([#7793](https://github.com/Microsoft/vscode-python/issues/7793)) +1. Support `expectedFailure` when running `unittest` tests using `pytest`. + ([#8427](https://github.com/Microsoft/vscode-python/issues/8427)) +1. Support environment variable substitution in `python` property for `launch.json`. + ([#12289](https://github.com/Microsoft/vscode-python/issues/12289)) +1. Update homebrew instructions to install python 3. + (thanks [Carolinekung2 ](https://github.com/Carolinekung2)) + ([#17590](https://github.com/Microsoft/vscode-python/issues/17590)) + +### Fixes + +1. Reworded message for A/B testing in the output channel to "Experiment 'X' is active/inactive". + (Thanks [Vidushi Gupta](https://github.com/Vidushi-Gupta) for the contribution) + ([#6352](https://github.com/Microsoft/vscode-python/issues/6352)) +1. Change text to "Select at workspace level" instead of "Entire workspace" when selecting or clearing interpreters in a multiroot folder scenario. + (Thanks [Quynh Do](https://github.com/quynhd07)) + ([#10737](https://github.com/Microsoft/vscode-python/issues/10737)) +1. Fix unresponsive extension issues caused by discovery component. + ([#11924](https://github.com/Microsoft/vscode-python/issues/11924)) +1. Remove duplicate 'Run Python file' commands in command palette. + ([#14562](https://github.com/Microsoft/vscode-python/issues/14562)) +1. Change drive first before changing directory in windows, to anticipate running file outside working directory with different storage drive. (thanks [afikrim](https://github.com/afikrim)) + ([#14730](https://github.com/Microsoft/vscode-python/issues/14730)) +1. Support installing Insiders extension in remote sessions. + ([#15145](https://github.com/Microsoft/vscode-python/issues/15145)) +1. If the executeInFileDir setting is enabled, always change to the script directory before running the script, even if the script is in the Workspace folder. (thanks (acash715)[https://github.com/acash715]) + ([#15181](https://github.com/Microsoft/vscode-python/issues/15181)) +1. replaceAll for replacing separators. (thanks [Aliva Das](https://github.com/IceJinx33)) + ([#15288](https://github.com/Microsoft/vscode-python/issues/15288)) +1. When activating environment, creating new Integrated Terminal doesn't take selected workspace into account. (Thanks [Vidushi Gupta](https://github.com/Vidushi-Gupta) for the contribution) + ([#15522](https://github.com/Microsoft/vscode-python/issues/15522)) +1. Fix truncated mypy errors by setting `--no-pretty`. + (thanks [Peter Lithammer](https://github.com/lithammer)) + ([#16836](https://github.com/Microsoft/vscode-python/issues/16836)) +1. Renamed the commands in the Run/Debug button of the editor title. (thanks (Analía Bannura)[https://github.com/analiabs] and (Anna Arsentieva)[https://github.com/arsentieva]) + ([#17019](https://github.com/Microsoft/vscode-python/issues/17019)) +1. Fix for `pytest` run all tests when using `pytest.ini`. + ([#17546](https://github.com/Microsoft/vscode-python/issues/17546)) +1. Ensures test node is updated when `unittest` sub-tests are used. + ([#17561](https://github.com/Microsoft/vscode-python/issues/17561)) +1. Update debugpy to 1.5.1 to ensure user-unhandled exception setting is false by default. + ([#17789](https://github.com/Microsoft/vscode-python/issues/17789)) +1. Ensure we filter out unsupported features in web scenario using `shellExecutionSupported` context key. + ([#17811](https://github.com/Microsoft/vscode-python/issues/17811)) +1. Remove `python.condaPath` from workspace scope. + ([#17819](https://github.com/Microsoft/vscode-python/issues/17819)) +1. Make updateTestItemFromRawData async to prevent blocking the extension. + ([#17823](https://github.com/Microsoft/vscode-python/issues/17823)) +1. Semantic colorization can sometimes require reopening or scrolling of a file. + ([#17878](https://github.com/Microsoft/vscode-python/issues/17878)) + +### Code Health + +1. Remove TSLint comments since we use ESLint. + ([#4060](https://github.com/Microsoft/vscode-python/issues/4060)) +1. Remove unused SHA512 hashing code. + ([#7333](https://github.com/Microsoft/vscode-python/issues/7333)) +1. Remove unused packages. + ([#16840](https://github.com/Microsoft/vscode-python/issues/16840)) +1. Remove old discovery code and discovery experiments. + ([#17795](https://github.com/Microsoft/vscode-python/issues/17795)) +1. Do not query for version and kind if it's not needed when reporting an issue. + ([#17815](https://github.com/Microsoft/vscode-python/issues/17815)) +1. Remove Microsoft Python Language Server support from the extension. + ([#17834](https://github.com/Microsoft/vscode-python/issues/17834)) +1. Bump `packaging` from 21.0 to 21.2. + ([#17886](https://github.com/Microsoft/vscode-python/issues/17886)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [poetry](https://pypi.org/project/poetry/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.10.1 (13 October 2021) + +### Enhancements + +1. Provide IntelliSense status information when using `github.dev` or any other web platform. + ([#17658](https://github.com/Microsoft/vscode-python/issues/17658)) + +### Fixes + +1. Ensure commands run are not logged twice in Python output channel. + ([#7160](https://github.com/Microsoft/vscode-python/issues/7160)) +1. Ensure we use fragment when formatting notebook cells. + ([#16980](https://github.com/Microsoft/vscode-python/issues/16980)) +1. Hide UI elements that are not applicable when using `github.dev` or any other web platform. + ([#17252](https://github.com/Microsoft/vscode-python/issues/17252)) +1. Localize strings on `github.dev` using VSCode FS API. + ([#17712](https://github.com/Microsoft/vscode-python/issues/17712)) + +### Code Health + +1. Log commands run by the discovery component in the output channel. + ([#16732](https://github.com/Microsoft/vscode-python/issues/16732)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [poetry](https://pypi.org/project/poetry/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.10.0 (7 October 2021) + +### Enhancements + +1. Set the default value of `python.linting.pylintEnabled` to `false`. + ([#3007](https://github.com/Microsoft/vscode-python/issues/3007)) +1. Phase out Jedi 0.17, and use Jedi behind a language server protocol as the Jedi option. Remove Jedi-related settings `python.jediMemoryLimit` and `python.jediPath`, since they are not used with the new language server implementation. + ([#11995](https://github.com/Microsoft/vscode-python/issues/11995)) +1. Add support for dynamic updates in interpreter list. + ([#17043](https://github.com/Microsoft/vscode-python/issues/17043)) +1. Query for fresh workspace envs when auto-selecting interpreters in a new workspace. + ([#17264](https://github.com/Microsoft/vscode-python/issues/17264)) +1. Increase Microsoft Python Language Server deprecation prompt frequency and update wording. + ([#17361](https://github.com/Microsoft/vscode-python/issues/17361)) +1. Remove "The Python extension will have limited support for Python 2.7 in the next release" notification. + ([#17451](https://github.com/Microsoft/vscode-python/issues/17451)) +1. Added non-blocking discovery APIs for Jupyter. + ([#17452](https://github.com/Microsoft/vscode-python/issues/17452)) +1. Resolve environments using cache if cache has complete env info. + ([#17474](https://github.com/Microsoft/vscode-python/issues/17474)) +1. Ensure debugger contribution points are turned off when using virtual workspaces. + ([#17493](https://github.com/Microsoft/vscode-python/issues/17493)) +1. Display a notification about the end of Jedi support when using Python 2.7. + ([#17512](https://github.com/Microsoft/vscode-python/issues/17512)) +1. If user has selected an interpreter which is not discovery cache, correctly add it to cache. + ([#17575](https://github.com/Microsoft/vscode-python/issues/17575)) +1. Update to latest version of Jedi LS. + ([#17591](https://github.com/Microsoft/vscode-python/issues/17591)) +1. Update to `vscode-extension-telemetry` 0.4.2. + ([#17608](https://github.com/Microsoft/vscode-python/issues/17608)) + +### Fixes + +1. Don't override user provided `--rootdir` in pytest args. + ([#8678](https://github.com/Microsoft/vscode-python/issues/8678)) +1. Don't log error during settings migration if settings.json doesn't exist. + ([#11354](https://github.com/Microsoft/vscode-python/issues/11354)) +1. Fix casing of text in `unittest` patterns quickpick. + (thanks [Anupama Nadig](https://github.com/anu-ka)) + ([#17093](https://github.com/Microsoft/vscode-python/issues/17093)) +1. Use quickpick details for the "Use Python from `python.defaultInterpreterPath` setting" entry. + ([#17124](https://github.com/Microsoft/vscode-python/issues/17124)) +1. Fix refreshing progress display in the status bar. + ([#17338](https://github.com/Microsoft/vscode-python/issues/17338)) +1. Ensure we do not start a new discovery for an event if one is already scheduled. + ([#17339](https://github.com/Microsoft/vscode-python/issues/17339)) +1. Do not display workspace related envs if no workspace is open. + ([#17358](https://github.com/Microsoft/vscode-python/issues/17358)) +1. Ensure we correctly evaluate Unknown type before sending startup telemetry. + ([#17362](https://github.com/Microsoft/vscode-python/issues/17362)) +1. Fix for unittest discovery failure due to root id mismatch. + ([#17386](https://github.com/Microsoft/vscode-python/issues/17386)) +1. Improve pattern matching for shell detection on Windows. + (thanks [Erik Demaine](https://github.com/edemaine/)) + ([#17426](https://github.com/Microsoft/vscode-python/issues/17426)) +1. Changed the way of searching left bracket `[` in case of subsets of tests. + (thanks [ilexei](https://github.com/ilexei)) + ([#17461](https://github.com/Microsoft/vscode-python/issues/17461)) +1. Fix hang caused by loop in getting interpreter information. + ([#17484](https://github.com/Microsoft/vscode-python/issues/17484)) +1. Ensure database storage extension uses to track all storages does not grow unnecessarily. + ([#17488](https://github.com/Microsoft/vscode-python/issues/17488)) +1. Ensure all users use new discovery code regardless of their experiment settings. + ([#17563](https://github.com/Microsoft/vscode-python/issues/17563)) +1. Add timeout when discovery runs `conda info --json` command. + ([#17576](https://github.com/Microsoft/vscode-python/issues/17576)) +1. Use `conda-forge` channel when installing packages into conda environments. + ([#17628](https://github.com/Microsoft/vscode-python/issues/17628)) + +### Code Health + +1. Remove support for `rope`. Refactoring now supported via language servers. + ([#10440](https://github.com/Microsoft/vscode-python/issues/10440)) +1. Remove `pylintMinimalCheckers` setting. Syntax errors now reported via language servers. + ([#13321](https://github.com/Microsoft/vscode-python/issues/13321)) +1. Remove `ctags` support. Workspace symbols now supported via language servers. + ([#16063](https://github.com/Microsoft/vscode-python/issues/16063)) +1. Fix linting for some files in .eslintignore. + ([#17181](https://github.com/Microsoft/vscode-python/issues/17181)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [poetry](https://pypi.org/project/poetry/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.9.3 (20 September 2021) + +### Fixes + +1. Fix `Python extension loading...` issue for users who have disabled telemetry. + ([#17447](https://github.com/Microsoft/vscode-python/issues/17447)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [poetry](https://pypi.org/project/poetry/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.9.2 (13 September 2021) + +### Fixes + +1. Ensure line feeds are changed to CRLF in test messages. + ([#17111](https://github.com/Microsoft/vscode-python/issues/17111)) +1. Fix for `unittest` ModuleNotFoundError when discovering tests. + ([#17363](https://github.com/Microsoft/vscode-python/issues/17363)) +1. Ensure we block getting active interpreter on auto-selection. + ([#17370](https://github.com/Microsoft/vscode-python/issues/17370)) +1. Fix to handle undefined uri in debug in terminal command. + ([#17374](https://github.com/Microsoft/vscode-python/issues/17374)) +1. Fix for missing buttons for tests when using multiple test folders. + ([#17378](https://github.com/Microsoft/vscode-python/issues/17378)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [poetry](https://pypi.org/project/poetry/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.9.1 (9 September 2021) + +### Fixes + +1. Fix for debug configuration used when no launch.json exists is still used after launch.json is created. + ([#17353](https://github.com/Microsoft/vscode-python/issues/17353)) +1. Ensure default python executable to use is 'python' instead of ''. + ([#17089](https://github.com/Microsoft/vscode-python/issues/17089)) +1. Ensure workspace interpreters are discovered and watched when in `pythonDiscoveryModuleWithoutWatcher` experiment. + ([#17144](https://github.com/Microsoft/vscode-python/issues/17144)) +1. Do path comparisons appropriately in the new discovery component. + ([#17244](https://github.com/Microsoft/vscode-python/issues/17244)) +1. Fix for test result not found for files starting with py. + ([#17270](https://github.com/Microsoft/vscode-python/issues/17270)) +1. Fix for unable to import when running unittest. + ([#17280](https://github.com/Microsoft/vscode-python/issues/17280)) +1. Fix for multiple folders in `pytest` args. + ([#17281](https://github.com/Microsoft/vscode-python/issues/17281)) +1. Fix issue with incomplete `unittest` runs. + ([#17282](https://github.com/Microsoft/vscode-python/issues/17282)) +1. Improve detecting lines when using testing wrappers. + ([#17285](https://github.com/Microsoft/vscode-python/issues/17285)) +1. Ensure we trigger discovery for the first time as part of extension activation. + ([#17303](https://github.com/Microsoft/vscode-python/issues/17303)) +1. Correctly indicate when interpreter refresh has finished. + ([#17335](https://github.com/Microsoft/vscode-python/issues/17335)) +1. Missing location info for `async def` functions. + ([#17309](https://github.com/Microsoft/vscode-python/issues/17309)) +1. For CI ensure `tensorboard` is installed in python 3 environments only. + ([#17325](https://github.com/Microsoft/vscode-python/issues/17325)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [poetry](https://pypi.org/project/poetry/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.9.0 (1 September 2021) + +### Enhancements + +1. Added commands to select and run a set of tests. + ([#3652](https://github.com/Microsoft/vscode-python/issues/3652)) +1. Fix for tests should be re-discovered after switching environment. + ([#5347](https://github.com/Microsoft/vscode-python/issues/5347)) +1. Remove the testing functionality from the status bar. + ([#8405](https://github.com/Microsoft/vscode-python/issues/8405)) +1. Automatically detect new test file in test explorer. + ([#8675](https://github.com/Microsoft/vscode-python/issues/8675)) +1. Search test names in test explorer. + ([#8836](https://github.com/Microsoft/vscode-python/issues/8836)) +1. Added a command for displaying the test explorer. + ([#9026](https://github.com/Microsoft/vscode-python/issues/9026)) +1. Make "run all tests" icon gray instead of green. + ([#9402](https://github.com/Microsoft/vscode-python/issues/9402)) +1. Use VS Code's test UI instead of code lenses above tests. + ([#10898](https://github.com/Microsoft/vscode-python/issues/10898)) +1. Added command to run last executed test. + ([#11864](https://github.com/Microsoft/vscode-python/issues/11864)) +1. Fix for PyTest discovery can fail but not give any clue as to what the problem is. + ([#12043](https://github.com/Microsoft/vscode-python/issues/12043)) +1. Add shortcut to run the current test (at cursor position). + ([#12218](https://github.com/Microsoft/vscode-python/issues/12218)) +1. Run all tests in a multi-root workspace without prompting. + ([#13147](https://github.com/Microsoft/vscode-python/issues/13147)) +1. Plug into VS Code's Test UI. + ([#15750](https://github.com/Microsoft/vscode-python/issues/15750)) +1. Show notification to join insiders after 5 mins. + ([#16833](https://github.com/Microsoft/vscode-python/issues/16833)) +1. Update Simplified Chinese translation. (thanks [FiftysixTimes7](https://github.com/FiftysixTimes7)) + ([#16916](https://github.com/Microsoft/vscode-python/issues/16916)) +1. Added Debug file button to editor run menu. + ([#16924](https://github.com/Microsoft/vscode-python/issues/16924)) +1. Cache last selection for debug configuration when debugging without launch.json. + ([#16934](https://github.com/Microsoft/vscode-python/issues/16934)) +1. Improve display of default interpreter and suggested interpreter in the interpreter selection quick pick. + ([#16971](https://github.com/Microsoft/vscode-python/issues/16971)) +1. Improve discovery component API. + ([#17005](https://github.com/Microsoft/vscode-python/issues/17005)) +1. Add a notification about Python 2.7 support, displayed whenever a tool is used or whenever debugging is started. + ([#17009](https://github.com/Microsoft/vscode-python/issues/17009)) +1. Add caching debug configuration behind experiment. + ([#17025](https://github.com/Microsoft/vscode-python/issues/17025)) +1. Do not query to get all interpreters where it's not needed in the extension code. + ([#17030](https://github.com/Microsoft/vscode-python/issues/17030)) +1. Add a warning prompt for the Microsoft Python Language Server deprecation. + ([#17056](https://github.com/Microsoft/vscode-python/issues/17056)) +1. Update to latest jedi-language-server. + ([#17072](https://github.com/Microsoft/vscode-python/issues/17072)) + +### Fixes + +1. Fix for test code lenses do not disappear even after disabling the unit tests. + ([#1654](https://github.com/Microsoft/vscode-python/issues/1654)) +1. Fix for code lens for a test class run under unittest doesn't show overall results for methods. + ([#2382](https://github.com/Microsoft/vscode-python/issues/2382)) +1. Fix for test code lens do not appear on initial activation of testing support. + ([#2644](https://github.com/Microsoft/vscode-python/issues/2644)) +1. Fix for "No tests ran, please check the configuration settings for the tests". + ([#2660](https://github.com/Microsoft/vscode-python/issues/2660)) +1. Fix for code lenses disappear on save, then re-appear when tabbing on/off the file. + ([#2790](https://github.com/Microsoft/vscode-python/issues/2790)) +1. Fix for code lenses for tests not showing up when test is defined on line 1. + ([#3062](https://github.com/Microsoft/vscode-python/issues/3062)) +1. Fix for command 'python.runtests' not found. + ([#3591](https://github.com/Microsoft/vscode-python/issues/3591)) +1. Fix for navigation to code doesn't work with parameterized tests. + ([#4469](https://github.com/Microsoft/vscode-python/issues/4469)) +1. Fix for tests are not being discovered at first in multiroot workspace. + ([#4848](https://github.com/Microsoft/vscode-python/issues/4848)) +1. Fix for tests not found after upgrade. + ([#5417](https://github.com/Microsoft/vscode-python/issues/5417)) +1. Fix for failed icon of the first failed test doesn't changed to running icon when using unittest framework. + ([#5791](https://github.com/Microsoft/vscode-python/issues/5791)) +1. Fix for failure details in unittest discovery are not always logged. + ([#5889](https://github.com/Microsoft/vscode-python/issues/5889)) +1. Fix for test results not updated if test is run via codelens. + ([#6787](https://github.com/Microsoft/vscode-python/issues/6787)) +1. Fix for "Run Current Test File" is not running tests, just discovering them. + ([#7150](https://github.com/Microsoft/vscode-python/issues/7150)) +1. Fix for testing code lenses don't show for remote sessions to a directory symlink. + ([#7443](https://github.com/Microsoft/vscode-python/issues/7443)) +1. Fix for discover test per folder icon is missing in multi-root workspace after upgrade. + ([#7870](https://github.com/Microsoft/vscode-python/issues/7870)) +1. Fix for clicking on a test in the Test Explorer does not navigate to the correct test. + ([#8448](https://github.com/Microsoft/vscode-python/issues/8448)) +1. Fix for if multiple tests have the same name, only one is run. + ([#8761](https://github.com/Microsoft/vscode-python/issues/8761)) +1. Fix for test failure is reported as a compile error. + ([#9640](https://github.com/Microsoft/vscode-python/issues/9640)) +1. Fix for discovering tests immediately after interpreter change often fails. + ([#9854](https://github.com/Microsoft/vscode-python/issues/9854)) +1. Fix for unittest module invoking wrong TestCase. + ([#10972](https://github.com/Microsoft/vscode-python/issues/10972)) +1. Fix for unable to navigate to test function. + ([#11866](https://github.com/Microsoft/vscode-python/issues/11866)) +1. Fix for running test fails trying to access non-existing file. + ([#12403](https://github.com/Microsoft/vscode-python/issues/12403)) +1. Fix for code lenses don't work after opening files from different projects in workspace. + ([#12995](https://github.com/Microsoft/vscode-python/issues/12995)) +1. Fix for the pytest icons keep spinning when run Test Method. + ([#13285](https://github.com/Microsoft/vscode-python/issues/13285)) +1. Test for any functionality related to testing doesn't work if language server is set to none. + ([#13713](https://github.com/Microsoft/vscode-python/issues/13713)) +1. Fix for cannot configure PyTest from UI. + ([#13916](https://github.com/Microsoft/vscode-python/issues/13916)) +1. Fix for test icons not updating when using pytest. + ([#15260](https://github.com/Microsoft/vscode-python/issues/15260)) +1. Fix for debugging tests is returning errors due to "unsupported status". + ([#15736](https://github.com/Microsoft/vscode-python/issues/15736)) +1. Removes `"request": "test"` as a config option. This can now be done with `"purpose": ["debug-test"]`. + ([#15790](https://github.com/Microsoft/vscode-python/issues/15790)) +1. Fix for "There was an error in running the tests" when stopping debugger. + ([#16475](https://github.com/Microsoft/vscode-python/issues/16475)) +1. Use the vscode API appropriately to find out what terminal is being used. + ([#16577](https://github.com/Microsoft/vscode-python/issues/16577)) +1. Fix unittest discovery. (thanks [JulianEdwards](https://github.com/bigjools)) + ([#16593](https://github.com/Microsoft/vscode-python/issues/16593)) +1. Fix run `installPythonLibs` error in windows. + ([#16844](https://github.com/Microsoft/vscode-python/issues/16844)) +1. Fix for test welcome screen flashes on refresh. + ([#16855](https://github.com/Microsoft/vscode-python/issues/16855)) +1. Show re-run failed test button only when there are failed tests. + ([#16856](https://github.com/Microsoft/vscode-python/issues/16856)) +1. Triggering test refresh shows progress indicator. + ([#16891](https://github.com/Microsoft/vscode-python/issues/16891)) +1. Fix environment sorting for the `Python: Select Interpreter` command. + (thanks [Marc Mueller](https://github.com/cdce8p)) + ([#16893](https://github.com/Microsoft/vscode-python/issues/16893)) +1. Fix for unittest not getting discovered in all cases. + ([#16902](https://github.com/Microsoft/vscode-python/issues/16902)) +1. Don't show full path in the description for each test node. + ([#16927](https://github.com/Microsoft/vscode-python/issues/16927)) +1. Fix for no notification shown if test framework is not configured and run all tests is called. + ([#16941](https://github.com/Microsoft/vscode-python/issues/16941)) +1. In experiments service don't always `await` on `initialfetch` which can be slow depending on the network. + ([#16959](https://github.com/Microsoft/vscode-python/issues/16959)) +1. Ensure 2.7 unittest still work with new test support. + ([#16962](https://github.com/Microsoft/vscode-python/issues/16962)) +1. Fix issue with parsing test run ids for reporting test status. + ([#16963](https://github.com/Microsoft/vscode-python/issues/16963)) +1. Fix cell magics, line magics, and shell escaping in jupyter notebooks to not show error diagnostics. + ([#17058](https://github.com/Microsoft/vscode-python/issues/17058)) +1. Fix for testing ui update issue when `pytest` parameter has '/'. + ([#17079](https://github.com/Microsoft/vscode-python/issues/17079)) + +### Code Health + +1. Remove nose test support. + ([#16371](https://github.com/Microsoft/vscode-python/issues/16371)) +1. Remove custom start page experience in favor of VSCode's built-in walkthrough support. + ([#16453](https://github.com/Microsoft/vscode-python/issues/16453)) +1. Run auto-selection only once, and return the cached value for subsequent calls. + ([#16735](https://github.com/Microsoft/vscode-python/issues/16735)) +1. Add telemetry for when an interpreter gets auto-selected. + ([#16764](https://github.com/Microsoft/vscode-python/issues/16764)) +1. Remove pre-existing environment sorting algorithm and old rule-based auto-selection logic. + ([#16935](https://github.com/Microsoft/vscode-python/issues/16935)) +1. Add API to run code after extension activation. + ([#16983](https://github.com/Microsoft/vscode-python/issues/16983)) +1. Add telemetry sending time it took to load data from experiment service. + ([#17011](https://github.com/Microsoft/vscode-python/issues/17011)) +1. Improve reliability of virtual env tests and disable poetry watcher tests. + ([#17088](https://github.com/Microsoft/vscode-python/issues/17088)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [poetry](https://pypi.org/project/poetry/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.8.3 (23 August 2021) + +### Fixes + +1. Update `vsce` to latest to fix metadata in VSIX for web extension. + ([#17049](https://github.com/Microsoft/vscode-python/issues/17049)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.8.2 (19 August 2021) + +### Enhancements + +1. Add a basic web extension bundle. + ([#16869](https://github.com/Microsoft/vscode-python/issues/16869)) +1. Add basic Pylance support to the web extension. + ([#16870](https://github.com/Microsoft/vscode-python/issues/16870)) + +### Code Health + +1. Update telemetry client to support browser, plumb to Pylance. + ([#16871](https://github.com/Microsoft/vscode-python/issues/16871)) +1. Refactor language server middleware to work in the browser. + ([#16872](https://github.com/Microsoft/vscode-python/issues/16872)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.8.1 (6 August 2021) + +### Fixes + +1. Fix random delay before running python code. + ([#16768](https://github.com/Microsoft/vscode-python/issues/16768)) +1. Fix the order of default unittest arguments. + (thanks [Nikolay Kondratyev](https://github.com/kondratyev-nv/)) + ([#16882](https://github.com/Microsoft/vscode-python/issues/16882)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.8.0 (5 August 2021) + +### Enhancements + +1. Add new getting started page using VS Code's API to replace our custom start page. + ([#16678](https://github.com/Microsoft/vscode-python/issues/16678)) +1. Replace deprecated vscode-test with @vscode/test-electron for CI. (thanks [iChenLei](https://github.com/iChenLei)) + ([#16765](https://github.com/Microsoft/vscode-python/issues/16765)) + +### Code Health + +1. Sort Settings Alphabetically. (thanks [bfarahdel](https://github.com/bfarahdel)) + ([#8406](https://github.com/Microsoft/vscode-python/issues/8406)) +1. Changed default language server to `Pylance` for extension development. (thanks [jasleen101010](https://github.com/jasleen101010)) + ([#13007](https://github.com/Microsoft/vscode-python/issues/13007)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.7.2 (23 July 2021) + +### Enhancements + +1. Update `debugpy` with fix for https://github.com/microsoft/debugpy/issues/669. + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.7.1 (21 July 2021) + +### Enhancements + +1. Update `debugpy` to the latest version. + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.7.0 (20 July 2021) + +### Enhancements + +1. Support starting a TensorBoard session with a remote URL hosting log files. + ([#16461](https://github.com/Microsoft/vscode-python/issues/16461)) +1. Sort environments in the selection quickpick by assumed usefulness. + ([#16520](https://github.com/Microsoft/vscode-python/issues/16520)) + +### Fixes + +1. Add link to docs page on how to install the Python extension to README. (thanks [KamalSinghKhanna](https://github.com/KamalSinghKhanna)) + ([#15199](https://github.com/Microsoft/vscode-python/issues/15199)) +1. Make test explorer only show file/folder names on nodes. + (thanks [bobwalker99](https://github.com/bobwalker99)) + ([#16368](https://github.com/Microsoft/vscode-python/issues/16368)) +1. Ensure we dispose restart command registration before we create a new instance of Jedi LS. + ([#16441](https://github.com/Microsoft/vscode-python/issues/16441)) +1. Ensure `shellIdentificationSource` is set correctly. (thanks [intrigus-lgtm](https://github.com/intrigus-lgtm)) + ([#16517](https://github.com/Microsoft/vscode-python/issues/16517)) +1. Clear Notebook Cell diagnostics when deleting a cell or closing a notebook. + ([#16528](https://github.com/Microsoft/vscode-python/issues/16528)) +1. The `poetryPath` setting will correctly apply system variable substitutions. (thanks [Anthony Shaw](https://github.com/tonybaloney)) + ([#16607](https://github.com/Microsoft/vscode-python/issues/16607)) +1. The Jupyter Notebook extension will install any missing dependencies using Poetry or Pipenv if those are the selected environments. (thanks [Anthony Shaw](https://github.com/tonybaloney)) + ([#16615](https://github.com/Microsoft/vscode-python/issues/16615)) +1. Ensure we block on autoselection when no interpreter is explictly set by user. + ([#16723](https://github.com/Microsoft/vscode-python/issues/16723)) +1. Fix autoselection when opening a python file directly. + ([#16733](https://github.com/Microsoft/vscode-python/issues/16733)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.6.0 (16 June 2021) + +### Enhancements + +1. Improved telemetry around the availability of `pip` for installation of Jupyter dependencies. + ([#15937](https://github.com/Microsoft/vscode-python/issues/15937)) +1. Move the Jupyter extension from being a hard dependency to an optional one, and display an informational prompt if Jupyter commands try to be executed from the Start Page. + ([#16102](https://github.com/Microsoft/vscode-python/issues/16102)) +1. Add an `enumDescriptions` key under the `python.languageServer` setting to describe all language server options. + ([#16141](https://github.com/Microsoft/vscode-python/issues/16141)) +1. Ensure users upgrade to v0.2.0 of the torch-tb-profiler TensorBoard plugin to access jump-to-source functionality. + ([#16330](https://github.com/Microsoft/vscode-python/issues/16330)) +1. Added `python.defaultInterpreterPath` setting at workspace level when in `pythonDeprecatePythonPath` experiment. + ([#16485](https://github.com/Microsoft/vscode-python/issues/16485)) +1. Added default Interpreter path entry at the bottom of the interpreter list. + ([#16485](https://github.com/Microsoft/vscode-python/issues/16485)) +1. Remove execution isolation script used to run tools. + ([#16485](https://github.com/Microsoft/vscode-python/issues/16485)) +1. Show `python.pythonPath` deprecation prompt when in `pythonDeprecatePythonPath` experiment. + ([#16485](https://github.com/Microsoft/vscode-python/issues/16485)) +1. Do not show safety prompt before auto-selecting a workspace interpreter. + ([#16485](https://github.com/Microsoft/vscode-python/issues/16485)) +1. Assume workspace interpreters are safe to execute for discovery. + ([#16485](https://github.com/Microsoft/vscode-python/issues/16485)) + +### Fixes + +1. Fixes a bug in the bandit linter where messages weren't being propagated to the editor. + (thanks [Anthony Shaw](https://github.com/tonybaloney)) + ([#15561](https://github.com/Microsoft/vscode-python/issues/15561)) +1. Workaround existing MIME type misconfiguration on Windows preventing TensorBoard from loading when starting TensorBoard. + ([#16072](https://github.com/Microsoft/vscode-python/issues/16072)) +1. Changed the version of npm to version 6 instead of 7 in the lockfile. + ([#16208](https://github.com/Microsoft/vscode-python/issues/16208)) +1. Ensure selected interpreter doesn't change when the extension is starting up and in experiment. + ([#16291](https://github.com/Microsoft/vscode-python/issues/16291)) +1. Fix issue with sys.prefix when getting environment details. + ([#16355](https://github.com/Microsoft/vscode-python/issues/16355)) +1. Activate the extension when selecting the command `Clear Internal Extension Cache (python.clearPersistentStorage)`. + ([#16397](https://github.com/Microsoft/vscode-python/issues/16397)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.5.2 (14 May 2021) + +### Fixes + +1. Ensure Pylance is used with Python 2 if explicitly chosen + ([#16246](https://github.com/microsoft/vscode-python/issues/16246)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.5.1 (13 May 2021) + +### Fixes + +1. Allow Pylance to be used with Python 2 if explicitly chosen + ([#16204](https://github.com/microsoft/vscode-python/issues/16204)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.5.0 (10 May 2021) + +### Enhancements + +1. In an integrated TensorBoard session, if the jump to source request is for a file that does not exist on disk, allow the user to manually specify the file using the system file picker. + ([#15695](https://github.com/Microsoft/vscode-python/issues/15695)) +1. Allow running tests for all files within directories from test explorer. + (thanks [Vladimir Kotikov](https://github.com/vladimir-kotikov)) + ([#15862](https://github.com/Microsoft/vscode-python/issues/15862)) +1. Reveal selection in editor after jump to source command. (thanks [Wenlu Wang](https://github.com/Kingwl)) + ([#15924](https://github.com/Microsoft/vscode-python/issues/15924)) +1. Add support for debugger code reloading. + ([#16029](https://github.com/Microsoft/vscode-python/issues/16029)) +1. Add Python: Refresh TensorBoard command, keybinding and editor title button to reload TensorBoard (equivalent to browser refresh). + ([#16053](https://github.com/Microsoft/vscode-python/issues/16053)) +1. Automatically indent following `match` and `case` statements. (thanks [Marc Mueller](https://github.com/cdce8p)) + ([#16104](https://github.com/Microsoft/vscode-python/issues/16104)) +1. Bundle Pylance with the extension as an optional dependency. + ([#16116](https://github.com/Microsoft/vscode-python/issues/16116)) +1. Add a "Default" language server option, which dynamically chooses which language server to use. + ([#16157](https://github.com/Microsoft/vscode-python/issues/16157)) + +### Fixes + +1. Stop `unittest.TestCase` appearing as a test suite in the test explorer tree. + (thanks [Bob](https://github.com/bobwalker99)). + ([#15681](https://github.com/Microsoft/vscode-python/issues/15681)) +1. Support `~` in WORKON_HOME and venvPath setting when in discovery experiment. + ([#15788](https://github.com/Microsoft/vscode-python/issues/15788)) +1. Fix TensorBoard integration in Remote-SSH by auto-configuring port forwards. + ([#15807](https://github.com/Microsoft/vscode-python/issues/15807)) +1. Ensure venvPath and venvFolders setting can only be set at User or Remote settings. + ([#15947](https://github.com/Microsoft/vscode-python/issues/15947)) +1. Added compatability with pypy3.7 interpreter. + (thanks [Oliver Margetts](https://github.com/olliemath)) + ([#15968](https://github.com/Microsoft/vscode-python/issues/15968)) +1. Revert linter installation prompt removal. + ([#16027](https://github.com/Microsoft/vscode-python/issues/16027)) +1. Ensure that `dataclasses` is installed when using Jedi LSP. + ([#16119](https://github.com/Microsoft/vscode-python/issues/16119)) + +### Code Health + +1. Log the failures when checking whether certain modules are installed or getting their version information. + ([#15837](https://github.com/Microsoft/vscode-python/issues/15837)) +1. Better logging (telemetry) when installation of Python packages fail. + ([#15933](https://github.com/Microsoft/vscode-python/issues/15933)) +1. Ensure npm packave `canvas` is setup as an optional dependency. + ([#16127](https://github.com/Microsoft/vscode-python/issues/16127)) +1. Add ability for Jupyter extension to pass addtional installer arguments. + ([#16131](https://github.com/Microsoft/vscode-python/issues/16131)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.4.0 (19 April 2021) + +### Enhancements + +1. Add new command to report an Issue using the vscode-python template. + ([#1119](https://github.com/microsoft/vscode-python/issues/1119)) +1. Highlight `.pypirc`, `.pep8`, and `.pylintrc` as ini-files. (thanks [Jan Pilzer](https://github.com/Hirse)) + ([#11250](https://github.com/Microsoft/vscode-python/issues/11250)) +1. Added `python.linting.cwd` to change the working directory of the linters. (thanks [Matthew Shirley](https://github.com/matthewshirley)) + ([#15170](https://github.com/Microsoft/vscode-python/issues/15170)) +1. Remove prompt to install a linter when none are available. + ([#15465](https://github.com/Microsoft/vscode-python/issues/15465)) +1. Add jump to source integration with the PyTorch profiler TensorBoard plugin during TensorBoard sessions. + ([#15641](https://github.com/Microsoft/vscode-python/issues/15641)) +1. Drop prompt being displayed on first extension launch with a tip or a survey. + ([#15647](https://github.com/Microsoft/vscode-python/issues/15647)) +1. Use the updated logic for normalizing code sent to REPL as the default behavior. + ([#15649](https://github.com/Microsoft/vscode-python/issues/15649)) +1. Open TensorBoard webview panel in the active viewgroup on the first launch or the last viewgroup that it was moved to. + ([#15708](https://github.com/Microsoft/vscode-python/issues/15708)) +1. Support discovering Poetry virtual environments when in discovery experiment. + ([#15765](https://github.com/Microsoft/vscode-python/issues/15765)) +1. Install dev tools using Poetry when the poetry environment related to current folder is selected when in discovery experiment. + ([#15786](https://github.com/Microsoft/vscode-python/issues/15786)) +1. Add a refresh icon next to interpreter list. + ([#15868](https://github.com/Microsoft/vscode-python/issues/15868)) +1. Added command `Python: Clear internal extension cache` to clear extension related cache. + ([#15883](https://github.com/Microsoft/vscode-python/issues/15883)) + +### Fixes + +1. Fix `python.poetryPath` setting for installer on Windows. + ([#9672](https://github.com/Microsoft/vscode-python/issues/9672)) +1. Prevent mypy errors for other files showing in current file. + (thanks [Steve Dignam](https://github.com/sbdchd)) + ([#10190](https://github.com/Microsoft/vscode-python/issues/10190)) +1. Update pytest results when debugging. (thanks [djplt](https://github.com/djplt)) + ([#15353](https://github.com/Microsoft/vscode-python/issues/15353)) +1. Ensure release level is set when using new environment discovery component. + ([#15462](https://github.com/Microsoft/vscode-python/issues/15462)) +1. Ensure right environment is activated in the terminal when installing Python packages. + ([#15503](https://github.com/Microsoft/vscode-python/issues/15503)) +1. Update nosetest results when debugging. (thanks [djplt](https://github.com/djplt)) + ([#15642](https://github.com/Microsoft/vscode-python/issues/15642)) +1. Ensure any stray jedi process is terminated on language server dispose. + ([#15644](https://github.com/Microsoft/vscode-python/issues/15644)) +1. Fix README image indent for VSCode extension page. (thanks [Johnson](https://github.com/j3soon/)) + ([#15662](https://github.com/Microsoft/vscode-python/issues/15662)) +1. Run `conda update` and not `conda install` when installing a compatible version of the `tensorboard` package. + ([#15778](https://github.com/Microsoft/vscode-python/issues/15778)) +1. Temporarily fix support for folders in interpreter path setting. + ([#15782](https://github.com/Microsoft/vscode-python/issues/15782)) +1. In completions.py: jedi.api.names has been deprecated, switch to new syntax. + (thanks [moselhy](https://github.com/moselhy)). + ([#15791](https://github.com/Microsoft/vscode-python/issues/15791)) +1. Fixes activation of prefixed conda environments. + ([#15823](https://github.com/Microsoft/vscode-python/issues/15823)) + +### Code Health + +1. Deprecating on-type line formatter since it isn't used in newer Language servers. + ([#15709](https://github.com/Microsoft/vscode-python/issues/15709)) +1. Removing old way of feature deprecation where we showed notification for each feature we deprecated. + ([#15714](https://github.com/Microsoft/vscode-python/issues/15714)) +1. Remove unused code from extension. + ([#15717](https://github.com/Microsoft/vscode-python/issues/15717)) +1. Add telemetry for identifying torch.profiler users. + ([#15825](https://github.com/Microsoft/vscode-python/issues/15825)) +1. Update notebook code to not use deprecated .cells function on NotebookDocument. + ([#15885](https://github.com/Microsoft/vscode-python/issues/15885)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.3.1 (23 March 2021) + +### Fixes + +1. Fix link to create a new Jupyter notebook in Python start page. + ([#15621](https://github.com/Microsoft/vscode-python/issues/15621)) +1. Upgrade to latest `jedi-language-server` and use it for python >= 3.6. Use `jedi<0.18` for python 2.7 and <=3.5. + ([#15724](https://github.com/Microsoft/vscode-python/issues/15724)) +1. Check if Python executable file exists instead of launching the Python process. + ([#15725](https://github.com/Microsoft/vscode-python/issues/15725)) +1. Fix for Go to definition needs to be pressed twice. + (thanks [djplt](https://github.com/djplt)) + ([#15727](https://github.com/Microsoft/vscode-python/issues/15727)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.3.0 (16 March 2021) + +### Enhancements + +1. Activate the extension when the following files are found: `Pipfile`, `setup.py`, `requirements.txt`, `manage.py`, `app.py` + (thanks [Dhaval Soneji](https://github.com/soneji)) + ([#4765](https://github.com/Microsoft/vscode-python/issues/4765)) +1. Add optional user-level `python.tensorBoard.logDirectory` setting. When starting a TensorBoard session, use this setting if it is present instead of prompting the user to select a log directory. + ([#15476](https://github.com/Microsoft/vscode-python/issues/15476)) + +### Fixes + +1. Fix nosetests to run tests only once. (thanks [djplt](https://github.com/djplt)) + ([#6043](https://github.com/Microsoft/vscode-python/issues/6043)) +1. Make on-enter behaviour after `raise` much more like that of `return`, fixing + handling in the case of pressing enter to wrap the parentheses of an exception + call. + (thanks [PeterJCLaw](https://github.com/PeterJCLaw)) + ([#10583](https://github.com/Microsoft/vscode-python/issues/10583)) +1. Add configuration debugpyPath. (thanks [djplt](https://github.com/djplt)) + ([#14631](https://github.com/Microsoft/vscode-python/issues/14631)) +1. Fix Mypy linter pointing to wrong column number (off by one). + (thanks [anttipessa](https://github.com/anttipessa/), [haalto](https://github.com/haalto/), [JeonCD](https://github.com/JeonCD/) and [junskU](https://github.com/junskU)) + ([#14978](https://github.com/Microsoft/vscode-python/issues/14978)) +1. Show each python.org install only once on Mac when in discovery experiment. + ([#15302](https://github.com/Microsoft/vscode-python/issues/15302)) +1. All relative interpreter path reported start with `~` when in discovery experiment. + ([#15312](https://github.com/Microsoft/vscode-python/issues/15312)) +1. Remove FLASK_DEBUG from flask debug configuration to allow reload. + ([#15373](https://github.com/Microsoft/vscode-python/issues/15373)) +1. Install using pipenv only if the selected environment is pipenv which is related to workspace folder, when in discovery experiment. + ([#15489](https://github.com/Microsoft/vscode-python/issues/15489)) +1. Fixes issue with detecting new installations of Windows Store python. + ([#15541](https://github.com/Microsoft/vscode-python/issues/15541)) +1. Add `cached-property` package to bundled python packages. This is needed by `jedi-language-server` running on `python 3.6` and `python 3.7`. + ([#15566](https://github.com/Microsoft/vscode-python/issues/15566)) +1. Remove limit on workspace symbols when using Jedi language server. + ([#15576](https://github.com/Microsoft/vscode-python/issues/15576)) +1. Use shorter paths for python interpreter when possible. + ([#15580](https://github.com/Microsoft/vscode-python/issues/15580)) +1. Ensure that jedi language server uses jedi shipped with the extension. + ([#15586](https://github.com/Microsoft/vscode-python/issues/15586)) +1. Updates to Proposed API, and fix the failure in VS Code Insider tests. + ([#15638](https://github.com/Microsoft/vscode-python/issues/15638)) + +### Code Health + +1. Add support for "Trusted Workspaces". + + "Trusted Workspaces" is an upcoming feature in VS Code. (See: + https://github.com/microsoft/vscode/issues/106488.) For now you need + the following for the experience: + + - the latest VS Code Insiders + - add `"workspace.trustEnabled": true` to your user settings.json + + At that point, when the Python extension would normally activate, VS Code + will prompt you about whether or not the current workspace is trusted. + If not then the extension will be disabled (but only for that workspace). + As soon as the workspace is marked as trusted, the extension will + activate. + ([#15525](https://github.com/Microsoft/vscode-python/issues/15525)) + +1. Updates to the VSCode Notebook API. + ([#15567](https://github.com/Microsoft/vscode-python/issues/15567)) +1. Fix failing smoke tests on CI. + ([#15573](https://github.com/Microsoft/vscode-python/issues/15573)) +1. Update VS Code engine to 1.54.0 + ([#15604](https://github.com/Microsoft/vscode-python/issues/15604)) +1. Use `onReady` method available on language client to ensure language server is ready. + ([#15612](https://github.com/Microsoft/vscode-python/issues/15612)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.2.4 (9 March 2021) + +### Fixes + +1. Update to latest VSCode Notebook API. + ([#15415](https://github.com/Microsoft/vscode-python/issues/15415)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.2.3 (8 March 2021) + +### Fixes + +1. Add event handlers to stream error events to prevent process from exiting due to errors in process stdout & stderr streams. + ([#15395](https://github.com/Microsoft/vscode-python/issues/15395)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.2.2 (5 March 2021) + +### Fixes + +1. Fixes issue with Jedi Language Server telemetry. + ([#15419](https://github.com/microsoft/vscode-python/issues/15419)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.2.1 (19 February 2021) + +### Fixes + +1. Fix for missing pyenv virtual environments from selectable environments. + ([#15439](https://github.com/Microsoft/vscode-python/issues/15439)) +1. Register Jedi regardless of what language server is configured. + ([#15452](https://github.com/Microsoft/vscode-python/issues/15452)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.2.0 (17 February 2021) + +### Enhancements + +1. Use Language Server Protocol to work with Jedi. + ([#11995](https://github.com/Microsoft/vscode-python/issues/11995)) + +### Fixes + +1. Don't suggest insiders program nor show start page when in Codespaces. + ([#14833](https://github.com/Microsoft/vscode-python/issues/14833)) +1. Fix description of `Pyramid` debug config. + (thanks [vvijayalakshmi21](https://github.com/vvijayalakshmi21/)) + ([#5479](https://github.com/Microsoft/vscode-python/issues/5479)) +1. Refactored the Enable Linting command to provide the user with a choice of "Enable" or "Disable" linting to make it more intuitive. (thanks [henryboisdequin](https://github.com/henryboisdequin)) + ([#8800](https://github.com/Microsoft/vscode-python/issues/8800)) +1. Fix marketplace links in popups opening a non-browser VS Code instance in Codespaces. + ([#14264](https://github.com/Microsoft/vscode-python/issues/14264)) +1. Fixed the error command suggested when attempting to use "debug tests" configuration + (Thanks [Shahzaib paracha](https://github.com/ShahzaibParacha)) + ([#14729](https://github.com/Microsoft/vscode-python/issues/14729)) +1. Single test run fails sometimes if there is an error in unrelated file imported during discovery. + (thanks [Szymon Janota](https://github.com/sjanota/)) + ([#15147](https://github.com/Microsoft/vscode-python/issues/15147)) +1. Re-enable localization on the start page. It was accidentally + disabled in October when the Jupyter extension was split out. + ([#15232](https://github.com/Microsoft/vscode-python/issues/15232)) +1. Ensure target environment is activated in the terminal when running install scripts. + ([#15285](https://github.com/Microsoft/vscode-python/issues/15285)) +1. Allow support for using notebook APIs in the VS code stable build. + ([#15364](https://github.com/Microsoft/vscode-python/issues/15364)) + +### Code Health + +1. Raised the minimum required VS Code version to 1.51. + ([#15237](https://github.com/Microsoft/vscode-python/issues/15237)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2021.1.0 (21 January 2021) + +### Enhancements + +1. Remove code snippets (you can copy the + [old snippets](https://github.com/microsoft/vscode-python/blob/2020.12.424452561/snippets/python.json) + and use them as + [your own snippets](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_create-your-own-snippets)). + ([#14781](https://github.com/Microsoft/vscode-python/issues/14781)) +1. Add PYTHONPATH to the language server settings response. + ([#15106](https://github.com/Microsoft/vscode-python/issues/15106)) +1. Integration with the bandit linter will highlight the variable, function or method for an issue instead of the entire line. + Requires latest version of the bandit package to be installed. + (thanks [Anthony Shaw](https://github.com/tonybaloney)) + ([#15003](https://github.com/Microsoft/vscode-python/issues/15003)) +1. Translated some more of the Python Extension messages in Simplified Chinese. + (thanks [Shinoyasan](https://github.com/shinoyasan/)) + ([#15079](https://github.com/Microsoft/vscode-python/issues/15079)) +1. Update Simplified Chinese translation. + (thanks [Fiftysixtimes7](https://github.com/FiftysixTimes7)) + ([#14997](https://github.com/Microsoft/vscode-python/issues/14997)) + +### Fixes + +1. Fix environment variables not refreshing on env file edits. + ([#3805](https://github.com/Microsoft/vscode-python/issues/3805)) +1. fix npm audit[high]: [Remote Code Execution](npmjs.com/advisories/1548) + ([#14640](https://github.com/Microsoft/vscode-python/issues/14640)) +1. Ignore false positives when scraping environment variables. + ([#14812](https://github.com/Microsoft/vscode-python/issues/14812)) +1. Fix unittest discovery when using VS Code Insiders by using Inversify's `skipBaseClassChecks` option. + ([#14962](https://github.com/Microsoft/vscode-python/issues/14962)) +1. Make filtering in findInterpretersInDir() faster. + ([#14983](https://github.com/Microsoft/vscode-python/issues/14983)) +1. Remove the Buffer() is deprecated warning from Developer tools. ([#15045](https://github.com/microsoft/vscode-python/issues/15045)) + ([#15045](https://github.com/Microsoft/vscode-python/issues/15045)) +1. Add support for pytest 6 options. + ([#15094](https://github.com/Microsoft/vscode-python/issues/15094)) + +### Code Health + +1. Update to Node 12.20.0. + ([#15046](https://github.com/Microsoft/vscode-python/issues/15046)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2020.12.2 (15 December 2020) + +### Fixes + +1. Only activate discovery component when in experiment. + ([#14977](https://github.com/Microsoft/vscode-python/issues/14977)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2020.12.1 (15 December 2020) + +### Fixes + +1. Fix for extension loading issue in the latest release. + ([#14977](https://github.com/Microsoft/vscode-python/issues/14977)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2020.12.0 (14 December 2020) + +### Enhancements + +1. FastAPI debugger feature. + (thanks [Marcelo Trylesinski](https://github.com/kludex/)!) + ([#14247](https://github.com/Microsoft/vscode-python/issues/14247)) +1. Put linter prompt behind an experiment flag. + ([#14760](https://github.com/Microsoft/vscode-python/issues/14760)) +1. Add Python: Launch TensorBoard command behind an experiment. + ([#14806](https://github.com/Microsoft/vscode-python/issues/14806)) +1. Detect tfevent files in workspace and prompt to launch native TensorBoard session. + ([#14807](https://github.com/Microsoft/vscode-python/issues/14807)) +1. Use default color for "Select Python interpreter" on the status bar. + (thanks [Daniel Rodriguez](https://github.com/danielfrg)!) + ([#14859](https://github.com/Microsoft/vscode-python/issues/14859)) +1. Experiment to use the new environment discovery module. + ([#14868](https://github.com/Microsoft/vscode-python/issues/14868)) +1. Add experimentation API support for Pylance. + ([#14895](https://github.com/Microsoft/vscode-python/issues/14895)) + +### Fixes + +1. Format `.pyi` files correctly when using Black. + (thanks [Steve Dignam](https://github.com/sbdchd)!) + ([#13341](https://github.com/Microsoft/vscode-python/issues/13341)) +1. Add `node-loader` to support `webpack` for `fsevents` package. + ([#14664](https://github.com/Microsoft/vscode-python/issues/14664)) +1. Don't show play icon in diff editor. + (thanks [David Sanders](https://github.com/dsanders11)!) + ([#14800](https://github.com/Microsoft/vscode-python/issues/14800)) +1. Do not show "You need to select a Python interpreter before you start debugging" when "python" in debug configuration is invalid. + ([#14814](https://github.com/Microsoft/vscode-python/issues/14814)) +1. Fix custom language server message handlers being registered too late in startup. + ([#14893](https://github.com/Microsoft/vscode-python/issues/14893)) + +### Code Health + +1. Modified the errors generated when `launch.json` is not properly configured to be more specific about which fields are missing. + (thanks [Shahzaib Paracha](https://github.com/ShahzaibP)!) + ([#14739](https://github.com/Microsoft/vscode-python/issues/14739)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2020.11.1 (17 November 2020) + +### Enhancements + +1. Replaced "pythonPath" debug configuration property with "python". + ([#12462](https://github.com/Microsoft/vscode-python/issues/12462)) + +### Fixes + +1. Fix for Process Id Picker no longer showing up + ([#14678](https://github.com/Microsoft/vscode-python/issues/14678))) +1. Fix workspace symbol searching always returning empty. + ([#14727](https://github.com/Microsoft/vscode-python/issues/14727)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2020.11.0 (11 November 2020) + +### Enhancements + +1. Update shipped debugger wheels to python 3.8. + ([#14614](https://github.com/Microsoft/vscode-python/issues/14614)) + +### Fixes + +1. Update the logic for parsing and sending selected code to the REPL. + ([#14048](https://github.com/Microsoft/vscode-python/issues/14048)) +1. Fix "TypeError: message must be set" error when debugging with `pytest`. + ([#14067](https://github.com/Microsoft/vscode-python/issues/14067)) +1. When sending code to the REPL, read input from `sys.stdin` instead of passing it as an argument. + ([#14471](https://github.com/Microsoft/vscode-python/issues/14471)) + +### Code Health + +1. Code for Jupyter Notebooks support has been refactored into the Jupyter extension, which is now a dependency for the Python extension + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +## 2020.10.0 (27 October 2020) + +### Enhancements + +1. `debugpy` updated to latest stable version. +1. Make data viewer openable from the variables window context menu while debugging. + ([#14406](https://github.com/Microsoft/vscode-python/issues/14406)) +1. Do not opt users out of the insiders program if they have a stable version installed. + ([#14090](https://github.com/Microsoft/vscode-python/issues/14090)) + +### Fixes + +1. Make sure not to set `__file__` unless necessary as this can mess up some modules (like multiprocessing). + ([#12530](https://github.com/Microsoft/vscode-python/issues/12530)) +1. Fix isolate script to only remove current working directory. + ([#13942](https://github.com/Microsoft/vscode-python/issues/13942)) +1. Make sure server name and kernel name show up when connecting. + ([#13955](https://github.com/Microsoft/vscode-python/issues/13955)) +1. Have Custom Editors load on editor show unless autostart is disabled. + ([#14016](https://github.com/Microsoft/vscode-python/issues/14016)) +1. For exporting, first check the notebook or interactive window interpreter before the jupyter selected interpreter. + ([#14143](https://github.com/Microsoft/vscode-python/issues/14143)) +1. Fix interactive debugging starting (trimQuotes error). + ([#14212](https://github.com/Microsoft/vscode-python/issues/14212)) +1. Use the kernel defined in the metadata of Notebook instead of using the default workspace interpreter. + ([#14213](https://github.com/Microsoft/vscode-python/issues/14213)) +1. Fix latex output not showing up without a 'display' call. + ([#14216](https://github.com/Microsoft/vscode-python/issues/14216)) +1. Fix markdown cell marker when exporting a notebook to a Python script. + ([#14359](https://github.com/Microsoft/vscode-python/issues/14359)) + +### Code Health + +1. Add Windows unit tests to the PR validation pipeline. + ([#14013](https://github.com/Microsoft/vscode-python/issues/14013)) +1. Functional test failures related to kernel ports overlapping. + ([#14290](https://github.com/Microsoft/vscode-python/issues/14290)) +1. Change message from `IPython kernel` to `Jupyter kernel`. + ([#14309](https://github.com/Microsoft/vscode-python/issues/14309)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + ## 2020.9.2 (6 October 2020) ### Fixes @@ -10,6 +3740,10 @@ ([#14182](https://github.com/Microsoft/vscode-python/issues/14182)) 1. Fix exporting from the interactive window. ([#14210](https://github.com/Microsoft/vscode-python/issues/14210)) +1. Fix for CVE-2020-16977 + ([CVE-2020-16977](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2020-16977)) +1. Fix for CVE-2020-17163 + ([CVE-2020-17163](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2020-17163)) ### Thanks @@ -216,6 +3950,13 @@ part of! ([#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 + ## 2020.8.6 (15 September 2020) ### Fixes diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 18873b3ccf8a..f9ba8cf65f3e 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,6 +1,9 @@ -This project has adopted the [Microsoft Open Source Code of -Conduct](https://opensource.microsoft.com/codeofconduct/). -For more information see the [Code of Conduct -FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or -contact [opencode@microsoft.com](mailto:opencode@microsoft.com) -with any additional questions or comments. +# Microsoft Open Source Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). + +Resources: + +- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) +- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) +- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns diff --git a/CODING_STANDARDS.md b/CODING_STANDARDS.md deleted file mode 100644 index f6d3a03a943d..000000000000 --- a/CODING_STANDARDS.md +++ /dev/null @@ -1,55 +0,0 @@ -## Coding guidelines for TypeScript - -- The following standards are inspired from [Coding guidelines for TypeScript](https://github.com/Microsoft/TypeScript/wiki/Coding-guidelines) (which you should follow when something is not specified in this document, although any pre-existing practices in a file being edited trump either style guide). - -### Names - -- Use `PascalCase` for type names. -- Use `I` as a prefix for interface names only when an interface is implemented by a class. -- Use `PascalCase` for enum values. -- Use `camelCase` for function names. -- Use `camelCase` for property names and local variables. -- Do not use `_` as a prefix for private properties (unless used as backing properties). -- Use whole words in names when possible. - -### Types - -- Do not export types/functions unless you need to share it across multiple components. -- Do not introduce new types/values to the global namespace. -- Shared types should be defined in `types.ts`. - Within a file, type definitions should come first. - -### null and undefined - -Use undefined. Do not use null. - -### Comments - -- Comments must end with a period. -- Use JSDoc style comments for functions, interfaces, enums, and classes. - -### Strings - -Use single quotes for strings. - -### Imports - -- Use ES6 module imports. -- Do not use bare `import *`; all imports should either explicitly pull in an object or import an entire module, otherwise you're implicitly polluting the global namespace and making it difficult to figure out from code examination where a name originates from. - -### Style - -- Use `prettier` to format `TypeScript` and `JavaScript` code. -- Use arrow functions over anonymous function expressions. -- Always surround loop and conditional bodies with curly braces. Statements on the same line are allowed to omit braces. -- Open curly braces always go on the same line as whatever necessitates them. -- Parenthesized constructs should have no surrounding whitespace. -- A single space follows commas, colons, and semicolons in those constructs. For example: - - - `for (var i = 0, n = str.length; i < 10; i++) { }` - - `if (x < 10) { }` - - `function f(x: number, y: string): void { }` - -- `else` goes on the same line from the closing curly brace. -- Use 4 spaces per indentation. -- All files must end with an empty line. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a0b321c581b6..c6c0998395b2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,358 +1 @@ -# Contributing to the Python extension for Visual Studio Code - ---- - -| `release` branch | `main` branch | Nightly CI | coverage (`main` branch) | -| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -| [![Build Status](https://dev.azure.com/ms/vscode-python/_apis/build/status/CI?branchName=release)](https://dev.azure.com/ms/vscode-python/_build/latest?definitionId=88&branchName=release) | [![Build Status](https://dev.azure.com/ms/vscode-python/_apis/build/status/CI?branchName=main)](https://dev.azure.com/ms/vscode-python/_build/latest?definitionId=88&branchName=main) | [![Build Status](https://dev.azure.com/ms/vscode-python/_apis/build/status/Nightly%20Build?branchName=main)](https://dev.azure.com/ms/vscode-python/_build/latest?definitionId=85&branchName=main) | [![codecov](https://codecov.io/gh/microsoft/vscode-python/branch/main/graph/badge.svg)](https://codecov.io/gh/microsoft/vscode-python) | - -[[Development build](https://pvsc.blob.core.windows.net/extension-builds/ms-python-insiders.vsix)] - ---- - -[For contributing to the [Microsoft Python Language Server](https://github.com/Microsoft/python-language-server) see its own repo; for [Pylance](https://github.com/microsoft/pylance-release) see its own repo; for [debugpy](https://github.com/microsoft/debugpy) see its own repo] - -## Contributing a pull request - -### Prerequisites - -1. [Node.js](https://nodejs.org/) 12.15 -1. [Python](https://www.python.org/) 2.7 or later -1. Windows, macOS, or Linux -1. [Visual Studio Code](https://code.visualstudio.com/) -1. The following VS Code extensions: - - [TSLint](https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-typescript-tslint-plugin) - - [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) - - [EditorConfig for VS Code](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) -1. Have an issue which has a "needs PR" label (feel free to indicate you would like to provide a PR for the issue so others don't work on it as well) - -### Setup - -```shell -git clone https://github.com/microsoft/vscode-python -cd vscode-python -npm ci -python3 -m venv .venv -# Activate the virtual environment as appropriate for your shell, For example, on bash it's ... -source .venv/bin/activate -# The Python code in the extension is formatted using Black. -python3 -m pip install black -# Install Python dependencies using `python3`. -# If you want to use a different interpreter then specify it in the -# CI_PYTHON_PATH environment variable. -npx gulp installPythonLibs -``` - -If you see warnings that `The engine "vscode" appears to be invalid.`, you can ignore these. - -### Incremental Build - -Run the `Compile` and `Hygiene` build Tasks from the [Run Build Task...](https://code.visualstudio.com/docs/editor/tasks) command picker (short cut `CTRL+SHIFT+B` or `⇧⌘B`). This will leave build and hygiene tasks running in the background and which will re-run as files are edited and saved. You can see the output from either task in the Terminal panel (use the selector to choose which output to look at). - -You can also compile from the command-line. For a full compile you can use: - -```shell -npx gulp prePublishNonBundle -``` - -For incremental builds you can use the following commands depending on your needs: - -```shell -npm run compile -npm run compile-webviews-watch # For data science (React Code) -``` - -Sometimes you will need to run `npm run clean` and even `rm -r out`. -This is especially true if you have added or removed files. - -### Errors and Warnings - -TypeScript errors and warnings will be displayed in the `Problems` window of Visual Studio Code. - -### Run dev build and validate your changes - -To test changes, open the `vscode-python` folder in VSCode, and select the workspace titled `vscode-python`. -Then, open the debug panel by clicking the `Run and Debug` icon on the sidebar, select the `Extension` -option from the top menu, and click start. A new window will launch with the title -`[Extension Development Host]`. - -### Running Unit Tests - -Note: Unit tests are those in files with extension `.unit.test.ts`. - -1. Make sure you have compiled all code (done automatically when using incremental building) -1. Ensure you have disabled breaking into 'Uncaught Exceptions' when running the Unit Tests -1. For the linters and formatters tests to pass successfully, you will need to have those corresponding Python libraries installed locally -1. Run the Tests via the `Unit Tests` launch option. - -You can also run them from the command-line (after compiling): - -```shell -npm run test:unittests # runs all unit tests -npm run test:unittests -- --grep='' -``` - -_To run only a specific test suite for unit tests:_ -Alter the `launch.json` file in the `"Debug Unit Tests"` section by setting the `grep` field: - -```js - "args": [ - "--timeout=60000", - "--grep", "" - ], -``` - -...this will only run the suite with the tests you care about during a test run (be sure to set the debugger to run the `Debug Unit Tests` launcher). - -### Running Functional Tests - -Functional tests are those in files with extension `.functional.test.ts`. -These tests are similar to system tests in scope, but are run like unit tests. - -You can run functional tests in a similar way to that for unit tests: - -- via the "Functional Tests" launch option, or -- on the command line via `npm run test:functional` - -### Running System Tests - -Note: System tests are those in files with extension `.test*.ts` but which are neither `.functional.test.ts` nor `.unit.test.ts`. - -1. Make sure you have compiled all code (done automatically when using incremental building) -1. Ensure you have disabled breaking into 'Uncaught Exceptions' when running the Unit Tests -1. For the linters and formatters tests to pass successfully, you will need to have those corresponding Python libraries installed locally by using the `./requirements.txt` and `build/test-requirements.txt` files -1. Run the tests via `npm run` or the Debugger launch options (you can "Start Without Debugging"). -1. **Note** you will be running tests under the default Python interpreter for the system. - -You can also run the tests from the command-line (after compiling): - -```shell -npm run testSingleWorkspace # will launch the VSC UI -npm run testMultiWorkspace # will launch the VSC UI -``` - -#### Customising the Test Run - -If you want to change which tests are run or which version of Python is used, -you can do this by setting environment variables. The same variables work when -running from the command line or launching from within VSCode, though the -mechanism used to specify them changes a little. - -- Setting `CI_PYTHON_PATH` lets you change the version of python the tests are executed with -- Setting `VSC_PYTHON_CI_TEST_GREP` lets you filter the tests by name - -_`CI_PYTHON_PATH`_ - -In some tests a Python executable is actually run. The default executable is -`python` (for now). Unless you've run the tests inside a virtual environment, -this will almost always mean Python 2 is used, which probably isn't what you -want. - -By setting the `CI_PYTHON_PATH` environment variable you can -control the exact Python executable that gets used. If the executable -you specify isn't on `$PATH` then be sure to use an absolute path. - -This is also the mechanism for testing against other versions of Python. - -_`VSC_PYTHON_CI_TEST_GREP`_ - -This environment variable allows providing a regular expression which will -be matched against suite and test "names" to be run. By default all tests -are run. - -For example, to run only the tests in the `Sorting` suite (from -[`src/test/format/extension.sort.test.ts`](https://github.com/Microsoft/vscode-python/blob/84f9c7a174111/src/test/format/extension.sort.test.ts)) -you would set the value to `Sorting`. To run the `ProcessService` and -`ProcessService Observable` tests which relate to `stderr` handling, you might -use the value `ProcessService.*stderr`. - -Be sure to escape any grep-sensitive characters in your suite name. - -In some rare cases in the "system" tests the `VSC_PYTHON_CI_TEST_GREP` -environment variable is ignored. If that happens then you will need to -temporarily modify the `const grep =` line in -[`src/test/index.ts`](https://github.com/Microsoft/vscode-python/blob/84f9c7a174111/src/test/index.ts#L64). - -_Launching from VSCode_ - -In order to set environment variables when launching the tests from VSCode you -should edit the `launch.json` file. For example you can add the following to the -appropriate configuration you want to run to change the interpreter used during -testing: - -```js - "env": { - "CI_PYTHON_PATH": "/absolute/path/to/interpreter/of/choice/python" - } -``` - -_On the command line_ - -The mechanism to set environment variables on the command line will vary based -on your system, however most systems support a syntax like the following for -setting a single variable for a subprocess: - -```shell -VSC_PYTHON_CI_TEST_GREP=Sorting npm run testSingleWorkspace -``` - -### Testing Python Scripts - -The extension has a number of scripts in ./pythonFiles. Tests for these -scripts are found in ./pythonFiles/tests. To run those tests: - -- `python2.7 pythonFiles/tests/run_all.py` -- `python3 -m pythonFiles.tests` - -By default, functional tests are included. To exclude them: - -`python3 -m pythonFiles.tests --no-functional` - -To run only the functional tests: - -`python3 -m pythonFiles.tests --functional` - -### Standard Debugging - -Clone the repo into any directory, open that directory in VSCode, and use the `Extension` launch option within VSCode. - -### Debugging the Python Extension Debugger - -The easiest way to debug the Python Debugger (in our opinion) is to clone this git repo directory into [your](https://code.visualstudio.com/docs/extensions/install-extension#_your-extensions-folder) extensions directory. -From there use the `Extension + Debugger` launch option. - -### Coding Standards - -Information on our coding standards can be found [here](https://github.com/Microsoft/vscode-python/blob/main/CODING_STANDARDS.md). -We have CI tests to ensure the code committed will adhere to the above coding standards. \*You can run this locally by executing the command `npx gulp precommit` or use the `precommit` Task. - -Messages displayed to the user must be localized using/created constants from/in the [localize.ts](https://github.com/Microsoft/vscode-python/blob/main/src/client/common/utils/localize.ts) file. - -## Development process - -To effectively contribute to this extension, it helps to know how its -development process works. That way you know not only why the -project maintainers do what they do to keep this project running -smoothly, but it allows you to help out by noticing when a step is -missed or to learn in case someday you become a project maintainer as -well! - -### Helping others - -First and foremost, we try to be helpful to users of the extension. -We monitor -[Stack Overflow questions](https://stackoverflow.com/questions/tagged/visual-studio-code+python) -to see where people might need help. We also try to respond to all -issues in some way in a timely manner (typically in less than one -business day, definitely no more than a week). We also answer -questions that reach us in other ways, e.g. Twitter. - -For pull requests, we aim to review any externally contributed PR no later -than the next sprint from when it was submitted (see -[Release Cycle](#release-cycle) below for our sprint schedule). - -### Release cycle - -Planning is done as one week sprints. We start a sprint every Thursday. -All [P0](https://github.com/Microsoft/vscode-python/labels/P0) issues are expected -to be fixed in the current sprint, else the next release will be blocked. -[P1](https://github.com/Microsoft/vscode-python/labels/P1) issues are a -top-priority and we try to close before the next release. All other issues are -considered best-effort for that sprint. - -The extension aims to do a new release once a month. A -[release plan](https://github.com/Microsoft/vscode-python/labels/release%20plan) -is created for each release to help track anything that requires a -person to do (long-term this project aims to automate as much of the -development process as possible). - -All development is actively done in the `main` branch of the -repository. This allows us to have a -[development build](#development-build) which is expected to be stable at -all times. Once we reach a release candidate, it becomes -our [release branch](https://github.com/microsoft/vscode-python/branches). -At that point only what is in the release branch will make it into the next -release. - -### Issue triaging - -#### Classifying issues - -To help actively track what stage -[issues](https://github.com/Microsoft/vscode-python/issues) -are at, various labels are used. The following label types are expected to -be set on all open issues (otherwise the issue is not considered triaged): - -1. `needs`/`triage`/`classify` -1. `feature` -1. `type` - -These labels cover what is blocking the issue from closing, what is affected by -the issue, and what kind of issue it is. (The `feature` label should be `feature-*` if the issue doesn't fit into any other `feature` label appropriately.) - -It is also very important to make the title accurate. People often write very brief, quick titles or ones that describe what they think the problem is. By updating the title to be appropriately descriptive for what _you_ think the issue is, you not only make finding older issues easier, but you also help make sure that you and the original reporter agree on what the issue is. - -#### Post-classification - -Once an issue has been appropriately classified, there are two keys ways to help out. One is to go through open issues that -have a merged fix and verify that the fix did in fact work. The other is to try to fix issues marked as `needs PR`. - -### Pull requests - -Key details that all pull requests are expected to handle should be -in the [pull request template](https://github.com/Microsoft/vscode-python/blob/main/.github/PULL_REQUEST_TEMPLATE.md). We do expect CI to be passing for a pull request before we will consider merging it. - -### Versioning - -Starting in 2018, the extension switched to -[calendar versioning](http://calver.org/) since the extension -auto-updates and thus there is no need to track its version -number for backwards-compatibility. In 2020, the extension switched to -having the the major version be the year of release, the minor version the -release count for that year, and the build number is a number that increments -for every build. -For example the first release made in 2020 is `2020.1.`. - -## Releasing - -Overall steps for releasing are covered in the -[release plan](https://github.com/Microsoft/vscode-python/labels/release%20plan) -([template](https://github.com/Microsoft/vscode-python/blob/main/.github/release_plan.md)). - -### Building a release - -To create a release _build_, follow the steps outlined in the [release plan](https://github.com/Microsoft/vscode-python/labels/release%20plan) (which has a [template](https://github.com/Microsoft/vscode-python/blob/main/.github/release_plan.md)). - -## Local Build - -Steps to build the extension on your machine once you've cloned the repo: - -```bash -> npm install -g vsce -# Perform the next steps in the vscode-python folder. -> npm ci -> python3 -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade -r requirements.txt -# For python 3.6 and lower use this command to install the debugger -> python3 -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade --pre debugpy -# For python 3.7 and greater use this command to install the debugger -> python3 -m pip --disable-pip-version-check install -r build/debugger-install-requirements.txt -> python3 ./pythonFiles/install_debugpy.py -> npm run clean -> npm run package # This step takes around 10 minutes. -``` - -Resulting in a `ms-python-insiders.vsix` file in your `vscode-python` folder. - -⚠️ If you made changes to `package.json`, run `npm install` (instead of `npm ci`) to update `package-lock.json` and install dependencies all at once. - -## Development Build - -If you would like to use the latest version of the extension as committed to `main` that has passed our test suite, then you may set the `"python.insidersChannel"` setting to `"daily"` or `"weekly"` based on how often you would like the extension to check for updates. - -You may also download and install the extension manually from the following -[location](https://pvsc.blob.core.windows.net/extension-builds/ms-python-insiders.vsix). -Once you have downloaded the -[ms-python-insiders.vsix](https://pvsc.blob.core.windows.net/extension-builds/ms-python-insiders.vsix) -file, please follow the instructions on -[this page](https://code.visualstudio.com/docs/editor/extension-gallery#_install-from-a-vsix) -to install the extension. Do note that the manual install will not automatically update to newer builds unless you set the `"python.insidersChannel"` setting (it will get replaced with released versions from the Marketplace once they are newer than the version install manually). +Please see [our wiki](https://github.com/microsoft/vscode-python/wiki) on how to contribute to this project. diff --git a/PYTHON_INTERACTIVE_TROUBLESHOOTING.md b/PYTHON_INTERACTIVE_TROUBLESHOOTING.md deleted file mode 100644 index 03688ee0a986..000000000000 --- a/PYTHON_INTERACTIVE_TROUBLESHOOTING.md +++ /dev/null @@ -1,66 +0,0 @@ -# Troubleshooting Jupyter issues in the Python Interactive Window or Notebook Editor - -This document is intended to help troubleshoot problems with starting Jupyter in the Python Interactive Window or Notebook Editor. - ---- - -## Jupyter Not Starting - -This error can happen when - -- Jupyter is out of date -- Jupyter is not installed -- You picked the wrong Python environment (one that doesn't have Jupyter installed). - -### The first step is to verify you are running the Python environment that you have Jupyter installed into. - -The first time that you start the Interactive Window or the Notebook Editor VS Code will attempt to locate a Python environment that has Jupyter installed in it and can start a notebook. - -The first Python interpreter to check will be the one selected with the selection dropdown on the bottom left of the VS Code window: - -![selector](resources/PythonSelector.png) - -Once a suitable interpreter with Jupyter has been located, VS Code will continue to use that interpreter for starting up Jupyter servers. -If no interpreters are found with Jupyter installed a popup message will ask if you would like to install Jupyter into the current interpreter. - -![install Jupyter](resources/InstallJupyter.png) - -If you would like to change from using the saved Python interpreter to a new interpreter for launching Jupyter just use the "Python: Select interpreter to start Jupyter server" VS Code command to change it. - -### The second step is to check that jupyter isn't giving any errors on startup. - -Run the following command from an environment that matches the Python you selected: -`python -m jupyter notebook --version` - -If this command shows any warnings, you need to upgrade or fix the warnings to continue with this version of Python. -If this command says 'no module named jupyter', you need to install Jupyter. - -### How to install Jupyter - -You can do this in a number of different ways: - -#### Anaconda - -Anaconda is a popular Python distribution. It makes it super easy to get Jupyter up and running. - -If you're already using Anaconda, follow these steps to get Jupyter - -1. Start anaconda environment -1. Run 'conda install jupyter' -1. Restart VS Code -1. Pick the conda version of Python in the python selector - -Otherwise you can install Anaconda and pick the default options -https://www.anaconda.com/download - -#### Pip - -You can also install Jupyter using pip. - -1. python -m pip install --upgrade pip -1. python -m pip install jupyter -1. Restart VS Code -1. Pick the Python environment you did the pip install in - -For more information see -http://jupyter.org/install diff --git a/README.md b/README.md index 41550296f7bc..e9dd52a538cd 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,44 @@ # Python extension for Visual Studio Code -A [Visual Studio Code](https://code.visualstudio.com/) [extension](https://marketplace.visualstudio.com/VSCode) with rich support for the [Python language](https://www.python.org/) (for all [actively supported versions](https://devguide.python.org/#status-of-python-branches) of the language: >=3.6), including features such as IntelliSense, linting, debugging, code navigation, code formatting, Jupyter notebook support, refactoring, variable explorer, test explorer, snippets, and more! +A [Visual Studio Code](https://code.visualstudio.com/) [extension](https://marketplace.visualstudio.com/VSCode) with rich support for the [Python language](https://www.python.org/) (for all [actively supported Python versions](https://devguide.python.org/versions/#supported-versions)), providing access points for extensions to seamlessly integrate and offer support for IntelliSense (Pylance), debugging (Python Debugger), formatting, linting, code navigation, refactoring, variable explorer, test explorer, environment management (**NEW** Python Environments Extension). + +## Support for [vscode.dev](https://vscode.dev/) + +The Python extension does offer [some support](https://github.com/microsoft/vscode-python/wiki/Partial-mode) when running on [vscode.dev](https://vscode.dev/) (which includes [github.dev](http://github.dev/)). This includes partial IntelliSense for open files in the editor. + + +## Installed extensions + +The Python extension will automatically install the following extensions by default to provide the best Python development experience in VS Code: + +- [Pylance](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance) – performant Python language support +- [Python Debugger](https://marketplace.visualstudio.com/items?itemName=ms-python.debugpy) – seamless debug experience with debugpy +- **(NEW)** [Python Environments](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-python-envs) – dedicated environment management (see below) + +These extensions are optional dependencies, meaning the Python extension will remain fully functional if they fail to be installed. Any or all of these extensions can be [disabled](https://code.visualstudio.com/docs/editor/extension-marketplace#_disable-an-extension) or [uninstalled](https://code.visualstudio.com/docs/editor/extension-marketplace#_uninstall-an-extension) at the expense of some features. Extensions installed through the marketplace are subject to the [Marketplace Terms of Use](https://cdn.vsassets.io/v/M146_20190123.39/_content/Microsoft-Visual-Studio-Marketplace-Terms-of-Use.pdf). + +### About the Python Environments Extension + +You may now see that the **Python Environments Extension** is installed for you, but it may or may not be "enabled" in your VS Code experience. Enablement is controlled by the setting `"python.useEnvironmentsExtension": true` (or `false`). + +- If you set this setting to `true`, you will manually opt in to using the Python Environments Extension for environment management. +- If you do not have this setting specified, you may be randomly assigned to have it turned on as we roll it out until it becomes the default experience for all users. + +The Python Environments Extension is still under active development and experimentation. Its goal is to provide a dedicated view and improved workflows for creating, deleting, and switching between Python environments, as well as managing packages. If you have feedback, please let us know via [issues](https://github.com/microsoft/vscode-python/issues). + +## Extensibility + +The Python extension provides pluggable access points for extensions that extend various feature areas to further improve your Python development experience. These extensions are all optional and depend on your project configuration and preferences. + +- [Python formatters](https://code.visualstudio.com/docs/python/formatting#_choose-a-formatter) +- [Python linters](https://code.visualstudio.com/docs/python/linting#_choose-a-linter) + +If you encounter issues with any of the listed extensions, please file an issue in its corresponding repo. ## Quick start -- **Step 1.** [Install a supported version of Python on your system](https://code.visualstudio.com/docs/python/python-tutorial#_prerequisites) (note: that the system install of Python on macOS is not supported). -- **Step 2.** Install the Python extension for Visual Studio Code. +- **Step 1.** [Install a supported version of Python on your system](https://code.visualstudio.com/docs/python/python-tutorial#_prerequisites) (note: the system install of Python on macOS is not supported). +- **Step 2.** [Install the Python extension for Visual Studio Code](https://code.visualstudio.com/docs/editor/extension-gallery). - **Step 3.** Open or create a Python file and start coding! ## Set up your environment @@ -18,17 +51,21 @@ A [Visual Studio Code](https://code.visualstudio.com/) [extension](https://marke - Configure the debugger through the Debug Activity Bar - + - Configure tests by running the `Configure Tests` command - + ## Jupyter Notebook quick start +The Python extension offers support for Jupyter notebooks via the [Jupyter extension](https://marketplace.visualstudio.com/items?itemName=ms-toolsai.jupyter) to provide you a great Python notebook experience in VS Code. + +- Install the [Jupyter extension](https://marketplace.visualstudio.com/items?itemName=ms-toolsai.jupyter). + - Open or create a Jupyter Notebook file (.ipynb) and start coding in our Notebook Editor! - + For more information you can: @@ -43,31 +80,26 @@ Open the Command Palette (Command+Shift+P on macOS and Ctrl+Shift+P on Windows/L | Command | Description | | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Python: Select Interpreter` | Switch between Python interpreters, versions, and environments. | -| `Python: Start REPL` | Start an interactive Python REPL using the selected interpreter in the VS Code terminal. | +| `Python: Start Terminal REPL` | Start an interactive Python REPL using the selected interpreter in the VS Code terminal. | | `Python: Run Python File in Terminal` | Runs the active Python file in the VS Code terminal. You can also run a Python file by right-clicking on the file and selecting `Run Python File in Terminal`. | -| `Python: Select Linter` | Switch from Pylint to Flake8 or other supported linters. | -| `Format Document` | Formats code using the provided [formatter](https://code.visualstudio.com/docs/python/editing#_formatting) in the `settings.json` file. | | `Python: Configure Tests` | Select a test framework and configure it to display the Test Explorer. | -To see all available Python commands, open the Command Palette and type `Python`. +To see all available Python commands, open the Command Palette and type `Python`. For Jupyter extension commands, just type `Jupyter`. ## Feature details Learn more about the rich features of the Python extension: -- [IntelliSense](https://code.visualstudio.com/docs/python/editing#_autocomplete-and-intellisense): Edit your code with auto-completion, code navigation, syntax checking and more -- [Linting](https://code.visualstudio.com/docs/python/linting): Get additional code analysis with Pylint, Flake8 and more -- [Code formatting](https://code.visualstudio.com/docs/python/editing#_formatting): Format your code with black, autopep or yapf - -- [Debugging](https://code.visualstudio.com/docs/python/debugging): Debug your Python scripts, web apps, remote or multi-threaded processes - -- [Testing](https://code.visualstudio.com/docs/python/unit-testing): Run and debug tests through the Test Explorer with unittest, pytest or nose - -- [Jupyter Notebooks](https://code.visualstudio.com/docs/python/jupyter-support): Create and edit Jupyter Notebooks, add and run code cells, render plots, visualize variables through the variable explorer, visualize dataframes with the data viewer, and more +- [IntelliSense](https://code.visualstudio.com/docs/python/editing#_autocomplete-and-intellisense): Edit your code with auto-completion, code navigation, syntax checking and more. +- [Linting](https://code.visualstudio.com/docs/python/linting): Get additional code analysis with Pylint, Flake8 and more. +- [Code formatting](https://code.visualstudio.com/docs/python/formatting): Format your code with black, autopep or yapf. +- [Debugging](https://code.visualstudio.com/docs/python/debugging): Debug your Python scripts, web apps, remote or multi-threaded processes. +- [Testing](https://code.visualstudio.com/docs/python/unit-testing): Run and debug tests through the Test Explorer with unittest or pytest. +- [Jupyter Notebooks](https://code.visualstudio.com/docs/python/jupyter-support): Create and edit Jupyter Notebooks, add and run code cells, render plots, visualize variables through the variable explorer, visualize dataframes with the data viewer, and more. +- [Environments](https://code.visualstudio.com/docs/python/environments): Automatically activate and switch between virtualenv, venv, pipenv, conda and pyenv environments. +- [Refactoring](https://code.visualstudio.com/docs/python/editing#_refactoring): Restructure your Python code with variable extraction and method extraction. Additionally, there is componentized support to enable additional refactoring, such as import sorting, through extensions including [isort](https://marketplace.visualstudio.com/items?itemName=ms-python.isort) and [Ruff](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff). -- [Environments](https://code.visualstudio.com/docs/python/environments): Automatically activate and switch between virtualenv, venv, pipenv, conda and pyenv environments -- [Refactoring](https://code.visualstudio.com/docs/python/editing#_refactoring): Restructure your Python code with variable extraction, method extraction and import sorting ## Supported locales @@ -75,13 +107,13 @@ The extension is available in multiple languages: `de`, `en`, `es`, `fa`, `fr`, ## Questions, issues, feature requests, and contributions -- If you have a question about how to accomplish something with the extension, please [ask on Stack Overflow](https://stackoverflow.com/questions/tagged/visual-studio-code+python) -- If you come across a problem with the extension, please [file an issue](https://github.com/microsoft/vscode-python) -- Contributions are always welcome! Please see our [contributing guide](https://github.com/Microsoft/vscode-python/blob/main/CONTRIBUTING.md) for more details +- If you have a question about how to accomplish something with the extension, please [ask on our Discussions page](https://github.com/microsoft/vscode-python/discussions/categories/q-a). +- If you come across a problem with the extension, please [file an issue](https://github.com/microsoft/vscode-python). +- Contributions are always welcome! Please see our [contributing guide](https://github.com/Microsoft/vscode-python/blob/main/CONTRIBUTING.md) for more details. - Any and all feedback is appreciated and welcome! - - If someone has already [filed an issue](https://github.com/Microsoft/vscode-python) that encompasses your feedback, please leave a 👍/👎 reaction on the issue - - Otherwise please file a new issue -- If you're interested in the development of the extension, you can read about our [development process](https://github.com/Microsoft/vscode-python/blob/main/CONTRIBUTING.md#development-process) + - If someone has already [filed an issue](https://github.com/Microsoft/vscode-python) that encompasses your feedback, please leave a 👍/👎 reaction on the issue. + - Otherwise please start a [new discussion](https://github.com/microsoft/vscode-python/discussions/categories/ideas). +- If you're interested in the development of the extension, you can read about our [development process](https://github.com/Microsoft/vscode-python/blob/main/CONTRIBUTING.md#development-process). ## Data and telemetry @@ -89,6 +121,6 @@ The Microsoft Python Extension for Visual Studio Code collects usage data and sends it to Microsoft to help improve our products and services. Read our [privacy statement](https://privacy.microsoft.com/privacystatement) to -learn more. This extension respects the `telemetry.enableTelemetry` +learn more. This extension respects the `telemetry.telemetryLevel` setting which you can learn more about at https://code.visualstudio.com/docs/supporting/faq#_how-to-disable-telemetry-reporting. diff --git a/ThirdPartyNotices-Distribution.txt b/ThirdPartyNotices-Distribution.txt deleted file mode 100644 index 98e654fb8cdb..000000000000 --- a/ThirdPartyNotices-Distribution.txt +++ /dev/null @@ -1,15745 +0,0 @@ -NOTICES AND INFORMATION -Do Not Translate or Localize - -This software incorporates material from third parties. -Microsoft makes certain open source code available at https://3rdpartysource.microsoft.com, -or you may send a check or money order for US $5.00, including the product name, -the open source component name, platform, and version number, to: - -Source Code Compliance Team -Microsoft Corporation -One Microsoft Way -Redmond, WA 98052 -USA - -Notwithstanding any other terms, you may reverse engineer this software to the extent -required to debug changes to any libraries licensed under the GNU Lesser General Public License. - ---------------------------------------------------------- - -json-schema 0.2.3 - AFL-2.1 OR BSD-3-Clause -https://github.com/kriszyp/json-schema#readme - -Copyright (c) 2007 Kris Zyp SitePen (www.sitepen.com) - -AFL-2.1 OR BSD-3-Clause - ---------------------------------------------------------- - ---------------------------------------------------------- - -acorn-node 1.7.0 - Apache-2.0 -https://github.com/browserify/acorn-node - -Copyright (c) 2016 Jordan Gensler -Copyright (c) 2017-2018 by Adrian Heine -Copyright 2018 Renee Kooi - -# [Apache License 2.0](https://spdx.org/licenses/Apache-2.0) - -Copyright 2018 Renée Kooi - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -> http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - -## acorn-bigint - -The code in the `lib/bigint` folder is compiled from code licensed as MIT: - -> Copyright (C) 2017-2018 by Adrian Heine -> -> 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. - -Find the source code at https://github.com/acornjs/acorn-bigint. - -## acorn-import-meta - -The code in the `lib/import-meta` folder is compiled from code licensed as MIT: - -> Copyright (C) 2017-2018 by Adrian Heine -> -> 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. - -Find the source code at https://github.com/acornjs/acorn-import-meta. - -## acorn-dynamic-import - -The code in the `lib/dynamic-import` folder is licensed as MIT: - -> MIT License -> -> Copyright (c) 2016 Jordan Gensler -> -> 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. - -Find the source code at https://github.com/kesne/acorn-dynamic-import. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -aws-sign2 0.7.0 - Apache-2.0 -https://github.com/mikeal/aws-sign#readme - -Copyright 2010 LearnBoost - -Apache License - -Version 2.0, January 2004 - -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: - -You must give any other recipients of the Work or Derivative Works a copy of this License; and - -You must cause any modified files to carry prominent notices stating that You changed the files; and - -You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and - -If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - ---------------------------------------------------------- - ---------------------------------------------------------- - -azure-storage 2.10.3 - Apache-2.0 -http://github.com/Azure/azure-storage-node - -Copyright (c) Microsoft and contributors. - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - ---------------------------------------------------------- - ---------------------------------------------------------- - -caseless 0.12.0 - Apache-2.0 -https://github.com/mikeal/caseless#readme - - -Apache License -Version 2.0, January 2004 -http://www.apache.org/licenses/ -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -1. Definitions. -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. -2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. -3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. -4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: -You must give any other recipients of the Work or Derivative Works a copy of this License; and -You must cause any modified files to carry prominent notices stating that You changed the files; and -You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and -If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. -5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. -6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. -8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. -9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. -END OF TERMS AND CONDITIONS - ---------------------------------------------------------- - ---------------------------------------------------------- - -diff-match-patch 1.0.4 - Apache-2.0 -https://github.com/JackuB/diff-match-patch#readme - -Copyright 2018 - - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - ---------------------------------------------------------- - ---------------------------------------------------------- - -estree-is-function 1.0.0 - Apache-2.0 -https://github.com/goto-bus-stop/estree-is-function - -Copyright 2017 Renee Kooi - -# [Apache License 2.0](https://spdx.org/licenses/Apache-2.0) - -Copyright 2017 Renée Kooi - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -> http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -forever-agent 0.6.1 - Apache-2.0 -https://github.com/mikeal/forever-agent - - -Apache License - -Version 2.0, January 2004 - -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: - -You must give any other recipients of the Work or Derivative Works a copy of this License; and - -You must cause any modified files to carry prominent notices stating that You changed the files; and - -You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and - -If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - ---------------------------------------------------------- - ---------------------------------------------------------- - -get-assigned-identifiers 1.2.0 - Apache-2.0 -https://github.com/goto-bus-stop/get-assigned-identifiers - -Copyright 2017 Renee Kooi - -# [Apache License 2.0](https://spdx.org/licenses/Apache-2.0) - -Copyright 2017 Renée Kooi - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -> http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -log4js 6.1.2 - Apache-2.0 -https://log4js-node.github.io/log4js-node/ - -Copyright 2015 Gareth Jones - -Copyright 2015 Gareth Jones (with contributions from many other people) - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -oauth-sign 0.9.0 - Apache-2.0 -https://github.com/mikeal/oauth-sign#readme - - -Apache License - -Version 2.0, January 2004 - -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: - -You must give any other recipients of the Work or Derivative Works a copy of this License; and - -You must cause any modified files to carry prominent notices stating that You changed the files; and - -You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and - -If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - ---------------------------------------------------------- - ---------------------------------------------------------- - -pygls 0.9.0 - Apache-2.0 - - -Copyright (c) Microsoft Corporation -Copyright 2017 Palantir Technologies, Inc. -Copyright 2018 Palantir Technologies, Inc. - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright (c) Open Law Library. All rights reserved. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -reflect-metadata 0.1.13 - Apache-2.0 -http://rbuckton.github.io/reflect-metadata - -Copyright (c) Microsoft. -Copyright (c) 2016 Brian Terlson -Copyright (c) 2015 Nicolas Bevacqua -Copyright (c) Microsoft Corporation. - -Apache License - -Version 2.0, January 2004 - -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: - -You must give any other recipients of the Work or Derivative Works a copy of this License; and - -You must cause any modified files to carry prominent notices stating that You changed the files; and - -You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and - -If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - ---------------------------------------------------------- - ---------------------------------------------------------- - -request 2.88.0 - Apache-2.0 -https://github.com/request/request#readme - -Copyright 2010-2012 Mikeal Rogers - -Apache License - -Version 2.0, January 2004 - -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: - -You must give any other recipients of the Work or Derivative Works a copy of this License; and - -You must cause any modified files to carry prominent notices stating that You changed the files; and - -You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and - -If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - ---------------------------------------------------------- - ---------------------------------------------------------- - -rxjs 6.5.4 - Apache-2.0 -https://github.com/ReactiveX/RxJS - -Copyright Google Inc. -Copyright (c) Microsoft Corporation. -Copyright (c) 2015-2018 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright (c) 2015-2018 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright (c) 2015-2018 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright (c) 2015-2018 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright (c) 2015-2018 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -rxjs-compat 6.5.4 - Apache-2.0 - - -(c) this.destination.next -(c) this.destination.error -Copyright (c) Microsoft Corporation. -Copyright (c) 2015-2018 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright (c) 2015-2018 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright (c) 2015-2018 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright (c) 2015-2018 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright (c) 2015-2018 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -scope-analyzer 2.0.5 - Apache-2.0 -https://github.com/goto-bus-stop/scope-analyzer - -Copyright 2017 Renee Kooi - -# [Apache License 2.0](https://spdx.org/licenses/Apache-2.0) - -Copyright 2017 Renée Kooi - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -> http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -tslib 1.10.0 - Apache-2.0 -http://typescriptlang.org/ - -Copyright (c) Microsoft Corporation. - -Apache License - -Version 2.0, January 2004 - -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: - -You must give any other recipients of the Work or Derivative Works a copy of this License; and - -You must cause any modified files to carry prominent notices stating that You changed the files; and - -You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and - -If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - - ---------------------------------------------------------- - ---------------------------------------------------------- - -tunnel-agent 0.6.0 - Apache-2.0 -https://github.com/mikeal/tunnel-agent#readme - - -Apache License - -Version 2.0, January 2004 - -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: - -You must give any other recipients of the Work or Derivative Works a copy of this License; and - -You must cause any modified files to carry prominent notices stating that You changed the files; and - -You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and - -If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - ---------------------------------------------------------- - ---------------------------------------------------------- - -async-listener 0.6.10 - BSD-2-Clause -https://github.com/othiym23/async-listener#readme - -Copyright (c) 2013-2017, Forrest L Norvell -Copyright Joyent, Inc. and other Node contributors. - -BSD 2-Clause License - -Copyright (c) 2013-2017, Forrest L Norvell -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -cls-hooked 4.2.2 - BSD-2-Clause -https://github.com/jeff-lewis/cls-hooked#readme - -Copyright (c) 2013-2016, Forrest L Norvell - -Copyright (c) 2013-2016, Forrest L Norvell -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -continuation-local-storage 3.2.1 - BSD-2-Clause -https://github.com/othiym23/node-continuation-local-storage#readme - -Copyright (c) 2013-2016, Forrest L Norvell - -Copyright (c) 2013-2016, Forrest L Norvell -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -emitter-listener 1.1.2 - BSD-2-Clause -https://github.com/othiym23/emitter-listener - - -Copyright (c) . All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -escodegen 1.9.1 - BSD-2-Clause -http://github.com/estools/escodegen - -Copyright (c) 2014 Ivan Nikulin -Copyright (c) 2012 Kris Kowal -Copyright (c) 2012 John Freeman -Copyright (c) 2015 Ingvar Stepanyan -Copyright (c) 2012 Yusuke Suzuki -Copyright (c) 2012-2013 Mathias Bynens -Copyright (c) 2013 Irakli Gozalishvili -Copyright (c) 2012 Arpad Borsos -Copyright (c) 2012-2014 Yusuke Suzuki -Copyright (c) 2011-2012 Ariya Hidayat -Copyright (c) 2012 Yusuke Suzuki (http://github.com/Constellation) -Copyright (c) 2012 Joost-Wim Boekesteijn -Copyright (c) 2012 Robert Gust-Bardon -Copyright (c) 2012-2013 Michael Ficarra - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -escodegen 1.2.0 - BSD-2-Clause -http://github.com/Constellation/escodegen - -Copyright (c) 2012 Kris Kowal -Copyright (c) 2012 John Freeman -Copyright (c) 2012 Yusuke Suzuki -Copyright (c) 2012-2013 Mathias Bynens -Copyright (c) 2014 Yusuke Suzuki -Copyright (c) 2013 Irakli Gozalishvili -Copyright (c) 2009-2011, Mozilla Foundation and contributors -Copyright (c) 2012 Arpad Borsos -Copyright (c) 2012-2013 Yusuke Suzuki -Copyright (c) 2011-2012 Ariya Hidayat -Copyright (c) 2012 Yusuke Suzuki (http://github.com/Constellation) -Copyright (c) 2012 Joost-Wim Boekesteijn -Copyright (c) 2012 Robert Gust-Bardon -Copyright (c) 2012-2013 Michael Ficarra - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -esprima 3.1.3 - BSD-2-Clause -http://esprima.org/ - -Copyright JS Foundation and other contributors, https://js.foundation - -Copyright JS Foundation and other contributors, https://js.foundation/ - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -esprima 1.0.4 - BSD-2-Clause -http://esprima.org/ - -Copyright (c) 2012 Mathias Bynens -Copyright (c) 2012 Kris Kowal -Copyright (c) 2011 Yusuke Suzuki -Copyright (c) 2012 Yusuke Suzuki -Copyright (c) 2011 Ariya Hidayat -Copyright (c) 2012 Ariya Hidayat -Copyright (c) 2011 Arpad Borsos -Copyright (c) 2012 Arpad Borsos -Copyright (c) 2012 Joost-Wim Boekesteijn -Copyright (c) 2012, 2011 Ariya Hidayat (http://ariya.ofilabs.com/about) and other contributors. - -Copyright (c) . All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -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-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: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -estraverse 1.5.1 - BSD-2-Clause -https://github.com/Constellation/estraverse - -Copyright (c) 2012 Ariya Hidayat -Copyright (c) 2012-2013 Yusuke Suzuki -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: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -esutils 2.0.3 - BSD-2-Clause -https://github.com/estools/esutils - -Copyright (c) 2014 Ivan Nikulin -Copyright (c) 2013 Yusuke Suzuki -Copyright (c) 2013-2014 Yusuke Suzuki -Copyright (c) 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: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -esutils 1.0.0 - BSD-2-Clause -https://github.com/Constellation/esutils - -Copyright (c) 2013 Yusuke Suzuki -Copyright (c) 2013 Yusuke Suzuki (http://github.com/Constellation) - -Copyright (c) . All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -http-cache-semantics 3.8.1 - BSD-2-Clause -https://github.com/pornel/http-cache-semantics#readme - - -Copyright (c) . All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -http-cache-semantics 4.1.0 - BSD-2-Clause -https://github.com/kornelski/http-cache-semantics#readme - -Copyright 2016-2018 Kornel Lesinski - -Copyright 2016-2018 Kornel Lesiński - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -sax 0.5.8 - BSD-2-Clause -https://github.com/isaacs/sax-js - -Copyright (c) Isaac Z. Schlueter -copyright-software-short-notice-20021231.html' W3C Software Short -copyright' Copyright 2012 W3C http://www.csail.mit.edu/' title Massachusetts Institute of Technology' MIT , http://www.ercim.org/' - -Copyright (c) Isaac Z. Schlueter ("Author") -All rights reserved. - -The BSD License - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR -BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN -IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -The file "examples/strict.dtd" is licensed by the W3C and used according -to the terms of the W3C SOFTWARE NOTICE AND LICENSE. See LICENSE-W3C.html -for details. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -shimmer 1.2.1 - BSD-2-Clause -https://github.com/othiym23/shimmer#readme - -Copyright (c) 2013-2019, Forrest L Norvell - -BSD 2-Clause License - -Copyright (c) 2013-2019, Forrest L Norvell -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -uri-js 4.2.2 - BSD-2-Clause -https://github.com/garycourt/uri-js - -(c) 2011 Gary Court. -Copyright 2011 Gary Court. -Copyright (c) 2008 Ariel Flesler -Copyright (c) 2009 John Resig, Jorn Zaefferer - -Copyright (c) . All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -winreg 1.2.4 - BSD-2-Clause -http://fresc81.github.io/node-winreg - -Copyright (c) 2016 Paul -Copyright (c) 2016, Paul Bottin - -Copyright (c) . All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -click 7.1.2 - BSD-2-Clause AND BSD-3-Clause - - -Copyright 2014 Pallets -copyright 2014 Pallets -Copyright 2001-2006 Gregory P. Ward. -Copyright 2002-2006 Python Software Foundation. - -Copyright 2014 Pallets - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED -TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@jupyterlab/coreutils 3.2.0 - BSD-3-Clause -https://github.com/jupyterlab/jupyterlab - -Copyright (c) Jupyter Development Team. -Copyright (c) 2015 Project Jupyter Contributors - -Copyright (c) 2015 Project Jupyter Contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -Semver File License -=================== - -The semver.py file is from https://github.com/podhmo/python-semver -which is licensed under the "MIT" license. See the semver.py file for details. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@jupyterlab/coreutils 3.1.0 - BSD-3-Clause -https://github.com/jupyterlab/jupyterlab - -Copyright (c) Jupyter Development Team. -Copyright (c) 2015 Project Jupyter Contributors - -Copyright (c) 2015 Project Jupyter Contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -Semver File License -=================== - -The semver.py file is from https://github.com/podhmo/python-semver -which is licensed under the "MIT" license. See the semver.py file for details. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@jupyterlab/observables 2.4.0 - BSD-3-Clause -https://github.com/jupyterlab/jupyterlab - -Copyright (c) Jupyter Development Team. -Copyright (c) 2015 Project Jupyter Contributors - -Copyright (c) 2015 Project Jupyter Contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -Semver File License -=================== - -The semver.py file is from https://github.com/podhmo/python-semver -which is licensed under the "MIT" license. See the semver.py file for details. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@jupyterlab/services 4.2.0 - BSD-3-Clause -https://github.com/jupyterlab/jupyterlab - -(c) 2011 Gary Court. -Copyright (c) Jupyter Development Team. -Copyright (c) 2015 Project Jupyter Contributors - -Copyright (c) 2015 Project Jupyter Contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -Semver File License -=================== - -The semver.py file is from https://github.com/podhmo/python-semver -which is licensed under the "MIT" license. See the semver.py file for details. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@jupyter-widgets/schema 0.4.0 - BSD-3-Clause -https://github.com/jupyter-widgets/ipywidgets#readme - -Copyright (c) Jupyter Development Team. - -Copyright (c) . All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -@nteract/commutable 7.2.9 - BSD-3-Clause - - -Copyright (c) 2016, nteract contributors - -Copyright (c) 2016, nteract contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of nteract nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@nteract/messaging 7.0.4 - BSD-3-Clause - - -Copyright (c) 2016, nteract contributors - -Copyright (c) 2016, nteract contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of nteract nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@phosphor/algorithm 1.2.0 - BSD-3-Clause -https://github.com/phosphorjs/phosphor - -Copyright (c) 2014-2017, PhosphorJS - -Copyright (c) 2014-2017, PhosphorJS Contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@phosphor/algorithm 1.1.3 - BSD-3-Clause -https://github.com/phosphorjs/phosphor - -Copyright (c) 2014-2017, PhosphorJS - -Copyright (c) . All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -@phosphor/collections 1.2.0 - BSD-3-Clause -https://github.com/phosphorjs/phosphor - -Copyright (c) 2014-2017, PhosphorJS -Copyright (c) 2014-2018, PhosphorJS - -Copyright (c) 2014-2017, PhosphorJS Contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@phosphor/commands 1.7.2 - BSD-3-Clause -https://github.com/phosphorjs/phosphor - -Copyright (c) 2014-2017, PhosphorJS - -Copyright (c) 2014-2017, PhosphorJS Contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@phosphor/coreutils 1.3.1 - BSD-3-Clause -https://github.com/phosphorjs/phosphor - -Copyright (c) 2014-2017, PhosphorJS - -Copyright (c) . All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -@phosphor/disposable 1.3.1 - BSD-3-Clause -https://github.com/phosphorjs/phosphor - -Copyright (c) 2014-2017, PhosphorJS - -Copyright (c) 2014-2017, PhosphorJS Contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@phosphor/disposable 1.2.0 - BSD-3-Clause -https://github.com/phosphorjs/phosphor - -Copyright (c) 2014-2017, PhosphorJS - -Copyright (c) 2014-2017, PhosphorJS Contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@phosphor/domutils 1.1.4 - BSD-3-Clause -https://github.com/phosphorjs/phosphor - -Copyright (c) 2014-2017, PhosphorJS -Copyright (c) 2014-2019, PhosphorJS - -Copyright (c) 2014-2017, PhosphorJS Contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@phosphor/keyboard 1.1.3 - BSD-3-Clause -https://github.com/phosphorjs/phosphor - -Copyright (c) 2014-2017, PhosphorJS - -Copyright (c) . All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -@phosphor/messaging 1.3.0 - BSD-3-Clause -https://github.com/phosphorjs/phosphor - -Copyright (c) 2014-2017, PhosphorJS - -Copyright (c) 2014-2017, PhosphorJS Contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@phosphor/properties 1.1.3 - BSD-3-Clause -https://github.com/phosphorjs/phosphor - -Copyright (c) 2014-2017, PhosphorJS - -Copyright (c) . All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -@phosphor/signaling 1.3.1 - BSD-3-Clause -https://github.com/phosphorjs/phosphor - -Copyright (c) 2014-2017, PhosphorJS - -Copyright (c) 2014-2017, PhosphorJS Contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@phosphor/signaling 1.2.3 - BSD-3-Clause -https://github.com/phosphorjs/phosphor - -Copyright (c) 2014-2017, PhosphorJS - -Copyright (c) . All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -bcrypt-pbkdf 1.0.2 - BSD-3-Clause -https://github.com/joyent/node-bcrypt-pbkdf#readme - -Copyright 2016, Joyent Inc -Copyright (c) 2013 Ted Unangst -Copyright 1997 Niels Provos - -The Blowfish portions are under the following license: - -Blowfish block cipher for OpenBSD -Copyright 1997 Niels Provos -All rights reserved. - -Implementation advice by David Mazieres . - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the author may not be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - -The bcrypt_pbkdf portions are under the following license: - -Copyright (c) 2013 Ted Unangst - -Permission to use, copy, modify, and distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - - -Performance improvements (Javascript-specific): - -Copyright 2016, Joyent Inc -Author: Alex Wilson - -Permission to use, copy, modify, and distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -charenc 0.0.2 - BSD-3-Clause -https://github.com/pvorb/node-charenc#readme - -Copyright (c) 2009, Jeff Mott. -Copyright (c) 2011, Paul Vorbach. - -Copyright (c) . All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -crypt 0.0.2 - BSD-3-Clause -https://github.com/pvorb/node-crypt#readme - -Copyright (c) 2009, Jeff Mott. -Copyright (c) 2011, Paul Vorbach. - -Copyright (c) . All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -duplexer2 0.1.4 - BSD-3-Clause -https://github.com/deoxxa/duplexer2#readme - -Copyright (c) 2013, Deoxxa Development - -Copyright (c) 2013, Deoxxa Development -====================================== -All rights reserved. --------------------- - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. Neither the name of Deoxxa Development nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY DEOXXA DEVELOPMENT ''AS IS'' AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL DEOXXA DEVELOPMENT BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -duplexer3 0.1.4 - BSD-3-Clause -https://github.com/floatdrop/duplexer3 - -Copyright (c) 2013, Deoxxa Development - -Copyright (c) 2013, Deoxxa Development -====================================== -All rights reserved. --------------------- - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. Neither the name of Deoxxa Development nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY DEOXXA DEVELOPMENT ''AS IS'' AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL DEOXXA DEVELOPMENT BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -hoist-non-react-statics 3.3.1 - BSD-3-Clause -https://github.com/mridgway/hoist-non-react-statics#readme - -Copyright 2015, Yahoo! Inc. -Copyright (c) 2015, Yahoo! Inc. - -Software License Agreement (BSD License) -======================================== - -Copyright (c) 2015, Yahoo! Inc. All rights reserved. ----------------------------------------------------- - -Redistribution and use of this software in source and binary forms, with or -without modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of Yahoo! Inc. nor the names of YUI's contributors may be - used to endorse or promote products derived from this software without - specific prior written permission of Yahoo! Inc. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -hoist-non-react-statics 3.3.0 - BSD-3-Clause -https://github.com/mridgway/hoist-non-react-statics#readme - -Copyright 2015, Yahoo! Inc. -Copyright (c) 2015, Yahoo! Inc. - -Software License Agreement (BSD License) -======================================== - -Copyright (c) 2015, Yahoo! Inc. All rights reserved. ----------------------------------------------------- - -Redistribution and use of this software in source and binary forms, with or -without modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of Yahoo! Inc. nor the names of YUI's contributors may be - used to endorse or promote products derived from this software without - specific prior written permission of Yahoo! Inc. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -md5 2.2.1 - BSD-3-Clause -https://github.com/pvorb/node-md5#readme - -Copyright (c) 2009, Jeff Mott. -Copyright (c) 2011-2012, Paul Vorbach. -Copyright (c) 2011-2015, Paul Vorbach. - -Copyright © 2011-2012, Paul Vorbach. -Copyright © 2009, Jeff Mott. - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the documentation and/or - other materials provided with the distribution. -* Neither the name Crypto-JS nor the names of its contributors may be used to - endorse or promote products derived from this software without specific prior - written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -qs 6.5.2 - BSD-3-Clause -https://github.com/ljharb/qs - -Copyright (c) 2014 Nathan LaFreniere and other contributors. - -Copyright (c) 2014 Nathan LaFreniere and other contributors. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The names of any contributors may not be used to endorse or promote - products derived from this software without specific prior written - permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - * * * - -The complete list of contributors can be found at: https://github.com/hapijs/qs/graphs/contributors - - ---------------------------------------------------------- - ---------------------------------------------------------- - -source-map 0.6.1 - BSD-3-Clause -https://github.com/mozilla/source-map - -Copyright 2011 The Closure Compiler -Copyright 2011 Mozilla Foundation and contributors -Copyright 2014 Mozilla Foundation and contributors -Copyright 2009-2011 Mozilla Foundation and contributors -Copyright (c) 2009-2011, Mozilla Foundation and contributors - - -Copyright (c) 2009-2011, Mozilla Foundation and contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the names of the Mozilla Foundation nor the names of project - contributors may be used to endorse or promote products derived from this - software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -source-map 0.5.7 - BSD-3-Clause -https://github.com/mozilla/source-map - -Copyright 2011 The Closure Compiler -Copyright 2011 Mozilla Foundation and contributors -Copyright 2014 Mozilla Foundation and contributors -Copyright 2009-2011 Mozilla Foundation and contributors -Copyright (c) 2009-2011, Mozilla Foundation and contributors - - -Copyright (c) 2009-2011, Mozilla Foundation and contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the names of the Mozilla Foundation nor the names of project - contributors may be used to endorse or promote products derived from this - software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -source-map 0.1.43 - BSD-3-Clause -https://github.com/mozilla/source-map - -Copyright 2011 The Closure Compiler -Copyright 2011 Mozilla Foundation and contributors -Copyright 2012 Mozilla Foundation and contributors -Copyright 2014 Mozilla Foundation and contributors -Copyright 2009-2011 Mozilla Foundation and contributors -Copyright (c) 2009-2011, Mozilla Foundation and contributors - - -Copyright (c) 2009-2011, Mozilla Foundation and contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the names of the Mozilla Foundation nor the names of project - contributors may be used to endorse or promote products derived from this - software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -tough-cookie 2.4.3 - BSD-3-Clause -https://github.com/salesforce/tough-cookie - -Copyright (c) 2015, Salesforce.com, Inc. -Copyright (c) 2018, Salesforce.com, Inc. - -Copyright (c) 2015, Salesforce.com, Inc. -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -3. Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -amdefine 1.0.1 - BSD-3-Clause OR MIT -http://github.com/jrburke/amdefine - -Copyright (c) 2011-2016, The Dojo Foundation - -amdefine is released under two licenses: new BSD, and MIT. You may pick the -license that best suits your development needs. The text of both licenses are -provided below. - - -The "New" BSD License: ----------------------- - -Copyright (c) 2011-2016, The Dojo Foundation -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of the Dojo Foundation nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - -MIT License ------------ - -Copyright (c) 2011-2016, The Dojo Foundation - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -d 1.0.1 - ISC -https://github.com/medikoo/d#readme - -Copyright (c) 2013-2019, Mariusz Nowak, medikoo, medikoo.com - -ISC License - -Copyright (c) 2013-2019, Mariusz Nowak, @medikoo, medikoo.com - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -es5-ext 0.10.50 - ISC -https://github.com/medikoo/es5-ext#readme - -Copyright (c) 2008 Matsuza -Copyright (c) 2011-2019, Mariusz Nowak, medikoo, medikoo.com - -ISC License - -Copyright (c) 2011-2019, Mariusz Nowak, @medikoo, medikoo.com - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -flatted 2.0.1 - ISC -https://github.com/WebReflection/flatted#readme - -(c) 2018, Andrea Giammarchi, (ISC) -Copyright (c) 2018, Andrea Giammarchi, WebReflection - -ISC License - -Copyright (c) 2018, Andrea Giammarchi, @WebReflection - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -fs.realpath 1.0.0 - ISC -https://github.com/isaacs/fs.realpath#readme - -Copyright (c) Isaac Z. Schlueter and Contributors -Copyright Joyent, Inc. and other Node contributors. - -The ISC License - -Copyright (c) Isaac Z. Schlueter and Contributors - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - ----- - -This library bundles a version of the `fs.realpath` and `fs.realpathSync` -methods from Node.js v0.10 under the terms of the Node.js MIT license. - -Node's license follows, also included at the header of `old.js` which contains -the licensed code: - - Copyright Joyent, Inc. and other Node 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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -glob 7.1.4 - ISC -https://github.com/isaacs/node-glob#readme - - -The ISC License - -Copyright (c) Isaac Z. Schlueter and Contributors - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -## Glob Logo - -Glob's logo created by Tanya Brassie , licensed -under a Creative Commons Attribution-ShareAlike 4.0 International License -https://creativecommons.org/licenses/by-sa/4.0/ - - ---------------------------------------------------------- - ---------------------------------------------------------- - -graceful-fs 4.2.0 - ISC -https://github.com/isaacs/node-graceful-fs#readme - -Copyright (c) Isaac Z. Schlueter, Ben Noordhuis, and Contributors - -The ISC License - -Copyright (c) Isaac Z. Schlueter, Ben Noordhuis, and Contributors - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -har-schema 2.0.0 - ISC -https://github.com/ahmadnassri/har-schema - -Copyright (c) 2015, Ahmad Nassri -copyright ahmadnassri.com (https://www.ahmadnassri.com/) - -Copyright (c) 2015, Ahmad Nassri - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -inflight 1.0.6 - ISC -https://github.com/isaacs/inflight - -Copyright (c) Isaac Z. Schlueter - -The ISC License - -Copyright (c) Isaac Z. Schlueter - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -inherits 2.0.4 - ISC -https://github.com/isaacs/inherits#readme - -Copyright (c) Isaac Z. Schlueter - -The ISC License - -Copyright (c) Isaac Z. Schlueter - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -json-stringify-safe 5.0.1 - ISC -https://github.com/isaacs/json-stringify-safe - -Copyright (c) Isaac Z. Schlueter and Contributors - -The ISC License - -Copyright (c) Isaac Z. Schlueter and Contributors - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -lru-cache 4.1.5 - ISC -https://github.com/isaacs/node-lru-cache#readme - -Copyright (c) Isaac Z. Schlueter and Contributors - -The ISC License - -Copyright (c) Isaac Z. Schlueter and Contributors - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -minimalistic-assert 1.0.1 - ISC -https://github.com/calvinmetcalf/minimalistic-assert - -Copyright 2015 Calvin Metcalf - -Copyright 2015 Calvin Metcalf - -Permission to use, copy, modify, and/or distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -minimatch 3.0.4 - ISC -https://github.com/isaacs/minimatch#readme - -Copyright (c) Isaac Z. Schlueter and Contributors - -The ISC License - -Copyright (c) Isaac Z. Schlueter and Contributors - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -once 1.4.0 - ISC -https://github.com/isaacs/once#readme - -Copyright (c) Isaac Z. Schlueter and Contributors - -The ISC License - -Copyright (c) Isaac Z. Schlueter and Contributors - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -path-posix 1.0.0 - ISC -https://github.com/jden/node-path-posix - -Copyright Joyent, Inc. and other Node contributors. - -Node's license follows: - -==== - -Copyright Joyent, Inc. and other Node contributors. All rights reserved. -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. - -==== - - ---------------------------------------------------------- - ---------------------------------------------------------- - -pseudomap 1.0.2 - ISC -https://github.com/isaacs/pseudomap#readme - -Copyright (c) Isaac Z. Schlueter and Contributors - -The ISC License - -Copyright (c) Isaac Z. Schlueter and Contributors - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -sax 1.2.4 - ISC -https://github.com/isaacs/sax-js#readme - -Copyright (c) Isaac Z. Schlueter and Contributors -Copyright Mathias Bynens - -The ISC License - -Copyright (c) Isaac Z. Schlueter and Contributors - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -==== - -`String.fromCodePoint` by Mathias Bynens used according to terms of MIT -License, as follows: - - Copyright Mathias Bynens - - 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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -semver 6.3.0 - ISC -https://github.com/npm/node-semver#readme - -Copyright Isaac Z. -Copyright Isaac Z. Schlueter -Copyright (c) Isaac Z. Schlueter and Contributors - -The ISC License - -Copyright (c) Isaac Z. Schlueter and Contributors - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -semver 5.7.0 - ISC -https://github.com/npm/node-semver#readme - -Copyright Isaac Z. -Copyright Isaac Z. Schlueter -Copyright (c) Isaac Z. Schlueter and Contributors - -The ISC License - -Copyright (c) Isaac Z. Schlueter and Contributors - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -type 1.0.1 - ISC -https://github.com/medikoo/type#readme - - -ISC License - -Copyright (c) 2004-2010 by Internet Systems Consortium, Inc. ("ISC") - -Copyright (c) 1995-2003 by Internet Software Consortium - -Permission to use, copy, modify, and /or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -wrappy 1.0.2 - ISC -https://github.com/npm/wrappy - -Copyright (c) Isaac Z. Schlueter and Contributors - -The ISC License - -Copyright (c) Isaac Z. Schlueter and Contributors - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -yallist 2.1.2 - ISC -https://github.com/isaacs/yallist#readme - -Copyright (c) Isaac Z. Schlueter and Contributors - -The ISC License - -Copyright (c) Isaac Z. Schlueter and Contributors - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -sanitize-filename 1.6.3 - ISC OR WTFPL OR (ISC AND WTFPL) -https://github.com/parshap/node-sanitize-filename#readme - -Copyright (c) 2004 Sam Hocevar - -This project is licensed under the [WTFPL][] and [ISC][] licenses. - -[WTFPL]: https://en.wikipedia.org/wiki/WTFPL -[ISC]: https://opensource.org/licenses/ISC - -## WTFPL - -DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE -Version 2, December 2004 - -Copyright (C) 2004 Sam Hocevar \ - -Everyone is permitted to copy and distribute verbatim or modified copies -of this license document, and changing it is allowed as long as the name -is changed. - -DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE TERMS AND CONDITIONS FOR -COPYING, DISTRIBUTION AND MODIFICATION - -0. You just DO WHAT THE FUCK YOU WANT TO. - -## ISC - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@babel/runtime 7.5.4 - MIT - - -Copyright (c) 2014-present Sebastian McKenzie and other contributors - -MIT License - -Copyright (c) 2014-present Sebastian McKenzie 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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@babel/runtime 7.8.3 - MIT -https://babeljs.io/docs/en/next/babel-runtime - -Copyright (c) 2014-present Sebastian McKenzie and other contributors - -MIT License - -Copyright (c) 2014-present Sebastian McKenzie 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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@loadable/component 5.12.0 - MIT -https://github.com/gregberge/loadable-components#readme - -Copyright 2019 Greg Berge - -Copyright 2019 Greg Bergé - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@nteract/types 6.0.4 - MIT - - -Copyright (c) 2016, nteract contributors - -Copyright (c) 2016, nteract contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of nteract nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@sindresorhus/is 0.7.0 - MIT -https://github.com/sindresorhus/is#readme - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@sindresorhus/is 0.14.0 - MIT -https://github.com/sindresorhus/is#readme - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@szmarczak/http-timer 1.1.2 - MIT -https://github.com/szmarczak/http-timer#readme - -Copyright (c) 2018 Szymon Marczak - -MIT License - -Copyright (c) 2018 Szymon Marczak - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@types/node 10.17.29 - MIT - - -Copyright (c) Microsoft Corporation. - - MIT License - - Copyright (c) Microsoft Corporation. - - 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 - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@types/tcp-port-used 1.0.0 - MIT - - -Copyright (c) Microsoft Corporation. - - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - 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 - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@types/uuid 3.4.5 - MIT - - -Copyright (c) Microsoft Corporation. - - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - 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 - - ---------------------------------------------------------- - ---------------------------------------------------------- - -@types/uuid 7.0.2 - MIT - - -Copyright (c) Microsoft Corporation. - - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - 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 - - ---------------------------------------------------------- - ---------------------------------------------------------- - -acorn 6.4.1 - MIT -https://github.com/acornjs/acorn - -Copyright (c) 2012-2018 by various contributors - -Copyright (C) 2012-2018 by various contributors (see AUTHORS) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -acorn 7.1.1 - MIT -https://github.com/acornjs/acorn - -Copyright (c) 2012-2018 by various contributors - -Copyright (C) 2012-2018 by various contributors (see AUTHORS) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -acorn-dynamic-import 4.0.0 - MIT -https://github.com/kesne/acorn-dynamic-import - -Copyright (c) 2016 Jordan Gensler - -MIT License - -Copyright (c) 2016 Jordan Gensler - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -acorn-walk 6.2.0 - MIT -https://github.com/acornjs/acorn - -Copyright (c) 2012-2018 by various contributors - -Copyright (C) 2012-2018 by various contributors (see AUTHORS) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -aggregate-error 3.0.1 - MIT -https://github.com/sindresorhus/aggregate-error#readme - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -ajv 6.10.1 - MIT -https://github.com/epoberezkin/ajv - -(c) 2011 Gary Court. -Copyright 2011 Gary Court. -Copyright (c) 2015-2017 Evgeny Poberezkin - -The MIT License (MIT) - -Copyright (c) 2015-2017 Evgeny Poberezkin - -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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -ansi-regex 4.1.0 - MIT -https://github.com/chalk/ansi-regex#readme - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -applicationinsights 1.7.4 - MIT -https://github.com/Microsoft/ApplicationInsights-node.js#readme - -Copyright (c) Microsoft Corporation. - -The MIT License (MIT) -Copyright © Microsoft Corporation - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -arch 2.1.1 - MIT -https://github.com/feross/arch - -Copyright (c) Feross Aboukhadijeh -Copyright (c) Feross Aboukhadijeh (http://feross.org). - -The MIT License (MIT) - -Copyright (c) Feross Aboukhadijeh - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -array-from 2.1.1 - MIT -https://github.com/studio-b12/array-from#readme - -(c) Studio B12 GmbH -Copyright (c) 2015-2016 Studio B12 GmbH - -Copyright © 2015-2016 Studio B12 GmbH - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -asn1 0.2.4 - MIT -https://github.com/joyent/node-asn1#readme - -Copyright (c) 2011 Mark Cavage -Copyright 2011 Mark Cavage - -Copyright (c) 2011 Mark Cavage, All rights reserved. - -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 - - ---------------------------------------------------------- - ---------------------------------------------------------- - -assert-plus 1.0.0 - MIT -https://github.com/mcavage/node-assert-plus#readme - -Copyright 2015 Joyent, Inc. -Copyright (c) 2012 Mark Cavage -Copyright (c) 2012, Mark Cavage. - -MIT License - -Copyright (c) - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -ast-transform 0.0.0 - MIT -https://github.com/hughsk/ast-transform - -Copyright (c) 2014 Hugh Kennedy - -## The MIT License (MIT) ## - -Copyright (c) 2014 Hugh Kennedy - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -ast-types 0.7.8 - MIT -http://github.com/benjamn/ast-types - -Copyright (c) 2013 Ben Newman - -Copyright (c) 2013 Ben Newman - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -async 2.6.2 - MIT -https://caolan.github.io/async/ - -Copyright (c) 2010-2018 Caolan McMahon - -Copyright (c) 2010-2018 Caolan McMahon - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -async-hook-jl 1.7.6 - MIT -https://github.com/jeff-lewis/async-hook-jl#readme - -Copyright (c) 2015 Andreas Madsen - -Copyright (c) 2015 Andreas Madsen - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -asynckit 0.4.0 - MIT -https://github.com/alexindigo/asynckit#readme - -Copyright (c) 2016 Alex Indigo - -The MIT License (MIT) - -Copyright (c) 2016 Alex Indigo - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -async-limiter 1.0.0 - MIT -https://github.com/strml/async-limiter#readme - -Copyright (c) 2017 Samuel Reed - -The MIT License (MIT) -Copyright (c) 2017 Samuel Reed - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -aws4 1.8.0 - MIT -https://github.com/mhart/aws4#readme - -Copyright 2013 Michael Hart (michael.hart.au@gmail.com) - -Copyright 2013 Michael Hart (michael.hart.au@gmail.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -axios 0.19.2 - MIT -https://github.com/axios/axios - -Copyright (c) 2014-present Matt Zabriskie - -Copyright (c) 2014-present Matt Zabriskie - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -babel-runtime 6.26.0 - MIT - - - -MIT License - -Copyright (c) - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -balanced-match 1.0.0 - MIT -https://github.com/juliangruber/balanced-match - -Copyright (c) 2013 Julian Gruber - -(MIT) - -Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -base64-js 0.0.8 - MIT -https://github.com/beatgammit/base64-js - -Copyright (c) 2014 - -The MIT License (MIT) - -Copyright (c) 2014 - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -base64-js 1.3.0 - MIT -https://github.com/beatgammit/base64-js - -Copyright (c) 2014 - -The MIT License (MIT) - -Copyright (c) 2014 - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -brace-expansion 1.1.11 - MIT -https://github.com/juliangruber/brace-expansion - -Copyright (c) 2013 Julian Gruber - -MIT License - -Copyright (c) 2013 Julian Gruber - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -brfs 1.6.1 - MIT -https://github.com/substack/brfs - - -This software is released under the MIT license: - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -brfs 2.0.2 - MIT -https://github.com/substack/brfs - - -This software is released under the MIT license: - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -brotli 1.3.2 - MIT -https://github.com/devongovett/brotli.js - -Copyright 2013 Google Inc. - -MIT License - -Copyright (c) - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -browserify-mime 1.2.9 - MIT - - -Copyright (c) 2010 Benjamin Thomas, Robert Kieffer - -Copyright (c) 2010 Benjamin Thomas, Robert Kieffer - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -browserify-optional 1.0.1 - MIT -https://github.com/devongovett/browserify-optional - - -MIT License - -Copyright (c) - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -browser-resolve 1.11.3 - MIT -https://github.com/shtylman/node-browser-resolve#readme - -Copyright (c) 2013-2015 Roman Shtylman - -The MIT License (MIT) - -Copyright (c) 2013-2015 Roman Shtylman - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -buffer-equal 0.0.1 - MIT -https://github.com/substack/node-buffer-equal - - -MIT License - -Copyright (c) - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -buffer-from 1.1.1 - MIT -https://github.com/LinusU/buffer-from#readme - -Copyright (c) 2016, 2018 Linus Unneback - -MIT License - -Copyright (c) 2016, 2018 Linus Unnebäck - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -cacheable-request 2.1.4 - MIT -https://github.com/lukechilds/cacheable-request - -(c) Luke Childs -Copyright (c) 2017 Luke Childs - -MIT License - -Copyright (c) 2017 Luke Childs - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -cacheable-request 6.1.0 - MIT -https://github.com/lukechilds/cacheable-request#readme - -(c) Luke Childs -Copyright (c) 2017 Luke Childs - -MIT License - -Copyright (c) 2017 Luke Childs - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -classnames 2.2.6 - MIT -https://github.com/JedWatson/classnames#readme - -Copyright (c) 2017 Jed Watson. - -The MIT License (MIT) - -Copyright (c) 2017 Jed Watson - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -clean-stack 2.2.0 - MIT -https://github.com/sindresorhus/clean-stack#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -clone 1.0.4 - MIT -https://github.com/pvorb/node-clone#readme - -Copyright (c) 2011-2015 Paul Vorbach -Copyright (c) 2011-2015 Paul Vorbach (http://paul.vorba.ch/) and contributors (https://github.com/pvorb/node-clone/graphs/contributors). - -Copyright © 2011-2015 Paul Vorbach - -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, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -clone-response 1.0.2 - MIT -https://github.com/lukechilds/clone-response - -(c) Luke Childs -Copyright (c) 2017 Luke Childs - -MIT License - -Copyright (c) 2017 Luke Childs - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -color 3.0.0 - MIT -https://github.com/Qix-/color#readme - -Copyright (c) 2012 Heather Arthur - -Copyright (c) 2012 Heather Arthur - -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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -color-convert 1.9.3 - MIT -https://github.com/Qix-/color-convert#readme - -Copyright (c) 2011-2016, Heather Arthur and Josh Junon. -Copyright (c) 2011-2016 Heather Arthur - -Copyright (c) 2011-2016 Heather Arthur - -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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -color-name 1.1.3 - MIT -https://github.com/dfcreative/color-name - -Copyright (c) 2015 Dmitry Ivanov - -The MIT License (MIT) -Copyright (c) 2015 Dmitry Ivanov - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -colornames 1.1.1 - MIT -https://github.com/timoxley/colornames#readme - -Copyright (c) 2015 Tim Oxley - -The MIT License (MIT) - -Copyright (c) 2015 Tim Oxley - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -colors 1.3.3 - MIT -https://github.com/Marak/colors.js - -Copyright (c) Marak Squires -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Original Library - - Copyright (c) Marak Squires - -Additional Functionality - - Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -colorspace 1.1.2 - MIT -https://github.com/3rd-Eden/colorspace - -Copyright (c) 2015 Arnout Kazemier, Martijn Swaagman - -The MIT License (MIT) - -Copyright (c) 2015 Arnout Kazemier, Martijn Swaagman, the 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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -color-string 1.5.3 - MIT -https://github.com/Qix-/color-string#readme - -Copyright (c) 2011 Heather Arthur - -Copyright (c) 2011 Heather Arthur - -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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -combined-stream 1.0.8 - MIT -https://github.com/felixge/node-combined-stream - -Copyright (c) 2011 Debuggable Limited - -Copyright (c) 2011 Debuggable Limited - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -concat-map 0.0.1 - MIT -https://github.com/substack/node-concat-map - - -This software is released under the MIT license: - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -concat-stream 1.6.2 - MIT -https://github.com/maxogden/concat-stream#readme - -Copyright (c) 2013 Max Ogden - -The MIT License - -Copyright (c) 2013 Max Ogden - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -convert-source-map 1.6.0 - MIT -https://github.com/thlorenz/convert-source-map - -Copyright 2013 Thorsten Lorenz. - -Copyright 2013 Thorsten Lorenz. -All rights reserved. - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -core-js 1.2.7 - MIT -https://github.com/zloirock/core-js#readme - -(c) 2016 Denis Pushkarev -Copyright (c) 2015 Denis Pushkarev - -Copyright (c) 2015 Denis Pushkarev - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -core-js 2.6.9 - MIT -https://github.com/zloirock/core-js#readme - -(c) 2019 Denis Pushkarev -copyright (c) 2019 Denis Pushkarev -Copyright (c) 2014-2019 Denis Pushkarev - -Copyright (c) 2014-2019 Denis Pushkarev - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -core-util-is 1.0.2 - MIT -https://github.com/isaacs/core-util-is#readme - -Copyright Joyent, Inc. and other Node contributors. - -Copyright Node.js contributors. All rights reserved. - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -crypto-js 3.3.0 - MIT -http://github.com/brix/crypto-js - -(c) 2012 by Cedric Mesnil. -Copyright (c) 2009-2013 Jeff Mott -Copyright (c) 2013-2016 Evan Vosberg - -# License - -[The MIT License (MIT)](http://opensource.org/licenses/MIT) - -Copyright (c) 2009-2013 Jeff Mott -Copyright (c) 2013-2016 Evan Vosberg - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -dashdash 1.14.1 - MIT -https://github.com/trentm/node-dashdash#readme - -Copyright 2016 Trent Mick -Copyright 2016 Joyent, Inc. -Copyright (c) 2013 Joyent Inc. -Copyright (c) 2013 Trent Mick. - -# This is the MIT license - -Copyright (c) 2013 Trent Mick. All rights reserved. -Copyright (c) 2013 Joyent Inc. All rights reserved. - -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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -date-format 2.1.0 - MIT -https://github.com/nomiddlename/date-format#readme - -Copyright (c) 2013 Gareth Jones - -The MIT License (MIT) - -Copyright (c) 2013 Gareth Jones - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -date-format 3.0.0 - MIT -https://github.com/nomiddlename/date-format#readme - -Copyright (c) 2013 Gareth Jones - -The MIT License (MIT) - -Copyright (c) 2013 Gareth Jones - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -debug 3.1.0 - MIT -https://github.com/visionmedia/debug#readme - -Copyright (c) 2014 TJ Holowaychuk -Copyright (c) 2014-2017 TJ Holowaychuk - -(The MIT License) - -Copyright (c) 2014 TJ Holowaychuk - -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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -debug 2.6.9 - MIT -https://github.com/visionmedia/debug#readme - -Copyright (c) 2014 TJ Holowaychuk -Copyright (c) 2014-2016 TJ Holowaychuk - -(The MIT License) - -Copyright (c) 2014 TJ Holowaychuk - -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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -debug 3.2.6 - MIT -https://github.com/visionmedia/debug#readme - -Copyright (c) 2014 TJ Holowaychuk -Copyright (c) 2014-2017 TJ Holowaychuk - -(The MIT License) - -Copyright (c) 2014 TJ Holowaychuk - -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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -debug 4.1.1 - MIT -https://github.com/visionmedia/debug#readme - -Copyright (c) 2014 TJ Holowaychuk -Copyright (c) 2014-2017 TJ Holowaychuk - -(The MIT License) - -Copyright (c) 2014 TJ Holowaychuk - -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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -debug 4.1.0 - MIT -https://github.com/visionmedia/debug#readme - -Copyright (c) 2014 TJ Holowaychuk -Copyright (c) 2014-2017 TJ Holowaychuk - -(The MIT License) - -Copyright (c) 2014 TJ Holowaychuk - -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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -decode-uri-component 0.2.0 - MIT -https://github.com/samverschueren/decode-uri-component#readme - -(c) Sam Verschueren (https://github.com/SamVerschueren) -Copyright (c) Sam Verschueren - -The MIT License (MIT) - -Copyright (c) Sam Verschueren (github.com/SamVerschueren) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -decompress-response 3.3.0 - MIT -https://github.com/sindresorhus/decompress-response#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -`The MIT License (MIT) - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -deep-equal 1.0.1 - MIT -https://github.com/substack/node-deep-equal#readme - - -This software is released under the MIT license: - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -deep-is 0.1.3 - MIT -https://github.com/thlorenz/deep-is - -Copyright (c) 2009 Thomas Robinson <280north.com> -Copyright (c) 2012 James Halliday -Copyright (c) 2012, 2013 Thorsten Lorenz - -Copyright (c) 2012, 2013 Thorsten Lorenz -Copyright (c) 2012 James Halliday -Copyright (c) 2009 Thomas Robinson <280north.com> - -This software is released under the MIT license: - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -defer-to-connect 1.1.3 - MIT -https://github.com/szmarczak/defer-to-connect#readme - -Copyright (c) 2018 Szymon Marczak - -MIT License - -Copyright (c) 2018 Szymon Marczak - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -delayed-stream 1.0.0 - MIT -https://github.com/felixge/node-delayed-stream - -Copyright (c) 2011 Debuggable Limited - -Copyright (c) 2011 Debuggable Limited - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -detect-indent 6.0.0 - MIT -https://github.com/sindresorhus/detect-indent#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -dfa 1.2.0 - MIT -https://github.com/devongovett/dfa#readme - - -MIT License - -Copyright (c) - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -diagnostic-channel 0.2.0 - MIT -https://github.com/Microsoft/node-diagnostic-channel - -Copyright (c) Microsoft Corporation. - - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - 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 - - ---------------------------------------------------------- - ---------------------------------------------------------- - -diagnostic-channel-publishers 0.3.4 - MIT -https://github.com/Microsoft/node-diagnostic-channel - -Copyright (c) Microsoft Corporation. - - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - 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 - - ---------------------------------------------------------- - ---------------------------------------------------------- - -diagnostics 1.1.1 - MIT -https://github.com/bigpipe/diagnostics - -Copyright (c) 2015 Arnout Kazemier, Martijn Swaagman - -The MIT License (MIT) - -Copyright (c) 2015 Arnout Kazemier, Martijn Swaagman, the 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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -dns-packet 5.2.1 - MIT -https://github.com/mafintosh/dns-packet - -Copyright (c) 2016 Mathias Buus - -The MIT License (MIT) - -Copyright (c) 2016 Mathias Buus - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -dns-socket 4.2.0 - MIT -https://github.com/mafintosh/dns-socket - -Copyright (c) 2016 Mathias Buus - -The MIT License (MIT) - -Copyright (c) 2016 Mathias Buus - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -ecc-jsbn 0.1.2 - MIT -https://github.com/quartzjer/ecc-jsbn - -Copyright (c) 2003-2005 Tom Wu -Copyright (c) 2014 Jeremie Miller - -The MIT License (MIT) - -Copyright (c) 2014 Jeremie Miller - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -enabled 1.0.2 - MIT -https://github.com/bigpipe/enabled#readme - -Copyright (c) 2015 Arnout Kazemier, Martijn Swaagman - -The MIT License (MIT) - -Copyright (c) 2015 Arnout Kazemier, Martijn Swaagman, the 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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -end-of-stream 1.4.1 - MIT -https://github.com/mafintosh/end-of-stream - -Copyright (c) 2014 Mathias Buus - -The MIT License (MIT) - -Copyright (c) 2014 Mathias Buus - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -env-variable 0.0.5 - MIT -https://github.com/3rd-Eden/env-variable - -Copyright 2014 Arnout Kazemier - -Copyright 2014 Arnout Kazemier - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -es6-iterator 2.0.3 - MIT -https://github.com/medikoo/es6-iterator#readme - -Copyright (c) 2013-2017 Mariusz Nowak (www.medikoo.com) - -The MIT License (MIT) - -Copyright (C) 2013-2017 Mariusz Nowak (www.medikoo.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -es6-map 0.1.5 - MIT -https://github.com/medikoo/es6-map#readme - -Copyright (c) 2013 Mariusz Nowak (www.medikoo.com) - -Copyright (C) 2013 Mariusz Nowak (www.medikoo.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -es6-set 0.1.5 - MIT -https://github.com/medikoo/es6-set#readme - -Copyright (c) 2013 Mariusz Nowak (www.medikoo.com) - -Copyright (C) 2013 Mariusz Nowak (www.medikoo.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -es6-symbol 3.1.1 - MIT -https://github.com/medikoo/es6-symbol#readme - -Copyright (c) 2013-2015 Mariusz Nowak (www.medikoo.com) - -Copyright (C) 2013-2015 Mariusz Nowak (www.medikoo.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -event-emitter 0.3.5 - MIT -https://github.com/medikoo/event-emitter#readme - -Copyright (c) 2012-2015 Mariusz Nowak (www.medikoo.com) - -Copyright (C) 2012-2015 Mariusz Nowak (www.medikoo.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -extend 3.0.2 - MIT -https://github.com/justmoon/node-extend#readme - -Copyright (c) 2014 Stefan Thomas - -The MIT License (MIT) - -Copyright (c) 2014 Stefan Thomas - -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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -extsprintf 1.3.0 - MIT -https://github.com/davepacheco/node-extsprintf - -Copyright (c) 2012, Joyent, Inc. - -Copyright (c) 2012, Joyent, Inc. All rights reserved. - -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 - - ---------------------------------------------------------- - ---------------------------------------------------------- - -falafel 2.2.0 - MIT -https://github.com/substack/node-falafel#readme - - -MIT License - -Copyright (c) - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -fast-deep-equal 2.0.1 - MIT -https://github.com/epoberezkin/fast-deep-equal#readme - -Copyright (c) 2017 Evgeny Poberezkin - -MIT License - -Copyright (c) 2017 Evgeny Poberezkin - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -fast-json-stable-stringify 2.0.0 - MIT -https://github.com/epoberezkin/fast-json-stable-stringify - - -This software is released under the MIT license: - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -fast-levenshtein 2.0.6 - MIT -https://github.com/hiddentao/fast-levenshtein#readme - -Copyright (c) 2013 Ramesh Nair (http://www.hiddentao.com/) - -(MIT License) - -Copyright (c) 2013 [Ramesh Nair](http://www.hiddentao.com/) - -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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -fast-safe-stringify 2.0.6 - MIT -https://github.com/davidmarkclements/fast-safe-stringify#readme - -Copyright (c) 2016 David Mark Clements -Copyright (c) 2017 David Mark Clements & Matteo Collina -Copyright (c) 2018 David Mark Clements, Matteo Collina & Ruben Bridgewater - -The MIT License (MIT) - -Copyright (c) 2016 David Mark Clements -Copyright (c) 2017 David Mark Clements & Matteo Collina -Copyright (c) 2018 David Mark Clements, Matteo Collina & Ruben Bridgewater - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -fecha 2.3.3 - MIT -https://github.com/taylorhakes/fecha - -Copyright (c) 2015 Taylor Hakes - -The MIT License (MIT) - -Copyright (c) 2015 Taylor Hakes - -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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -follow-redirects 1.5.10 - MIT -https://github.com/follow-redirects/follow-redirects - -Copyright 2014-present Olivier Lalonde , James Talmage , Ruben Verborgh - -Copyright 2014–present Olivier Lalonde , James Talmage , Ruben Verborgh - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -fontkit 1.8.0 - MIT - - - -MIT License - -Copyright (c) - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -foreach 2.0.5 - MIT -https://github.com/manuelstofer/foreach - -Copyright (c) 2013 Manuel Stofer - -The MIT License - -Copyright (c) 2013 Manuel Stofer - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -form-data 2.3.3 - MIT -https://github.com/form-data/form-data#readme - -Copyright (c) 2012 Felix Geisendorfer (felix@debuggable.com) and contributors - -Copyright (c) 2012 Felix Geisendörfer (felix@debuggable.com) and 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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -from2 2.3.0 - MIT -https://github.com/hughsk/from2 - -Copyright (c) 2014 Hugh Kennedy - -## The MIT License (MIT) ## - -Copyright (c) 2014 Hugh Kennedy - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -fs-extra 4.0.3 - MIT -https://github.com/jprichardson/node-fs-extra - -Copyright (c) 2011-2017 JP Richardson -Copyright (c) 2011-2017 JP Richardson (https://github.com/jprichardson) -Copyright (c) 2014-2016 Jonathan Ong me@jongleberry.com and Contributors - -(The MIT License) - -Copyright (c) 2011-2017 JP Richardson - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -fs-extra 8.1.0 - MIT -https://github.com/jprichardson/node-fs-extra - -Copyright (c) 2011-2017 JP Richardson -Copyright (c) 2011-2017 JP Richardson (https://github.com/jprichardson) -Copyright (c) 2014-2016 Jonathan Ong me@jongleberry.com and Contributors - -(The MIT License) - -Copyright (c) 2011-2017 JP Richardson - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -function-bind 1.1.1 - MIT -https://github.com/Raynos/function-bind - -Copyright (c) 2013 Raynos. - -Copyright (c) 2013 Raynos. - -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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -future 0.18.2 - MIT - - -Copyright 2006 Google, Inc. -Copyright 2013 by the Jinja team -Copyright (c) 2013 - Damian Avila -Copyright 2008 by Armin Ronacher. -Copyright (c) 2000 Bastian Kleineidam -Copyright (c) 2010 by Armin Ronacher. -Copyright (c) 1999-2002 by Fredrik Lundh. -Copyright (c) 1999-2002 by Secret Labs AB. -Copyright 2013-2019 Python Charmers Pty Ltd -copyright u'2013-2019, Python Charmers Pty Ltd -Copyright (c) 2013-2019 Python Charmers Pty Ltd -Copyright (c) 2001-2006 Python Software Foundation -Copyright (c) 2001-2007 Python Software Foundation -Copyright (c) 2001-2010 Python Software Foundation -Copyright (c) 2002-2006 Python Software Foundation -Copyright (c) 2002-2007 Python Software Foundation -Copyright (c) 2004-2006 Python Software Foundation -Copyright 2000 by Timothy O'Malley -Copyright 2011 by Armin Ronacher. :license Flask Design License -Copyright (c) 2000 Luke Kenneth Casson Leighton -Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Python Software Foundation. - -Copyright (c) 2013-2019 Python Charmers Pty Ltd, Australia - -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. - - -Copyright (c) 2010 by Armin Ronacher. - -Some rights reserved. - -Redistribution and use in source and binary forms of the theme, with or -without modification, are permitted provided that the following conditions -are met: - -* Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - -* The names of the contributors may not be used to endorse or - promote products derived from this software without specific - prior written permission. - -We kindly ask you to only use these themes in an unmodified manner just -for Flask and Flask-related products, not for unrelated projects. If you -like the visual style and want to use it for your own projects, please -consider making some larger changes to the themes (such as changing -font faces, sizes, colors or margins). - -THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -fuzzy 0.1.3 - MIT -https://github.com/mattyork/fuzzy - -Copyright (c) 2012 Matt York -Copyright (c) 2015 Matt York - -Copyright (c) 2012 Matt York - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -getpass 0.1.7 - MIT -https://github.com/arekinath/node-getpass#readme - -Copyright Joyent, Inc. -Copyright 2016, Joyent, Inc. - -Copyright Joyent, Inc. All rights reserved. -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -get-port 3.2.0 - MIT -https://github.com/sindresorhus/get-port#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -get-stream 3.0.0 - MIT -https://github.com/sindresorhus/get-stream#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -The MIT License (MIT) - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -get-stream 4.1.0 - MIT -https://github.com/sindresorhus/get-stream#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -get-stream 5.1.0 - MIT -https://github.com/sindresorhus/get-stream#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -got 9.6.0 - MIT -https://github.com/sindresorhus/got#readme - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -got 8.3.2 - MIT -https://github.com/sindresorhus/got#readme - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -har-validator 5.1.3 - MIT -https://github.com/ahmadnassri/node-har-validator - -Copyright (c) 2018 Ahmad Nassri - -MIT License - -Copyright (c) 2018 Ahmad Nassri - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -has 1.0.3 - MIT -https://github.com/tarruda/has - -Copyright (c) 2013 Thiago de Arruda - -Copyright (c) 2013 Thiago de Arruda - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -hash.js 1.1.7 - MIT -https://github.com/indutny/hash.js - -Copyright Fedor Indutny, 2014. - -MIT License - -Copyright (c) - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -hash-base 3.0.4 - MIT -https://github.com/crypto-browserify/hash-base - -Copyright (c) 2016 Kirill Fomichev - -The MIT License (MIT) - -Copyright (c) 2016 Kirill Fomichev - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -has-symbol-support-x 1.4.2 - MIT -https://github.com/Xotic750/has-symbol-support-x - -Copyright (c) 2015-present -Copyright (c) 2015-present Graham Fairweather. - -https://opensource.org/licenses/MIT - -Copyright (c) 2015-present Graham Fairweather. - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -has-to-string-tag-x 1.4.1 - MIT -https://github.com/Xotic750/has-to-string-tag-x - -Copyright (c) 2015-2017 -Copyright (c) 2015-2017 Graham Fairweather. - -https://opensource.org/licenses/MIT - -Copyright (c) 2015-2017 Graham Fairweather. - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -http-signature 1.2.0 - MIT -https://github.com/joyent/node-http-signature/ - -Copyright Joyent, Inc. -Copyright 2012 Joyent, Inc. -Copyright 2015 Joyent, Inc. -Copyright (c) 2011 Joyent, Inc. - -Copyright Joyent, Inc. All rights reserved. -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -iconv-lite 0.4.24 - MIT -https://github.com/ashtuchkin/iconv-lite - -Copyright (c) Microsoft Corporation. -Copyright (c) 2011 Alexander Shtuchkin - -Copyright (c) 2011 Alexander Shtuchkin - -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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -immutable 4.0.0-rc.12 - MIT -https://facebook.github.com/immutable-js - -Copyright (c) 2014-present, Facebook, Inc. - -MIT License - -Copyright (c) 2014-present, Facebook, Inc. - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -indent-string 4.0.0 - MIT -https://github.com/sindresorhus/indent-string#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -into-stream 3.1.0 - MIT -https://github.com/sindresorhus/into-stream#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -The MIT License (MIT) - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -inversify 4.13.0 - MIT -http://inversify.io/ - -Copyright (c) 2015-2017 Remo H. Jansen -Copyright (c) 2015-2017 Remo H. Jansen (http://www.remojansen.com) - -The MIT License (MIT) - -Copyright (c) 2015-2017 Remo H. Jansen - -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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -ip 1.1.5 - MIT -https://github.com/indutny/node-ip - -Copyright Fedor Indutny, 2012. - -MIT License - -Copyright (c) - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -ip-regex 2.1.0 - MIT -https://github.com/sindresorhus/ip-regex#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -The MIT License (MIT) - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -ip-regex 4.1.0 - MIT -https://github.com/sindresorhus/ip-regex#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -is2 2.0.1 - MIT -http://github.com/stdarg/is2 - -Copyright (c) 2011 Enrico Marino -Copyright (c) 2013 Edmond Meinfelder -Copyright (c) 2013,2014 Edmond Meinfelder -Copyright (c) 2011 Enrico Marino -Copyright (c) 2013,2014 Edmond Meinfelder - -The MIT License (MIT) - -Copyright (c) 2013 Edmond Meinfelder - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -isarray 0.0.1 - MIT -https://github.com/juliangruber/isarray - -Copyright (c) 2013 Julian Gruber - -MIT License - -Copyright (c) - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -isarray 1.0.0 - MIT -https://github.com/juliangruber/isarray - -Copyright (c) 2013 Julian Gruber - -MIT License - -Copyright (c) - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -is-arrayish 0.3.2 - MIT -https://github.com/qix-/node-is-arrayish#readme - -Copyright (c) 2015 JD Ballard - -The MIT License (MIT) - -Copyright (c) 2015 JD Ballard - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -is-buffer 1.1.6 - MIT -https://github.com/feross/is-buffer#readme - -Copyright (c) Feross Aboukhadijeh -Copyright (c) Feross Aboukhadijeh (http://feross.org). - -The MIT License (MIT) - -Copyright (c) Feross Aboukhadijeh - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -is-ip 2.0.0 - MIT -https://github.com/sindresorhus/is-ip#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -The MIT License (MIT) - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -is-ip 3.1.0 - MIT -https://github.com/sindresorhus/is-ip#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -is-object 1.0.1 - MIT -https://github.com/ljharb/is-object - -Copyright (c) 2013 Colingo. - -Copyright (c) 2013 Colingo. - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -is-online 8.2.1 - MIT -https://github.com/sindresorhus/is-online#readme - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -isort 5.5.3 - MIT - - -Copyright 2017 Jack Evans -Copyright 2018 Google LLC -Copyright 2019 Google LLC -Copyright 2011 VMware, Inc -Copyright 2016 Google Inc. -Copyright 2017 Nate Prewitt -Copyright 2017 Samuel Vasko -Copyright 2013 Red Hat, Inc. -Copyright 2019 Filippo Broggini -Copyright 2015-2016 Julien Enselme -Copyright 2013-2019 William Pearson -Copyright (c) 2009-2018, Marcel Hellkamp. -Copyright (c) 2013 Timothy Edmund Crosley -Copyright (c) 2016 Timothy Edmund Crosley Under - -The MIT License (MIT) - -Copyright (c) 2013 Timothy Edmund Crosley - -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. - - -The MIT License - -Copyright 2013-2019 William Pearson -Copyright 2015-2016 Julien Enselme -Copyright 2016 Google Inc. -Copyright 2017 Samuel Vasko -Copyright 2017 Nate Prewitt -Copyright 2017 Jack Evans -Copyright 2019 Filippo Broggini - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -is-plain-obj 1.1.0 - MIT -https://github.com/sindresorhus/is-plain-obj - -(c) Sindre Sorhus (http://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -The MIT License (MIT) - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -is-retry-allowed 1.1.0 - MIT -https://github.com/floatdrop/is-retry-allowed#readme - -(c) Vsevolod Strukchinsky (http://github.com/floatdrop) -Copyright (c) Vsevolod Strukchinsky - -The MIT License (MIT) - -Copyright (c) Vsevolod Strukchinsky (github.com/floatdrop) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -isstream 0.1.2 - MIT -https://github.com/rvagg/isstream - -Copyright (c) 2015 Rod Vagg -Copyright (c) 2015 Rod Vagg rvagg (https://twitter.com/rvagg) - -The MIT License (MIT) -===================== - -Copyright (c) 2015 Rod Vagg ---------------------------- - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -is-stream 1.1.0 - MIT -https://github.com/sindresorhus/is-stream#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -The MIT License (MIT) - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -is-typedarray 1.0.0 - MIT -https://github.com/hughsk/is-typedarray - - -This software is released under the MIT license: - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -isurl 1.0.0 - MIT -https://github.com/stevenvachon/isurl#readme - -Copyright (c) 2017 Steven Vachon - -MIT License - -Copyright (c) 2017 Steven Vachon - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -is-url 1.2.4 - MIT -https://github.com/segmentio/is-url#readme - - -MIT LICENSE - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -jedi 0.17.2 - MIT - - -Copyright (c) <2013> -Copyright (c) Maxim Kurnikov. -copyright u'jedi contributors -copyright (c) 2014 by Armin Ronacher. -Copyright (c) 2015 Jukka Lehtosalo and contributors - -All contributions towards Jedi are MIT licensed. - -------------------------------------------------------------------------------- -The MIT License (MIT) - -Copyright (c) <2013> - -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. - - -Copyright (c) Maxim Kurnikov. -All rights reserved. - -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. - - -The "typeshed" project is licensed under the terms of the Apache license, as -reproduced below. - -= = = = = - -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -= = = = = - -Parts of typeshed are licensed under different licenses (like the MIT -license), reproduced below. - -= = = = = - -The MIT License - -Copyright (c) 2015 Jukka Lehtosalo and 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. - -= = = = = - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -jsbn 0.1.1 - MIT -https://github.com/andyperlitch/jsbn#readme - -Copyright (c) 2005 Tom Wu -Copyright (c) 2003-2005 Tom Wu -Copyright (c) 2005-2009 Tom Wu - -Licensing ---------- - -This software is covered under the following copyright: - -/* - * Copyright (c) 2003-2005 Tom Wu - * All Rights Reserved. - * - * 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" AND WITHOUT WARRANTY OF ANY KIND, - * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY - * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. - * - * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL, - * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER - * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF - * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT - * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * In addition, the following condition applies: - * - * All redistributions must retain an intact copy of this copyright notice - * and disclaimer. - */ - -Address all questions regarding this license to: - - Tom Wu - tjw@cs.Stanford.EDU - ---------------------------------------------------------- - ---------------------------------------------------------- - -json5 2.1.0 - MIT -http://json5.org/ - -Copyright (c) 2012-2018 Aseem Kishore, and others - -MIT License - -Copyright (c) 2012-2018 Aseem Kishore, and [others]. - -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. - -[others]: https://github.com/json5/json5/contributors - - ---------------------------------------------------------- - ---------------------------------------------------------- - -json-buffer 3.0.0 - MIT -https://github.com/dominictarr/json-buffer - -Copyright (c) 2013 Dominic Tarr - -Copyright (c) 2013 Dominic Tarr - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -jsonc-parser 2.1.0 - MIT -https://github.com/Microsoft/node-jsonc-parser#readme - -Copyright (c) Microsoft -Copyright 2018, Microsoft -Copyright (c) Microsoft Corporation. - -The MIT License (MIT) - -Copyright (c) Microsoft - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -json-edm-parser 0.1.2 - MIT -https://github.com/yaxia/json-edm-parser#readme - -Copyright (c) 2016 Yang Xia - -The MIT License (MIT) - -Copyright (c) 2016 Yang Xia - -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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -jsonfile 4.0.0 - MIT -https://github.com/jprichardson/node-jsonfile#readme - -Copyright 2012-2016, JP Richardson -Copyright (c) 2012-2015, JP Richardson - -(The MIT License) - -Copyright (c) 2012-2015, JP Richardson - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -jsonparse 1.2.0 - MIT -https://github.com/creationix/jsonparse - -Copyright (c) 2012 Tim Caswell -Copyright (c) 2011-2012 Tim Caswell - -The MIT License - -Copyright (c) 2012 Tim Caswell - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -json-schema-traverse 0.4.1 - MIT -https://github.com/epoberezkin/json-schema-traverse#readme - -Copyright (c) 2017 Evgeny Poberezkin - -MIT License - -Copyright (c) 2017 Evgeny Poberezkin - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -jsprim 1.4.1 - MIT -https://github.com/joyent/node-jsprim#readme - -Copyright (c) 2012, Joyent, Inc. - -Copyright (c) 2012, Joyent, Inc. All rights reserved. - -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 - - ---------------------------------------------------------- - ---------------------------------------------------------- - -js-tokens 4.0.0 - MIT -https://github.com/lydell/js-tokens#readme - -Copyright 2014, 2015, 2016, 2017, 2018 Simon Lydell -Copyright (c) 2014, 2015, 2016, 2017, 2018 Simon Lydell - -The MIT License (MIT) - -Copyright (c) 2014, 2015, 2016, 2017, 2018 Simon Lydell - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -keyv 3.0.0 - MIT -https://github.com/lukechilds/keyv - -(c) Luke Childs -Copyright (c) 2017 Luke Childs - -MIT License - -Copyright (c) 2017 Luke Childs - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -kuler 1.0.1 - MIT -https://github.com/3rd-Eden/kuler - -Copyright 2014 Arnout Kazemier - -Copyright 2014 Arnout Kazemier - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -levn 0.3.0 - MIT -https://github.com/gkz/levn - -Copyright (c) George Zahariev - -Copyright (c) George Zahariev - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -linebreak 1.0.2 - MIT -https://github.com/devongovett/linebreaker - -Copyright (c) 1991-2014 Unicode, Inc. - -MIT License - -Copyright (c) - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -line-by-line 0.1.6 - MIT -https://github.com/Osterjour/line-by-line - -Copyright (c) 2012 Markus von der Wehd -Copyright (c) 2012 Markus von der Wehd - - -Copyright (c) 2012 Markus von der Wehd - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -lodash 4.17.19 - MIT -https://lodash.com/ - -Copyright OpenJS Foundation and other contributors -Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors -copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - -Copyright OpenJS Foundation and other contributors - -Based on Underscore.js, copyright Jeremy Ashkenas, -DocumentCloud and Investigative Reporters & Editors - -This software consists of voluntary contributions made by many -individuals. For exact contribution history, see the revision history -available at https://github.com/lodash/lodash - -The following license applies to all parts of this software except as -documented below: - -==== - -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. - -==== - -Copyright and related rights for sample code are waived via CC0. Sample -code is defined as all source code displayed within the prose of the -documentation. - -CC0: http://creativecommons.org/publicdomain/zero/1.0/ - -==== - -Files located in the node_modules and vendor directories are externally -maintained libraries used by this software which have their own -licenses; we recommend you read them, as their terms may differ from the -terms above. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -lodash.clonedeep 4.5.0 - MIT -https://lodash.com/ - -Copyright jQuery Foundation and other contributors -Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors -copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - -Copyright jQuery Foundation and other contributors - -Based on Underscore.js, copyright Jeremy Ashkenas, -DocumentCloud and Investigative Reporters & Editors - -This software consists of voluntary contributions made by many -individuals. For exact contribution history, see the revision history -available at https://github.com/lodash/lodash - -The following license applies to all parts of this software except as -documented below: - -==== - -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. - -==== - -Copyright and related rights for sample code are waived via CC0. Sample -code is defined as all source code displayed within the prose of the -documentation. - -CC0: http://creativecommons.org/publicdomain/zero/1.0/ - -==== - -Files located in the node_modules and vendor directories are externally -maintained libraries used by this software which have their own -licenses; we recommend you read them, as their terms may differ from the -terms above. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -logform 2.1.2 - MIT -https://github.com/winstonjs/logform#readme - -Copyright (c) 2017 Charlie Robbins & the Contributors. - -MIT License - -Copyright (c) 2017 Charlie Robbins & the 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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -loose-envify 1.4.0 - MIT -https://github.com/zertosh/loose-envify - -Copyright (c) 2015 Andres Suarez - -The MIT License (MIT) - -Copyright (c) 2015 Andres Suarez - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -lowercase-keys 1.0.0 - MIT -https://github.com/sindresorhus/lowercase-keys - -(c) Sindre Sorhus (http://sindresorhus.com) - -MIT License - -Copyright (c) - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -lowercase-keys 1.0.1 - MIT -https://github.com/sindresorhus/lowercase-keys#readme - -(c) Sindre Sorhus (http://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -The MIT License (MIT) - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -lowercase-keys 2.0.0 - MIT -https://github.com/sindresorhus/lowercase-keys#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -magic-string 0.22.5 - MIT -https://github.com/rich-harris/magic-string#readme - - -MIT License - -Copyright (c) - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -md5.js 1.3.4 - MIT -https://github.com/crypto-browserify/md5.js - -Copyright (c) 2016 Kirill Fomichev - -The MIT License (MIT) - -Copyright (c) 2016 Kirill Fomichev - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -merge-source-map 1.0.4 - MIT -https://github.com/keik/merge-source-map#readme - - -The MIT License (MIT) - -Copyright (c) keik - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -mime-db 1.40.0 - MIT -https://github.com/jshttp/mime-db#readme - -Copyright (c) 2014 Jonathan Ong -Copyright (c) 2014 Jonathan Ong me@jongleberry.com - - -The MIT License (MIT) - -Copyright (c) 2014 Jonathan Ong me@jongleberry.com - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -mime-types 2.1.24 - MIT -https://github.com/jshttp/mime-types#readme - -Copyright (c) 2014 Jonathan Ong -Copyright (c) 2015 Douglas Christopher Wilson -Copyright (c) 2014 Jonathan Ong -Copyright (c) 2015 Douglas Christopher Wilson - -(The MIT License) - -Copyright (c) 2014 Jonathan Ong -Copyright (c) 2015 Douglas Christopher Wilson - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -mimic-response 1.0.1 - MIT -https://github.com/sindresorhus/mimic-response#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -minimist 1.2.5 - MIT -https://github.com/substack/minimist - - -This software is released under the MIT license: - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -mkdirp 0.5.5 - MIT -https://github.com/substack/node-mkdirp#readme - -Copyright 2010 James Halliday (mail@substack.net) - -Copyright 2010 James Halliday (mail@substack.net) - -This project is free software released under the MIT/X11 license: - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -moment 2.24.0 - MIT -http://momentjs.com/ - -Copyright (c) JS Foundation and other contributors - -Copyright (c) JS Foundation 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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -ms 2.0.0 - MIT -https://github.com/zeit/ms#readme - -Copyright (c) 2016 Zeit, Inc. - -The MIT License (MIT) - -Copyright (c) 2016 Zeit, Inc. - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -ms 2.1.2 - MIT -https://github.com/zeit/ms#readme - -Copyright (c) 2016 Zeit, Inc. - -The MIT License (MIT) - -Copyright (c) 2016 Zeit, Inc. - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -named-js-regexp 1.3.5 - MIT -https://github.com/edvinv/named-js-regexp#readme - -Copyright (c) 2015 - -The MIT License - -Copyright (c) 2015, @edvinv - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -next-tick 1.0.0 - MIT -https://github.com/medikoo/next-tick#readme - -Copyright (c) 2012-2016 Mariusz Nowak - -The MIT License - -Copyright (C) 2012-2016 Mariusz Nowak - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -node-fetch 2.6.1 - MIT -https://github.com/bitinn/node-fetch - -Copyright (c) 2016 David Frank - -The MIT License (MIT) - -Copyright (c) 2016 David Frank - -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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -node-gyp-build 4.2.1 - MIT -https://github.com/prebuild/node-gyp-build - -Copyright (c) 2017 Mathias Buus - -The MIT License (MIT) - -Copyright (c) 2017 Mathias Buus - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -node-stream-zip 1.8.2 - MIT -https://github.com/antelle/node-stream-zip - -Copyright (c) 2015 Antelle https://github.com/antelle -(c) 2015 Antelle https://github.com/antelle/node-stream-zip/blob/master/LICENSE -Copyright (c) 2012 Another-D-Mention Software and other contributors, http://www.another-d-mention.ro -Portions copyright https://github.com/cthackers/adm-zip https://raw.githubusercontent.com/cthackers/adm-zip/master/LICENSE - -Copyright (c) 2015 Antelle https://github.com/antelle - -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. - -== dependency license: adm-zip == - -Copyright (c) 2012 Another-D-Mention Software and other contributors, -http://www.another-d-mention.ro/ - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -normalize-url 4.5.0 - MIT -https://github.com/sindresorhus/normalize-url#readme - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -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 - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -object-assign 4.1.1 - MIT -https://github.com/sindresorhus/object-assign#readme - -(c) Sindre Sorhus -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -The MIT License (MIT) - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -object-inspect 1.4.1 - MIT -https://github.com/substack/object-inspect - -Copyright Joyent, Inc. and other Node contributors. - -This software is released under the MIT license: - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -object-keys 1.1.1 - MIT -https://github.com/ljharb/object-keys#readme - -Copyright (c) 2013 Jordan Harband - -The MIT License (MIT) - -Copyright (C) 2013 Jordan Harband - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -one-time 0.0.4 - MIT -https://github.com/unshiftio/one-time - -Copyright (c) 2015 Unshift.io, Arnout Kazemier - -The MIT License (MIT) - -Copyright (c) 2015 Unshift.io, Arnout Kazemier, the 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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -onigasm 2.2.2 - MIT -https://github.com/NeekSandhu/onigasm#readme - -Copyright (c) 2018 Neek Sandhu - -The MIT License (MIT) - -Copyright (c) 2018 Neek Sandhu - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -optionator 0.8.2 - MIT -https://github.com/gkz/optionator - -Copyright (c) George Zahariev - -Copyright (c) George Zahariev - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -os-tmpdir 1.0.2 - MIT -https://github.com/sindresorhus/os-tmpdir#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -The MIT License (MIT) - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -pako 0.2.9 - MIT -https://github.com/nodeca/pako - -Copyright (c) 2014-2016 by Vitaly Puzrin - -(The MIT License) - -Copyright (C) 2014-2016 by Vitaly Puzrin - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -p-any 2.1.0 - MIT -https://github.com/sindresorhus/p-any#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -parso 0.7.0 - MIT - - -Copyright (c) <2013-2017> -Copyright 2006 Google, Inc. -copyright u'parso contributors -Copyright (c) 2010 by Armin Ronacher. -Copyright David Halter and Contributors -Copyright 2004-2005 Elemental Security, Inc. -Copyright 2014 David Halter and Contributors -Copyright (c) 2014-2016 Ian Lee -Copyright (c) 2017-???? Dave Halter -Copyright (c) 2006-2009 Johann C. Rocholl -Copyright 2010 by Armin Ronacher. :license Flask Design License -Copyright (c) 2009-2014 Florent Xicluna -Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Python Software Foundation - -All contributions towards parso are MIT licensed. - -Some Python files have been taken from the standard library and are therefore -PSF licensed. Modifications on these files are dual licensed (both MIT and -PSF). These files are: - -- parso/pgen2/* -- parso/tokenize.py -- parso/token.py -- test/test_pgen2.py - -Also some test files under test/normalizer_issue_files have been copied from -https://github.com/PyCQA/pycodestyle (Expat License == MIT License). - -------------------------------------------------------------------------------- -The MIT License (MIT) - -Copyright (c) <2013-2017> - -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. - -------------------------------------------------------------------------------- - -PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 --------------------------------------------- - -1. This LICENSE AGREEMENT is between the Python Software Foundation -("PSF"), and the Individual or Organization ("Licensee") accessing and -otherwise using this software ("Python") in source or binary form and -its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, PSF hereby -grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, -analyze, test, perform and/or display publicly, prepare derivative works, -distribute, and otherwise use Python alone or in any derivative version, -provided, however, that PSF's License Agreement and PSF's notice of copyright, -i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, -2011, 2012, 2013, 2014, 2015 Python Software Foundation; All Rights Reserved" -are retained in Python alone or in any derivative version prepared by Licensee. - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python. - -4. PSF is making Python available to Licensee on an "AS IS" -basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. Nothing in this License Agreement shall be deemed to create any -relationship of agency, partnership, or joint venture between PSF and -Licensee. This License Agreement does not grant permission to use PSF -trademarks or trade name in a trademark sense to endorse or promote -products or services of Licensee, or any third party. - -8. By copying, installing or otherwise using Python, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. - - -Copyright (c) 2010 by Armin Ronacher. - -Some rights reserved. - -Redistribution and use in source and binary forms of the theme, with or -without modification, are permitted provided that the following conditions -are met: - -* Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - -* The names of the contributors may not be used to endorse or - promote products derived from this software without specific - prior written permission. - -We kindly ask you to only use these themes in an unmodified manner just -for Flask and Flask-related products, not for unrelated projects. If you -like the visual style and want to use it for your own projects, please -consider making some larger changes to the themes (such as changing -font faces, sizes, colors or margins). - -THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -path-is-absolute 1.0.1 - MIT -https://github.com/sindresorhus/path-is-absolute#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -The MIT License (MIT) - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -path-parse 1.0.6 - MIT -https://github.com/jbgutierrez/path-parse#readme - -Copyright (c) 2015 Javier Blanco -(c) Javier Blanco (http://jbgutierrez.info) - -The MIT License (MIT) - -Copyright (c) 2015 Javier Blanco - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -p-cancelable 1.1.0 - MIT -https://github.com/sindresorhus/p-cancelable#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -p-cancelable 0.4.1 - MIT -https://github.com/sindresorhus/p-cancelable#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -p-cancelable 2.0.0 - MIT -https://github.com/sindresorhus/p-cancelable#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -pdfkit 0.11.0 - MIT -http://pdfkit.org/ - -(c) 2012 by Cedric Mesnil. -Copyright 2013 Google Inc. -Copyright (c) 2011 Devon Govett -Copyright (c) 2014 Devon Govett -copyright (c) 2019 Denis Pushkarev -Copyright (c) 2014-present, Facebook, Inc. -(c) 1995-2013 Jean-loup Gailly and Mark Adler -(c) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -Copyright (c) 2009 Thomas Robinson <280north.com> -Copyright Joyent, Inc. and other Node contributors. -Copyright (c) 1985, 1987, 1988, 1989, 1997 Adobe Systems Incorporated. -Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. -Copyright (c) 1989, 1990, 1991, 1993, 1997 Adobe Systems Incorporated. -Copyright (c) 1985, 1987, 1989, 1990, 1993, 1997 Adobe Systems Incorporated. -Copyright (c) 1989, 1990, 1991, 1992, 1993, 1997 Adobe Systems Incorporated. - -MIT LICENSE -Copyright (c) 2014 Devon Govett - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -performance-now 2.1.0 - MIT -https://github.com/braveg1rl/performance-now - -Copyright (c) 2013 Braveg1rl -Copyright (c) 2017 Braveg1rl - -Copyright (c) 2013 Braveg1rl - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -p-finally 1.0.0 - MIT -https://github.com/sindresorhus/p-finally#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -The MIT License (MIT) - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -pidusage 1.2.0 - MIT -https://github.com/soyuka/pidusage - -Copyright (c) 2014 - -The MIT License (MIT) - -Copyright (c) 2014 soyuka - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -pify 4.0.1 - MIT -https://github.com/sindresorhus/pify#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -pify 3.0.0 - MIT -https://github.com/sindresorhus/pify#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -p-is-promise 1.1.0 - MIT -https://github.com/sindresorhus/p-is-promise#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -The MIT License (MIT) - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -png-js 1.0.0 - MIT -https://github.com/devongovett/png.js#readme - -Copyright (c) 2011 Devon Govett -Copyright (c) 2017 Devon Govett -Copyright (c) 2011 Mozilla Foundation - -MIT License - -Copyright (c) 2017 Devon Govett - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -portfinder 1.0.25 - MIT -https://github.com/indexzero/node-portfinder#readme - -(c) 2011, Charlie Robbins -Copyright (c) 2012 Charlie Robbins - -node-portfinder - -Copyright (c) 2012 Charlie Robbins - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -prelude-ls 1.1.2 - MIT -http://preludels.com/ - -Copyright (c) George Zahariev - -Copyright (c) George Zahariev - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -prepend-http 2.0.0 - MIT -https://github.com/sindresorhus/prepend-http#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -process-nextick-args 1.0.7 - MIT -https://github.com/calvinmetcalf/process-nextick-args - -Copyright (c) 2015 Calvin Metcalf - -# Copyright (c) 2015 Calvin Metcalf - -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.** - - ---------------------------------------------------------- - ---------------------------------------------------------- - -process-nextick-args 2.0.1 - MIT -https://github.com/calvinmetcalf/process-nextick-args - -Copyright (c) 2015 Calvin Metcalf - -# Copyright (c) 2015 Calvin Metcalf - -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.** - - ---------------------------------------------------------- - ---------------------------------------------------------- - -prop-types 15.7.2 - MIT -https://facebook.github.io/react/ - -(c) Sindre Sorhus -Copyright (c) 2013-present, Facebook, Inc. -Copyright (c) Facebook, Inc. and its affiliates. - -MIT License - -Copyright (c) 2013-present, Facebook, Inc. - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -psl 1.2.0 - MIT -https://github.com/lupomontero/psl#readme - -Copyright (c) 2017 Lupo Montero lupomontero@gmail.com -Copyright (c) 2017 Lupo Montero - -The MIT License (MIT) - -Copyright (c) 2017 Lupo Montero lupomontero@gmail.com - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -p-some 4.1.0 - MIT -https://github.com/sindresorhus/p-some#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -p-timeout 2.0.1 - MIT -https://github.com/sindresorhus/p-timeout#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -p-timeout 3.2.0 - MIT -https://github.com/sindresorhus/p-timeout#readme - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -public-ip 3.2.0 - MIT -https://github.com/sindresorhus/public-ip#readme - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -pump 2.0.1 - MIT -https://github.com/mafintosh/pump#readme - -Copyright (c) 2014 Mathias Buus - -The MIT License (MIT) - -Copyright (c) 2014 Mathias Buus - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -pump 3.0.0 - MIT -https://github.com/mafintosh/pump#readme - -Copyright (c) 2014 Mathias Buus - -The MIT License (MIT) - -Copyright (c) 2014 Mathias Buus - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -punycode 1.4.1 - MIT -https://mths.be/punycode - -Copyright Mathias Bynens - -Copyright Mathias Bynens - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -punycode 2.1.1 - MIT -https://mths.be/punycode - -Copyright Mathias Bynens - -Copyright Mathias Bynens - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -python-jsonrpc-server 0.2.0 - MIT - - -Copyright 2017 Palantir Technologies, Inc. -Copyright 2018 Palantir Technologies, Inc. - -The MIT License (MIT) - -Copyright 2017 Palantir Technologies, Inc. - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -query-string 5.1.1 - MIT -https://github.com/sindresorhus/query-string#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -querystringify 2.1.1 - MIT -https://github.com/unshiftio/querystringify - -Copyright (c) 2015 Unshift.io, Arnout Kazemier - -The MIT License (MIT) - -Copyright (c) 2015 Unshift.io, Arnout Kazemier, the 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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -quote-stream 1.0.2 - MIT -https://github.com/substack/quote-stream - - -This software is released under the MIT license: - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -react-draggable 4.4.2 - MIT -https://github.com/mzabriskie/react-draggable - -Copyright (c) 2017 Jed Watson. -Copyright (c) 2014-2016 Matt Zabriskie. -Copyright (c) 2013-present, Facebook, Inc. - -(MIT License) - -Copyright (c) 2014-2016 Matt Zabriskie. All rights reserved. - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -react-is 16.8.6 - MIT -https://reactjs.org/ - -Copyright (c) Facebook, Inc. and its affiliates. - -MIT License - -Copyright (c) Facebook, Inc. and its affiliates. - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -readable-stream 2.0.6 - MIT -https://github.com/nodejs/readable-stream#readme - -Copyright Joyent, Inc. and other Node contributors. - -Copyright Joyent, Inc. and other Node contributors. All rights reserved. -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -readable-stream 2.3.6 - MIT -https://github.com/nodejs/readable-stream#readme - -Copyright Joyent, Inc. and other Node contributors. - -Node.js is licensed for use as follows: - -""" -Copyright Node.js contributors. All rights reserved. - -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. -""" - -This license applies to parts of Node.js originating from the -https://github.com/joyent/node repository: - -""" -Copyright Joyent, Inc. and other Node contributors. All rights reserved. -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. -""" - - ---------------------------------------------------------- - ---------------------------------------------------------- - -readable-stream 2.3.7 - MIT -https://github.com/nodejs/readable-stream#readme - -Copyright Joyent, Inc. and other Node contributors. - -Node.js is licensed for use as follows: - -""" -Copyright Node.js contributors. All rights reserved. - -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. -""" - -This license applies to parts of Node.js originating from the -https://github.com/joyent/node repository: - -""" -Copyright Joyent, Inc. and other Node contributors. All rights reserved. -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. -""" - - ---------------------------------------------------------- - ---------------------------------------------------------- - -readable-stream 3.4.0 - MIT -https://github.com/nodejs/readable-stream#readme - -Copyright Joyent, Inc. and other Node contributors. - -Node.js is licensed for use as follows: - -""" -Copyright Node.js contributors. All rights reserved. - -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. -""" - -This license applies to parts of Node.js originating from the -https://github.com/joyent/node repository: - -""" -Copyright Joyent, Inc. and other Node contributors. All rights reserved. -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. -""" - - ---------------------------------------------------------- - ---------------------------------------------------------- - -regenerator-runtime 0.11.1 - MIT - - -Copyright (c) 2014-present, Facebook, Inc. - -MIT License - -Copyright (c) - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -regenerator-runtime 0.13.2 - MIT - - -Copyright (c) 2014-present, Facebook, Inc. - -MIT License - -Copyright (c) - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -request-progress 3.0.0 - MIT -https://github.com/IndigoUnited/node-request-progress#readme - -Copyright (c) 2012 IndigoUnited - -Copyright (c) 2012 IndigoUnited - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -requires-port 1.0.0 - MIT -https://github.com/unshiftio/requires-port - -Copyright (c) 2015 Unshift.io, Arnout Kazemier - -The MIT License (MIT) - -Copyright (c) 2015 Unshift.io, Arnout Kazemier, the 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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -resolve 1.11.1 - MIT -https://github.com/browserify/resolve#readme - - -This software is released under the MIT license: - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -resolve 1.1.7 - MIT -https://github.com/substack/node-resolve#readme - - -This software is released under the MIT license: - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -responselike 1.0.2 - MIT -https://github.com/lukechilds/responselike#readme - -(c) Luke Childs -Copyright (c) 2017 Luke Childs - -Copyright (c) 2017 Luke Childs - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -restructure 0.5.4 - MIT -https://github.com/devongovett/restructure - - -MIT License - -Copyright (c) - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -rfdc 1.1.4 - MIT -https://github.com/davidmarkclements/rfdc#readme - -Copyright 2019 David Mark Clements - -Copyright 2019 "David Mark Clements " - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -safe-buffer 5.1.2 - MIT -https://github.com/feross/safe-buffer - -Copyright (c) Feross Aboukhadijeh -Copyright (c) Feross Aboukhadijeh (http://feross.org) - -The MIT License (MIT) - -Copyright (c) Feross Aboukhadijeh - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -safer-buffer 2.1.2 - MIT -https://github.com/ChALkeR/safer-buffer#readme - -Copyright (c) 2018 Nikita Skovoroda - -MIT License - -Copyright (c) 2018 Nikita Skovoroda - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -shallow-copy 0.0.1 - MIT -https://github.com/substack/shallow-copy - - -This software is released under the MIT license: - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -simple-swizzle 0.2.2 - MIT -https://github.com/qix-/node-simple-swizzle#readme - -Copyright (c) 2015 Josh Junon - -The MIT License (MIT) - -Copyright (c) 2015 Josh Junon - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -sort-keys 1.1.2 - MIT -https://github.com/sindresorhus/sort-keys#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -The MIT License (MIT) - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -sort-keys 2.0.0 - MIT -https://github.com/sindresorhus/sort-keys#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -sshpk 1.16.1 - MIT -https://github.com/arekinath/node-sshpk#readme - -Copyright Joyent, Inc. -Copyright 2015 Joyent, Inc. -Copyright 2016 Joyent, Inc. -Copyright 2017 Joyent, Inc. -Copyright 2018 Joyent, Inc. - -Copyright Joyent, Inc. All rights reserved. -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -stack-chain 1.3.7 - MIT -https://github.com/AndreasMadsen/stack-chain#readme - -Copyright 2012 the V8 project -Copyright (c) 2012 Andreas Madsen - -Copyright (c) 2012 Andreas Madsen - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -stack-trace 0.0.10 - MIT -https://github.com/felixge/node-stack-trace - -Copyright (c) 2011 Felix Geisendorfer (felix@debuggable.com) - -Copyright (c) 2011 Felix Geisendörfer (felix@debuggable.com) - - 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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -static-eval 2.0.2 - MIT -https://github.com/substack/static-eval - - -This software is released under the MIT license: - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -static-module 3.0.3 - MIT -https://github.com/substack/static-module - - -This software is released under the MIT license: - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -static-module 2.2.5 - MIT -https://github.com/substack/static-module - - -This software is released under the MIT license: - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -streamroller 2.2.3 - MIT -https://github.com/nomiddlename/streamroller#readme - -Copyright (c) 2013 Gareth Jones - -The MIT License (MIT) - -Copyright (c) 2013 Gareth Jones - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -strict-uri-encode 1.1.0 - MIT -https://github.com/kevva/strict-uri-encode#readme - -(c) Kevin Martensson (http://github.com/kevva) -Copyright (c) Kevin Martensson - -The MIT License (MIT) - -Copyright (c) Kevin Mårtensson (github.com/kevva) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -string_decoder 0.10.31 - MIT -https://github.com/rvagg/string_decoder - -Copyright Joyent, Inc. and other Node contributors. - -Copyright Joyent, Inc. and other Node 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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -string_decoder 1.1.1 - MIT -https://github.com/nodejs/string_decoder - -Copyright Joyent, Inc. and other Node contributors. - -Node.js is licensed for use as follows: - -""" -Copyright Node.js contributors. All rights reserved. - -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. -""" - -This license applies to parts of Node.js originating from the -https://github.com/joyent/node repository: - -""" -Copyright Joyent, Inc. and other Node contributors. All rights reserved. -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. -""" - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -string_decoder 1.2.0 - MIT -https://github.com/nodejs/string_decoder - -Copyright Joyent, Inc. and other Node contributors. - -Node.js is licensed for use as follows: - -""" -Copyright Node.js contributors. All rights reserved. - -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. -""" - -This license applies to parts of Node.js originating from the -https://github.com/joyent/node repository: - -""" -Copyright Joyent, Inc. and other Node contributors. All rights reserved. -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. -""" - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -string-argv 0.3.1 - MIT -https://github.com/mccormicka/string-argv - -Copyright 2014 Anthony McCormick - -The MIT License (MIT) - -Copyright 2014 Anthony McCormick - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -strip-ansi 5.2.0 - MIT -https://github.com/chalk/strip-ansi#readme - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -sudo-prompt 8.2.5 - MIT -https://github.com/jorangreef/sudo-prompt#readme - -Copyright (c) 2015 Joran Dirk Greef - -The MIT License (MIT) - -Copyright (c) 2015 Joran Dirk Greef - -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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -svg-to-pdfkit 0.1.8 - MIT -https://github.com/alafr/SVG-to-PDFKit#readme - -(c) www.w3.org -Copyright 2013 Google Inc. -Copyright (c) 2011 Devon Govett -copyright (c) 2018 Denis Pushkarev -Copyright (c) 2014-present, Facebook, Inc. -(c) 1995-2013 Jean-loup Gailly and Mark Adler -Copyright (c) 2019 SVG-to-PDFKit contributors -(c) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -Copyright (c) 2009 Thomas Robinson <280north.com> -Copyright Joyent, Inc. and other Node contributors. -Copyright 2008 World Wide Web Consortium, Massachusetts Institute -Copyright 2009 World Wide Web Consortium, Massachusetts Institute -Copyright (c) 1985, 1987, 1988, 1989, 1997 Adobe Systems Incorporated. -Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. -Copyright (c) 1989, 1990, 1991, 1993, 1997 Adobe Systems Incorporated. -Copyright (c) 1985, 1987, 1989, 1990, 1993, 1997 Adobe Systems Incorporated. -Copyright (c) 1989, 1990, 1991, 1992, 1993, 1997 Adobe Systems Incorporated. -Copyright 2009 World Wide Web Consortium, (Massachusetts Institute of Technology, European Research Consortium for Informatics and Mathematics (ERCIM), Keio University). - -The MIT License (MIT) - -Copyright (c) 2019 SVG-to-PDFKit 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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -tas-client 0.0.950 - MIT - - -Copyright (c) Microsoft Corporation. - -MIT License - -Copyright (c) - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -tcp-port-used 1.0.1 - MIT -https://github.com/stdarg/tcp-port-used - -Copyright (c) 2013 - -The MIT License (MIT) - -Copyright (c) 2013 jut-io - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -text-hex 1.0.0 - MIT -https://github.com/3rd-Eden/text-hex - -Copyright (c) 2014-2015 Arnout Kazemier - -The MIT License (MIT) - -Copyright (c) 2014-2015 Arnout Kazemier - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -throttleit 1.0.0 - MIT -https://github.com/component/throttle - - -MIT License - -Copyright (c) - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -through 2.3.8 - MIT -https://github.com/dominictarr/through - -Copyright (c) 2011 Dominic Tarr - -The MIT License - -Copyright (c) 2011 Dominic Tarr - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -through2 2.0.5 - MIT -https://github.com/rvagg/through2#readme - -Copyright (c) Rod Vagg -Copyright (c) Rod Vagg rvagg (https://twitter.com/rvagg) and additional contributors - -# The MIT License (MIT) - -**Copyright (c) Rod Vagg (the "Original Author") and additional 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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -timed-out 4.0.1 - MIT -https://github.com/floatdrop/timed-out#readme - -(c) Vsevolod Strukchinsky (floatdrop@gmail.com) -Copyright (c) Vsevolod Strukchinsky - -The MIT License (MIT) - -Copyright (c) Vsevolod Strukchinsky - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -tiny-inflate 1.0.3 - MIT -https://github.com/devongovett/tiny-inflate - -Copyright (c) 2015-present Devon Govett - -MIT License - -Copyright (c) 2015-present Devon Govett - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -tmp 0.0.29 - MIT -http://github.com/raszi/node-tmp - -Copyright (c) 2014 KARASZI Istvan -Copyright (c) 2011-2015 KARASZI Istvan - -The MIT License (MIT) - -Copyright (c) 2014 KARASZI István - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -to-readable-stream 1.0.0 - MIT -https://github.com/sindresorhus/to-readable-stream#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -tree-kill 1.2.2 - MIT -https://github.com/pkrumins/node-tree-kill - -Copyright (c) 2018 Peter Krumins - -MIT License - -Copyright (c) 2018 Peter Krumins - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -triple-beam 1.3.0 - MIT -https://github.com/winstonjs/triple-beam#readme - -Copyright (c) 2017 -(c) 2010 Charlie Robbins - -MIT License - -Copyright (c) 2017 winstonjs - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -type-check 0.3.2 - MIT -https://github.com/gkz/type-check - -Copyright (c) George Zahariev - -Copyright (c) George Zahariev - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -typedarray 0.0.6 - MIT -https://github.com/substack/typedarray - -Copyright (c) 2012, Joshua Bell -Copyright (c) 2010, Linden Research, Inc. - -/* - Copyright (c) 2010, Linden Research, Inc. - Copyright (c) 2012, Joshua Bell - - 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. - $/LicenseInfo$ - */ - -// Original can be found at: -// https://bitbucket.org/lindenlab/llsd -// Modifications by Joshua Bell inexorabletash@gmail.com -// https://github.com/inexorabletash/polyfill - -// ES3/ES5 implementation of the Krhonos Typed Array Specification -// Ref: http://www.khronos.org/registry/typedarray/specs/latest/ -// Date: 2011-02-01 -// -// Variations: -// * Allows typed_array.get/set() as alias for subscripts (typed_array[]) - - ---------------------------------------------------------- - ---------------------------------------------------------- - -uint64be 1.0.1 - MIT -https://github.com/mafintosh/uint64be - -Copyright (c) 2015 Mathias Buus - -The MIT License (MIT) - -Copyright (c) 2015 Mathias Buus - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -underscore 1.8.3 - MIT -http://underscorejs.org/ - -Copyright (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors -(c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors Underscore - -Copyright (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative -Reporters & Editors - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -unicode 10.0.0 - MIT -http://github.com/eversport/node-unicodetable - -Copyright (c) 2014 - -Copyright (c) 2014 ▟ ▖▟ ▖(dodo) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -unicode-properties 1.3.1 - MIT -https://github.com/devongovett/unicode-properties - -Copyright 2018 - -Copyright 2018 - -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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -unicode-trie 0.3.1 - MIT -https://github.com/devongovett/unicode-trie - - -MIT License - -Copyright (c) - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -unicode-trie 1.0.0 - MIT -https://github.com/devongovett/unicode-trie - -Copyright 2018 - -Copyright 2018 - -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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -unicode-trie 2.0.0 - MIT -https://github.com/devongovett/unicode-trie - -Copyright 2018 - -Copyright 2018 - -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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -universalify 0.1.2 - MIT -https://github.com/RyanZim/universalify#readme - -Copyright (c) 2017, Ryan Zimmerman - -(The MIT License) - -Copyright (c) 2017, Ryan Zimmerman - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -untildify 3.0.3 - MIT -https://github.com/sindresorhus/untildify#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -The MIT License (MIT) - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -url-parse 1.4.7 - MIT -https://github.com/unshiftio/url-parse#readme - -Copyright (c) 2015 Unshift.io, Arnout Kazemier - -The MIT License (MIT) - -Copyright (c) 2015 Unshift.io, Arnout Kazemier, the 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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -url-parse-lax 3.0.0 - MIT -https://github.com/sindresorhus/url-parse-lax#readme - -(c) Sindre Sorhus (https://sindresorhus.com) -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -url-to-options 1.0.1 - MIT -https://github.com/stevenvachon/url-to-options#readme - -Copyright (c) 2017 Steven Vachon - -MIT License - -Copyright (c) 2017 Steven Vachon - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -util-deprecate 1.0.2 - MIT -https://github.com/TooTallNate/util-deprecate - -Copyright (c) 2014 Nathan Rajlich - -(The MIT License) - -Copyright (c) 2014 Nathan Rajlich - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -uuid 3.3.2 - MIT -https://github.com/kelektiv/node-uuid#readme - -Copyright 2011, Sebastian Tschan https://blueimp.net -Copyright (c) 2010-2016 Robert Kieffer and other contributors -Copyright (c) Paul Johnston 1999 - 2009 Other contributors Greg Holt, Andrew Kepert, Ydnar, Lostinet - -The MIT License (MIT) - -Copyright (c) 2010-2016 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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -uuid 7.0.3 - MIT -https://github.com/uuidjs/uuid#readme - -Copyright 2011, Sebastian Tschan https://blueimp.net -Copyright (c) 2010-2016 Robert Kieffer and other contributors -Copyright (c) Paul Johnston 1999 - 2009 Other contributors Greg Holt, Andrew Kepert, Ydnar, Lostinet - -The MIT License (MIT) - -Copyright (c) 2010-2016 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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -validator 9.4.1 - MIT -http://github.com/chriso/validator.js - -Copyright (c) 2016 Chris O'Hara -Copyright (c) 2017 Chris O'Hara - -Copyright (c) 2016 Chris O'Hara - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -verror 1.10.0 - MIT -https://github.com/davepacheco/node-verror - -Copyright (c) 2016, Joyent, Inc. - -Copyright (c) 2016, Joyent, Inc. All rights reserved. - -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 - - ---------------------------------------------------------- - ---------------------------------------------------------- - -vlq 0.2.3 - MIT -https://github.com/Rich-Harris/vlq#readme - -Copyright (c) 2017 these people (https://github.com/Rich-Harris/vlq/graphs/contributors) - -Copyright (c) 2017 [these people](https://github.com/Rich-Harris/vlq/graphs/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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -vscode-debugadapter 1.35.0 - MIT -https://github.com/Microsoft/vscode-debugadapter-node#readme - -Copyright (c) Microsoft Corporation. - -Copyright (c) Microsoft Corporation - -All rights reserved. - -MIT License - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -vscode-debugprotocol 1.35.0 - MIT -https://github.com/Microsoft/vscode-debugadapter-node#readme - -Copyright (c) Microsoft Corporation. - -Copyright (c) Microsoft Corporation - -All rights reserved. - -MIT License - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -vscode-extension-telemetry 0.1.4 - MIT -https://github.com/Microsoft/vscode-extension-telemetry#readme - -Copyright (c) Microsoft Corporation. - -vscode-extension-telemetry - -The MIT License (MIT) - -Copyright (c) Microsoft Corporation - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -vscode-jsonrpc 6.0.0-next.5 - MIT -https://github.com/Microsoft/vscode-languageserver-node#readme - -Copyright (c) Microsoft Corporation. - -Copyright (c) Microsoft Corporation - -All rights reserved. - -MIT License - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -vscode-languageclient 7.0.0-next.9 - MIT -https://github.com/Microsoft/vscode-languageserver-node#readme - -Copyright (c) Microsoft Corporation. -Copyright (c) Isaac Z. Schlueter and Contributors - -Copyright (c) Microsoft Corporation - -All rights reserved. - -MIT License - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -vscode-languageserver 7.0.0-next.7 - MIT -https://github.com/Microsoft/vscode-languageserver-node#readme - -Copyright (c) Microsoft Corporation. - -Copyright (c) Microsoft Corporation - -All rights reserved. - -MIT License - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -vscode-languageserver-protocol 3.16.0-next.7 - MIT -https://github.com/Microsoft/vscode-languageserver-node#readme - -Copyright (c) TypeFox and others. -Copyright (c) Microsoft Corporation. - -Copyright (c) Microsoft Corporation - -All rights reserved. - -MIT License - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -vscode-languageserver-types 3.16.0-next.3 - MIT -https://github.com/Microsoft/vscode-languageserver-node#readme - -Copyright (c) Microsoft Corporation. - -Copyright (c) Microsoft Corporation - -All rights reserved. - -MIT License - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -vscode-tas-client 0.1.4 - MIT - - -Copyright (c) Microsoft Corporation. - -MIT License - -Copyright (c) - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -winston 3.2.1 - MIT -https://github.com/winstonjs/winston#readme - -(c) 2015 Nimrod Becker -(c) 2010 Charlie Robbins -(c) 2016 Charlie Robbins -(c) 2011 Daniel Aristizabal -Copyright (c) 2010 Charlie Robbins - -Copyright (c) 2010 Charlie Robbins - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -winston-transport 4.3.0 - MIT -https://github.com/winstonjs/winston-transport#readme - -Copyright (c) 2015 Charlie Robbins & the contributors. - -The MIT License (MIT) - -Copyright (c) 2015 Charlie Robbins & the 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. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -wordwrap 1.0.0 - MIT -https://github.com/substack/node-wordwrap#readme - - -This software is released under the MIT license: - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -ws 7.2.0 - MIT -https://github.com/websockets/ws - -Copyright (c) 2011 Einar Otto Stangvik - -The MIT License (MIT) - -Copyright (c) 2011 Einar Otto Stangvik - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -ws 6.2.1 - MIT -https://github.com/websockets/ws - -Copyright (c) 2011 Einar Otto Stangvik - -The MIT License (MIT) - -Copyright (c) 2011 Einar Otto Stangvik - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -xml2js 0.2.8 - MIT -https://github.com/Leonidas-from-XIV/node-xml2js - -Copyright 2010, 2011, 2012, 2013. - -Copyright 2010, 2011, 2012, 2013. All rights reserved. - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -xml2js 0.4.19 - MIT -https://github.com/Leonidas-from-XIV/node-xml2js - -Copyright 2010, 2011, 2012, 2013. - -Copyright 2010, 2011, 2012, 2013. All rights reserved. - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -xmlbuilder 9.0.7 - MIT -http://github.com/oozcitak/xmlbuilder-js - -Copyright (c) 2013 Ozgur Ozcitak - -The MIT License (MIT) - -Copyright (c) 2013 Ozgur Ozcitak - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -xtend 4.0.2 - MIT -https://github.com/Raynos/xtend - -Copyright (c) 2012-2014 Raynos. - -The MIT License (MIT) -Copyright (c) 2012-2014 Raynos. - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -zeromq 6.0.0-beta.6 - MIT -https://github.com/zeromq/zeromq.js#readme - -Copyright (c) 2017 -Copyright (c) 2011 TJ Holowaychuk -Copyright 2017-2019 Rolf Timmermans -Copyright (c) 2010, 2011 Justin Tulloss -Copyright (c) 2017-2019 Rolf Timmermans - -Copyright 2017-2019 Rolf Timmermans - -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. - - -The MIT License (MIT) -===================== - -Copyright (c) 2017 Node.js API collaborators ------------------------------------ - -*Node.js API collaborators listed at * - -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. - ---------------------------------------------------------- - ---------------------------------------------------------- - -type-fest 0.8.1 - MIT OR (CC0-1.0 AND MIT) -https://github.com/sindresorhus/type-fest#readme - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -type-fest 0.3.1 - MIT OR CC0-1.0 -https://github.com/sindresorhus/type-fest#readme - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -MIT License - -Copyright (c) Sindre Sorhus (sindresorhus.com) - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -font-awesome 4.7.0 - OFL-1.1 AND MIT -http://fontawesome.io/ - -Copyright Dave Gandy 2016. - -OFL-1.1 AND MIT - ---------------------------------------------------------- - ---------------------------------------------------------- - -tweetnacl 0.14.5 - Unlicense -https://tweetnacl.js.org/ - - -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -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 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. - -For more information, please refer to - - ---------------------------------------------------------- - ---------------------------------------------------------- - -typescript-char 0.0.0 - Unlicense -https://github.com/mason-lang/typescript-char#readme - - -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. - -In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and - -successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. - -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 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. - -For more information, please refer to - ---------------------------------------------------------- - ---------------------------------------------------------- - -truncate-utf8-bytes 1.0.2 - WTFPL -https://github.com/parshap/truncate-utf8-bytes#readme - - -DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE - -Version 2, December 2004 - -Copyright (C) 2004 Sam Hocevar - -Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed. - -DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE - -TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. You just DO WHAT THE FUCK YOU WANT TO. - ---------------------------------------------------------- - ---------------------------------------------------------- - -utf8-byte-length 1.0.4 - WTFPL -https://github.com/parshap/utf8-byte-length#readme - - -DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE - -Version 2, December 2004 - -Copyright (C) 2004 Sam Hocevar - -Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed. - -DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE - -TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. You just DO WHAT THE FUCK YOU WANT TO. - ---------------------------------------------------------- - diff --git a/ThirdPartyNotices-Repository.txt b/ThirdPartyNotices-Repository.txt index 98c64072ca51..9e7e822af1bb 100644 --- a/ThirdPartyNotices-Repository.txt +++ b/ThirdPartyNotices-Repository.txt @@ -6,20 +6,17 @@ Microsoft Python extension for Visual Studio Code incorporates third party mater 1. Go for Visual Studio Code (https://github.com/Microsoft/vscode-go) 2. Files from the Python Project (https://www.python.org/) -3. Google Diff Match and Patch (https://github.com/GerHobbelt/google-diff-match-patch) -4. enchannel-zmq-backend (https://github.com/nteract/enchannel-zmq-backend) -6. omnisharp-vscode (https://github.com/OmniSharp/omnisharp-vscode) -8. PTVS (https://github.com/Microsoft/PTVS) -9. Python documentation (https://docs.python.org/) -10. python-functools32 (https://github.com/MiCHiLU/python-functools32/blob/master/functools32/functools32.py) -11. pythonVSCode (https://github.com/DonJayamanne/pythonVSCode) -12. Sphinx (http://sphinx-doc.org/) -13. nteract (https://github.com/nteract/nteract) -14. less-plugin-inline-urls (https://github.com/less/less-plugin-inline-urls/) -15. jupyter-notebook (https://github.com/jupyter/notebook) -16. ipywidgets (https://github.com/jupyter-widgets) -17. vscode-cpptools (https://github.com/microsoft/vscode-cpptools) -18. font-awesome (https://github.com/FortAwesome/Font-Awesome) +3. omnisharp-vscode (https://github.com/OmniSharp/omnisharp-vscode) +4. PTVS (https://github.com/Microsoft/PTVS) +5. Python documentation (https://docs.python.org/) +6. python-functools32 (https://github.com/MiCHiLU/python-functools32/blob/master/functools32/functools32.py) +7. pythonVSCode (https://github.com/DonJayamanne/pythonVSCode) +8. Sphinx (http://sphinx-doc.org/) +9. nteract (https://github.com/nteract/nteract) +10. less-plugin-inline-urls (https://github.com/less/less-plugin-inline-urls/) +11. vscode-cpptools (https://github.com/microsoft/vscode-cpptools) +12. mocha (https://github.com/mochajs/mocha) +13. get-pip (https://github.com/pypa/get-pip) %% Go for Visual Studio Code NOTICES, INFORMATION, AND LICENSE BEGIN HERE @@ -246,25 +243,6 @@ OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ========================================= END OF Files from the Python Project NOTICES, INFORMATION, AND LICENSE -%% Google Diff Match and Patch NOTICES, INFORMATION, AND LICENSE BEGIN HERE -========================================= - * Copyright 2006 Google Inc. - * http://code.google.com/p/google-diff-match-patch/ - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. -========================================= -END OF Google Diff Match and Patch NOTICES, INFORMATION, AND LICENSE - %% omnisharp-vscode NOTICES, INFORMATION, AND LICENSE BEGIN HERE ========================================= Copyright (c) Microsoft Corporation @@ -966,107 +944,9 @@ Apache License ========================================= END OF less-plugin-inline-urls NOTICES, INFORMATION, AND LICENSE -%% jupyter notebook NOTICES, INFORMATION, AND LICENSE BEGIN HERE -========================================= -# Licensing terms - -This project is licensed under the terms of the Modified BSD License -(also known as New or Revised or 3-Clause BSD), as follows: - -- Copyright (c) 2001-2015, IPython Development Team -- Copyright (c) 2015-, Jupyter Development Team - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of the Jupyter Development Team nor the names of its -contributors may be used to endorse or promote products derived from this -software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -## About the Jupyter Development Team - -The Jupyter Development Team is the set of all contributors to the Jupyter project. -This includes all of the Jupyter subprojects. - -The core team that coordinates development on GitHub can be found here: -https://github.com/jupyter/. - -## Our Copyright Policy - -Jupyter uses a shared copyright model. Each contributor maintains copyright -over their contributions to Jupyter. But, it is important to note that these -contributions are typically only changes to the repositories. Thus, the Jupyter -source code, in its entirety is not the copyright of any single person or -institution. Instead, it is the collective copyright of the entire Jupyter -Development Team. If individual contributors want to maintain a record of what -changes/contributions they have specific copyright on, they should indicate -their copyright in the commit message of the change, when they commit the -change to one of the Jupyter repositories. - -With this in mind, the following banner should be used in any source code file -to indicate the copyright and license terms: - - # Copyright (c) Jupyter Development Team. - # Distributed under the terms of the Modified BSD License. - -========================================= -END OF jupyter notebook NOTICES, INFORMATION, AND LICENSE - -%% ipywidgets NOTICES, INFORMATION, AND LICENSE BEGIN HERE -========================================= -Copyright (c) 2015 Project Jupyter Contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -========================================= -END OF ipywidgets NOTICES, INFORMATION, AND LICENSE - %% vscode-cpptools NOTICES, INFORMATION, AND LICENSE BEGIN HERE ========================================= -vscode-cpptools +vscode-cpptools Copyright (c) Microsoft Corporation @@ -1074,125 +954,81 @@ All rights reserved. MIT License -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, +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 +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 +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 +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 vscode-cpptools NOTICES, INFORMATION, AND LICENSE -%% enchannel-zmq-backend NOTICES, INFORMATION, AND LICENSE BEGIN HERE -Copyright (c) 2016, -All rights reserved. +%% mocha NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: +(The MIT License) -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. +Copyright (c) 2011-2020 OpenJS Foundation and contributors, https://openjsf.org -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. +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: -* Neither the name of enchannel-zmq-backend nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -========================================= -END OF enchannel-zmq-backend NOTICES, INFORMATION, AND LICENSE -======= - -%% font-awesome NOTICES, INFORMATION, AND LICENSE BEGIN HERE -========================================= -Font Name - FontAwesome - -Font Awesome Free License -------------------------- - -Font Awesome Free is free, open source, and GPL friendly. You can use it for -commercial projects, open source projects, or really almost whatever you want. -Full Font Awesome Free license: https://fontawesome.com/license/free. - -# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/) -In the Font Awesome Free download, the CC BY 4.0 license applies to all icons -packaged as SVG and JS file types. - -# Fonts: SIL OFL 1.1 License (https://scripts.sil.org/OFL) -In the Font Awesome Free download, the SIL OFL license applies to all icons -packaged as web and desktop font files. - -# Code: MIT License (https://opensource.org/licenses/MIT) -In the Font Awesome Free download, the MIT license applies to all non-font and -non-icon files. - -# Attribution -Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font -Awesome Free files already contain embedded comments with sufficient -attribution, so you shouldn't need to do anything additional when using these -files normally. - -We've kept attribution comments terse, so we ask that you do not actively work -to remove them from files, especially code. They're a great way for folks to -learn about Font Awesome. - -# Brand Icons -All brand icons are trademarks of their respective owners. The use of these -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 +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. -%% jedi-language-server NOTICES, INFORMATION, AND LICENSE BEGIN HERE ========================================= -jedi-language-server - -MIT License +END OF mocha NOTICES, INFORMATION, AND 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: +%% get-pip NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Copyright (c) 2008-2019 The pip developers + +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. -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 +END OF get-pip NOTICES, INFORMATION, AND LICENSE diff --git a/build/.eslintrc b/build/.eslintrc deleted file mode 100644 index 8a44c2cfe2cc..000000000000 --- a/build/.eslintrc +++ /dev/null @@ -1,41 +0,0 @@ -{ - "root": true, - "env": { - "node": true, - "es6": true, - "mocha": true - }, - "extends": [ - "airbnb" - ], - "rules": { - "comma-dangle": "off", - "func-names": "off", - "import/no-extraneous-dependencies": [ - "error", - { - "peerDependencies": true, - "devDependencies": true - } - ], - "import/prefer-default-export": "off", - "indent": [ - "error", - 4, - { - "SwitchCase": 1 - } - ], - "max-len": [ - "error", - { - "code": 120 - } - ], - "no-console": "off", - "no-param-reassign": "off", - "no-underscore-dangle": "off", - "no-useless-escape": "off", - "strict": "off" - } -} diff --git a/build/.mocha-multi-reporters.config b/build/.mocha-multi-reporters.config index 539aa1c15b60..abe46f117f5b 100644 --- a/build/.mocha-multi-reporters.config +++ b/build/.mocha-multi-reporters.config @@ -1,3 +1,3 @@ { - "reporterEnabled": "spec,mocha-junit-reporter" + "reporterEnabled": "./build/ci/scripts/spec_with_pid,mocha-junit-reporter" } diff --git a/build/.mocha.functional.json b/build/.mocha.functional.json index 3cebb9367a52..71998902e984 100644 --- a/build/.mocha.functional.json +++ b/build/.mocha.functional.json @@ -1,9 +1,11 @@ { "spec": "./out/test/**/*.functional.test.js", - "require": ["out/test/unittests.js"], + "require": [ + "out/test/unittests.js" + ], "exclude": "out/**/*.jsx", "reporter": "mocha-multi-reporters", - "reporter-option": "configFile=build/.mocha-multi-reporters.config", + "reporter-option": "configFile=./build/.mocha-multi-reporters.config", "ui": "tdd", "recursive": true, "colors": true, diff --git a/build/.mocha.performance.json b/build/.mocha.performance.json index 4e398d556887..84dc3952cc85 100644 --- a/build/.mocha.performance.json +++ b/build/.mocha.performance.json @@ -7,6 +7,5 @@ "recursive": true, "colors": true, "exit": true, - "timeout": 30000, - "grep": "DataScience" + "timeout": 30000 } diff --git a/build/.mocha.unittests.json b/build/.mocha.unittests.json index b4dcc393c7eb..cb6bff959497 100644 --- a/build/.mocha.unittests.json +++ b/build/.mocha.unittests.json @@ -1,9 +1,13 @@ { "spec": "./out/test/**/*.unit.test.js", - "require": ["out/test/unittests.js"], + "require": [ + "out/test/unittests.js" + ], + "exclude": "out/**/*.jsx", "reporter": "mocha-multi-reporters", - "reporter-option": "configFile=build/.mocha-multi-reporters.config", + "reporter-option": "configFile=./build/.mocha-multi-reporters.config", "ui": "tdd", "recursive": true, - "colors": true + "colors": true, + "timeout": 180000 } diff --git a/build/.nycrc b/build/.nycrc index e9540ef130f2..b92a4f36785d 100644 --- a/build/.nycrc +++ b/build/.nycrc @@ -2,8 +2,8 @@ "extends": "@istanbuljs/nyc-config-typescript", "all": true, "include": [ - "src/client/**/*.ts", "src/test/**/*.js", - "src/datascience-ui/**/*.ts", "src/datascience-ui/**/*.js" + "src/client/**/*.ts", "out/client/**/*.js" ], - "exclude": ["src/test/**/*.ts", "src/test/**/*.js"] -} \ No newline at end of file + "exclude": ["src/test/**/*.ts", "out/test/**/*.js"], + "exclude-node-modules": true +} diff --git a/build/azure-pipeline.pre-release.yml b/build/azure-pipeline.pre-release.yml new file mode 100644 index 000000000000..e7159618d3ae --- /dev/null +++ b/build/azure-pipeline.pre-release.yml @@ -0,0 +1,158 @@ +# Run on a schedule +trigger: none +pr: none + +schedules: + - cron: '0 10 * * 1-5' # 10AM UTC (2AM PDT) MON-FRI (VS Code Pre-release builds at 9PM PDT) + displayName: Nightly Pre-Release Schedule + always: false # only run if there are source code changes + branches: + include: + - main + +resources: + repositories: + - repository: templates + type: github + name: microsoft/vscode-engineering + ref: main + endpoint: Monaco + +parameters: + - name: publishExtension + displayName: 🚀 Publish Extension + type: boolean + default: false + +extends: + template: azure-pipelines/extension/pre-release.yml@templates + parameters: + publishExtension: ${{ parameters.publishExtension }} + ghCreateTag: false + standardizedVersioning: true + l10nSourcePaths: ./src/client + + buildPlatforms: + - name: Linux + vsceTarget: 'web' + - name: Linux + packageArch: arm64 + vsceTarget: linux-arm64 + - name: Linux + packageArch: arm + vsceTarget: linux-armhf + - name: Linux + packageArch: x64 + vsceTarget: linux-x64 + - name: Linux + packageArch: arm64 + vsceTarget: alpine-arm64 + - name: Linux + packageArch: x64 + vsceTarget: alpine-x64 + - name: MacOS + packageArch: arm64 + vsceTarget: darwin-arm64 + - name: MacOS + packageArch: x64 + vsceTarget: darwin-x64 + - name: Windows + packageArch: arm + vsceTarget: win32-arm64 + - name: Windows + packageArch: x64 + vsceTarget: win32-x64 + + buildSteps: + - task: NodeTool@0 + inputs: + versionSpec: '22.17.0' + displayName: Select Node version + + - task: UsePythonVersion@0 + inputs: + versionSpec: '3.9' + addToPath: true + architecture: 'x64' + displayName: Select Python version + + - script: python -m pip install -U pip + displayName: Upgrade pip + + - script: python -m pip install wheel nox + displayName: Install wheel and nox + + - script: npm ci + displayName: Install NPM dependencies + + - script: nox --session install_python_libs + displayName: Install Jedi, get-pip, etc + + - script: python ./build/update_package_file.py + displayName: Update telemetry in package.json + + - script: npm run addExtensionPackDependencies + displayName: Update optional extension dependencies + + - script: npx gulp prePublishBundle + displayName: Build + + - bash: | + mkdir -p $(Build.SourcesDirectory)/python-env-tools/bin + chmod +x $(Build.SourcesDirectory)/python-env-tools/bin + displayName: Make Directory for python-env-tool binary + + - bash: | + if [ "$(vsceTarget)" == "win32-x64" ]; then + echo "##vso[task.setvariable variable=buildTarget]x86_64-pc-windows-msvc" + elif [ "$(vsceTarget)" == "win32-arm64" ]; then + echo "##vso[task.setvariable variable=buildTarget]aarch64-pc-windows-msvc" + elif [ "$(vsceTarget)" == "linux-x64" ]; then + echo "##vso[task.setvariable variable=buildTarget]x86_64-unknown-linux-musl" + elif [ "$(vsceTarget)" == "linux-arm64" ]; then + echo "##vso[task.setvariable variable=buildTarget]aarch64-unknown-linux-gnu" + elif [ "$(vsceTarget)" == "linux-armhf" ]; then + echo "##vso[task.setvariable variable=buildTarget]armv7-unknown-linux-gnueabihf" + elif [ "$(vsceTarget)" == "darwin-x64" ]; then + echo "##vso[task.setvariable variable=buildTarget]x86_64-apple-darwin" + elif [ "$(vsceTarget)" == "darwin-arm64" ]; then + echo "##vso[task.setvariable variable=buildTarget]aarch64-apple-darwin" + elif [ "$(vsceTarget)" == "alpine-x64" ]; then + echo "##vso[task.setvariable variable=buildTarget]x86_64-unknown-linux-musl" + elif [ "$(vsceTarget)" == "alpine-arm64" ]; then + echo "##vso[task.setvariable variable=buildTarget]aarch64-unknown-linux-gnu" + elif [ "$(vsceTarget)" == "web" ]; then + echo "##vso[task.setvariable variable=buildTarget]x86_64-unknown-linux-musl" + else + echo "##vso[task.setvariable variable=buildTarget]x86_64-unknown-linux-musl" + fi + displayName: Set buildTarget variable + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'specific' + project: 'Monaco' + definition: 591 + buildVersionToDownload: 'latest' + branchName: 'refs/heads/main' + targetPath: '$(Build.SourcesDirectory)/python-env-tools/bin' + artifactName: 'bin-$(buildTarget)' + itemPattern: | + pet.exe + pet + ThirdPartyNotices.txt + + - bash: | + ls -lf ./python-env-tools/bin + chmod +x ./python-env-tools/bin/pet* + ls -lf ./python-env-tools/bin + displayName: Set chmod for pet binary + + - script: python -c "import shutil; shutil.rmtree('.nox', ignore_errors=True)" + displayName: Clean up Nox + + tsa: + config: + areaPath: 'Visual Studio Code Python Extensions' + serviceTreeID: '6e6194bc-7baa-4486-86d0-9f5419626d46' + enabled: true diff --git a/build/azure-pipeline.stable.yml b/build/azure-pipeline.stable.yml new file mode 100644 index 000000000000..cd66613eec8d --- /dev/null +++ b/build/azure-pipeline.stable.yml @@ -0,0 +1,153 @@ +trigger: none +# branches: +# include: +# - release* +# tags: +# include: ['*'] +pr: none + +resources: + repositories: + - repository: templates + type: github + name: microsoft/vscode-engineering + ref: main + endpoint: Monaco + +parameters: + - name: publishExtension + displayName: 🚀 Publish Extension + type: boolean + default: false + +extends: + template: azure-pipelines/extension/stable.yml@templates + parameters: + publishExtension: ${{ parameters.publishExtension }} + l10nSourcePaths: ./src/client + + buildPlatforms: + - name: Linux + vsceTarget: 'web' + - name: Linux + packageArch: arm64 + vsceTarget: linux-arm64 + - name: Linux + packageArch: arm + vsceTarget: linux-armhf + - name: Linux + packageArch: x64 + vsceTarget: linux-x64 + - name: Linux + packageArch: arm64 + vsceTarget: alpine-arm64 + - name: Linux + packageArch: x64 + vsceTarget: alpine-x64 + - name: MacOS + packageArch: arm64 + vsceTarget: darwin-arm64 + - name: MacOS + packageArch: x64 + vsceTarget: darwin-x64 + - name: Windows + packageArch: arm + vsceTarget: win32-arm64 + - name: Windows + packageArch: x64 + vsceTarget: win32-x64 + + buildSteps: + - task: NodeTool@0 + inputs: + versionSpec: '22.17.0' + displayName: Select Node version + + - task: UsePythonVersion@0 + inputs: + versionSpec: '3.9' + addToPath: true + architecture: 'x64' + displayName: Select Python version + + - script: python -m pip install -U pip + displayName: Upgrade pip + + - script: python -m pip install wheel nox + displayName: Install wheel and nox + + - script: npm ci + displayName: Install NPM dependencies + + - script: nox --session install_python_libs + displayName: Install Jedi, get-pip, etc + + - script: python ./build/update_package_file.py + displayName: Update telemetry in package.json + + - script: npm run addExtensionPackDependencies + displayName: Update optional extension dependencies + + - script: npx gulp prePublishBundle + displayName: Build + + - bash: | + mkdir -p $(Build.SourcesDirectory)/python-env-tools/bin + chmod +x $(Build.SourcesDirectory)/python-env-tools/bin + displayName: Make Directory for python-env-tool binary + + - bash: | + if [ "$(vsceTarget)" == "win32-x64" ]; then + echo "##vso[task.setvariable variable=buildTarget]x86_64-pc-windows-msvc" + elif [ "$(vsceTarget)" == "win32-arm64" ]; then + echo "##vso[task.setvariable variable=buildTarget]aarch64-pc-windows-msvc" + elif [ "$(vsceTarget)" == "linux-x64" ]; then + echo "##vso[task.setvariable variable=buildTarget]x86_64-unknown-linux-musl" + elif [ "$(vsceTarget)" == "linux-arm64" ]; then + echo "##vso[task.setvariable variable=buildTarget]aarch64-unknown-linux-gnu" + elif [ "$(vsceTarget)" == "linux-armhf" ]; then + echo "##vso[task.setvariable variable=buildTarget]armv7-unknown-linux-gnueabihf" + elif [ "$(vsceTarget)" == "darwin-x64" ]; then + echo "##vso[task.setvariable variable=buildTarget]x86_64-apple-darwin" + elif [ "$(vsceTarget)" == "darwin-arm64" ]; then + echo "##vso[task.setvariable variable=buildTarget]aarch64-apple-darwin" + elif [ "$(vsceTarget)" == "alpine-x64" ]; then + echo "##vso[task.setvariable variable=buildTarget]x86_64-unknown-linux-musl" + elif [ "$(vsceTarget)" == "alpine-arm64" ]; then + echo "##vso[task.setvariable variable=buildTarget]aarch64-unknown-linux-gnu" + elif [ "$(vsceTarget)" == "web" ]; then + echo "##vso[task.setvariable variable=buildTarget]x86_64-unknown-linux-musl" + else + echo "##vso[task.setvariable variable=buildTarget]x86_64-unknown-linux-musl" + fi + displayName: Set buildTarget variable + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'specific' + project: 'Monaco' + definition: 593 + buildVersionToDownload: 'latestFromBranch' + branchName: 'refs/heads/release/2026.4' + targetPath: '$(Build.SourcesDirectory)/python-env-tools/bin' + artifactName: 'bin-$(buildTarget)' + itemPattern: | + pet.exe + pet + ThirdPartyNotices.txt + + - bash: | + ls -lf ./python-env-tools/bin + chmod +x ./python-env-tools/bin/pet* + ls -lf ./python-env-tools/bin + displayName: Set chmod for pet binary + + - script: python -c "import shutil; shutil.rmtree('.nox', ignore_errors=True)" + displayName: Clean up Nox + tsa: + config: + areaPath: 'Visual Studio Code Python Extensions' + serviceTreeID: '6e6194bc-7baa-4486-86d0-9f5419626d46' + enabled: true + apiScanDependentPipelineId: '593' # python-environment-tools + apiScanSoftwareVersion: '2024' diff --git a/build/azure-pipelines/pipeline.yml b/build/azure-pipelines/pipeline.yml new file mode 100644 index 000000000000..0796e38ca598 --- /dev/null +++ b/build/azure-pipelines/pipeline.yml @@ -0,0 +1,58 @@ +############################################################################################### +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +############################################################################################### +name: $(Date:yyyyMMdd)$(Rev:.r) + +trigger: none + +pr: none + +resources: + repositories: + - repository: templates + type: github + name: microsoft/vscode-engineering + ref: main + endpoint: Monaco + +parameters: + - name: quality + displayName: Quality + type: string + default: latest + values: + - latest + - next + - name: publishPythonApi + displayName: 🚀 Publish pythonExtensionApi + type: boolean + default: false + +extends: + template: azure-pipelines/npm-package/pipeline.yml@templates + parameters: + npmPackages: + - name: pythonExtensionApi + testPlatforms: + - name: Linux + nodeVersions: + - 22.21.1 + - name: MacOS + nodeVersions: + - 22.21.1 + - name: Windows + nodeVersions: + - 22.21.1 + testSteps: + - template: /build/azure-pipelines/templates/test-steps.yml@self + parameters: + package: pythonExtensionApi + buildSteps: + - template: /build/azure-pipelines/templates/pack-steps.yml@self + parameters: + package: pythonExtensionApi + ghTagPrefix: release/pythonExtensionApi/ + tag: ${{ parameters.quality }} + publishPackage: ${{ parameters.publishPythonApi }} + workingDirectory: $(Build.SourcesDirectory)/pythonExtensionApi diff --git a/build/azure-pipelines/templates/pack-steps.yml b/build/azure-pipelines/templates/pack-steps.yml new file mode 100644 index 000000000000..97037efb59ba --- /dev/null +++ b/build/azure-pipelines/templates/pack-steps.yml @@ -0,0 +1,14 @@ +############################################################################################### +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +############################################################################################### +parameters: +- name: package + +steps: + - script: npm install --root-only + workingDirectory: $(Build.SourcesDirectory) + displayName: Install root dependencies + - script: npm install + workingDirectory: $(Build.SourcesDirectory)/${{ parameters.package }} + displayName: Install package dependencies diff --git a/build/azure-pipelines/templates/test-steps.yml b/build/azure-pipelines/templates/test-steps.yml new file mode 100644 index 000000000000..15eb3db6384d --- /dev/null +++ b/build/azure-pipelines/templates/test-steps.yml @@ -0,0 +1,23 @@ +############################################################################################### +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +############################################################################################### +parameters: +- name: package + type: string +- name: script + type: string + default: 'all:publish' + +steps: + - script: npm install --root-only + workingDirectory: $(Build.SourcesDirectory) + displayName: Install root dependencies + - bash: | + /usr/bin/Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & + echo ">>> Started xvfb" + displayName: Start xvfb + condition: eq(variables['Agent.OS'], 'Linux') + - script: npm run ${{ parameters.script }} + workingDirectory: $(Build.SourcesDirectory)/${{ parameters.package }} + displayName: Verify package diff --git a/build/build-install-requirements.txt b/build/build-install-requirements.txt new file mode 100644 index 000000000000..8baaa59ded67 --- /dev/null +++ b/build/build-install-requirements.txt @@ -0,0 +1,2 @@ +# Requirements needed to run install_debugpy.py and download_get_pip.py +packaging diff --git a/build/ci/addEnvPath.py b/build/ci/addEnvPath.py index abad9ec3b5c9..66eff2a7b25d 100644 --- a/build/ci/addEnvPath.py +++ b/build/ci/addEnvPath.py @@ -3,7 +3,8 @@ #Adds the virtual environment's executable path to json file -import json,sys +import json +import sys import os.path jsonPath = sys.argv[1] key = sys.argv[2] diff --git a/build/ci/codecov.yml b/build/ci/codecov.yml deleted file mode 100644 index 8ed52955f093..000000000000 --- a/build/ci/codecov.yml +++ /dev/null @@ -1,27 +0,0 @@ -codecov: - notify: - require_ci_to_pass: yes - -coverage: - precision: 0 - round: down - range: '70...100' - - status: - project: yes - patch: yes - changes: no - -parsers: - gcov: - branch_detection: - conditional: yes - loop: yes - method: no - macro: no - -comment: - branch: !release* - layout: 'header, diff, files' - behavior: default - require_changes: no diff --git a/build/ci/conda_base.yml b/build/ci/conda_base.yml index 0467c61b793d..a1b589e38a32 100644 --- a/build/ci/conda_base.yml +++ b/build/ci/conda_base.yml @@ -1,5 +1 @@ -pandas -jupyter -numpy -matplotlib pip diff --git a/build/ci/conda_env_1.yml b/build/ci/conda_env_1.yml index 8d5ef6398197..4f9ceefd27fb 100644 --- a/build/ci/conda_env_1.yml +++ b/build/ci/conda_env_1.yml @@ -1,8 +1,4 @@ name: conda_env_1 dependencies: - - python=3.7 - - pandas - - jupyter - - numpy - - matplotlib + - python=3.9 - pip diff --git a/build/ci/conda_env_2.yml b/build/ci/conda_env_2.yml index 6ccdb55b80b7..af9d7a46ba3e 100644 --- a/build/ci/conda_env_2.yml +++ b/build/ci/conda_env_2.yml @@ -1,8 +1,4 @@ name: conda_env_2 dependencies: - - python=3.8 - - pandas - - jupyter - - numpy - - matplotlib + - python=3.9 - pip diff --git a/build/ci/performance/DS_test_benchmark.json b/build/ci/performance/DS_test_benchmark.json deleted file mode 100644 index 5d83e811700a..000000000000 --- a/build/ci/performance/DS_test_benchmark.json +++ /dev/null @@ -1,542 +0,0 @@ -[ - { - "name": "DataScience gotocell tests Basic execution", - "time": 0.135 - }, - { - "name": "DataScience gotocell tests Basic edit", - "time": 0.199 - }, - { - "name": "DataScience Error Handler Functional Tests Jupyter not installed", - "time": 0.215 - }, - { - "name": "DataScience Intellisense tests Simple autocomplete", - "time": 0.623 - }, - { - "name": "DataScience Intellisense tests Multiple interpreters", - "time": 0.535 - }, - { - "name": "DataScience Intellisense tests Jupyter autocomplete", - "time": 0.425 - }, - { - "name": "DataScience Intellisense tests Jupyter autocomplete not timeout", - "time": 0.496 - }, - { - "name": "DataScience Intellisense tests Filtered Jupyter autocomplete, verify magic commands appear", - "time": 0.409 - }, - { - "name": "DataScience Intellisense tests Filtered Jupyter autocomplete, verify magic commands are filtered", - "time": 0.366 - }, - { - "name": "DataScience Interactive Panel Input Cell is displayed", - "time": 0.121 - }, - { - "name": "DataScience Interactive Window output tests Simple text", - "time": 0.528 - }, - { - "name": "DataScience Interactive Window output tests Clear output", - "time": 0.495 - }, - { - "name": "DataScience Interactive Window output tests Hide inputs", - "time": 0.559 - }, - { - "name": "DataScience Interactive Window output tests Show inputs", - "time": 0.481 - }, - { - "name": "DataScience Interactive Window output tests Expand inputs", - "time": 0.496 - }, - { - "name": "DataScience Interactive Window output tests Ctrl + 1/Ctrl + 2", - "time": 0.427 - }, - { - "name": "DataScience Interactive Window output tests Escape/Ctrl+U", - "time": 0.365 - }, - { - "name": "DataScience Interactive Window output tests Click outside cells sets focus to input box", - "time": 0.447 - }, - { - "name": "DataScience Interactive Window output tests Collapse / expand cell", - "time": 0.568 - }, - { - "name": "DataScience Interactive Window output tests Hide / show cell", - "time": 0.486 - }, - { - "name": "DataScience Interactive Window output tests Mime Types", - "time": 1.141 - }, - { - "name": "DataScience Interactive Window output tests Undo/redo commands", - "time": 0.74 - }, - { - "name": "DataScience Interactive Window output tests Click buttons", - "time": 0.706 - }, - { - "name": "DataScience Interactive Window output tests Export", - "time": 0.462 - }, - { - "name": "DataScience Interactive Window output tests Dispose test", - "time": 0.466 - }, - { - "name": "DataScience Interactive Window output tests Editor Context", - "time": 0.469 - }, - { - "name": "DataScience Interactive Window output tests Simple input", - "time": 0.516 - }, - { - "name": "DataScience Interactive Window output tests Copy to source input", - "time": 0.561 - }, - { - "name": "DataScience Interactive Window output tests Multiple input", - "time": 0.96 - }, - { - "name": "DataScience Interactive Window output tests Restart with session failure", - "time": 0.89 - }, - { - "name": "DataScience Interactive Window output tests Gather code run from text editor", - "time": 0.485 - }, - { - "name": "DataScience Interactive Window output tests Gather code run from input box", - "time": 0.502 - }, - { - "name": "DataScience Interactive Window output tests Copy back to source", - "time": 0.296 - }, - { - "name": "DataScience Interactive Window output tests Limit text output", - "time": 0.431 - }, - { - "name": "DataScience Interactive Window output tests Type in input", - "time": 0.617 - }, - { - "name": "DataScience LiveShare tests Host alone", - "time": 0.473 - }, - { - "name": "DataScience LiveShare tests Host Shutdown and Run", - "time": 0.596 - }, - { - "name": "DataScience LiveShare tests Going through codewatcher", - "time": 0.711 - }, - { - "name": "DataScience LiveShare tests Export from guest", - "time": 0.138 - }, - { - "name": "DataScience LiveShare tests Guest does not have extension", - "time": 1.839 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Simple text", - "time": 0.596 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Invalid session still runs", - "time": 0.515 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Invalid kernel still runs", - "time": 0.563 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Mime Types", - "time": 1.979 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Click buttons", - "time": 0.551 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Server already loaded", - "time": 2.183 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Server load skipped", - "time": 0.672 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Convert to python", - "time": 0.563 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Save As", - "time": 0.516 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests RunAllCells", - "time": 0.629 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Startup and shutdown", - "time": 1.198 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Failure", - "time": 0.745 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Selection/Focus Markdown saved when selecting another cell", - "time": 0.464 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Model updates Add a cell and undo", - "time": 0.179 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Model updates Edit a cell and undo", - "time": 0.962 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Model updates Remove, move, and undo", - "time": 0.339 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Model updates Update as user types into editor (update redux store and model)", - "time": 0.609 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Model updates Updates are not lost when switching to markdown (update redux store and model)", - "time": 0.698 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Keyboard Shortcuts Traverse cells by using ArrowUp and ArrowDown, k and j", - "time": 0.307 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Keyboard Shortcuts Traverse cells by using ArrowUp and ArrowDown, k and j", - "time": 0.155 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Keyboard Shortcuts Pressing 'Shift+Enter' on a selected cell executes the cell and advances to the next cell", - "time": 0.529 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Keyboard Shortcuts Pressing 'Ctrl+Enter' on a selected cell executes the cell and cell selection is not changed", - "time": 0.283 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Keyboard Shortcuts Pressing 'Alt+Enter' on a selected cell adds a new cell below it", - "time": 0.158 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Keyboard Shortcuts Auto brackets work", - "time": 0.343 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Keyboard Shortcuts Navigating cells using up/down keys while focus is set to editor", - "time": 0.154 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Keyboard Shortcuts Navigating cells using up/down keys through code & markdown cells, while focus is set to editor", - "time": 1.632 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Keyboard Shortcuts Pressing 'a' on a selected cell adds a cell at the current position", - "time": 0.201 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Keyboard Shortcuts Pressing 'b' on a selected cell adds a cell after the current position", - "time": 0.21 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Keyboard Shortcuts Toggle visibility of output", - "time": 0.358 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Keyboard Shortcuts Toggle markdown and code modes using 'y' and 'm' keys (cells should not be focused)", - "time": 0.274 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Keyboard Shortcuts Toggle markdown and code modes using 'y' and 'm' keys & ensure changes to cells is preserved", - "time": 0.568 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Keyboard Shortcuts Test undo using the key 'z'", - "time": 2.229 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Keyboard Shortcuts Test save using the key 'ctrl+s' on Windows", - "time": 0.785 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Keyboard Shortcuts Test save using the key 'ctrl+s' on Mac", - "time": 1.685 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Keyboard Shortcuts Test save using the key 'cmd+s' on a Mac", - "time": 0.655 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Keyboard Shortcuts Test save using the key 'cmd+s' on a Windows", - "time": 1.671 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Auto Save Auto save notebook every 1s", - "time": 3.03 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Auto Save File saved with same format", - "time": 2.031 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Auto Save Should not auto save notebook, ever", - "time": 5.039 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Auto Save Auto save notebook when focus changes from active editor to none", - "time": 0.425 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Auto Save Auto save notebook when focus changes from active editor to something else", - "time": 0.435 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Auto Save Should not auto save notebook when active editor changes", - "time": 5.014 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Auto Save Auto save notebook when window state changes to being not focused", - "time": 0.456 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Auto Save Auto save notebook when window state changes to being focused", - "time": 0.649 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Auto Save Auto save notebook when window state changes to being focused for focusChange", - "time": 0.471 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Auto Save Auto save notebook when window state changes to being not focused for focusChange", - "time": 0.48 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Auto Save Auto save notebook when view state changes", - "time": 0.495 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Update Metadata Update notebook metadata on execution", - "time": 0.885 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Clear Outputs Clear Outputs in WebView", - "time": 0.479 - }, - { - "name": "DataScience Native Editor Without Custom Editor API Editor tests Clear Outputs Clear execution_count and outputs in notebook", - "time": 0.508 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Simple text", - "time": 0.966 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Invalid session still runs", - "time": 0.894 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Invalid kernel still runs", - "time": 0.946 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Mime Types", - "time": 3.283 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Click buttons", - "time": 0.984 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Server already loaded", - "time": 2.298 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Server load skipped", - "time": 0.778 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Convert to python", - "time": 1.022 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests RunAllCells", - "time": 1.048 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Startup and shutdown", - "time": 2.3 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Failure", - "time": 1.219 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Selection/Focus Markdown saved when selecting another cell", - "time": 1.319 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Model updates Add a cell and undo", - "time": 0.257 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Model updates Edit a cell and undo", - "time": 2.682 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Model updates Remove, move, and undo", - "time": 0.709 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Model updates Update as user types into editor (update redux store and model)", - "time": 1.802 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Model updates Updates are not lost when switching to markdown (update redux store and model)", - "time": 1.91 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Keyboard Shortcuts Traverse cells by using ArrowUp and ArrowDown, k and j", - "time": 0.341 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Keyboard Shortcuts Traverse cells by using ArrowUp and ArrowDown, k and j", - "time": 0.164 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Keyboard Shortcuts Pressing 'Shift+Enter' on a selected cell executes the cell and advances to the next cell", - "time": 0.987 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Keyboard Shortcuts Pressing 'Ctrl+Enter' on a selected cell executes the cell and cell selection is not changed", - "time": 0.425 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Keyboard Shortcuts Pressing 'Alt+Enter' on a selected cell adds a new cell below it", - "time": 0.287 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Keyboard Shortcuts Auto brackets work", - "time": 0.796 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Keyboard Shortcuts Navigating cells using up/down keys while focus is set to editor", - "time": 0.146 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Keyboard Shortcuts Navigating cells using up/down keys through code & markdown cells, while focus is set to editor", - "time": 3.292 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Keyboard Shortcuts Pressing 'a' on a selected cell adds a cell at the current position", - "time": 0.331 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Keyboard Shortcuts Pressing 'b' on a selected cell adds a cell after the current position", - "time": 0.349 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Keyboard Shortcuts Toggle visibility of output", - "time": 0.509 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Keyboard Shortcuts Toggle markdown and code modes using 'y' and 'm' keys (cells should not be focused)", - "time": 0.358 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Keyboard Shortcuts Toggle markdown and code modes using 'y' and 'm' keys & ensure changes to cells is preserved", - "time": 1.251 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Update Metadata Update notebook metadata on execution", - "time": 1.242 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Clear Outputs Clear Outputs in WebView", - "time": 0.593 - }, - { - "name": "DataScience Native Editor With Custom Editor API Editor tests Clear Outputs Clear execution_count and outputs in notebook", - "time": 0.614 - }, - { - "name": "DataScience notebook tests Verify manual working directory", - "time": 0.117 - }, - { - "name": "DataScience notebook tests Verify ${fileDirname} working directory", - "time": 0.353 - }, - { - "name": "DataScience notebook tests Change Interpreter", - "time": 0.218 - }, - { - "name": "DataScience notebook tests Restart kernel", - "time": 0.492 - }, - { - "name": "DataScience notebook tests Cancel execution", - "time": 0.318 - }, - { - "name": "DataScience notebook tests Interrupt kernel", - "time": 15.383 - }, - { - "name": "DataScience notebook tests MimeTypes", - "time": 0.603 - }, - { - "name": "DataScience notebook tests Non default config fails", - "time": 0.104 - }, - { - "name": "DataScience notebook tests Invalid kernel spec works", - "time": 0.151 - }, - { - "name": "DataScience notebook tests Server cache working", - "time": 0.255 - }, - { - "name": "DataScience notebook tests Server death", - "time": 0.282 - }, - { - "name": "DataScience notebook tests Execution logging", - "time": 0.152 - } -] diff --git a/build/ci/performance/checkPerformanceResults.js b/build/ci/performance/checkPerformanceResults.js deleted file mode 100644 index f632fd0b5224..000000000000 --- a/build/ci/performance/checkPerformanceResults.js +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -const fs = require('fs'); -const path = require('path'); -const constants = require('../../constants'); - -const benchmarkFile = path.join(constants.ExtensionRootDir, 'build', 'ci', 'performance', 'DS_test_benchmark.json'); -const performanceResultsFile = path.join( - constants.ExtensionRootDir, - 'build', - 'ci', - 'performance', - 'performance-results.json' -); -const errorMargin = 1.1; -let failedTests = ''; - -function getFailingTimesString(missedTimes) { - let printValue = ''; - for (const time of missedTimes) { - printValue += String(time) + ', '; - } - return printValue.substring(0, printValue.length - 2); -} - -fs.readFile(benchmarkFile, 'utf8', (benchmarkError, benchmark) => { - if (benchmarkError) { - throw benchmarkError; - } - - fs.readFile(performanceResultsFile, 'utf8', (performanceResultsFileError, performanceData) => { - if (performanceResultsFileError) { - throw performanceResultsFileError; - } - - const benchmarkJson = JSON.parse(benchmark); - const performanceJson = JSON.parse(performanceData); - - performanceJson.forEach((result) => { - const cleanTimes = result.times.filter((x) => x !== 'S' && x !== 'F'); - const n = cleanTimes.length; - const testcase = benchmarkJson.find((x) => x.name === result.name); - - if (testcase && testcase.time !== 'S') { - if (n === 0 && result.times.every((t) => t === 'F')) { - // Test failed every time - failedTests += 'Failed every time: ' + testcase.name + '\n'; - } else { - let missedTimes = []; - for (let time of cleanTimes) { - if (parseFloat(time) > parseFloat(testcase.time) * errorMargin) { - missedTimes.push(parseFloat(time)); - } - } - - if (missedTimes.length >= 2) { - const skippedTimes = result.times.filter((t) => t === 'S'); - const failedTimes = result.times.filter((t) => t === 'F'); - - failedTests += - 'Performance is slow in: ' + - testcase.name + - '.\n\tBenchmark time: ' + - String(parseFloat(testcase.time) * errorMargin) + - '\n\tTimes the test missed the benchmark: ' + - missedTimes.length + - '\n\tFailing times: ' + - getFailingTimesString(missedTimes) + - '\n\tTimes it was skipped: ' + - skippedTimes.length + - '\n\tTimes it failed: ' + - failedTimes.length + - '\n'; - } - } - } - }); - - // Delete performance-results.json - fs.unlink(performanceResultsFile, (deleteError) => { - if (deleteError) { - if (failedTests.length > 0) { - console.log(failedTests); - } - throw deleteError; - } - }); - - if (failedTests.length > 0) { - throw new Error(failedTests); - } - }); -}); diff --git a/build/ci/performance/createNewPerformanceBenchmark.js b/build/ci/performance/createNewPerformanceBenchmark.js deleted file mode 100644 index 7cdf93d3ac81..000000000000 --- a/build/ci/performance/createNewPerformanceBenchmark.js +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -const fastXmlParser = require('fast-xml-parser'); -const fs = require('fs'); -const path = require('path'); -const constants = require('../../constants'); - -const xmlFile = path.join(constants.ExtensionRootDir, 'xunit-test-results.xml'); -let performanceData = []; - -function getTime(testcase) { - if (testcase.failure) { - return 'F'; - } else if (testcase.skipped === '') { - return 'S'; - } - return parseFloat(testcase.time); -} - -fs.readFile(xmlFile, 'utf8', (xmlReadError, xmlData) => { - if (xmlReadError) { - throw xmlReadError; - } - - if (fastXmlParser.validate(xmlData)) { - const defaultOptions = { - attributeNamePrefix: '', - ignoreAttributes: false - }; - const jsonObj = fastXmlParser.parse(xmlData, defaultOptions); - - jsonObj.testsuite.testcase.forEach((testcase) => { - const test = { - name: testcase.classname + ' ' + testcase.name, - time: getTime(testcase) - }; - - if (test.time !== 'S' && test.time > 0.1) { - performanceData.push(test); - } - }); - - fs.writeFile( - path.join(constants.ExtensionRootDir, 'build', 'ci', 'performance', 'DS_test_benchmark.json'), - JSON.stringify(performanceData, null, 2), - (writeResultsError) => { - if (writeResultsError) { - throw writeResultsError; - } - console.log('DS_test_benchmark.json was saved!'); - } - ); - } -}); diff --git a/build/ci/performance/perfJobs.yaml b/build/ci/performance/perfJobs.yaml deleted file mode 100644 index f902ff0c5f9d..000000000000 --- a/build/ci/performance/perfJobs.yaml +++ /dev/null @@ -1,79 +0,0 @@ -jobs: - - job: Testing_Round_1 - timeoutInMinutes: 120 - strategy: - matrix: - Python37: - PythonVersion: '3.7' - pool: DSPerf - steps: - - task: Npm@1 - displayName: 'npm ci' - inputs: - workingDir: ${{ parameters.workingDirectory }} - command: custom - verbose: true - customCommand: ci - - task: Gulp@0 - displayName: 'Compile and check for errors' - inputs: - targets: 'prePublishNonBundle' - - powershell: | - mocha --require source-map-support/register --config ./build/.mocha.performance.json - node ./build/ci/performance/savePerformanceResults.js - - - job: Testing_Round_2 - dependsOn: - - Testing_Round_1 - timeoutInMinutes: 120 - strategy: - matrix: - Python37: - PythonVersion: '3.7' - pool: DSPerf - steps: - - powershell: | - mocha --require source-map-support/register --config ./build/.mocha.performance.json - node ./build/ci/performance/savePerformanceResults.js - - - job: Testing_Round_3 - dependsOn: - - Testing_Round_2 - timeoutInMinutes: 120 - strategy: - matrix: - Python37: - PythonVersion: '3.7' - pool: DSPerf - steps: - - powershell: | - mocha --require source-map-support/register --config ./build/.mocha.performance.json - node ./build/ci/performance/savePerformanceResults.js - - - job: Testing_Round_4 - dependsOn: - - Testing_Round_3 - timeoutInMinutes: 120 - strategy: - matrix: - Python37: - PythonVersion: '3.7' - pool: DSPerf - steps: - - powershell: | - mocha --require source-map-support/register --config ./build/.mocha.performance.json - node ./build/ci/performance/savePerformanceResults.js - - - job: Testing_Round_5 - dependsOn: - - Testing_Round_4 - timeoutInMinutes: 120 - strategy: - matrix: - Python37: - PythonVersion: '3.7' - pool: DSPerf - steps: - - powershell: | - mocha --require source-map-support/register --config ./build/.mocha.performance.json - node ./build/ci/performance/savePerformanceResults.js diff --git a/build/ci/performance/savePerformanceResults.js b/build/ci/performance/savePerformanceResults.js deleted file mode 100644 index 456eb254331b..000000000000 --- a/build/ci/performance/savePerformanceResults.js +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -const fastXmlParser = require('fast-xml-parser'); -const fs = require('fs'); -const path = require('path'); -const constants = require('../../constants'); - -const xmlFile = path.join(constants.ExtensionRootDir, 'xunit-test-results.xml'); -const jsonFile = path.join(constants.ExtensionRootDir, 'build', 'ci', 'performance', 'performance-results.json'); -let performanceData = []; - -function getTime(testcase) { - if (testcase.failure) { - return 'F'; - } else if (testcase.skipped === '') { - return 'S'; - } - return parseFloat(testcase.time); -} - -fs.readFile(xmlFile, 'utf8', (xmlReadError, xmlData) => { - if (xmlReadError) { - throw xmlReadError; - } - - if (fastXmlParser.validate(xmlData)) { - const defaultOptions = { - attributeNamePrefix: '', - ignoreAttributes: false - }; - const jsonObj = fastXmlParser.parse(xmlData, defaultOptions); - - fs.readFile(jsonFile, 'utf8', (jsonReadError, data) => { - if (jsonReadError) { - // File doesn't exist, so we create it - jsonObj.testsuite.testcase.forEach((testcase) => { - const test = { - name: testcase.classname + ' ' + testcase.name, - times: [getTime(testcase)] - }; - - performanceData.push(test); - }); - } else { - performanceData = JSON.parse(data); - - jsonObj.testsuite.testcase.forEach((testcase) => { - let test = performanceData.find((x) => x.name === testcase.classname + ' ' + testcase.name); - let time = getTime(testcase); - - if (test) { - // if the test name is already there, we add the new time - test.times.push(time); - } else { - // if its not there, we add the whole thing - const test = { - name: testcase.classname + ' ' + testcase.name, - times: [time] - }; - - performanceData.push(test); - } - }); - } - - fs.writeFile( - path.join(constants.ExtensionRootDir, 'build', 'ci', 'performance', 'performance-results.json'), - JSON.stringify(performanceData, null, 2), - (writeResultsError) => { - if (writeResultsError) { - throw writeResultsError; - } - console.log('performance-results.json was saved!'); - } - ); - }); - } -}); diff --git a/build/ci/performance/vscode-python-performance.yaml b/build/ci/performance/vscode-python-performance.yaml deleted file mode 100644 index c0de78757a36..000000000000 --- a/build/ci/performance/vscode-python-performance.yaml +++ /dev/null @@ -1,26 +0,0 @@ -name: '$(Year:yyyy).$(Month).0.$(BuildID)-weekly-performance-test' - -trigger: none -pr: none -schedules: - - cron: '0 0 * * sat' - displayName: Weekly Performance Test - branches: - include: - - main - -stages: - - stage: Test_performance - jobs: - - template: perfJobs.yaml - - - stage: Results - dependsOn: - - Test_performance - jobs: - - job: CheckResults - timeoutInMinutes: 5 - pool: DSPerf - steps: - - powershell: | - node ./build/ci/performance/checkPerformanceResults.js diff --git a/build/ci/postInstall.js b/build/ci/postInstall.js deleted file mode 100644 index be42594e0504..000000000000 --- a/build/ci/postInstall.js +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -var colors = require('colors/safe'); -var fs = require('fs'); -var path = require('path'); -var constants_1 = require('../constants'); -/** - * In order to compile the extension in strict mode, one of the dependencies (@jupyterlab) has some files that - * just won't compile in strict mode. - * Unfortunately we cannot fix it by overriding their type definitions - * Note: that has been done for a few of the JupyterLabl files (see typings/index.d.ts). - * The solution is to modify the type definition file after `npm install`. - */ -function fixJupyterLabDTSFiles() { - var relativePath = path.join( - 'node_modules', - '@jupyterlab', - 'services', - 'node_modules', - '@jupyterlab', - 'coreutils', - 'lib', - 'settingregistry.d.ts' - ); - var filePath = path.join(constants_1.ExtensionRootDir, relativePath); - if (!fs.existsSync(filePath)) { - throw new Error("Type Definition file from JupyterLab not found '" + filePath + "' (pvsc post install script)"); - } - var fileContents = fs.readFileSync(filePath, { encoding: 'utf8' }); - if (fileContents.indexOf('[key: string]: ISchema | undefined;') > 0) { - // tslint:disable-next-line:no-console - console.log(colors.blue(relativePath + ' file already updated (by Python VSC)')); - return; - } - if (fileContents.indexOf('[key: string]: ISchema;') > 0) { - var replacedText = fileContents.replace('[key: string]: ISchema;', '[key: string]: ISchema | undefined;'); - if (fileContents === replacedText) { - throw new Error("Fix for JupyterLabl file 'settingregistry.d.ts' failed (pvsc post install script)"); - } - fs.writeFileSync(filePath, replacedText); - // tslint:disable-next-line:no-console - console.log(colors.green(relativePath + ' file updated (by Python VSC)')); - } else { - // tslint:disable-next-line:no-console - console.log(colors.red(relativePath + ' file does not need updating.')); - } -} - -/** - * In order to get raw kernels working, we reuse the default kernel that jupyterlab ships. - * However it expects to be talking to a websocket which is serializing the messages to strings. - * Our raw kernel is not a web socket and needs to do its own serialization. To do so, we make a copy - * of the default kernel with the serialization stripped out. This is simpler than making a copy of the module - * at runtime. - */ -function createJupyterKernelWithoutSerialization() { - var relativePath = path.join('node_modules', '@jupyterlab', 'services', 'lib', 'kernel', 'default.js'); - var filePath = path.join(constants_1.ExtensionRootDir, relativePath); - if (!fs.existsSync(filePath)) { - throw new Error("Jupyter lab default kernel not found '" + filePath + "' (pvsc post install script)"); - } - var fileContents = fs.readFileSync(filePath, { encoding: 'utf8' }); - var replacedContents = fileContents.replace( - /^const serialize =.*$/gm, - 'const serialize = { serialize: (a) => a, deserialize: (a) => a };' - ); - if (replacedContents === fileContents) { - throw new Error('Jupyter lab default kernel cannot be made non serializing'); - } - var destPath = path.join(path.dirname(filePath), 'nonSerializingKernel.js'); - fs.writeFileSync(destPath, replacedContents); - console.log(colors.green(destPath + ' file generated (by Python VSC)')); -} - -fixJupyterLabDTSFiles(); -createJupyterKernelWithoutSerialization(); diff --git a/build/ci/pyproject.toml b/build/ci/pyproject.toml new file mode 100644 index 000000000000..6335f021a637 --- /dev/null +++ b/build/ci/pyproject.toml @@ -0,0 +1,8 @@ +[tool.poetry] +name = "poetry-tutorial-project" +version = "0.1.0" +description = "" +authors = [""] + +[tool.poetry.dependencies] +python = "*" diff --git a/build/ci/scripts/runFunctionalTests.js b/build/ci/scripts/runFunctionalTests.js deleted file mode 100644 index e1e8de8a4318..000000000000 --- a/build/ci/scripts/runFunctionalTests.js +++ /dev/null @@ -1,160 +0,0 @@ -// This script will run all of the functional tests for each functional test file in sequence -// This prevents mocha from running out of memory when running all of the tests -// -// This could potentially be improved to run tests in parallel (try that later) -// -// Additionally this was written in python at first but running python on an azure -// machine may pick up an invalid environment for the subprocess. Node doesn't have this problem -var path = require('path'); -var glob = require('glob'); -var child_process = require('child_process'); -var fs = require('fs-extra'); - -// Create a base for the output file -var originalMochaFile = process.env['MOCHA_FILE']; -var mochaFile = originalMochaFile || './test-results.xml'; -var mochaBaseFile = path.join(path.dirname(mochaFile), path.basename(mochaFile, '.xml')); -var mochaFileExt = '.xml'; -var groupCount = 4; - -function gatherArgs(extraArgs, file) { - return [ - file, - '--require=out/test/unittests.js', - '--exclude=out/**/*.jsx', - '--reporter=mocha-multi-reporters', - '--reporter-option=configFile=build/.mocha-multi-reporters.config', - '--ui=tdd', - '--recursive', - '--colors', - '--exit', - '--timeout=180000', - ...extraArgs - ]; -} - -async function generateGroups(files) { - // Go through each file putting it into a bucket. Each bucket will attempt to - // have equal size - - // Start with largest files first (sort by size) - var stats = await Promise.all(files.map((f) => fs.stat(f))); - var filesWithSize = files.map((f, i) => { - return { - file: f, - size: stats[i].size - }; - }); - var sorted = filesWithSize.sort((a, b) => b.size - a.size); - - // Generate buckets that try to hold the largest file first - var buckets = new Array(groupCount).fill().map((_, i) => { - return { - index: i, - totalSize: 0, - files: [] - }; - }); - var lowestBucket = buckets[0]; - sorted.forEach((fs) => { - buckets[lowestBucket.index].totalSize += fs.size; - buckets[lowestBucket.index].files.push(fs.file); - lowestBucket = buckets.find((b) => b.totalSize < lowestBucket.totalSize) || lowestBucket; - }); - - // Return these groups of files - return buckets.map((b) => b.files); -} - -async function runIndividualTest(extraArgs, file, index) { - var subMochaFile = `${mochaBaseFile}_${index}_${path.basename(file)}${mochaFileExt}`; - process.env['MOCHA_FILE'] = subMochaFile; - var args = gatherArgs(extraArgs, file); - console.log(`Running functional test for file ${file} ...`); - var exitCode = await new Promise((resolve) => { - // Spawn the sub node process - var proc = child_process.fork('./node_modules/mocha/bin/_mocha', args); - proc.on('exit', resolve); - }); - - // If failed keep track - if (exitCode !== 0) { - console.log(`Functional tests for ${file} failed.`); - } else { - console.log(`Functional test for ${file} succeeded`); - } - - return exitCode; -} - -// Wrap async code in a function so can wait till done -async function main() { - console.log('Globbing files for functional tests'); - - // Glob all of the files that we usually send to mocha as a group (see mocha.functional.opts.xml) - var files = await new Promise((resolve, reject) => { - glob('./out/test/datascience/**/*.functional.test.js', (ex, res) => { - if (ex) { - reject(ex); - } else { - resolve(res); - } - }); - }); - - // Figure out what group is running (should be something like --group1, --group2 etc.) - var groupArgIndex = process.argv.findIndex((a) => a.includes('--group')); - var groupIndex = groupArgIndex >= 0 ? parseInt(process.argv[groupArgIndex].slice(7), 10) - 1 : -1; - - // Generate 4 groups based on sorting by size - var groups = await generateGroups(files); - files = groupIndex >= 0 ? groups[groupIndex] : files; - console.log(`Running for group ${groupIndex}`); - - // Extract any extra args for the individual mocha processes - var extraArgs = - groupIndex >= 0 && process.argv.length > 3 - ? process.argv.slice(3) - : process.argv.length > 2 - ? process.argv.slice(2) - : []; - - // Iterate over them, running mocha on each - var returnCode = 0; - - // Start timing now (don't care about glob time) - var startTime = Date.now(); - - // Run all of the tests (in parallel or sync based on env) - try { - if (process.env.VSCODE_PYTHON_FORCE_TEST_SYNC) { - for (var i = 0; i < files.length; i += 1) { - // Synchronous, one at a time - returnCode = returnCode | (await runIndividualTest(extraArgs, files[i], i)); - } - } else { - // Parallel, all at once - const returnCodes = await Promise.all(files.map(runIndividualTest.bind(undefined, extraArgs))); - - // Or all of the codes together - returnCode = returnCodes.reduce((p, c) => p | c); - } - } catch (ex) { - console.log(`Functional tests run failure: ${ex}.`); - returnCode = -1; - } - - // Reset the mocha file variable - if (originalMochaFile) { - process.env['MOCHA_FILE'] = originalMochaFile; - } - - var endTime = Date.now(); - - // Indicate error code and total time of the run - console.log(`Functional test run result: ${returnCode} after ${(endTime - startTime) / 1_000} seconds`); - process.exit(returnCode); -} - -// Call the main function. It will exit when promise is finished. -main(); diff --git a/build/ci/scripts/spec_with_pid.js b/build/ci/scripts/spec_with_pid.js new file mode 100644 index 000000000000..a8453353aa79 --- /dev/null +++ b/build/ci/scripts/spec_with_pid.js @@ -0,0 +1,103 @@ +'use strict'; + +/** + * @module Spec + */ +/** + * Module dependencies. + */ + +const Base = require('mocha/lib/reporters/base'); +const { constants } = require('mocha/lib/runner'); + +const { EVENT_RUN_BEGIN } = constants; +const { EVENT_RUN_END } = constants; +const { EVENT_SUITE_BEGIN } = constants; +const { EVENT_SUITE_END } = constants; +const { EVENT_TEST_FAIL } = constants; +const { EVENT_TEST_PASS } = constants; +const { EVENT_TEST_PENDING } = constants; +const { inherits } = require('mocha/lib/utils'); + +const { color } = Base; + +const prefix = process.env.VSC_PYTHON_CI_TEST_PARALLEL ? `${process.pid} ` : ''; + +/** + * Constructs a new `Spec` reporter instance. + * + * @public + * @class + * @memberof Mocha.reporters + * @extends Mocha.reporters.Base + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options + */ +function Spec(runner, options) { + Base.call(this, runner, options); + + let indents = 0; + let n = 0; + + function indent() { + return Array(indents).join(' '); + } + + runner.on(EVENT_RUN_BEGIN, () => { + Base.consoleLog(); + }); + + runner.on(EVENT_SUITE_BEGIN, (suite) => { + indents += 1; + Base.consoleLog(color('suite', `${prefix}%s%s`), indent(), suite.title); + }); + + runner.on(EVENT_SUITE_END, () => { + indents -= 1; + if (indents === 1) { + Base.consoleLog(); + } + }); + + runner.on(EVENT_TEST_PENDING, (test) => { + const fmt = indent() + color('pending', `${prefix} %s`); + Base.consoleLog(fmt, test.title); + }); + + runner.on(EVENT_TEST_PASS, (test) => { + let fmt; + if (test.speed === 'fast') { + fmt = indent() + color('checkmark', prefix + Base.symbols.ok) + color('pass', ' %s'); + Base.consoleLog(fmt, test.title); + } else { + fmt = + indent() + + color('checkmark', prefix + Base.symbols.ok) + + color('pass', ' %s') + + color(test.speed, ' (%dms)'); + Base.consoleLog(fmt, test.title, test.duration); + } + }); + + runner.on(EVENT_TEST_FAIL, (test) => { + n += 1; + Base.consoleLog(indent() + color('fail', `${prefix}%d) %s`), n, test.title); + }); + + runner.once(EVENT_RUN_END, this.epilogue.bind(this)); +} + +/** + * Inherit from `Base.prototype`. + */ +inherits(Spec, Base); + +Spec.description = 'hierarchical & verbose [default]'; + +/** + * Expose `Spec`. + */ + +// eslint-disable-next-line no-global-assign +exports = Spec; +module.exports = exports; diff --git a/build/ci/static_analysis/credscan/CredScanSuppressions.json b/build/ci/static_analysis/credscan/CredScanSuppressions.json deleted file mode 100644 index a3e8d5418561..000000000000 --- a/build/ci/static_analysis/credscan/CredScanSuppressions.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "tool": "Credential Scanner", - "suppressions": [ - { - "file": "src\\test\\datascience\\serverConfigFiles\\jkey.key", - "_justification": "Key file used for testing purposes, it is not a key relating to anything real" - }, - { - "file": "src\\test\\datascience\\serverConfigFiles\\remotePassword.py", - "_justification": "The secret in this file used here for testing." - } - ] -} diff --git a/build/ci/templates/globals.yml b/build/ci/templates/globals.yml deleted file mode 100644 index 03457023e99e..000000000000 --- a/build/ci/templates/globals.yml +++ /dev/null @@ -1,13 +0,0 @@ -variables: - PythonVersion: '3.8' # Always use latest version. - NodeVersion: '12.8.1' # Check version of node used in VS Code. - NpmVersion: '6.13.4' - MOCHA_FILE: '$(Build.ArtifactStagingDirectory)/test-junit.xml' # All test files will write their JUnit xml output to this file, clobbering the last time it was written. - MOCHA_REPORTER_JUNIT: true # Use the mocha-multi-reporters and send output to both console (spec) and JUnit (mocha-junit-reporter). - VSC_PYTHON_FORCE_LOGGING: true # Enable this to turn on console output for the logger - VSC_PYTHON_LOG_FILE: '$(Build.ArtifactStagingDirectory)/pvsc.log' - VSC_PYTHON_WEBVIEW_LOG_FILE: '$(Build.ArtifactStagingDirectory)/pvsc_webview.log' - CI_BRANCH_NAME: ${Build.SourceBranchName} - 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). diff --git a/build/ci/templates/jobs/build_compile.yml b/build/ci/templates/jobs/build_compile.yml deleted file mode 100644 index 8a258787070e..000000000000 --- a/build/ci/templates/jobs/build_compile.yml +++ /dev/null @@ -1,50 +0,0 @@ -# Overview: -# Generic jobs template to compile and build extension - -jobs: - - job: Compile - pool: - vmImage: 'ubuntu-20.04' - steps: - - template: ../steps/compile.yml - - - job: Build - pool: - vmImage: 'ubuntu-20.04' - steps: - - template: ../steps/build.yml - - - job: Dependencies - pool: - vmImage: 'ubuntu-20.04' - steps: - - template: ../steps/dependencies.yml - - - job: Hygiene - pool: - vmImage: 'ubuntu-20.04' - steps: - - template: ../steps/initialization.yml - parameters: - PythonVersion: $(PythonVersion) - workingDirectory: $(Build.SourcesDirectory) - compile: 'false' - installVSCEorNPX: 'false' - - - bash: npx tslint --project tsconfig.json - displayName: 'Lint' - workingDirectory: $(Build.SourcesDirectory) - - - bash: npx prettier "src/**/*.ts*" --check - displayName: 'Code Format (TypeScript)' - workingDirectory: $(Build.SourcesDirectory) - - - bash: npx prettier "build/**/*.js" --check - displayName: 'Code Format (JavaScript)' - workingDirectory: $(Build.SourcesDirectory) - - - bash: | - python -m pip install -U black - python -m black . --check - displayName: 'Code Format (Python)' - workingDirectory: $(Build.SourcesDirectory)/pythonFiles diff --git a/build/ci/templates/jobs/coverage.yml b/build/ci/templates/jobs/coverage.yml deleted file mode 100644 index 26914271f5f0..000000000000 --- a/build/ci/templates/jobs/coverage.yml +++ /dev/null @@ -1,8 +0,0 @@ -jobs: - - job: Coverage - pool: - vmImage: 'ubuntu-20.04' - variables: - TestsToRun: 'testUnitTests' - steps: - - template: ../steps/merge_upload_coverage.yml diff --git a/build/ci/templates/steps/build.yml b/build/ci/templates/steps/build.yml deleted file mode 100644 index decf72c27dd3..000000000000 --- a/build/ci/templates/steps/build.yml +++ /dev/null @@ -1,59 +0,0 @@ -# ----------------------------------------------------------------------------------------------------------------------------- -# Overview: -# ----------------------------------------------------------------------------------------------------------------------------- -# Set of steps used to compile and build the extension -# -# ----------------------------------------------------------------------------------------------------------------------------- -# Variables -# ----------------------------------------------------------------------------------------------------------------------------- -# 1. build -# Mandatory -# Possible values, `true` or `false`. -# If `true`, means we need to build the VSIX, else just compile. - -steps: - - template: initialization.yml - parameters: - PythonVersion: $(PythonVersion) - workingDirectory: $(Build.SourcesDirectory) - compile: 'false' - - - bash: | - python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade -r requirements.txt - failOnStderr: true - displayName: 'pip install requirements' - - - bash: | - python -m pip --disable-pip-version-check install -r build/debugger-install-requirements.txt - python ./pythonFiles/install_debugpy.py - failOnStderr: true - displayName: 'Install DEBUGPY wheels' - - - bash: npm run clean - displayName: 'Clean' - - - bash: | - npm run updateBuildNumber -- --buildNumber $BUILD_BUILDID - displayName: 'Update dev Version of Extension (main)' - condition: and(succeeded(), eq(variables['Build.SourceBranchName'], 'main')) - - - bash: | - npm run updateBuildNumber -- --buildNumber $BUILD_BUILDID --updateChangelog - displayName: 'Update release Version of Extension' - condition: and(succeeded(), startsWith(variables['Build.SourceBranchName'], 'release')) - - - bash: | - npm run package - displayName: 'Build VSIX' - - - task: CopyFiles@2 - inputs: - contents: '*.vsix' - targetFolder: $(Build.ArtifactStagingDirectory) - displayName: 'Copy VSIX' - - - task: PublishBuildArtifacts@1 - inputs: - pathtoPublish: $(Build.ArtifactStagingDirectory) - artifactName: VSIX - displayName: 'Publish VSIX to Artifacts' diff --git a/build/ci/templates/steps/compile.yml b/build/ci/templates/steps/compile.yml deleted file mode 100644 index e35db11613a8..000000000000 --- a/build/ci/templates/steps/compile.yml +++ /dev/null @@ -1,14 +0,0 @@ -# Compiles the source - -steps: - - template: initialization.yml - parameters: - PythonVersion: $(PythonVersion) - workingDirectory: $(Build.SourcesDirectory) - compile: 'false' - installVSCEorNPX: 'false' - - - task: Gulp@0 - displayName: 'Compile and check for errors' - inputs: - targets: 'prePublishNonBundle' diff --git a/build/ci/templates/steps/dependencies.yml b/build/ci/templates/steps/dependencies.yml deleted file mode 100644 index 5850b0def046..000000000000 --- a/build/ci/templates/steps/dependencies.yml +++ /dev/null @@ -1,20 +0,0 @@ -# ----------------------------------------------------------------------------------------------------------------------------- -# Overview: -# ----------------------------------------------------------------------------------------------------------------------------- -# Set of steps used to validate dependencies for the extension. In a separate pipeline because this -# can take a long time. -# - -steps: - - template: initialization.yml - parameters: - PythonVersion: $(PythonVersion) - workingDirectory: $(Build.SourcesDirectory) - compile: 'false' - - - bash: npm run clean - displayName: 'Clean' - - # This is a slow process, hence do this as a separate step - - bash: npm run checkDependencies - displayName: 'Check Dependencies' diff --git a/build/ci/templates/steps/generate_upload_coverage.yml b/build/ci/templates/steps/generate_upload_coverage.yml deleted file mode 100644 index 608a2fff8bdc..000000000000 --- a/build/ci/templates/steps/generate_upload_coverage.yml +++ /dev/null @@ -1,23 +0,0 @@ -steps: - # Generate the coverage reports. - - bash: npm run test:cover:report - displayName: 'run test:cover:report' - condition: contains(variables['TestsToRun'], 'testUnitTests') - failOnStderr: false - - # Publish Code Coverage Results - - task: PublishCodeCoverageResults@1 - displayName: 'Publish test:unittests coverage results' - condition: contains(variables['TestsToRun'], 'testUnitTests') - inputs: - codeCoverageTool: 'cobertura' - summaryFileLocation: '$(System.DefaultWorkingDirectory)/coverage/cobertura-coverage.xml' - reportDirectory: '$(System.DefaultWorkingDirectory)/coverage' - - - bash: cat ./coverage/cobertura-coverage.xml | ./node_modules/.bin/codecov --pipe - displayName: 'Upload coverage to codecov' - continueOnError: true - condition: contains(variables['TestsToRun'], 'testUnitTests') - failOnStderr: false - env: - CODECOV_TOKEN: $(CODECOV_TOKEN) diff --git a/build/ci/templates/steps/initialization.yml b/build/ci/templates/steps/initialization.yml deleted file mode 100644 index ed6c6b0c4fb4..000000000000 --- a/build/ci/templates/steps/initialization.yml +++ /dev/null @@ -1,135 +0,0 @@ -# ----------------------------------------------------------------------------------------------------------------------------- -# Overview: -# ----------------------------------------------------------------------------------------------------------------------------- -# Set of common steps required when initializing a job. -# -# ----------------------------------------------------------------------------------------------------------------------------- -# Variables -# ----------------------------------------------------------------------------------------------------------------------------- -# 1. workingDirectory -# Mandatory -# Working directory. -# 2. PythonVersion -# Python version to be used. -# If not provided, python will not be setup on CI. -parameters: - workingDirectory: '' - PythonVersion: '' - compile: 'true' - sqlite: 'false' - installVSCEorNPX: 'true' - -steps: - - bash: | - printenv - displayName: 'Show all env vars' - condition: eq(variables['system.debug'], 'true') - - - task: NodeTool@0 - displayName: 'Use Node $(NodeVersion)' - inputs: - versionSpec: $(NodeVersion) - - - task: UsePythonVersion@0 - displayName: 'Use Python ${{ parameters.PythonVersion }}' - condition: and(succeeded(), not(eq('${{ parameters.PythonVersion }}', ''))) - inputs: - versionSpec: ${{ parameters.PythonVersion }} - - # Install the a version of python that works with sqlite3 until this bug is addressed - # https://mseng.visualstudio.com/AzureDevOps/_workitems/edit/1535830 - # - # This task will only run if variable `NeedsPythonFunctionalReqs` is true. - - bash: | - sudo apt-get update - sudo apt-get install libsqlite3-dev - version=$(python -V 2>&1 | grep -Po '(?<=Python )(.+)') - wget https://www.python.org/ftp/python/$version/Python-$version.tar.xz - tar xvf Python-$version.tar.xz - cd Python-$version - ./configure --enable-loadable-sqlite-extensions --with-ensurepip=install --prefix=$HOME/py-$version - make - sudo make install - sudo chmod -R 777 $HOME/py-$version - export PATH=$HOME/py-$version/bin:$PATH - sudo ln -s $HOME/py-$version/bin/python3 $HOME/py-$version/bin/python - echo '##vso[task.prependpath]'$HOME/py-$version/bin - displayName: 'Setup python to run with sqlite on 3.*' - condition: and(succeeded(), eq('${{ parameters.sqlite }}', 'true'), not(eq('${{ parameters.PythonVersion }}', '')), not(eq('${{ parameters.PythonVersion }}', '2.7')), eq(variables['Agent.Os'], 'Linux')) - - - task: Npm@1 - displayName: 'Use NPM $(NpmVersion)' - inputs: - command: custom - verbose: true - customCommand: 'install -g npm@$(NpmVersion)' - - # In the past installs of npm, node-pre-gyp 0.12.0 was installed. - # However in the latest versions, 0.11.0 is getting isntalled. - # - bash: | - # npm uninstall canvas - # npm i -g node-pre-gyp@0.12.0 - # npm i -D node-pre-gyp@0.12.0 - # npm install -D canvas --build-from-source - # displayName: 'Uninstall canvas and install build from source (only for Linux)' - # condition: and(succeeded(), eq(variables['Agent.Os'], 'Linux')) - - # See the help here on how to cache npm - # https://docs.microsoft.com/en-us/azure/devops/pipelines/caching/?view=azure-devops#nodejsnpm - - task: CacheBeta@0 - inputs: - key: npm | $(Agent.OS) | package-lock.json - path: $(npm_config_cache) - restoreKeys: | - npm | $(Agent.OS) - displayName: Cache npm - - - task: Npm@1 - displayName: 'npm ci' - inputs: - workingDir: ${{ parameters.workingDirectory }} - command: custom - verbose: true - customCommand: ci - - # On Mac, the command `node` doesn't always point to the current node version. - # Debugger tests use the variable process.env.NODE_PATH - - script: | - export NODE_PATH=`which node` - echo $NODE_PATH - echo '##vso[task.setvariable variable=NODE_PATH]'$NODE_PATH - displayName: 'Setup NODE_PATH for extension (Debugger Tests)' - condition: and(succeeded(), eq(variables['agent.os'], 'Darwin')) - - # Install vsce - - bash: | - npm install -g vsce - displayName: 'Install vsce' - condition: and(succeeded(), eq('${{ parameters.installVSCEorNPX }}', 'true')) - - - bash: npx tsc -p ./ - displayName: 'compile (npx tsc -p ./)' - workingDirectory: ${{ parameters.workingDirectory }} - condition: and(succeeded(), eq('${{ parameters.compile }}', 'true')) - - # https://code.visualstudio.com/api/working-with-extensions/continuous-integration#azure-pipelines - - bash: | - set -e - /usr/bin/Xvfb :10 -ac >> /tmp/Xvfb.out 2>&1 & - disown -ar - displayName: 'Start Display Server (xvfb) to launch VS Code)' - condition: and(succeeded(), eq(variables['Agent.Os'], 'Linux')) - - - bash: python -m pip install -U pip - displayName: 'Install pip' - # # Show all versions installed/available on PATH if in verbose mode. - # # Example command line (windows pwsh): - # # > Write-Host Node ver: $(& node -v) NPM Ver: $(& npm -v) Python ver: $(& python --version)" - # - bash: | - # echo AVAILABLE DEPENDENCY VERSIONS - # echo Node Version = `node -v` - # echo NPM Version = `npm -v` - # echo Python Version = `python --version` - # echo Gulp Version = `gulp --version` - # condition: and(succeeded(), eq(variables['system.debug'], 'true')) - # displayName: Show Dependency Versions diff --git a/build/ci/templates/steps/merge_upload_coverage.yml b/build/ci/templates/steps/merge_upload_coverage.yml deleted file mode 100644 index 51ef76aac61c..000000000000 --- a/build/ci/templates/steps/merge_upload_coverage.yml +++ /dev/null @@ -1,29 +0,0 @@ -steps: - - template: initialization.yml - parameters: - workingDirectory: $(Build.SourcesDirectory) - compile: 'false' - - # Download previously generated coverage artifacts - - task: DownloadPipelineArtifact@2 - inputs: - patterns: '**/.nyc_output/**' - displayName: 'Download .nyc_output coverage artifacts' - condition: always() - - # Now that we have downloaded artifacts from `coverage-output-`, copy them - # into the root directory (they'll go under `.nyc_output/...`) - # These are the coverage output files that can be merged and then we can generate a report from them. - # This step results in downloading all individual `./nyc_output` results in coverage - # from all different test outputs. - # Running the process of generating reports, will result in generation of - # reports from all coverage data, i.e. we're basically combining all to generate a single merged report. - - bash: | - cp -r $(Pipeline.Workspace)/coverage-output-*/.nyc_output/ ./ - cd .nyc_output/ - ls -dlU .*/ */ - ls - displayName: 'Copy ./.nyc_output' - condition: always() - - - template: generate_upload_coverage.yml diff --git a/build/ci/templates/test_phases.yml b/build/ci/templates/test_phases.yml deleted file mode 100644 index cdeb8841c7cb..000000000000 --- a/build/ci/templates/test_phases.yml +++ /dev/null @@ -1,571 +0,0 @@ -# To use this step template from a job, use the following code: -# ```yaml -# steps: -# template: path/to/this/dir/test_phases.yml -# ``` -# -# Your job using this template *must* supply these values: -# - TestsToRun: 'testA, testB, ..., testN' - the list of tests to execute, see the list above. -# -# Your job using this template *may* supply these values: -# - NeedsPythonTestReqs: [true|false] - install the test-requirements prior to running tests. False if not set. -# - NeedsPythonFunctionalReqs: [true|false] - install the functional-requirements prior to running tests. False if not set. -# - NeedsIPythonReqs: [true|false] - install the ipython-test-requirements prior to running tests. False if not set. -# - PythonVersion: 'M.m' - the Python version to run. DefaultPythonVersion (from globals.yml) if not set. -# - NodeVersion: 'x.y.z' - Node version to use. DefaultNodeVersion (from globals.yml) if not set. - -## Supported `TestsToRun` values, multiples are allowed separated by commas or spaces: -# -# 'testUnitTests' -# 'pythonUnitTests' -# 'pythonInternalTools' -# 'testSingleWorkspace' -# 'testMultiWorkspace' -# 'testDebugger' -# 'testFunctional' -# 'testPerformance' -# 'venvTests' - -steps: - - template: steps/initialization.yml - parameters: - PythonVersion: $(PythonVersion) - workingDirectory: $(Build.SourcesDirectory) - compile: 'false' - sqlite: $(NeedsIPythonReqs) - - # When running unit tests, we need to just compile extension code (not webviews & the like). - - task: Gulp@0 - displayName: 'gulp compile' - inputs: - targets: 'compile' - condition: and(succeeded(), contains(variables['TestsToRun'], 'testUnitTests')) - - # Run the `prePublishNonBundle` gulp task to build the binaries we will be testing. - # This produces the .js files required into the out/ folder. - # Example command line (windows pwsh): - # > gulp prePublishNonBundle - - task: Gulp@0 - displayName: 'gulp prePublishNonBundle' - inputs: - targets: 'prePublishNonBundle' - condition: and(succeeded(), not(contains(variables['TestsToRun'], 'testSmoke')), not(contains(variables['TestsToRun'], 'testUnitTests'))) - - # Run the typescript unit tests. - # - # This will only run if the string 'testUnitTests' exists in variable `TestsToRun` - # - # Example command line (windows pwsh): - # > npm run test:unittests:cover - - bash: | - npm run test:unittests:cover - displayName: 'run test:unittests' - condition: and(succeeded(), contains(variables['TestsToRun'], 'testUnitTests')) - - # Upload the test results to Azure DevOps to facilitate test reporting in their UX. - - task: PublishTestResults@2 - displayName: 'Publish test:unittests results' - condition: contains(variables['TestsToRun'], 'testUnitTests') - inputs: - testResultsFiles: '$(MOCHA_FILE)' - testRunTitle: 'unittests-$(Agent.Os)-Py$(pythonVersion)' - buildPlatform: '$(Agent.Os)-Py$(pythonVersion)' - buildConfiguration: 'UnitTests' - - - task: CopyFiles@2 - inputs: - sourceFolder: '$(Build.SourcesDirectory)/.nyc_output' - targetFolder: '$(Build.ArtifactStagingDirectory)/nyc/.nyc_output' - displayName: 'Copy nyc_output to publish as artificat' - condition: contains(variables['TestsToRun'], 'testUnitTests') - - # Upload Code Coverage Results (to be merged later). - - task: PublishBuildArtifacts@1 - inputs: - pathtoPublish: '$(Build.ArtifactStagingDirectory)/nyc' - artifactName: 'coverage-output-$(Agent.Os)' - condition: contains(variables['TestsToRun'], 'testUnitTests') - - - template: steps/generate_upload_coverage.yml - - # Install the requirements for the Python or the system tests. This includes the supporting libs that - # we ship in our extension such as DEBUGPY and Jedi. - # - # This task will only run if variable `NeedsPythonTestReqs` is true. - # - # Example command line (windows pwsh): - # > python -m pip install -m -U pip - # > python -m pip install --upgrade -r build/test-requirements.txt - # > python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade -r requirements.txt - - bash: | - python -m pip install --upgrade -r build/test-requirements.txt - python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade -r requirements.txt - python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade --pre debugpy - displayName: 'pip install system test requirements' - condition: and(succeeded(), eq(variables['NeedsPythonTestReqs'], 'true')) - - # Install the requirements for functional tests. - # - # This task will only run if variable `NeedsPythonFunctionalReqs` is true. - # - # Example command line (windows pwsh): - # > python -m pip install numpy - # > python -m pip install --upgrade -r build/functional-test-requirements.txt - # > python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade -r requirements.txt - - bash: | - python -m pip install numpy - python -m pip install --upgrade -r ./build/functional-test-requirements.txt - python -c "import sys;print(sys.executable)" - displayName: 'pip install functional requirements' - condition: and(succeeded(), eq(variables['NeedsPythonFunctionalReqs'], 'true')) - - # Add CONDA to the path so anaconda works - # - # This task will only run if variable `NeedsPythonFunctionalReqs` is true. - - bash: | - echo "##vso[task.prependpath]$CONDA/bin" - displayName: 'Add conda to the path' - condition: and(succeeded(), eq(variables['NeedsPythonFunctionalReqs'], 'true'), ne(variables['Agent.Os'], 'Windows_NT')) - - # Add CONDA to the path so anaconda works (windows) - # - # This task will only run if variable `NeedsPythonFunctionalReqs` is true. - - powershell: | - Write-Host "##vso[task.prependpath]$env:CONDA\Scripts" - displayName: 'Add conda to the path' - condition: and(succeeded(), eq(variables['NeedsPythonFunctionalReqs'], 'true'), eq(variables['Agent.Os'], 'Windows_NT')) - - # On MAC let CONDA update install paths - - bash: | - sudo chown -R $USER $CONDA - displayName: 'Give CONDA permission to its own files' - condition: and(succeeded(), eq(variables['Agent.Os'], 'Darwin')) - - # Create the two anaconda environments - # - # This task will only run if variable `NeedsPythonFunctionalReqs` is true. - # - - script: | - conda env create --quiet --force --file build/ci/conda_env_1.yml - conda env create --quiet --force --file build/ci/conda_env_2.yml - displayName: 'Create CONDA Environments' - condition: and(succeeded(), eq(variables['NeedsPythonFunctionalReqs'], 'true')) - - # Run the pip installs in the 3 environments (darwin linux) - - bash: | - source activate base - conda install --quiet -y --file build/ci/conda_base.yml - python -m pip install --upgrade -r build/conda-functional-requirements.txt - source activate conda_env_1 - python -m pip install --upgrade -r build/conda-functional-requirements.txt - source activate conda_env_2 - python -m pip install --upgrade -r build/conda-functional-requirements.txt - conda deactivate - displayName: 'Install Pip requirements for CONDA envs' - condition: and(succeeded(), eq(variables['NeedsPythonFunctionalReqs'], 'true'), ne(variables['Agent.Os'], 'Windows_NT')) - - # Run the pip installs in the 3 environments (windows) - - script: | - call activate base - call conda install --quiet -y --file build/ci/conda_base.yml - python -m pip install --upgrade -r build/conda-functional-requirements.txt - call activate conda_env_1 - python -m pip install --upgrade -r build/conda-functional-requirements.txt - call activate conda_env_2 - python -m pip install --upgrade -r build/conda-functional-requirements.txt - displayName: 'Install Pip requirements for CONDA envs' - condition: and(succeeded(), eq(variables['NeedsPythonFunctionalReqs'], 'true'), eq(variables['Agent.Os'], 'Windows_NT')) - - # Downgrade pywin32 on Windows due to bug https://github.com/jupyter/notebook/issues/4909 - # - # This task will only run if variable `NeedsPythonFunctionalReqs` is true. - - bash: | - python -m pip install --upgrade pywin32==224 - displayName: 'Downgrade pywin32 on Windows / Python 3.6' - condition: and(succeeded(), eq(variables['NeedsPythonFunctionalReqs'], 'true'), eq(variables['Agent.Os'], 'Windows_NT'), eq(variables['PythonVersion'], '3.6')) - - # Install the requirements for ipython tests. - # - # This task will only run if variable `NeedsIPythonReqs` is true. - # - # Example command line (windows pwsh): - # > python -m pip install numpy - # > python -m pip install --upgrade -r build/ipython-test-requirements.txt - # > python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade -r requirements.txt - - bash: | - python -m pip install numpy - python -m pip install --upgrade -r ./build/ipython-test-requirements.txt - displayName: 'pip install ipython requirements' - condition: and(succeeded(), eq(variables['NeedsIPythonReqs'], 'true')) - - # Install jupyter for smoke tests. - - bash: | - python -m pip install --upgrade jupyter - displayName: 'pip install jupyter' - condition: and(succeeded(), eq(variables['NeedsIPythonReqs'], 'true'), contains(variables['TestsToRun'], 'testSmoke')) - - - bash: | - python -m pip --disable-pip-version-check install -r build/debugger-install-requirements.txt - python ./pythonFiles/install_debugpy.py - failOnStderr: true - displayName: 'Install DEBUGPY wheels' - condition: and(eq(variables['NeedsPythonTestReqs'], 'true'), eq(variables['PythonVersion'], '3.7')) - - # Run the Python unit tests in our codebase. Produces a JUnit-style log file that - # will be uploaded after all tests are complete. - # - # This task only runs if the string 'pythonUnitTests' exists in variable `TestsToRun`. - # - # Example command line (windows pwsh): - # > python -m pip install -m -U pip - # > python -m pip install -U -r build/test-requirements.txt - # > python pythonFiles/tests/run_all.py --color=yes --junit-xml=python-tests-junit.xml - - bash: | - python pythonFiles/tests/run_all.py --color=no --junit-xml=$COMMON_TESTRESULTSDIRECTORY/python-tests-junit.xml - displayName: 'Python unittests' - condition: and(succeeded(), contains(variables['TestsToRun'], 'pythonUnitTests')) - - # Upload the test results to Azure DevOps to facilitate test reporting in their UX. - - task: PublishTestResults@2 - displayName: 'Publish Python unittests results' - condition: contains(variables['TestsToRun'], 'pythonUnitTests') - inputs: - testResultsFiles: 'python-tests-junit.xml' - searchFolder: '$(Common.TestResultsDirectory)' - testRunTitle: 'pythonUnitTests-$(Agent.Os)-Py$(pythonVersion)' - buildPlatform: '$(Agent.Os)-Py$(pythonVersion)' - buildConfiguration: 'UnitTests' - - # Run the Python IPython tests in our codebase. Produces a JUnit-style log file that - # will be uploaded after all tests are complete. - # - # This task only runs if the string 'pythonIPythonTests' exists in variable `TestsToRun`. - # - # Example command line (windows pwsh): - # > python -m pip install -m -U pip - # > python -m pip install -U -r build/test-requirements.txt - # > python pythonFiles/tests/run_all.py --color=yes --junit-xml=python-tests-junit.xml - - bash: | - python -m IPython pythonFiles/tests/run_all.py -- --color=no --junit-xml=$COMMON_TESTRESULTSDIRECTORY/ipython-tests-junit.xml - displayName: 'Python ipython tests' - condition: and(succeeded(), contains(variables['TestsToRun'], 'pythonIPythonTests')) - - # Upload the test results to Azure DevOps to facilitate test reporting in their UX. - - task: PublishTestResults@2 - displayName: 'Publish IPython test results' - condition: contains(variables['TestsToRun'], 'pythonIPythonTests') - inputs: - testResultsFiles: 'ipython-tests-junit.xml' - searchFolder: '$(Common.TestResultsDirectory)' - testRunTitle: 'pythonIPythonTests-$(Agent.Os)-Py$(pythonVersion)' - buildPlatform: '$(Agent.Os)-Py$(pythonVersion)' - buildConfiguration: 'UnitTests' - - # Run the News tool tests. - # - # This task only runs if the string 'pythonInternalTools' exists in variable `TestsToRun` - # - # Example command line (windows pwsh): - # > python -m pip install -U -r news/requirements.txt - - script: | - python -m pip install --upgrade -r news/requirements.txt - python -m pytest news --color=yes --junit-xml=$COMMON_TESTRESULTSDIRECTORY/python-news-junit.xml - displayName: 'Run Python tests for news' - condition: and(succeeded(), contains(variables['TestsToRun'], 'pythonInternalTools')) - - # Upload the test results to Azure DevOps to facilitate test reporting in their UX. - - task: PublishTestResults@2 - displayName: 'Publish Python tests for news results' - condition: contains(variables['TestsToRun'], 'pythonInternalTools') - inputs: - testResultsFiles: 'python-news-junit.xml' - searchFolder: '$(Common.TestResultsDirectory)' - testRunTitle: 'news-$(Agent.Os)-Py$(pythonVersion)' - buildPlatform: '$(Agent.Os)-Py$(pythonVersion)' - buildConfiguration: 'UnitTests' - - # Venv tests: Prepare the various virtual environments and record their details into the - # JSON file that venvTests require to run. - # - # This task only runs if the string 'venvTests' exists in variable 'TestsToRun' - # - # This task has a bunch of steps, all of which are to fill the `EnvPath` struct found in - # the file: - # `src/test/common/terminals/environmentActionProviders/terminalActivation.testvirtualenvs.ts` - # - # Example command line (windows pwsh): - # // This is done in powershell. Copy/paste the code below. - - pwsh: | - # venv/bin or venv\\Scripts (windows)? - $environmentExecutableFolder = 'bin' - if ($Env:AGENT_OS -match '.*Windows.*') { - $environmentExecutableFolder = 'Scripts' - } - - # pipenv - python -m pip install pipenv - python -m pipenv run python build/ci/addEnvPath.py $(PYTHON_VIRTUAL_ENVS_LOCATION) pipenvPath - - # venv - # what happens when running under Python 2.7? - python -m venv .venv - & ".venv/$environmentExecutableFolder/python" ./build/ci/addEnvPath.py $(PYTHON_VIRTUAL_ENVS_LOCATION) venvPath - - # virtualenv - python -m pip install virtualenv - python -m virtualenv .virtualenv - & ".virtualenv/$environmentExecutableFolder/python" ./build/ci/addEnvPath.py $(PYTHON_VIRTUAL_ENVS_LOCATION) virtualEnvPath - - # conda - - # 1. For `terminalActivation.testvirtualenvs.test.ts` - - $condaExecPath = Join-Path -Path $Env:CONDA -ChildPath $environmentExecutableFolder | Join-Path -ChildPath conda - if( '$(Agent.Os)' -match '.*Windows.*' ){ - $condaPythonPath = Join-Path -Path $Env:CONDA -ChildPath python - } else{ - $condaPythonPath = Join-Path -Path $Env:CONDA -ChildPath $environmentExecutableFolder | Join-Path -ChildPath python - & $condaPythonPath ./build/ci/addEnvPath.py $(PYTHON_VIRTUAL_ENVS_LOCATION) condaExecPath $condaExecPath - } - & $condaPythonPath ./build/ci/addEnvPath.py $(PYTHON_VIRTUAL_ENVS_LOCATION) condaPath - - # 2. For `interpreterLocatorService.testvirtualenvs.ts` - - & $condaExecPath create -n "test_env1" -y python - & $condaExecPath create -p "./test_env2" -y python - & $condaExecPath create -p "$Env:HOME/test_env3" -y python - - # Set the TEST_FILES_SUFFIX - Write-Host '##vso[task.setvariable variable=TEST_FILES_SUFFIX;]testvirtualenvs' - - displayName: 'Prepare Venv-Test Environment' - condition: and(succeeded(), contains(variables['TestsToRun'], 'venvTests')) - - # Run the virtual environment based tests. - # This set of tests is simply using the `testSingleWorkspace` set of tests, but - # with the environment variable `TEST_FILES_SUFFIX` set to `testvirtualenvs`, which - # got set in the Prepare Venv-Test Environment task above. - # **Note**: Azure DevOps tasks set environment variables via a specially formatted - # string sent to stdout. - # - # This task only runs if the string 'venvTests' exists in variable 'TestsToRun' - # - # Example command line (windows pwsh): - # > $Env:TEST_FILES_SUFFIX=testvirtualenvs - # > npm run testSingleWorkspace - - script: | - cat $PYTHON_VIRTUAL_ENVS_LOCATION - - npm run testSingleWorkspace - - displayName: 'Run Venv-Tests' - condition: and(succeeded(), contains(variables['TestsToRun'], 'venvTests')) - env: - DISPLAY: :10 - - # Upload the test results to Azure DevOps to facilitate test reporting in their UX. - - task: PublishTestResults@2 - displayName: 'Publish Venv-Tests results' - condition: contains(variables['TestsToRun'], 'venvTests') - inputs: - testResultsFiles: '$(MOCHA_FILE)' - testRunTitle: 'venvTest-$(Agent.Os)-Py$(pythonVersion)' - buildPlatform: '$(Agent.Os)-Py$(pythonVersion)' - buildConfiguration: 'SystemTests' - - - # Slow DataScience tests with VS Code - # Create a venv & register it as a kernel. - # These tests are slow hence will only run on linux. - # This env will be used to install ipykernel & test for prompts if ipykernel is missing & similar tests. - # Ensure this is registered as a kernel. - - bash: | - python -m venv .venvnokernel - source .venvnokernel/bin/activate - - python -m pip install ipykernel - python -m ipykernel install --user --name .venvnokernel --display-name .venvnokernel - python -m pip uninstall ipykernel --yes - displayName: 'Prepare Virtual Env for Kernel Tests' - workingDirectory: $(Build.SourcesDirectory)/src/test/datascience - condition: and(succeeded(), contains(variables['TestsToRun'], 'testDataScienceInVSCode')) - - # Set the CI_PYTHON_PATH variable that forces VS Code system tests to use - # the specified Python interpreter. - # - # This is how to set an environment variable in the Azure DevOps pipeline, write - # a specially formatted string to stdout. For details, please see - # https://docs.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch#set-in-script - # - # Example command line (windows pwsd): - # > $Env:CI_PYTHON_PATH=(& python -c 'import sys;print(sys.executable)') - - script: | - python -c "from __future__ import print_function;import sys;print('##vso[task.setvariable variable=CI_PYTHON_PATH;]{}'.format(sys.executable))" - displayName: 'Set CI_PYTHON_PATH' - - # Run the non DS functional tests - # - # This task only runs if the string 'testFunctional' exists in variable `TestsToRun`. - # - # It runs the functional tests that don't start with 'DataScience'. DataScience functional tests - # will be handled in a separate yml. - # - # Example command line (windows pwsh): - # > npm run test:functional - - script: | - npm run test:functional -- --grep="^(?!DataScience).*$" - displayName: 'Run functional tests' - condition: and(succeeded(), contains(variables['TestsToRun'], 'testFunctional')) - env: - DISPLAY: :10 - - # Upload the test results to Azure DevOps to facilitate test reporting in their UX. - - task: PublishTestResults@2 - displayName: 'Publish functional tests results' - condition: or(contains(variables['TestsToRun'], 'testFunctional'), contains(variables['TestsToRun'], 'testParallelFunctional')) - inputs: - testResultsFiles: '$(Build.ArtifactStagingDirectory)/test-junit*.xml' - testRunTitle: 'functional-$(Agent.Os)-Py$(pythonVersion)' - buildPlatform: '$(Agent.Os)-Py$(pythonVersion)' - buildConfiguration: 'FunctionalTests' - - # Run the single workspace tests. - # - # This task only runs if the string 'testSingleWorkspace' exists in variable `TestsToRun`. - # - # Example command line (windows pwsh): - # > npm run testSingleWorkspace - - script: | - npm run testSingleWorkspace - displayName: 'Run single workspace tests' - condition: and(succeeded(), contains(variables['TestsToRun'], 'testSingleWorkspace')) - env: - DISPLAY: :10 - - # Run the single workspace tests for DS in VS Code Insiders. - - script: | - npm run testDataScience - continueOnError: true - displayName: 'Run Native Notebook Tests in VSCode Insiders' - condition: and(succeeded(), contains(variables['TestsToRun'], 'testDataScience'), not(contains(variables['TestsToRun'], 'testDataScienceInVSCode'))) - env: - DISPLAY: :10 - VSC_PYTHON_CI_TEST_VSC_CHANNEL: 'insiders' - VSC_PYTHON_LOAD_EXPERIMENTS_FROM_FILE: 'true' - TEST_FILES_SUFFIX: 'native.vscode.test' - - # Run the single workspace tests for DS in VS Code Stable. - - script: | - npm run testDataScienceInVSCode - displayName: 'Run DataScience Tests in VSCode Stable' - condition: and(succeeded(), contains(variables['TestsToRun'], 'testDataScienceInVSCode')) - env: - DISPLAY: :10 - VSC_PYTHON_CI_TEST_VSC_CHANNEL: 'stable' - VSC_PYTHON_LOAD_EXPERIMENTS_FROM_FILE: 'true' - TEST_FILES_SUFFIX: 'vscode.test' - - # Upload the test results to Azure DevOps to facilitate test reporting in their UX. - - task: PublishTestResults@2 - displayName: 'Publish single workspace tests results' - condition: contains(variables['TestsToRun'], 'testSingleWorkspace') - inputs: - testResultsFiles: '$(MOCHA_FILE)' - testRunTitle: 'singleWorkspace-$(Agent.Os)-Py$(pythonVersion)' - buildPlatform: '$(Agent.Os)-Py$(pythonVersion)' - buildConfiguration: 'SystemTests' - - # Run the multi-workspace tests. - # - # This task only runs if the string 'testMultiWorkspace' exists in variable `TestsToRun`. - # - # Example command line (windows pwsh): - # > npm run testMultiWorkspace - - script: | - npm run testMultiWorkspace - displayName: 'Run multi-workspace tests' - condition: and(succeeded(), contains(variables['TestsToRun'], 'testMultiWorkspace')) - env: - DISPLAY: :10 - - # Upload the test results to Azure DevOps to facilitate test reporting in their UX. - - task: PublishTestResults@2 - displayName: 'Publish multi-workspace tests results' - condition: contains(variables['TestsToRun'], 'testMultiWorkspace') - inputs: - testResultsFiles: '$(MOCHA_FILE)' - testRunTitle: 'multiWorkspace-$(Agent.Os)-Py$(pythonVersion)' - buildPlatform: '$(Agent.Os)-Py$(pythonVersion)' - buildConfiguration: 'SystemTests' - - # Run the debugger integration tests. - # - # This task only runs if the string 'testDebugger' exists in variable `TestsToRun`. - # - # Example command line (windows pwsh): - # > npm run testDebugger - - script: | - npm run testDebugger - displayName: 'Run debugger tests' - condition: and(succeeded(), contains(variables['TestsToRun'], 'testDebugger')) - env: - DISPLAY: :10 - - # Upload the test results to Azure DevOps to facilitate test reporting in their UX. - - task: PublishTestResults@2 - displayName: 'Publish debugger tests results' - condition: contains(variables['TestsToRun'], 'testDebugger') - inputs: - testResultsFiles: '$(MOCHA_FILE)' - testRunTitle: 'debugger-$(Agent.Os)-Py$(pythonVersion)' - buildPlatform: '$(Agent.Os)-Py$(pythonVersion)' - buildConfiguration: 'SystemTests' - - # Run the performance tests. - # - # This task only runs if the string 'testPerformance' exists in variable `TestsToRun`. - # - # Example command line (windows pwsh): - # > npm run testPerformance - - script: | - npm run testPerformance - displayName: 'Run Performance Tests' - condition: and(succeeded(), contains(variables['TestsToRun'], 'testPerformance')) - env: - DISPLAY: :10 - - # Run the smoke tests. - # - # This task only runs if the string 'testSmoke' exists in variable `TestsToRun`. - # - # Example command line (windows pwsh): - # > npm run clean - # > npm run updateBuildNumber -- --buildNumber 0.0.0-local - # > npm run package - # > npx gulp clean:cleanExceptTests - # > npm run testSmoke - - bash: | - npm install -g vsce - npm run clean - npx tsc -p ./ - mkdir -p ./tmp/client/logging - cp -r ./out/client/logging ./tmp/client - npx gulp clean:cleanExceptTests - cp -r ./out/test ./tmp/test - npm run updateBuildNumber -- --buildNumber $BUILD_BUILDID - npm run package - npx gulp clean:cleanExceptTests - mkdir -p ./out/client/logging - cp -r ./tmp/client/logging ./out/client - cp -r ./tmp/test ./out/test - node --no-force-async-hooks-checks ./out/test/smokeTest.js - displayName: 'Run Smoke Tests' - condition: and(succeeded(), contains(variables['TestsToRun'], 'testSmoke')) - env: - DISPLAY: :10 - - - task: PublishBuildArtifacts@1 - inputs: - pathtoPublish: $(Build.ArtifactStagingDirectory) - artifactName: $(Agent.JobName) - condition: always() diff --git a/build/ci/vscode-python-ci-manual.yaml b/build/ci/vscode-python-ci-manual.yaml deleted file mode 100644 index b16482e851be..000000000000 --- a/build/ci/vscode-python-ci-manual.yaml +++ /dev/null @@ -1,296 +0,0 @@ -# manual CI build - -name: '$(Year:yyyy).$(Month).0.$(BuildID)-manual' - -trigger: none -pr: none - -# Variables that are available for the entire pipeline. -variables: - - template: templates/globals.yml - -stages: - - stage: Build - jobs: - - template: templates/jobs/build_compile.yml - - # Each item in each matrix has a number of possible values it may - # define. They are detailed in templates/test_phases.yml. The only - # required value is "TestsToRun". - - - stage: Linux - dependsOn: [] - jobs: - - job: 'Py3x' - dependsOn: [] - timeoutInMinutes: 120 - strategy: - matrix: - 'Unit': - # with mocks - # focused on small units (i.e. functions) - # and tightly controlled dependencies - TestsToRun: 'testUnitTests, pythonUnitTests, pythonInternalTools, pythonIPythonTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - 'Functional': - # no mocks, no vscode - # focused on integration - TestsToRun: 'testfunctional' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Single Workspace': - # no mocks, with vscode - # focused on integration - TestsToRun: 'testSingleWorkspace' - NeedsPythonTestReqs: true - 'Multi Workspace': - # no mocks, with vscode - # focused on integration - TestsToRun: 'testMultiWorkspace' - NeedsPythonTestReqs: true - 'Venv': - TestsToRun: 'venvTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - # This is for the venvTests to use, not needed if you don't run venv tests... - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - 'Debugger': - TestsToRun: 'testDebugger' - NeedsPythonTestReqs: true - 'Smoke': - TestsToRun: 'testSmoke' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - #maxParallel: 3 - pool: - vmImage: 'ubuntu-20.04' - steps: - - template: templates/test_phases.yml - - # This is the oldest Python 3 version we support. - - job: 'Py36' - dependsOn: [] - timeoutInMinutes: 120 - strategy: - matrix: - 'Unit': - PythonVersion: '3.6' - # Note: "pythonInternalTools" tests are 3.7+. - TestsToRun: 'testUnitTests, pythonUnitTests, pythonIPythonTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - 'Functional': - PythonVersion: '3.6' - TestsToRun: 'testfunctional' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Single Workspace': - PythonVersion: '3.6' - TestsToRun: 'testSingleWorkspace' - NeedsPythonTestReqs: true - #maxParallel: 3 - pool: - vmImage: 'ubuntu-20.04' - steps: - - template: templates/test_phases.yml - - # This is the oldest Python 3 version we support. - - job: 'Py35' - dependsOn: [] - timeoutInMinutes: 120 - strategy: - matrix: - 'Unit': - PythonVersion: '3.5' - # Note: "pythonInternalTools" tests are 3.7+. - TestsToRun: 'testUnitTests, pythonUnitTests, pythonIPythonTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - 'Functional': - PythonVersion: '3.5' - TestsToRun: 'testfunctional' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Single Workspace': - PythonVersion: '3.5' - TestsToRun: 'testSingleWorkspace' - NeedsPythonTestReqs: true - 'Venv': - PythonVersion: '3.5' - TestsToRun: 'venvTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - 'Debugger': - PythonVersion: '3.5' - TestsToRun: 'testDebugger' - NeedsPythonTestReqs: true - #maxParallel: 3 - pool: - vmImage: 'ubuntu-20.04' - steps: - - template: templates/test_phases.yml - - - stage: Mac - dependsOn: [] - jobs: - - job: 'Py3x' - dependsOn: [] - timeoutInMinutes: 120 - strategy: - matrix: - 'Unit': - TestsToRun: 'testUnitTests, pythonUnitTests, pythonInternalTools, pythonIPythonTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - 'Functional': - TestsToRun: 'testfunctional' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Single Workspace': - TestsToRun: 'testSingleWorkspace' - NeedsPythonTestReqs: true - 'Multi Workspace': - TestsToRun: 'testMultiWorkspace' - NeedsPythonTestReqs: true - 'Venv': - TestsToRun: 'venvTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - 'Debugger': - TestsToRun: 'testDebugger' - NeedsPythonTestReqs: true - 'Smoke': - TestsToRun: 'testSmoke' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - #maxParallel: 3 - pool: - vmImage: '$(vmImageMacOS)' - steps: - - template: templates/test_phases.yml - - # This is the oldest Python 3 version we support. - - job: 'Py35' - dependsOn: [] - timeoutInMinutes: 120 - strategy: - matrix: - 'Unit': - PythonVersion: '3.5' - # Note: "pythonInternalTools" tests are 3.7+. - TestsToRun: 'testUnitTests, pythonUnitTests, pythonIPythonTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - 'Functional': - PythonVersion: '3.5' - TestsToRun: 'testfunctional' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Single Workspace': - PythonVersion: '3.5' - TestsToRun: 'testSingleWorkspace' - NeedsPythonTestReqs: true - 'Venv': - PythonVersion: '3.5' - TestsToRun: 'venvTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - 'Debugger': - PythonVersion: '3.5' - TestsToRun: 'testDebugger' - NeedsPythonTestReqs: true - #maxParallel: 3 - pool: - vmImage: '$(vmImageMacOS)' - steps: - - template: templates/test_phases.yml - - - stage: Windows - dependsOn: [] - jobs: - - job: 'Py3x' - dependsOn: [] - timeoutInMinutes: 120 - strategy: - matrix: - 'Unit': - TestsToRun: 'testUnitTests, pythonUnitTests, pythonInternalTools, pythonIPythonTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - 'Functional': - TestsToRun: 'testfunctional' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Single Workspace': - TestsToRun: 'testSingleWorkspace' - NeedsPythonTestReqs: true - 'Multi Workspace': - TestsToRun: 'testMultiWorkspace' - NeedsPythonTestReqs: true - 'Venv': - TestsToRun: 'venvTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - 'Debugger': - TestsToRun: 'testDebugger' - NeedsPythonTestReqs: true - 'Smoke': - TestsToRun: 'testSmoke' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - #maxParallel: 3 - pool: - vmImage: 'vs2017-win2016' - steps: - - template: templates/test_phases.yml - - # This is the oldest Python 3 version we support. - - job: 'Py35' - dependsOn: [] - timeoutInMinutes: 120 - strategy: - matrix: - 'Unit': - PythonVersion: '3.5' - # Note: "pythonInternalTools" tests are 3.7+. - TestsToRun: 'testUnitTests, pythonUnitTests, pythonIPythonTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - 'Functional': - PythonVersion: '3.5' - TestsToRun: 'testfunctional' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Single Workspace': - PythonVersion: '3.5' - TestsToRun: 'testSingleWorkspace' - NeedsPythonTestReqs: true - 'Venv': - PythonVersion: '3.5' - TestsToRun: 'venvTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - 'Debugger': - PythonVersion: '3.5' - TestsToRun: 'testDebugger' - NeedsPythonTestReqs: true - #maxParallel: 3 - pool: - vmImage: 'vs2017-win2016' - steps: - - template: templates/test_phases.yml - - - stage: Reports - dependsOn: - - Linux - - Mac - - Windows - condition: always() - jobs: - - template: templates/jobs/coverage.yml diff --git a/build/ci/vscode-python-ci.yaml b/build/ci/vscode-python-ci.yaml deleted file mode 100644 index f8b9a39c2a77..000000000000 --- a/build/ci/vscode-python-ci.yaml +++ /dev/null @@ -1,202 +0,0 @@ -# CI build (PR merge) - -name: '$(Year:yyyy).$(Month).0.$(BuildID)-ci' - -# Notes: Only trigger a commit for main and release, and skip build/rebuild -# on changes in the news and .vscode folders. -trigger: - branches: - include: ['main', 'release*'] - paths: - exclude: ['/news/1 Enhancements', '/news/2 Fixes', '/news/3 Code Health', '/.vscode'] - -# Not the PR build for merges to main and release. -pr: none - -# Variables that are available for the entire pipeline. -variables: - - template: templates/globals.yml - -stages: - - stage: Build - jobs: - - template: templates/jobs/build_compile.yml - - # Each item in each matrix has a number of possible values it may - # define. They are detailed in templates/test_phases.yml. The only - # required value is "TestsToRun". - - - stage: Linux - dependsOn: [] - jobs: - - job: 'Py3x' - dependsOn: [] - timeoutInMinutes: 120 - strategy: - matrix: - 'Unit': - # with mocks - # focused on small units (i.e. functions) - # and tightly controlled dependencies - TestsToRun: 'testUnitTests, pythonUnitTests, pythonInternalTools, pythonIPythonTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - 'Functional': - # no mocks, no vscode - # focused on integration - TestsToRun: 'testfunctional' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Single Workspace': - # no mocks, with vscode - # focused on integration - TestsToRun: 'testSingleWorkspace' - NeedsPythonTestReqs: true - 'Multi Workspace': - # no mocks, with vscode - # focused on integration - TestsToRun: 'testMultiWorkspace' - NeedsPythonTestReqs: true - 'Venv': - TestsToRun: 'venvTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - # This is for the venvTests to use, not needed if you don't run venv tests... - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - 'Debugger': - TestsToRun: 'testDebugger' - NeedsPythonTestReqs: true - 'DataScience in VSCode': - TestsToRun: 'testDataScienceInVSCode' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Smoke': - TestsToRun: 'testSmoke' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - maxParallel: 2 - pool: - vmImage: 'ubuntu-20.04' - steps: - - template: templates/test_phases.yml - - # This is the oldest Python 3 version we support. - - job: 'Py35' - dependsOn: [] - timeoutInMinutes: 120 - strategy: - matrix: - 'Unit': - PythonVersion: '3.5' - # Note: "pythonInternalTools" tests are 3.7+. - TestsToRun: 'testUnitTests, pythonUnitTests, pythonIPythonTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - 'Venv': - PythonVersion: '3.5' - TestsToRun: 'venvTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - 'Debugger': - PythonVersion: '3.5' - TestsToRun: 'testDebugger' - NeedsPythonTestReqs: true - maxParallel: 2 - pool: - vmImage: 'ubuntu-20.04' - steps: - - template: templates/test_phases.yml - - - stage: Mac - dependsOn: [] - jobs: - - job: 'Py3x' - dependsOn: [] - timeoutInMinutes: 120 - strategy: - matrix: - 'Unit': - TestsToRun: 'testUnitTests, pythonUnitTests, pythonInternalTools, pythonIPythonTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - 'Functional': - TestsToRun: 'testfunctional' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Single Workspace': - TestsToRun: 'testSingleWorkspace' - NeedsPythonTestReqs: true - 'Multi Workspace': - TestsToRun: 'testMultiWorkspace' - NeedsPythonTestReqs: true - 'Venv': - TestsToRun: 'venvTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - 'Debugger': - TestsToRun: 'testDebugger' - NeedsPythonTestReqs: true - 'DataScience in VSCode': - TestsToRun: 'testDataScienceInVSCode' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Smoke': - TestsToRun: 'testSmoke' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - maxParallel: 2 - pool: - vmImage: '$(vmImageMacOS)' - steps: - - template: templates/test_phases.yml - - - stage: Windows - dependsOn: [] - jobs: - - job: 'Py3x' - dependsOn: [] - timeoutInMinutes: 120 - strategy: - matrix: - 'Unit': - TestsToRun: 'testUnitTests, pythonUnitTests, pythonInternalTools, pythonIPythonTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - 'Functional': - TestsToRun: 'testfunctional' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Single Workspace': - TestsToRun: 'testSingleWorkspace' - NeedsPythonTestReqs: true - 'Multi Workspace': - TestsToRun: 'testMultiWorkspace' - NeedsPythonTestReqs: true - 'Venv': - TestsToRun: 'venvTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - 'Debugger': - TestsToRun: 'testDebugger' - NeedsPythonTestReqs: true - 'Smoke': - TestsToRun: 'testSmoke' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - maxParallel: 2 - pool: - vmImage: 'vs2017-win2016' - steps: - - template: templates/test_phases.yml - - - stage: Reports - dependsOn: - - Linux - - Mac - - Windows - condition: always() - jobs: - - template: templates/jobs/coverage.yml diff --git a/build/ci/vscode-python-nightly-ci.yaml b/build/ci/vscode-python-nightly-ci.yaml deleted file mode 100644 index aa3bc2d007cb..000000000000 --- a/build/ci/vscode-python-nightly-ci.yaml +++ /dev/null @@ -1,510 +0,0 @@ -# Nightly build - -name: '$(Year:yyyy).$(Month).0.$(BuildID)-nightly' - -# Not the CI build, see `vscode-python-ci.yaml`. -trigger: none - -# Not the PR build for merges to main and release. -pr: none - -schedules: - - cron: '0 8 * * 1-5' - # Daily midnight PST build, runs Monday - Friday always - displayName: Nightly build - branches: - include: - - main - - release* - always: true - -# Variables that are available for the entire pipeline. -variables: - - template: templates/globals.yml - -stages: - - stage: Build - jobs: - - template: templates/jobs/build_compile.yml - - # Each item in each matrix has a number of possible values it may - # define. They are detailed in templates/test_phases.yml. The only - # required value is "TestsToRun". - - - stage: Linux - dependsOn: - - Build - jobs: - - job: 'Py3x' - dependsOn: [] - timeoutInMinutes: 120 - strategy: - matrix: - 'Unit': - TestsToRun: 'testUnitTests, pythonUnitTests, pythonInternalTools, pythonIPythonTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - 'Functional': - TestsToRun: 'testfunctional' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Single Workspace': - TestsToRun: 'testSingleWorkspace' - NeedsPythonTestReqs: true - 'Multi Workspace': - TestsToRun: 'testMultiWorkspace' - NeedsPythonTestReqs: true - 'Debugger': - TestsToRun: 'testDebugger' - NeedsPythonTestReqs: true - 'Venv': - TestsToRun: 'venvTests' - NeedsPythonTestReqs: true - # This is for the venvTests to use, not needed if you don't run venv tests... - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - 'Smoke': - TestsToRun: 'testSmoke' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - maxParallel: 1 - pool: - vmImage: 'ubuntu-20.04' - steps: - - template: templates/test_phases.yml - - - job: 'Py36' - dependsOn: [] - timeoutInMinutes: 120 - strategy: - matrix: - 'Unit': - PythonVersion: '3.6' - # Note: "pythonInternalTools" tests are 3.7+. - TestsToRun: 'testUnitTests, pythonUnitTests, pythonIPythonTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - 'Functional': - PythonVersion: '3.6' - TestsToRun: 'testfunctional' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Single Workspace': - PythonVersion: '3.6' - TestsToRun: 'testSingleWorkspace' - NeedsPythonTestReqs: true - 'Multi Workspace': - PythonVersion: '3.6' - TestsToRun: 'testMultiWorkspace' - NeedsPythonTestReqs: true - 'Debugger': - PythonVersion: '3.6' - TestsToRun: 'testDebugger' - NeedsPythonTestReqs: true - 'Venv': - PythonVersion: '3.6' - TestsToRun: 'venvTests' - NeedsPythonTestReqs: true - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - # Note: We only run the smoke tests with the latest Python release. - maxParallel: 1 - pool: - vmImage: 'ubuntu-20.04' - steps: - - template: templates/test_phases.yml - - - job: 'Py35' - dependsOn: [] - timeoutInMinutes: 120 - strategy: - matrix: - 'Unit': - PythonVersion: '3.5' - # Note: "pythonInternalTools" tests are 3.7+. - TestsToRun: 'testUnitTests, pythonUnitTests, pythonIPythonTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - 'Functional': - PythonVersion: '3.5' - TestsToRun: 'testfunctional' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Single Workspace': - PythonVersion: '3.5' - TestsToRun: 'testSingleWorkspace' - NeedsPythonTestReqs: true - 'Multi Workspace': - PythonVersion: '3.5' - TestsToRun: 'testMultiWorkspace' - NeedsPythonTestReqs: true - 'Debugger': - PythonVersion: '3.5' - TestsToRun: 'testDebugger' - NeedsPythonTestReqs: true - 'Venv': - PythonVersion: '3.5' - TestsToRun: 'venvTests' - NeedsPythonTestReqs: true - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - # Note: We only run the smoke tests with the latest Python release. - maxParallel: 1 - pool: - vmImage: 'ubuntu-20.04' - steps: - - template: templates/test_phases.yml - - - job: 'Py27' - dependsOn: [] - timeoutInMinutes: 120 - strategy: - matrix: - 'Unit': - PythonVersion: '2.7' - # Note: "pythonInternalTools" tests are 3.7+. - TestsToRun: 'testUnitTests, pythonUnitTests, pythonIPythonTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - 'Functional': - PythonVersion: '2.7' - TestsToRun: 'testfunctional' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Single Workspace': - PythonVersion: '2.7' - TestsToRun: 'testSingleWorkspace' - NeedsPythonTestReqs: true - 'Multi Workspace': - PythonVersion: '2.7' - TestsToRun: 'testMultiWorkspace' - NeedsPythonTestReqs: true - 'Debugger': - PythonVersion: '2.7' - TestsToRun: 'testDebugger' - NeedsPythonTestReqs: true - # Note: Virtual env tests use `venv` and won't currently work with Python 2.7 - # Note: We only run the smoke tests with the latest Python release. - maxParallel: 1 - pool: - vmImage: 'ubuntu-20.04' - steps: - - template: templates/test_phases.yml - - - stage: Mac - dependsOn: - - Build - jobs: - - job: 'Py3x' - dependsOn: [] - timeoutInMinutes: 120 - strategy: - matrix: - 'Unit': - TestsToRun: 'testUnitTests, pythonUnitTests, pythonInternalTools, pythonIPythonTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - 'Functional': - TestsToRun: 'testfunctional' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Single Workspace': - TestsToRun: 'testSingleWorkspace' - NeedsPythonTestReqs: true - 'Multi Workspace': - TestsToRun: 'testMultiWorkspace' - NeedsPythonTestReqs: true - 'Debugger': - TestsToRun: 'testDebugger' - NeedsPythonTestReqs: true - 'Venv': - TestsToRun: 'venvTests' - NeedsPythonTestReqs: true - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - 'Smoke': - TestsToRun: 'testSmoke' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - maxParallel: 1 - pool: - vmImage: '$(vmImageMacOS)' - steps: - - template: templates/test_phases.yml - - - job: 'Py36' - dependsOn: [] - timeoutInMinutes: 120 - strategy: - matrix: - 'Unit': - PythonVersion: '3.6' - # Note: "pythonInternalTools" tests are 3.7+. - TestsToRun: 'testUnitTests, pythonUnitTests, pythonIPythonTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - 'Functional': - PythonVersion: '3.6' - TestsToRun: 'testfunctional' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Single Workspace': - PythonVersion: '3.6' - TestsToRun: 'testSingleWorkspace' - NeedsPythonTestReqs: true - 'Multi Workspace': - PythonVersion: '3.6' - TestsToRun: 'testMultiWorkspace' - NeedsPythonTestReqs: true - 'Debugger': - PythonVersion: '3.6' - TestsToRun: 'testDebugger' - NeedsPythonTestReqs: true - 'Venv': - PythonVersion: '3.6' - TestsToRun: 'venvTests' - NeedsPythonTestReqs: true - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - # Note: We only run the smoke tests with the latest Python release. - maxParallel: 1 - pool: - vmImage: '$(vmImageMacOS)' - steps: - - template: templates/test_phases.yml - - - job: 'Py35' - dependsOn: [] - timeoutInMinutes: 120 - strategy: - matrix: - 'Unit': - PythonVersion: '3.5' - # Note: "pythonInternalTools" tests are 3.7+. - TestsToRun: 'testUnitTests, pythonUnitTests, pythonIPythonTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - 'Functional': - PythonVersion: '3.5' - TestsToRun: 'testfunctional' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Single Workspace': - PythonVersion: '3.5' - TestsToRun: 'testSingleWorkspace' - NeedsPythonTestReqs: true - 'Multi Workspace': - PythonVersion: '3.5' - TestsToRun: 'testMultiWorkspace' - NeedsPythonTestReqs: true - 'Debugger': - PythonVersion: '3.5' - TestsToRun: 'testDebugger' - NeedsPythonTestReqs: true - 'Venv': - PythonVersion: '3.5' - TestsToRun: 'venvTests' - NeedsPythonTestReqs: true - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - # Note: We only run the smoke tests with the latest Python release. - maxParallel: 1 - pool: - vmImage: '$(vmImageMacOS)' - steps: - - template: templates/test_phases.yml - - - job: 'Py27' - dependsOn: [] - timeoutInMinutes: 120 - strategy: - matrix: - 'Unit': - PythonVersion: '2.7' - # Note: "pythonInternalTools" tests are 3.7+. - TestsToRun: 'testUnitTests, pythonUnitTests, pythonIPythonTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - 'Functional': - PythonVersion: '2.7' - TestsToRun: 'testfunctional' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Single Workspace': - PythonVersion: '2.7' - TestsToRun: 'testSingleWorkspace' - NeedsPythonTestReqs: true - 'Multi Workspace': - PythonVersion: '2.7' - TestsToRun: 'testMultiWorkspace' - NeedsPythonTestReqs: true - 'Debugger': - PythonVersion: '2.7' - TestsToRun: 'testDebugger' - NeedsPythonTestReqs: true - # Note: Virtual env tests use `venv` and won't currently work with Python 2.7 - # Note: We only run the smoke tests with the latest Python release. - maxParallel: 1 - pool: - vmImage: '$(vmImageMacOS)' - steps: - - template: templates/test_phases.yml - - - stage: Windows - dependsOn: - - Build - jobs: - - job: 'Py3x' - dependsOn: [] - timeoutInMinutes: 120 - strategy: - matrix: - 'Unit': - TestsToRun: 'testUnitTests, pythonUnitTests, pythonInternalTools, pythonIPythonTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - 'Functional': - TestsToRun: 'testfunctional' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Single Workspace': - TestsToRun: 'testSingleWorkspace' - NeedsPythonTestReqs: true - 'Multi Workspace': - TestsToRun: 'testMultiWorkspace' - NeedsPythonTestReqs: true - 'Debugger': - TestsToRun: 'testDebugger' - NeedsPythonTestReqs: true - 'Venv': - TestsToRun: 'venvTests' - NeedsPythonTestReqs: true - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - 'Smoke': - TestsToRun: 'testSmoke' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - maxParallel: 1 - pool: - vmImage: 'vs2017-win2016' - steps: - - template: templates/test_phases.yml - - - job: 'Py36' - dependsOn: [] - timeoutInMinutes: 120 - strategy: - matrix: - 'Unit': - PythonVersion: '3.6' - # Note: "pythonInternalTools" tests are 3.7+. - TestsToRun: 'testUnitTests, pythonUnitTests, pythonIPythonTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - 'Functional': - PythonVersion: '3.6' - TestsToRun: 'testfunctional' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Single Workspace': - PythonVersion: '3.6' - TestsToRun: 'testSingleWorkspace' - NeedsPythonTestReqs: true - 'Multi Workspace': - PythonVersion: '3.6' - TestsToRun: 'testMultiWorkspace' - NeedsPythonTestReqs: true - 'Debugger': - PythonVersion: '3.6' - TestsToRun: 'testDebugger' - NeedsPythonTestReqs: true - 'Venv': - PythonVersion: '3.6' - TestsToRun: 'venvTests' - NeedsPythonTestReqs: true - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - # Note: We only run the smoke tests with the latest Python release. - maxParallel: 1 - pool: - vmImage: 'vs2017-win2016' - steps: - - template: templates/test_phases.yml - - - job: 'Py35' - dependsOn: [] - timeoutInMinutes: 120 - strategy: - matrix: - 'Unit': - PythonVersion: '3.5' - # Note: "pythonInternalTools" tests are 3.7+. - TestsToRun: 'testUnitTests, pythonUnitTests, pythonIPythonTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - 'Functional': - PythonVersion: '3.5' - TestsToRun: 'testfunctional' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Single Workspace': - PythonVersion: '3.5' - TestsToRun: 'testSingleWorkspace' - NeedsPythonTestReqs: true - 'Multi Workspace': - PythonVersion: '3.5' - TestsToRun: 'testMultiWorkspace' - NeedsPythonTestReqs: true - 'Debugger': - PythonVersion: '3.5' - TestsToRun: 'testDebugger' - NeedsPythonTestReqs: true - 'Venv': - PythonVersion: '3.5' - TestsToRun: 'venvTests' - NeedsPythonTestReqs: true - PYTHON_VIRTUAL_ENVS_LOCATION: './src/tmp/envPaths.json' - # Note: We only run the smoke tests with the latest Python release. - maxParallel: 1 - pool: - vmImage: 'vs2017-win2016' - steps: - - template: templates/test_phases.yml - - - job: 'Py27' - dependsOn: [] - timeoutInMinutes: 120 - strategy: - matrix: - 'Unit': - PythonVersion: '2.7' - # Note: "pythonInternalTools" tests are 3.7+. - TestsToRun: 'testUnitTests, pythonUnitTests, pythonIPythonTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - 'Functional': - PythonVersion: '2.7' - TestsToRun: 'testfunctional' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Single Workspace': - PythonVersion: '2.7' - TestsToRun: 'testSingleWorkspace' - NeedsPythonTestReqs: true - 'Multi Workspace': - PythonVersion: '2.7' - TestsToRun: 'testMultiWorkspace' - NeedsPythonTestReqs: true - 'Debugger': - PythonVersion: '2.7' - TestsToRun: 'testDebugger' - NeedsPythonTestReqs: true - # Note: Virtual env tests use `venv` and won't currently work with Python 2.7 - # Note: We only run the smoke tests with the latest Python release. - maxParallel: 1 - pool: - vmImage: 'vs2017-win2016' - steps: - - template: templates/test_phases.yml - - - stage: Reports - dependsOn: - - Linux - - Mac - - Windows - condition: always() - jobs: - - template: templates/jobs/coverage.yml diff --git a/build/ci/vscode-python-pr-validation.yaml b/build/ci/vscode-python-pr-validation.yaml deleted file mode 100644 index fefedc881d4d..000000000000 --- a/build/ci/vscode-python-pr-validation.yaml +++ /dev/null @@ -1,140 +0,0 @@ -# PR Validation build. - -name: '$(Year:yyyy).$(Month).0.$(BuildID)-pr' - -# Notes: Only trigger a PR build for main and release, and skip build/rebuild -# on changes in the news and .vscode folders. -pr: - autoCancel: true - branches: - include: - - 'main' - - 'release*' - - 'ds*' - paths: - exclude: ['/news/1 Enhancements', '/news/2 Fixes', '/news/3 Code Health', '/.vscode'] - -# Not the CI build for merges to main and release. -trigger: none - -# Variables that are available for the entire pipeline. -variables: - - template: templates/globals.yml - -stages: - - stage: Build - jobs: - - template: templates/jobs/build_compile.yml - - # Each item in each matrix has a number of possible values it may - # define. They are detailed in templates/test_phases.yml. The only - # required value is "TestsToRun". - - - stage: Linux - dependsOn: [] - jobs: - - job: 'Py3x' - dependsOn: [] - strategy: - matrix: - 'NodeUnit': - TestsToRun: 'testUnitTests' - NeedsPythonTestReqs: false - NeedsIPythonReqs: false - 'Unit': - TestsToRun: 'pythonUnitTests, pythonInternalTools, pythonIPythonTests' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - 'Functional Non DataScience': - TestsToRun: 'testfunctional' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Single Workspace': - TestsToRun: 'testSingleWorkspace' - NeedsPythonTestReqs: true - 'Native Notebook': - TestsToRun: 'testDataScience' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'DataScience in VSCode': - TestsToRun: 'testDataScienceInVSCode' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Smoke': - TestsToRun: 'testSmoke' - NeedsPythonTestReqs: true - NeedsIPythonReqs: true - pool: - vmImage: 'ubuntu-20.04' - steps: - - template: templates/test_phases.yml - - - job: 'Py27' - dependsOn: [] - strategy: - matrix: - 'Unit': - PythonVersion: '2.7' - # Note: "pythonInternalTools" tests are 3.7+. - TestsToRun: 'pythonUnitTests' - NeedsPythonTestReqs: true - 'Functional Non DS': - PythonVersion: '2.7' - TestsToRun: 'testfunctional' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - pool: - vmImage: 'ubuntu-20.04' - steps: - - template: templates/test_phases.yml - - - stage: Mac - dependsOn: [] - jobs: - - job: 'Py3x' - dependsOn: [] - strategy: - matrix: - # This gives us our best functional coverage for the OS. - 'Functional+Single': - TestsToRun: 'testfunctional, testSingleWorkspace' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - pool: - vmImage: '$(vmImageMacOS)' - steps: - - template: templates/test_phases.yml - - - stage: Windows - dependsOn: [] - jobs: - - job: 'Py3x' - dependsOn: [] - 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 Non DS': - TestsToRun: 'testfunctional' - NeedsPythonTestReqs: true - NeedsPythonFunctionalReqs: true - 'Single Workspace': - TestsToRun: 'testSingleWorkspace' - NeedsPythonTestReqs: true - pool: - vmImage: 'vs2017-win2016' - steps: - - template: templates/test_phases.yml - - - stage: Reports - dependsOn: - - Linux - - Mac - - Windows - condition: always() - jobs: - - template: templates/jobs/coverage.yml diff --git a/build/conda-functional-requirements.txt b/build/conda-functional-requirements.txt deleted file mode 100644 index a00a76726786..000000000000 --- a/build/conda-functional-requirements.txt +++ /dev/null @@ -1,26 +0,0 @@ -# List of requirements for conda environments that cannot be installed using conda -livelossplot -versioneer -flake8 -autopep8 -bandit -black ; python_version>='3.6' -yapf -pylint -pycodestyle -pydocstyle -nose -pytest==4.6.9 # Last version of pytest with Python 2.7 support -rope -flask -django -isort -pathlib2>=2.2.0 ; python_version<'3.6' # Python 2.7 compatibility (pytest) -pythreejs -ipysheet -ipyvolume -beakerx -py4j -bqplot -K3D -debugpy \ No newline at end of file diff --git a/build/constants.js b/build/constants.js index 60109ea4ce0e..73815ebea45f 100644 --- a/build/constants.js +++ b/build/constants.js @@ -1,8 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + 'use strict'; const util = require('./util'); + exports.ExtensionRootDir = util.ExtensionRootDir; // This is a list of files that existed before MS got the extension. exports.existingFiles = util.getListOfFiles('existingFiles.json'); diff --git a/build/debug/replaceWithWebBrowserPanel.js b/build/debug/replaceWithWebBrowserPanel.js deleted file mode 100644 index 21c293117535..000000000000 --- a/build/debug/replaceWithWebBrowserPanel.js +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -const path = require('path'); -const fs = require('fs-extra'); -const common = require('../constants'); - -// Used to debug extension with the DS UI loaded in a web browser. -// We don't want to include test code into extension, hence just patch the js code for debugging. -// This code is used in fucntional ui tests. - -const fileToModify = path.join(common.ExtensionRootDir, 'out/client/common/installer/serviceRegistry.js'); -const fileContents = fs.readFileSync(fileToModify).toString(); - -const newInjection = 'require("../../../test/datascience/uiTests/webBrowserPanelProvider").WebBrowserPanelProvider'; -const oldInjection = 'webPanelProvider_1.WebPanelProvider'; - -if (fileContents.indexOf(oldInjection) === -1 && fileContents.indexOf(newInjection) === -1) { - throw new Error('Unable to modify serviceRegistry.js for WebBrowser debugging'); -} -if (fileContents.indexOf(oldInjection)) { - const newFileContents = fileContents.replace(oldInjection, newInjection); - fs.writeFileSync(fileToModify, newFileContents); -} diff --git a/build/debugger-install-requirements.txt b/build/debugger-install-requirements.txt deleted file mode 100644 index 6ee0765db4b3..000000000000 --- a/build/debugger-install-requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -# Requirements needed to run install_debugpy.py -packaging diff --git a/build/existingFiles.json b/build/existingFiles.json index 4dbf1af34b78..48ab84ff565d 100644 --- a/build/existingFiles.json +++ b/build/existingFiles.json @@ -170,7 +170,6 @@ "src/client/interpreter/configuration/types.ts", "src/client/interpreter/contracts.ts", "src/client/interpreter/display/index.ts", - "src/client/interpreter/display/shebangCodeLensProvider.ts", "src/client/interpreter/helpers.ts", "src/client/interpreter/interpreterService.ts", "src/client/interpreter/interpreterVersion.ts", @@ -196,7 +195,6 @@ "src/client/ioc/index.ts", "src/client/ioc/serviceManager.ts", "src/client/ioc/types.ts", - "src/client/jupyter/provider.ts", "src/client/language/braceCounter.ts", "src/client/language/characters.ts", "src/client/language/characterStream.ts", @@ -381,6 +379,7 @@ "src/test/common/socketStream.test.ts", "src/test/common/terminals/activation.bash.unit.test.ts", "src/test/common/terminals/activation.commandPrompt.unit.test.ts", + "src/test/common/terminals/activation.nushell.unit.test.ts", "src/test/common/terminals/activation.conda.unit.test.ts", "src/test/common/terminals/activation.unit.test.ts", "src/test/common/terminals/activator/base.unit.test.ts", @@ -501,7 +500,7 @@ "src/test/providers/shebangCodeLenseProvider.test.ts", "src/test/providers/symbolProvider.unit.test.ts", "src/test/providers/terminal.unit.test.ts", - "src/test/pythonFiles/formatting/dummy.ts", + "src/test/python_files/formatting/dummy.ts", "src/test/refactor/extension.refactor.extract.method.test.ts", "src/test/refactor/extension.refactor.extract.var.test.ts", "src/test/refactor/rename.test.ts", diff --git a/build/fail.js b/build/fail.js new file mode 100644 index 000000000000..2adc808d8da9 --- /dev/null +++ b/build/fail.js @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +process.exitCode = 1; diff --git a/build/functional-test-requirements.txt b/build/functional-test-requirements.txt index d2f1977a7be4..5c3a9e3116ed 100644 --- a/build/functional-test-requirements.txt +++ b/build/functional-test-requirements.txt @@ -1,2 +1,5 @@ # List of requirements for functional tests versioneer +numpy +pytest +pytest-cov diff --git a/build/ipython-test-requirements.txt b/build/ipython-test-requirements.txt deleted file mode 100644 index 688e039a4461..000000000000 --- a/build/ipython-test-requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -# List of requirements for ipython tests -numpy -pandas -ipython diff --git a/build/license-header.txt b/build/license-header.txt new file mode 100644 index 000000000000..2970b03d7a1c --- /dev/null +++ b/build/license-header.txt @@ -0,0 +1,9 @@ +PLEASE NOTE: This is the license for the Python extension for Visual Studio Code. The Python extension automatically installs other extensions as optional dependencies, which can be uninstalled at any time. These extensions have separate licenses: + + - The Python Debugger extension is released under an MIT License: + https://marketplace.visualstudio.com/items/ms-python.debugpy/license + + - The Pylance extension is only available in binary form and is released under a Microsoft proprietary license, the terms of which are available here: + https://marketplace.visualstudio.com/items/ms-python.vscode-pylance/license + +------------------------------------------------------------------------------ diff --git a/build/test-requirements.txt b/build/test-requirements.txt index 90239895741d..ff9afdfc8a2e 100644 --- a/build/test-requirements.txt +++ b/build/test-requirements.txt @@ -1,21 +1,42 @@ -# Install flake8 first, as both flake8 and autopep8 require pycodestyle, -# but flake8 has a tighter pinning. +# pin setoptconf to prevent issue with 'use_2to3' +setoptconf==0.3.0 + flake8 -autopep8 bandit -black ; python_version>='3.6' -yapf pylint pycodestyle -prospector==1.2.0 # Last version of prospector with Python 2.7 support pydocstyle -nose -pytest < 6.0.0; python_version > '2.7' # Tests do not support pytest 6 yet. -rope +prospector +pytest flask +fastapi +uvicorn django -isort - # Python 2.7 compatibility (pytest) -pytest==4.6.9; python_version == '2.7' -pathlib2>=2.2.0; python_version == '2.7' -py==1.8.1; python_version == '2.7' +testscenarios +testtools + +# Integrated TensorBoard tests +tensorboard +torch-tb-profiler + +# extension build tests +freezegun + +# testing custom pytest plugin require the use of named pipes +namedpipe; platform_system == "Windows" + +# typing for Django files +django-stubs + +coverage +pytest-cov +pytest-json +pytest-timeout + + +# for pytest-describe related tests +pytest-describe + +# for pytest-ruff related tests +pytest-ruff +pytest-black diff --git a/build/test_update_ext_version.py b/build/test_update_ext_version.py new file mode 100644 index 000000000000..b94484775f59 --- /dev/null +++ b/build/test_update_ext_version.py @@ -0,0 +1,126 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import datetime +import json + +import freezegun +import pytest +import update_ext_version + + +CURRENT_YEAR = datetime.datetime.now().year +TEST_DATETIME = f"{CURRENT_YEAR}-03-14 01:23:45" + +# The build ID is calculated via: +# "1" + datetime.datetime.strptime(TEST_DATETIME,"%Y-%m-%d %H:%M:%S").strftime('%j%H%M') +EXPECTED_BUILD_ID = "10730123" + + +def create_package_json(directory, version): + """Create `package.json` in `directory` with a specified version of `version`.""" + package_json = directory / "package.json" + package_json.write_text(json.dumps({"version": version}), encoding="utf-8") + return package_json + + +def run_test(tmp_path, version, args, expected): + package_json = create_package_json(tmp_path, version) + update_ext_version.main(package_json, args) + package = json.loads(package_json.read_text(encoding="utf-8")) + assert expected == update_ext_version.parse_version(package["version"]) + + +@pytest.mark.parametrize( + "version, args", + [ + ("2000.1.0", []), # Wrong year for CalVer + (f"{CURRENT_YEAR}.0.0-rc", []), + (f"{CURRENT_YEAR}.1.0-rc", ["--release"]), + (f"{CURRENT_YEAR}.0.0-rc", ["--release", "--build-id", "-1"]), + ( + f"{CURRENT_YEAR}.0.0-rc", + ["--release", "--for-publishing", "--build-id", "-1"], + ), + ( + f"{CURRENT_YEAR}.0.0-rc", + ["--release", "--for-publishing", "--build-id", "999999999999"], + ), + (f"{CURRENT_YEAR}.1.0-rc", ["--build-id", "-1"]), + (f"{CURRENT_YEAR}.1.0-rc", ["--for-publishing", "--build-id", "-1"]), + (f"{CURRENT_YEAR}.1.0-rc", ["--for-publishing", "--build-id", "999999999999"]), + ], +) +def test_invalid_args(tmp_path, version, args): + with pytest.raises(ValueError): + run_test(tmp_path, version, args, None) + + +@pytest.mark.parametrize( + "version, args, expected", + [ + ( + f"{CURRENT_YEAR}.1.0-rc", + ["--build-id", "12345"], + (f"{CURRENT_YEAR}", "1", "12345", "rc"), + ), + ( + f"{CURRENT_YEAR}.0.0-rc", + ["--release", "--build-id", "12345"], + (f"{CURRENT_YEAR}", "0", "12345", ""), + ), + ( + f"{CURRENT_YEAR}.1.0-rc", + ["--for-publishing", "--build-id", "12345"], + (f"{CURRENT_YEAR}", "1", "12345", ""), + ), + ( + f"{CURRENT_YEAR}.0.0-rc", + ["--release", "--for-publishing", "--build-id", "12345"], + (f"{CURRENT_YEAR}", "0", "12345", ""), + ), + ( + f"{CURRENT_YEAR}.0.0-rc", + ["--release", "--build-id", "999999999999"], + (f"{CURRENT_YEAR}", "0", "999999999999", ""), + ), + ( + f"{CURRENT_YEAR}.1.0-rc", + ["--build-id", "999999999999"], + (f"{CURRENT_YEAR}", "1", "999999999999", "rc"), + ), + ( + f"{CURRENT_YEAR}.1.0-rc", + [], + (f"{CURRENT_YEAR}", "1", EXPECTED_BUILD_ID, "rc"), + ), + ( + f"{CURRENT_YEAR}.0.0-rc", + ["--release"], + (f"{CURRENT_YEAR}", "0", "0", ""), + ), + ( + f"{CURRENT_YEAR}.1.0-rc", + ["--for-publishing"], + (f"{CURRENT_YEAR}", "1", EXPECTED_BUILD_ID, ""), + ), + ( + f"{CURRENT_YEAR}.0.0-rc", + ["--release", "--for-publishing"], + (f"{CURRENT_YEAR}", "0", "0", ""), + ), + ( + f"{CURRENT_YEAR}.0.0-rc", + ["--release"], + (f"{CURRENT_YEAR}", "0", "0", ""), + ), + ( + f"{CURRENT_YEAR}.1.0-rc", + [], + (f"{CURRENT_YEAR}", "1", EXPECTED_BUILD_ID, "rc"), + ), + ], +) +@freezegun.freeze_time(f"{CURRENT_YEAR}-03-14 01:23:45") +def test_update_ext_version(tmp_path, version, args, expected): + run_test(tmp_path, version, args, expected) diff --git a/build/tslint-rules/baseRuleWalker.js b/build/tslint-rules/baseRuleWalker.js deleted file mode 100644 index b8ce93d4179d..000000000000 --- a/build/tslint-rules/baseRuleWalker.js +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -const path = require('path'); -const Lint = require('tslint'); -const util = require('../util'); -class BaseRuleWalker extends Lint.RuleWalker { - shouldIgnoreCurrentFile(node, filesToIgnore) { - const sourceFile = node.getSourceFile(); - if (sourceFile && sourceFile.fileName) { - const filename = path.resolve(util.ExtensionRootDir, sourceFile.fileName); - if (filesToIgnore.indexOf(filename.replace(/\//g, path.sep)) >= 0) { - return true; - } - } - return false; - } -} -exports.BaseRuleWalker = BaseRuleWalker; diff --git a/build/tslint-rules/messagesMustBeLocalizedRule.js b/build/tslint-rules/messagesMustBeLocalizedRule.js deleted file mode 100644 index acf4beaba811..000000000000 --- a/build/tslint-rules/messagesMustBeLocalizedRule.js +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -const path = require('path'); -const Lint = require('tslint'); -const ts = require('typescript'); -const util = require('../util'); -const baseRuleWalker = require('./baseRuleWalker'); -const methodNames = [ - // From IApplicationShell (vscode.window): - 'showErrorMessage', - 'showInformationMessage', - 'showWarningMessage', - 'setStatusBarMessage', - // From IOutputChannel (vscode.OutputChannel): - 'appendLine', - 'appendLine' -]; -// tslint:ignore-next-line:no-suspicious-comments -// TODO: Ideally we would not ignore any files. -const ignoredFiles = util.getListOfFiles('unlocalizedFiles.json'); -const ignoredPrefix = path.normalize('src/test'); -const failureMessage = 'Messages must be localized in the Python Extension (use src/client/common/utils/localize.ts)'; -class NoStringLiteralsInMessages extends baseRuleWalker.BaseRuleWalker { - visitCallExpression(node) { - if (!this.shouldIgnoreNode(node)) { - node.arguments - .filter((arg) => ts.isStringLiteral(arg) || ts.isTemplateLiteral(arg)) - .forEach((arg) => { - this.addFailureAtNode(arg, failureMessage); - }); - } - super.visitCallExpression(node); - } - shouldIgnoreCurrentFile(node) { - //console.log(''); - //console.log(node.getSourceFile().fileName); - //console.log(ignoredFiles); - if (super.shouldIgnoreCurrentFile(node, ignoredFiles)) { - return true; - } - const sourceFile = node.getSourceFile(); - if (sourceFile && sourceFile.fileName) { - if (sourceFile.fileName.startsWith(ignoredPrefix)) { - return true; - } - } - return false; - } - shouldIgnoreNode(node) { - if (this.shouldIgnoreCurrentFile(node)) { - return true; - } - if (!ts.isPropertyAccessExpression(node.expression)) { - return true; - } - const prop = node.expression; - if (methodNames.indexOf(prop.name.text) < 0) { - return true; - } - return false; - } -} -class Rule extends Lint.Rules.AbstractRule { - apply(sourceFile) { - return this.applyWithWalker(new NoStringLiteralsInMessages(sourceFile, this.getOptions())); - } -} -Rule.FAILURE_STRING = failureMessage; -exports.Rule = Rule; diff --git a/build/update_ext_version.py b/build/update_ext_version.py new file mode 100644 index 000000000000..6d709ae05f7f --- /dev/null +++ b/build/update_ext_version.py @@ -0,0 +1,126 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import argparse +import datetime +import json +import pathlib +import sys +from typing import Sequence, Tuple, Union + +EXT_ROOT = pathlib.Path(__file__).parent.parent +PACKAGE_JSON_PATH = EXT_ROOT / "package.json" + + +def build_arg_parse() -> argparse.ArgumentParser: + """Builds the arguments parser.""" + parser = argparse.ArgumentParser( + description="This script updates the python extension micro version based on the release or pre-release channel." + ) + parser.add_argument( + "--release", + action="store_true", + help="Treats the current build as a release build.", + ) + parser.add_argument( + "--build-id", + action="store", + type=int, + default=None, + help="If present, will be used as a micro version.", + required=False, + ) + parser.add_argument( + "--for-publishing", + action="store_true", + help="Removes `-dev` or `-rc` suffix.", + ) + return parser + + +def is_even(v: Union[int, str]) -> bool: + """Returns True if `v` is even.""" + return not int(v) % 2 + + +def micro_build_number() -> str: + """Generates the micro build number. + The format is `1`. + """ + return f"1{datetime.datetime.now(tz=datetime.timezone.utc).strftime('%j%H%M')}" + + +def parse_version(version: str) -> Tuple[str, str, str, str]: + """Parse a version string into a tuple of version parts.""" + major, minor, parts = version.split(".", maxsplit=2) + try: + micro, suffix = parts.split("-", maxsplit=1) + except ValueError: + micro = parts + suffix = "" + return major, minor, micro, suffix + + +def main(package_json: pathlib.Path, argv: Sequence[str]) -> None: + parser = build_arg_parse() + args = parser.parse_args(argv) + + package = json.loads(package_json.read_text(encoding="utf-8")) + + major, minor, micro, suffix = parse_version(package["version"]) + + current_year = datetime.datetime.now().year + current_month = datetime.datetime.now().month + int_major = int(major) + valid_major = ( + int_major + == current_year # Between JAN-DEC major version should be current year + or ( + int_major == current_year - 1 and current_month == 1 + ) # After new years the check is relaxed for JAN to allow releases of previous year DEC + or ( + int_major == current_year + 1 and current_month == 12 + ) # Before new years the check is relaxed for DEC to allow pre-releases of next year JAN + ) + if not valid_major: + raise ValueError( + f"Major version [{major}] must be the current year [{current_year}].", + f"If changing major version after new year's, change to {current_year}.1.0", + "Minor version must be updated based on release or pre-release channel.", + ) + + if args.release and not is_even(minor): + raise ValueError( + f"Release version should have EVEN numbered minor version: {package['version']}" + ) + elif not args.release and is_even(minor): + raise ValueError( + f"Pre-Release version should have ODD numbered minor version: {package['version']}" + ) + + print(f"Updating build FROM: {package['version']}") + if args.build_id: + # If build id is provided it should fall within the 0-INT32 max range + # that the max allowed value for publishing to the Marketplace. + if args.build_id < 0 or (args.for_publishing and args.build_id > ((2**32) - 1)): + raise ValueError(f"Build ID must be within [0, {(2**32) - 1}]") + + package["version"] = ".".join((major, minor, str(args.build_id))) + elif args.release: + package["version"] = ".".join((major, minor, micro)) + else: + # micro version only updated for pre-release. + package["version"] = ".".join((major, minor, micro_build_number())) + + if not args.for_publishing and not args.release and len(suffix): + package["version"] += "-" + suffix + print(f"Updating build TO: {package['version']}") + + # Overwrite package.json with new data add a new-line at the end of the file. + package_json.write_text( + json.dumps(package, indent=4, ensure_ascii=False) + "\n", encoding="utf-8" + ) + + +if __name__ == "__main__": + main(PACKAGE_JSON_PATH, sys.argv[1:]) diff --git a/build/update_package_file.py b/build/update_package_file.py new file mode 100644 index 000000000000..f82587ced846 --- /dev/null +++ b/build/update_package_file.py @@ -0,0 +1,22 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import json +import pathlib + +EXT_ROOT = pathlib.Path(__file__).parent.parent +PACKAGE_JSON_PATH = EXT_ROOT / "package.json" + + +def main(package_json: pathlib.Path) -> None: + package = json.loads(package_json.read_text(encoding="utf-8")) + package["enableTelemetry"] = True + + # Overwrite package.json with new data add a new-line at the end of the file. + package_json.write_text( + json.dumps(package, indent=4, ensure_ascii=False) + "\n", encoding="utf-8" + ) + + +if __name__ == "__main__": + main(PACKAGE_JSON_PATH) diff --git a/build/util.js b/build/util.js index 93a60e7fb19b..c54e204ae7d7 100644 --- a/build/util.js +++ b/build/util.js @@ -1,9 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + 'use strict'; const fs = require('fs'); const path = require('path'); + exports.ExtensionRootDir = path.dirname(__dirname); function getListOfFiles(filename) { filename = path.normalize(filename); @@ -12,8 +14,6 @@ function getListOfFiles(filename) { } const data = fs.readFileSync(filename).toString(); const files = JSON.parse(data); - return files.map((file) => { - return path.join(exports.ExtensionRootDir, file.replace(/\//g, path.sep)); - }); + return files.map((file) => path.join(exports.ExtensionRootDir, file.replace(/\//g, path.sep))); } exports.getListOfFiles = getListOfFiles; diff --git a/build/webpack/common.js b/build/webpack/common.js index 4d3ef5a2367a..c7f7460adf86 100644 --- a/build/webpack/common.js +++ b/build/webpack/common.js @@ -1,11 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + 'use strict'; const glob = require('glob'); const path = require('path'); +// eslint-disable-next-line camelcase const webpack_bundle_analyzer = require('webpack-bundle-analyzer'); const constants = require('../constants'); + exports.nodeModulesToExternalize = [ 'unicode/category/Lu', 'unicode/category/Ll', @@ -17,22 +20,10 @@ exports.nodeModulesToExternalize = [ 'unicode/category/Mc', 'unicode/category/Nd', 'unicode/category/Pc', - '@jupyterlab/services', - 'azure-storage', - 'request', - 'request-progress', 'source-map-support', - 'diff-match-patch', 'sudo-prompt', 'node-stream-zip', 'xml2js', - 'vsls/vscode', - 'pdfkit/js/pdfkit.standalone', - 'crypto-js', - 'fontkit', - 'linebreak', - 'png-js', - 'zeromq' ]; exports.nodeModulesToReplacePaths = [...exports.nodeModulesToExternalize]; function getDefaultPlugins(name) { @@ -45,8 +36,8 @@ function getDefaultPlugins(name) { reportFilename: `${name}.analyzer.html`, generateStatsFile: true, statsFilename: `${name}.stats.json`, - openAnalyzer: false // Open file manually if you want to see it :) - }) + openAnalyzer: false, // Open file manually if you want to see it :) + }), ); } return plugins; diff --git a/build/webpack/loaders/externalizeDependencies.js b/build/webpack/loaders/externalizeDependencies.js index ff2fc0b81ea2..0ada9b0424d8 100644 --- a/build/webpack/loaders/externalizeDependencies.js +++ b/build/webpack/loaders/externalizeDependencies.js @@ -2,12 +2,14 @@ // Licensed under the MIT License. const common = require('../common'); + function replaceModule(prefixRegex, prefix, contents, moduleName, quotes) { const stringToSearch = `${prefixRegex}${quotes}${moduleName}${quotes}`; const stringToReplaceWith = `${prefix}${quotes}./node_modules/${moduleName}${quotes}`; return contents.replace(new RegExp(stringToSearch, 'gm'), stringToReplaceWith); } -// tslint:disable:no-default-export no-invalid-this + +// eslint-disable-next-line camelcase function default_1(source) { common.nodeModulesToReplacePaths.forEach((moduleName) => { if (source.indexOf(moduleName) > 0) { @@ -21,4 +23,5 @@ function default_1(source) { }); return source; } +// eslint-disable-next-line camelcase exports.default = default_1; diff --git a/build/webpack/loaders/fixNodeFetch.js b/build/webpack/loaders/fixNodeFetch.js deleted file mode 100644 index 0d648d131349..000000000000 --- a/build/webpack/loaders/fixNodeFetch.js +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -const path = require('path'); -const constants = require('../../constants'); - -const nodeFetchIndexFile = path.join( - constants.ExtensionRootDir, - 'node_modules', - '@jupyterlab', - 'services', - 'node_modules', - 'node-fetch', - 'lib', - 'index.js' -); -// On windows replace `\` with `\\`, else we get an error in webpack (Module parse failed: Octal literal in strict mode). -const nodeFetchFile = constants.isWindows ? nodeFetchIndexFile.replace(/\\/g, '\\\\') : nodeFetchIndexFile; - -/** - * Node fetch has an es6 module file. That gets bundled into @jupyterlab/services. - * However @jupyterlab/services/serverconnection.js is written such that it uses fetch from either node or browser. - * We need to force the browser version for things to work correctly. - * - * @export - * @param {string} source - * @returns - */ -exports.default = function (source) { - if (source.indexOf("require('node-fetch')") > 0) { - source = source.replace(/require\('node-fetch'\)/g, `require('${nodeFetchFile}')`); - } - return source; -}; diff --git a/build/webpack/pdfkit.js b/build/webpack/pdfkit.js deleted file mode 100644 index 5c31590a3924..000000000000 --- a/build/webpack/pdfkit.js +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -/* -This file is only used when using webpack for bundling. -We have a dummy file so that webpack doesn't fall over when trying to bundle pdfkit. -Just point it to a dummy file (this file). -Once webpack is done, we override the pdfkit.js file in the externalized node modules directory -with the actual source of pdfkit that needs to be used by nodejs (our extension code). -*/ - -class PDFDocument {} -module.exports = PDFDocument; diff --git a/build/webpack/plugins/less-plugin-base64.js b/build/webpack/plugins/less-plugin-base64.js deleted file mode 100644 index 7c42013b4e55..000000000000 --- a/build/webpack/plugins/less-plugin-base64.js +++ /dev/null @@ -1,64 +0,0 @@ -// Most of this was based on https://github.com/less/less-plugin-inline-urls -// License for this was included in the ThirdPartyNotices-Repository.txt -const less = require('less'); - -class Base64MimeTypeNode { - constructor() { - this.value = 'image/svg+xml;base64'; - this.type = 'Base64MimeTypeNode'; - } - - eval(context) { - return this; - } -} - -class Base64Visitor { - constructor() { - this.visitor = new less.visitors.Visitor(this); - - // Set to a preEval visitor to make sure this runs before - // any evals - this.isPreEvalVisitor = true; - - // Make sure this is a replacing visitor so we remove the old data. - this.isReplacing = true; - } - - run(root) { - return this.visitor.visit(root); - } - - visitUrl(URLNode, visitArgs) { - // Return two new nodes in the call. One that has the mime type and other with the node. The data-uri - // evaluator will transform this into a base64 string - return new less.tree.Call( - 'data-uri', - [new Base64MimeTypeNode(), URLNode.value], - URLNode.index || 0, - URLNode.currentFileInfo - ); - } -} -/* - * This was originally used to perform less on uris and turn them into base64 encoded so they can be loaded into - * a webpack html. There's one caveat though. Less and webpack don't play well together. It runs the less at the root dir. - * This means in order to use this in a less file, you need to qualify the urls as if they come from the root dir. - * Example: - * url("./foo.svg") - * becomes - * url("./src/datascience-ui/history-react/images/foo.svg") - */ -class Base64Plugin { - constructor() {} - - install(less, pluginManager) { - pluginManager.addVisitor(new Base64Visitor()); - } - - printUsage() { - console.log('Base64 Plugin. Add to your webpack.config.js as a plugin to convert URLs to base64 inline'); - } -} - -module.exports = Base64Plugin; diff --git a/build/webpack/webpack.datascience-ui-notebooks.config.js b/build/webpack/webpack.datascience-ui-notebooks.config.js deleted file mode 100644 index e661d5ee7620..000000000000 --- a/build/webpack/webpack.datascience-ui-notebooks.config.js +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -const builder = require('./webpack.datascience-ui.config.builder'); -module.exports = [builder.notebooks]; diff --git a/build/webpack/webpack.datascience-ui-renderers.config.js b/build/webpack/webpack.datascience-ui-renderers.config.js deleted file mode 100644 index 022a483c113a..000000000000 --- a/build/webpack/webpack.datascience-ui-renderers.config.js +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -const builder = require('./webpack.datascience-ui.config.builder'); -module.exports = [builder.renderers]; diff --git a/build/webpack/webpack.datascience-ui-viewers.config.js b/build/webpack/webpack.datascience-ui-viewers.config.js deleted file mode 100644 index 89e3f2c56fdd..000000000000 --- a/build/webpack/webpack.datascience-ui-viewers.config.js +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -const builder = require('./webpack.datascience-ui.config.builder'); -module.exports = [builder.viewers]; diff --git a/build/webpack/webpack.datascience-ui.config.builder.js b/build/webpack/webpack.datascience-ui.config.builder.js deleted file mode 100644 index c85046241aa6..000000000000 --- a/build/webpack/webpack.datascience-ui.config.builder.js +++ /dev/null @@ -1,339 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -// Note to editors, if you change this file you have to restart compile-webviews. -// It doesn't reload the config otherwise. -const common = require('./common'); -const webpack = require('webpack'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); -const FixDefaultImportPlugin = require('webpack-fix-default-import-plugin'); -const path = require('path'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); -const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); -const TerserPlugin = require('terser-webpack-plugin'); -const constants = require('../constants'); -const configFileName = 'tsconfig.datascience-ui.json'; -const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); - -// Any build on the CI is considered production mode. -const isProdBuild = constants.isCI || process.argv.includes('--mode'); - -function getEntry(bundle) { - switch (bundle) { - case 'notebook': - return { - nativeEditor: ['babel-polyfill', `./src/datascience-ui/native-editor/index.tsx`], - interactiveWindow: ['babel-polyfill', `./src/datascience-ui/history-react/index.tsx`] - }; - case 'renderers': - return { - renderers: ['babel-polyfill', `./src/datascience-ui/renderers/index.tsx`] - }; - case 'viewers': - return { - plotViewer: ['babel-polyfill', `./src/datascience-ui/plot/index.tsx`], - dataExplorer: ['babel-polyfill', `./src/datascience-ui/data-explorer/index.tsx`], - startPage: ['babel-polyfill', `./src/datascience-ui/startPage/index.tsx`] - }; - default: - throw new Error(`Bundle not supported ${bundle}`); - } -} - -function getPlugins(bundle) { - const plugins = [ - new ForkTsCheckerWebpackPlugin({ - checkSyntacticErrors: true, - tsconfig: configFileName, - reportFiles: ['src/datascience-ui/**/*.{ts,tsx}'], - memoryLimit: 9096 - }) - ]; - if (isProdBuild) { - plugins.push(...common.getDefaultPlugins(bundle)); - } - switch (bundle) { - case 'notebook': - plugins.push( - new MonacoWebpackPlugin({ - languages: [] // force to empty so onigasm will be used - }), - new HtmlWebpackPlugin({ - template: path.join(__dirname, '/nativeOrInteractivePicker.html'), - chunks: [], - filename: 'index.html' - }), - new HtmlWebpackPlugin({ - template: 'src/datascience-ui/native-editor/index.html', - chunks: ['monaco', 'commons', 'nativeEditor'], - filename: 'index.nativeEditor.html' - }), - new HtmlWebpackPlugin({ - template: 'src/datascience-ui/history-react/index.html', - chunks: ['monaco', 'commons', 'interactiveWindow'], - filename: 'index.interactiveWindow.html' - }) - ); - break; - case 'renderers': { - const definePlugin = new webpack.DefinePlugin({ - 'process.env': { - NODE_ENV: JSON.stringify('production') - } - }); - - plugins.push(...(isProdBuild ? [definePlugin] : [])); - break; - } - case 'viewers': { - const definePlugin = new webpack.DefinePlugin({ - 'process.env': { - NODE_ENV: JSON.stringify('production') - } - }); - - plugins.push( - ...(isProdBuild ? [definePlugin] : []), - ...[ - new HtmlWebpackPlugin({ - template: 'src/datascience-ui/plot/index.html', - indexUrl: `${constants.ExtensionRootDir}/out/1`, - chunks: ['commons', 'plotViewer'], - filename: 'index.plotViewer.html' - }), - new HtmlWebpackPlugin({ - template: 'src/datascience-ui/data-explorer/index.html', - indexUrl: `${constants.ExtensionRootDir}/out/1`, - chunks: ['commons', 'dataExplorer'], - filename: 'index.dataExplorer.html' - }), - new HtmlWebpackPlugin({ - template: 'src/datascience-ui/startPage/index.html', - indexUrl: `${constants.ExtensionRootDir}/out/1`, - chunks: ['commons', 'startPage'], - filename: 'index.startPage.html' - }) - ] - ); - break; - } - default: - throw new Error(`Bundle not supported ${bundle}`); - } - - return plugins; -} - -function buildConfiguration(bundle) { - // Folder inside `datascience-ui` that will be created and where the files will be dumped. - const bundleFolder = bundle; - const filesToCopy = []; - if (bundle === 'notebook') { - // Include files only for notebooks. - filesToCopy.push( - ...[ - { - from: path.join(constants.ExtensionRootDir, 'out/ipywidgets/dist/ipywidgets.js'), - to: path.join(constants.ExtensionRootDir, 'out', 'datascience-ui', bundleFolder) - }, - { - from: path.join(constants.ExtensionRootDir, 'node_modules/font-awesome/**/*'), - to: path.join(constants.ExtensionRootDir, 'out', 'datascience-ui', bundleFolder, 'node_modules') - } - ] - ); - } - const config = { - context: constants.ExtensionRootDir, - entry: getEntry(bundle), - output: { - path: path.join(constants.ExtensionRootDir, 'out', 'datascience-ui', bundleFolder), - filename: '[name].js', - chunkFilename: `[name].bundle.js` - }, - mode: 'development', // Leave as is, we'll need to see stack traces when there are errors. - devtool: isProdBuild ? 'source-map' : 'inline-source-map', - optimization: { - minimize: isProdBuild, - minimizer: isProdBuild ? [new TerserPlugin({ sourceMap: true })] : [], - moduleIds: 'hashed', // (doesn't re-generate bundles unnecessarily) https://webpack.js.org/configuration/optimization/#optimizationmoduleids. - splitChunks: { - chunks: 'all', - cacheGroups: { - // These are bundles that will be created and loaded when page first loads. - // These must be added to the page along with the main entry point. - // Smaller they are, the faster the load in SSH. - // Interactive and native editors will share common code in commons. - commons: { - name: 'commons', - chunks: 'initial', - minChunks: bundle === 'notebook' ? 2 : 1, // We want at least one shared bundle (2 for notebooks, as we want monago split into another). - filename: '[name].initial.bundle.js' - }, - // Even though nteract has been split up, some of them are large as nteract alone is large. - // This will ensure nteract (just some of the nteract) goes into a separate bundle. - // Webpack will bundle others separately when loading them asynchronously using `await import(...)` - nteract: { - name: 'nteract', - chunks: 'all', - minChunks: 2, - test(module, _chunks) { - // `module.resource` contains the absolute path of the file on disk. - // Look for `node_modules/monaco...`. - const path = require('path'); - return ( - module.resource && - module.resource.includes(`${path.sep}node_modules${path.sep}@nteract`) - ); - } - }, - // Bundling `plotly` with nteract isn't the best option, as this plotly alone is 6mb. - // This will ensure it is in a seprate bundle, hence small files for SSH scenarios. - plotly: { - name: 'plotly', - chunks: 'all', - minChunks: 1, - test(module, _chunks) { - // `module.resource` contains the absolute path of the file on disk. - // Look for `node_modules/monaco...`. - const path = require('path'); - return ( - module.resource && module.resource.includes(`${path.sep}node_modules${path.sep}plotly`) - ); - } - }, - // Monaco is a monster. For SSH again, we pull this into a seprate bundle. - // This is only a solution for SSH. - // Ideal solution would be to dynamically load monaoc `await import`, that way it will benefit UX and SSH. - // This solution doesn't improve UX, as we still need to wait for monaco to load. - monaco: { - name: 'monaco', - chunks: 'all', - minChunks: 1, - test(module, _chunks) { - // `module.resource` contains the absolute path of the file on disk. - // Look for `node_modules/monaco...`. - const path = require('path'); - return ( - module.resource && module.resource.includes(`${path.sep}node_modules${path.sep}monaco`) - ); - } - } - } - }, - chunkIds: 'named' - }, - node: { - fs: 'empty' - }, - plugins: [ - new FixDefaultImportPlugin(), - new CopyWebpackPlugin( - [ - { from: './**/*.png', to: '.' }, - { from: './**/*.svg', to: '.' }, - { from: './**/*.css', to: '.' }, - { from: './**/*theme*.json', to: '.' }, - { - from: path.join(constants.ExtensionRootDir, 'node_modules/requirejs/require.js'), - to: path.join(constants.ExtensionRootDir, 'out', 'datascience-ui', bundleFolder) - }, - ...filesToCopy - ], - { context: 'src' } - ), - new webpack.optimize.LimitChunkCountPlugin({ - maxChunks: 100 - }), - ...getPlugins(bundle) - ], - externals: ['log4js'], - resolve: { - // Add '.ts' and '.tsx' as resolvable extensions. - extensions: ['.ts', '.tsx', '.js', '.json', '.svg'] - }, - - module: { - rules: [ - { - test: /\.tsx?$/, - use: [ - { loader: 'cache-loader' }, - { - loader: 'thread-loader', - options: { - // there should be 1 cpu for the fork-ts-checker-webpack-plugin - workers: require('os').cpus().length - 1, - workerNodeArgs: ['--max-old-space-size=9096'], - poolTimeout: isProdBuild ? 1000 : Infinity // set this to Infinity in watch mode - see https://github.com/webpack-contrib/thread-loader - } - }, - { - loader: 'ts-loader', - options: { - happyPackMode: true, // IMPORTANT! use happyPackMode mode to speed-up compilation and reduce errors reported to webpack - configFile: configFileName, - // Faster (turn on only on CI, for dev we don't need this). - transpileOnly: true, - reportFiles: ['src/datascience-ui/**/*.{ts,tsx}'] - } - } - ] - }, - { - test: /\.svg$/, - use: ['svg-inline-loader'] - }, - { - test: /\.css$/, - use: ['style-loader', 'css-loader'] - }, - { - test: /\.js$/, - include: /node_modules.*remark.*default.*js/, - use: [ - { - loader: path.resolve('./build/webpack/loaders/remarkLoader.js'), - options: {} - } - ] - }, - { - test: /\.json$/, - type: 'javascript/auto', - include: /node_modules.*remark.*/, - use: [ - { - loader: path.resolve('./build/webpack/loaders/jsonloader.js'), - options: {} - } - ] - }, - { - test: /\.(png|woff|woff2|eot|gif|ttf)$/, - use: [ - { - loader: 'url-loader?limit=100000', - options: { esModule: false } - } - ] - }, - { - test: /\.less$/, - use: ['style-loader', 'css-loader', 'less-loader'] - } - ] - } - }; - - if (bundle === 'renderers') { - delete config.optimization; - } - return config; -} - -exports.notebooks = buildConfiguration('notebook'); -exports.viewers = buildConfiguration('viewers'); -exports.renderers = buildConfiguration('renderers'); diff --git a/build/webpack/webpack.datascience-ui.config.js b/build/webpack/webpack.datascience-ui.config.js deleted file mode 100644 index 5edb7cf8166a..000000000000 --- a/build/webpack/webpack.datascience-ui.config.js +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -const builder = require('./webpack.datascience-ui.config.builder'); -module.exports = [builder.notebooks, builder.viewers]; diff --git a/build/webpack/webpack.extension.browser.config.js b/build/webpack/webpack.extension.browser.config.js new file mode 100644 index 000000000000..909cceaf1bea --- /dev/null +++ b/build/webpack/webpack.extension.browser.config.js @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// @ts-check + +'use strict'; + +const path = require('path'); +const webpack = require('webpack'); +const NodePolyfillPlugin = require('node-polyfill-webpack-plugin'); + +const packageRoot = path.resolve(__dirname, '..', '..'); +const outDir = path.resolve(packageRoot, 'dist'); + +/** @type {(env: any, argv: { mode: 'production' | 'development' | 'none' }) => import('webpack').Configuration} */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const nodeConfig = (_, { mode }) => ({ + context: packageRoot, + entry: { + extension: './src/client/browser/extension.ts', + }, + target: 'webworker', + output: { + filename: '[name].browser.js', + path: outDir, + libraryTarget: 'commonjs2', + devtoolModuleFilenameTemplate: '../../[resource-path]', + }, + devtool: 'source-map', + // stats: { + // all: false, + // errors: true, + // warnings: true, + // }, + resolve: { + extensions: ['.ts', '.js'], + fallback: { path: require.resolve('path-browserify') }, + }, + plugins: [ + new NodePolyfillPlugin(), + new webpack.optimize.LimitChunkCountPlugin({ + maxChunks: 1, + }), + ], + externals: { + vscode: 'commonjs vscode', + + // These dependencies are ignored because we don't use them, and App Insights has try-catch protecting their loading if they don't exist + // See: https://github.com/microsoft/vscode-extension-telemetry/issues/41#issuecomment-598852991 + 'applicationinsights-native-metrics': 'commonjs applicationinsights-native-metrics', + '@opentelemetry/tracing': 'commonjs @opentelemetry/tracing', + }, + module: { + rules: [ + { + test: /\.ts$/, + loader: 'ts-loader', + options: { + configFile: 'tsconfig.browser.json', + }, + }, + { + test: /\.node$/, + loader: 'node-loader', + }, + ], + }, + // optimization: { + // usedExports: true, + // splitChunks: { + // cacheGroups: { + // defaultVendors: { + // name: 'vendor', + // test: /[\\/]node_modules[\\/]/, + // chunks: 'all', + // priority: -10, + // }, + // }, + // }, + // }, +}); + +module.exports = nodeConfig; diff --git a/build/webpack/webpack.extension.config.js b/build/webpack/webpack.extension.config.js index 8e10d6a34317..082ce52a4d32 100644 --- a/build/webpack/webpack.extension.config.js +++ b/build/webpack/webpack.extension.config.js @@ -1,95 +1,90 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + 'use strict'; -const copyWebpackPlugin = require('copy-webpack-plugin'); -const removeFilesWebpackPlugin = require('remove-files-webpack-plugin'); const path = require('path'); +// eslint-disable-next-line camelcase const tsconfig_paths_webpack_plugin = require('tsconfig-paths-webpack-plugin'); const constants = require('../constants'); const common = require('./common'); -// tslint:disable-next-line:no-var-requires no-require-imports + const configFileName = path.join(constants.ExtensionRootDir, 'tsconfig.extension.json'); -// Some modules will be pre-genearted and stored in out/.. dir and they'll be referenced via NormalModuleReplacementPlugin -// We need to ensure they do not get bundled into the output (as they are large). +// Some modules will be pre-genearted and stored in out/.. dir and they'll be referenced via +// NormalModuleReplacementPlugin. We need to ensure they do not get bundled into the output +// (as they are large). const existingModulesInOutDir = common.getListOfExistingModulesInOutDir(); const config = { mode: 'production', target: 'node', entry: { - extension: './src/client/extension.ts' + extension: './src/client/extension.ts', + 'shellExec.worker': './src/client/common/process/worker/shellExec.worker.ts', + 'plainExec.worker': './src/client/common/process/worker/plainExec.worker.ts', + 'registryKeys.worker': 'src/client/pythonEnvironments/common/registryKeys.worker.ts', + 'registryValues.worker': 'src/client/pythonEnvironments/common/registryValues.worker.ts', }, devtool: 'source-map', node: { - __dirname: false + __dirname: false, }, module: { rules: [ { - // JupyterServices imports node-fetch. - test: /@jupyterlab[\\\/]services[\\\/].*js$/, + test: /\.ts$/, use: [ { - loader: path.join(__dirname, 'loaders', 'fixNodeFetch.js') - } - ] + loader: path.join(__dirname, 'loaders', 'externalizeDependencies.js'), + }, + ], }, { test: /\.ts$/, + exclude: /node_modules/, use: [ { - loader: path.join(__dirname, 'loaders', 'externalizeDependencies.js') - } - ] + loader: 'ts-loader', + }, + ], }, { - test: /\.ts$/, - exclude: /node_modules/, + test: /\.node$/, use: [ { - loader: 'ts-loader' - } - ] + loader: 'node-loader', + }, + ], }, - { enforce: 'post', test: /unicode-properties[\/\\]index.js$/, loader: 'transform-loader?brfs' }, - { enforce: 'post', test: /fontkit[\/\\]index.js$/, loader: 'transform-loader?brfs' }, - { enforce: 'post', test: /linebreak[\/\\]src[\/\\]linebreaker.js/, loader: 'transform-loader?brfs' } - ] - }, - externals: ['vscode', 'commonjs', ...existingModulesInOutDir], - plugins: [ - ...common.getDefaultPlugins('extension'), - new copyWebpackPlugin([ { - from: './node_modules/pdfkit/js/pdfkit.standalone.js', - to: './node_modules/pdfkit/js/pdfkit.standalone.js' - } - ]), - // ZMQ requires prebuilds to be in our node_modules directory. So recreate the ZMQ structure. - // However we don't webpack to manage this, so it was part of the excluded modules. Delete it from there - // so at runtime we pick up the original structure. - new removeFilesWebpackPlugin({ after: { include: ['./out/client/node_modules/zeromq.js'], log: false } }), - new copyWebpackPlugin([{ from: './node_modules/zeromq/**/*.js' }]), - new copyWebpackPlugin([{ from: './node_modules/zeromq/**/*.node' }]), - new copyWebpackPlugin([{ from: './node_modules/zeromq/**/*.json' }]), - new copyWebpackPlugin([{ from: './node_modules/node-gyp-build/**/*' }]) + test: /\.worker\.js$/, + use: { loader: 'worker-loader' }, + }, + ], + }, + externals: [ + 'vscode', + 'commonjs', + ...existingModulesInOutDir, + // These dependencies are ignored because we don't use them, and App Insights has try-catch protecting their loading if they don't exist + // See: https://github.com/microsoft/vscode-extension-telemetry/issues/41#issuecomment-598852991 + 'applicationinsights-native-metrics', + '@opentelemetry/tracing', + '@azure/opentelemetry-instrumentation-azure-sdk', + '@opentelemetry/instrumentation', + '@azure/functions-core', ], + plugins: [...common.getDefaultPlugins('extension')], resolve: { - alias: { - // Pointing pdfkit to a dummy js file so webpack doesn't fall over. - // Since pdfkit has been externalized (it gets updated with the valid code by copying the pdfkit files - // into the right destination). - pdfkit: path.resolve(__dirname, 'pdfkit.js') - }, extensions: ['.ts', '.js'], - plugins: [new tsconfig_paths_webpack_plugin.TsconfigPathsPlugin({ configFile: configFileName })] + plugins: [new tsconfig_paths_webpack_plugin.TsconfigPathsPlugin({ configFile: configFileName })], + conditionNames: ['import', 'require', 'node'], }, output: { filename: '[name].js', path: path.resolve(constants.ExtensionRootDir, 'out', 'client'), libraryTarget: 'commonjs2', - devtoolModuleFilenameTemplate: '../../[resource-path]' - } + devtoolModuleFilenameTemplate: '../../[resource-path]', + }, }; -// tslint:disable-next-line:no-default-export + exports.default = config; diff --git a/build/webpack/webpack.extension.dependencies.config.js b/build/webpack/webpack.extension.dependencies.config.js index 3ef870b67fd3..a90e9135a605 100644 --- a/build/webpack/webpack.extension.dependencies.config.js +++ b/build/webpack/webpack.extension.dependencies.config.js @@ -1,12 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + 'use strict'; -// tslint:disable-next-line: no-require-imports const copyWebpackPlugin = require('copy-webpack-plugin'); const path = require('path'); const constants = require('../constants'); const common = require('./common'); + const entryItems = {}; common.nodeModulesToExternalize.forEach((moduleName) => { entryItems[`node_modules/${moduleName}`] = `./node_modules/${moduleName}`; @@ -18,50 +19,26 @@ const config = { entry: entryItems, devtool: 'source-map', node: { - __dirname: false - }, - module: { - rules: [ - { - // JupyterServices imports node-fetch. - test: /@jupyterlab[\\\/]services[\\\/].*js$/, - use: [ - { - loader: path.join(__dirname, 'loaders', 'fixNodeFetch.js') - } - ] - }, - { enforce: 'post', test: /unicode-properties[\/\\]index.js$/, loader: 'transform-loader?brfs' }, - { enforce: 'post', test: /fontkit[\/\\]index.js$/, loader: 'transform-loader?brfs' }, - { enforce: 'post', test: /linebreak[\/\\]src[\/\\]linebreaker.js/, loader: 'transform-loader?brfs' } - ] + __dirname: false, }, + module: {}, externals: ['vscode', 'commonjs'], plugins: [ ...common.getDefaultPlugins('dependencies'), // vsls requires our package.json to be next to node_modules. It's how they // 'find' the calling extension. - new copyWebpackPlugin([{ from: './package.json', to: '.' }]), - // onigasm requires our onigasm.wasm to be in node_modules - new copyWebpackPlugin([ - { from: './node_modules/onigasm/lib/onigasm.wasm', to: './node_modules/onigasm/lib/onigasm.wasm' } - ]) + // eslint-disable-next-line new-cap + new copyWebpackPlugin({ patterns: [{ from: './package.json', to: '.' }] }), ], resolve: { - alias: { - // Pointing pdfkit to a dummy js file so webpack doesn't fall over. - // Since pdfkit has been externalized (it gets updated with the valid code by copying the pdfkit files - // into the right destination). - pdfkit: path.resolve(__dirname, 'pdfkit.js') - }, - extensions: ['.js'] + extensions: ['.js'], }, output: { filename: '[name].js', path: path.resolve(constants.ExtensionRootDir, 'out', 'client'), libraryTarget: 'commonjs2', - devtoolModuleFilenameTemplate: '../../[resource-path]' - } + devtoolModuleFilenameTemplate: '../../[resource-path]', + }, }; -// tslint:disable-next-line:no-default-export + exports.default = config; diff --git a/cgmanifest.json b/cgmanifest.json new file mode 100644 index 000000000000..57123f566794 --- /dev/null +++ b/cgmanifest.json @@ -0,0 +1,15 @@ +{ + "Registrations": [ + { + "Component": { + "Other": { + "Name": "get-pip", + "Version": "21.3.1", + "DownloadUrl": "https://github.com/pypa/get-pip" + }, + "Type": "other" + }, + "DevelopmentDependency": false + } + ] +} diff --git a/data/.vscode/settings.json b/data/.vscode/settings.json deleted file mode 100644 index 99acc159fcaa..000000000000 --- a/data/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "python.pythonPath": "/usr/bin/python3" -} diff --git a/data/test.py b/data/test.py deleted file mode 100644 index 3b316dc1e8d1..000000000000 --- a/data/test.py +++ /dev/null @@ -1,2 +0,0 @@ -#%% -print('hello') diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 000000000000..8e1aa990a2c2 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,393 @@ +/** + * ESLint Configuration for VS Code Python Extension + * This file configures linting rules for the TypeScript/JavaScript codebase. + * It uses the new flat config format introduced in ESLint 8.21.0 + */ + +// Import essential ESLint plugins and configurations +import tseslint from '@typescript-eslint/eslint-plugin'; +import tsParser from '@typescript-eslint/parser'; +import noOnlyTests from 'eslint-plugin-no-only-tests'; +import prettier from 'eslint-config-prettier'; +import importPlugin from 'eslint-plugin-import'; +import js from '@eslint/js'; +import noBadGdprCommentPlugin from './.eslintplugin/no-bad-gdpr-comment.js'; // Ensure the path is correct + +export default [ + { + ignores: ['**/node_modules/**', '**/out/**'], + }, + // Base configuration for all files + { + ignores: [ + '**/node_modules/**', + '**/out/**', + 'src/test/analysisEngineTest.ts', + 'src/test/ciConstants.ts', + 'src/test/common.ts', + 'src/test/constants.ts', + 'src/test/core.ts', + 'src/test/extension-version.functional.test.ts', + 'src/test/fixtures.ts', + 'src/test/index.ts', + 'src/test/initialize.ts', + 'src/test/mockClasses.ts', + 'src/test/performanceTest.ts', + 'src/test/proc.ts', + 'src/test/smokeTest.ts', + 'src/test/standardTest.ts', + 'src/test/startupTelemetry.unit.test.ts', + 'src/test/testBootstrap.ts', + 'src/test/testLogger.ts', + 'src/test/testRunner.ts', + 'src/test/textUtils.ts', + 'src/test/unittests.ts', + 'src/test/vscode-mock.ts', + 'src/test/interpreters/mocks.ts', + 'src/test/interpreters/virtualEnvs/condaInheritEnvPrompt.unit.test.ts', + 'src/test/interpreters/pythonPathUpdaterFactory.unit.test.ts', + 'src/test/interpreters/activation/service.unit.test.ts', + 'src/test/interpreters/helpers.unit.test.ts', + 'src/test/interpreters/display.unit.test.ts', + 'src/test/terminals/codeExecution/terminalCodeExec.unit.test.ts', + 'src/test/terminals/codeExecution/codeExecutionManager.unit.test.ts', + 'src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts', + 'src/test/activation/activeResource.unit.test.ts', + 'src/test/activation/extensionSurvey.unit.test.ts', + 'src/test/utils/fs.ts', + 'src/test/api.functional.test.ts', + 'src/test/testing/common/debugLauncher.unit.test.ts', + 'src/test/testing/common/services/configSettingService.unit.test.ts', + 'src/test/common/exitCIAfterTestReporter.ts', + 'src/test/common/terminals/activator/index.unit.test.ts', + 'src/test/common/terminals/activator/base.unit.test.ts', + 'src/test/common/terminals/shellDetector.unit.test.ts', + 'src/test/common/terminals/service.unit.test.ts', + 'src/test/common/terminals/helper.unit.test.ts', + 'src/test/common/terminals/activation.unit.test.ts', + 'src/test/common/terminals/shellDetectors/shellDetectors.unit.test.ts', + 'src/test/common/terminals/environmentActivationProviders/terminalActivation.testvirtualenvs.ts', + 'src/test/common/socketStream.test.ts', + 'src/test/common/configSettings.test.ts', + 'src/test/common/experiments/telemetry.unit.test.ts', + 'src/test/common/platform/filesystem.unit.test.ts', + 'src/test/common/platform/errors.unit.test.ts', + 'src/test/common/platform/utils.ts', + 'src/test/common/platform/fs-temp.unit.test.ts', + 'src/test/common/platform/fs-temp.functional.test.ts', + 'src/test/common/platform/filesystem.functional.test.ts', + 'src/test/common/platform/filesystem.test.ts', + 'src/test/common/utils/cacheUtils.unit.test.ts', + 'src/test/common/utils/decorators.unit.test.ts', + 'src/test/common/utils/version.unit.test.ts', + 'src/test/common/configSettings/configSettings.unit.test.ts', + 'src/test/common/serviceRegistry.unit.test.ts', + 'src/test/common/extensions.unit.test.ts', + 'src/test/common/variables/envVarsService.unit.test.ts', + 'src/test/common/helpers.test.ts', + 'src/test/common/application/commands/reloadCommand.unit.test.ts', + 'src/test/common/installer/channelManager.unit.test.ts', + 'src/test/common/installer/pipInstaller.unit.test.ts', + 'src/test/common/installer/pipEnvInstaller.unit.test.ts', + 'src/test/common/socketCallbackHandler.test.ts', + 'src/test/common/process/decoder.test.ts', + 'src/test/common/process/processFactory.unit.test.ts', + 'src/test/common/process/pythonToolService.unit.test.ts', + 'src/test/common/process/proc.observable.test.ts', + 'src/test/common/process/logger.unit.test.ts', + 'src/test/common/process/proc.exec.test.ts', + 'src/test/common/process/pythonProcess.unit.test.ts', + 'src/test/common/process/proc.unit.test.ts', + 'src/test/common/interpreterPathService.unit.test.ts', + 'src/test/debugger/extension/adapter/adapter.test.ts', + 'src/test/debugger/extension/adapter/outdatedDebuggerPrompt.unit.test.ts', + 'src/test/debugger/extension/adapter/factory.unit.test.ts', + 'src/test/debugger/extension/adapter/logging.unit.test.ts', + 'src/test/debugger/extension/hooks/childProcessAttachHandler.unit.test.ts', + 'src/test/debugger/extension/hooks/childProcessAttachService.unit.test.ts', + 'src/test/debugger/utils.ts', + 'src/test/debugger/envVars.test.ts', + 'src/test/telemetry/index.unit.test.ts', + 'src/test/telemetry/envFileTelemetry.unit.test.ts', + 'src/test/application/diagnostics/checks/macPythonInterpreter.unit.test.ts', + 'src/test/application/diagnostics/checks/pythonInterpreter.unit.test.ts', + 'src/test/application/diagnostics/checks/powerShellActivation.unit.test.ts', + 'src/test/application/diagnostics/checks/envPathVariable.unit.test.ts', + 'src/test/application/diagnostics/applicationDiagnostics.unit.test.ts', + 'src/test/application/diagnostics/promptHandler.unit.test.ts', + 'src/test/application/diagnostics/commands/ignore.unit.test.ts', + 'src/test/performance/load.perf.test.ts', + 'src/client/interpreter/configuration/interpreterSelector/commands/base.ts', + 'src/client/interpreter/configuration/interpreterSelector/commands/resetInterpreter.ts', + 'src/client/interpreter/configuration/pythonPathUpdaterServiceFactory.ts', + 'src/client/interpreter/configuration/services/globalUpdaterService.ts', + 'src/client/interpreter/configuration/services/workspaceUpdaterService.ts', + 'src/client/interpreter/configuration/services/workspaceFolderUpdaterService.ts', + 'src/client/interpreter/helpers.ts', + 'src/client/interpreter/virtualEnvs/condaInheritEnvPrompt.ts', + 'src/client/interpreter/display/index.ts', + 'src/client/extension.ts', + 'src/client/startupTelemetry.ts', + 'src/client/terminals/codeExecution/terminalCodeExecution.ts', + 'src/client/terminals/codeExecution/codeExecutionManager.ts', + 'src/client/terminals/codeExecution/djangoContext.ts', + 'src/client/activation/commands.ts', + 'src/client/activation/progress.ts', + 'src/client/activation/extensionSurvey.ts', + 'src/client/activation/common/analysisOptions.ts', + 'src/client/activation/languageClientMiddleware.ts', + 'src/client/testing/serviceRegistry.ts', + 'src/client/testing/main.ts', + 'src/client/testing/configurationFactory.ts', + 'src/client/testing/common/constants.ts', + 'src/client/testing/common/testUtils.ts', + 'src/client/common/helpers.ts', + 'src/client/common/net/browser.ts', + 'src/client/common/net/socket/socketCallbackHandler.ts', + 'src/client/common/net/socket/socketServer.ts', + 'src/client/common/net/socket/SocketStream.ts', + 'src/client/common/contextKey.ts', + 'src/client/common/experiments/telemetry.ts', + 'src/client/common/platform/serviceRegistry.ts', + 'src/client/common/platform/errors.ts', + 'src/client/common/platform/fs-temp.ts', + 'src/client/common/platform/fs-paths.ts', + 'src/client/common/platform/registry.ts', + 'src/client/common/platform/pathUtils.ts', + 'src/client/common/persistentState.ts', + 'src/client/common/terminal/activator/base.ts', + 'src/client/common/terminal/activator/powershellFailedHandler.ts', + 'src/client/common/terminal/activator/index.ts', + 'src/client/common/terminal/helper.ts', + 'src/client/common/terminal/syncTerminalService.ts', + 'src/client/common/terminal/factory.ts', + 'src/client/common/terminal/commandPrompt.ts', + 'src/client/common/terminal/service.ts', + 'src/client/common/terminal/shellDetector.ts', + 'src/client/common/terminal/shellDetectors/userEnvironmentShellDetector.ts', + 'src/client/common/terminal/shellDetectors/vscEnvironmentShellDetector.ts', + 'src/client/common/terminal/shellDetectors/terminalNameShellDetector.ts', + 'src/client/common/terminal/shellDetectors/settingsShellDetector.ts', + 'src/client/common/terminal/shellDetectors/baseShellDetector.ts', + 'src/client/common/utils/decorators.ts', + 'src/client/common/utils/enum.ts', + 'src/client/common/utils/platform.ts', + 'src/client/common/utils/stopWatch.ts', + 'src/client/common/utils/random.ts', + 'src/client/common/utils/sysTypes.ts', + 'src/client/common/utils/misc.ts', + 'src/client/common/utils/cacheUtils.ts', + 'src/client/common/utils/workerPool.ts', + 'src/client/common/extensions.ts', + 'src/client/common/variables/serviceRegistry.ts', + 'src/client/common/variables/environment.ts', + 'src/client/common/variables/types.ts', + 'src/client/common/variables/systemVariables.ts', + 'src/client/common/cancellation.ts', + 'src/client/common/interpreterPathService.ts', + 'src/client/common/application/applicationShell.ts', + 'src/client/common/application/languageService.ts', + 'src/client/common/application/clipboard.ts', + 'src/client/common/application/workspace.ts', + 'src/client/common/application/debugSessionTelemetry.ts', + 'src/client/common/application/documentManager.ts', + 'src/client/common/application/debugService.ts', + 'src/client/common/application/commands/reloadCommand.ts', + 'src/client/common/application/terminalManager.ts', + 'src/client/common/application/applicationEnvironment.ts', + 'src/client/common/errors/errorUtils.ts', + 'src/client/common/installer/serviceRegistry.ts', + 'src/client/common/installer/channelManager.ts', + 'src/client/common/installer/moduleInstaller.ts', + 'src/client/common/installer/types.ts', + 'src/client/common/installer/pipEnvInstaller.ts', + 'src/client/common/installer/productService.ts', + 'src/client/common/installer/pipInstaller.ts', + 'src/client/common/installer/productPath.ts', + 'src/client/common/process/currentProcess.ts', + 'src/client/common/process/processFactory.ts', + 'src/client/common/process/serviceRegistry.ts', + 'src/client/common/process/pythonToolService.ts', + 'src/client/common/process/internal/python.ts', + 'src/client/common/process/internal/scripts/testing_tools.ts', + 'src/client/common/process/types.ts', + 'src/client/common/process/logger.ts', + 'src/client/common/process/pythonProcess.ts', + 'src/client/common/process/pythonEnvironment.ts', + 'src/client/common/process/decoder.ts', + 'src/client/debugger/extension/adapter/remoteLaunchers.ts', + 'src/client/debugger/extension/adapter/outdatedDebuggerPrompt.ts', + 'src/client/debugger/extension/adapter/factory.ts', + 'src/client/debugger/extension/adapter/activator.ts', + 'src/client/debugger/extension/adapter/logging.ts', + 'src/client/debugger/extension/hooks/eventHandlerDispatcher.ts', + 'src/client/debugger/extension/hooks/childProcessAttachService.ts', + 'src/client/debugger/extension/attachQuickPick/wmicProcessParser.ts', + 'src/client/debugger/extension/attachQuickPick/factory.ts', + 'src/client/debugger/extension/attachQuickPick/psProcessParser.ts', + 'src/client/debugger/extension/attachQuickPick/picker.ts', + 'src/client/application/serviceRegistry.ts', + 'src/client/application/diagnostics/base.ts', + 'src/client/application/diagnostics/applicationDiagnostics.ts', + 'src/client/application/diagnostics/filter.ts', + 'src/client/application/diagnostics/promptHandler.ts', + 'src/client/application/diagnostics/commands/base.ts', + 'src/client/application/diagnostics/commands/ignore.ts', + 'src/client/application/diagnostics/commands/factory.ts', + 'src/client/application/diagnostics/commands/execVSCCommand.ts', + 'src/client/application/diagnostics/commands/launchBrowser.ts', + ], + linterOptions: { + reportUnusedDisableDirectives: 'off', + }, + rules: { + ...js.configs.recommended.rules, + 'no-undef': 'off', + }, + }, + // TypeScript-specific configuration + { + files: ['**/*.ts', '**/*.tsx', '**/*.js', 'src', 'pythonExtensionApi/src'], + languageOptions: { + parser: tsParser, + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + }, + globals: { + ...(js.configs.recommended.languageOptions?.globals || {}), + mocha: true, + require: 'readonly', + process: 'readonly', + exports: 'readonly', + module: 'readonly', + __dirname: 'readonly', + __filename: 'readonly', + setTimeout: 'readonly', + setInterval: 'readonly', + clearTimeout: 'readonly', + clearInterval: 'readonly', + }, + }, + plugins: { + '@typescript-eslint': tseslint, + 'no-only-tests': noOnlyTests, + import: importPlugin, + prettier: prettier, + 'no-bad-gdpr-comment': noBadGdprCommentPlugin, // Register your plugin + }, + settings: { + 'import/resolver': { + node: { + extensions: ['.js', '.ts'], + }, + }, + }, + rules: { + 'no-bad-gdpr-comment/no-bad-gdpr-comment': 'warn', // Enable your rule + // Base configurations + ...tseslint.configs.recommended.rules, + ...prettier.rules, + + // TypeScript-specific rules + '@typescript-eslint/ban-ts-comment': [ + 'error', + { + 'ts-ignore': 'allow-with-description', + }, + ], + '@typescript-eslint/ban-types': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-empty-interface': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-namespace': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/no-loss-of-precision': 'off', + '@typescript-eslint/no-unused-vars': [ + 'warn', + { + varsIgnorePattern: '^_', + argsIgnorePattern: '^_', + }, + ], + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/no-use-before-define': [ + 'error', + { + functions: false, + }, + ], + + // Import rules + 'import/extensions': 'off', + 'import/namespace': 'off', + 'import/no-extraneous-dependencies': 'off', + 'import/no-unresolved': 'off', + 'import/prefer-default-export': 'off', + + // Testing rules + 'no-only-tests/no-only-tests': [ + 'error', + { + block: ['test', 'suite'], + focus: ['only'], + }, + ], + + // Code style rules + 'linebreak-style': 'off', + 'no-bitwise': 'off', + 'no-console': 'off', + 'no-underscore-dangle': 'off', + 'operator-assignment': 'off', + 'func-names': 'off', + + // Error handling and control flow + 'no-empty': ['error', { allowEmptyCatch: true }], + 'no-async-promise-executor': 'off', + 'no-await-in-loop': 'off', + 'no-unreachable': 'off', + 'no-void': 'off', + + // Duplicates and overrides (TypeScript handles these) + 'no-dupe-class-members': 'off', + 'no-redeclare': 'off', + 'no-undef': 'off', + + // Miscellaneous rules + 'no-control-regex': 'off', + 'no-extend-native': 'off', + 'no-inner-declarations': 'off', + 'no-multi-str': 'off', + 'no-param-reassign': 'off', + 'no-prototype-builtins': 'off', + 'no-empty-function': 'off', + 'no-template-curly-in-string': 'off', + 'no-useless-escape': 'off', + 'no-extra-parentheses': 'off', + 'no-extra-paren': 'off', + '@typescript-eslint/no-extra-parens': 'off', + strict: 'off', + + // Restricted syntax + 'no-restricted-syntax': [ + 'error', + { + selector: 'ForInStatement', + message: + 'for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.', + }, + { + selector: 'LabeledStatement', + message: + 'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.', + }, + { + selector: 'WithStatement', + message: + '`with` is disallowed in strict mode because it makes code impossible to predict and optimize.', + }, + ], + }, + }, +]; diff --git a/experiments.json b/experiments.json deleted file mode 100644 index 0c2d81ad0440..000000000000 --- a/experiments.json +++ /dev/null @@ -1,182 +0,0 @@ -[ - { - "name": "AlwaysDisplayTestExplorer - experiment", - "salt": "AlwaysDisplayTestExplorer", - "min": 80, - "max": 100 - }, - { - "name": "AlwaysDisplayTestExplorer - control", - "salt": "AlwaysDisplayTestExplorer", - "min": 0, - "max": 20 - }, - { - "name": "ShowPlayIcon - start", - "salt": "ShowPlayIcon", - "max": 100, - "min": 0 - }, - { - "name": "ShowExtensionSurveyPrompt - enabled", - "salt": "ShowExtensionSurveyPrompt", - "max": 100, - "min": 80 - }, - { - "name": "ShowExtensionSurveyPrompt - control", - "salt": "ShowExtensionSurveyPrompt", - "min": 0, - "max": 20 - }, - { - "name": "DebugAdapterFactory - control", - "salt": "DebugAdapterFactory", - "min": 0, - "max": 0 - }, - { - "name": "DebugAdapterFactory - experiment", - "salt": "DebugAdapterFactory", - "min": 0, - "max": 100 - }, - { - "name": "PtvsdWheels37 - control", - "salt": "DebugAdapterFactory", - "min": 0, - "max": 0 - }, - { - "name": "PtvsdWheels37 - experiment", - "salt": "DebugAdapterFactory", - "min": 0, - "max": 100 - }, - { - "name": "Reload - control", - "salt": "DebugAdapterFactory", - "min": 0, - "max": 0 - }, - { - "name": "Reload - experiment", - "salt": "DebugAdapterFactory", - "min": 0, - "max": 0 - }, - { - "name": "UseTerminalToGetActivatedEnvVars - experiment", - "salt": "UseTerminalToGetActivatedEnvVars", - "min": 0, - "max": 0 - }, - { - "name": "UseTerminalToGetActivatedEnvVars - control", - "salt": "UseTerminalToGetActivatedEnvVars", - "min": 0, - "max": 100 - }, - { - "name": "AA_testing - control", - "salt": "AA_testing", - "min": 0, - "max": 10 - }, - { - "name": "AA_testing - experiment", - "salt": "AA_testing", - "min": 90, - "max": 100 - }, - { - "name": "LocalZMQKernel - experiment", - "salt": "LocalZMQKernel", - "min": 0, - "max": 100 - }, - { - "name": "LocalZMQKernel - control", - "salt": "LocalZMQKernel", - "min": 0, - "max": 0 - }, - { - "name": "CollectLSRequestTiming - experiment", - "salt": "CollectLSRequestTiming", - "min": 0, - "max": 10 - }, - { - "name": "CollectLSRequestTiming - control", - "salt": "CollectLSRequestTiming", - "min": 10, - "max": 100 - }, - { - "name": "CollectNodeLSRequestTiming - experiment", - "salt": "CollectNodeLSRequestTiming", - "min": 0, - "max": 100 - }, - { - "name": "CollectNodeLSRequestTiming - control", - "salt": "CollectNodeLSRequestTiming", - "min": 0, - "max": 0 - }, - { - "name": "EnableIPyWidgets - experiment", - "salt": "EnableIPyWidgets", - "min": 0, - "max": 100 - }, - { - "name": "EnableIPyWidgets - control", - "salt": "EnableIPyWidgets", - "min": 0, - "max": 0 - }, - { - "name": "DeprecatePythonPath - experiment", - "salt": "DeprecatePythonPath", - "min": 0, - "max": 20 - }, - { - "name": "DeprecatePythonPath - control", - "salt": "DeprecatePythonPath", - "min": 80, - "max": 100 - }, - { - "name": "RunByLine - experiment", - "salt": "RunByLine", - "min": 0, - "max": 40 - }, - { - "name": "RunByLine - control", - "salt": "RunByLine", - "min": 40, - "max": 100 - }, - { - "name": "CustomEditorSupport - control", - "salt": "CustomEditorSupport", - "min": 0, - "max": 100 - }, - { - "name": "CustomEditorSupport - experiment", - "salt": "CustomEditorSupport", - "min": 0, - "max": 0 - }, - { - "name": "NativeNotebook - experiment", - "salt": "CustomEditorSupport", - "max": 0, - "min": 0 - } -] diff --git a/gulpfile.js b/gulpfile.js index 036eb353592f..0b919f16572a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -9,100 +9,59 @@ 'use strict'; const gulp = require('gulp'); -const filter = require('gulp-filter'); -const es = require('event-stream'); -const tsfmt = require('typescript-formatter'); -const tslint = require('tslint'); -const relative = require('relative'); const ts = require('gulp-typescript'); -const cp = require('child_process'); const spawn = require('cross-spawn'); -const colors = require('colors/safe'); const path = require('path'); const del = require('del'); -const sourcemaps = require('gulp-sourcemaps'); -const fs = require('fs-extra'); const fsExtra = require('fs-extra'); const glob = require('glob'); const _ = require('lodash'); const nativeDependencyChecker = require('node-has-native-dependencies'); const flat = require('flat'); -const argv = require('yargs').argv; +const { argv } = require('yargs'); const os = require('os'); -const rmrf = require('rimraf'); +const typescript = require('typescript'); -const isCI = process.env.TRAVIS === 'true' || process.env.TF_BUILD !== undefined; - -const noop = function () {}; -/** - * Hygiene works by creating cascading subsets of all our files and - * passing them through a sequence of checks. Here are the current subsets, - * named according to the checks performed on them. Each subset contains - * the following one, as described in mathematical notation: - * - * all ⊃ indentation ⊃ typescript - */ - -const all = ['src/**/*.ts', 'src/**/*.tsx', 'src/**/*.d.ts', 'src/**/*.js', 'src/**/*.jsx']; - -const tsFilter = ['src/**/*.ts*', '!out/**/*']; +const tsProject = ts.createProject('./tsconfig.json', { typescript }); -const indentationFilter = ['src/**/*.ts*', '!**/typings/**/*']; - -const tslintFilter = [ - 'src/**/*.ts*', - 'test/**/*.ts*', - '!**/node_modules/**', - '!out/**/*', - '!images/**/*', - '!.vscode/**/*', - '!pythonFiles/**/*', - '!resources/**/*', - '!snippets/**/*', - '!syntaxes/**/*', - '!**/typings/**/*', - '!**/*.d.ts' -]; +const isCI = process.env.TRAVIS === 'true' || process.env.TF_BUILD !== undefined; -gulp.task('compile', (done) => { +gulp.task('compileCore', (done) => { let failed = false; - const tsProject = ts.createProject('tsconfig.json'); tsProject .src() .pipe(tsProject()) - .on('error', () => (failed = true)) + .on('error', () => { + failed = true; + }) .js.pipe(gulp.dest('out')) .on('finish', () => (failed ? done(new Error('TypeScript compilation errors')) : done())); }); -gulp.task('precommit', (done) => run({ exitOnError: true, mode: 'staged' }, done)); - -gulp.task('hygiene-watch', () => gulp.watch(tsFilter, gulp.series('hygiene-modified'))); - -gulp.task('hygiene', (done) => run({ mode: 'compile', skipFormatCheck: true, skipIndentationCheck: true }, done)); - -gulp.task( - 'hygiene-modified', - gulp.series('compile', (done) => run({ mode: 'changes' }, done)) -); - -gulp.task('watch', gulp.parallel('hygiene-modified', 'hygiene-watch')); - -// Duplicate to allow duplicate task in tasks.json (one ith problem matching, and one without) -gulp.task('watchProblems', gulp.parallel('hygiene-modified', 'hygiene-watch')); - -gulp.task('hygiene-watch-branch', () => gulp.watch(tsFilter, gulp.series('hygiene-branch'))); +gulp.task('compileApi', (done) => { + spawnAsync('npm', ['run', 'compileApi'], undefined, true) + .then((stdout) => { + if (stdout.includes('error')) { + done(new Error(stdout)); + } else { + done(); + } + }) + .catch((ex) => { + console.log(ex); + done(new Error('TypeScript compilation errors', ex)); + }); +}); -gulp.task('hygiene-all', (done) => run({ mode: 'all' }, done)); +gulp.task('compile', gulp.series('compileCore', 'compileApi')); -gulp.task('hygiene-branch', (done) => run({ mode: 'diffMain' }, done)); +gulp.task('precommit', (done) => run({ exitOnError: true, mode: 'staged' }, done)); gulp.task('output:clean', () => del(['coverage'])); -gulp.task('clean:cleanExceptTests', () => del(['clean:vsix', 'out/client', 'out/datascience-ui', 'out/server'])); +gulp.task('clean:cleanExceptTests', () => del(['clean:vsix', 'out/client'])); gulp.task('clean:vsix', () => del(['*.vsix'])); gulp.task('clean:out', () => del(['out'])); -gulp.task('clean:ipywidgets', () => spawnAsync('npm', ['run', 'build-ipywidgets-clean'], webpackEnv)); gulp.task('clean', gulp.parallel('output:clean', 'clean:vsix', 'clean:out')); @@ -113,53 +72,8 @@ gulp.task('checkNativeDependencies', (done) => { done(); }); -gulp.task('compile-ipywidgets', () => buildIPyWidgets()); - const webpackEnv = { NODE_OPTIONS: '--max_old_space_size=9096' }; -async function buildIPyWidgets() { - // if the output ipywidgest file exists, then no need to re-build. - // Barely changes. If making changes, then re-build manually. - if (!isCI && fs.existsSync(path.join(__dirname, 'out/ipywidgets/dist/ipywidgets.js'))) { - return; - } - await spawnAsync('npm', ['run', 'build-ipywidgets'], webpackEnv); -} -gulp.task('compile-notebooks', async () => { - await buildWebPackForDevOrProduction('./build/webpack/webpack.datascience-ui-notebooks.config.js'); -}); - -gulp.task('compile-renderers', async () => { - await buildWebPackForDevOrProduction('./build/webpack/webpack.datascience-ui-renderers.config.js'); -}); - -gulp.task('compile-viewers', async () => { - await buildWebPackForDevOrProduction('./build/webpack/webpack.datascience-ui-viewers.config.js'); -}); - -gulp.task('compile-webviews', gulp.series('compile-ipywidgets', 'compile-notebooks', 'compile-viewers')); - -gulp.task( - 'check-datascience-dependencies', - gulp.series( - (done) => { - if (process.env.VSC_PYTHON_FORCE_ANALYZER) { - process.env.XVSC_PYTHON_FORCE_ANALYZER = '1'; - } - process.env.VSC_PYTHON_FORCE_ANALYZER = '1'; - done(); - }, - 'compile-webviews', - () => checkDatascienceDependencies(), - (done) => { - if (!process.env.XVSC_PYTHON_FORCE_ANALYZER) { - delete process.env.VSC_PYTHON_FORCE_ANALYZER; - } - done(); - } - ) -); - async function buildWebPackForDevOrProduction(configFile, configNameForProductionBuilds) { if (configNameForProductionBuilds) { await buildWebPack(configNameForProductionBuilds, ['--config', configFile], webpackEnv); @@ -170,15 +84,40 @@ async function buildWebPackForDevOrProduction(configFile, configNameForProductio gulp.task('webpack', async () => { // Build node_modules. await buildWebPackForDevOrProduction('./build/webpack/webpack.extension.dependencies.config.js', 'production'); - // Build DS stuff (separately as it uses far too much memory and slows down CI). - // Individually is faster on CI. - await buildIPyWidgets(); - await buildWebPackForDevOrProduction('./build/webpack/webpack.datascience-ui-notebooks.config.js', 'production'); - await buildWebPackForDevOrProduction('./build/webpack/webpack.datascience-ui-viewers.config.js', 'production'); await buildWebPackForDevOrProduction('./build/webpack/webpack.extension.config.js', 'extension'); - await buildWebPackForDevOrProduction('./build/webpack/webpack.datascience-ui-renderers.config.js', 'production'); + await buildWebPackForDevOrProduction('./build/webpack/webpack.extension.browser.config.js', 'browser'); +}); + +gulp.task('addExtensionPackDependencies', async () => { + await buildLicense(); + await addExtensionPackDependencies(); }); +async function addExtensionPackDependencies() { + // Update the package.json to add extension pack dependencies at build time so that + // extension dependencies need not be installed during development + const packageJsonContents = await fsExtra.readFile('package.json', 'utf-8'); + const packageJson = JSON.parse(packageJsonContents); + packageJson.extensionPack = [ + 'ms-python.vscode-pylance', + 'ms-python.debugpy', + 'ms-python.vscode-python-envs', + ].concat(packageJson.extensionPack ? packageJson.extensionPack : []); + // Remove potential duplicates. + packageJson.extensionPack = packageJson.extensionPack.filter( + (item, index) => packageJson.extensionPack.indexOf(item) === index, + ); + await fsExtra.writeFile('package.json', JSON.stringify(packageJson, null, 4), 'utf-8'); +} + +async function buildLicense() { + const headerPath = path.join(__dirname, 'build', 'license-header.txt'); + const licenseHeader = await fsExtra.readFile(headerPath, 'utf-8'); + const license = await fsExtra.readFile('LICENSE', 'utf-8'); + + await fsExtra.writeFile('LICENSE', `${licenseHeader}\n${license}`, 'utf-8'); +} + gulp.task('updateBuildNumber', async () => { await updateBuildNumber(argv); }); @@ -190,14 +129,14 @@ async function updateBuildNumber(args) { const packageJson = JSON.parse(packageJsonContents); // Change version number - const versionParts = packageJson['version'].split('.'); + const versionParts = packageJson.version.split('.'); const buildNumberPortion = versionParts.length > 2 ? versionParts[2].replace(/(\d+)/, args.buildNumber) : args.buildNumber; const newVersion = versionParts.length > 1 ? `${versionParts[0]}.${versionParts[1]}.${buildNumberPortion}` - : packageJson['version']; - packageJson['version'] = newVersion; + : packageJson.version; + packageJson.version = newVersion; // Write back to the package json await fsExtra.writeFile('package.json', JSON.stringify(packageJson, null, 4), 'utf-8'); @@ -207,7 +146,7 @@ async function updateBuildNumber(args) { const changeLogContents = await fsExtra.readFile('CHANGELOG.md', 'utf-8'); const fixedContents = changeLogContents.replace( /##\s*(\d+)\.(\d+)\.(\d+)\s*\(/, - `## $1.$2.${buildNumberPortion} (` + `## $1.$2.${buildNumberPortion} (`, ); // Write back to changelog.md @@ -224,7 +163,7 @@ async function buildWebPack(webpackConfigName, args, env) { const stdOut = await spawnAsync( 'npm', ['run', 'webpack', '--', ...args, ...['--mode', 'production', '--devtool', 'source-map']], - env + env, ); const stdOutLines = stdOut .split(os.EOL) @@ -236,8 +175,8 @@ async function buildWebPack(webpackConfigName, args, env) { .filter( (item) => allowedWarnings.findIndex((allowedWarning) => - item.toLowerCase().startsWith(allowedWarning.toLowerCase()) - ) == -1 + item.toLowerCase().startsWith(allowedWarning.toLowerCase()), + ) === -1, ); const errors = stdOutLines.some((item) => item.startsWith('ERROR in')); if (errors) { @@ -245,7 +184,7 @@ async function buildWebPack(webpackConfigName, args, env) { } if (warnings.length > 0) { throw new Error( - `Warnings in ${webpackConfigName}, Check gulpfile.js to see if the warning should be allowed., \n\n${stdOut}` + `Warnings in ${webpackConfigName}, Check gulpfile.js to see if the warning should be allowed., \n\n${stdOut}`, ); } } @@ -256,163 +195,48 @@ function getAllowedWarningsForWebPack(buildConfig) { 'WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).', 'WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.', 'WARNING in webpack performance recommendations:', - 'WARNING in ./node_modules/vsls/vscode.js', 'WARNING in ./node_modules/encoding/lib/iconv-loader.js', - 'WARNING in ./node_modules/ws/lib/BufferUtil.js', - 'WARNING in ./node_modules/ws/lib/buffer-util.js', - 'WARNING in ./node_modules/ws/lib/Validation.js', - 'WARNING in ./node_modules/ws/lib/validation.js', - 'WARNING in ./node_modules/@jupyterlab/services/node_modules/ws/lib/buffer-util.js', - 'WARNING in ./node_modules/@jupyterlab/services/node_modules/ws/lib/validation.js', 'WARNING in ./node_modules/any-promise/register.js', - 'WARNING in ./node_modules/log4js/lib/appenders/index.js', - 'WARNING in ./node_modules/log4js/lib/clustering.js', 'WARNING in ./node_modules/diagnostic-channel-publishers/dist/src/azure-coretracing.pub.js', - 'WARNING in ./node_modules/applicationinsights/out/AutoCollection/NativePerformance.js' + 'WARNING in ./node_modules/applicationinsights/out/AutoCollection/NativePerformance.js', ]; case 'extension': return [ 'WARNING in ./node_modules/encoding/lib/iconv-loader.js', - 'WARNING in ./node_modules/ws/lib/BufferUtil.js', - 'WARNING in ./node_modules/ws/lib/buffer-util.js', - 'WARNING in ./node_modules/ws/lib/Validation.js', - 'WARNING in ./node_modules/ws/lib/validation.js', 'WARNING in ./node_modules/any-promise/register.js', 'remove-files-plugin@1.4.0:', - 'WARNING in ./node_modules/@jupyterlab/services/node_modules/ws/lib/buffer-util.js', - 'WARNING in ./node_modules/@jupyterlab/services/node_modules/ws/lib/validation.js', - 'WARNING in ./node_modules/@jupyterlab/services/node_modules/ws/lib/Validation.js', 'WARNING in ./node_modules/diagnostic-channel-publishers/dist/src/azure-coretracing.pub.js', - 'WARNING in ./node_modules/applicationinsights/out/AutoCollection/NativePerformance.js' + 'WARNING in ./node_modules/applicationinsights/out/AutoCollection/NativePerformance.js', ]; case 'debugAdapter': return [ 'WARNING in ./node_modules/vscode-uri/lib/index.js', 'WARNING in ./node_modules/diagnostic-channel-publishers/dist/src/azure-coretracing.pub.js', - 'WARNING in ./node_modules/applicationinsights/out/AutoCollection/NativePerformance.js' + 'WARNING in ./node_modules/applicationinsights/out/AutoCollection/NativePerformance.js', + ]; + case 'browser': + return [ + 'WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).', + 'WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.', + 'WARNING in webpack performance recommendations:', ]; default: throw new Error('Unknown WebPack Configuration'); } } -gulp.task('renameSourceMaps', async () => { - // By default source maps will be disabled in the extension. - // Users will need to use the command `python.enableSourceMapSupport` to enable source maps. - const extensionSourceMap = path.join(__dirname, 'out', 'client', 'extension.js.map'); - await fs.rename(extensionSourceMap, `${extensionSourceMap}.disabled`); -}); gulp.task('verifyBundle', async () => { const matches = await glob.sync(path.join(__dirname, '*.vsix')); - if (!matches || matches.length == 0) { + if (!matches || matches.length === 0) { throw new Error('Bundle does not exist'); } else { console.log(`Bundle ${matches[0]} exists.`); } }); -gulp.task('prePublishBundle', gulp.series('webpack', 'renameSourceMaps')); -gulp.task('checkDependencies', gulp.series('checkNativeDependencies', 'check-datascience-dependencies')); -gulp.task('prePublishNonBundle', gulp.series('compile', 'compile-webviews')); - -gulp.task('installPythonRequirements', async () => { - const args = [ - '-m', - 'pip', - '--disable-pip-version-check', - 'install', - '-t', - './pythonFiles/lib/python', - '--no-cache-dir', - '--implementation', - 'py', - '--no-deps', - '--upgrade', - '-r', - './requirements.txt' - ]; - const success = await spawnAsync(process.env.CI_PYTHON_PATH || 'python3', args, undefined, true) - .then(() => true) - .catch((ex) => { - console.error("Failed to install Python Libs using 'python3'", ex); - return false; - }); - if (!success) { - console.info("Failed to install Python Libs using 'python3', attempting to install using 'python'"); - await spawnAsync('python', args).catch((ex) => - console.error("Failed to install Python Libs using 'python'", ex) - ); - } -}); - -// See https://github.com/microsoft/vscode-python/issues/7136 -gulp.task('installDebugpy', async () => { - // Install dependencies needed for 'install_debugpy.py' - const depsArgs = [ - '-m', - 'pip', - '--disable-pip-version-check', - 'install', - '-t', - './pythonFiles/lib/temp', - '-r', - './build/debugger-install-requirements.txt' - ]; - const successWithWheelsDeps = await spawnAsync(process.env.CI_PYTHON_PATH || 'python3', depsArgs, undefined, true) - .then(() => true) - .catch((ex) => { - console.error("Failed to install new DEBUGPY wheels using 'python3'", ex); - return false; - }); - if (!successWithWheelsDeps) { - console.info( - "Failed to install dependencies need by 'install_debugpy.py' using 'python3', attempting to install using 'python'" - ); - await spawnAsync('python', depsArgs).catch((ex) => - console.error("Failed to install dependencies need by 'install_debugpy.py' using 'python'", ex) - ); - } - - // Install new DEBUGPY with wheels for python 3.7 - const wheelsArgs = ['./pythonFiles/install_debugpy.py']; - const wheelsEnv = { PYTHONPATH: './pythonFiles/lib/temp' }; - const successWithWheels = await spawnAsync(process.env.CI_PYTHON_PATH || 'python3', wheelsArgs, wheelsEnv, true) - .then(() => true) - .catch((ex) => { - console.error("Failed to install new DEBUGPY wheels using 'python3'", ex); - return false; - }); - if (!successWithWheels) { - console.info("Failed to install new DEBUGPY wheels using 'python3', attempting to install using 'python'"); - await spawnAsync('python', wheelsArgs, wheelsEnv).catch((ex) => - console.error("Failed to install DEBUGPY wheels using 'python'", ex) - ); - } - - rmrf.sync('./pythonFiles/lib/temp'); -}); - -gulp.task('installPythonLibs', gulp.series('installPythonRequirements', 'installDebugpy')); - -function uploadExtension(uploadBlobName) { - const azure = require('gulp-azure-storage'); - const rename = require('gulp-rename'); - return gulp - .src('*python*.vsix') - .pipe(rename(uploadBlobName)) - .pipe( - azure.upload({ - account: process.env.AZURE_STORAGE_ACCOUNT, - key: process.env.AZURE_STORAGE_ACCESS_KEY, - container: process.env.AZURE_STORAGE_CONTAINER - }) - ); -} - -gulp.task('uploadDeveloperExtension', () => uploadExtension('ms-python-insiders.vsix')); -gulp.task('uploadReleaseExtension', () => - uploadExtension(`ms-python-${process.env.TRAVIS_BRANCH || process.env.BUILD_SOURCEBRANCHNAME}.vsix`) -); +gulp.task('prePublishBundle', gulp.series('webpack')); +gulp.task('checkDependencies', gulp.series('checkNativeDependencies')); +gulp.task('prePublishNonBundle', gulp.series('compile')); function spawnAsync(command, args, env, rejectOnStdErr = false) { env = env || {}; @@ -439,206 +263,6 @@ function spawnAsync(command, args, env, rejectOnStdErr = false) { }); } -/** - * Analyzes the dependencies pulled in by WebPack. - * Details on the structure of the stats json file can be found here https://webpack.js.org/api/stats/ - * - * We go through the stats file and check all node modules that are part of the bundle(s). - * If they are in the bundle, they are used, hence they need to be registered in the `package.datascience-ui.dependencies.json` file. - * If not found, this will throw an error with the list of those dependencies. - * If a dependency is no longer use, this will throw an error with the details of the module to be removed from the `package.datascience-ui.dependencies.json` file. - * - */ -async function checkDatascienceDependencies() { - const existingModulesFileName = 'package.datascience-ui.dependencies.json'; - const existingModulesFile = path.join(__dirname, existingModulesFileName); - const existingModulesList = JSON.parse(await fsExtra.readFile(existingModulesFile).then((data) => data.toString())); - const existingModules = new Set(existingModulesList); - const existingModulesCopy = new Set(existingModulesList); - - const newModules = new Set(); - const packageLock = JSON.parse(await fsExtra.readFile('package-lock.json').then((data) => data.toString())); - const modulesInPackageLock = Object.keys(packageLock.dependencies); - - // Right now the script only handles two parts in the dependency name (with one '/'). - // If we have dependencies with more than one '/', then update this code. - if (modulesInPackageLock.some((dependency) => dependency.indexOf('/') !== dependency.lastIndexOf('/'))) { - throwAndLogError("Dependencies detected with more than one '/', please update this script."); - } - - /** - * Processes the output in a webpack stat file. - * - * @param {string} statFile - */ - async function processWebpackStatFile(statFile) { - /** @type{import("webpack").Stats.ToJsonOutput} */ - const json = await fsExtra.readFile(statFile).then((data) => JSON.parse(data.toString())); - json.children.forEach((child) => { - child.chunks.forEach((chunk) => { - processModules(chunk.modules); - (chunk.origins || []).forEach((origin) => processOriginOrReason(origin)); - }); - }); - json.chunks.forEach((chunk) => { - processModules(chunk.modules); - (chunk.origins || []).forEach((origin) => processOriginOrReason(origin)); - }); - } - - /** - * @param {string} name Name of module to find. - * @param {string} moduleName1 Another name of module to find. - * @param {string} moduleName2 Yet another name of module to find. - * @returns - */ - function findModule(name, moduleName1, moduleName2) { - // If the module name contains `?`, then its a webpack loader that can be ignored. - if (name.includes('loader') && (name.includes('?') || name.includes('!'))) { - return; - } - const matchedModules = modulesInPackageLock.filter( - (dependency) => dependency === moduleName2 || dependency === moduleName1 || dependency === name - ); - switch (matchedModules.length) { - case 0: - throwAndLogError( - `Dependency not found in package-lock.json, Dependency = '${name}, ${moduleName1}, ${moduleName2}'` - ); - break; - case 1: - break; - default: { - throwAndLogError(`Exact Dependency not found in package-lock.json, Dependency = '${name}'`); - } - } - - const moduleName = matchedModules[0]; - if (existingModulesCopy.has(moduleName)) { - existingModulesCopy.delete(moduleName); - } - if (existingModules.has(moduleName) || newModules.has(moduleName)) { - return; - } - newModules.add(moduleName); - } - - /** - * Processes webpack stat Modules. - * - * @param modules { Array. } - * @returns - */ - function processModules(modules) { - (modules || []).forEach(processModule); - } - - /** - * Processes a webpack stat Module. - * - * @param module { import("webpack").Stats.FnModules } - * @returns - */ - function processModule(module) { - const name = module.name; - - if (!name.includes('/node_modules')) { - processReasons(module.reasons); - processModules(module.modules); - return; - } - - let nameWithoutNodeModules = name.substring('/node_modules'.length); - // Special case expose-loader. - if (nameWithoutNodeModules.startsWith('/expose-loader')) { - nameWithoutNodeModules = nameWithoutNodeModules.substring( - nameWithoutNodeModules.indexOf('/node_modules') + '/node_modules'.length - ); - } - - let moduleName1 = nameWithoutNodeModules.split('/')[1]; - moduleName1 = moduleName1.endsWith('!.') ? moduleName1.substring(0, moduleName1.length - 2) : moduleName1; - const moduleName2 = `${nameWithoutNodeModules.split('/')[1]}/${nameWithoutNodeModules.split('/')[2]}`; - - findModule(name, moduleName1, moduleName2); - - processModules(module.modules); - processReasons(module.reasons); - } - - /** - * Processes a origin or a reason object from a webpack stat. - * - * @param {*} origin - * @returns - */ - function processOriginOrReason(origin) { - if (!origin || !origin.name) { - return; - } - const name = origin.name; - if (!name.includes('/node_modules')) { - processReasons(origin.reasons); - return; - } - - let nameWithoutNodeModules = name.substring('/node_modules'.length); - // Special case expose-loader. - if (nameWithoutNodeModules.startsWith('/expose-loader')) { - nameWithoutNodeModules = nameWithoutNodeModules.substring( - nameWithoutNodeModules.indexOf('/node_modules') + '/node_modules'.length - ); - } - - let moduleName1 = nameWithoutNodeModules.split('/')[1]; - moduleName1 = moduleName1.endsWith('!.') ? moduleName1.substring(0, moduleName1.length - 2) : moduleName1; - const moduleName2 = `${nameWithoutNodeModules.split('/')[1]}/${nameWithoutNodeModules.split('/')[2]}`; - - findModule(name, moduleName1, moduleName2); - - processReasons(origin.reasons); - } - - /** - * Processes the `reasons` property of a webpack stat module object. - * - * @param {*} reasons - */ - function processReasons(reasons) { - reasons = (reasons || []) - .map((reason) => reason.userRequest) - .filter((item) => typeof item === 'string' && !item.startsWith('.')); - reasons.forEach((item) => processOriginOrReason(item)); - } - - await processWebpackStatFile(path.join(__dirname, 'out', 'datascience-ui', 'notebook', 'notebook.stats.json')); - await processWebpackStatFile(path.join(__dirname, 'out', 'datascience-ui', 'viewers', 'viewers.stats.json')); - - const errorMessages = []; - if (newModules.size > 0) { - errorMessages.push( - `Add the untracked dependencies '${Array.from(newModules.values()).join( - ', ' - )}' to ${existingModulesFileName}` - ); - } - if (existingModulesCopy.size > 0) { - errorMessages.push( - `Remove the unused '${Array.from(existingModulesCopy.values()).join( - ', ' - )}' dependencies from ${existingModulesFileName}` - ); - } - if (errorMessages.length > 0) { - throwAndLogError(errorMessages.join('\n')); - } -} -function throwAndLogError(message) { - if (message.length > 0) { - console.error(colors.red(message)); - throw new Error(message); - } -} function hasNativeDependencies() { let nativeDependencies = nativeDependencyChecker.check(path.join(__dirname, 'node_modules')); if (!Array.isArray(nativeDependencies) || nativeDependencies.length === 0) { @@ -647,15 +271,15 @@ function hasNativeDependencies() { const dependencies = JSON.parse(spawn.sync('npm', ['ls', '--json', '--prod']).stdout.toString()); const jsonProperties = Object.keys(flat.flatten(dependencies)); nativeDependencies = _.flatMap(nativeDependencies, (item) => - path.dirname(item.substring(item.indexOf('node_modules') + 'node_modules'.length)).split(path.sep) + path.dirname(item.substring(item.indexOf('node_modules') + 'node_modules'.length)).split(path.sep), ) .filter((item) => item.length > 0) - .filter((item) => !item.includes('zeromq')) // This is a known native. Allow this one for now + .filter((item) => item !== 'fsevents') .filter( (item) => jsonProperties.findIndex((flattenedDependency) => - flattenedDependency.endsWith(`dependencies.${item}.version`) - ) >= 0 + flattenedDependency.endsWith(`dependencies.${item}.version`), + ) >= 0, ); if (nativeDependencies.length > 0) { console.error('Native dependencies detected', nativeDependencies); @@ -663,443 +287,3 @@ function hasNativeDependencies() { } return false; } - -/** - * @typedef {Object} hygieneOptions - creates a new type named 'SpecialType' - * @property {'changes'|'staged'|'all'|'compile'|'diffMain'} [mode=] - Mode. - * @property {boolean=} skipIndentationCheck - Skip indentation checks. - * @property {boolean=} skipFormatCheck - Skip format checks. - * @property {boolean=} skipLinter - Skip linter. - */ - -/** - * - * @param {hygieneOptions} options - */ -function getTsProject(options) { - return ts.createProject('tsconfig.json'); -} - -let configuration; -/** - * - * @param {hygieneOptions} options - */ -function getLinter(options) { - configuration = configuration ? configuration : tslint.Configuration.findConfiguration(null, '.'); - const program = tslint.Linter.createProgram('./tsconfig.json'); - const linter = new tslint.Linter({ formatter: 'json' }, program); - return { linter, configuration }; -} -let compilationInProgress = false; -let reRunCompilation = false; -/** - * - * @param {hygieneOptions} options - * @returns {NodeJS.ReadWriteStream} - */ -const hygiene = (options, done) => { - done = done || noop; - if (compilationInProgress) { - reRunCompilation = true; - return done(); - } - const fileListToProcess = options.mode === 'compile' ? undefined : getFileListToProcess(options); - if ( - Array.isArray(fileListToProcess) && - fileListToProcess !== all && - fileListToProcess.filter((item) => item.endsWith('.ts')).length === 0 - ) { - return done(); - } - - const started = new Date().getTime(); - compilationInProgress = true; - options = options || {}; - let errorCount = 0; - - const indentation = es.through(function (file) { - file.contents - .toString('utf8') - .split(/\r\n|\r|\n/) - .forEach((line, i) => { - if (/^\s*$/.test(line) || /^\S+.*$/.test(line)) { - // Empty or whitespace lines are OK. - } else if (/^(\s\s\s\s)+.*/.test(line)) { - // Good indent. - } else if (/^[\t]+.*/.test(line)) { - console.error( - file.relative + - '(' + - (i + 1) + - ',1): Bad whitespace indentation (use 4 spaces instead of tabs or other)' - ); - errorCount++; - } - }); - - this.emit('data', file); - }); - - const formatOptions = { verify: true, tsconfig: true, tslint: true, editorconfig: true, tsfmt: true }; - const formatting = es.map(function (file, cb) { - tsfmt - .processString(file.path, file.contents.toString('utf8'), formatOptions) - .then((result) => { - if (result.error) { - let message = result.message.trim(); - let formattedMessage = ''; - if (message.startsWith(__dirname)) { - message = message.substr(__dirname.length); - message = message.startsWith(path.sep) ? message.substr(1) : message; - const index = message.indexOf('.ts '); - if (index === -1) { - formattedMessage = colors.red(message); - } else { - const file = message.substr(0, index + 3); - const errorMessage = message.substr(index + 4).trim(); - formattedMessage = `${colors.red(file)} ${errorMessage}`; - } - } else { - formattedMessage = colors.red(message); - } - console.error(formattedMessage); - errorCount++; - } - cb(null, file); - }) - .catch(cb); - }); - - let reportedLinterFailures = []; - /** - * Report the linter failures - * @param {any[]} failures - */ - function reportLinterFailures(failures) { - return ( - failures - .map((failure) => { - const name = failure.name || failure.fileName; - const position = failure.startPosition; - const line = position.lineAndCharacter ? position.lineAndCharacter.line : position.line; - const character = position.lineAndCharacter - ? position.lineAndCharacter.character - : position.character; - - // Output in format similar to tslint for the linter to pickup. - const message = `ERROR: (${failure.ruleName}) ${relative(__dirname, name)}[${line + 1}, ${ - character + 1 - }]: ${failure.failure}`; - if (reportedLinterFailures.indexOf(message) === -1) { - console.error(message); - reportedLinterFailures.push(message); - return true; - } else { - return false; - } - }) - .filter((reported) => reported === true).length > 0 - ); - } - - const { linter, configuration } = getLinter(options); - const tsl = es.through(function (file) { - const contents = file.contents.toString('utf8'); - if (isCI) { - // Don't print anything to the console, we'll do that. - console.log('.'); - } - // Yes this is a hack, but tslinter doesn't provide an option to prevent this. - const oldWarn = console.warn; - console.warn = () => {}; - linter.failures = []; - linter.fixes = []; - linter.lint(file.relative, contents, configuration.results); - console.warn = oldWarn; - const result = linter.getResult(); - if (result.failureCount > 0 || result.errorCount > 0) { - const reported = reportLinterFailures(result.failures); - if (result.failureCount && reported) { - errorCount += result.failureCount; - } - if (result.errorCount && reported) { - errorCount += result.errorCount; - } - } - this.emit('data', file); - }); - - const tsFiles = []; - const tscFilesTracker = es.through(function (file) { - tsFiles.push(file.path.replace(/\\/g, '/')); - tsFiles.push(file.path); - this.emit('data', file); - }); - - const tsProject = getTsProject(options); - - const tsc = function () { - function customReporter() { - return { - error: function (error, typescript) { - const fullFilename = error.fullFilename || ''; - const relativeFilename = error.relativeFilename || ''; - if (tsFiles.findIndex((file) => fullFilename === file || relativeFilename === file) === -1) { - return; - } - console.error(`Error: ${error.message}`); - errorCount += 1; - }, - finish: function () { - // forget the summary. - console.log('Finished compilation'); - } - }; - } - const reporter = customReporter(); - return tsProject(reporter); - }; - - const files = options.mode === 'compile' ? tsProject.src() : getFilesToProcess(fileListToProcess); - const dest = options.mode === 'compile' ? './out' : '.'; - let result = files.pipe(filter((f) => f && f.stat && !f.stat.isDirectory())); - - if (!options.skipIndentationCheck) { - result = result.pipe(filter(indentationFilter)).pipe(indentation); - } - - result = result.pipe(filter(tslintFilter)); - - if (!options.skipFormatCheck) { - // result = result - // .pipe(formatting); - } - - if (!options.skipLinter) { - result = result.pipe(tsl); - } - let totalTime = 0; - result = result - .pipe(tscFilesTracker) - .pipe(sourcemaps.init()) - .pipe(tsc()) - .pipe( - sourcemaps.mapSources(function (sourcePath, file) { - let tsFileName = path.basename(file.path).replace(/js$/, 'ts'); - const qualifiedSourcePath = path.dirname(file.path).replace('out/', 'src/').replace('out\\', 'src\\'); - if (!fs.existsSync(path.join(qualifiedSourcePath, tsFileName))) { - const tsxFileName = path.basename(file.path).replace(/js$/, 'tsx'); - if (!fs.existsSync(path.join(qualifiedSourcePath, tsxFileName))) { - console.error(`ERROR: (source-maps) ${file.path}[1,1]: Source file not found`); - } else { - tsFileName = tsxFileName; - } - } - return path.join(path.relative(path.dirname(file.path), qualifiedSourcePath), tsFileName); - }) - ) - .pipe(sourcemaps.write('.', { includeContent: false })) - .pipe(gulp.dest(dest)) - .pipe( - es.through(null, function () { - if (errorCount > 0) { - const errorMessage = `Hygiene failed with errors 👎 . Check 'gulpfile.js' (completed in ${ - new Date().getTime() - started - }ms).`; - console.error(colors.red(errorMessage)); - exitHandler(options); - } else { - console.log( - colors.green( - `Hygiene passed with 0 errors 👍 (completed in ${new Date().getTime() - started}ms).` - ) - ); - } - // Reset error counter. - errorCount = 0; - reportedLinterFailures = []; - compilationInProgress = false; - if (reRunCompilation) { - reRunCompilation = false; - setTimeout(() => { - hygiene(options, done); - }, 10); - } - done(); - this.emit('end'); - }) - ) - .on('error', (ex) => { - exitHandler(options, ex); - done(); - }); - - return result; -}; - -/** - * @typedef {Object} runOptions - * @property {boolean=} exitOnError - Exit on error. - * @property {'changes'|'staged'|'all'} [mode=] - Mode. - * @property {string[]=} files - Optional list of files to be modified. - * @property {boolean=} skipIndentationCheck - Skip indentation checks. - * @property {boolean=} skipFormatCheck - Skip format checks. - * @property {boolean=} skipLinter - Skip linter. - * @property {boolean=} watch - Watch mode. - */ - -/** - * Run the linters. - * @param {runOptions} options - * @param {Error} ex - */ -function exitHandler(options, ex) { - console.error(); - if (ex) { - console.error(ex); - console.error(colors.red(ex)); - } - if (options.exitOnError) { - console.log('exit'); - process.exit(1); - } -} - -/** - * Run the linters. - * @param {runOptions} options - */ -function run(options, done) { - done = done || noop; - options = options ? options : {}; - options.exitOnError = typeof options.exitOnError === 'undefined' ? isCI : options.exitOnError; - process.once('unhandledRejection', (reason, p) => { - console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); - exitHandler(options); - }); - - // Clear screen each time - console.log('\x1Bc'); - const startMessage = `Hygiene starting`; - console.log(colors.blue(startMessage)); - - hygiene(options, done); -} - -function git(args) { - let result = cp.spawnSync('git', args, { encoding: 'utf-8' }); - return result.output.join('\n'); -} - -function getStagedFilesSync() { - const out = git(['diff', '--cached', '--name-only']); - return out.split(/\r?\n/).filter((l) => !!l); -} -function getAddedFilesSync() { - const out = git(['status', '-u', '-s']); - return out - .split(/\r?\n/) - .filter((l) => !!l) - .filter((l) => _.intersection(['A', '?', 'U'], l.substring(0, 2).trim().split('')).length > 0) - .map((l) => path.join(__dirname, l.substring(2).trim())); -} -function getAzureDevOpsVarValue(varName) { - return process.env[varName.replace(/\./g, '_').toUpperCase()]; -} -function getModifiedFilesSync() { - if (isCI) { - const isAzurePR = getAzureDevOpsVarValue('System.PullRequest.SourceBranch') !== undefined; - const isTravisPR = process.env.TRAVIS_PULL_REQUEST !== undefined && process.env.TRAVIS_PULL_REQUEST !== 'true'; - if (!isAzurePR && !isTravisPR) { - return []; - } - const targetBranch = process.env.TRAVIS_BRANCH || getAzureDevOpsVarValue('System.PullRequest.TargetBranch'); - if (targetBranch !== 'main') { - return []; - } - - const repo = process.env.TRAVIS_REPO_SLUG || getAzureDevOpsVarValue('Build.Repository.Name'); - const originOrUpstream = - repo.toUpperCase() === 'MICROSOFT/VSCODE-PYTHON' || - repo.toUpperCase() === 'VSCODE-PYTHON-DATASCIENCE/VSCODE-PYTHON' - ? 'origin' - : 'upstream'; - - // If on CI, get a list of modified files comparing against - // PR branch and main of current (assumed 'origin') repo. - try { - cp.execSync(`git remote set-branches --add ${originOrUpstream} main`, { - encoding: 'utf8', - cwd: __dirname - }); - cp.execSync('git fetch', { encoding: 'utf8', cwd: __dirname }); - } catch (ex) { - return []; - } - const cmd = `git diff --name-only HEAD ${originOrUpstream}/main`; - console.info(cmd); - const out = cp.execSync(cmd, { encoding: 'utf8', cwd: __dirname }); - return out - .split(/\r?\n/) - .filter((l) => !!l) - .filter((l) => l.length > 0) - .map((l) => l.trim().replace(/\//g, path.sep)) - .map((l) => path.join(__dirname, l)); - } else { - const out = cp.execSync('git status -u -s', { encoding: 'utf8' }); - return out - .split(/\r?\n/) - .filter((l) => !!l) - .filter( - (l) => _.intersection(['M', 'A', 'R', 'C', 'U', '?'], l.substring(0, 2).trim().split('')).length > 0 - ) - .map((l) => path.join(__dirname, l.substring(2).trim().replace(/\//g, path.sep))); - } -} - -function getDifferentFromMainFilesSync() { - const out = git(['diff', '--name-status', 'main']); - return out - .split(/\r?\n/) - .filter((l) => !!l) - .map((l) => path.join(__dirname, l.substring(2).trim())); -} - -/** - * @param {hygieneOptions} options - */ -function getFilesToProcess(fileList) { - const gulpSrcOptions = { base: '.' }; - return gulp.src(fileList, gulpSrcOptions); -} - -/** - * @param {hygieneOptions} options - */ -function getFileListToProcess(options) { - const mode = options ? options.mode : 'all'; - const gulpSrcOptions = { base: '.' }; - - // If we need only modified files, then filter the glob. - if (options && options.mode === 'changes') { - return getModifiedFilesSync().filter((f) => fs.existsSync(f)); - } - - if (options && options.mode === 'staged') { - return getStagedFilesSync().filter((f) => fs.existsSync(f)); - } - - if (options && options.mode === 'diffMain') { - return getDifferentFromMainFilesSync().filter((f) => fs.existsSync(f)); - } - - return all; -} - -exports.hygiene = hygiene; - -// this allows us to run hygiene via CLI (e.g. `node gulfile.js`). -if (require.main === module) { - run({ exitOnError: true, mode: 'staged' }, () => {}); -} diff --git a/images/ConfigureDebugger.gif b/images/ConfigureDebugger.gif index 359e1a7493fd..41113d65896d 100644 Binary files a/images/ConfigureDebugger.gif and b/images/ConfigureDebugger.gif differ diff --git a/images/ConfigureTests.gif b/images/ConfigureTests.gif index 6da0100d593b..38ae2db551e1 100644 Binary files a/images/ConfigureTests.gif and b/images/ConfigureTests.gif differ diff --git a/images/InterpreterSelectionZoom.gif b/images/InterpreterSelectionZoom.gif index 576a438a7d3b..dc5db03aad3d 100644 Binary files a/images/InterpreterSelectionZoom.gif and b/images/InterpreterSelectionZoom.gif differ diff --git a/images/OpenOrCreateNotebook.gif b/images/OpenOrCreateNotebook.gif index 98256a97f190..a0957d415d7d 100644 Binary files a/images/OpenOrCreateNotebook.gif and b/images/OpenOrCreateNotebook.gif differ diff --git a/images/addIcon.PNG b/images/addIcon.PNG new file mode 100644 index 000000000000..8027e617e9ec Binary files /dev/null and b/images/addIcon.PNG differ diff --git a/images/codeIcon.PNG b/images/codeIcon.PNG new file mode 100644 index 000000000000..7ad46cee077f Binary files /dev/null and b/images/codeIcon.PNG differ diff --git a/images/dataViewerIcon.PNG b/images/dataViewerIcon.PNG new file mode 100644 index 000000000000..6848c600794d Binary files /dev/null and b/images/dataViewerIcon.PNG differ diff --git a/images/dataviewer.gif b/images/dataviewer.gif index b2f5e080a401..ce0c81676c09 100644 Binary files a/images/dataviewer.gif and b/images/dataviewer.gif differ diff --git a/images/exportIcon.PNG b/images/exportIcon.PNG new file mode 100644 index 000000000000..e5e588040ee6 Binary files /dev/null and b/images/exportIcon.PNG differ diff --git a/images/kernelchange.gif b/images/kernelchange.gif index a414e2252efa..d2b753b84c09 100644 Binary files a/images/kernelchange.gif and b/images/kernelchange.gif differ diff --git a/images/markdownIcon.PNG b/images/markdownIcon.PNG new file mode 100644 index 000000000000..04e5d67749db Binary files /dev/null and b/images/markdownIcon.PNG differ diff --git a/images/playIcon.PNG b/images/playIcon.PNG new file mode 100644 index 000000000000..60ae4a2051df Binary files /dev/null and b/images/playIcon.PNG differ diff --git a/images/plotViewerIcon.PNG b/images/plotViewerIcon.PNG new file mode 100644 index 000000000000..e8ecf0d97b5e Binary files /dev/null and b/images/plotViewerIcon.PNG differ diff --git a/images/plotviewer.gif b/images/plotviewer.gif index d1f6524f3769..a3c438b761e0 100644 Binary files a/images/plotviewer.gif and b/images/plotviewer.gif differ diff --git a/images/remoteserver.gif b/images/remoteserver.gif index be836e673b72..f979d557aa6b 100644 Binary files a/images/remoteserver.gif and b/images/remoteserver.gif differ diff --git a/images/savetopythonfile.png b/images/savetopythonfile.png index ccb0a7bfa387..e4a7f08d3db0 100644 Binary files a/images/savetopythonfile.png and b/images/savetopythonfile.png differ diff --git a/images/variableExplorerIcon.PNG b/images/variableExplorerIcon.PNG new file mode 100644 index 000000000000..f8363dda9de4 Binary files /dev/null and b/images/variableExplorerIcon.PNG differ diff --git a/images/variableexplorer.png b/images/variableexplorer.png index 0f6c9f73b4de..31197571b796 100644 Binary files a/images/variableexplorer.png and b/images/variableexplorer.png differ diff --git a/news/.vscode/settings.json b/news/.vscode/settings.json deleted file mode 100644 index 2b759d72bd35..000000000000 --- a/news/.vscode/settings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "python.languageServer": "Microsoft", - "python.formatting.provider": "black", - "editor.formatOnSave": true, - "python.testing.pytestArgs": ["."], - "python.testing.unittestEnabled": false, - "python.testing.nosetestsEnabled": false, - "python.testing.pytestEnabled": true -} diff --git a/news/1 Enhancements/14090.md b/news/1 Enhancements/14090.md deleted file mode 100644 index 341e003b141c..000000000000 --- a/news/1 Enhancements/14090.md +++ /dev/null @@ -1 +0,0 @@ -Do not opt users out of the insiders program if they have a stable version installed. diff --git a/news/1 Enhancements/README.md b/news/1 Enhancements/README.md deleted file mode 100644 index 2a159b65f4f0..000000000000 --- a/news/1 Enhancements/README.md +++ /dev/null @@ -1,2 +0,0 @@ -Changes that add new features. - diff --git a/news/2 Fixes/13942.md b/news/2 Fixes/13942.md deleted file mode 100644 index cfed3d1937a1..000000000000 --- a/news/2 Fixes/13942.md +++ /dev/null @@ -1 +0,0 @@ -Fix isolate script to only remove current working directory. diff --git a/news/2 Fixes/13955.md b/news/2 Fixes/13955.md deleted file mode 100644 index 8d0adec6d36d..000000000000 --- a/news/2 Fixes/13955.md +++ /dev/null @@ -1 +0,0 @@ -Make sure server name and kernel name show up when connecting. \ No newline at end of file diff --git a/news/2 Fixes/14016.md b/news/2 Fixes/14016.md deleted file mode 100644 index 022396ed2b00..000000000000 --- a/news/2 Fixes/14016.md +++ /dev/null @@ -1 +0,0 @@ -Have Custom Editors load on editor show unless autostart is disabled. \ No newline at end of file diff --git a/news/2 Fixes/14212.md b/news/2 Fixes/14212.md deleted file mode 100644 index 97b17eefe462..000000000000 --- a/news/2 Fixes/14212.md +++ /dev/null @@ -1 +0,0 @@ -Fix interactive debugging starting (trimQuotes error). \ No newline at end of file diff --git a/news/2 Fixes/14213.md b/news/2 Fixes/14213.md deleted file mode 100644 index 2e3a90f6bdd0..000000000000 --- a/news/2 Fixes/14213.md +++ /dev/null @@ -1 +0,0 @@ -Use the kernel defined in the metadata of Notebook instead of using the default workspace interpreter. diff --git a/news/2 Fixes/14216.md b/news/2 Fixes/14216.md deleted file mode 100644 index 8dfd8ad1ba21..000000000000 --- a/news/2 Fixes/14216.md +++ /dev/null @@ -1 +0,0 @@ -Fix latex output not showing up without a 'display' call. \ No newline at end of file diff --git a/news/2 Fixes/README.md b/news/2 Fixes/README.md deleted file mode 100644 index cc5e1020961d..000000000000 --- a/news/2 Fixes/README.md +++ /dev/null @@ -1 +0,0 @@ -Changes that fix broken behaviour. diff --git a/news/3 Code Health/14013.md b/news/3 Code Health/14013.md deleted file mode 100644 index 0a4af31929a2..000000000000 --- a/news/3 Code Health/14013.md +++ /dev/null @@ -1 +0,0 @@ -Add Windows unit tests to the PR validation pipeline . diff --git a/news/3 Code Health/README.md b/news/3 Code Health/README.md deleted file mode 100644 index 10619f41f3a4..000000000000 --- a/news/3 Code Health/README.md +++ /dev/null @@ -1 +0,0 @@ -Changes that should not be user-facing. diff --git a/news/README.md b/news/README.md deleted file mode 100644 index f26d25030fab..000000000000 --- a/news/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# News - -Our changelog is automatically generated from individual news entry files. -This alleviates the burden of having to go back and try to figure out -what changed in a release. It also helps tie pull requests back to the -issue(s) it addresses. Finally, it avoids merge conflicts between pull requests -which would occur if multiple pull requests tried to edit the changelog. - -If a change does not warrant a news entry, the `skip news` label can be added -to a pull request to signal this fact. - -## Entries - -Each news entry is represented by a Markdown file that contains the -relevant details of what changed. The file name of the news entry is -the issue that corresponds to the change along with an optional nonce in -case a single issue corresponds to multiple changes. The directory -the news entry is saved in specifies what section of the changelog the -change corresponds to. External contributors should also make sure to -thank themselves for taking the time and effort to contribute. - -As an example, a change corresponding to a bug reported in issue #42 -would be saved in the `1 Fixes` directory and named `42.md` -(or `42-nonce_value.md` if there was a need for multiple entries -regarding issue #42) and could contain the following: - -```markdown -[Answer]() -to the Ultimate Question of Life, the Universe, and Everything! -(thanks [Don Jaymanne](https://github.com/donjayamanne/)) -``` - -This would then be made into an entry in the changelog that was in the -`Fixes` section, contained the details as found in the file, and tied -to issue #42. - -## Generating the changelog - -The `announce` script can do 3 possible things: - -1. Validate that the changelog _could_ be successfully generated -2. Generate the changelog entries -3. Generate the changelog entries **and** `git-rm` the news entry files - -The first option is used in CI to make sure any added news entries -will not cause trouble at release time. The second option is for -filling in the changelog for interim releases, e.g. a beta release. -The third option is for final releases that get published to the -[VS Code marketplace](https://marketplace.visualstudio.com/VSCode). - -For options 2 & 3, the changelog is sent to stdout so it can be temporarily -saved to a file: - -```sh -python3 news > entry.txt -``` - -It can also be redirected to an editor buffer, e.g.: - -```sh -python3 news | code-insiders - -``` diff --git a/news/__main__.py b/news/__main__.py deleted file mode 100644 index b496ec1d0c8c..000000000000 --- a/news/__main__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -import runpy - -runpy.run_module('announce', run_name='__main__', alter_sys=True) diff --git a/news/announce.py b/news/announce.py deleted file mode 100644 index d4d7dd1cfd66..000000000000 --- a/news/announce.py +++ /dev/null @@ -1,193 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -"""Generate the changelog. - -Usage: announce [--dry_run | --interim | --final] [--update=] [] - -""" -import dataclasses -import datetime -import enum -import json -import operator -import os -import pathlib -import re -import subprocess -import sys - -import docopt - - -FILENAME_RE = re.compile(r"(?P\d+)(?P-\S+)?\.md") - - -@dataclasses.dataclass -class NewsEntry: - """Representation of a news entry.""" - - issue_number: int - description: str - path: pathlib.Path - - -def news_entries(directory): - """Yield news entries in the directory. - - Entries are sorted by issue number. - - """ - entries = [] - for path in directory.iterdir(): - if path.name == "README.md": - continue - match = FILENAME_RE.match(path.name) - if match is None: - raise ValueError(f"{path} has a bad file name") - issue = int(match.group("issue")) - try: - entry = path.read_text("utf-8") - except UnicodeDecodeError as exc: - raise ValueError(f"'{path}' is not encoded as UTF-8") from exc - if "\ufeff" in entry: - raise ValueError(f"'{path}' contains the BOM") - entries.append(NewsEntry(issue, entry, path)) - entries.sort(key=operator.attrgetter("issue_number")) - yield from entries - - -@dataclasses.dataclass -class SectionTitle: - """Create a data object for a section of the changelog.""" - - index: int - title: str - path: pathlib.Path - - -def sections(directory): - """Yield the sections in their appropriate order.""" - found = [] - for path in directory.iterdir(): - if not path.is_dir() or path.name.startswith((".", "_")): - continue - position, sep, title = path.name.partition(" ") - if not sep: - print( - f"directory {path.name!r} is missing a ranking; skipping", - file=sys.stderr, - ) - continue - found.append(SectionTitle(int(position), title, path)) - return sorted(found, key=operator.attrgetter("index")) - - -def gather(directory): - """Gather all the entries together.""" - data = [] - for section in sections(directory): - data.append((section, list(news_entries(section.path)))) - return data - - -def entry_markdown(entry): - """Generate the Markdown for the specified entry.""" - enumerated_item = "1. " - indent = " " * len(enumerated_item) - issue_url = ( - f"https://github.com/Microsoft/vscode-python/issues/{entry.issue_number}" - ) - issue_md = f"([#{entry.issue_number}]({issue_url}))" - entry_lines = entry.description.strip().splitlines() - formatted_lines = [f"{enumerated_item}{entry_lines[0]}"] - formatted_lines.extend(f"{indent}{line}" for line in entry_lines[1:]) - formatted_lines.append(f"{indent}{issue_md}") - return "\n".join(formatted_lines) - - -def changelog_markdown(data): - """Generate the Markdown for the release.""" - changelog = [] - for section, entries in data: - changelog.append(f"### {section.title}") - changelog.append("") - changelog.extend(map(entry_markdown, entries)) - changelog.append("") - return "\n".join(changelog) - - -def git_rm(path): - """Run git-rm on the path.""" - status = subprocess.run( - ["git", "rm", os.fspath(path.resolve())], - shell=False, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) - try: - status.check_returncode() - except Exception: - print(status.stdout, file=sys.stderr) - raise - - -def cleanup(data): - """Remove news entries from git and disk.""" - for section, entries in data: - for entry in entries: - git_rm(entry.path) - - -class RunType(enum.Enum): - """Possible run-time options.""" - - dry_run = 0 - interim = 1 - final = 2 - - -def complete_news(version, entry, previous_news): - """Prepend a news entry to the previous news file.""" - title, _, previous_news = previous_news.partition("\n") - title = title.strip() - previous_news = previous_news.strip() - section_title = (f"## {version} ({datetime.date.today().strftime('%d %B %Y')})" - ).replace("(0", "(") - # TODO: Insert the "Thank you!" section (in monthly releases)? - return f"{title}\n\n{section_title}\n\n{entry.strip()}\n\n\n{previous_news}" - - -def main(run_type, directory, news_file=None): - directory = pathlib.Path(directory) - data = gather(directory) - markdown = changelog_markdown(data) - if news_file: - with open(news_file, "r", encoding="utf-8") as file: - previous_news = file.read() - package_config_path = pathlib.Path(news_file).parent / "package.json" - config = json.loads(package_config_path.read_text()) - new_news = complete_news(config["version"], markdown, previous_news) - if run_type == RunType.dry_run: - print(f"would be written to {news_file}:") - print() - print(new_news) - else: - with open(news_file, "w", encoding="utf-8") as file: - file.write(new_news) - else: - print(markdown) - if run_type == RunType.final: - cleanup(data) - - -if __name__ == "__main__": - arguments = docopt.docopt(__doc__) - for possible_run_type in RunType: - if arguments[f"--{possible_run_type.name}"]: - run_type = possible_run_type - break - else: - run_type = RunType.interim - directory = arguments[""] or pathlib.Path(__file__).parent - main(run_type, directory, arguments["--update"]) diff --git a/news/requirements.in b/news/requirements.in deleted file mode 100644 index ab0e1cc5187e..000000000000 --- a/news/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -docopt -pytest diff --git a/news/requirements.txt b/news/requirements.txt deleted file mode 100644 index 2fdb54237f62..000000000000 --- a/news/requirements.txt +++ /dev/null @@ -1,16 +0,0 @@ -# -# This file is autogenerated by pip-compile -# To update, run: -# -# pip-compile -# -attrs==19.3.0 # via pytest -docopt==0.6.2 # via -r requirements.in -iniconfig==1.0.1 # via pytest -packaging==20.4 # via pytest -pluggy==0.13.1 # via pytest -py==1.9.0 # via pytest -pyparsing==2.4.5 # via packaging -pytest==6.1.0 # via -r requirements.in -six==1.13.0 # via packaging -toml==0.10.1 # via pytest diff --git a/news/test_announce.py b/news/test_announce.py deleted file mode 100644 index acc125a7c360..000000000000 --- a/news/test_announce.py +++ /dev/null @@ -1,208 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -import codecs -import datetime -import pathlib - -import docopt -import pytest - -import announce as ann - - -@pytest.fixture -def directory(tmpdir): - """Fixture to create a temp directory wrapped in a pathlib.Path object.""" - return pathlib.Path(tmpdir) - - -def test_news_entry_formatting(directory): - issue = 42 - normal_entry = directory / f"{issue}.md" - nonce_entry = directory / f"{issue}-nonce.md" - body = "Hello, world!" - normal_entry.write_text(body, encoding="utf-8") - nonce_entry.write_text(body, encoding="utf-8") - results = list(ann.news_entries(directory)) - assert len(results) == 2 - for result in results: - assert result.issue_number == issue - assert result.description == body - - -def test_news_entry_sorting(directory): - oldest_entry = directory / "45.md" - newest_entry = directory / "123.md" - oldest_entry.write_text("45", encoding="utf-8") - newest_entry.write_text("123", encoding="utf-8") - results = list(ann.news_entries(directory)) - assert len(results) == 2 - assert results[0].issue_number == 45 - assert results[1].issue_number == 123 - - -def test_only_utf8(directory): - entry = directory / "42.md" - entry.write_text("Hello, world", encoding="utf-16") - with pytest.raises(ValueError): - list(ann.news_entries(directory)) - - -def test_no_bom_allowed(directory): - entry = directory / "42.md" - entry.write_bytes(codecs.BOM_UTF8 + "Hello, world".encode("utf-8")) - with pytest.raises(ValueError): - list(ann.news_entries(directory)) - - -def test_bad_news_entry_file_name(directory): - entry = directory / "bunk.md" - entry.write_text("Hello, world!") - with pytest.raises(ValueError): - list(ann.news_entries(directory)) - - -def test_news_entry_README_skipping(directory): - entry = directory / "README.md" - entry.write_text("Hello, world!") - assert len(list(ann.news_entries(directory))) == 0 - - -def test_sections_sorting(directory): - dir2 = directory / "2 Hello" - dir1 = directory / "1 World" - dir2.mkdir() - dir1.mkdir() - results = list(ann.sections(directory)) - assert [found.title for found in results] == ["World", "Hello"] - - -def test_sections_naming(directory): - (directory / "Hello").mkdir() - assert not ann.sections(directory) - - -def test_gather(directory): - fixes = directory / "2 Fixes" - fixes.mkdir() - fix1 = fixes / "1.md" - fix1.write_text("Fix 1", encoding="utf-8") - fix2 = fixes / "3.md" - fix2.write_text("Fix 2", encoding="utf-8") - enhancements = directory / "1 Enhancements" - enhancements.mkdir() - enhancement1 = enhancements / "2.md" - enhancement1.write_text("Enhancement 1", encoding="utf-8") - enhancement2 = enhancements / "4.md" - enhancement2.write_text("Enhancement 2", encoding="utf-8") - results = ann.gather(directory) - assert len(results) == 2 - section, entries = results[0] - assert section.title == "Enhancements" - assert len(entries) == 2 - assert entries[0].description == "Enhancement 1" - assert entries[1].description == "Enhancement 2" - section, entries = results[1] - assert len(entries) == 2 - assert section.title == "Fixes" - assert entries[0].description == "Fix 1" - assert entries[1].description == "Fix 2" - - -def test_entry_markdown(): - markdown = ann.entry_markdown(ann.NewsEntry(42, "Hello, world!", None)) - assert "42" in markdown - assert "Hello, world!" in markdown - assert "https://github.com/Microsoft/vscode-python/issues/42" in markdown - - -def test_changelog_markdown(): - data = [ - ( - ann.SectionTitle(1, "Enhancements", None), - [ - ann.NewsEntry(2, "Enhancement 1", None), - ann.NewsEntry(4, "Enhancement 2", None), - ], - ), - ( - ann.SectionTitle(1, "Fixes", None), - [ann.NewsEntry(1, "Fix 1", None), ann.NewsEntry(3, "Fix 2", None)], - ), - ] - markdown = ann.changelog_markdown(data) - assert "### Enhancements" in markdown - assert "### Fixes" in markdown - assert "1" in markdown - assert "Fix 1" in markdown - assert "2" in markdown - assert "Enhancement 1" in markdown - assert "https://github.com/Microsoft/vscode-python/issues/2" in markdown - assert "3" in markdown - assert "Fix 2" in markdown - assert "https://github.com/Microsoft/vscode-python/issues/3" in markdown - assert "4" in markdown - assert "Enhancement 2" in markdown - - -def test_cleanup(directory, monkeypatch): - rm_path = None - - def fake_git_rm(path): - nonlocal rm_path - rm_path = path - - monkeypatch.setattr(ann, "git_rm", fake_git_rm) - fixes = directory / "2 Fixes" - fixes.mkdir() - fix1 = fixes / "1.md" - fix1.write_text("Fix 1", encoding="utf-8") - results = ann.gather(directory) - assert len(results) == 1 - ann.cleanup(results) - section, entries = results.pop() - assert len(entries) == 1 - assert rm_path == entries[0].path - - -TITLE = "# Our most excellent changelog" -OLD_NEWS = f"""\ -## 2018.12.0 (31 Dec 2018) - -We did things! - -## 2017.11.16 (16 Nov 2017) - -We started going stuff. -""" -NEW_NEWS = """\ -We fixed all the things! - -### Code Health - -We deleted all the code to fix all the things. ;) -""" - - -def test_complete_news(): - version = "2019.3.0" - # Remove leading `0`. - date = datetime.date.today().strftime("%d %B %Y").lstrip("0") - news = ann.complete_news(version, NEW_NEWS, f"{TITLE}\n\n\n{OLD_NEWS}") - expected = f"{TITLE}\n\n## {version} ({date})\n\n{NEW_NEWS.strip()}\n\n\n{OLD_NEWS.strip()}" - assert news == expected - - -def test_cli(): - for option in ("--" + opt for opt in ["dry_run", "interim", "final"]): - args = docopt.docopt(ann.__doc__, [option]) - assert args[option] - args = docopt.docopt(ann.__doc__, ["./news"]) - assert args[""] == "./news" - args = docopt.docopt(ann.__doc__, ["--dry_run", "./news"]) - assert args["--dry_run"] - assert args[""] == "./news" - args = docopt.docopt(ann.__doc__, ["--update", "CHANGELOG.md", "./news"]) - assert args["--update"] == "CHANGELOG.md" - assert args[""] == "./news" diff --git a/noxfile.py b/noxfile.py new file mode 100644 index 000000000000..3991ee8c025a --- /dev/null +++ b/noxfile.py @@ -0,0 +1,161 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import os +import pathlib +import nox +import shutil +import sys +import sysconfig +import uuid + +EXT_ROOT = pathlib.Path(__file__).parent + + +def delete_dir(path: pathlib.Path, ignore_errors=None): + attempt = 0 + known = [] + while attempt < 5: + try: + shutil.rmtree(os.fspath(path), ignore_errors=ignore_errors) + return + except PermissionError as pe: + if os.fspath(pe.filename) in known: + break + print(f"Changing permissions on {pe.filename}") + os.chmod(pe.filename, 0o666) + + shutil.rmtree(os.fspath(path)) + + +@nox.session() +def install_python_libs(session: nox.Session): + requirements = [ + ("./python_files/lib/python", "./requirements.txt"), + ( + "./python_files/lib/jedilsp", + "./python_files/jedilsp_requirements/requirements.txt", + ), + ] + for target, file in requirements: + session.install( + "-t", + target, + "--no-cache-dir", + "--implementation", + "py", + "--no-deps", + "--require-hashes", + "--only-binary", + ":all:", + "-r", + file, + ) + + session.install("packaging") + session.install("debugpy") + + # Download get-pip script + session.run( + "python", + "./python_files/download_get_pip.py", + env={"PYTHONPATH": "./python_files/lib/temp"}, + ) + + if pathlib.Path("./python_files/lib/temp").exists(): + shutil.rmtree("./python_files/lib/temp") + + +@nox.session() +def native_build(session: nox.Session): + source_dir = pathlib.Path(pathlib.Path.cwd() / "python-env-tools").resolve() + dest_dir = pathlib.Path(pathlib.Path.cwd() / "python-env-tools").resolve() + + with session.cd(source_dir): + if not pathlib.Path(dest_dir / "bin").exists(): + pathlib.Path(dest_dir / "bin").mkdir() + + if not pathlib.Path(dest_dir / "bin" / ".gitignore").exists(): + pathlib.Path(dest_dir / "bin" / ".gitignore").write_text( + "*\n", encoding="utf-8" + ) + + ext = sysconfig.get_config_var("EXE") or "" + target = os.environ.get("CARGO_TARGET", None) + + session.run("cargo", "fetch", external=True) + if target: + session.run( + "cargo", + "build", + "--frozen", + "--release", + "--target", + target, + external=True, + ) + source = source_dir / "target" / target / "release" / f"pet{ext}" + else: + session.run( + "cargo", + "build", + "--frozen", + "--release", + external=True, + ) + source = source_dir / "target" / "release" / f"pet{ext}" + dest = dest_dir / "bin" / f"pet{ext}" + shutil.copy(source, dest) + + # Remove python-env-tools/bin exclusion from .vscodeignore + vscode_ignore = EXT_ROOT / ".vscodeignore" + remove_patterns = ("python-env-tools/bin/**",) + lines = vscode_ignore.read_text(encoding="utf-8").splitlines() + filtered_lines = [line for line in lines if not line.startswith(remove_patterns)] + vscode_ignore.write_text("\n".join(filtered_lines) + "\n", encoding="utf-8") + + +@nox.session() +def checkout_native(session: nox.Session): + dest = (pathlib.Path.cwd() / "python-env-tools").resolve() + if dest.exists(): + shutil.rmtree(os.fspath(dest)) + + temp_dir = os.getenv("TEMP") or os.getenv("TMP") or "/tmp" + temp_dir = pathlib.Path(temp_dir) / str(uuid.uuid4()) / "python-env-tools" + temp_dir.mkdir(0o766, parents=True) + + session.log(f"Cloning python-environment-tools to {temp_dir}") + try: + with session.cd(temp_dir): + session.run("git", "init", external=True) + session.run( + "git", + "remote", + "add", + "origin", + "https://github.com/microsoft/python-environment-tools", + external=True, + ) + session.run("git", "fetch", "origin", "main", external=True) + session.run( + "git", "checkout", "--force", "-B", "main", "origin/main", external=True + ) + delete_dir(temp_dir / ".git") + delete_dir(temp_dir / ".github") + delete_dir(temp_dir / ".vscode") + (temp_dir / "CODE_OF_CONDUCT.md").unlink() + shutil.move(os.fspath(temp_dir), os.fspath(dest)) + except PermissionError as e: + print(f"Permission error: {e}") + if not dest.exists(): + raise + finally: + delete_dir(temp_dir.parent, ignore_errors=True) + + +@nox.session() +def setup_repo(session: nox.Session): + install_python_libs(session) + checkout_native(session) + native_build(session) diff --git a/package-lock.json b/package-lock.json index 4e692024fb85..6de6edae81c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,27003 +1,26357 @@ { "name": "python", - "version": "2020.10.0-dev", - "lockfileVersion": 1, + "version": "2026.5.0-dev", + "lockfileVersion": 2, "requires": true, - "dependencies": { - "@babel/cli": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.8.4.tgz", - "integrity": "sha512-XXLgAm6LBbaNxaGhMAznXXaxtCWfuv6PIDJ9Alsy9JYTOh+j2jJz+L/162kkfU1j/pTSxK1xGmlwI4pdIMkoag==", - "dev": true, - "requires": { - "chokidar": "^2.1.8", - "commander": "^4.0.1", - "convert-source-map": "^1.1.0", - "fs-readdir-recursive": "^1.1.0", - "glob": "^7.0.0", - "lodash": "^4.17.13", - "make-dir": "^2.1.0", - "slash": "^2.0.0", - "source-map": "^0.5.0" - }, - "dependencies": { - "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "dev": true, - "optional": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } - }, - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } + "packages": { + "": { + "name": "python", + "version": "2026.5.0-dev", + "license": "MIT", + "dependencies": { + "@iarna/toml": "^3.0.0", + "@vscode/extension-telemetry": "^0.8.4", + "arch": "^2.1.0", + "fs-extra": "^11.2.0", + "glob": "^7.2.0", + "iconv-lite": "^0.6.3", + "inversify": "^6.0.2", + "jsonc-parser": "^3.0.0", + "lodash": "^4.18.1", + "minimatch": "^5.1.8", + "named-js-regexp": "^1.3.3", + "node-stream-zip": "^1.6.0", + "reflect-metadata": "^0.2.2", + "rxjs": "^6.5.4", + "rxjs-compat": "^6.5.4", + "semver": "^7.5.2", + "stack-trace": "0.0.10", + "sudo-prompt": "^9.2.1", + "tmp": "^0.2.5", + "uint64be": "^3.0.0", + "unicode": "^14.0.0", + "vscode-debugprotocol": "^1.28.0", + "vscode-jsonrpc": "^9.0.0-next.5", + "vscode-languageclient": "^10.0.0-next.12", + "vscode-languageserver-protocol": "^3.17.6-next.10", + "vscode-tas-client": "^0.1.84", + "which": "^2.0.2", + "winreg": "^1.2.4", + "xml2js": "^0.5.0" + }, + "devDependencies": { + "@istanbuljs/nyc-config-typescript": "^1.0.2", + "@types/bent": "^7.3.0", + "@types/chai": "^4.1.2", + "@types/chai-arrays": "^2.0.0", + "@types/chai-as-promised": "^7.1.0", + "@types/download": "^8.0.1", + "@types/fs-extra": "^11.0.4", + "@types/glob": "^7.2.0", + "@types/lodash": "^4.14.104", + "@types/mocha": "^9.1.0", + "@types/node": "^22.19.1", + "@types/semver": "^5.5.0", + "@types/shortid": "^0.0.29", + "@types/sinon": "^17.0.3", + "@types/stack-trace": "0.0.29", + "@types/tmp": "^0.0.33", + "@types/vscode": "^1.95.0", + "@types/which": "^2.0.1", + "@types/winreg": "^1.2.30", + "@types/xml2js": "^0.4.2", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", + "@vscode/test-electron": "^2.3.8", + "@vscode/vsce": "^2.27.0", + "bent": "^7.3.12", + "chai": "^4.1.2", + "chai-arrays": "^2.0.0", + "chai-as-promised": "^7.1.1", + "copy-webpack-plugin": "^9.1.0", + "cross-env": "^7.0.3", + "cross-spawn": "^6.0.5", + "del": "^6.0.0", + "download": "^8.0.0", + "eslint": "^8.57.1", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsx-a11y": "^6.3.1", + "eslint-plugin-no-only-tests": "^3.3.0", + "eslint-plugin-react": "^7.20.3", + "eslint-plugin-react-hooks": "^4.0.0", + "expose-loader": "^3.1.0", + "flat": "^5.0.2", + "get-port": "^5.1.1", + "gulp": "^5.0.0", + "gulp-typescript": "^5.0.0", + "mocha": "^11.1.0", + "mocha-junit-reporter": "^2.0.2", + "mocha-multi-reporters": "^1.1.7", + "node-has-native-dependencies": "^1.0.2", + "node-loader": "^1.0.2", + "node-polyfill-webpack-plugin": "^1.1.4", + "nyc": "^15.0.0", + "prettier": "^2.0.2", + "rewiremock": "^3.13.0", + "shortid": "^2.2.8", + "sinon": "^18.0.0", + "source-map-support": "^0.5.12", + "ts-loader": "^9.2.8", + "ts-mockito": "^2.5.0", + "ts-node": "^10.7.0", + "tsconfig-paths-webpack-plugin": "^3.2.0", + "typemoq": "^2.1.0", + "typescript": "~5.2", + "uuid": "^8.3.2", + "webpack": "^5.105.0", + "webpack-bundle-analyzer": "^4.5.0", + "webpack-cli": "^4.9.2", + "webpack-fix-default-import-plugin": "^1.0.3", + "webpack-merge": "^5.8.0", + "webpack-node-externals": "^3.0.0", + "webpack-require-from": "^1.8.6", + "worker-loader": "^3.0.8", + "yargs": "^15.3.1" + }, + "engines": { + "vscode": "^1.95.0" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, - "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" } }, - "@babel/core": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.5.4.tgz", - "integrity": "sha512-+DaeBEpYq6b2+ZmHx3tHspC+ZRflrvLqwfv8E3hNr5LVQoyBnL8RPKSBCg+rK2W2My9PWlujBiqd0ZPsR9Q6zQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.5.0", - "@babel/helpers": "^7.5.4", - "@babel/parser": "^7.5.0", - "@babel/template": "^7.4.4", - "@babel/traverse": "^7.5.0", - "@babel/types": "^7.5.0", - "convert-source-map": "^1.1.0", - "debug": "^4.1.0", - "json5": "^2.1.0", - "lodash": "^4.17.11", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - }, + "node_modules/@azure/abort-controller": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz", + "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==", "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" } }, - "@babel/generator": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.5.0.tgz", - "integrity": "sha512-1TTVrt7J9rcG5PMjvO7VEG3FrEoEJNHxumRq66GemPmzboLWtIjjcJgk8rokuAS7IiRSpgVSu5Vb9lc99iJkOA==", - "dev": true, - "requires": { - "@babel/types": "^7.5.0", - "jsesc": "^2.5.1", - "lodash": "^4.17.11", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - }, + "node_modules/@azure/abort-controller/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@azure/core-auth": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.5.0.tgz", + "integrity": "sha512-udzoBuYG1VBoHVohDTrvKjyzel34zt77Bhp7dQntVGGD0ehVq48owENbBG8fIgkHRNUBQH5k1r0hpoMu5L8+kw==", "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } + "@azure/abort-controller": "^1.0.0", + "@azure/core-util": "^1.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" } }, - "@babel/helper-annotate-as-pure": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", - "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } + "node_modules/@azure/core-auth/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz", - "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==", + "node_modules/@azure/core-client": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.9.2.tgz", + "integrity": "sha512-kRdry/rav3fUKHl/aDLd/pDLcB+4pOFwPPTVEExuMyaI5r+JBbMWqRbCY1pn5BniDaU3lRxO9eaQ1AmSMehl/w==", "dev": true, - "requires": { - "@babel/helper-explode-assignable-expression": "^7.1.0", - "@babel/types": "^7.0.0" + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-rest-pipeline": "^1.9.1", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.6.1", + "@azure/logger": "^1.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "@babel/helper-builder-react-jsx": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.3.0.tgz", - "integrity": "sha512-MjA9KgwCuPEkQd9ncSXvSyJ5y+j2sICHyrI0M3L+6fnS4wMSNDc1ARXsbTfbb2cXHn17VisSnU/sHFTCxVxSMw==", + "node_modules/@azure/core-client/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", "dev": true, - "requires": { - "@babel/types": "^7.3.0", - "esutils": "^2.0.0" + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "@babel/helper-call-delegate": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.4.4.tgz", - "integrity": "sha512-l79boDFJ8S1c5hvQvG+rc+wHw6IuH7YldmRKsYtpbawsxURu/paVy57FZMomGK22/JckepaikOkY0MoAmdyOlQ==", + "node_modules/@azure/core-client/node_modules/@azure/core-util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.9.0.tgz", + "integrity": "sha512-AfalUQ1ZppaKuxPPMsFEUdX6GZPB3d9paR9d/TTL7Ow2De8cJaC7ibi7kWVlFAVPCYo31OcnGymc0R89DX8Oaw==", "dev": true, - "requires": { - "@babel/helper-hoist-variables": "^7.4.4", - "@babel/traverse": "^7.4.4", - "@babel/types": "^7.4.4" + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "@babel/helper-define-map": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.4.4.tgz", - "integrity": "sha512-IX3Ln8gLhZpSuqHJSnTNBWGDE9kdkTEWl21A/K7PQ00tseBwbqCHTvNLHSBd9M0R5rER4h5Rsvj9vw0R5SieBg==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.1.0", - "@babel/types": "^7.4.4", - "lodash": "^4.17.11" + "node_modules/@azure/core-client/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "node_modules/@azure/core-rest-pipeline": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.10.1.tgz", + "integrity": "sha512-Kji9k6TOFRDB5ZMTw8qUf2IJ+CeJtsuMdAHox9eqpTf1cefiNMpzrfnF6sINEBZJsaVaWgQ0o48B6kcUH68niA==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-tracing": "^1.0.1", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "form-data": "^4.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "tslib": "^2.2.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=14.0.0" } }, - "@babel/helper-explode-assignable-expression": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz", - "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==", - "dev": true, - "requires": { - "@babel/traverse": "^7.1.0", - "@babel/types": "^7.0.0" + "node_modules/@azure/core-rest-pipeline/node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "engines": { + "node": ">= 10" } }, - "@babel/helper-function-name": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", - "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.0.0", - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0" + "node_modules/@azure/core-rest-pipeline/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "@babel/helper-get-function-arity": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", - "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" + "node_modules/@azure/core-rest-pipeline/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" } }, - "@babel/helper-hoist-variables": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.4.tgz", - "integrity": "sha512-VYk2/H/BnYbZDDg39hr3t2kKyifAm1W6zHRfhx8jGjIHpQEBv9dry7oQ2f3+J703TLu69nYdxsovl0XYfcnK4w==", - "dev": true, - "requires": { - "@babel/types": "^7.4.4" + "node_modules/@azure/core-rest-pipeline/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@azure/core-tracing": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.1.tgz", + "integrity": "sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" } }, - "@babel/helper-member-expression-to-functions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz", - "integrity": "sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" + "node_modules/@azure/core-tracing/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@azure/core-util": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.2.0.tgz", + "integrity": "sha512-ffGIw+Qs8bNKNLxz5UPkz4/VBM/EZY07mPve1ZYFqYUdPwFqRj0RPk0U7LZMOfT7GCck9YjuT1Rfp1PApNl1ng==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" } }, - "@babel/helper-module-imports": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", - "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" + "node_modules/@azure/core-util/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@azure/identity": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.2.1.tgz", + "integrity": "sha512-U8hsyC9YPcEIzoaObJlRDvp7KiF0MGS7xcWbyJSVvXRkC/HXo1f0oYeBYmEvVgRfacw7GHf6D6yAoh9JHz6A5Q==", + "dev": true, + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.5.0", + "@azure/core-client": "^1.4.0", + "@azure/core-rest-pipeline": "^1.1.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.3.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^3.11.1", + "@azure/msal-node": "^2.9.2", + "events": "^3.0.0", + "jws": "^4.0.0", + "open": "^8.0.0", + "stoppable": "^1.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=18.0.0" } }, - "@babel/helper-module-transforms": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.4.4.tgz", - "integrity": "sha512-3Z1yp8TVQf+B4ynN7WoHPKS8EkdTbgAEy0nU0rs/1Kw4pDgmvYH3rz3aI11KgxKCba2cn7N+tqzV1mY2HMN96w==", + "node_modules/@azure/identity/node_modules/@azure/core-util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.9.0.tgz", + "integrity": "sha512-AfalUQ1ZppaKuxPPMsFEUdX6GZPB3d9paR9d/TTL7Ow2De8cJaC7ibi7kWVlFAVPCYo31OcnGymc0R89DX8Oaw==", "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/helper-simple-access": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.4.4", - "@babel/template": "^7.4.4", - "@babel/types": "^7.4.4", - "lodash": "^4.17.11" + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "@babel/helper-optimise-call-expression": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz", - "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==", + "node_modules/@azure/identity/node_modules/@azure/core-util/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", "dev": true, - "requires": { - "@babel/types": "^7.0.0" + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "@babel/helper-plugin-utils": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", - "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", + "node_modules/@azure/identity/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true }, - "@babel/helper-regex": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.4.4.tgz", - "integrity": "sha512-Y5nuB/kESmR3tKjU8Nkn1wMGEx1tjJX076HBMeL3XLQCu6vA/YRzuTW0bbb+qRnXvQGn+d6Rx953yffl8vEy7Q==", - "dev": true, - "requires": { - "lodash": "^4.17.11" - } - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz", - "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-wrap-function": "^7.1.0", - "@babel/template": "^7.1.0", - "@babel/traverse": "^7.1.0", - "@babel/types": "^7.0.0" + "node_modules/@azure/logger": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.4.tgz", + "integrity": "sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg==", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" } }, - "@babel/helper-replace-supers": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.4.4.tgz", - "integrity": "sha512-04xGEnd+s01nY1l15EuMS1rfKktNF+1CkKmHoErDppjAAZL+IUBZpzT748x262HF7fibaQPhbvWUl5HeSt1EXg==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.0.0", - "@babel/helper-optimise-call-expression": "^7.0.0", - "@babel/traverse": "^7.4.4", - "@babel/types": "^7.4.4" - } + "node_modules/@azure/logger/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, - "@babel/helper-simple-access": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz", - "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==", + "node_modules/@azure/msal-browser": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.14.0.tgz", + "integrity": "sha512-Un85LhOoecJ3HDTS3Uv3UWnXC9/43ZSO+Kc+anSqpZvcEt58SiO/3DuVCAe1A3I5UIBYJNMgTmZPGXQ0MVYrwA==", "dev": true, - "requires": { - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0" + "dependencies": { + "@azure/msal-common": "14.10.0" + }, + "engines": { + "node": ">=0.8.0" } }, - "@babel/helper-split-export-declaration": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", - "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "node_modules/@azure/msal-common": { + "version": "14.10.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.10.0.tgz", + "integrity": "sha512-Zk6DPDz7e1wPgLoLgAp0349Yay9RvcjPM5We/ehuenDNsz/t9QEFI7tRoHpp/e47I4p20XE3FiDlhKwAo3utDA==", "dev": true, - "requires": { - "@babel/types": "^7.4.4" + "engines": { + "node": ">=0.8.0" } }, - "@babel/helper-validator-identifier": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", - "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==", - "dev": true - }, - "@babel/helper-wrap-function": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz", - "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==", + "node_modules/@azure/msal-node": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.9.2.tgz", + "integrity": "sha512-8tvi6Cos3m+0KmRbPjgkySXi+UQU/QiuVRFnrxIwt5xZlEEFa69O04RTaNESGgImyBBlYbo2mfE8/U8Bbdk1WQ==", "dev": true, - "requires": { - "@babel/helper-function-name": "^7.1.0", - "@babel/template": "^7.1.0", - "@babel/traverse": "^7.1.0", - "@babel/types": "^7.2.0" + "dependencies": { + "@azure/msal-common": "14.12.0", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=16" } }, - "@babel/helpers": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.5.4.tgz", - "integrity": "sha512-6LJ6xwUEJP51w0sIgKyfvFMJvIb9mWAfohJp0+m6eHJigkFdcH8duZ1sfhn0ltJRzwUIT/yqqhdSfRpCpL7oow==", + "node_modules/@azure/msal-node/node_modules/@azure/msal-common": { + "version": "14.12.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.12.0.tgz", + "integrity": "sha512-IDDXmzfdwmDkv4SSmMEyAniJf6fDu3FJ7ncOjlxkDuT85uSnLEhZi3fGZpoR7T4XZpOMx9teM9GXBgrfJgyeBw==", "dev": true, - "requires": { - "@babel/template": "^7.4.4", - "@babel/traverse": "^7.5.0", - "@babel/types": "^7.5.0" + "engines": { + "node": ">=0.8.0" } }, - "@babel/highlight": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", - "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" + "node_modules/@azure/opentelemetry-instrumentation-azure-sdk": { + "version": "1.0.0-beta.5", + "resolved": "https://registry.npmjs.org/@azure/opentelemetry-instrumentation-azure-sdk/-/opentelemetry-instrumentation-azure-sdk-1.0.0-beta.5.tgz", + "integrity": "sha512-fsUarKQDvjhmBO4nIfaZkfNSApm1hZBzcvpNbSrXdcUBxu7lRvKsV5DnwszX7cnhLyVOW9yl1uigtRQ1yDANjA==", + "dependencies": { + "@azure/core-tracing": "^1.0.0", + "@azure/logger": "^1.0.0", + "@opentelemetry/api": "^1.4.1", + "@opentelemetry/core": "^1.15.2", + "@opentelemetry/instrumentation": "^0.41.2", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" } }, - "@babel/parser": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.5.0.tgz", - "integrity": "sha512-I5nW8AhGpOXGCCNYGc+p7ExQIBxRFnS2fd/d862bNOKvmoEPjYPcfIjsfdy0ujagYOIYPczKgD9l3FsgTkAzKA==", - "dev": true + "node_modules/@azure/opentelemetry-instrumentation-azure-sdk/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz", - "integrity": "sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==", + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-remap-async-to-generator": "^7.1.0", - "@babel/plugin-syntax-async-generators": "^7.2.0" + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.5.0.tgz", - "integrity": "sha512-x/iMjggsKTFHYC6g11PL7Qy58IK8H5zqfm9e6hu4z1iH2IRyAp9u9dL80zA6R76yFovETFLKz2VJIC2iIPBuFw==", + "node_modules/@babel/compat-data": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.6.tgz", + "integrity": "sha512-29tfsWTq2Ftu7MXmimyC0C5FDZv5DYxOZkh3XD3+QW4V/BYuv/LyEsjj3c0hqedEaDt6DBfDvexMKU8YevdqFg==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.2.0" + "engines": { + "node": ">=6.9.0" } }, - "@babel/plugin-proposal-json-strings": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz", - "integrity": "sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==", + "node_modules/@babel/core": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.6.tgz", + "integrity": "sha512-HPIyDa6n+HKw5dEuway3vVAhBboYCtREBMp+IWeseZy6TFtzn6MHkCH2KKYUOC/vKKwgSMHQW4htBOrmuRPXfw==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-json-strings": "^7.2.0" + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helpers": "^7.22.6", + "@babel/parser": "^7.22.6", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.6", + "@babel/types": "^7.22.5", + "@nicolo-ribaudo/semver-v6": "^6.3.3", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.5.4.tgz", - "integrity": "sha512-KCx0z3y7y8ipZUMAEEJOyNi11lMb/FOPUjjB113tfowgw0c16EGYos7worCKBcUAh2oG+OBnoUhsnTSoLpV9uA==", + "node_modules/@babel/core/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-object-rest-spread": "^7.2.0" + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz", - "integrity": "sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==", + "node_modules/@babel/generator": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.2.0" + "dependencies": { + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz", - "integrity": "sha512-j1NwnOqMG9mFUOH58JTFsA/+ZYzQLUZ/drqWUqxCYLGeu2JFZL8YrNC9hBxKmWtAuOCHPcRpgv7fhap09Fb4kA==", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.6.tgz", + "integrity": "sha512-534sYEqWD9VfUm3IPn2SLcH4Q3P86XL+QvqdC7ZsFrzyyPF3T4XGiVghF6PTYNdWg6pXuoqXxNQAhbYeEInTzA==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-regex": "^7.4.4", - "regexpu-core": "^4.5.4" + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-validator-option": "^7.22.5", + "@nicolo-ribaudo/semver-v6": "^6.3.3", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "@babel/plugin-syntax-async-generators": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz", - "integrity": "sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==", + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "dependencies": { + "yallist": "^3.0.2" } }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz", - "integrity": "sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" - } + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true }, - "@babel/plugin-syntax-json-strings": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz", - "integrity": "sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==", + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "engines": { + "node": ">=6.9.0" } }, - "@babel/plugin-syntax-jsx": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.2.0.tgz", - "integrity": "sha512-VyN4QANJkRW6lDBmENzRszvZf3/4AXaj9YR7GwrWeeN9tEBPuXbmDYVU9bYBN0D70zCWVwUy0HWq2553VCb6Hw==", + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", - "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz", - "integrity": "sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==", + "node_modules/@babel/helper-module-imports": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", + "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz", - "integrity": "sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==", + "node_modules/@babel/helper-module-transforms": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz", + "integrity": "sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.5.0.tgz", - "integrity": "sha512-mqvkzwIGkq0bEF1zLRRiTdjfomZJDV33AH3oQzHVGkI2VzEmXLpKKOBvEVaFZBJdN0XTyH38s9j/Kiqr68dggg==", + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-remap-async-to-generator": "^7.1.0" + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz", - "integrity": "sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==", + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/plugin-transform-block-scoping": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.4.4.tgz", - "integrity": "sha512-jkTUyWZcTrwxu5DD4rWz6rDB5Cjdmgz6z7M7RLXOJyCUkFBawssDGcGh8M/0FTSB87avyJI1HsTwUXp9nKA1PA==", + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "lodash": "^4.17.11" + "license": "MIT", + "engines": { + "node": ">=6.9.0" } }, - "@babel/plugin-transform-classes": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.4.4.tgz", - "integrity": "sha512-/e44eFLImEGIpL9qPxSRat13I5QNRgBLu2hOQJCF7VLy/otSM/sypV1+XaIw5+502RX/+6YaSAPmldk+nhHDPw==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-define-map": "^7.4.4", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-optimise-call-expression": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.4.4", - "@babel/helper-split-export-declaration": "^7.4.4", - "globals": "^11.1.0" + "license": "MIT", + "engines": { + "node": ">=6.9.0" } }, - "@babel/plugin-transform-computed-properties": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz", - "integrity": "sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==", + "node_modules/@babel/helper-validator-option": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", + "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "engines": { + "node": ">=6.9.0" } }, - "@babel/plugin-transform-destructuring": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.5.0.tgz", - "integrity": "sha512-YbYgbd3TryYYLGyC7ZR+Tq8H/+bCmwoaxHfJHupom5ECstzbRLTch6gOQbhEY9Z4hiCNHEURgq06ykFv9JZ/QQ==", + "node_modules/@babel/helpers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", + "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.4.tgz", - "integrity": "sha512-P05YEhRc2h53lZDjRPk/OektxCVevFzZs2Gfjd545Wde3k+yFDbXORgl2e0xpbq8mLcKJ7Idss4fAg0zORN/zg==", + "node_modules/@babel/parser": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", + "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-regex": "^7.4.4", - "regexpu-core": "^4.5.4" + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" } }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.5.0.tgz", - "integrity": "sha512-igcziksHizyQPlX9gfSjHkE2wmoCH3evvD2qR5w29/Dk0SMKE/eOI7f1HhBdNhR/zxJDqrgpoDTq5YSLH/XMsQ==", + "node_modules/@babel/runtime": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", + "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "license": "MIT", + "engines": { + "node": ">=6.9.0" } }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz", - "integrity": "sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==", + "node_modules/@babel/runtime-corejs3": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.27.1.tgz", + "integrity": "sha512-909rVuj3phpjW6y0MCXAZ5iNeORePa6ldJvp2baWGcTjwqbBDDz6xoS5JHJ7lS88NlwLYj07ImL/8IUMtDZzTA==", "dev": true, - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0", - "@babel/helper-plugin-utils": "^7.0.0" + "license": "MIT", + "dependencies": { + "core-js-pure": "^3.30.2" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/plugin-transform-for-of": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz", - "integrity": "sha512-9T/5Dlr14Z9TIEXLXkt8T1DU7F24cbhwhMNUziN3hB1AXoZcdzPcTiKGRn/6iOymDqtTKWnr/BtRKN9JwbKtdQ==", + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/plugin-transform-function-name": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.4.tgz", - "integrity": "sha512-iU9pv7U+2jC9ANQkKeNF6DrPy4GBa4NWQtl6dHB4Pb3izX2JOEvDTFarlNsBj/63ZEzNNIAMs3Qw4fNCcSOXJA==", + "node_modules/@babel/traverse": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "dev": true, - "requires": { - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-plugin-utils": "^7.0.0" + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/plugin-transform-literals": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz", - "integrity": "sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==", + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz", - "integrity": "sha512-HiU3zKkSU6scTidmnFJ0bMX8hz5ixC93b4MHMiYebmk2lUVNGOboPsqQvx5LzooihijUoLR/v7Nc1rbBtnc7FA==", + "node_modules/@babel/types": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "@babel/plugin-transform-modules-amd": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.5.0.tgz", - "integrity": "sha512-n20UsQMKnWrltocZZm24cRURxQnWIvsABPJlw/fvoy9c6AgHZzoelAIzajDHAQrDpuKFFPPcFGd7ChsYuIUMpg==", + "node_modules/@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.1.0", - "@babel/helper-plugin-utils": "^7.0.0", - "babel-plugin-dynamic-import-node": "^2.3.0" + "engines": { + "node": ">= 12" } }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.5.0.tgz", - "integrity": "sha512-xmHq0B+ytyrWJvQTc5OWAC4ii6Dhr0s22STOoydokG51JjWhyYo5mRPXoi+ZmtHQhZZwuXNN+GG5jy5UZZJxIQ==", + "node_modules/@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.4.4", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-simple-access": "^7.1.0", - "babel-plugin-dynamic-import-node": "^2.3.0" + "dependencies": { + "@cspotcode/source-map-consumer": "0.8.0" + }, + "engines": { + "node": ">=12" } }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.5.0.tgz", - "integrity": "sha512-Q2m56tyoQWmuNGxEtUyeEkm6qJYFqs4c+XyXH5RAuYxObRNz9Zgj/1g2GMnjYp2EUyEy7YTrxliGCXzecl/vJg==", + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", "dev": true, - "requires": { - "@babel/helper-hoist-variables": "^7.4.4", - "@babel/helper-plugin-utils": "^7.0.0", - "babel-plugin-dynamic-import-node": "^2.3.0" + "engines": { + "node": ">=10.0.0" } }, - "@babel/plugin-transform-modules-umd": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz", - "integrity": "sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.1.0", - "@babel/helper-plugin-utils": "^7.0.0" + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.4.5.tgz", - "integrity": "sha512-z7+2IsWafTBbjNsOxU/Iv5CvTJlr5w4+HGu1HovKYTtgJ362f7kBcQglkfmlspKKZ3bgrbSGvLfNx++ZJgCWsg==", + "node_modules/@eslint-community/regexpp": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", "dev": true, - "requires": { - "regexp-tree": "^0.1.6" + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "@babel/plugin-transform-new-target": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz", - "integrity": "sha512-r1z3T2DNGQwwe2vPGZMBNjioT2scgWzK9BCnDEh+46z8EEwXBq24uRzd65I7pjtugzPSj921aM15RpESgzsSuA==", + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "@babel/plugin-transform-object-super": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.2.0.tgz", - "integrity": "sha512-VMyhPYZISFZAqAPVkiYb7dUe2AsVi2/wCT5+wZdsNO31FojQJa9ns40hzZ6U9f50Jlq4w6qwzdBB2uwqZ00ebg==", + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.1.0" - } + "license": "Python-2.0" }, - "@babel/plugin-transform-parameters": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.4.tgz", - "integrity": "sha512-oMh5DUO1V63nZcu/ZVLQFqiihBGo4OpxJxR1otF50GMeCLiRx5nUdtokd+u9SuVJrvvuIh9OosRFPP4pIPnwmw==", + "node_modules/@eslint/eslintrc/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dev": true, - "requires": { - "@babel/helper-call-delegate": "^7.4.4", - "@babel/helper-get-function-arity": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0" + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "@babel/plugin-transform-property-literals": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz", - "integrity": "sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ==", + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "@babel/plugin-transform-react-display-name": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.2.0.tgz", - "integrity": "sha512-Htf/tPa5haZvRMiNSQSFifK12gtr/8vwfr+A9y69uF0QcU77AVu4K7MiHEkTxF7lQoHOL0F9ErqgfNEAKgXj7A==", + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "@babel/plugin-transform-react-jsx": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.3.0.tgz", - "integrity": "sha512-a/+aRb7R06WcKvQLOu4/TpjKOdvVEKRLWFpKcNuHhiREPgGRB4TQJxq07+EZLS8LFVYpfq1a5lDUnuMdcCpBKg==", + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "requires": { - "@babel/helper-builder-react-jsx": "^7.3.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-jsx": "^7.2.0" + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "@babel/plugin-transform-react-jsx-self": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.2.0.tgz", - "integrity": "sha512-v6S5L/myicZEy+jr6ielB0OR8h+EH/1QFx/YJ7c7Ua+7lqsjj/vW6fD5FR9hB/6y7mGbfT4vAURn3xqBxsUcdg==", + "node_modules/@eslint/eslintrc/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-jsx": "^7.2.0" - } + "license": "MIT" }, - "@babel/plugin-transform-react-jsx-source": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.5.0.tgz", - "integrity": "sha512-58Q+Jsy4IDCZx7kqEZuSDdam/1oW8OdDX8f+Loo6xyxdfg1yF0GE2XNJQSTZCaMol93+FBzpWiPEwtbMloAcPg==", + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-jsx": "^7.2.0" + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "@babel/plugin-transform-regenerator": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.5.tgz", - "integrity": "sha512-gBKRh5qAaCWntnd09S8QC7r3auLCqq5DI6O0DlfoyDjslSBVqBibrMdsqO+Uhmx3+BlOmE/Kw1HFxmGbv0N9dA==", + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, - "requires": { - "regenerator-transform": "^0.14.0" + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "@babel/plugin-transform-reserved-words": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz", - "integrity": "sha512-fz43fqW8E1tAB3DKF19/vxbpib1fuyCwSPE418ge5ZxILnBhWyhtPgz8eh1RCGGJlwvksHkyxMxh0eenFi+kFw==", + "node_modules/@gulpjs/messages": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@gulpjs/messages/-/messages-1.1.0.tgz", + "integrity": "sha512-Ys9sazDatyTgZVb4xPlDufLweJ/Os2uHWOv+Caxvy2O85JcnT4M3vc73bi8pdLWlv3fdWQz3pdI9tVwo8rQQSg==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "engines": { + "node": ">=10.13.0" } }, - "@babel/plugin-transform-runtime": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.5.0.tgz", - "integrity": "sha512-LmPIZOAgTLl+86gR9KjLXex6P/lRz1fWEjTz6V6QZMmKie51ja3tvzdwORqhHc4RWR8TcZ5pClpRWs0mlaA2ng==", + "node_modules/@gulpjs/to-absolute-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@gulpjs/to-absolute-glob/-/to-absolute-glob-4.0.0.tgz", + "integrity": "sha512-kjotm7XJrJ6v+7knhPaRgaT6q8F8K2jiafwYdNHLzmV0uGLuZY43FK6smNSHUPrhq5kX2slCUy+RGG/xGqmIKA==", "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0", - "resolve": "^1.8.1", - "semver": "^5.5.1" + "dependencies": { + "is-negated-glob": "^1.0.0" + }, + "engines": { + "node": ">=10.13.0" } }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz", - "integrity": "sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==", + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" } }, - "@babel/plugin-transform-spread": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz", - "integrity": "sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w==", + "node_modules/@humanwhocodes/config-array/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz", - "integrity": "sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==", + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-regex": "^7.0.0" + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "@babel/plugin-transform-template-literals": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz", - "integrity": "sha512-mQrEC4TWkhLN0z8ygIvEL9ZEToPhG5K7KDW3pzGqOfIGZ28Jb0POUkeWcoz8HnHvhFy6dwAT1j8OzqN8s804+g==", + "node_modules/@humanwhocodes/config-array/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0" - } + "license": "MIT" }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz", - "integrity": "sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.4.4.tgz", - "integrity": "sha512-il+/XdNw01i93+M9J9u4T7/e/Ue/vWfNZE4IRUQjplu2Mqb/AFTDimkw2tdEdSH50wuQXZAbXSql0UphQke+vA==", + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-regex": "^7.4.4", - "regexpu-core": "^4.5.4" - } + "license": "BSD-3-Clause" }, - "@babel/polyfill": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.4.4.tgz", - "integrity": "sha512-WlthFLfhQQhh+A2Gn5NSFl0Huxz36x86Jn+E9OW7ibK8edKPq+KLy4apM1yDpQ8kJOVi1OVjpP4vSDLdrI04dg==", + "node_modules/@iarna/toml": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-3.0.0.tgz", + "integrity": "sha512-td6ZUkz2oS3VeleBcN+m//Q6HlCFCPrnI0FZhrt/h4XqLEdOyYp2u21nd8MdsR+WJy5r9PTDaHTDDfhf4H4l6Q==", + "license": "ISC" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, - "requires": { - "core-js": "^2.6.5", - "regenerator-runtime": "^0.13.2" - }, + "license": "ISC", "dependencies": { - "core-js": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", - "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==", - "dev": true - } + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" } }, - "@babel/preset-env": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.5.4.tgz", - "integrity": "sha512-hFnFnouyRNiH1rL8YkX1ANCNAUVC8Djwdqfev8i1415tnAG+7hlA5zhZ0Q/3Q5gkop4HioIPbCEWAalqcbxRoQ==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-async-generator-functions": "^7.2.0", - "@babel/plugin-proposal-dynamic-import": "^7.5.0", - "@babel/plugin-proposal-json-strings": "^7.2.0", - "@babel/plugin-proposal-object-rest-spread": "^7.5.4", - "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-syntax-async-generators": "^7.2.0", - "@babel/plugin-syntax-dynamic-import": "^7.2.0", - "@babel/plugin-syntax-json-strings": "^7.2.0", - "@babel/plugin-syntax-object-rest-spread": "^7.2.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", - "@babel/plugin-transform-arrow-functions": "^7.2.0", - "@babel/plugin-transform-async-to-generator": "^7.5.0", - "@babel/plugin-transform-block-scoped-functions": "^7.2.0", - "@babel/plugin-transform-block-scoping": "^7.4.4", - "@babel/plugin-transform-classes": "^7.4.4", - "@babel/plugin-transform-computed-properties": "^7.2.0", - "@babel/plugin-transform-destructuring": "^7.5.0", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/plugin-transform-duplicate-keys": "^7.5.0", - "@babel/plugin-transform-exponentiation-operator": "^7.2.0", - "@babel/plugin-transform-for-of": "^7.4.4", - "@babel/plugin-transform-function-name": "^7.4.4", - "@babel/plugin-transform-literals": "^7.2.0", - "@babel/plugin-transform-member-expression-literals": "^7.2.0", - "@babel/plugin-transform-modules-amd": "^7.5.0", - "@babel/plugin-transform-modules-commonjs": "^7.5.0", - "@babel/plugin-transform-modules-systemjs": "^7.5.0", - "@babel/plugin-transform-modules-umd": "^7.2.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.4.5", - "@babel/plugin-transform-new-target": "^7.4.4", - "@babel/plugin-transform-object-super": "^7.2.0", - "@babel/plugin-transform-parameters": "^7.4.4", - "@babel/plugin-transform-property-literals": "^7.2.0", - "@babel/plugin-transform-regenerator": "^7.4.5", - "@babel/plugin-transform-reserved-words": "^7.2.0", - "@babel/plugin-transform-shorthand-properties": "^7.2.0", - "@babel/plugin-transform-spread": "^7.2.0", - "@babel/plugin-transform-sticky-regex": "^7.2.0", - "@babel/plugin-transform-template-literals": "^7.4.4", - "@babel/plugin-transform-typeof-symbol": "^7.2.0", - "@babel/plugin-transform-unicode-regex": "^7.4.4", - "@babel/types": "^7.5.0", - "browserslist": "^4.6.0", - "core-js-compat": "^3.1.1", - "invariant": "^2.2.2", - "js-levenshtein": "^1.1.3", - "semver": "^5.5.0" - } - }, - "@babel/preset-react": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.0.0.tgz", - "integrity": "sha512-oayxyPS4Zj+hF6Et11BwuBkmpgT/zMxyuZgFrMeZID6Hdh3dGlk4sHCAhdBCpuCKW2ppBfl2uCCetlrUIJRY3w==", + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0" + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "@babel/register": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.9.0.tgz", - "integrity": "sha512-Tv8Zyi2J2VRR8g7pC5gTeIN8Ihultbmk0ocyNz8H2nEZbmhp1N6q0A1UGsQbDvGP/sNinQKUHf3SqXwqjtFv4Q==", + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, - "requires": { - "find-cache-dir": "^2.0.0", - "lodash": "^4.17.13", - "make-dir": "^2.1.0", - "pirates": "^4.0.0", - "source-map-support": "^0.5.16" + "license": "MIT", + "engines": { + "node": ">=12" }, - "dependencies": { - "source-map-support": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", - "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - } + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "@babel/runtime": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.5.4.tgz", - "integrity": "sha512-Na84uwyImZZc3FKf4aUF1tysApzwf3p2yuFBIyBfbzT5glzKTdvYI4KVW4kcgjrzoGUjC7w3YyCHcJKaRxsr2Q==", + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, - "requires": { - "regenerator-runtime": "^0.13.2" - } + "license": "MIT" }, - "@babel/runtime-corejs2": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.5.4.tgz", - "integrity": "sha512-sHv74OzyZ18d6tjHU0HmlVES3+l+lydkOMTiKsJSTGWcTBpIMfXLEgduahlJrQjknW9RCQAqLIEdLOHjBmq/hg==", + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, - "requires": { - "core-js": "^2.6.5", - "regenerator-runtime": "^0.13.2" - }, + "license": "MIT", "dependencies": { - "core-js": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", - "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==", - "dev": true - } + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "@babel/runtime-corejs3": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.10.5.tgz", - "integrity": "sha512-RMafpmrNB5E/bwdSphLr8a8++9TosnyJp98RZzI6VOx2R2CCMpsXXXRvmI700O9oEKpXdZat6oEK68/F0zjd4A==", + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, - "requires": { - "core-js-pure": "^3.0.0", - "regenerator-runtime": "^0.13.4" - }, + "license": "MIT", "dependencies": { - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - } + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "@babel/template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz", - "integrity": "sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==", + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.4.4", - "@babel/types": "^7.4.4" + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "@babel/traverse": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.5.0.tgz", - "integrity": "sha512-SnA9aLbyOCcnnbQEGwdfBggnc142h/rbqqsXcaATj2hZcegCl903pUD/lfpsNBlBSuWow/YDfRyJuWi2EPR5cg==", + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz", + "integrity": "sha512-ZR0rq/f/E4f4XcgnDvtMWXCUJpi8eO0rssVhmztsZqLIEFA9UUP9zmpE0VxlM+kv/E1ul2I876Fwil2ayptDVg==", "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.5.0", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.4.4", - "@babel/parser": "^7.5.0", - "@babel/types": "^7.5.0", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.11" + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/nyc-config-typescript": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/nyc-config-typescript/-/nyc-config-typescript-1.0.2.tgz", + "integrity": "sha512-iKGIyMoyJuFnJRSVTZ78POIRvNnwZaWIf8vG4ZS3rQq58MMDrqEX2nnzx0R28V2X8JvmKYiqY9FP2hlJsm8A0w==", + "dev": true, "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } + "@istanbuljs/schema": "^0.1.2" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "nyc": ">=15" } }, - "@babel/types": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.0.tgz", - "integrity": "sha512-UFpDVqRABKsW01bvw7/wSUe56uy6RXM5+VJibVVAybDGxEW25jdwiFJEf7ASvSaC7sN7rbE/l3cLp2izav+CtQ==", + "node_modules/@istanbuljs/schema": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", + "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.11", - "to-fast-properties": "^2.0.0" + "engines": { + "node": ">=8" } }, - "@blueprintjs/core": { - "version": "3.22.3", - "resolved": "https://registry.npmjs.org/@blueprintjs/core/-/core-3.22.3.tgz", - "integrity": "sha512-IaxkvJyF+4VCvAjMvyHtJ4qUiQNwgPu4zIxLRo1cBsu30gHjYzwe+xDdssBR7yRnXARguM6bkI523w+yJOerCA==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, - "requires": { - "@blueprintjs/icons": "^3.12.0", - "@types/dom4": "^2.0.1", - "classnames": "^2.2", - "dom4": "^2.1.5", - "normalize.css": "^8.0.1", - "popper.js": "^1.15.0", - "react-lifecycles-compat": "^3.0.4", - "react-popper": "^1.3.7", - "react-transition-group": "^2.9.0", - "resize-observer-polyfill": "^1.5.1", - "tslib": "~1.9.0" - }, "dependencies": { - "tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", - "dev": true - } + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" } }, - "@blueprintjs/icons": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@blueprintjs/icons/-/icons-3.13.0.tgz", - "integrity": "sha512-fvXGsAJ66SSjeHv3OeXjLEdKdPJ3wVztjhJQCAd51uebhj3FJ16EDDvO7BqBw5FyVkLkU11KAxSoCFZt7TC9GA==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", "dev": true, - "requires": { - "classnames": "^2.2", - "tslib": "~1.9.0" - }, - "dependencies": { - "tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", - "dev": true - } + "engines": { + "node": ">=6.0.0" } }, - "@blueprintjs/select": { - "version": "3.11.2", - "resolved": "https://registry.npmjs.org/@blueprintjs/select/-/select-3.11.2.tgz", - "integrity": "sha512-fU0Km6QI/ayWhzYeu9N1gTj0+L0XUO4KB3u2LfJXgj648UGY8F4HX2ETdJ+XPdtsu6TesrIL7ghMQhtLcvafBg==", + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, - "requires": { - "@blueprintjs/core": "^3.20.0", - "classnames": "^2.2", - "tslib": "~1.9.0" - }, - "dependencies": { - "tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", - "dev": true - } + "engines": { + "node": ">=6.0.0" } }, - "@emotion/babel-utils": { - "version": "0.6.10", - "resolved": "https://registry.npmjs.org/@emotion/babel-utils/-/babel-utils-0.6.10.tgz", - "integrity": "sha512-/fnkM/LTEp3jKe++T0KyTszVGWNKPNOUJfjNKLO17BzQ6QPxgbg3whayom1Qr2oLFH3V92tDymU+dT5q676uow==", + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, - "requires": { - "@emotion/hash": "^0.6.6", - "@emotion/memoize": "^0.6.6", - "@emotion/serialize": "^0.9.1", - "convert-source-map": "^1.5.1", - "find-root": "^1.1.0", - "source-map": "^0.7.2" - }, "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, - "@emotion/hash": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.6.6.tgz", - "integrity": "sha512-ojhgxzUHZ7am3D2jHkMzPpsBAiB005GF5YU4ea+8DNPybMk01JJUM9V9YRlF/GE95tcOm8DxQvWA2jq19bGalQ==", - "dev": true - }, - "@emotion/memoize": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.6.6.tgz", - "integrity": "sha512-h4t4jFjtm1YV7UirAFuSuFGyLa+NNxjdkq6DpFLANNQY5rHueFZHVY+8Cu1HYVP6DrheB0kv4m5xPjo7eKT7yQ==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, - "@emotion/serialize": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.9.1.tgz", - "integrity": "sha512-zTuAFtyPvCctHBEL8KZ5lJuwBanGSutFEncqLn/m9T1a6a93smBStK+bZzcNPgj4QS8Rkw9VTwJGhRIUVO8zsQ==", + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, - "requires": { - "@emotion/hash": "^0.6.6", - "@emotion/memoize": "^0.6.6", - "@emotion/unitless": "^0.6.7", - "@emotion/utils": "^0.8.2" + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "@emotion/stylis": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.7.1.tgz", - "integrity": "sha512-/SLmSIkN13M//53TtNxgxo57mcJk/UJIDFRKwOiLIBEyBHEcipgR6hNMQ/59Sl4VjCJ0Z/3zeAZyvnSLPG/1HQ==", - "dev": true - }, - "@emotion/unitless": { - "version": "0.6.7", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.6.7.tgz", - "integrity": "sha512-Arj1hncvEVqQ2p7Ega08uHLr1JuRYBuO5cIvcA+WWEQ5+VmkOE3ZXzl04NbQxeQpWX78G7u6MqxKuNX3wvYZxg==", - "dev": true - }, - "@emotion/utils": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.8.2.tgz", - "integrity": "sha512-rLu3wcBWH4P5q1CGoSSH/i9hrXs7SlbRLkoq9IGuoPYNGQvDJ3pt/wmOM+XgYjIDRMVIdkUWt0RsfzF50JfnCw==", - "dev": true + "node_modules/@microsoft/1ds-core-js": { + "version": "3.2.13", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-3.2.13.tgz", + "integrity": "sha512-CluYTRWcEk0ObG5EWFNWhs87e2qchJUn0p2D21ZUa3PWojPZfPSBs4//WIE0MYV8Qg1Hdif2ZTwlM7TbYUjfAg==", + "dependencies": { + "@microsoft/applicationinsights-core-js": "2.8.15", + "@microsoft/applicationinsights-shims": "^2.0.2", + "@microsoft/dynamicproto-js": "^1.1.7" + } }, - "@enonic/fnv-plus": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@enonic/fnv-plus/-/fnv-plus-1.3.0.tgz", - "integrity": "sha512-BCN9uNWH8AmiP7BXBJqEinUY9KXalmRzo+L0cB/mQsmFfzODxwQrbvxCHXUNH2iP+qKkWYtB4vyy8N62PViMFw==", - "dev": true + "node_modules/@microsoft/1ds-post-js": { + "version": "3.2.13", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-3.2.13.tgz", + "integrity": "sha512-HgS574fdD19Bo2vPguyznL4eDw7Pcm1cVNpvbvBLWiW3x4e1FCQ3VMXChWnAxCae8Hb0XqlA2sz332ZobBavTA==", + "dependencies": { + "@microsoft/1ds-core-js": "3.2.13", + "@microsoft/applicationinsights-shims": "^2.0.2", + "@microsoft/dynamicproto-js": "^1.1.7" + } }, - "@eslint/eslintrc": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz", - "integrity": "sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "lodash": "^4.17.19", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" + "node_modules/@microsoft/applicationinsights-channel-js": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.0.2.tgz", + "integrity": "sha512-jDBNKbCHsJgmpv0CKNhJ/uN9ZphvfGdb93Svk+R4LjO8L3apNNMbDDPxBvXXi0uigRmA1TBcmyBG4IRKjabGhw==", + "dependencies": { + "@microsoft/applicationinsights-common": "3.0.2", + "@microsoft/applicationinsights-core-js": "3.0.2", + "@microsoft/applicationinsights-shims": "3.0.1", + "@microsoft/dynamicproto-js": "^2.0.2", + "@nevware21/ts-async": ">= 0.2.4 < 2.x", + "@nevware21/ts-utils": ">= 0.9.5 < 2.x" }, + "peerDependencies": { + "tslib": "*" + } + }, + "node_modules/@microsoft/applicationinsights-channel-js/node_modules/@microsoft/applicationinsights-core-js": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.2.tgz", + "integrity": "sha512-WQhVhzlRlLDrQzn3OShCW/pL3BW5WC57t0oywSknX3q7lMzI3jDg7Ihh0iuIcNTzGCTbDkuqr4d6IjEDWIMtJQ==", "dependencies": { - "ajv": { - "version": "6.12.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", - "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", - "dev": true, - "requires": { - "type-fest": "^0.8.1" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - } + "@microsoft/applicationinsights-shims": "3.0.1", + "@microsoft/dynamicproto-js": "^2.0.2", + "@nevware21/ts-async": ">= 0.2.4 < 2.x", + "@nevware21/ts-utils": ">= 0.9.5 < 2.x" + }, + "peerDependencies": { + "tslib": "*" } }, - "@gulp-sourcemaps/identity-map": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-1.0.2.tgz", - "integrity": "sha512-ciiioYMLdo16ShmfHBXJBOFm3xPC4AuwO4xeRpFeHz7WK9PYsWCmigagG2XyzZpubK4a3qNKoUBDhbzHfa50LQ==", - "dev": true, - "requires": { - "acorn": "^5.0.3", - "css": "^2.2.1", - "normalize-path": "^2.1.1", - "source-map": "^0.6.0", - "through2": "^2.0.3" - }, + "node_modules/@microsoft/applicationinsights-channel-js/node_modules/@microsoft/applicationinsights-shims": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", + "integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==", "dependencies": { - "acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", - "dev": true - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } + "@nevware21/ts-utils": ">= 0.9.4 < 2.x" } }, - "@gulp-sourcemaps/map-sources": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", - "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", - "dev": true, - "requires": { - "normalize-path": "^2.0.1", - "through2": "^2.0.3" - }, + "node_modules/@microsoft/applicationinsights-channel-js/node_modules/@microsoft/dynamicproto-js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz", + "integrity": "sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg==", "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } + "@nevware21/ts-utils": ">= 0.9.4 < 2.x" } }, - "@icons/material": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz", - "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==", - "dev": true - }, - "@istanbuljs/load-nyc-config": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz", - "integrity": "sha512-ZR0rq/f/E4f4XcgnDvtMWXCUJpi8eO0rssVhmztsZqLIEFA9UUP9zmpE0VxlM+kv/E1ul2I876Fwil2ayptDVg==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" + "node_modules/@microsoft/applicationinsights-common": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.0.2.tgz", + "integrity": "sha512-y+WXWop+OVim954Cu1uyYMnNx6PWO8okHpZIQi/1YSqtqaYdtJVPv4P0AVzwJdohxzVfgzKvqj9nec/VWqE2Zg==", + "dependencies": { + "@microsoft/applicationinsights-core-js": "3.0.2", + "@microsoft/applicationinsights-shims": "3.0.1", + "@microsoft/dynamicproto-js": "^2.0.2", + "@nevware21/ts-utils": ">= 0.9.5 < 2.x" }, + "peerDependencies": { + "tslib": "*" + } + }, + "node_modules/@microsoft/applicationinsights-common/node_modules/@microsoft/applicationinsights-core-js": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.2.tgz", + "integrity": "sha512-WQhVhzlRlLDrQzn3OShCW/pL3BW5WC57t0oywSknX3q7lMzI3jDg7Ihh0iuIcNTzGCTbDkuqr4d6IjEDWIMtJQ==", "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } + "@microsoft/applicationinsights-shims": "3.0.1", + "@microsoft/dynamicproto-js": "^2.0.2", + "@nevware21/ts-async": ">= 0.2.4 < 2.x", + "@nevware21/ts-utils": ">= 0.9.5 < 2.x" + }, + "peerDependencies": { + "tslib": "*" } }, - "@istanbuljs/nyc-config-typescript": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/nyc-config-typescript/-/nyc-config-typescript-0.1.3.tgz", - "integrity": "sha512-EzRFg92bRSD1W/zeuNkeGwph0nkWf+pP2l/lYW4/5hav7RjKKBN5kV1Ix7Tvi0CMu3pC4Wi/U7rNisiJMR3ORg==", - "dev": true + "node_modules/@microsoft/applicationinsights-common/node_modules/@microsoft/applicationinsights-shims": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", + "integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==", + "dependencies": { + "@nevware21/ts-utils": ">= 0.9.4 < 2.x" + } }, - "@istanbuljs/schema": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", - "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", - "dev": true + "node_modules/@microsoft/applicationinsights-common/node_modules/@microsoft/dynamicproto-js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz", + "integrity": "sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg==", + "dependencies": { + "@nevware21/ts-utils": ">= 0.9.4 < 2.x" + } }, - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" + "node_modules/@microsoft/applicationinsights-core-js": { + "version": "2.8.15", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.15.tgz", + "integrity": "sha512-yYAs9MyjGr2YijQdUSN9mVgT1ijI1FPMgcffpaPmYbHAVbQmF7bXudrBWHxmLzJlwl5rfep+Zgjli2e67lwUqQ==", + "dependencies": { + "@microsoft/applicationinsights-shims": "2.0.2", + "@microsoft/dynamicproto-js": "^1.1.9" + }, + "peerDependencies": { + "tslib": "*" } }, - "@jupyter-widgets/base": { + "node_modules/@microsoft/applicationinsights-shims": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@jupyter-widgets/base/-/base-2.0.2.tgz", - "integrity": "sha512-nNpD+RGJ0As74XxDSGMeObfXSZ8XPBFHJ1AyugzYxpmxIigB2n3DxTyonASkR/3hXwxl3/nXBxHGlxQGs/+nOA==", - "dev": true, - "requires": { - "@jupyterlab/services": "^4.0.0", - "@phosphor/coreutils": "^1.2.0", - "@phosphor/messaging": "^1.2.1", - "@phosphor/widgets": "^1.3.0", - "@types/backbone": "^1.4.1", - "@types/lodash": "^4.14.134", - "backbone": "1.2.3", - "base64-js": "^1.2.1", - "jquery": "^3.1.1", - "lodash": "^4.17.4" + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.2.tgz", + "integrity": "sha512-PoHEgsnmcqruLNHZ/amACqdJ6YYQpED0KSRe6J7gIJTtpZC1FfFU9b1fmDKDKtFoUSrPzEh1qzO3kmRZP0betg==" + }, + "node_modules/@microsoft/applicationinsights-web-basic": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.0.2.tgz", + "integrity": "sha512-6Lq0DE/pZp9RvSV+weGbcxN1NDmfczj6gNPhvZKV2YSQ3RK0LZE3+wjTWLXfuStq8a+nCBdsRpWk8tOKgsoxcg==", + "dependencies": { + "@microsoft/applicationinsights-channel-js": "3.0.2", + "@microsoft/applicationinsights-common": "3.0.2", + "@microsoft/applicationinsights-core-js": "3.0.2", + "@microsoft/applicationinsights-shims": "3.0.1", + "@microsoft/dynamicproto-js": "^2.0.2", + "@nevware21/ts-async": ">= 0.2.4 < 2.x", + "@nevware21/ts-utils": ">= 0.9.5 < 2.x" + }, + "peerDependencies": { + "tslib": "*" } }, - "@jupyter-widgets/controls": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@jupyter-widgets/controls/-/controls-1.5.3.tgz", - "integrity": "sha512-eigiIdhYeziKslm+pddZSz5RkzbkDB1C1O25K/S1+yzHm5DTR4iPt00vliOn2wokTvZUsmaC2JPL05eaVAf2iA==", - "dev": true, - "requires": { - "@jupyter-widgets/base": "^2.0.2", - "@phosphor/algorithm": "^1.1.0", - "@phosphor/domutils": "^1.1.0", - "@phosphor/messaging": "^1.2.1", - "@phosphor/signaling": "^1.2.0", - "@phosphor/widgets": "^1.3.0", - "d3-format": "^1.3.0", - "jquery": "^3.1.1", - "jquery-ui": "^1.12.1", - "underscore": "^1.8.3" + "node_modules/@microsoft/applicationinsights-web-basic/node_modules/@microsoft/applicationinsights-core-js": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.2.tgz", + "integrity": "sha512-WQhVhzlRlLDrQzn3OShCW/pL3BW5WC57t0oywSknX3q7lMzI3jDg7Ihh0iuIcNTzGCTbDkuqr4d6IjEDWIMtJQ==", + "dependencies": { + "@microsoft/applicationinsights-shims": "3.0.1", + "@microsoft/dynamicproto-js": "^2.0.2", + "@nevware21/ts-async": ">= 0.2.4 < 2.x", + "@nevware21/ts-utils": ">= 0.9.5 < 2.x" + }, + "peerDependencies": { + "tslib": "*" } }, - "@jupyter-widgets/jupyterlab-manager": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@jupyter-widgets/jupyterlab-manager/-/jupyterlab-manager-1.1.0.tgz", - "integrity": "sha512-2dKdzl43nMvmr8A/AtonPb23SWWTQW9KjwCmTjWcWMfMsAcmAg5yLY+KfmQE59RnVA/xEcgdb53y1/ADuNbzhg==", - "dev": true, - "requires": { - "@jupyter-widgets/base": "^2.0.2", - "@jupyter-widgets/controls": "^1.5.3", - "@jupyter-widgets/output": "^2.0.1", - "@jupyterlab/application": "^1.2.0", - "@jupyterlab/coreutils": "^3.2.0", - "@jupyterlab/docregistry": "^1.2.0", - "@jupyterlab/logconsole": "^1.0.0", - "@jupyterlab/mainmenu": "^1.2.0", - "@jupyterlab/notebook": "^1.2.0", - "@jupyterlab/outputarea": "^1.2.0", - "@jupyterlab/rendermime": "^1.2.0", - "@jupyterlab/rendermime-interfaces": "^1.5.0", - "@jupyterlab/services": "^4.2.0", - "@phosphor/algorithm": "^1.1.0", - "@phosphor/coreutils": "^1.3.0", - "@phosphor/disposable": "^1.1.1", - "@phosphor/messaging": "^1.2.1", - "@phosphor/properties": "^1.1.0", - "@phosphor/signaling": "^1.2.0", - "@phosphor/widgets": "^1.3.0", - "@types/backbone": "^1.4.1", - "jquery": "^3.1.1", - "semver": "^6.1.1" - }, - "dependencies": { - "@jupyterlab/coreutils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@jupyterlab/coreutils/-/coreutils-3.2.0.tgz", - "integrity": "sha512-LATiUsHuwze/h3JC2EZOBV+kGBoUKO3npqw/Pcgge4bz09xF/oTDrx4G8jl5eew3w1dCUNp9eLduNh8Orrw7xQ==", - "dev": true, - "requires": { - "@phosphor/commands": "^1.7.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/properties": "^1.1.3", - "@phosphor/signaling": "^1.3.0", - "ajv": "^6.5.5", - "json5": "^2.1.0", - "minimist": "~1.2.0", - "moment": "^2.24.0", - "path-posix": "~1.0.0", - "url-parse": "~1.4.3" - }, - "dependencies": { - "@phosphor/algorithm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz", - "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==", - "dev": true - }, - "@phosphor/disposable": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/disposable/-/disposable-1.3.1.tgz", - "integrity": "sha512-0NGzoTXTOizWizK/brKKd5EjJhuuEH4903tLika7q6wl/u0tgneJlTh7R+MBVeih0iNxtuJAfBa3IEY6Qmj+Sw==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/signaling": "^1.3.1" - } - }, - "@phosphor/signaling": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/signaling/-/signaling-1.3.1.tgz", - "integrity": "sha512-Eq3wVCPQAhUd9+gUGaYygMr+ov7dhSGblSBXiDzpZlSIfa8OVD4P3cCvYXr/acDTNmZ/gHTcSFO8/n3rDkeXzg==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0" - } - } - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } + "node_modules/@microsoft/applicationinsights-web-basic/node_modules/@microsoft/applicationinsights-shims": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", + "integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==", + "dependencies": { + "@nevware21/ts-utils": ">= 0.9.4 < 2.x" } }, - "@jupyter-widgets/output": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@jupyter-widgets/output/-/output-2.0.1.tgz", - "integrity": "sha512-8yljIRbn98XNAoLnTOGEiBGKc7SH/ZEueatcJqu8FBMv3FN8hUKc2mgPLfcrlFRDwfe09p51j793oBUYdJUKPg==", - "dev": true, - "requires": { - "@jupyter-widgets/base": "^2.0.2" + "node_modules/@microsoft/applicationinsights-web-basic/node_modules/@microsoft/dynamicproto-js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz", + "integrity": "sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg==", + "dependencies": { + "@nevware21/ts-utils": ">= 0.9.4 < 2.x" } }, - "@jupyter-widgets/schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@jupyter-widgets/schema/-/schema-0.4.0.tgz", - "integrity": "sha512-0MAZ6hLOCe2dYiUvEAfYvWKD7zV9AdkC4AoIEQiWqAai9Pq06oPNWMMg6x+J0ZaNnZWqR2c16f62ehd57Ql7Zw==" + "node_modules/@microsoft/applicationinsights-web-snippet": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-snippet/-/applicationinsights-web-snippet-1.0.1.tgz", + "integrity": "sha512-2IHAOaLauc8qaAitvWS+U931T+ze+7MNWrDHY47IENP5y2UA0vqJDu67kWZDdpCN1fFC77sfgfB+HV7SrKshnQ==" }, - "@jupyterlab/application": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@jupyterlab/application/-/application-1.2.2.tgz", - "integrity": "sha512-HNHVR6ApLdS542ndCmvD0GBitNuLeQwAP2cc+L7fSu67KG4CiMaPyJfyFuQKcMyZbWb5ngebDf8qMgyUX8+gwQ==", - "dev": true, - "requires": { - "@jupyterlab/apputils": "^1.2.2", - "@jupyterlab/coreutils": "^3.2.0", - "@jupyterlab/docregistry": "^1.2.2", - "@jupyterlab/rendermime": "^1.2.2", - "@jupyterlab/rendermime-interfaces": "^1.5.0", - "@jupyterlab/services": "^4.2.0", - "@jupyterlab/ui-components": "^1.2.1", - "@phosphor/algorithm": "^1.2.0", - "@phosphor/application": "^1.7.0", - "@phosphor/commands": "^1.7.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/messaging": "^1.3.0", - "@phosphor/properties": "^1.1.3", - "@phosphor/signaling": "^1.3.0", - "@phosphor/widgets": "^1.9.0", - "font-awesome": "~4.7.0" - }, - "dependencies": { - "@jupyterlab/coreutils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@jupyterlab/coreutils/-/coreutils-3.2.0.tgz", - "integrity": "sha512-LATiUsHuwze/h3JC2EZOBV+kGBoUKO3npqw/Pcgge4bz09xF/oTDrx4G8jl5eew3w1dCUNp9eLduNh8Orrw7xQ==", - "dev": true, - "requires": { - "@phosphor/commands": "^1.7.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/properties": "^1.1.3", - "@phosphor/signaling": "^1.3.0", - "ajv": "^6.5.5", - "json5": "^2.1.0", - "minimist": "~1.2.0", - "moment": "^2.24.0", - "path-posix": "~1.0.0", - "url-parse": "~1.4.3" - } - }, - "@phosphor/algorithm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz", - "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==", - "dev": true - }, - "@phosphor/disposable": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/disposable/-/disposable-1.3.1.tgz", - "integrity": "sha512-0NGzoTXTOizWizK/brKKd5EjJhuuEH4903tLika7q6wl/u0tgneJlTh7R+MBVeih0iNxtuJAfBa3IEY6Qmj+Sw==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/signaling": "^1.3.1" - } - }, - "@phosphor/signaling": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/signaling/-/signaling-1.3.1.tgz", - "integrity": "sha512-Eq3wVCPQAhUd9+gUGaYygMr+ov7dhSGblSBXiDzpZlSIfa8OVD4P3cCvYXr/acDTNmZ/gHTcSFO8/n3rDkeXzg==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0" - } - } + "node_modules/@microsoft/dynamicproto-js": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.9.tgz", + "integrity": "sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ==" + }, + "node_modules/@nevware21/ts-async": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.3.0.tgz", + "integrity": "sha512-ZUcgUH12LN/F6nzN0cYd0F/rJaMLmXr0EHVTyYfaYmK55bdwE4338uue4UiVoRqHVqNW4KDUrJc49iGogHKeWA==", + "dependencies": { + "@nevware21/ts-utils": ">= 0.10.0 < 2.x" } }, - "@jupyterlab/apputils": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@jupyterlab/apputils/-/apputils-1.2.2.tgz", - "integrity": "sha512-9JFub/RTxL/9w2oyihDrUGk/hJW06PbeE7aIyl4S92ePK6trl3q/N1nPqEoC0N1Kj3KmMt9wFpNHt7BYHyVoQw==", - "dev": true, - "requires": { - "@jupyterlab/coreutils": "^3.2.0", - "@jupyterlab/services": "^4.2.0", - "@jupyterlab/ui-components": "^1.2.1", - "@phosphor/algorithm": "^1.2.0", - "@phosphor/commands": "^1.7.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/domutils": "^1.1.3", - "@phosphor/messaging": "^1.3.0", - "@phosphor/properties": "^1.1.3", - "@phosphor/signaling": "^1.3.0", - "@phosphor/virtualdom": "^1.2.0", - "@phosphor/widgets": "^1.9.0", - "@types/react": "~16.8.18", - "react": "~16.8.4", - "react-dom": "~16.8.4", - "sanitize-html": "~1.20.1" - }, - "dependencies": { - "@jupyterlab/coreutils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@jupyterlab/coreutils/-/coreutils-3.2.0.tgz", - "integrity": "sha512-LATiUsHuwze/h3JC2EZOBV+kGBoUKO3npqw/Pcgge4bz09xF/oTDrx4G8jl5eew3w1dCUNp9eLduNh8Orrw7xQ==", - "dev": true, - "requires": { - "@phosphor/commands": "^1.7.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/properties": "^1.1.3", - "@phosphor/signaling": "^1.3.0", - "ajv": "^6.5.5", - "json5": "^2.1.0", - "minimist": "~1.2.0", - "moment": "^2.24.0", - "path-posix": "~1.0.0", - "url-parse": "~1.4.3" - } - }, - "@phosphor/algorithm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz", - "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==", - "dev": true - }, - "@phosphor/disposable": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/disposable/-/disposable-1.3.1.tgz", - "integrity": "sha512-0NGzoTXTOizWizK/brKKd5EjJhuuEH4903tLika7q6wl/u0tgneJlTh7R+MBVeih0iNxtuJAfBa3IEY6Qmj+Sw==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/signaling": "^1.3.1" - } - }, - "@phosphor/signaling": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/signaling/-/signaling-1.3.1.tgz", - "integrity": "sha512-Eq3wVCPQAhUd9+gUGaYygMr+ov7dhSGblSBXiDzpZlSIfa8OVD4P3cCvYXr/acDTNmZ/gHTcSFO8/n3rDkeXzg==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0" - } - } + "node_modules/@nevware21/ts-utils": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.10.1.tgz", + "integrity": "sha512-pMny25NnF2/MJwdqC3Iyjm2pGIXNxni4AROpcqDeWa+td9JMUY4bUS9uU9XW+BoBRqTLUL+WURF9SOd/6OQzRg==" + }, + "node_modules/@nicolo-ribaudo/semver-v6": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz", + "integrity": "sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==", + "dev": true, + "bin": { + "semver": "bin/semver.js" } }, - "@jupyterlab/attachments": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@jupyterlab/attachments/-/attachments-1.2.2.tgz", - "integrity": "sha512-ME7aWyEs4piW6tCQbJ4zfuX6XipX07Wb9vVcW7SuRPlDp7lIUi4nPINkMO2us/lTsuS0/ZjgXjb+VKu4CbTYFQ==", + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, - "requires": { - "@jupyterlab/coreutils": "^3.2.0", - "@jupyterlab/observables": "^2.4.0", - "@jupyterlab/rendermime": "^1.2.2", - "@jupyterlab/rendermime-interfaces": "^1.5.0", - "@phosphor/disposable": "^1.3.0", - "@phosphor/signaling": "^1.3.0" - }, "dependencies": { - "@jupyterlab/coreutils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@jupyterlab/coreutils/-/coreutils-3.2.0.tgz", - "integrity": "sha512-LATiUsHuwze/h3JC2EZOBV+kGBoUKO3npqw/Pcgge4bz09xF/oTDrx4G8jl5eew3w1dCUNp9eLduNh8Orrw7xQ==", - "dev": true, - "requires": { - "@phosphor/commands": "^1.7.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/properties": "^1.1.3", - "@phosphor/signaling": "^1.3.0", - "ajv": "^6.5.5", - "json5": "^2.1.0", - "minimist": "~1.2.0", - "moment": "^2.24.0", - "path-posix": "~1.0.0", - "url-parse": "~1.4.3" - } - }, - "@phosphor/algorithm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz", - "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==", - "dev": true - }, - "@phosphor/disposable": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/disposable/-/disposable-1.3.1.tgz", - "integrity": "sha512-0NGzoTXTOizWizK/brKKd5EjJhuuEH4903tLika7q6wl/u0tgneJlTh7R+MBVeih0iNxtuJAfBa3IEY6Qmj+Sw==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/signaling": "^1.3.1" - } - }, - "@phosphor/signaling": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/signaling/-/signaling-1.3.1.tgz", - "integrity": "sha512-Eq3wVCPQAhUd9+gUGaYygMr+ov7dhSGblSBXiDzpZlSIfa8OVD4P3cCvYXr/acDTNmZ/gHTcSFO8/n3rDkeXzg==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0" - } - } + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" } }, - "@jupyterlab/cells": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@jupyterlab/cells/-/cells-1.2.3.tgz", - "integrity": "sha512-7ZyXdq/bcQAlPS5ACAGSB/T2NLSnMhMSmItyYq1j77VCKAh2RW5IJEdGuHfUn+6+wBrBIcF3/P4wwzPGVUtCgw==", - "dev": true, - "requires": { - "@jupyterlab/apputils": "^1.2.2", - "@jupyterlab/attachments": "^1.2.2", - "@jupyterlab/codeeditor": "^1.2.0", - "@jupyterlab/codemirror": "^1.2.2", - "@jupyterlab/coreutils": "^3.2.0", - "@jupyterlab/filebrowser": "^1.2.2", - "@jupyterlab/observables": "^2.4.0", - "@jupyterlab/outputarea": "^1.2.3", - "@jupyterlab/rendermime": "^1.2.2", - "@jupyterlab/services": "^4.2.0", - "@phosphor/algorithm": "^1.2.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/dragdrop": "^1.3.0", - "@phosphor/messaging": "^1.3.0", - "@phosphor/signaling": "^1.3.0", - "@phosphor/virtualdom": "^1.2.0", - "@phosphor/widgets": "^1.9.0", - "react": "~16.8.4" - }, - "dependencies": { - "@jupyterlab/coreutils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@jupyterlab/coreutils/-/coreutils-3.2.0.tgz", - "integrity": "sha512-LATiUsHuwze/h3JC2EZOBV+kGBoUKO3npqw/Pcgge4bz09xF/oTDrx4G8jl5eew3w1dCUNp9eLduNh8Orrw7xQ==", - "dev": true, - "requires": { - "@phosphor/commands": "^1.7.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/properties": "^1.1.3", - "@phosphor/signaling": "^1.3.0", - "ajv": "^6.5.5", - "json5": "^2.1.0", - "minimist": "~1.2.0", - "moment": "^2.24.0", - "path-posix": "~1.0.0", - "url-parse": "~1.4.3" - } - }, - "@phosphor/algorithm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz", - "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==", - "dev": true - }, - "@phosphor/disposable": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/disposable/-/disposable-1.3.1.tgz", - "integrity": "sha512-0NGzoTXTOizWizK/brKKd5EjJhuuEH4903tLika7q6wl/u0tgneJlTh7R+MBVeih0iNxtuJAfBa3IEY6Qmj+Sw==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/signaling": "^1.3.1" - } - }, - "@phosphor/signaling": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/signaling/-/signaling-1.3.1.tgz", - "integrity": "sha512-Eq3wVCPQAhUd9+gUGaYygMr+ov7dhSGblSBXiDzpZlSIfa8OVD4P3cCvYXr/acDTNmZ/gHTcSFO8/n3rDkeXzg==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0" - } - } + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" } }, - "@jupyterlab/codeeditor": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jupyterlab/codeeditor/-/codeeditor-1.2.0.tgz", - "integrity": "sha512-toejhF/a80X10SZyvEnsnnlS9SxR5W4cz67ju7e/2lsZ8RMwZEDDJAJXyW3mw/EEjt8oVRNP2QpM8L5clE9XyQ==", + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, - "requires": { - "@jupyterlab/coreutils": "^3.2.0", - "@jupyterlab/observables": "^2.4.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/dragdrop": "^1.3.0", - "@phosphor/messaging": "^1.3.0", - "@phosphor/signaling": "^1.3.0", - "@phosphor/widgets": "^1.9.0" + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.4.1.tgz", + "integrity": "sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.15.2.tgz", + "integrity": "sha512-+gBv15ta96WqkHZaPpcDHiaz0utiiHZVfm2YOYSqFGrUaJpPkMoSuLBB58YFQGi6Rsb9EHos84X6X5+9JspmLw==", "dependencies": { - "@jupyterlab/coreutils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@jupyterlab/coreutils/-/coreutils-3.2.0.tgz", - "integrity": "sha512-LATiUsHuwze/h3JC2EZOBV+kGBoUKO3npqw/Pcgge4bz09xF/oTDrx4G8jl5eew3w1dCUNp9eLduNh8Orrw7xQ==", - "dev": true, - "requires": { - "@phosphor/commands": "^1.7.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/properties": "^1.1.3", - "@phosphor/signaling": "^1.3.0", - "ajv": "^6.5.5", - "json5": "^2.1.0", - "minimist": "~1.2.0", - "moment": "^2.24.0", - "path-posix": "~1.0.0", - "url-parse": "~1.4.3" - } - }, - "@phosphor/algorithm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz", - "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==", - "dev": true - }, - "@phosphor/disposable": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/disposable/-/disposable-1.3.1.tgz", - "integrity": "sha512-0NGzoTXTOizWizK/brKKd5EjJhuuEH4903tLika7q6wl/u0tgneJlTh7R+MBVeih0iNxtuJAfBa3IEY6Qmj+Sw==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/signaling": "^1.3.1" - } - }, - "@phosphor/signaling": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/signaling/-/signaling-1.3.1.tgz", - "integrity": "sha512-Eq3wVCPQAhUd9+gUGaYygMr+ov7dhSGblSBXiDzpZlSIfa8OVD4P3cCvYXr/acDTNmZ/gHTcSFO8/n3rDkeXzg==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0" - } - } + "@opentelemetry/semantic-conventions": "1.15.2" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.5.0" } }, - "@jupyterlab/codemirror": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@jupyterlab/codemirror/-/codemirror-1.2.2.tgz", - "integrity": "sha512-hVPpUa5FnuseJj+ZXIUtjP2nP4fjJB/Fwwc5EdpQHiogwpCR6xibyGdSlkRWmIEzFBp4C0yAB2XbqJt13Ofkkg==", - "dev": true, - "requires": { - "@jupyterlab/apputils": "^1.2.2", - "@jupyterlab/codeeditor": "^1.2.0", - "@jupyterlab/coreutils": "^3.2.0", - "@jupyterlab/observables": "^2.4.0", - "@jupyterlab/statusbar": "^1.2.2", - "@phosphor/algorithm": "^1.2.0", - "@phosphor/commands": "^1.7.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/signaling": "^1.3.0", - "@phosphor/widgets": "^1.9.0", - "codemirror": "~5.47.0", - "react": "~16.8.4" - }, - "dependencies": { - "@jupyterlab/coreutils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@jupyterlab/coreutils/-/coreutils-3.2.0.tgz", - "integrity": "sha512-LATiUsHuwze/h3JC2EZOBV+kGBoUKO3npqw/Pcgge4bz09xF/oTDrx4G8jl5eew3w1dCUNp9eLduNh8Orrw7xQ==", - "dev": true, - "requires": { - "@phosphor/commands": "^1.7.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/properties": "^1.1.3", - "@phosphor/signaling": "^1.3.0", - "ajv": "^6.5.5", - "json5": "^2.1.0", - "minimist": "~1.2.0", - "moment": "^2.24.0", - "path-posix": "~1.0.0", - "url-parse": "~1.4.3" - } - }, - "@phosphor/algorithm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz", - "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==", - "dev": true - }, - "@phosphor/disposable": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/disposable/-/disposable-1.3.1.tgz", - "integrity": "sha512-0NGzoTXTOizWizK/brKKd5EjJhuuEH4903tLika7q6wl/u0tgneJlTh7R+MBVeih0iNxtuJAfBa3IEY6Qmj+Sw==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/signaling": "^1.3.1" - } - }, - "@phosphor/signaling": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/signaling/-/signaling-1.3.1.tgz", - "integrity": "sha512-Eq3wVCPQAhUd9+gUGaYygMr+ov7dhSGblSBXiDzpZlSIfa8OVD4P3cCvYXr/acDTNmZ/gHTcSFO8/n3rDkeXzg==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0" - } - } + "node_modules/@opentelemetry/instrumentation": { + "version": "0.41.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.41.2.tgz", + "integrity": "sha512-rxU72E0pKNH6ae2w5+xgVYZLzc5mlxAbGzF4shxMVK8YC2QQsfN38B2GPbj0jvrKWWNUElfclQ+YTykkNg/grw==", + "dependencies": { + "@types/shimmer": "^1.0.2", + "import-in-the-middle": "1.4.2", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.1", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" } }, - "@jupyterlab/coreutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jupyterlab/coreutils/-/coreutils-3.1.0.tgz", - "integrity": "sha512-ZqgzDUyanyvc86gtCrIbc1M6iniKHYmWNWHvWOcnq3KIP3wk3grchsTYPTfQDxcUS6F04baPGp/KohEU2ml40Q==", - "requires": { - "@phosphor/commands": "^1.6.3", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.2.0", - "@phosphor/properties": "^1.1.3", - "@phosphor/signaling": "^1.2.3", - "ajv": "^6.5.5", - "json5": "^2.1.0", - "minimist": "~1.2.0", - "moment": "^2.24.0", - "path-posix": "~1.0.0", - "url-parse": "~1.4.3" - } - }, - "@jupyterlab/docmanager": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@jupyterlab/docmanager/-/docmanager-1.2.2.tgz", - "integrity": "sha512-JlA/4rUIumH2sZndTzd2/f8Aevegi4eXvUrvZY6cA3fG9Nir6hHSBznUfaL3iR2YHmnxTM5hUwAK8jnjwmLuig==", - "dev": true, - "requires": { - "@jupyterlab/apputils": "^1.2.2", - "@jupyterlab/coreutils": "^3.2.0", - "@jupyterlab/docregistry": "^1.2.2", - "@jupyterlab/services": "^4.2.0", - "@jupyterlab/statusbar": "^1.2.2", - "@phosphor/algorithm": "^1.2.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/messaging": "^1.3.0", - "@phosphor/properties": "^1.1.3", - "@phosphor/signaling": "^1.3.0", - "@phosphor/widgets": "^1.9.0", - "react": "~16.8.4" - }, - "dependencies": { - "@jupyterlab/coreutils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@jupyterlab/coreutils/-/coreutils-3.2.0.tgz", - "integrity": "sha512-LATiUsHuwze/h3JC2EZOBV+kGBoUKO3npqw/Pcgge4bz09xF/oTDrx4G8jl5eew3w1dCUNp9eLduNh8Orrw7xQ==", - "dev": true, - "requires": { - "@phosphor/commands": "^1.7.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/properties": "^1.1.3", - "@phosphor/signaling": "^1.3.0", - "ajv": "^6.5.5", - "json5": "^2.1.0", - "minimist": "~1.2.0", - "moment": "^2.24.0", - "path-posix": "~1.0.0", - "url-parse": "~1.4.3" - } - }, - "@phosphor/algorithm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz", - "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==", - "dev": true - }, - "@phosphor/disposable": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/disposable/-/disposable-1.3.1.tgz", - "integrity": "sha512-0NGzoTXTOizWizK/brKKd5EjJhuuEH4903tLika7q6wl/u0tgneJlTh7R+MBVeih0iNxtuJAfBa3IEY6Qmj+Sw==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/signaling": "^1.3.1" - } - }, - "@phosphor/signaling": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/signaling/-/signaling-1.3.1.tgz", - "integrity": "sha512-Eq3wVCPQAhUd9+gUGaYygMr+ov7dhSGblSBXiDzpZlSIfa8OVD4P3cCvYXr/acDTNmZ/gHTcSFO8/n3rDkeXzg==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0" - } - } + "node_modules/@opentelemetry/resources": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.15.2.tgz", + "integrity": "sha512-xmMRLenT9CXmm5HMbzpZ1hWhaUowQf8UB4jMjFlAxx1QzQcsD3KFNAVX/CAWzFPtllTyTplrA4JrQ7sCH3qmYw==", + "dependencies": { + "@opentelemetry/core": "1.15.2", + "@opentelemetry/semantic-conventions": "1.15.2" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.5.0" } }, - "@jupyterlab/docregistry": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@jupyterlab/docregistry/-/docregistry-1.2.2.tgz", - "integrity": "sha512-2c3nA+PsTsZ8h5Z80EE38Iz7LJTpxmk57c7aZ0LWw65eAf+c071w9xUdWRg9apmXz4o7NJsMeDm6Dns+7+AZow==", - "dev": true, - "requires": { - "@jupyterlab/apputils": "^1.2.2", - "@jupyterlab/codeeditor": "^1.2.0", - "@jupyterlab/codemirror": "^1.2.2", - "@jupyterlab/coreutils": "^3.2.0", - "@jupyterlab/observables": "^2.4.0", - "@jupyterlab/rendermime": "^1.2.2", - "@jupyterlab/rendermime-interfaces": "^1.5.0", - "@jupyterlab/services": "^4.2.0", - "@phosphor/algorithm": "^1.2.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/messaging": "^1.3.0", - "@phosphor/signaling": "^1.3.0", - "@phosphor/widgets": "^1.9.0" - }, - "dependencies": { - "@jupyterlab/coreutils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@jupyterlab/coreutils/-/coreutils-3.2.0.tgz", - "integrity": "sha512-LATiUsHuwze/h3JC2EZOBV+kGBoUKO3npqw/Pcgge4bz09xF/oTDrx4G8jl5eew3w1dCUNp9eLduNh8Orrw7xQ==", - "dev": true, - "requires": { - "@phosphor/commands": "^1.7.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/properties": "^1.1.3", - "@phosphor/signaling": "^1.3.0", - "ajv": "^6.5.5", - "json5": "^2.1.0", - "minimist": "~1.2.0", - "moment": "^2.24.0", - "path-posix": "~1.0.0", - "url-parse": "~1.4.3" - } - }, - "@phosphor/algorithm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz", - "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==", - "dev": true - }, - "@phosphor/disposable": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/disposable/-/disposable-1.3.1.tgz", - "integrity": "sha512-0NGzoTXTOizWizK/brKKd5EjJhuuEH4903tLika7q6wl/u0tgneJlTh7R+MBVeih0iNxtuJAfBa3IEY6Qmj+Sw==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/signaling": "^1.3.1" - } - }, - "@phosphor/signaling": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/signaling/-/signaling-1.3.1.tgz", - "integrity": "sha512-Eq3wVCPQAhUd9+gUGaYygMr+ov7dhSGblSBXiDzpZlSIfa8OVD4P3cCvYXr/acDTNmZ/gHTcSFO8/n3rDkeXzg==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0" - } - } + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.15.2.tgz", + "integrity": "sha512-BEaxGZbWtvnSPchV98qqqqa96AOcb41pjgvhfzDij10tkBhIu9m0Jd6tZ1tJB5ZHfHbTffqYVYE0AOGobec/EQ==", + "dependencies": { + "@opentelemetry/core": "1.15.2", + "@opentelemetry/resources": "1.15.2", + "@opentelemetry/semantic-conventions": "1.15.2" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.5.0" } }, - "@jupyterlab/filebrowser": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@jupyterlab/filebrowser/-/filebrowser-1.2.2.tgz", - "integrity": "sha512-xs01yLt6LBCDaCDVowthRQT+oIFuLQ9n3unwMctHZLi1aeo5cJVzBq/OKMBNJcMAOMAn6HSAEvKwfSnWkpKuiw==", - "dev": true, - "requires": { - "@jupyterlab/apputils": "^1.2.2", - "@jupyterlab/coreutils": "^3.2.0", - "@jupyterlab/docmanager": "^1.2.2", - "@jupyterlab/docregistry": "^1.2.2", - "@jupyterlab/services": "^4.2.0", - "@jupyterlab/statusbar": "^1.2.2", - "@jupyterlab/ui-components": "^1.2.1", - "@phosphor/algorithm": "^1.2.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/domutils": "^1.1.3", - "@phosphor/dragdrop": "^1.3.0", - "@phosphor/messaging": "^1.3.0", - "@phosphor/signaling": "^1.3.0", - "@phosphor/widgets": "^1.9.0", - "react": "~16.8.4" - }, - "dependencies": { - "@jupyterlab/coreutils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@jupyterlab/coreutils/-/coreutils-3.2.0.tgz", - "integrity": "sha512-LATiUsHuwze/h3JC2EZOBV+kGBoUKO3npqw/Pcgge4bz09xF/oTDrx4G8jl5eew3w1dCUNp9eLduNh8Orrw7xQ==", - "dev": true, - "requires": { - "@phosphor/commands": "^1.7.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/properties": "^1.1.3", - "@phosphor/signaling": "^1.3.0", - "ajv": "^6.5.5", - "json5": "^2.1.0", - "minimist": "~1.2.0", - "moment": "^2.24.0", - "path-posix": "~1.0.0", - "url-parse": "~1.4.3" - } - }, - "@phosphor/algorithm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz", - "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==", - "dev": true - }, - "@phosphor/disposable": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/disposable/-/disposable-1.3.1.tgz", - "integrity": "sha512-0NGzoTXTOizWizK/brKKd5EjJhuuEH4903tLika7q6wl/u0tgneJlTh7R+MBVeih0iNxtuJAfBa3IEY6Qmj+Sw==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/signaling": "^1.3.1" - } - }, - "@phosphor/signaling": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/signaling/-/signaling-1.3.1.tgz", - "integrity": "sha512-Eq3wVCPQAhUd9+gUGaYygMr+ov7dhSGblSBXiDzpZlSIfa8OVD4P3cCvYXr/acDTNmZ/gHTcSFO8/n3rDkeXzg==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0" - } - } + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.15.2.tgz", + "integrity": "sha512-CjbOKwk2s+3xPIMcd5UNYQzsf+v94RczbdNix9/kQh38WiQkM90sUOi3if8eyHFgiBjBjhwXrA7W3ydiSQP9mw==", + "engines": { + "node": ">=14" } }, - "@jupyterlab/logconsole": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@jupyterlab/logconsole/-/logconsole-1.0.3.tgz", - "integrity": "sha512-W/YCr57UwH11O+6iyWyLILffBeIGDAzCY6B/Yb97+YH4Cgdxovmy0zlTazLLBD+Pju7oDqJht4OQonDc0XchIA==", + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, - "requires": { - "@jupyterlab/coreutils": "^3.2.0", - "@jupyterlab/outputarea": "^1.2.3", - "@jupyterlab/rendermime": "^1.2.2", - "@jupyterlab/services": "^4.2.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/messaging": "^1.3.0", - "@phosphor/signaling": "^1.3.0", - "@phosphor/widgets": "^1.9.0" - }, - "dependencies": { - "@jupyterlab/coreutils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@jupyterlab/coreutils/-/coreutils-3.2.0.tgz", - "integrity": "sha512-LATiUsHuwze/h3JC2EZOBV+kGBoUKO3npqw/Pcgge4bz09xF/oTDrx4G8jl5eew3w1dCUNp9eLduNh8Orrw7xQ==", - "dev": true, - "requires": { - "@phosphor/commands": "^1.7.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/properties": "^1.1.3", - "@phosphor/signaling": "^1.3.0", - "ajv": "^6.5.5", - "json5": "^2.1.0", - "minimist": "~1.2.0", - "moment": "^2.24.0", - "path-posix": "~1.0.0", - "url-parse": "~1.4.3" - } - }, - "@phosphor/algorithm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz", - "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==", - "dev": true - }, - "@phosphor/disposable": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/disposable/-/disposable-1.3.1.tgz", - "integrity": "sha512-0NGzoTXTOizWizK/brKKd5EjJhuuEH4903tLika7q6wl/u0tgneJlTh7R+MBVeih0iNxtuJAfBa3IEY6Qmj+Sw==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/signaling": "^1.3.1" - } - }, - "@phosphor/signaling": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/signaling/-/signaling-1.3.1.tgz", - "integrity": "sha512-Eq3wVCPQAhUd9+gUGaYygMr+ov7dhSGblSBXiDzpZlSIfa8OVD4P3cCvYXr/acDTNmZ/gHTcSFO8/n3rDkeXzg==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0" - } - } + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" } }, - "@jupyterlab/mainmenu": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@jupyterlab/mainmenu/-/mainmenu-1.2.2.tgz", - "integrity": "sha512-hNFvNvsjbqzM1GQp3tF/34j7Y9eR1zoGUuZBsAkge3W0Nsri8fUXfPwBTfOFBXhIx4j9aB4vZhCrgU91eNt7MA==", + "node_modules/@polka/url": { + "version": "1.0.0-next.21", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", + "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", + "dev": true + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", "dev": true, - "requires": { - "@jupyterlab/apputils": "^1.2.2", - "@jupyterlab/services": "^4.2.0", - "@phosphor/algorithm": "^1.2.0", - "@phosphor/commands": "^1.7.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/widgets": "^1.9.0" - }, - "dependencies": { - "@phosphor/algorithm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz", - "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==", - "dev": true - }, - "@phosphor/disposable": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/disposable/-/disposable-1.3.1.tgz", - "integrity": "sha512-0NGzoTXTOizWizK/brKKd5EjJhuuEH4903tLika7q6wl/u0tgneJlTh7R+MBVeih0iNxtuJAfBa3IEY6Qmj+Sw==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/signaling": "^1.3.1" - } - }, - "@phosphor/signaling": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/signaling/-/signaling-1.3.1.tgz", - "integrity": "sha512-Eq3wVCPQAhUd9+gUGaYygMr+ov7dhSGblSBXiDzpZlSIfa8OVD4P3cCvYXr/acDTNmZ/gHTcSFO8/n3rDkeXzg==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0" - } - } + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", + "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" } }, - "@jupyterlab/notebook": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@jupyterlab/notebook/-/notebook-1.2.3.tgz", - "integrity": "sha512-+LGIgWRTIaCVTFG8v3Ps1v0lb717Q8ojwvKvv1DdH4g/nbd6KiO5LXKbeprhZ55nyHmxJMzD6Zu87YzDSea5aA==", - "dev": true, - "requires": { - "@jupyterlab/apputils": "^1.2.2", - "@jupyterlab/cells": "^1.2.3", - "@jupyterlab/codeeditor": "^1.2.0", - "@jupyterlab/coreutils": "^3.2.0", - "@jupyterlab/docregistry": "^1.2.2", - "@jupyterlab/observables": "^2.4.0", - "@jupyterlab/rendermime": "^1.2.2", - "@jupyterlab/services": "^4.2.0", - "@jupyterlab/statusbar": "^1.2.2", - "@jupyterlab/ui-components": "^1.2.1", - "@phosphor/algorithm": "^1.2.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/domutils": "^1.1.3", - "@phosphor/dragdrop": "^1.3.0", - "@phosphor/messaging": "^1.3.0", - "@phosphor/properties": "^1.1.3", - "@phosphor/signaling": "^1.3.0", - "@phosphor/virtualdom": "^1.2.0", - "@phosphor/widgets": "^1.9.0", - "react": "~16.8.4" - }, - "dependencies": { - "@jupyterlab/coreutils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@jupyterlab/coreutils/-/coreutils-3.2.0.tgz", - "integrity": "sha512-LATiUsHuwze/h3JC2EZOBV+kGBoUKO3npqw/Pcgge4bz09xF/oTDrx4G8jl5eew3w1dCUNp9eLduNh8Orrw7xQ==", - "dev": true, - "requires": { - "@phosphor/commands": "^1.7.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/properties": "^1.1.3", - "@phosphor/signaling": "^1.3.0", - "ajv": "^6.5.5", - "json5": "^2.1.0", - "minimist": "~1.2.0", - "moment": "^2.24.0", - "path-posix": "~1.0.0", - "url-parse": "~1.4.3" - } - }, - "@phosphor/algorithm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz", - "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==", - "dev": true - }, - "@phosphor/disposable": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/disposable/-/disposable-1.3.1.tgz", - "integrity": "sha512-0NGzoTXTOizWizK/brKKd5EjJhuuEH4903tLika7q6wl/u0tgneJlTh7R+MBVeih0iNxtuJAfBa3IEY6Qmj+Sw==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/signaling": "^1.3.1" - } - }, - "@phosphor/signaling": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/signaling/-/signaling-1.3.1.tgz", - "integrity": "sha512-Eq3wVCPQAhUd9+gUGaYygMr+ov7dhSGblSBXiDzpZlSIfa8OVD4P3cCvYXr/acDTNmZ/gHTcSFO8/n3rDkeXzg==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0" - } - } + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" } }, - "@jupyterlab/observables": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@jupyterlab/observables/-/observables-2.4.0.tgz", - "integrity": "sha512-M/fhAnPqd6F4Zwt4IIsvHCkJmwbSw1Tko/hUXgdUQG86lPsJiTOh98sB3qwV1gtzb9oFF+kH21XsHnQZ6Yl6Pw==", - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/messaging": "^1.3.0", - "@phosphor/signaling": "^1.3.0" - }, + "node_modules/@sinonjs/fake-timers": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", + "dev": true, "dependencies": { - "@phosphor/algorithm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz", - "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==" - }, - "@phosphor/disposable": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/disposable/-/disposable-1.3.1.tgz", - "integrity": "sha512-0NGzoTXTOizWizK/brKKd5EjJhuuEH4903tLika7q6wl/u0tgneJlTh7R+MBVeih0iNxtuJAfBa3IEY6Qmj+Sw==", - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/signaling": "^1.3.1" - } - }, - "@phosphor/signaling": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/signaling/-/signaling-1.3.1.tgz", - "integrity": "sha512-Eq3wVCPQAhUd9+gUGaYygMr+ov7dhSGblSBXiDzpZlSIfa8OVD4P3cCvYXr/acDTNmZ/gHTcSFO8/n3rDkeXzg==", - "requires": { - "@phosphor/algorithm": "^1.2.0" - } - } + "@sinonjs/commons": "^3.0.0" } }, - "@jupyterlab/outputarea": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@jupyterlab/outputarea/-/outputarea-1.2.3.tgz", - "integrity": "sha512-7bFq4sUwoQfAS4KlDu8ogKwo2xGelj5YnVIxw4jlVY2+InMptfhltCI4q8qwvxgTAXVW9m+8QGC4ZHgtRdJt7Q==", - "dev": true, - "requires": { - "@jupyterlab/apputils": "^1.2.2", - "@jupyterlab/coreutils": "^3.2.0", - "@jupyterlab/observables": "^2.4.0", - "@jupyterlab/rendermime": "^1.2.2", - "@jupyterlab/rendermime-interfaces": "^1.5.0", - "@jupyterlab/services": "^4.2.0", - "@phosphor/algorithm": "^1.2.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/messaging": "^1.3.0", - "@phosphor/properties": "^1.1.3", - "@phosphor/signaling": "^1.3.0", - "@phosphor/widgets": "^1.9.0" - }, - "dependencies": { - "@jupyterlab/coreutils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@jupyterlab/coreutils/-/coreutils-3.2.0.tgz", - "integrity": "sha512-LATiUsHuwze/h3JC2EZOBV+kGBoUKO3npqw/Pcgge4bz09xF/oTDrx4G8jl5eew3w1dCUNp9eLduNh8Orrw7xQ==", - "dev": true, - "requires": { - "@phosphor/commands": "^1.7.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/properties": "^1.1.3", - "@phosphor/signaling": "^1.3.0", - "ajv": "^6.5.5", - "json5": "^2.1.0", - "minimist": "~1.2.0", - "moment": "^2.24.0", - "path-posix": "~1.0.0", - "url-parse": "~1.4.3" - } - }, - "@phosphor/algorithm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz", - "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==", - "dev": true - }, - "@phosphor/disposable": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/disposable/-/disposable-1.3.1.tgz", - "integrity": "sha512-0NGzoTXTOizWizK/brKKd5EjJhuuEH4903tLika7q6wl/u0tgneJlTh7R+MBVeih0iNxtuJAfBa3IEY6Qmj+Sw==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/signaling": "^1.3.1" - } - }, - "@phosphor/signaling": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/signaling/-/signaling-1.3.1.tgz", - "integrity": "sha512-Eq3wVCPQAhUd9+gUGaYygMr+ov7dhSGblSBXiDzpZlSIfa8OVD4P3cCvYXr/acDTNmZ/gHTcSFO8/n3rDkeXzg==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0" - } - } + "node_modules/@sinonjs/samsam": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" } }, - "@jupyterlab/rendermime": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@jupyterlab/rendermime/-/rendermime-1.2.2.tgz", - "integrity": "sha512-Meptrw869XQ5LUVOTpKUZNzJPcAO1rCc71Ch1jrPM4VRq3wkGvFGN5p82z2qWPOWXIG5IcQbfTnqpWLDVNzqMw==", - "dev": true, - "requires": { - "@jupyterlab/apputils": "^1.2.2", - "@jupyterlab/codemirror": "^1.2.2", - "@jupyterlab/coreutils": "^3.2.0", - "@jupyterlab/observables": "^2.4.0", - "@jupyterlab/rendermime-interfaces": "^1.5.0", - "@jupyterlab/services": "^4.2.0", - "@phosphor/algorithm": "^1.2.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/messaging": "^1.3.0", - "@phosphor/signaling": "^1.3.0", - "@phosphor/widgets": "^1.9.0", - "lodash.escape": "^4.0.1", - "marked": "^0.7.0" - }, - "dependencies": { - "@jupyterlab/coreutils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@jupyterlab/coreutils/-/coreutils-3.2.0.tgz", - "integrity": "sha512-LATiUsHuwze/h3JC2EZOBV+kGBoUKO3npqw/Pcgge4bz09xF/oTDrx4G8jl5eew3w1dCUNp9eLduNh8Orrw7xQ==", - "dev": true, - "requires": { - "@phosphor/commands": "^1.7.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/properties": "^1.1.3", - "@phosphor/signaling": "^1.3.0", - "ajv": "^6.5.5", - "json5": "^2.1.0", - "minimist": "~1.2.0", - "moment": "^2.24.0", - "path-posix": "~1.0.0", - "url-parse": "~1.4.3" - } - }, - "@phosphor/algorithm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz", - "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==", - "dev": true - }, - "@phosphor/disposable": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/disposable/-/disposable-1.3.1.tgz", - "integrity": "sha512-0NGzoTXTOizWizK/brKKd5EjJhuuEH4903tLika7q6wl/u0tgneJlTh7R+MBVeih0iNxtuJAfBa3IEY6Qmj+Sw==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/signaling": "^1.3.1" - } - }, - "@phosphor/signaling": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/signaling/-/signaling-1.3.1.tgz", - "integrity": "sha512-Eq3wVCPQAhUd9+gUGaYygMr+ov7dhSGblSBXiDzpZlSIfa8OVD4P3cCvYXr/acDTNmZ/gHTcSFO8/n3rDkeXzg==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0" - } - } + "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" } }, - "@jupyterlab/rendermime-interfaces": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jupyterlab/rendermime-interfaces/-/rendermime-interfaces-1.5.0.tgz", - "integrity": "sha512-k6DjX/srKl1FA1CZyrAzz1qA2v1arXUIAmbEddZ5L3O+dnvDlOKjkI/NexaRQvmQ62aziSln+wKrr2P1JPNmGg==", + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", + "dev": true + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "dev": true, - "requires": { - "@phosphor/coreutils": "^1.3.1", - "@phosphor/widgets": "^1.9.0" + "engines": { + "node": ">= 6" } }, - "@jupyterlab/services": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@jupyterlab/services/-/services-4.2.0.tgz", - "integrity": "sha512-diqiWCYLROwZoeZ46hO4oCINQKr3S789GOA3drw5gDie18/u1nJcvegnY9Oo58xHINnKTs0rKZmFgTe2DvYgQg==", - "requires": { - "@jupyterlab/coreutils": "^3.2.0", - "@jupyterlab/observables": "^2.4.0", - "@phosphor/algorithm": "^1.2.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/signaling": "^1.3.0", - "node-fetch": "^2.6.0", - "ws": "^7.0.0" - }, + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "node_modules/@types/bent": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@types/bent/-/bent-7.3.3.tgz", + "integrity": "sha512-5NEIhVzHiZ6wMjFBmJ3gwjxwGug6amMoAn93rtDBttwrODxm+bt63u+MJA7H9NGGM4X1m73sJrAxDapktl036Q==", + "dev": true, "dependencies": { - "@jupyterlab/coreutils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@jupyterlab/coreutils/-/coreutils-3.2.0.tgz", - "integrity": "sha512-LATiUsHuwze/h3JC2EZOBV+kGBoUKO3npqw/Pcgge4bz09xF/oTDrx4G8jl5eew3w1dCUNp9eLduNh8Orrw7xQ==", - "requires": { - "@phosphor/commands": "^1.7.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/properties": "^1.1.3", - "@phosphor/signaling": "^1.3.0", - "ajv": "^6.5.5", - "json5": "^2.1.0", - "minimist": "~1.2.0", - "moment": "^2.24.0", - "path-posix": "~1.0.0", - "url-parse": "~1.4.3" - } - }, - "@phosphor/algorithm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz", - "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==" - }, - "@phosphor/disposable": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/disposable/-/disposable-1.3.1.tgz", - "integrity": "sha512-0NGzoTXTOizWizK/brKKd5EjJhuuEH4903tLika7q6wl/u0tgneJlTh7R+MBVeih0iNxtuJAfBa3IEY6Qmj+Sw==", - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/signaling": "^1.3.1" - } - }, - "@phosphor/signaling": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/signaling/-/signaling-1.3.1.tgz", - "integrity": "sha512-Eq3wVCPQAhUd9+gUGaYygMr+ov7dhSGblSBXiDzpZlSIfa8OVD4P3cCvYXr/acDTNmZ/gHTcSFO8/n3rDkeXzg==", - "requires": { - "@phosphor/algorithm": "^1.2.0" - } - }, - "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" - }, - "ws": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.0.tgz", - "integrity": "sha512-+SqNqFbwTm/0DC18KYzIsMTnEWpLwJsiasW/O17la4iDRRIO9uaHbvKiAS3AHgTiuuWerK/brj4O6MYZkei9xg==", - "requires": { - "async-limiter": "^1.0.0" - } - } + "@types/node": "*" } }, - "@jupyterlab/statusbar": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@jupyterlab/statusbar/-/statusbar-1.2.2.tgz", - "integrity": "sha512-1SIyW3pqoqD1y7g5WvGB2UJASQ6uikE1HtRLj67UEstdTfiZyVPSutTZJrXqkRqsB7jPEU4EfwCLUFXEbZkLZw==", - "dev": true, - "requires": { - "@jupyterlab/apputils": "^1.2.2", - "@jupyterlab/codeeditor": "^1.2.0", - "@jupyterlab/coreutils": "^3.2.0", - "@jupyterlab/services": "^4.2.0", - "@jupyterlab/ui-components": "^1.2.1", - "@phosphor/algorithm": "^1.2.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/messaging": "^1.3.0", - "@phosphor/signaling": "^1.3.0", - "@phosphor/widgets": "^1.9.0", - "react": "~16.8.4", - "typestyle": "^2.0.1" - }, - "dependencies": { - "@jupyterlab/coreutils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@jupyterlab/coreutils/-/coreutils-3.2.0.tgz", - "integrity": "sha512-LATiUsHuwze/h3JC2EZOBV+kGBoUKO3npqw/Pcgge4bz09xF/oTDrx4G8jl5eew3w1dCUNp9eLduNh8Orrw7xQ==", - "dev": true, - "requires": { - "@phosphor/commands": "^1.7.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/properties": "^1.1.3", - "@phosphor/signaling": "^1.3.0", - "ajv": "^6.5.5", - "json5": "^2.1.0", - "minimist": "~1.2.0", - "moment": "^2.24.0", - "path-posix": "~1.0.0", - "url-parse": "~1.4.3" - } - }, - "@phosphor/algorithm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz", - "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==", - "dev": true - }, - "@phosphor/disposable": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/disposable/-/disposable-1.3.1.tgz", - "integrity": "sha512-0NGzoTXTOizWizK/brKKd5EjJhuuEH4903tLika7q6wl/u0tgneJlTh7R+MBVeih0iNxtuJAfBa3IEY6Qmj+Sw==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/signaling": "^1.3.1" - } - }, - "@phosphor/signaling": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/signaling/-/signaling-1.3.1.tgz", - "integrity": "sha512-Eq3wVCPQAhUd9+gUGaYygMr+ov7dhSGblSBXiDzpZlSIfa8OVD4P3cCvYXr/acDTNmZ/gHTcSFO8/n3rDkeXzg==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0" - } - } - } + "node_modules/@types/chai": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz", + "integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==", + "dev": true }, - "@jupyterlab/ui-components": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jupyterlab/ui-components/-/ui-components-1.2.1.tgz", - "integrity": "sha512-GUtIRwTmFnlJaPUM8SiFw1STmsyMVGjchLKqIoQnn0qYAJvaSUGyRqqoSD5iIpwov6OHCOOyxH6fQ5OAtH1kwA==", + "node_modules/@types/chai-arrays": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/chai-arrays/-/chai-arrays-2.0.0.tgz", + "integrity": "sha512-5h5jnAC9C64YnD7WJpA5gBG7CppF/QmoWytOssJ6ysENllW49NBdpsTx6uuIBOpnzAnXThb8jBICgB62wezTLQ==", "dev": true, - "requires": { - "@blueprintjs/core": "^3.9.0", - "@blueprintjs/select": "^3.3.0", - "@jupyterlab/coreutils": "^3.2.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/messaging": "^1.3.0", - "@phosphor/virtualdom": "^1.2.0", - "@phosphor/widgets": "^1.9.0", - "react": "~16.8.4", - "typestyle": "^2.0.1" - }, "dependencies": { - "@jupyterlab/coreutils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@jupyterlab/coreutils/-/coreutils-3.2.0.tgz", - "integrity": "sha512-LATiUsHuwze/h3JC2EZOBV+kGBoUKO3npqw/Pcgge4bz09xF/oTDrx4G8jl5eew3w1dCUNp9eLduNh8Orrw7xQ==", - "dev": true, - "requires": { - "@phosphor/commands": "^1.7.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.0", - "@phosphor/properties": "^1.1.3", - "@phosphor/signaling": "^1.3.0", - "ajv": "^6.5.5", - "json5": "^2.1.0", - "minimist": "~1.2.0", - "moment": "^2.24.0", - "path-posix": "~1.0.0", - "url-parse": "~1.4.3" - } - }, - "@phosphor/algorithm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz", - "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==", - "dev": true - }, - "@phosphor/disposable": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/disposable/-/disposable-1.3.1.tgz", - "integrity": "sha512-0NGzoTXTOizWizK/brKKd5EjJhuuEH4903tLika7q6wl/u0tgneJlTh7R+MBVeih0iNxtuJAfBa3IEY6Qmj+Sw==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/signaling": "^1.3.1" - } - }, - "@phosphor/signaling": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/signaling/-/signaling-1.3.1.tgz", - "integrity": "sha512-Eq3wVCPQAhUd9+gUGaYygMr+ov7dhSGblSBXiDzpZlSIfa8OVD4P3cCvYXr/acDTNmZ/gHTcSFO8/n3rDkeXzg==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0" - } - } + "@types/chai": "*" } }, - "@loadable/component": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/@loadable/component/-/component-5.12.0.tgz", - "integrity": "sha512-eDG7FPZ8tCFA/mqu2IrYV6eS+UxGBo21PwtEV9QpkpYrx25xKRXzJUm36yfQPK3o7jXu43xpPkwiU4mLWcjJlw==", - "requires": { - "@babel/runtime": "^7.7.7", - "hoist-non-react-statics": "^3.3.1" - }, + "node_modules/@types/chai-as-promised": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.5.tgz", + "integrity": "sha512-jStwss93SITGBwt/niYrkf2C+/1KTeZCZl1LaeezTlqppAKeoQC7jxyqYuP72sxBGKCIbw7oHgbYssIRzT5FCQ==", + "dev": true, "dependencies": { - "@babel/runtime": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.3.tgz", - "integrity": "sha512-fVHx1rzEmwB130VTkLnxR+HmxcTjGzH12LYQcFFoBwakMd3aOMD4OsRN7tGG/UOYE2ektgFrS8uACAoRk1CY0w==", - "requires": { - "regenerator-runtime": "^0.13.2" - } - }, - "hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-wbg3bpgA/ZqWrZuMOeJi8+SKMhr7X9TesL/rXMjTzh0p0JUBo3II8DHboYbuIXWRlttrUFxwcu/5kygrCw8fJw==", - "requires": { - "react-is": "^16.7.0" - } - } + "@types/chai": "*" } }, - "@mapbox/polylabel": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@mapbox/polylabel/-/polylabel-1.0.2.tgz", - "integrity": "sha1-xXFGGbZa3QgmOOoGAn5psUUA76Y=", + "node_modules/@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "node_modules/@types/decompress": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@types/decompress/-/decompress-4.2.5.tgz", + "integrity": "sha512-LdL+kbcKGs9TzvB/K+OBGzPfDoP6gwwTsykYjodlzUJUUYp/43c1p1jE5YTtz3z4Ml90iruvBXbJ6+kDvb3WSQ==", "dev": true, - "requires": { - "tinyqueue": "^1.1.0" + "dependencies": { + "@types/node": "*" } }, - "@npmcli/move-file": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz", - "integrity": "sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==", + "node_modules/@types/download": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@types/download/-/download-8.0.3.tgz", + "integrity": "sha512-IDwXjU7zCtuFVvI0Plnb02TpXyj3RA4YeOKQvEfsjdJeWxZ9hTl6lxeNsU2bLWn0aeAS7fyMl74w/TbdOlS2KQ==", "dev": true, - "requires": { - "mkdirp": "^1.0.4" - }, "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - } + "@types/decompress": "*", + "@types/got": "^9", + "@types/node": "*" } }, - "@nteract/commutable": { - "version": "7.2.9", - "resolved": "https://registry.npmjs.org/@nteract/commutable/-/commutable-7.2.9.tgz", - "integrity": "sha512-QwNp8LO4vq5j1W1ls2ZMpwS4E1YIk92ZmfZLy7QLbNumLrkxP/Q0SWNyxRGfugAsWco+8ORqFf/tY8ePBUCcxg==", - "requires": { - "immutable": "^4.0.0-rc.12", - "uuid": "^7.0.0" - }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, "dependencies": { - "uuid": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", - "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==" - } + "@types/estree": "*", + "@types/json-schema": "*" } }, - "@nteract/markdown": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@nteract/markdown/-/markdown-3.0.1.tgz", - "integrity": "sha512-Dr79niruytzbcDsLLS5NoPIzvVJ3YkJX18O0hmZAmrVn2Ni8L5VW+ZymWsSgRBogDgrYQzhV4L1MmIC/X6rbUg==", + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dev": true, - "requires": { - "@babel/runtime-corejs2": "^7.0.0", - "@nteract/mathjax": "^3.0.1", - "babel-runtime": "^6.26.0", - "react-markdown": "^4.0.0" + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" } }, - "@nteract/mathjax": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@nteract/mathjax/-/mathjax-3.0.1.tgz", - "integrity": "sha512-jL4a2KjY9wzcg7dG3HvufKMXOk+FxWQ6BFH6p3fwJUerbf3SF6H/RlaLxuMgFP+Tv+sHVXo7FHTTTIIsRyuv8g==", + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + }, + "node_modules/@types/fs-extra": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", + "integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==", "dev": true, - "requires": { - "@babel/runtime-corejs2": "^7.0.0", - "babel-runtime": "^6.26.0" + "dependencies": { + "@types/jsonfile": "*", + "@types/node": "*" } }, - "@nteract/messaging": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@nteract/messaging/-/messaging-7.0.4.tgz", - "integrity": "sha512-n0vggzsrRFEtte5SgMVMbgjo19qXa5khDGhvXt4qqmNY3gOSnNSQMpVkXbno6fQXreKOYQ1uO8YIlQTDfOt/Nw==", - "requires": { - "@nteract/types": "^6.0.4", - "@types/uuid": "^7.0.0", - "lodash.clonedeep": "^4.5.0", - "rxjs": "^6.3.3", - "uuid": "^7.0.0" - }, + "node_modules/@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, "dependencies": { - "@types/uuid": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-7.0.2.tgz", - "integrity": "sha512-8Ly3zIPTnT0/8RCU6Kg/G3uTICf9sRwYOpUzSIM3503tLIKcnJPRuinHhXngJUy2MntrEf6dlpOHXJju90Qh5w==" - }, - "uuid": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", - "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==" - } + "@types/minimatch": "*", + "@types/node": "*" } }, - "@nteract/octicons": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@nteract/octicons/-/octicons-0.5.1.tgz", - "integrity": "sha512-7d42YfTbaiL2cF8txcQirDCNTbhmKfSoJSRlFMnEH11D0e0XUk/dxEWRGxxLrbP3SApY84hF/9uP3u1p6jqiAQ==", + "node_modules/@types/got": { + "version": "9.6.12", + "resolved": "https://registry.npmjs.org/@types/got/-/got-9.6.12.tgz", + "integrity": "sha512-X4pj/HGHbXVLqTpKjA2ahI4rV/nNBc9mGO2I/0CgAra+F2dKgMXnENv2SRpemScBzBAI4vMelIVYViQxlSE6xA==", "dev": true, - "requires": { - "@babel/runtime-corejs2": "^7.0.0", - "babel-runtime": "^6.26.0" + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" } }, - "@nteract/plotly": { - "version": "1.48.3", - "resolved": "https://registry.npmjs.org/@nteract/plotly/-/plotly-1.48.3.tgz", - "integrity": "sha512-1Km5MtjyUL9POZjU6LMzH/npFUYIC6vewH0Gd2Ua1FON3qN1KCRoJ8em5Gkp+K57QJRpMET1F4z4N9d3U9HOqA==", - "dev": true - }, - "@nteract/styled-blueprintjsx": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@nteract/styled-blueprintjsx/-/styled-blueprintjsx-1.1.1.tgz", - "integrity": "sha512-y5HLOPqbjapP7918rpp0o6Y6a8ZGZyINFPUQYoOc7iN8KqIOrNvWnhMISgzGwmXv8T+wDZFycSOnPxDbH7tBUA==", + "node_modules/@types/got/node_modules/form-data": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", + "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", "dev": true, - "requires": { - "@babel/runtime-corejs2": "^7.0.0", - "babel-runtime": "^6.26.0", - "styled-jsx": "^3.1.0" + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" } }, - "@nteract/transform-dataresource": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/@nteract/transform-dataresource/-/transform-dataresource-4.5.2.tgz", - "integrity": "sha512-48m/Xd14+vyNGp11qrnts9APKu8kSqZLaI+VJpgErZieakleESaVbleg4EiTlU6j/p7DvihBSKDTuFXuBOpyUA==", + "node_modules/@types/got/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true, - "requires": { - "@babel/runtime": "^7.0.0", - "@babel/runtime-corejs2": "^7.0.0", - "@nteract/octicons": "^0.5.1", - "@nteract/styled-blueprintjsx": "^1.1.1", - "@nteract/transform-plotly": "^3.2.5", - "d3-collection": "^1.0.7", - "d3-scale": "^2.1.2", - "d3-shape": "^1.2.2", - "d3-time-format": "^2.0.5", - "lodash": "^4.17.4", - "moment": "^2.18.1", - "numeral": "^2.0.6", - "react-color": "^2.14.1", - "react-hot-loader": "^4.1.2", - "react-table": "^6.8.6", - "react-table-hoc-fixed-columns": "1.0.2", - "semiotic": "^1.15.6", - "styled-jsx": "^3.1.0", - "tv4": "^1.3.0" - }, - "dependencies": { - "@nteract/transform-plotly": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@nteract/transform-plotly/-/transform-plotly-3.2.5.tgz", - "integrity": "sha512-tQi47Ly1b/YLNJWhUV8gKVkweJb6ugfGg4XylG2wnLVDoaM8ObJrHAtY74S+Kg9e88N8ExNPy9ulG2xhhSrMag==", - "dev": true, - "requires": { - "@babel/runtime-corejs2": "^7.0.0", - "@nteract/plotly": "^1.0.0", - "babel-runtime": "^6.26.0", - "lodash": "^4.17.4" - } + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" } - } + ] + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true }, - "@nteract/transform-geojson": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@nteract/transform-geojson/-/transform-geojson-3.2.5.tgz", - "integrity": "sha512-crP5yFXAFNgE8CQzwcvCbBkbcSdJhn7YruiYreo2gU48fbX8YXOXRMwFsk31jVcMqE9/fYh9zA9sHX38UjKg/w==", + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, + "node_modules/@types/jsonfile": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz", + "integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==", "dev": true, - "requires": { - "@babel/runtime-corejs2": "^7.0.0", - "babel-runtime": "^6.26.0", - "leaflet": "^1.0.3" + "dependencies": { + "@types/node": "*" } }, - "@nteract/transform-model-debug": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@nteract/transform-model-debug/-/transform-model-debug-3.2.5.tgz", - "integrity": "sha512-vmtuPfMuClpFo9j9LLX9EqwEkT8B6jrYUSO30YeG2Ct9G1fPlmvmGBtIJSgHv7Km3W4FlVvwglsenFBg/ff6Og==", + "node_modules/@types/lodash": { + "version": "4.14.181", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.181.tgz", + "integrity": "sha512-n3tyKthHJbkiWhDZs3DkhkCzt2MexYHXlX0td5iMplyfwketaOeKboEVBqzceH7juqvEg3q5oUoBFxSLu7zFag==", + "dev": true + }, + "node_modules/@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, + "node_modules/@types/mocha": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz", + "integrity": "sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==", + "dev": true + }, + "node_modules/@types/node": { + "version": "22.19.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", + "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", "dev": true, - "requires": { - "@babel/runtime-corejs2": "^7.0.0", - "babel-runtime": "^6.26.0" + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" } }, - "@nteract/transform-plotly": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@nteract/transform-plotly/-/transform-plotly-6.0.0.tgz", - "integrity": "sha512-RnKPL/UxY0slrXtsP/7ShqZAgXSQjohOVyQcXBXZzqaC3KSd+esJhHYtYzTK2obaLv4YZwjhlLupcVolslN03A==", + "node_modules/@types/semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==", + "dev": true + }, + "node_modules/@types/shimmer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.0.2.tgz", + "integrity": "sha512-dKkr1bTxbEsFlh2ARpKzcaAmsYixqt9UyCdoEZk8rHyE4iQYcDCyvSjDSf7JUWJHlJiTtbIoQjxKh6ViywqDAg==" + }, + "node_modules/@types/shortid": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz", + "integrity": "sha1-gJPuBBam4r8qpjOBCRFLP7/6Dps=", + "dev": true + }, + "node_modules/@types/sinon": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.3.tgz", + "integrity": "sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==", "dev": true, - "requires": { - "lodash": "^4.17.4", - "plotly.js-dist": "^1.48.3" + "dependencies": { + "@types/sinonjs__fake-timers": "*" } }, - "@nteract/transform-vdom": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@nteract/transform-vdom/-/transform-vdom-2.2.5.tgz", - "integrity": "sha512-q6FbWlrSEWUmQpDV1DBPcw5FZpUcQbKOQ2a59vY/qcQ/Qjh1KUCC+gortso+WIE4P36eHZRxKz5ptCu5i47OLg==", + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", + "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", + "dev": true + }, + "node_modules/@types/stack-trace": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/stack-trace/-/stack-trace-0.0.29.tgz", + "integrity": "sha512-TgfOX+mGY/NyNxJLIbDWrO9DjGoVSW9+aB8H2yy1fy32jsvxijhmyJI9fDFgvz3YP4lvJaq9DzdR/M1bOgVc9g==", + "dev": true + }, + "node_modules/@types/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha1-EHPEvIJHVK49EM+riKsCN7qWTk0=", + "dev": true + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.3.tgz", + "integrity": "sha512-THo502dA5PzG/sfQH+42Lw3fvmYkceefOspdCwpHRul8ik2Jv1K8I5OZz1AT3/rs46kwgMCe9bSBmDLYkkOMGg==", + "dev": true + }, + "node_modules/@types/vscode": { + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.100.0.tgz", + "integrity": "sha512-4uNyvzHoraXEeCamR3+fzcBlh7Afs4Ifjs4epINyUX/jvdk0uzLnwiDY35UKDKnkCHP5Nu3dljl2H8lR6s+rQw==", "dev": true, - "requires": { - "@babel/runtime-corejs2": "^7.0.0", - "babel-runtime": "^6.26.0", - "lodash": "^4.17.4" - } + "license": "MIT" }, - "@nteract/transform-vega": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@nteract/transform-vega/-/transform-vega-6.0.3.tgz", - "integrity": "sha512-sUY9EeCguUfP3G6AI4yg0jUOT2r7eB1VvuQGxjGj0fyhWnYIZR3xv80/aq2J6iACSBxazV+rU2Ne4abWYy6nwQ==", + "node_modules/@types/which": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.1.tgz", + "integrity": "sha512-Jjakcv8Roqtio6w1gr0D7y6twbhx6gGgFGF5BLwajPpnOIOxFkakFhCq+LmyyeAz7BX6ULrjBOxdKaCDy+4+dQ==", + "dev": true + }, + "node_modules/@types/winreg": { + "version": "1.2.31", + "resolved": "https://registry.npmjs.org/@types/winreg/-/winreg-1.2.31.tgz", + "integrity": "sha512-SDatEMEtQ1cJK3esIdH6colduWBP+42Xw9Guq1sf/N6rM3ZxgljBduvZOwBsxRps/k5+Wwf5HJun6pH8OnD2gg==", + "dev": true + }, + "node_modules/@types/xml2js": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.9.tgz", + "integrity": "sha512-CHiCKIihl1pychwR2RNX5mAYmJDACgFVCMT5OArMaO3erzwXVcBqPcusr+Vl8yeeXukxZqtF8mZioqX+mpjjdw==", "dev": true, - "requires": { - "@nteract/vega-embed-v2": "^1.1.0", - "@nteract/vega-embed-v3": "^1.1.1", - "lodash": "^4.17.4", - "vega": "^5.4.0", - "vega-embed": "^4.2.0", - "vega-lite": "^3.3.0" + "dependencies": { + "@types/node": "*" } }, - "@nteract/transforms": { - "version": "4.4.7", - "resolved": "https://registry.npmjs.org/@nteract/transforms/-/transforms-4.4.7.tgz", - "integrity": "sha512-G84BeBfzfdAg3cAV78ES9/yxe2ZNitg7j8MCKKx4iNS9vqeQbO92vyBsBW2D6qlddefU/RB5/HUvvtNUpd0ZRw==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", "dev": true, - "requires": { - "@babel/runtime-corejs2": "^7.0.0", - "@nteract/markdown": "^3.0.1", - "@nteract/mathjax": "^3.0.1", - "@nteract/transform-vdom": "^2.2.5", - "ansi-to-react": "^3.3.5", - "babel-runtime": "^6.26.0", - "react-json-tree": "^0.11.0" + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "@nteract/types": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@nteract/types/-/types-6.0.4.tgz", - "integrity": "sha512-Td7+ql+7XukuHzP2b4qiq7xjD7y6hLJAnXfX4aikjYMzUGXNUAfODqeetKEXDRODFIcwr/jcGewIlWKwedqgLQ==", - "requires": { - "@nteract/commutable": "^7.2.9", - "immutable": "^4.0.0-rc.12", - "rxjs": "^6.3.3", - "uuid": "^7.0.0" - }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dev": true, "dependencies": { - "uuid": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", - "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true } } }, - "@nteract/vega-embed-v2": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@nteract/vega-embed-v2/-/vega-embed-v2-1.1.0.tgz", - "integrity": "sha512-5sCymGk2qzb1BC8P8MTkD0L9rG/8gPb8jyQ2Fz5aqftblgZ+DooHNcR4oi4T49mhRv0zLjqstBwDxx5JmzNdtw==", + "node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, - "requires": { - "d3": "3.5.17", - "d3-cloud": "^1.2.5", - "datalib": "^1.9.1", - "json-stable-stringify": "^1.0.1" + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "@nteract/vega-embed-v3": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@nteract/vega-embed-v3/-/vega-embed-v3-1.1.1.tgz", - "integrity": "sha512-tPMGmYI3aJIu4+HI03Rtub6yjCmACuvtVTjrq8+h2/OIgxBIrzaexo/QCdTKgV4bg0Pd88hskwZ8heqdMFDoHw==", + "node_modules/@typescript-eslint/parser/node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "dev": true, - "requires": { - "d3": "3.5.17", - "d3-cloud": "^1.2.5", - "d3-request": "^1.0.6", - "d3-scale-chromatic": "^1.3.3", - "datalib": "^1.9.1", - "json-stable-stringify": "^1.0.1" + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "@phosphor/algorithm": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.1.3.tgz", - "integrity": "sha512-+dkdYTBglR+qGnLVQdCvYojNZMGxf+xSl1Jeksha3pm7niQktSFz2aR5gEPu/nI5LM8T8slTpqE4Pjvq8P+IVA==" - }, - "@phosphor/application": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@phosphor/application/-/application-1.7.3.tgz", - "integrity": "sha512-ohxrW7rv5Tms4PSyPRZT6YArZQQGQNG4MgTeFzkoLJ+7mp/BcbFuvEoaV1/CUKQArofl0DCkKDOTOIkXP+/32A==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, - "requires": { - "@phosphor/commands": "^1.7.2", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/widgets": "^1.9.3" + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "@phosphor/collections": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/collections/-/collections-1.2.0.tgz", - "integrity": "sha512-T9/0EjSuY6+ga2LIFRZ0xupciOR3Qnyy8Q95lhGTC0FXZUFwC8fl9e8On6IcwasCszS+1n8dtZUWSIynfgdpzw==", - "requires": { - "@phosphor/algorithm": "^1.2.0" + "node_modules/@typescript-eslint/type-utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dev": true, "dependencies": { - "@phosphor/algorithm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz", - "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true } } }, - "@phosphor/commands": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@phosphor/commands/-/commands-1.7.2.tgz", - "integrity": "sha512-iSyBIWMHsus323BVEARBhuVZNnVel8USo+FIPaAxGcq+icTSSe6+NtSxVQSmZblGN6Qm4iw6I6VtiSx0e6YDgQ==", - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.1", - "@phosphor/domutils": "^1.1.4", - "@phosphor/keyboard": "^1.1.3", - "@phosphor/signaling": "^1.3.1" + "node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, "dependencies": { - "@phosphor/algorithm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz", - "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==" - }, - "@phosphor/disposable": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/disposable/-/disposable-1.3.1.tgz", - "integrity": "sha512-0NGzoTXTOizWizK/brKKd5EjJhuuEH4903tLika7q6wl/u0tgneJlTh7R+MBVeih0iNxtuJAfBa3IEY6Qmj+Sw==", - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/signaling": "^1.3.1" - } - }, - "@phosphor/signaling": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/signaling/-/signaling-1.3.1.tgz", - "integrity": "sha512-Eq3wVCPQAhUd9+gUGaYygMr+ov7dhSGblSBXiDzpZlSIfa8OVD4P3cCvYXr/acDTNmZ/gHTcSFO8/n3rDkeXzg==", - "requires": { - "@phosphor/algorithm": "^1.2.0" - } + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true } } }, - "@phosphor/coreutils": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/coreutils/-/coreutils-1.3.1.tgz", - "integrity": "sha512-9OHCn8LYRcPU/sbHm5v7viCA16Uev3gbdkwqoQqlV+EiauDHl70jmeL7XVDXdigl66Dz0LI11C99XOxp+s3zOA==" - }, - "@phosphor/disposable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/disposable/-/disposable-1.2.0.tgz", - "integrity": "sha512-4PoWoffdrLyWOW5Qv7I8//owvZmv57YhaxetAMWeJl13ThXc901RprL0Gxhtue2ZxL2PtUjM1207HndKo2FVjA==", - "requires": { - "@phosphor/algorithm": "^1.1.3", - "@phosphor/signaling": "^1.2.3" + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" } }, - "@phosphor/domutils": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@phosphor/domutils/-/domutils-1.1.4.tgz", - "integrity": "sha512-ivwq5TWjQpKcHKXO8PrMl+/cKqbgxPClPiCKc1gwbMd+6hnW5VLwNG0WBzJTxCzXK43HxX18oH+tOZ3E04wc3w==" - }, - "@phosphor/dragdrop": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@phosphor/dragdrop/-/dragdrop-1.4.1.tgz", - "integrity": "sha512-77paMoubIWk7pdwA2GVFkqba1WP48hTZZvS17N30+KVOeWfSqBL3flPSnW2yC4y6FnOP2PFOCtuPIbQv+pYhCA==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "dev": true, - "requires": { - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.1" - }, "dependencies": { - "@phosphor/algorithm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz", - "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==", - "dev": true - }, - "@phosphor/disposable": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/disposable/-/disposable-1.3.1.tgz", - "integrity": "sha512-0NGzoTXTOizWizK/brKKd5EjJhuuEH4903tLika7q6wl/u0tgneJlTh7R+MBVeih0iNxtuJAfBa3IEY6Qmj+Sw==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/signaling": "^1.3.1" - } - }, - "@phosphor/signaling": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/signaling/-/signaling-1.3.1.tgz", - "integrity": "sha512-Eq3wVCPQAhUd9+gUGaYygMr+ov7dhSGblSBXiDzpZlSIfa8OVD4P3cCvYXr/acDTNmZ/gHTcSFO8/n3rDkeXzg==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0" - } + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true } } }, - "@phosphor/keyboard": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@phosphor/keyboard/-/keyboard-1.1.3.tgz", - "integrity": "sha512-dzxC/PyHiD6mXaESRy6PZTd9JeK+diwG1pyngkyUf127IXOEzubTIbu52VSdpGBklszu33ws05BAGDa4oBE4mQ==" - }, - "@phosphor/messaging": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@phosphor/messaging/-/messaging-1.3.0.tgz", - "integrity": "sha512-k0JE+BTMKlkM335S2AmmJxoYYNRwOdW5jKBqLgjJdGRvUQkM0+2i60ahM45+J23atGJDv9esKUUBINiKHFhLew==", - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/collections": "^1.2.0" + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "dev": true, "dependencies": { - "@phosphor/algorithm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz", - "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==" - } + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" } }, - "@phosphor/properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@phosphor/properties/-/properties-1.1.3.tgz", - "integrity": "sha512-GiglqzU77s6+tFVt6zPq9uuyu/PLQPFcqZt914ZhJ4cN/7yNI/SLyMzpYZ56IRMXvzK9TUgbRna6URE3XAwFUg==" + "node_modules/@typescript-eslint/utils/node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true }, - "@phosphor/signaling": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@phosphor/signaling/-/signaling-1.2.3.tgz", - "integrity": "sha512-DMwS0m9OgfY5ljpTsklRQPUQpTyg4obz85FyImRDacUVxUVbas95djIDEbU4s1TMzdHBBO+gfki3V4giXUvXzw==", - "requires": { - "@phosphor/algorithm": "^1.1.3" + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "@phosphor/virtualdom": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/virtualdom/-/virtualdom-1.2.0.tgz", - "integrity": "sha512-L9mKNhK2XtVjzjuHLG2uYuepSz8uPyu6vhF4EgCP0rt0TiLYaZeHwuNu3XeFbul9DMOn49eBpye/tfQVd4Ks+w==", + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0" - }, + "license": "ISC" + }, + "node_modules/@vscode/extension-telemetry": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.8.4.tgz", + "integrity": "sha512-UqM9+KZDDK3MyoHTsg6XNM+XO6pweQxzCpqJz33BoBEYAGsbBviRYcVpJglgay2oReuDD2pOI1Nio3BKNDLhWA==", "dependencies": { - "@phosphor/algorithm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz", - "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==", - "dev": true - } + "@microsoft/1ds-core-js": "^3.2.13", + "@microsoft/1ds-post-js": "^3.2.13", + "@microsoft/applicationinsights-web-basic": "^3.0.2", + "applicationinsights": "^2.7.1" + }, + "engines": { + "vscode": "^1.75.0" } }, - "@phosphor/widgets": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@phosphor/widgets/-/widgets-1.9.3.tgz", - "integrity": "sha512-61jsxloDrW/+WWQs8wOgsS5waQ/MSsXBuhONt0o6mtdeL93HVz7CYO5krOoot5owammfF6oX1z0sDaUYIYgcPA==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/commands": "^1.7.2", - "@phosphor/coreutils": "^1.3.1", - "@phosphor/disposable": "^1.3.1", - "@phosphor/domutils": "^1.1.4", - "@phosphor/dragdrop": "^1.4.1", - "@phosphor/keyboard": "^1.1.3", - "@phosphor/messaging": "^1.3.0", - "@phosphor/properties": "^1.1.3", - "@phosphor/signaling": "^1.3.1", - "@phosphor/virtualdom": "^1.2.0" - }, - "dependencies": { - "@phosphor/algorithm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@phosphor/algorithm/-/algorithm-1.2.0.tgz", - "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==", - "dev": true - }, - "@phosphor/disposable": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/disposable/-/disposable-1.3.1.tgz", - "integrity": "sha512-0NGzoTXTOizWizK/brKKd5EjJhuuEH4903tLika7q6wl/u0tgneJlTh7R+MBVeih0iNxtuJAfBa3IEY6Qmj+Sw==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0", - "@phosphor/signaling": "^1.3.1" - } - }, - "@phosphor/signaling": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@phosphor/signaling/-/signaling-1.3.1.tgz", - "integrity": "sha512-Eq3wVCPQAhUd9+gUGaYygMr+ov7dhSGblSBXiDzpZlSIfa8OVD4P3cCvYXr/acDTNmZ/gHTcSFO8/n3rDkeXzg==", - "dev": true, - "requires": { - "@phosphor/algorithm": "^1.2.0" - } - } + "node_modules/@vscode/test-electron": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.3.8.tgz", + "integrity": "sha512-b4aZZsBKtMGdDljAsOPObnAi7+VWIaYl3ylCz1jTs+oV6BZ4TNHcVNC3xUn0azPeszBmwSBDQYfFESIaUQnrOg==", + "dev": true, + "dependencies": { + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "jszip": "^3.10.1", + "semver": "^7.5.2" + }, + "engines": { + "node": ">=16" } }, - "@sheerun/mutationobserver-shim": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz", - "integrity": "sha512-vTCdPp/T/Q3oSqwHmZ5Kpa9oI7iLtGl3RQaA/NyLHikvcrPxACkkKVr/XzkSPJWXHRhKGzVvb0urJsbMlRxi1Q==", - "dev": true - }, - "@sindresorhus/df": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/df/-/df-2.1.0.tgz", - "integrity": "sha1-0gjPJ+BvC7R20U197M19cm6ao4k=", + "node_modules/@vscode/vsce": { + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.27.0.tgz", + "integrity": "sha512-FFUMBVSyyjjJpWszwqk7d4U3YllY8FdWslbUDMRki1x4ZjA3Z0hmRMfypWrjP9sptbSR9nyPFU4uqjhy2qRB/w==", "dev": true, - "requires": { - "execa": "^0.2.2" - }, "dependencies": { - "execa": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.2.2.tgz", - "integrity": "sha1-4urUcsLDGq1vc/GslW7vReEjIMs=", - "dev": true, - "requires": { - "cross-spawn-async": "^2.1.1", - "npm-run-path": "^1.0.0", - "object-assign": "^4.0.1", - "path-key": "^1.0.0", - "strip-eof": "^1.0.0" - } - }, - "npm-run-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-1.0.0.tgz", - "integrity": "sha1-9cMr9ZX+ga6Sfa7FLoL4sACsPI8=", - "dev": true, - "requires": { - "path-key": "^1.0.0" - } - }, - "path-key": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-1.0.0.tgz", - "integrity": "sha1-XVPVeAGWRsDWiADbThRua9wqx68=", - "dev": true - } + "@azure/identity": "^4.1.0", + "@vscode/vsce-sign": "^2.0.0", + "azure-devops-node-api": "^12.5.0", + "chalk": "^2.4.2", + "cheerio": "^1.0.0-rc.9", + "cockatiel": "^3.1.2", + "commander": "^6.2.1", + "form-data": "^4.0.0", + "glob": "^7.0.6", + "hosted-git-info": "^4.0.2", + "jsonc-parser": "^3.2.0", + "leven": "^3.1.0", + "markdown-it": "^12.3.2", + "mime": "^1.3.4", + "minimatch": "^3.0.3", + "parse-semver": "^1.1.1", + "read": "^1.0.7", + "semver": "^7.5.2", + "tmp": "^0.2.1", + "typed-rest-client": "^1.8.4", + "url-join": "^4.0.1", + "xml2js": "^0.5.0", + "yauzl": "^2.3.1", + "yazl": "^2.2.2" + }, + "bin": { + "vsce": "vsce" + }, + "engines": { + "node": ">= 16" + }, + "optionalDependencies": { + "keytar": "^7.7.0" } }, - "@sindresorhus/is": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", - "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==", - "dev": true + "node_modules/@vscode/vsce-sign": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign/-/vsce-sign-2.0.4.tgz", + "integrity": "sha512-0uL32egStKYfy60IqnynAChMTbL0oqpqk0Ew0YHiIb+fayuGZWADuIPHWUcY1GCnAA+VgchOPDMxnc2R3XGWEA==", + "dev": true, + "hasInstallScript": true, + "optionalDependencies": { + "@vscode/vsce-sign-alpine-arm64": "2.0.2", + "@vscode/vsce-sign-alpine-x64": "2.0.2", + "@vscode/vsce-sign-darwin-arm64": "2.0.2", + "@vscode/vsce-sign-darwin-x64": "2.0.2", + "@vscode/vsce-sign-linux-arm": "2.0.2", + "@vscode/vsce-sign-linux-arm64": "2.0.2", + "@vscode/vsce-sign-linux-x64": "2.0.2", + "@vscode/vsce-sign-win32-arm64": "2.0.2", + "@vscode/vsce-sign-win32-x64": "2.0.2" + } + }, + "node_modules/@vscode/vsce-sign-alpine-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-arm64/-/vsce-sign-alpine-arm64-2.0.2.tgz", + "integrity": "sha512-E80YvqhtZCLUv3YAf9+tIbbqoinWLCO/B3j03yQPbjT3ZIHCliKZlsy1peNc4XNZ5uIb87Jn0HWx/ZbPXviuAQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "alpine" + ] }, - "@sinonjs/commons": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.7.0.tgz", - "integrity": "sha512-qbk9AP+cZUsKdW1GJsBpxPKFmCJ0T8swwzVje3qFd+AkQb74Q/tiuzrdfFg8AD2g5HH/XbE/I8Uc1KYHVYWfhg==", + "node_modules/@vscode/vsce-sign-alpine-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-x64/-/vsce-sign-alpine-x64-2.0.2.tgz", + "integrity": "sha512-n1WC15MSMvTaeJ5KjWCzo0nzjydwxLyoHiMJHu1Ov0VWTZiddasmOQHekA47tFRycnt4FsQrlkSCTdgHppn6bw==", + "cpu": [ + "x64" + ], "dev": true, - "requires": { - "type-detect": "4.0.8" - } + "optional": true, + "os": [ + "alpine" + ] }, - "@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "node_modules/@vscode/vsce-sign-darwin-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-arm64/-/vsce-sign-darwin-arm64-2.0.2.tgz", + "integrity": "sha512-rz8F4pMcxPj8fjKAJIfkUT8ycG9CjIp888VY/6pq6cuI2qEzQ0+b5p3xb74CJnBbSC0p2eRVoe+WgNCAxCLtzQ==", + "cpu": [ + "arm64" + ], "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } + "optional": true, + "os": [ + "darwin" + ] }, - "@sinonjs/formatio": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-4.0.1.tgz", - "integrity": "sha512-asIdlLFrla/WZybhm0C8eEzaDNNrzymiTqHMeJl6zPW2881l3uuVRpm0QlRQEjqYWv6CcKMGYME3LbrLJsORBw==", + "node_modules/@vscode/vsce-sign-darwin-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-x64/-/vsce-sign-darwin-x64-2.0.2.tgz", + "integrity": "sha512-MCjPrQ5MY/QVoZ6n0D92jcRb7eYvxAujG/AH2yM6lI0BspvJQxp0o9s5oiAM9r32r9tkLpiy5s2icsbwefAQIw==", + "cpu": [ + "x64" + ], "dev": true, - "requires": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^4.2.0" - } + "optional": true, + "os": [ + "darwin" + ] }, - "@sinonjs/samsam": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-4.2.0.tgz", - "integrity": "sha512-yG7QbUz38ZPIegfuSMEcbOo0kkLGmPa8a0Qlz4dk7+cXYALDScWjIZzAm/u2+Frh+bcdZF6wZJZwwuJjY0WAjA==", + "node_modules/@vscode/vsce-sign-linux-arm": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm/-/vsce-sign-linux-arm-2.0.2.tgz", + "integrity": "sha512-Fkb5jpbfhZKVw3xwR6t7WYfwKZktVGNXdg1m08uEx1anO0oUPUkoQRsNm4QniL3hmfw0ijg00YA6TrxCRkPVOQ==", + "cpu": [ + "arm" + ], "dev": true, - "requires": { - "@sinonjs/commons": "^1.6.0", - "array-from": "^2.1.1", - "lodash.get": "^4.4.2" - } + "optional": true, + "os": [ + "linux" + ] }, - "@sinonjs/text-encoding": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", - "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", - "dev": true + "node_modules/@vscode/vsce-sign-linux-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm64/-/vsce-sign-linux-arm64-2.0.2.tgz", + "integrity": "sha512-Ybeu7cA6+/koxszsORXX0OJk9N0GgfHq70Wqi4vv2iJCZvBrOWwcIrxKjvFtwyDgdeQzgPheH5nhLVl5eQy7WA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] }, - "@stroncium/procfs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@stroncium/procfs/-/procfs-1.1.1.tgz", - "integrity": "sha512-c8A7hTAu1FgWJTfOQNqp0N9K/9amlVktNRQidzeeX4dTJpSNl2Y65s9BSfnMlFFGMGP+rBvrb0WTTv7ad6MJ9A==", - "dev": true + "node_modules/@vscode/vsce-sign-linux-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-x64/-/vsce-sign-linux-x64-2.0.2.tgz", + "integrity": "sha512-NsPPFVtLaTlVJKOiTnO8Cl78LZNWy0Q8iAg+LlBiCDEgC12Gt4WXOSs2pmcIjDYzj2kY4NwdeN1mBTaujYZaPg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] }, - "@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "requires": { - "defer-to-connect": "^1.0.1" + "node_modules/@vscode/vsce-sign-win32-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-arm64/-/vsce-sign-win32-arm64-2.0.2.tgz", + "integrity": "sha512-wPs848ymZ3Ny+Y1Qlyi7mcT6VSigG89FWQnp2qRYCyMhdJxOpA4lDwxzlpL8fG6xC8GjQjGDkwbkWUcCobvksQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vscode/vsce-sign-win32-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-x64/-/vsce-sign-win32-x64-2.0.2.tgz", + "integrity": "sha512-pAiRN6qSAhDM5SVOIxgx+2xnoVUePHbRNC7OD2aOR3WltTKxxF25OfpK8h8UQ7A0BuRkSgREbB59DBlFk4iAeg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vscode/vsce/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "engines": { + "node": ">= 6" } }, - "@testing-library/dom": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-6.11.0.tgz", - "integrity": "sha512-Pkx9LMIGshyNbfmecjt18rrAp/ayMqGH674jYER0SXj0iG9xZc+zWRjk2Pg9JgPBDvwI//xGrI/oOQkAi4YEew==", + "node_modules/@vscode/vsce/node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", "dev": true, - "requires": { - "@babel/runtime": "^7.6.2", - "@sheerun/mutationobserver-shim": "^0.3.2", - "@types/testing-library__dom": "^6.0.0", - "aria-query": "3.0.0", - "pretty-format": "^24.9.0", - "wait-for-expect": "^3.0.0" - }, "dependencies": { - "@babel/runtime": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.3.tgz", - "integrity": "sha512-fVHx1rzEmwB130VTkLnxR+HmxcTjGzH12LYQcFFoBwakMd3aOMD4OsRN7tGG/UOYE2ektgFrS8uACAoRk1CY0w==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.2" - } - } + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" } }, - "@testing-library/react": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-9.4.0.tgz", - "integrity": "sha512-XdhDWkI4GktUPsz0AYyeQ8M9qS/JFie06kcSnUVcpgOwFjAu9vhwR83qBl+lw9yZWkbECjL8Hd+n5hH6C0oWqg==", + "node_modules/@vscode/vsce/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "requires": { - "@babel/runtime": "^7.7.6", - "@testing-library/dom": "^6.11.0", - "@types/testing-library__react": "^9.1.2" + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" }, + "engines": { + "node": "*" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, "dependencies": { - "@babel/runtime": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.3.tgz", - "integrity": "sha512-fVHx1rzEmwB130VTkLnxR+HmxcTjGzH12LYQcFFoBwakMd3aOMD4OsRN7tGG/UOYE2ektgFrS8uACAoRk1CY0w==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.2" - } - } + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, - "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", "dev": true }, - "@types/ansi-regex": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/ansi-regex/-/ansi-regex-4.0.0.tgz", - "integrity": "sha512-r1W316vjsZXn1/csLC4HcCJs6jIHIzksHJd7xx+Dl+PAb0S2Dh9cR8ZsIMEfGmbBtP7JNWlf2KKahSkDP6rg3g==", + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", "dev": true }, - "@types/anymatch": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", - "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", "dev": true }, - "@types/backbone": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@types/backbone/-/backbone-1.4.1.tgz", - "integrity": "sha512-KYfGuQy4d2vvYXbn0uHFZ6brFLndatTMomxBlljpbWf4kFpA3BG/6LA3ec+J9iredrX6eAVI7sm9SVAvwiIM6g==", + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dev": true, - "requires": { - "@types/jquery": "*", - "@types/underscore": "*" + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, - "@types/body-parser": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.1.tgz", - "integrity": "sha512-RoX2EZjMiFMjZh9lmYrwgoP9RTpAjSHiJxdp4oidAQVO02T7HER3xj9UKue5534ULWeqVEkujhWcyvUce+d68w==", + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dev": true, - "requires": { - "@types/connect": "*", - "@types/node": "*" + "dependencies": { + "@xtuc/ieee754": "^1.2.0" } }, - "@types/caseless": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", - "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==", - "dev": true + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } }, - "@types/chai": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz", - "integrity": "sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==", + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", "dev": true }, - "@types/chai-arrays": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/chai-arrays/-/chai-arrays-1.0.2.tgz", - "integrity": "sha512-/kgYvj5Pwiv/bOlJ6c5GlRF/W6lUGSLrpQGl/7Gg6w7tvBYcf0iF91+wwyuwDYGO2zM0wNpcoPixZVif8I/r6g==", + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dev": true, - "requires": { - "@types/chai": "*" + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, - "@types/chai-as-promised": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.0.tgz", - "integrity": "sha512-MFiW54UOSt+f2bRw8J7LgQeIvE/9b4oGvwU7XW30S9QGAiHGnU/fmiOprsyMkdmH2rl8xSPc0/yrQw8juXU6bQ==", + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "dev": true, - "requires": { - "@types/chai": "*" + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, - "@types/cheerio": { - "version": "0.22.12", - "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.12.tgz", - "integrity": "sha512-aczowyAJNfrkBV+HS8DyAA87OnvkqGrrOmm5s7V6Jbgimzv/1ZoAy91cLJX8GQrUS60KufD7EIzA2LbK8HV4hg==", + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dev": true, - "requires": { - "@types/node": "*" + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, - "@types/clean-css": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@types/clean-css/-/clean-css-4.2.1.tgz", - "integrity": "sha512-A1HQhQ0hkvqqByJMgg+Wiv9p9XdoYEzuwm11SVo1mX2/4PSdhjcrUlilJQoqLscIheC51t1D5g+EFWCXZ2VTQQ==", + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dev": true, - "requires": { - "@types/node": "*" + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, - "@types/clone": { - "version": "0.1.30", - "resolved": "https://registry.npmjs.org/@types/clone/-/clone-0.1.30.tgz", - "integrity": "sha1-5zZWSMG0ITalnH1QQGN7O1yDthQ=", - "dev": true + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } }, - "@types/color-name": { + "node_modules/@webpack-cli/configtest": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true - }, - "@types/concat-stream": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.0.tgz", - "integrity": "sha1-OU2+C7X+5Gs42JZzXoto7yOQ0A0=", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.1.tgz", + "integrity": "sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg==", "dev": true, - "requires": { - "@types/node": "*" + "peerDependencies": { + "webpack": "4.x.x || 5.x.x", + "webpack-cli": "4.x.x" } }, - "@types/connect": { - "version": "3.4.32", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz", - "integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==", + "node_modules/@webpack-cli/info": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.1.tgz", + "integrity": "sha512-PKVGmazEq3oAo46Q63tpMr4HipI3OPfP7LiNOEJg963RMgT0rqheag28NCML0o3GIzA3DmxP1ZIAv9oTX1CUIA==", "dev": true, - "requires": { - "@types/node": "*" + "dependencies": { + "envinfo": "^7.7.3" + }, + "peerDependencies": { + "webpack-cli": "4.x.x" } }, - "@types/cookiejar": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.1.tgz", - "integrity": "sha512-aRnpPa7ysx3aNW60hTiCtLHlQaIFsXFCgQlpakNgDNVFzbtusSY8PwjAQgRWfSk0ekNoBjO51eQRB6upA9uuyw==", - "dev": true - }, - "@types/copy-webpack-plugin": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@types/copy-webpack-plugin/-/copy-webpack-plugin-4.4.4.tgz", - "integrity": "sha512-D8NyCMfHFWi638Cn5gi88mELGrISQdzDAcmOk4j70bzm0kLey99Zp5xsFsL44qPi+a22tcB39U5jiJiV2XMd9A==", + "node_modules/@webpack-cli/serve": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.1.tgz", + "integrity": "sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw==", "dev": true, - "requires": { - "@types/minimatch": "*", - "@types/node": "*", - "@types/webpack": "*" + "peerDependencies": { + "webpack-cli": "4.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } } }, - "@types/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-invOmosX0DqbpA+cE2yoHGUlF/blyf7nB0OGYBBiH27crcVm5NmFaZkLP4Ta1hGaesckCi5lVLlydNJCxkTOSg==", - "dev": true, - "requires": { - "@types/express": "*" - } + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true }, - "@types/debug": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", - "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==", + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, - "@types/decompress": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@types/decompress/-/decompress-4.2.3.tgz", - "integrity": "sha512-W24e3Ycz1UZPgr1ZEDHlK4XnvOr+CpJH3qNsFeqXwwlW/9END9gxn3oJSsp7gYdiQxrXUHwUUd3xuzVz37MrZQ==", - "dev": true, - "requires": { - "@types/node": "*" + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" } }, - "@types/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@types/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-EGlKlgMhnLt/cM4DbUSafFdrkeJoC9Mvnj0PUCU7tFmTjMjNRT957kXCx0wYm3JuEq4o4ZsS5vG+NlkM2DMd2A==", - "dev": true + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "peerDependencies": { + "acorn": "^8" + } }, - "@types/del": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/del/-/del-3.0.1.tgz", - "integrity": "sha512-y6qRq6raBuu965clKgx6FHuiPu3oHdtmzMPXi8Uahsjdq1L6DL5fS/aY5/s71YwM7k6K1QIWvem5vNwlnNGIkQ==", + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "dev": true, - "requires": { - "@types/glob": "*" + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" } }, - "@types/diff-match-patch": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.32.tgz", - "integrity": "sha512-bPYT5ECFiblzsVzyURaNhljBH2Gh1t9LowgUwciMrNAhFewLkHT2H0Mto07Y4/3KCOGZHRQll3CTtQZ0X11D/A==", - "dev": true - }, - "@types/dom4": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/dom4/-/dom4-2.0.1.tgz", - "integrity": "sha512-kSkVAvWmMZiCYtvqjqQEwOmvKwcH+V4uiv3qPQ8pAh1Xl39xggGEo8gHUqV4waYGHezdFw0rKBR8Jt0CrQSDZA==", - "dev": true - }, - "@types/download": { - "version": "6.2.4", - "resolved": "https://registry.npmjs.org/@types/download/-/download-6.2.4.tgz", - "integrity": "sha512-Lo5dy3ai6LNnbL663sgdzqL1eib11u1yKH6w3v3IXEOO4kRfQpMn1qWUTaumcHLACjFp1RcBx9tUXEvJoR3vcA==", + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, - "requires": { - "@types/decompress": "*", - "@types/got": "^8", - "@types/node": "*" + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "@types/enzyme": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.10.2.tgz", - "integrity": "sha512-tAFqfBVAxaxOCCcCpFrv6yFDg/hwL1KtJc7dsOaO+BBdSppTcyRP7jO2ze5va7NDa8iSFfAaZ9NeuybmMulu0w==", + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true, - "requires": { - "@types/cheerio": "*", - "@types/react": "*" + "engines": { + "node": ">=0.4.0" } }, - "@types/enzyme-adapter-react-16": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.0.5.tgz", - "integrity": "sha512-K7HLFTkBDN5RyRmU90JuYt8OWEY2iKUn43SDWEoBOXd/PowUWjLZ3Q6qMBiQuZeFYK/TOstaZxsnI0fXoAfLpg==", - "dev": true, - "requires": { - "@types/enzyme": "*" + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" } }, - "@types/eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", - "dev": true + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } }, - "@types/event-stream": { - "version": "3.3.34", - "resolved": "https://registry.npmjs.org/@types/event-stream/-/event-stream-3.3.34.tgz", - "integrity": "sha512-LLiivgWKii4JeMzFy3trrxqkRrVSdue8WmbXyHuSJLwNrhIQU5MTrc65jhxEPwMyh5HR1xevSdD+k2nnSRKw9g==", + "node_modules/aggregate-error": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", + "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", "dev": true, - "requires": { - "@types/node": "*" + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "@types/events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", - "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", - "dev": true + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } }, - "@types/express": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.2.tgz", - "integrity": "sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==", + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, - "requires": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "*", - "@types/serve-static": "*" + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "@types/express-serve-static-core": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.0.tgz", - "integrity": "sha512-Xnub7w57uvcBqFdIGoRg1KhNOeEj0vB6ykUM7uFWyxvbdE89GFyqgmUcanAriMr4YOxNFZBAWkfcWIb4WBPt3g==", + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, - "requires": { - "@types/node": "*", - "@types/range-parser": "*" + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "@types/fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha512-mky/O83TXmGY39P1H9YbUpjV6l6voRYlufqfFCvel8l1phuy8HRjdWc1rrPuN53ITBJlbyMSV6z3niOySO5pgQ==", + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, - "@types/form-data": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz", - "integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==", + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true, - "requires": { - "@types/node": "*" + "peerDependencies": { + "ajv": "^6.9.1" } }, - "@types/fs-extra": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.1.0.tgz", - "integrity": "sha512-AInn5+UBFIK9FK5xc9yP5e3TQSPNNgjHByqYcj9g5elVBnDQcQL7PlO1CIRy2gWlbwK7UPYqi7vRvFA44dCmYQ==", + "node_modules/ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", "dev": true, - "requires": { - "@types/node": "*" + "dependencies": { + "ansi-wrap": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "@types/get-port": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@types/get-port/-/get-port-3.2.0.tgz", - "integrity": "sha512-TiNg8R1kjDde5Pub9F9vCwZA/BNW9HeXP5b9j7Qucqncy/McfPZ6xze/EyBdXS5FhMIGN6Fx3vg75l5KHy3V1Q==", - "dev": true - }, - "@types/glob": { - "version": "5.0.36", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-5.0.36.tgz", - "integrity": "sha512-KEzSKuP2+3oOjYYjujue6Z3Yqis5HKA1BsIC+jZ1v3lrRNdsqyNNtX0rQf6LSuI4DJJ2z5UV//zBZCcvM0xikg==", + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "requires": { - "@types/events": "*", - "@types/minimatch": "*", - "@types/node": "*" + "engines": { + "node": ">=8" } }, - "@types/got": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/@types/got/-/got-8.3.5.tgz", - "integrity": "sha512-AaXSrIF99SjjtPVNmCmYb388HML+PKEJb/xmj4SbL2ZO0hHuETZZzyDIKfOqaEoAHZEuX4sC+FRFrHYJoIby6A==", + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, - "requires": { - "@types/node": "*" + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" } }, - "@types/hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "node_modules/ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", "dev": true, - "requires": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" + "engines": { + "node": ">=0.10.0" } }, - "@types/html-minifier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/@types/html-minifier/-/html-minifier-3.5.3.tgz", - "integrity": "sha512-j1P/4PcWVVCPEy5lofcHnQ6BtXz9tHGiFPWzqm7TtGuWZEfCHEP446HlkSNc9fQgNJaJZ6ewPtp2aaFla/Uerg==", + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, - "requires": { - "@types/clean-css": "*", - "@types/relateurl": "*", - "@types/uglify-js": "*" + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" } }, - "@types/html-webpack-plugin": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@types/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", - "integrity": "sha512-in9rViBsTRB4ZApndZ12It68nGzSMHVK30JD7c49iLIHMFeTPbP7I7wevzMv7re2o0k5TlU6Ry/beyrmgWX7Bg==", + "node_modules/append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", "dev": true, - "requires": { - "@types/html-minifier": "*", - "@types/tapable": "*", - "@types/webpack": "*" + "dependencies": { + "buffer-equal": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "@types/iconv-lite": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/@types/iconv-lite/-/iconv-lite-0.0.1.tgz", - "integrity": "sha1-qjuL2ivlErGuCgV7lC6GnDcKVWk=", + "node_modules/append-buffer/node_modules/buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", "dev": true, - "requires": { - "@types/node": "*" + "engines": { + "node": ">=0.4.0" } }, - "@types/istanbul-lib-coverage": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", - "integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz", - "integrity": "sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg==", + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" + "dependencies": { + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "@types/istanbul-reports": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz", - "integrity": "sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*", - "@types/istanbul-lib-report": "*" + "node_modules/applicationinsights": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-2.7.3.tgz", + "integrity": "sha512-JY8+kTEkjbA+kAVNWDtpfW2lqsrDALfDXuxOs74KLPu2y13fy/9WB52V4LfYVTVcW1/jYOXjTxNS2gPZIDh1iw==", + "dependencies": { + "@azure/core-auth": "^1.5.0", + "@azure/core-rest-pipeline": "1.10.1", + "@azure/core-util": "1.2.0", + "@azure/opentelemetry-instrumentation-azure-sdk": "^1.0.0-beta.5", + "@microsoft/applicationinsights-web-snippet": "^1.0.1", + "@opentelemetry/api": "^1.4.1", + "@opentelemetry/core": "^1.15.2", + "@opentelemetry/sdk-trace-base": "^1.15.2", + "@opentelemetry/semantic-conventions": "^1.15.2", + "cls-hooked": "^4.2.2", + "continuation-local-storage": "^3.2.1", + "diagnostic-channel": "1.1.1", + "diagnostic-channel-publishers": "1.0.7" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "applicationinsights-native-metrics": "*" + }, + "peerDependenciesMeta": { + "applicationinsights-native-metrics": { + "optional": true + } } }, - "@types/jquery": { - "version": "1.10.35", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-1.10.35.tgz", - "integrity": "sha512-SVtqEcudm7yjkTwoRA1gC6CNMhGDdMx4Pg8BPdiqI7bXXdCn1BPmtxgeWYQOgDxrq53/5YTlhq5ULxBEAlWIBg==", - "dev": true + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, - "@types/jsdom": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-11.12.0.tgz", - "integrity": "sha512-XHMNZFQ0Ih3A4/NTWAO15+OsQafPKnQCanN0FYGbsTM/EoI5EoEAvvkF51/DQC2BT5low4tomp7k2RLMlriA5Q==", + "node_modules/archive-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/archive-type/-/archive-type-4.0.0.tgz", + "integrity": "sha512-zV4Ky0v1F8dBrdYElwTvQhweQ0P7Kwc1aluqJsYtOBP01jXcWCyW2IEfI1YiqsG+Iy7ZR+o5LF1N+PGECBxHWA==", "dev": true, - "requires": { - "@types/events": "*", - "@types/node": "*", - "@types/tough-cookie": "*", - "parse5": "^4.0.0" + "license": "MIT", + "dependencies": { + "file-type": "^4.2.0" + }, + "engines": { + "node": ">=4" } }, - "@types/json-schema": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz", - "integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==", + "node_modules/archive-type/node_modules/file-type": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-4.4.0.tgz", + "integrity": "sha512-f2UbFQEk7LXgWpi5ntcO86OeA/cC80fuDDDaX/fZ2ZGel+AF7leRQqBBW1eJNiiQkrZlAoM6P+VYP5P6bOlDEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", "dev": true }, - "@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "node_modules/arg": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz", + "integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==", "dev": true }, - "@types/loadable__component": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@types/loadable__component/-/loadable__component-5.10.0.tgz", - "integrity": "sha512-AaDP1VxV3p7CdPOtOTl3ALgQ6ES4AxJKO9UGj9vJonq/w2yERxwdzFiWNQFh9fEDXEzjxujBlM2RmSJtHV1/pA==", + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, - "requires": { - "@types/react": "*" + "dependencies": { + "sprintf-js": "~1.0.2" } }, - "@types/loader-utils": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@types/loader-utils/-/loader-utils-1.1.3.tgz", - "integrity": "sha512-euKGFr2oCB3ASBwG39CYJMR3N9T0nanVqXdiH7Zu/Nqddt6SmFRxytq/i2w9LQYNQekEtGBz+pE3qG6fQTNvRg==", + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", "dev": true, - "requires": { - "@types/node": "*", - "@types/webpack": "*" + "engines": { + "node": ">=0.10.0" } }, - "@types/lodash": { - "version": "4.14.136", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.136.tgz", - "integrity": "sha512-0GJhzBdvsW2RUccNHOBkabI8HZVdOXmXbXhuKlDEd5Vv12P7oAVGfomGp3Ne21o5D/qu1WmthlNKFaoZJJeErA==", - "dev": true - }, - "@types/md5": { - "version": "2.1.33", - "resolved": "https://registry.npmjs.org/@types/md5/-/md5-2.1.33.tgz", - "integrity": "sha512-8+X960EtKLoSblhauxLKy3zzotagjoj3Jt1Tx9oaxUdZEPIBl+mkrUz6PNKpzJgkrKSN9YgkWTA29c0KnLshmA==", + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true, - "requires": { - "@types/node": "*" + "engines": { + "node": ">=0.10.0" } }, - "@types/memoize-one": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@types/memoize-one/-/memoize-one-4.1.1.tgz", - "integrity": "sha512-+9djKUUn8hOyktLCfCy4hLaIPgDNovaU36fsnZe9trFHr6ddlbIn2q0SEsnkCkNR+pBWEU440Molz/+Mpyf+gQ==", - "dev": true + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "@types/mime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", - "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==", - "dev": true + "node_modules/array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "@types/minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", - "dev": true + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "@types/mocha": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", - "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", - "dev": true + "node_modules/array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "@types/nock": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@types/nock/-/nock-10.0.3.tgz", - "integrity": "sha512-OthuN+2FuzfZO3yONJ/QVjKmLEuRagS9TV9lEId+WHL9KhftYG+/2z+pxlr0UgVVXSpVD8woie/3fzQn8ft/Ow==", + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, - "requires": { - "@types/node": "*" + "engines": { + "node": ">=8" } }, - "@types/node": { - "version": "10.17.29", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.29.tgz", - "integrity": "sha512-zLo9rjUeQ5+QVhOufDwrb3XKyso31fJBJnk9wUUQIBDExF/O4LryvpOfozfUaxgqifTnlt7FyqsAPXUq5yFZSA==", - "dev": true - }, - "@types/node-fetch": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.7.tgz", - "integrity": "sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw==", + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", "dev": true, - "requires": { - "@types/node": "*", - "form-data": "^3.0.0" - }, "dependencies": { - "form-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", - "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - } + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "@types/pdfkit": { - "version": "0.7.36", - "resolved": "https://registry.npmjs.org/@types/pdfkit/-/pdfkit-0.7.36.tgz", - "integrity": "sha512-9eRA6MuW+n78yU3HhoIrDxjyAX2++B5MpLDYqHOnaRTquCw+5sYXT+QN8E1eSaxvNUwlRfU3tOm4UzTeGWmBqg==", + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", "dev": true, - "requires": { - "@types/node": "*" + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "@types/promisify-node": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@types/promisify-node/-/promisify-node-0.4.0.tgz", - "integrity": "sha1-3MceY8Cr9oYbrn0S9swzlceDk2s=", - "dev": true - }, - "@types/prop-types": { - "version": "15.7.1", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.1.tgz", - "integrity": "sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg==", - "dev": true - }, - "@types/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-Jugo5V/1bS0fRhy2z8+cUAHEyWOATaz4rbyLVvcFs7+dXp5HfwpEwzF1Q11bB10ApUqHf+yTauxI0UXQDwGrbA==", - "dev": true - }, - "@types/range-parser": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", - "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", - "dev": true - }, - "@types/react": { - "version": "16.8.23", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.8.23.tgz", - "integrity": "sha512-abkEOIeljniUN9qB5onp++g0EY38h7atnDHxwKUFz1r3VH1+yG1OKi2sNPTyObL40goBmfKFpdii2lEzwLX1cA==", + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", "dev": true, - "requires": { - "@types/prop-types": "*", - "csstype": "^2.2.0" + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "@types/react-dom": { - "version": "16.8.4", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.8.4.tgz", - "integrity": "sha512-eIRpEW73DCzPIMaNBDP5pPIpK1KXyZwNgfxiVagb5iGiz6da+9A5hslSX6GAQKdO7SayVCS/Fr2kjqprgAvkfA==", + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", "dev": true, - "requires": { - "@types/react": "*" + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "@types/react-json-tree": { - "version": "0.6.11", - "resolved": "https://registry.npmjs.org/@types/react-json-tree/-/react-json-tree-0.6.11.tgz", - "integrity": "sha512-HP0Sf0ZHjCi1FHLJxh/pLaxaevEW6ILlV2C5Dn3EZFTkLjWkv+EVf/l/zvtmoU9ZwuO/3TKVeWK/700UDxunTw==", + "node_modules/asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", "dev": true, - "requires": { - "@types/react": "*" + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" } }, - "@types/react-redux": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.5.tgz", - "integrity": "sha512-ZoNGQMDxh5ENY7PzU7MVonxDzS1l/EWiy8nUhDqxFqUZn4ovboCyvk4Djf68x6COb7vhGTKjyjxHxtFdAA5sUA==", + "node_modules/assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", "dev": true, - "requires": { - "@types/hoist-non-react-statics": "^3.3.0", - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0", - "redux": "^4.0.0" + "dependencies": { + "object-assign": "^4.1.1", + "util": "0.10.3" } }, - "@types/react-virtualized": { - "version": "9.21.2", - "resolved": "https://registry.npmjs.org/@types/react-virtualized/-/react-virtualized-9.21.2.tgz", - "integrity": "sha512-Q6geJaDd8FlBw3ilD4ODferTyVtYAmDE3d7+GacfwN0jPt9rD9XkeuPjcHmyIwTrMXuLv1VIJmRxU9WQoQFBJw==", - "dev": true, - "requires": { - "@types/prop-types": "*", - "@types/react": "*" - } + "node_modules/assert/node_modules/inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true }, - "@types/redux-logger": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/redux-logger/-/redux-logger-3.0.7.tgz", - "integrity": "sha512-oV9qiCuowhVR/ehqUobWWkXJjohontbDGLV88Be/7T4bqMQ3kjXwkFNL7doIIqlbg3X2PC5WPziZ8/j/QHNQ4A==", + "node_modules/assert/node_modules/util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "dev": true, - "requires": { - "redux": "^3.6.0" - }, "dependencies": { - "redux": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz", - "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==", - "dev": true, - "requires": { - "lodash": "^4.2.1", - "lodash-es": "^4.2.1", - "loose-envify": "^1.1.0", - "symbol-observable": "^1.0.3" - } - }, - "symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", - "dev": true - } + "inherits": "2.0.1" } }, - "@types/relateurl": { - "version": "0.2.28", - "resolved": "https://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.28.tgz", - "integrity": "sha1-a9p9uGU/piZD9e5p6facEaOS46Y=", - "dev": true - }, - "@types/request": { - "version": "2.48.1", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.1.tgz", - "integrity": "sha512-ZgEZ1TiD+KGA9LiAAPPJL68Id2UWfeSO62ijSXZjFJArVV+2pKcsVHmrcu+1oiE3q6eDGiFiSolRc4JHoerBBg==", + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true, - "requires": { - "@types/caseless": "*", - "@types/form-data": "*", - "@types/node": "*", - "@types/tough-cookie": "*" + "engines": { + "node": "*" } }, - "@types/semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==", - "dev": true - }, - "@types/serve-static": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz", - "integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==", + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true, - "requires": { - "@types/express-serve-static-core": "*", - "@types/mime": "*" + "engines": { + "node": ">=0.10.0" } }, - "@types/shortid": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz", - "integrity": "sha1-gJPuBBam4r8qpjOBCRFLP7/6Dps=", - "dev": true - }, - "@types/sinon": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.5.1.tgz", - "integrity": "sha512-EZQUP3hSZQyTQRfiLqelC9NMWd1kqLcmQE0dMiklxBkgi84T+cHOhnKpgk4NnOWpGX863yE6+IaGnOXUNFqDnQ==", + "node_modules/ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", "dev": true }, - "@types/sinonjs__fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz", - "integrity": "sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA==", + "node_modules/async": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==", "dev": true }, - "@types/socket.io": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-2.1.4.tgz", - "integrity": "sha512-cI98INy7tYnweTsUlp8ocveVdAxENUThO0JsLSCs51cjOP2yV5Mqo5QszMDPckyRRA+PO6+wBgKvGvHUCc23TQ==", + "node_modules/async-done": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-2.0.0.tgz", + "integrity": "sha512-j0s3bzYq9yKIVLKGE/tWlCpa3PfFLcrDZLTSVdnnCTGagXuXBJO4SsY9Xdk/fQBirCkH4evW5xOeJXqlAQFdsw==", "dev": true, - "requires": { - "@types/node": "*" + "dependencies": { + "end-of-stream": "^1.4.4", + "once": "^1.4.0", + "stream-exhaust": "^1.0.2" + }, + "engines": { + "node": ">= 10.13.0" } }, - "@types/source-list-map": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", - "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", - "dev": true - }, - "@types/stack-trace": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/stack-trace/-/stack-trace-0.0.29.tgz", - "integrity": "sha512-TgfOX+mGY/NyNxJLIbDWrO9DjGoVSW9+aB8H2yy1fy32jsvxijhmyJI9fDFgvz3YP4lvJaq9DzdR/M1bOgVc9g==", - "dev": true - }, - "@types/superagent": { - "version": "3.8.7", - "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-3.8.7.tgz", - "integrity": "sha512-9KhCkyXv268A2nZ1Wvu7rQWM+BmdYUVkycFeNnYrUL5Zwu7o8wPQ3wBfW59dDP+wuoxw0ww8YKgTNv8j/cgscA==", - "dev": true, - "requires": { - "@types/cookiejar": "*", - "@types/node": "*" + "node_modules/async-hook-jl": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/async-hook-jl/-/async-hook-jl-1.7.6.tgz", + "integrity": "sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg==", + "dependencies": { + "stack-chain": "^1.3.7" + }, + "engines": { + "node": "^4.7 || >=6.9 || >=7.3" } }, - "@types/tapable": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.4.tgz", - "integrity": "sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ==", - "dev": true + "node_modules/async-listener": { + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.6.10.tgz", + "integrity": "sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw==", + "dependencies": { + "semver": "^5.3.0", + "shimmer": "^1.1.0" + }, + "engines": { + "node": "<=0.11.8 || >0.11.10" + } }, - "@types/tcp-port-used": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/tcp-port-used/-/tcp-port-used-1.0.0.tgz", - "integrity": "sha512-UbspV5WZNhfM55HyvLEFyVc5n6K6OKuKep0mzvsgoUXQU1FS42GbePjreBnTCoKXfNzK/3/RJVCRlUDTuszFPg==" + "node_modules/async-listener/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } }, - "@types/temp": { - "version": "0.8.34", - "resolved": "https://registry.npmjs.org/@types/temp/-/temp-0.8.34.tgz", - "integrity": "sha512-oLa9c5LHXgS6UimpEVp08De7QvZ+Dfu5bMQuWyMhf92Z26Q10ubEMOWy9OEfUdzW7Y/sDWVHmUaLFtmnX/2j0w==", + "node_modules/async-settle": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-2.0.0.tgz", + "integrity": "sha512-Obu/KE8FurfQRN6ODdHN9LuXqwC+JFIM9NRyZqJJ4ZfLJmIYN9Rg0/kb+wF70VV5+fJusTMQlJ1t5rF7J/ETdg==", "dev": true, - "requires": { - "@types/node": "*" + "dependencies": { + "async-done": "^2.0.0" + }, + "engines": { + "node": ">= 10.13.0" } }, - "@types/testing-library__dom": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/@types/testing-library__dom/-/testing-library__dom-6.11.1.tgz", - "integrity": "sha512-ImChHtQqmjwraRLqBC2sgSQFtczeFvBmBcfhTYZn/3KwXbyD07LQykEQ0xJo7QHc1GbVvf7pRyGaIe6PkCdxEw==", + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, - "requires": { - "pretty-format": "^24.3.0" + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "@types/testing-library__react": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@types/testing-library__react/-/testing-library__react-9.1.2.tgz", - "integrity": "sha512-CYaMqrswQ+cJACy268jsLAw355DZtPZGt3Jwmmotlcu8O/tkoXBI6AeZ84oZBJsIsesozPKzWzmv/0TIU+1E9Q==", + "node_modules/axe-core": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.1.tgz", + "integrity": "sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw==", "dev": true, - "requires": { - "@types/react-dom": "*", - "@types/testing-library__dom": "*" + "engines": { + "node": ">=4" } }, - "@types/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha1-EHPEvIJHVK49EM+riKsCN7qWTk0=", - "dev": true - }, - "@types/tough-cookie": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz", - "integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==", + "node_modules/axobject-query": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", "dev": true }, - "@types/uglify-js": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.0.4.tgz", - "integrity": "sha512-SudIN9TRJ+v8g5pTG8RRCqfqTMNqgWCKKd3vtynhGzkIIjxaicNAMuY5TRadJ6tzDu3Dotf3ngaMILtmOdmWEQ==", + "node_modules/azure-devops-node-api": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-12.5.0.tgz", + "integrity": "sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og==", "dev": true, - "requires": { - "source-map": "^0.6.1" + "dependencies": { + "tunnel": "0.0.6", + "typed-rest-client": "^1.8.4" } }, - "@types/underscore": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.9.4.tgz", - "integrity": "sha512-CjHWEMECc2/UxOZh0kpiz3lEyX2Px3rQS9HzD20lxMvx571ivOBQKeLnqEjxUY0BMgp6WJWo/pQLRBwMW5v4WQ==", - "dev": true - }, - "@types/untildify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/untildify/-/untildify-3.0.0.tgz", - "integrity": "sha512-FTktI3Y1h+gP9GTjTvXBP5v8xpH4RU6uS9POoBcGy4XkS2Np6LNtnP1eiNNth4S7P+qw2c/rugkwBasSHFzJEg==", + "node_modules/b4a": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", "dev": true }, - "@types/uuid": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.5.tgz", - "integrity": "sha512-MNL15wC3EKyw1VLF+RoVO4hJJdk9t/Hlv3rt1OL65Qvuadm4BYo6g9ZJQqoq7X8NBFSsQXgAujWciovh2lpVjA==", + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, - "requires": { - "@types/node": "*" + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } }, - "@types/vscode": { - "version": "1.48.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.48.0.tgz", - "integrity": "sha512-sZJKzsJz1gSoFXcOJWw3fnKl2sseUgZmvB4AJZS+Fea+bC/jfGPVhmFL/FfQHld/TKtukVONsmoD3Pkyx9iadg==", - "dev": true + "node_modules/babel-runtime/node_modules/core-js": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", + "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==", + "deprecated": "core-js@<3.4 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.", + "dev": true, + "hasInstallScript": true }, - "@types/vscode-notebook-renderer": { - "version": "1.48.0", - "resolved": "https://registry.npmjs.org/@types/vscode-notebook-renderer/-/vscode-notebook-renderer-1.48.0.tgz", - "integrity": "sha512-V7ifnlOgvVP3Ud1mM6EO98+oWRk9bt59INUL3Q0o06cAw45jOfos8pAFjDyCEHd1n6d3Q0ObPzdB0jgg8TdgDA==", + "node_modules/babel-runtime/node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", "dev": true }, - "@types/webpack": { - "version": "4.4.34", - "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.4.34.tgz", - "integrity": "sha512-GnEBgjHsfO1M7DIQ0dAupSofcmDItE3Zsu3reK8SQpl/6N0rtUQxUmQzVFAS5ou/FGjsYKjXAWfItLZ0kNFTfQ==", + "node_modules/bach": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bach/-/bach-2.0.1.tgz", + "integrity": "sha512-A7bvGMGiTOxGMpNupYl9HQTf0FFDNF4VCmks4PJpFyN1AX2pdKuxuwdvUz2Hu388wcgp+OvGFNsumBfFNkR7eg==", "dev": true, - "requires": { - "@types/anymatch": "*", - "@types/node": "*", - "@types/tapable": "*", - "@types/uglify-js": "*", - "source-map": "^0.6.0" + "dependencies": { + "async-done": "^2.0.0", + "async-settle": "^2.0.0", + "now-and-later": "^3.0.0" + }, + "engines": { + "node": ">=10.13.0" } }, - "@types/webpack-bundle-analyzer": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/@types/webpack-bundle-analyzer/-/webpack-bundle-analyzer-2.13.1.tgz", - "integrity": "sha512-9M9jingj0izx1VfglYYJ+dvd0YCMlFgtrSCeb+C3VIQP8hmTXGJ5qqVeqiBnv0ffMyeKWqyij4K2F4VBcazQNw==", + "node_modules/bach/node_modules/now-and-later": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-3.0.0.tgz", + "integrity": "sha512-pGO4pzSdaxhWTGkfSfHx3hVzJVslFPwBp2Myq9MYN/ChfJZF87ochMAXnvz6/58RJSf5ik2q9tXprBBrk2cpcg==", "dev": true, - "requires": { - "@types/webpack": "*" + "dependencies": { + "once": "^1.4.0" + }, + "engines": { + "node": ">= 10.13.0" } }, - "@types/webpack-sources": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.6.tgz", - "integrity": "sha512-FtAWR7wR5ocJ9+nP137DV81tveD/ZgB1sadnJ/axUGM3BUVfRPx8oQNMtv3JNfTeHx3VP7cXiyfR/jmtEsVHsQ==", + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "node_modules/bare-events": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.3.1.tgz", + "integrity": "sha512-sJnSOTVESURZ61XgEleqmP255T6zTYwHPwE4r6SssIh0U9/uDvfpdoJYpVUerJJZH2fueO+CdT8ZT+OC/7aZDA==", "dev": true, - "requires": { - "@types/node": "*", - "@types/source-list-map": "*", - "source-map": "^0.6.1" - } + "optional": true }, - "@types/winreg": { - "version": "1.2.30", - "resolved": "https://registry.npmjs.org/@types/winreg/-/winreg-1.2.30.tgz", - "integrity": "sha1-kdZxDlNtNFucmwF8V0z2qNpkxRg=", - "dev": true + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, - "@types/ws": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-6.0.1.tgz", - "integrity": "sha512-EzH8k1gyZ4xih/MaZTXwT2xOkPiIMSrhQ9b8wrlX88L0T02eYsddatQlwVFlEPyEqV0ChpdpNnE51QPH6NVT4Q==", + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", "dev": true, - "requires": { - "@types/events": "*", - "@types/node": "*" + "bin": { + "baseline-browser-mapping": "dist/cli.js" } }, - "@types/xml2js": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.4.tgz", - "integrity": "sha512-O6Xgai01b9PB3IGA0lRIp1Ex3JBcxGDhdO0n3NIIpCyDOAjxcIGQFmkvgJpP8anTrthxOUQjBfLdRRi0Zn/TXA==", + "node_modules/bent": { + "version": "7.3.12", + "resolved": "https://registry.npmjs.org/bent/-/bent-7.3.12.tgz", + "integrity": "sha512-T3yrKnVGB63zRuoco/7Ybl7BwwGZR0lceoVG5XmQyMIH9s19SV5m+a8qam4if0zQuAmOQTyPTPmsQBdAorGK3w==", "dev": true, - "requires": { - "@types/node": "*" + "dependencies": { + "bytesish": "^0.4.1", + "caseless": "~0.12.0", + "is-stream": "^2.0.0" } }, - "@types/yargs": { - "version": "13.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.5.tgz", - "integrity": "sha512-CF/+sxTO7FOwbIRL4wMv0ZYLCRfMid2HQpzDRyViH7kSpfoAFiMdGqKIxb1PxWfjtQXQhnQuD33lvRHNwr809Q==", + "node_modules/bent/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, - "requires": { - "@types/yargs-parser": "*" + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "@types/yargs-parser": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", - "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==", - "dev": true + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } }, - "@types/yauzl": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", - "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true, - "optional": true, - "requires": { - "@types/node": "*" + "engines": { + "node": ">=8" } }, - "@typescript-eslint/eslint-plugin": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.10.1.tgz", - "integrity": "sha512-PQg0emRtzZFWq6PxBcdxRH3QIQiyFO3WCVpRL3fgj5oQS3CDs3AeAKfv4DxNhzn8ITdNJGJ4D3Qw8eAJf3lXeQ==", + "node_modules/bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", "dev": true, - "requires": { - "@typescript-eslint/experimental-utils": "3.10.1", - "debug": "^4.1.1", - "functional-red-black-tree": "^1.0.1", - "regexpp": "^3.0.0", - "semver": "^7.3.2", - "tsutils": "^3.17.1" - }, + "license": "MIT", "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "dev": true - } + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" } }, - "@typescript-eslint/experimental-utils": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz", - "integrity": "sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==", + "node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", "dev": true, - "requires": { - "@types/json-schema": "^7.0.3", - "@typescript-eslint/types": "3.10.1", - "@typescript-eslint/typescript-estree": "3.10.1", - "eslint-scope": "^5.0.0", - "eslint-utils": "^2.0.0" - }, - "dependencies": { - "eslint-scope": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", - "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - } - } - }, - "@typescript-eslint/parser": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.10.1.tgz", - "integrity": "sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw==", - "dev": true, - "requires": { - "@types/eslint-visitor-keys": "^1.0.0", - "@typescript-eslint/experimental-utils": "3.10.1", - "@typescript-eslint/types": "3.10.1", - "@typescript-eslint/typescript-estree": "3.10.1", - "eslint-visitor-keys": "^1.1.0" - } + "license": "MIT" }, - "@typescript-eslint/types": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", - "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==", + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, - "@typescript-eslint/typescript-estree": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz", - "integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==", - "dev": true, - "requires": { - "@typescript-eslint/types": "3.10.1", - "@typescript-eslint/visitor-keys": "3.10.1", - "debug": "^4.1.1", - "glob": "^7.1.6", - "is-glob": "^4.0.1", - "lodash": "^4.17.15", - "semver": "^7.3.2", - "tsutils": "^3.17.1" - }, + "node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "dev": true - } - } - }, - "@typescript-eslint/visitor-keys": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz", - "integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "@webassemblyjs/ast": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", - "integrity": "sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==", + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, - "requires": { - "@webassemblyjs/helper-module-context": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/wast-parser": "1.8.5" + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" } }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz", - "integrity": "sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ==", + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", "dev": true }, - "@webassemblyjs/helper-api-error": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz", - "integrity": "sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==", + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, - "@webassemblyjs/helper-buffer": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz", - "integrity": "sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==", - "dev": true + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } }, - "@webassemblyjs/helper-code-frame": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz", - "integrity": "sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==", + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", "dev": true, - "requires": { - "@webassemblyjs/wast-printer": "1.8.5" + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" } }, - "@webassemblyjs/helper-fsm": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz", - "integrity": "sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow==", - "dev": true + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } }, - "@webassemblyjs/helper-module-context": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz", - "integrity": "sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==", + "node_modules/browserify-rsa": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.1.tgz", + "integrity": "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==", "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "mamacro": "^0.0.3" + "license": "MIT", + "dependencies": { + "bn.js": "^5.2.1", + "randombytes": "^2.1.0", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" } }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz", - "integrity": "sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ==", - "dev": true + "node_modules/browserify-rsa/node_modules/bn.js": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz", + "integrity": "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==", + "dev": true, + "license": "MIT" }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz", - "integrity": "sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==", + "node_modules/browserify-rsa/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-buffer": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/wasm-gen": "1.8.5" - } + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" }, - "@webassemblyjs/ieee754": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz", - "integrity": "sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==", + "node_modules/browserify-sign": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.5.tgz", + "integrity": "sha512-C2AUdAJg6rlM2W5QMp2Q4KGQMVBwR1lIimTsUnutJ8bMpW5B52pGpR2gEnNBNwijumDo5FojQ0L9JrXA8m4YEw==", "dev": true, - "requires": { - "@xtuc/ieee754": "^1.2.0" + "license": "ISC", + "dependencies": { + "bn.js": "^5.2.2", + "browserify-rsa": "^4.1.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.6.1", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.9", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" } }, - "@webassemblyjs/leb128": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.8.5.tgz", - "integrity": "sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==", + "node_modules/browserify-sign/node_modules/bn.js": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz", + "integrity": "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==", "dev": true, - "requires": { - "@xtuc/long": "4.2.2" - } + "license": "MIT" }, - "@webassemblyjs/utf8": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.8.5.tgz", - "integrity": "sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==", - "dev": true + "node_modules/browserify-sign/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, - "@webassemblyjs/wasm-edit": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz", - "integrity": "sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==", + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-buffer": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/helper-wasm-section": "1.8.5", - "@webassemblyjs/wasm-gen": "1.8.5", - "@webassemblyjs/wasm-opt": "1.8.5", - "@webassemblyjs/wasm-parser": "1.8.5", - "@webassemblyjs/wast-printer": "1.8.5" + "dependencies": { + "pako": "~1.0.5" } }, - "@webassemblyjs/wasm-gen": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz", - "integrity": "sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==", + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/ieee754": "1.8.5", - "@webassemblyjs/leb128": "1.8.5", - "@webassemblyjs/utf8": "1.8.5" + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "@webassemblyjs/wasm-opt": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz", - "integrity": "sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==", + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-buffer": "1.8.5", - "@webassemblyjs/wasm-gen": "1.8.5", - "@webassemblyjs/wasm-parser": "1.8.5" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, - "@webassemblyjs/wasm-parser": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz", - "integrity": "sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==", + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-api-error": "1.8.5", - "@webassemblyjs/helper-wasm-bytecode": "1.8.5", - "@webassemblyjs/ieee754": "1.8.5", - "@webassemblyjs/leb128": "1.8.5", - "@webassemblyjs/utf8": "1.8.5" + "license": "MIT", + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" } }, - "@webassemblyjs/wast-parser": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz", - "integrity": "sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==", + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/floating-point-hex-parser": "1.8.5", - "@webassemblyjs/helper-api-error": "1.8.5", - "@webassemblyjs/helper-code-frame": "1.8.5", - "@webassemblyjs/helper-fsm": "1.8.5", - "@xtuc/long": "4.2.2" - } + "license": "MIT" }, - "@webassemblyjs/wast-printer": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz", - "integrity": "sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==", + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/wast-parser": "1.8.5", - "@xtuc/long": "4.2.2" + "engines": { + "node": "*" } }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "dev": true }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, - "abab": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", - "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==", + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", "dev": true }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "node_modules/builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", "dev": true }, - "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "node_modules/bytesish": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/bytesish/-/bytesish-0.4.4.tgz", + "integrity": "sha512-i4uu6M4zuMUiyfZN4RU2+i9+peJh//pXhd9x1oSe1LBkZ3LEbCoygu8W0bXTukU1Jme2txKuotpCZRaC3FLxcQ==", + "dev": true + }, + "node_modules/cacheable-request": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", + "integrity": "sha512-vag0O2LKZ/najSoUwDbVlnlCFvhBE/7mGTY2B5FgCBDcRD+oVV1HYTOwM6JZfMg/hIcM6IwnTZ1uQQL5/X3xIQ==", "dev": true, - "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "license": "MIT", + "dependencies": { + "clone-response": "1.0.2", + "get-stream": "3.0.0", + "http-cache-semantics": "3.8.1", + "keyv": "3.0.0", + "lowercase-keys": "1.0.0", + "normalize-url": "2.0.1", + "responselike": "1.0.2" } }, - "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", - "dev": true + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } }, - "acorn-dynamic-import": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz", - "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==" + "node_modules/cacheable-request/node_modules/lowercase-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", + "integrity": "sha512-RPlX0+PHuvxVDZ7xX+EBVAp4RsVxP/TdDSN2mJYdiq1Lc4Hz7EUSjUI7RZrKKlmrIzVhf6Jo2stj7++gVarS0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "acorn-globals": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.2.tgz", - "integrity": "sha512-BbzvZhVtZP+Bs1J1HcwrQe8ycfO0wStkSGxuul3He3GkHOIZ6eTqOkPuw9IP1X3+IkOo4wiJmwkobzXYz4wewQ==", + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", "dev": true, - "requires": { - "acorn": "^6.0.1", - "acorn-walk": "^6.0.1" - }, "dependencies": { - "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", - "dev": true - } + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "acorn-jsx": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", - "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", - "dev": true - }, - "acorn-node": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.7.0.tgz", - "integrity": "sha512-XhahLSsCB6X6CJbe+uNu3Mn9sJBNFxtBN9NLgAOQovfS6Kh0lDUtmlclhjn9CvEK7A7YyRU13PXlNcpSiLI9Yw==", - "requires": { - "acorn": "^6.1.1", - "acorn-dynamic-import": "^4.0.0", - "acorn-walk": "^6.1.1", - "xtend": "^4.0.1" + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", "dependencies": { - "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==" - } + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" } }, - "acorn-walk": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", - "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==" + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "address": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/address/-/address-1.0.3.tgz", - "integrity": "sha512-z55ocwKBRLryBs394Sm3ushTtBeg6VAeuku7utSoSnsJKvKcnXFIyC6vh27n3rXyxSgkJBBCAvyOn7gSUcTYjg==", - "dev": true + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } }, - "after": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001768", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001768.tgz", + "integrity": "sha512-qY3aDRZC5nWPgHUgIB84WL+nySuo19wk0VJpp/XI9T34lrvkyhRvNVOFJOp2kxClQhiFBu+TaUSudf6oa3vkSA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", "dev": true }, - "agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "node_modules/chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", "dev": true, - "requires": { - "es6-promisify": "^5.0.0" + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" } }, - "aggregate-error": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", - "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" + "node_modules/chai-arrays": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chai-arrays/-/chai-arrays-2.2.0.tgz", + "integrity": "sha512-4awrdGI2EH8owJ9I58PXwG4N56/FiM8bsn4CVSNEgr4GKAM6Kq5JPVApUbhUBjDakbZNuRvV7quRSC38PWq/tg==", + "dev": true, + "engines": { + "node": ">=0.10" } }, - "airbnb-prop-types": { - "version": "2.13.2", - "resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.13.2.tgz", - "integrity": "sha512-2FN6DlHr6JCSxPPi25EnqGaXC4OC3/B3k1lCd6MMYrZ51/Gf/1qDfaR+JElzWa+Tl7cY2aYOlsYJGFeQyVHIeQ==", + "node_modules/chai-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", "dev": true, - "requires": { - "array.prototype.find": "^2.0.4", - "function.prototype.name": "^1.1.0", - "has": "^1.0.3", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object.assign": "^4.1.0", - "object.entries": "^1.1.0", - "prop-types": "^15.7.2", - "prop-types-exact": "^1.2.0", - "react-is": "^16.8.6" + "dependencies": { + "check-error": "^1.0.2" + }, + "peerDependencies": { + "chai": ">= 2.1.2 < 5" } }, - "ajv": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.1.tgz", - "integrity": "sha512-w1YQaVGNC6t2UCPjEawK/vo/dG8OOrVtUmhBT1uJJYxbl5kU2Tj3v6LGqBcsysN1yhuCStJCCA3GqdvKY8sqXQ==", - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" } }, - "ajv-errors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true - }, - "ajv-keywords": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", - "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", - "dev": true - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "optional": true + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=", + "dev": true, + "engines": { + "node": "*" + } }, - "anser": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.8.tgz", - "integrity": "sha512-tVHucTCKIt9VRrpQKzPtOlwm/3AmyQ7J+QE29ixFnvuE2hm83utEVrN7jJapYkHV6hI0HOHkEX9TOMCzHtwvuA==", - "dev": true + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true, + "engines": { + "node": "*" + } }, - "ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "node_modules/cheerio": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz", + "integrity": "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==", "dev": true, - "requires": { - "ansi-wrap": "^0.1.0" + "dependencies": { + "cheerio-select": "^1.5.0", + "dom-serializer": "^1.3.2", + "domhandler": "^4.2.0", + "htmlparser2": "^6.1.0", + "parse5": "^6.0.1", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" } }, - "ansi-cyan": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", - "integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=", + "node_modules/cheerio-select": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.5.0.tgz", + "integrity": "sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg==", "dev": true, - "requires": { - "ansi-wrap": "0.1.0" + "dependencies": { + "css-select": "^4.1.3", + "css-what": "^5.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0", + "domutils": "^2.7.0" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" } }, - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true + "node_modules/cheerio-select/node_modules/css-select": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", + "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^5.0.0", + "domhandler": "^4.2.0", + "domutils": "^2.6.0", + "nth-check": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } }, - "ansi-gray": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", - "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "node_modules/cheerio-select/node_modules/css-what": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.0.1.tgz", + "integrity": "sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg==", "dev": true, - "requires": { - "ansi-wrap": "0.1.0" + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" } }, - "ansi-red": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", - "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", + "node_modules/cheerio-select/node_modules/dom-serializer": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", + "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", "dev": true, - "requires": { - "ansi-wrap": "0.1.0" + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + "node_modules/cheerio-select/node_modules/domelementtype": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", + "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/cheerio-select/node_modules/domhandler": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.0.tgz", + "integrity": "sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA==", "dev": true, - "requires": { - "color-convert": "^1.9.0" + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" } }, - "ansi-to-html": { - "version": "0.6.11", - "resolved": "https://registry.npmjs.org/ansi-to-html/-/ansi-to-html-0.6.11.tgz", - "integrity": "sha512-88XZtrcwrfkyn6fGstHnkaF1kl7hGtNCYh4vSmItgEV+6JnQHryDBf7udF4f2RhTRQmYvJvPcTtqgaqrxzc9oA==", + "node_modules/cheerio-select/node_modules/domutils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz", + "integrity": "sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==", "dev": true, - "requires": { - "entities": "^1.1.1" + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" } }, - "ansi-to-react": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/ansi-to-react/-/ansi-to-react-3.3.5.tgz", - "integrity": "sha512-uAI8NOh+/5PC1poTnLwhuO7whaxPst1lZCeq+7P7hlP0A6GRXjXu1f5qprTwT3NHtjIWyMcFJAL0Im0HyB2XeQ==", + "node_modules/cheerio-select/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", "dev": true, - "requires": { - "@babel/runtime-corejs2": "^7.0.0", - "anser": "^1.4.1", - "babel-runtime": "^6.26.0", - "escape-carriage": "^1.2.0" + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", - "dev": true - }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "node_modules/cheerio/node_modules/dom-serializer": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", + "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, - "append-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", - "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", + "node_modules/cheerio/node_modules/domelementtype": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", + "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", "dev": true, - "requires": { - "buffer-equal": "^1.0.0" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/cheerio/node_modules/domhandler": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.0.tgz", + "integrity": "sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA==", + "dev": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/domutils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz", + "integrity": "sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==", + "dev": true, "dependencies": { - "buffer-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", - "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", - "dev": true - } + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" } }, - "append-transform": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", - "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "node_modules/cheerio/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", "dev": true, - "requires": { - "default-require-extensions": "^3.0.0" + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "applicationinsights": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-1.7.4.tgz", - "integrity": "sha512-XFLsNlcanpjFhHNvVWEfcm6hr7lu9znnb6Le1Lk5RE03YUV9X2B2n2MfM4kJZRrUdV+C0hdHxvWyv+vWoLfY7A==", - "requires": { - "cls-hooked": "^4.2.2", - "continuation-local-storage": "^3.2.1", - "diagnostic-channel": "0.2.0", - "diagnostic-channel-publishers": "^0.3.3" + "node_modules/cheerio/node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" } }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "node_modules/cheerio/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "dev": true }, - "arch": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/arch/-/arch-2.1.1.tgz", - "integrity": "sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg==" + "node_modules/cheerio/node_modules/tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true }, - "archive-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/archive-type/-/archive-type-4.0.0.tgz", - "integrity": "sha1-+S5yIzBW38aWlHJ0nCZ72wRrHXA=", + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, - "requires": { - "file-type": "^4.2.0" + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "dependencies": { - "file-type": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-4.4.0.tgz", - "integrity": "sha1-G2AOX8ofvcboDApwxxyNul95BsU=", - "dev": true - } + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" } }, - "archiver": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-3.1.1.tgz", - "integrity": "sha512-5Hxxcig7gw5Jod/8Gq0OneVgLYET+oNHcxgWItq4TbhOzRLKNAFUb9edAftiMKXvXfCB0vbGrJdZDNq0dWMsxg==", + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "async": "^2.6.3", - "buffer-crc32": "^0.2.1", - "glob": "^7.1.4", - "readable-stream": "^3.4.0", - "tar-stream": "^2.1.0", - "zip-stream": "^2.1.2" + "optional": true + }, + "node_modules/chrome-trace-event": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/cipher-base": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.7.tgz", + "integrity": "sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==", + "dev": true, + "license": "MIT", "dependencies": { - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, - "bl": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", - "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cipher-base/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" }, - "buffer": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", - "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", - "dev": true, - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } + { + "type": "patreon", + "url": "https://www.patreon.com/feross" }, - "tar-stream": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.3.tgz", - "integrity": "sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA==", - "dev": true, - "requires": { - "bl": "^4.0.1", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } + { + "type": "consulting", + "url": "https://feross.org/support" } - } + ], + "license": "MIT" }, - "archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "node_modules/circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "deprecated": "CircularJSON is in maintenance only, flatted is its successor.", + "dev": true + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==" + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, - "requires": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" - }, "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, - "archy": { + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-buffer": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", - "dev": true + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true, + "engines": { + "node": ">= 0.10" + } }, - "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "dev": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - }, "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" } }, - "arg": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz", - "integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==", "dev": true, - "requires": { - "sprintf-js": "~1.0.2" + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" } }, - "argv": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/argv/-/argv-0.0.2.tgz", - "integrity": "sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas=", + "node_modules/clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", "dev": true }, - "aria-query": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", - "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", + "node_modules/cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", "dev": true, - "requires": { - "ast-types-flow": "0.0.7", - "commander": "^2.11.0" + "dependencies": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" } }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true + "node_modules/cls-hooked": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/cls-hooked/-/cls-hooked-4.2.2.tgz", + "integrity": "sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw==", + "dependencies": { + "async-hook-jl": "^1.7.6", + "emitter-listener": "^1.0.1", + "semver": "^5.4.1" + }, + "engines": { + "node": "^4.7 || >=6.9 || >=7.3 || >=8.2.1" + } }, - "arr-filter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", - "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", - "dev": true, - "requires": { - "make-iterator": "^1.0.0" + "node_modules/cls-hooked/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" } }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true + "node_modules/cockatiel": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.1.3.tgz", + "integrity": "sha512-xC759TpZ69d7HhfDp8m2WkRwEUiCkxY8Ee2OQH/3H6zmy2D/5Sm+zSTbPRa+V2QyjDtpMvjOIAOVjA2gp6N1kQ==", + "dev": true, + "engines": { + "node": ">=16" + } }, - "arr-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", - "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, - "requires": { - "make-iterator": "^1.0.0" + "dependencies": { + "color-name": "1.1.3" } }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, - "array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", + "node_modules/colorette": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", + "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", "dev": true }, - "array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, - "array-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, - "array-filter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", - "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", + "node_modules/compare-module-exports": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/compare-module-exports/-/compare-module-exports-2.1.0.tgz", + "integrity": "sha512-3Lc0sTIuX1jmY2K2RrXRJOND6KsRTX2D4v3+eu1PDptsuJZVK4LZc852eZa9I+avj0NrUKlTNgqvccNOH6mbGg==", "dev": true }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "node_modules/console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", "dev": true }, - "array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=" + "node_modules/constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true }, - "array-includes": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", - "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0", - "is-string": "^1.0.5" - }, + "license": "MIT", "dependencies": { - "es-abstract": { - "version": "1.17.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", - "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-regex": "^1.1.0", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "is-callable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", - "dev": true - }, - "is-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", - "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", - "dev": true - }, - "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", - "dev": true - } + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" } }, - "array-initial": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", - "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true, - "requires": { - "array-slice": "^1.0.0", - "is-number": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" } - } + ], + "license": "MIT" }, - "array-last": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", - "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", - "dev": true, - "requires": { - "is-number": "^4.0.0" - }, + "node_modules/continuation-local-storage": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz", + "integrity": "sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==", "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } + "async-listener": "^0.6.0", + "emitter-listener": "^1.1.1" } }, - "array-map": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", - "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=", + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true }, - "array-reduce": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz", - "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=", - "dev": true + "node_modules/copy-props": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-4.0.0.tgz", + "integrity": "sha512-bVWtw1wQLzzKiYROtvNlbJgxgBYt2bMJpkCbKmXM3xyijvcjjWXEk5nyrrT3bgJ7ODb19ZohE2T0Y3FgNPyoTw==", + "dev": true, + "dependencies": { + "each-props": "^3.0.0", + "is-plain-object": "^5.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } }, - "array-slice": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", - "dev": true + "node_modules/copy-props/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "array-sort": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", - "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", + "node_modules/copy-webpack-plugin": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-9.1.0.tgz", + "integrity": "sha512-rxnR7PaGigJzhqETHGmAcxKnLZSR5u1Y3/bcIv/1FnqXedcL/E2ewK7ZCNrArJKCiSv8yVXhTqetJh8inDvfsA==", "dev": true, - "requires": { - "default-compare": "^1.0.0", - "get-value": "^2.0.6", - "kind-of": "^5.0.2" - }, "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } + "fast-glob": "^3.2.7", + "glob-parent": "^6.0.1", + "globby": "^11.0.3", + "normalize-path": "^3.0.0", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" } }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "node_modules/copy-webpack-plugin/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, - "requires": { - "array-uniq": "^1.0.1" + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" } }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true + "node_modules/core-js-pure": { + "version": "3.42.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.42.0.tgz", + "integrity": "sha512-007bM04u91fF4kMgwom2I5cQxAFIy8jVulgr9eozILl/SZE53QOqnW/+vviC+wQWLv+AunBG+8Q0TLoeSsSxRQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, - "array.prototype.find": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.1.0.tgz", - "integrity": "sha512-Wn41+K1yuO5p7wRZDl7890c3xvv5UBrfVXTVIe28rSQb6LS0fZMDrQB6PAcxQFRFy6vJTLDc3A2+3CjQdzVKRg==", + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.13.0" + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" } }, - "array.prototype.flat": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.1.tgz", - "integrity": "sha512-rVqIs330nLJvfC7JqYvEWwqVr5QjYF1ib02i3YJtR/fICO6527Tjpc/e4Mvmxh3GIePPreRXMdaGyC99YphWEw==", + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, - "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.10.0", - "function-bind": "^1.1.1" + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" } }, - "array.prototype.flatmap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.3.tgz", - "integrity": "sha512-OOEk+lkePcg+ODXIpvuU9PAryCikCJyo7GlDG1upleEpQRx6mzL9puEBkozQ5iAx20KV0l3DbyQwqciJtqe5Pg==", + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1" - }, "dependencies": { - "es-abstract": { - "version": "1.17.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", - "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-regex": "^1.1.0", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "is-callable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", - "dev": true - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", - "dev": true - } + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" } }, - "array.prototype.map": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.2.tgz", - "integrity": "sha512-Az3OYxgsa1g7xDYp86l0nnN4bcmuEITGe1rbdEBVkrqkzMgDcbdQ2R7r41pNzti+4NMces3H8gMmuioZUilLgw==", + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "es-array-method-boxes-properly": "^1.0.0", - "is-string": "^1.0.4" + "dependencies": { + "cross-spawn": "^7.0.1" }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/cross-env/node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", "dependencies": { - "es-abstract": { - "version": "1.17.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", - "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-regex": "^1.1.0", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "is-callable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", - "dev": true - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", - "dev": true - } + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" } }, - "arraybuffer.slice": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", - "dev": true + "node_modules/cross-env/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true + "node_modules/cross-env/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", - "dev": true + "node_modules/cross-env/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "requires": { - "safer-buffer": "~2.1.0" + "node_modules/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" } }, - "asn1.js": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", - "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "node_modules/cross-spawn/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, - "requires": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" + "bin": { + "semver": "bin/semver" } }, - "assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", - "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "node_modules/cross-spawn/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, - "requires": { - "object-assign": "^4.1.1", - "util": "0.10.3" - }, "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - }, - "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "requires": { - "inherits": "2.0.1" - } - } + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" } }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=", + "dev": true, + "engines": { + "node": "*" + } }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true + "node_modules/crypto-browserify": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.1.tgz", + "integrity": "sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserify-cipher": "^1.0.1", + "browserify-sign": "^4.2.3", + "create-ecdh": "^4.0.4", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "diffie-hellman": "^5.0.3", + "hash-base": "~3.0.4", + "inherits": "^2.0.4", + "pbkdf2": "^3.1.2", + "public-encrypt": "^4.0.3", + "randombytes": "^2.1.0", + "randomfill": "^1.0.4" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", "dev": true }, - "ast-transform": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/ast-transform/-/ast-transform-0.0.0.tgz", - "integrity": "sha1-dJRAWIh9goPhidlUYAlHvJj+AGI=", - "requires": { - "escodegen": "~1.2.0", - "esprima": "~1.0.4", - "through": "~2.3.4" - }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, "dependencies": { - "escodegen": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.2.0.tgz", - "integrity": "sha1-Cd55Z3kcyVi3+Jot220jRRrzJ+E=", - "requires": { - "esprima": "~1.0.4", - "estraverse": "~1.5.0", - "esutils": "~1.0.0", - "source-map": "~0.1.30" - } - }, - "esprima": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", - "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=" - }, - "estraverse": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.5.1.tgz", - "integrity": "sha1-hno+jlip+EYYr7bC3bzZFrfLr3E=" - }, - "esutils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.0.0.tgz", - "integrity": "sha1-gVHTWOIMisx/t0XnRywAJf5JZXA=" - }, - "source-map": { - "version": "0.1.43", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", - "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", - "optional": true, - "requires": { - "amdefine": ">=0.0.4" - } - } + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "ast-types": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.7.8.tgz", - "integrity": "sha1-kC0uDWDQcb3NRtwRXhgJ7RHBOKk=" - }, - "ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", - "dev": true + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "astral-regex": { + "node_modules/data-view-byte-offset": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, - "async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", - "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", - "requires": { - "lodash": "^4.17.11" + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "async-done": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", - "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.2", - "process-nextick-args": "^2.0.0", - "stream-exhaust": "^1.0.1" - }, "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - } + "ms": "2.0.0" } }, - "async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, - "async-hook-jl": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/async-hook-jl/-/async-hook-jl-1.7.6.tgz", - "integrity": "sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg==", - "requires": { - "stack-chain": "^1.3.7" + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, - "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" - }, - "async-listener": { - "version": "0.6.10", - "resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.6.10.tgz", - "integrity": "sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw==", - "requires": { - "semver": "^5.3.0", - "shimmer": "^1.1.0" + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" } }, - "async-settle": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", - "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", + "node_modules/decompress": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", + "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", "dev": true, - "requires": { - "async-done": "^1.2.2" + "license": "MIT", + "dependencies": { + "decompress-tar": "^4.0.0", + "decompress-tarbz2": "^4.0.0", + "decompress-targz": "^4.0.0", + "decompress-unzip": "^4.0.1", + "graceful-fs": "^4.1.10", + "make-dir": "^1.0.0", + "pify": "^2.3.0", + "strip-dirs": "^2.0.0" + }, + "engines": { + "node": ">=4" } }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, - "autoprefixer": { - "version": "7.2.6", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-7.2.6.tgz", - "integrity": "sha512-Iq8TRIB+/9eQ8rbGhcP7ct5cYb/3qjNYAR2SnzLCEcwF6rvVOax8+9+fccgXk4bEhQGjOZd5TLhsksmAdsbGqQ==", + "node_modules/decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", "dev": true, - "requires": { - "browserslist": "^2.11.3", - "caniuse-lite": "^1.0.30000805", - "normalize-range": "^0.1.2", - "num2fraction": "^1.2.2", - "postcss": "^6.0.17", - "postcss-value-parser": "^3.2.3" - }, + "license": "MIT", "dependencies": { - "browserslist": { - "version": "2.11.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", - "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30000792", - "electron-to-chromium": "^1.3.30" - } - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - } + "mimic-response": "^1.0.0" + }, + "engines": { + "node": ">=4" } }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + "node_modules/decompress-tar": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", + "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "^5.2.0", + "is-stream": "^1.1.0", + "tar-stream": "^1.5.2" + }, + "engines": { + "node": ">=4" + } }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + "node_modules/decompress-tar/node_modules/file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } }, - "axe-core": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-3.5.5.tgz", - "integrity": "sha512-5P0QZ6J5xGikH780pghEdbEKijCTrruK9KxtPZCFWUpef0f6GipO+xEZ5GKCb020mmqgbiNO6TcA55CriL784Q==", - "dev": true + "node_modules/decompress-tarbz2": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", + "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "decompress-tar": "^4.1.0", + "file-type": "^6.1.0", + "is-stream": "^1.1.0", + "seek-bzip": "^1.0.5", + "unbzip2-stream": "^1.0.9" + }, + "engines": { + "node": ">=4" + } }, - "axios": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", - "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", - "requires": { - "follow-redirects": "1.5.10" + "node_modules/decompress-tarbz2/node_modules/file-type": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", + "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" } }, - "axobject-query": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", - "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", - "dev": true + "node_modules/decompress-targz": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", + "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "decompress-tar": "^4.1.1", + "file-type": "^5.2.0", + "is-stream": "^1.1.0" + }, + "engines": { + "node": ">=4" + } }, - "azure-devops-node-api": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-7.2.0.tgz", - "integrity": "sha512-pMfGJ6gAQ7LRKTHgiRF+8iaUUeGAI0c8puLaqHLc7B8AR7W6GJLozK9RFeUHFjEGybC9/EB3r67WPd7e46zQ8w==", + "node_modules/decompress-targz/node_modules/file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", "dev": true, - "requires": { - "os": "0.1.1", - "tunnel": "0.0.4", - "typed-rest-client": "1.2.0", - "underscore": "1.8.3" + "license": "MIT", + "engines": { + "node": ">=4" } }, - "azure-storage": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/azure-storage/-/azure-storage-2.10.3.tgz", - "integrity": "sha512-IGLs5Xj6kO8Ii90KerQrrwuJKexLgSwYC4oLWmc11mzKe7Jt2E5IVg+ZQ8K53YWZACtVTMBNO3iGuA+4ipjJxQ==", - "requires": { - "browserify-mime": "~1.2.9", - "extend": "^3.0.2", - "json-edm-parser": "0.1.2", - "md5.js": "1.3.4", - "readable-stream": "~2.0.0", - "request": "^2.86.0", - "underscore": "~1.8.3", - "uuid": "^3.0.0", - "validator": "~9.4.1", - "xml2js": "0.2.8", - "xmlbuilder": "^9.0.7" - }, + "node_modules/decompress-unzip": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", + "integrity": "sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==", + "dev": true, + "license": "MIT", "dependencies": { - "readable-stream": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", - "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "string_decoder": "~0.10.x", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - }, - "xml2js": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.2.8.tgz", - "integrity": "sha1-m4FpCTFjH/CdGVdUn69U9PmAs8I=", - "requires": { - "sax": "0.5.x" - } - } + "file-type": "^3.8.0", + "get-stream": "^2.2.0", + "pify": "^2.3.0", + "yauzl": "^2.4.2" + }, + "engines": { + "node": ">=4" } }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "node_modules/decompress-unzip/node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "babel-loader": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.6.tgz", - "integrity": "sha512-4BmWKtBOBm13uoUwd08UwjZlaw3O9GWf456R9j+5YykFZ6LUIjIKLc0zEZf+hauxPOJs96C8k6FvYD09vWzhYw==", + "node_modules/decompress-unzip/node_modules/get-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", + "integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==", "dev": true, - "requires": { - "find-cache-dir": "^2.0.0", - "loader-utils": "^1.0.2", - "mkdirp": "^0.5.1", - "pify": "^4.0.1" + "license": "MIT", + "dependencies": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-dynamic-import-node": { + "node_modules/decompress-unzip/node_modules/pify": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", - "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "dev": true, - "requires": { - "object.assign": "^4.1.0" + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-emotion": { - "version": "9.2.11", - "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-9.2.11.tgz", - "integrity": "sha512-dgCImifnOPPSeXod2znAmgc64NhaaOjGEHROR/M+lmStb3841yK1sgaDYAYMnlvWNz8GnpwIPN0VmNpbWYZ+VQ==", + "node_modules/decompress/node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@emotion/babel-utils": "^0.6.4", - "@emotion/hash": "^0.6.2", - "@emotion/memoize": "^0.6.1", - "@emotion/stylis": "^0.7.0", - "babel-plugin-macros": "^2.0.0", - "babel-plugin-syntax-jsx": "^6.18.0", - "convert-source-map": "^1.5.0", - "find-root": "^1.1.0", - "mkdirp": "^0.5.1", - "source-map": "^0.5.7", - "touch": "^2.0.1" - }, + "license": "MIT", "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" } }, - "babel-plugin-inline-json-import": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/babel-plugin-inline-json-import/-/babel-plugin-inline-json-import-0.3.2.tgz", - "integrity": "sha512-QNNJx08KjmMT25Cw7rAPQ6dlREDPiZGDyApHL8KQ9vrQHbrr4PTi7W8g1tMMZPz0jEMd39nx/eH7xjnDNxq5sA==", + "node_modules/decompress/node_modules/make-dir/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", "dev": true, - "requires": { - "decache": "^4.5.1" + "license": "MIT", + "engines": { + "node": ">=4" } }, - "babel-plugin-macros": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.6.1.tgz", - "integrity": "sha512-6W2nwiXme6j1n2erPOnmRiWfObUhWH7Qw1LMi9XZy8cj+KtESu3T6asZvtk5bMQQjX8te35o7CFueiSdL/2NmQ==", + "node_modules/decompress/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "dev": true, - "requires": { - "@babel/runtime": "^7.4.2", - "cosmiconfig": "^5.2.0", - "resolve": "^1.10.0" + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "babel-plugin-syntax-jsx": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", - "dev": true + "node_modules/deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=0.12" + } }, - "babel-plugin-transform-runtime": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz", - "integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=", + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, - "requires": { - "babel-runtime": "^6.22.0" + "optional": true, + "engines": { + "node": ">=4.0.0" } }, - "babel-polyfill": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", - "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", + "node_modules/deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "node_modules/default-require-extensions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "regenerator-runtime": "^0.10.5" - }, "dependencies": { - "core-js": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", - "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", - "dev": true - } + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - }, - "dependencies": { - "core-js": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", - "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==" - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" - } + "node_modules/default-require-extensions/node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" } }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - }, "dependencies": { - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true - } + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "bach": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", - "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", "dev": true, - "requires": { - "arr-filter": "^1.1.1", - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "array-each": "^1.0.0", - "array-initial": "^1.0.0", - "array-last": "^1.1.1", - "async-done": "^1.2.2", - "async-settle": "^1.0.0", - "now-and-later": "^2.0.0" + "engines": { + "node": ">=8" } }, - "backbone": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/backbone/-/backbone-1.2.3.tgz", - "integrity": "sha1-wiz9B/yG676uYdGJKe0RXpmdZbk=", + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, - "requires": { - "underscore": ">=1.7.0" + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", - "dev": true + "node_modules/del": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", + "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", + "dev": true, + "dependencies": { + "globby": "^11.0.1", + "graceful-fs": "^4.2.4", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.2", + "p-map": "^4.0.0", + "rimraf": "^3.0.2", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "bail": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", - "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", - "dev": true + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } }, - "balanced-match": { + "node_modules/des.js": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" } }, - "base16": { + "node_modules/detect-file": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", - "integrity": "sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=", - "dev": true - }, - "base64-arraybuffer": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", - "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", - "dev": true + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "base64-js": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", - "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" + "node_modules/detect-libc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "dev": true, + "optional": true, + "engines": { + "node": ">=8" + } }, - "base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "dev": true + "node_modules/diagnostic-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/diagnostic-channel/-/diagnostic-channel-1.1.1.tgz", + "integrity": "sha512-r2HV5qFkUICyoaKlBEpLKHjxMXATUf/l+h8UZPGBHGLy4DDiY2sOLcIctax4eRnTw5wH2jTMExLntGPJ8eOJxw==", + "dependencies": { + "semver": "^7.5.3" + } }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "requires": { - "tweetnacl": "^0.14.3" + "node_modules/diagnostic-channel-publishers": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/diagnostic-channel-publishers/-/diagnostic-channel-publishers-1.0.7.tgz", + "integrity": "sha512-SEECbY5AiVt6DfLkhkaHNeshg1CogdLLANA8xlG/TKvS+XUgvIKl7VspJGYiEdL5OUyzMVnr7o0AwB7f+/Mjtg==", + "peerDependencies": { + "diagnostic-channel": "*" } }, - "better-assert": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", - "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "dev": true, - "requires": { - "callsite": "1.0.0" + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" } }, - "bfj": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.2.tgz", - "integrity": "sha512-BmBJa4Lip6BPRINSZ0BPEIfB1wUY/9rwbwvIHQA1KjX9om29B6id0wnWXq7m3bn5JrUVjeOTnVuhPT1FiHwPGw==", + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, - "requires": { - "bluebird": "^3.5.5", - "check-types": "^8.0.3", - "hoopy": "^0.1.4", - "tryer": "^1.0.1" + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" } }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "node_modules/domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "dev": true, - "optional": true, - "requires": { - "file-uri-to-path": "1.0.0" + "engines": { + "node": ">=0.4", + "npm": ">=1.2" } }, - "bintrees": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", - "integrity": "sha1-SfiW1uhYpKSZ34XDj7OZua/4QPg=", - "dev": true + "node_modules/download": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/download/-/download-8.0.0.tgz", + "integrity": "sha512-ASRY5QhDk7FK+XrQtQyvhpDKanLluEEQtWl/J7Lxuf/b+i8RYh997QeXvL85xitrmRKVlx9c7eTrcRdq2GS4eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "archive-type": "^4.0.0", + "content-disposition": "^0.5.2", + "decompress": "^4.2.1", + "ext-name": "^5.0.0", + "file-type": "^11.1.0", + "filenamify": "^3.0.0", + "get-stream": "^4.1.0", + "got": "^8.3.1", + "make-dir": "^2.1.0", + "p-event": "^2.1.0", + "pify": "^4.0.1" + }, + "engines": { + "node": ">=10" + } }, - "bl": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", - "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "node_modules/download/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, - "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" + "license": "MIT", + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" }, + "engines": { + "node": ">=6" + } + }, + "node_modules/download/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" } }, - "blob": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", - "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, - "block-stream": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "node_modules/duplexer3": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", + "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==", "dev": true, - "requires": { - "inherits": "~2.0.0" + "license": "BSD-3-Clause" + }, + "node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" } }, - "bluebird": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", - "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", - "dev": true + "node_modules/each-props": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-3.0.0.tgz", + "integrity": "sha512-IYf1hpuWrdzse/s/YJOrFmU15lyhSzxelNVAHTEG3DtP4QsLTWZUzcUL3HMXmKQxXpa4EIrBPpwRgj0aehdvAw==", + "dev": true, + "dependencies": { + "is-plain-object": "^5.0.0", + "object.defaults": "^1.1.0" + }, + "engines": { + "node": ">= 10.13.0" + } }, - "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true + "node_modules/each-props/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", "dev": true, - "requires": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, "dependencies": { - "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "dev": true - } + "safe-buffer": "^5.0.1" } }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "node_modules/electron-to-chromium": { + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", "dev": true }, - "bootstrap": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.3.1.tgz", - "integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==", - "dev": true + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } }, - "bootstrap-less": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/bootstrap-less/-/bootstrap-less-3.3.8.tgz", - "integrity": "sha1-cfKd1af//t/onxYFu63+CjONrlM=", + "node_modules/emitter-listener": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz", + "integrity": "sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==", + "dependencies": { + "shimmer": "^1.2.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" } }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "once": "^1.4.0" } }, - "brfs": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brfs/-/brfs-2.0.2.tgz", - "integrity": "sha512-IrFjVtwu4eTJZyu8w/V2gxU7iLTtcHih67sgEdzrhjLBMHp2uYefUBfdM4k2UvcuWMgV7PQDZHSLeNWnLFKWVQ==", - "requires": { - "quote-stream": "^1.0.1", - "resolve": "^1.1.5", - "static-module": "^3.0.2", - "through2": "^2.0.0" - }, + "node_modules/enhanced-resolve": { + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", + "dev": true, "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "static-module": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/static-module/-/static-module-3.0.3.tgz", - "integrity": "sha512-RDaMYaI5o/ym0GkCqL/PlD1Pn216omp8fY81okxZ6f6JQxWW5tptOw9reXoZX85yt/scYvbWIt6uoszeyf+/MQ==", - "requires": { - "acorn-node": "^1.3.0", - "concat-stream": "~1.6.0", - "convert-source-map": "^1.5.1", - "duplexer2": "~0.1.4", - "escodegen": "~1.9.0", - "has": "^1.0.1", - "magic-string": "^0.22.4", - "merge-source-map": "1.0.4", - "object-inspect": "~1.4.0", - "readable-stream": "~2.3.3", - "scope-analyzer": "^2.0.1", - "shallow-copy": "~0.0.1", - "static-eval": "^2.0.2", - "through2": "~2.0.3" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" } }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true + "node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } }, - "brotli": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.2.tgz", - "integrity": "sha1-UlqcrU/LqWR119OI9q7LE+7VL0Y=", - "requires": { - "base64-js": "^1.1.2" + "node_modules/envinfo": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", + "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" } }, - "browser-process-hrtime": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", - "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "dev": true }, - "browser-resolve": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", - "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", - "requires": { - "resolve": "1.1.7" - }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", "dependencies": { - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" - } + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" } }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } }, - "browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", "dev": true, - "requires": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "dependencies": { + "hasown": "^2.0.0" } }, - "browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, - "requires": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "node_modules/es6-object-assign": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", + "integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=", + "dev": true + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" + "engines": { + "node": ">=6" } }, - "browserify-mime": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/browserify-mime/-/browserify-mime-1.2.9.tgz", - "integrity": "sha1-rrGvKN5sDXpqLOQK22j/GEIq8x8=" + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } }, - "browserify-optional": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-optional/-/browserify-optional-1.0.1.tgz", - "integrity": "sha1-HhNyLP3g2F8SFnbCpyztUzoBiGk=", - "requires": { - "ast-transform": "0.0.0", - "ast-types": "^0.7.0", - "browser-resolve": "^1.8.1" + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "browserify-rsa": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", - "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "node_modules/eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", "dev": true, - "requires": { - "bn.js": "^4.1.0", - "randombytes": "^2.0.1" + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" } }, - "browserify-sign": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", - "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dev": true, - "requires": { - "bn.js": "^4.1.1", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.2", - "elliptic": "^6.0.0", - "inherits": "^2.0.1", - "parse-asn1": "^5.0.0" + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" } }, - "browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "requires": { - "pako": "~1.0.5" - }, "dependencies": { - "pako": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", - "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", - "dev": true - } + "ms": "^2.1.1" } }, - "browserslist": { - "version": "4.6.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.4.tgz", - "integrity": "sha512-ErJT8qGfRt/VWHSr1HeqZzz50DvxHtr1fVL1m5wf20aGrG8e1ce8fpZ2EjZEfs09DDZYSvtRaDlMpWslBf8Low==", + "node_modules/eslint-module-utils": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", "dev": true, - "requires": { - "caniuse-lite": "^1.0.30000981", - "electron-to-chromium": "^1.3.188", - "node-releases": "^1.1.25" + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", - "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" } }, - "buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "node_modules/eslint-plugin-import": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, - "requires": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, - "buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", - "dev": true + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } }, - "buffer-equal": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", - "integrity": "sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs=" + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } }, - "buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", - "dev": true + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.5.1.tgz", + "integrity": "sha512-sVCFKX9fllURnXT2JwLN5Qgo24Ug5NF6dxhkmxsMEUZhXRcGg+X3e1JbJ84YePQKBl5E0ZjAH5Q4rkdcGY99+g==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.16.3", + "aria-query": "^4.2.2", + "array-includes": "^3.1.4", + "ast-types-flow": "^0.0.7", + "axe-core": "^4.3.5", + "axobject-query": "^2.2.0", + "damerau-levenshtein": "^1.0.7", + "emoji-regex": "^9.2.2", + "has": "^1.0.3", + "jsx-ast-utils": "^3.2.1", + "language-tags": "^1.0.5", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + "node_modules/eslint-plugin-jsx-a11y/node_modules/aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + }, + "engines": { + "node": ">=6.0" + } }, - "buffer-json": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/buffer-json/-/buffer-json-2.0.0.tgz", - "integrity": "sha512-+jjPFVqyfF1esi9fvfUs3NqM0pH1ziZ36VP4hmA/y/Ssfo/5w5xHKfTw9BwQjoJ1w/oVtpLomqwUHKdefGyuHw==", + "node_modules/eslint-plugin-jsx-a11y/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, - "buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true + "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true + "node_modules/eslint-plugin-no-only-tests": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-only-tests/-/eslint-plugin-no-only-tests-3.3.0.tgz", + "integrity": "sha512-brcKcxGnISN2CcVhXJ/kEQlNa0MEfGRtwKtWA16SkqXHKitaKIMrfemJKLKX1YqDU5C/5JY3PvZXd5jEW04e0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=5.0.0" + } }, - "builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", - "dev": true + "node_modules/eslint-plugin-react": { + "version": "7.29.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.29.4.tgz", + "integrity": "sha512-CVCXajliVh509PcZYRFyu/BoUEz452+jtQJq2b3Bae4v3xBUWPLCmtmBM+ZinG4MzwmxJgJ2M5rMqhqLVn7MtQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.4", + "array.prototype.flatmap": "^1.2.5", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.5", + "object.fromentries": "^2.0.5", + "object.hasown": "^1.1.0", + "object.values": "^1.1.5", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.3", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.6" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } }, - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "dev": true + "node_modules/eslint-plugin-react-hooks": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.4.0.tgz", + "integrity": "sha512-U3RVIfdzJaeKDQKEJbz5p3NW8/L80PCATJAfuojwbaEL+gBjfGdhUcGde+WGUW46Q5sr/NgxevsIiDtNXrvZaQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } }, - "cacache": { - "version": "12.0.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz", - "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==", + "node_modules/eslint-plugin-react/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "requires": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.3", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz", + "integrity": "sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==", + "dev": true, "dependencies": { - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - } + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" + "bin": { + "semver": "bin/semver.js" } }, - "cache-loader": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cache-loader/-/cache-loader-4.1.0.tgz", - "integrity": "sha512-ftOayxve0PwKzBF/GLsZNC9fJBXl8lkZE3TOsjkboHfVHVkL39iUEs1FO07A33mizmci5Dudt38UZrrYXDtbhw==", + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, - "requires": { - "buffer-json": "^2.0.0", - "find-cache-dir": "^3.0.0", - "loader-utils": "^1.2.3", - "mkdirp": "^0.5.1", - "neo-async": "^2.6.1", - "schema-utils": "^2.0.0" + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, "dependencies": { - "ajv": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", - "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", - "dev": true - }, - "find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "schema-utils": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.6.tgz", - "integrity": "sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA==", - "dev": true, - "requires": { - "ajv": "^6.12.0", - "ajv-keywords": "^3.4.1" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "cacheable-request": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", - "integrity": "sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0=", + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, - "requires": { - "clone-response": "1.0.2", - "get-stream": "3.0.0", - "http-cache-semantics": "3.8.1", - "keyv": "3.0.0", - "lowercase-keys": "1.0.0", - "normalize-url": "2.0.1", - "responselike": "1.0.2" + "license": "Python-2.0" + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { - "lowercase-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", - "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=", - "dev": true - } + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "caching-transform": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", - "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, - "requires": { - "hasha": "^5.0.0", - "make-dir": "^3.0.0", - "package-hash": "^4.0.0", - "write-file-atomic": "^3.0.0" + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/eslint/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, "dependencies": { - "make-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", - "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true } } }, - "caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "node_modules/eslint/node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, - "requires": { - "callsites": "^2.0.0" + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" } }, - "caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, - "requires": { - "caller-callsite": "^2.0.0" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "callsite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", - "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", - "dev": true + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } }, - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", - "dev": true + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } }, - "camel-case": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", - "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, - "requires": { - "no-case": "^2.2.0", - "upper-case": "^1.1.1" + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } }, - "caniuse-api": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-2.0.0.tgz", - "integrity": "sha1-sd21pZZrFvSNxJmERNS7xsfZ2DQ=", + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, - "requires": { - "browserslist": "^2.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, "dependencies": { - "browserslist": { - "version": "2.11.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", - "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30000792", - "electron-to-chromium": "^1.3.30" - } - } + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "caniuse-lite": { - "version": "1.0.30000983", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000983.tgz", - "integrity": "sha512-/llD1bZ6qwNkt41AsvjsmwNOoA4ZB+8iqmf5LVyeSXuBODT/hAMFNVOh84NdUzoiYiSKqo5vQ3ZzeYHSi/olDQ==", - "dev": true + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "canvas": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.6.0.tgz", - "integrity": "sha512-bEO9f1ThmbknLPxCa8Es7obPlN9W3stB1bo7njlhOFKIdUTldeTqXCh9YclCPAi2pSQs84XA0jq/QEZXSzgyMw==", + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "requires": { - "nan": "^2.14.0", - "node-pre-gyp": "^0.11.0", - "simple-get": "^3.0.3" + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "caw": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", - "integrity": "sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==", + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, - "requires": { - "get-proxy": "^2.0.0", - "isurl": "^1.0.0-alpha5", - "tunnel-agent": "^0.6.0", - "url-to-options": "^1.0.1" + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "chai": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", - "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "node_modules/eslint/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.0", - "type-detect": "^4.0.5" + "engines": { + "node": ">=8" } }, - "chai-arrays": { + "node_modules/eslint/node_modules/shebang-command": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chai-arrays/-/chai-arrays-2.0.0.tgz", - "integrity": "sha512-jWAvZu1BV8tL3pj0iosBECzzHEg+XB1zSnMjJGX83bGi/1GlGdDO7J/A0sbBBS6KJT0FVqZIzZW9C6WLiMkHpQ==", - "dev": true + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } }, - "chai-as-promised": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", - "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "node_modules/eslint/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, - "requires": { - "check-error": "^1.0.2" + "engines": { + "node": ">=8" } }, - "chai-http": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/chai-http/-/chai-http-4.3.0.tgz", - "integrity": "sha512-zFTxlN7HLMv+7+SPXZdkd5wUlK+KxH6Q7bIEMiEx0FK3zuuMqL7cwICAQ0V1+yYRozBburYuxN1qZstgHpFZQg==", + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "requires": { - "@types/chai": "4", - "@types/superagent": "^3.8.3", - "cookiejar": "^2.1.1", - "is-ip": "^2.0.0", - "methods": "^1.1.2", - "qs": "^6.5.1", - "superagent": "^3.7.0" + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "character-entities": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", - "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", - "dev": true + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } }, - "character-entities-legacy": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", - "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", - "dev": true + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } }, - "character-reference-invalid": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", - "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", - "dev": true + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } }, - "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", - "dev": true + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } }, - "charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true - }, - "check-types": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz", - "integrity": "sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ==", - "dev": true - }, - "cheerio": { - "version": "1.0.0-rc.3", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", - "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "requires": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.1", - "entities": "~1.1.1", - "htmlparser2": "^3.9.1", - "lodash": "^4.15.0", - "parse5": "^3.0.1" - }, - "dependencies": { - "parse5": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", - "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", - "dev": true, - "requires": { - "@types/node": "*" - } - } + "engines": { + "node": ">=4.0" } }, - "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" + "engines": { + "node": ">=4.0" } }, - "chownr": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", - "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==", - "dev": true - }, - "chrome-trace-event": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", - "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, - "requires": { - "tslib": "^1.9.0" + "engines": { + "node": ">=0.10.0" } }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "engines": { + "node": ">=0.8.x" } }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "dev": true - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" } }, - "classnames": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", - "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } }, - "clean-css": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", - "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "node_modules/execa/node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, - "requires": { - "source-map": "~0.6.0" + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" } }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" + "node_modules/execa/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "node_modules/execa/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, - "requires": { - "restore-cursor": "^2.0.0" + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true + "node_modules/execa/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "node_modules/execa/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" + "node_modules/execa/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", - "dev": true + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6" + } }, - "clone-deep": { + "node_modules/expand-tilde": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz", - "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", "dev": true, - "requires": { - "for-own": "^1.0.0", - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.0", - "shallow-clone": "^1.0.0" + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expose-loader": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-3.1.0.tgz", + "integrity": "sha512-2RExSo0yJiqP+xiUue13jQa2IHE8kLDzTI7b6kn+vUlBVvlzNSiLDzo4e5Pp5J039usvTUnxZ8sUOhv0Kg15NA==", + "dev": true, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/ext-list": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", + "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", + "dev": true, + "license": "MIT", "dependencies": { - "for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - } + "mime-db": "^1.28.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "requires": { - "mimic-response": "^1.0.0" + "node_modules/ext-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", + "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ext-list": "^2.0.0", + "sort-keys-length": "^1.0.0" + }, + "engines": { + "node": ">=4" } }, - "clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, - "cloneable-readable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", - "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, - "requires": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" - }, "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "cls-hooked": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/cls-hooked/-/cls-hooked-4.2.2.tgz", - "integrity": "sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw==", - "requires": { - "async-hook-jl": "^1.7.6", - "emitter-listener": "^1.0.1", - "semver": "^5.4.1" + "node_modules/extend-shallow/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" } }, - "clsx": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.0.4.tgz", - "integrity": "sha512-1mQ557MIZTrL/140j+JVdRM6e31/OA4vTYxXgqIIZlndyfjHpyawKZia1Im05Vp9BWmImkcNrNtFYQMyFcgJDg==", - "dev": true - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", "dev": true }, - "codecov": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/codecov/-/codecov-3.7.1.tgz", - "integrity": "sha512-JHWxyPTkMLLJn9SmKJnwAnvY09kg2Os2+Ux+GG7LwZ9g8gzDDISpIN5wAsH1UBaafA/yGcd3KofMaorE8qd6Lw==", + "node_modules/fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", "dev": true, - "requires": { - "argv": "0.0.2", - "ignore-walk": "3.0.3", - "js-yaml": "3.13.1", - "teeny-request": "6.0.1", - "urlgrey": "0.4.4" + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "dependencies": { - "ignore-walk": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", - "dev": true, - "requires": { - "minimatch": "^3.0.4" - } - } + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" } }, - "codemirror": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.47.0.tgz", - "integrity": "sha512-kV49Fr+NGFHFc/Imsx6g180hSlkGhuHxTSDDmDHOuyln0MQYFLixDY4+bFkBVeCEiepYfDimAF/e++9jPJk4QA==", + "node_modules/fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", "dev": true }, - "collapse-white-space": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", - "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==", + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, - "collection-map": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", - "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", "dev": true, - "requires": { - "arr-map": "^2.0.2", - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "dependencies": { - "for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" } - } + ] }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "node_modules/fastest-levenshtein": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", + "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" + "dependencies": { + "reusify": "^1.0.4" } }, - "color": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", - "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", - "requires": { - "color-convert": "^1.9.1", - "color-string": "^1.5.2" + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "dependencies": { + "pend": "~1.2.0" } }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" } }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "node_modules/file-type": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-11.1.0.tgz", + "integrity": "sha512-rM0UO7Qm9K7TWTtA6AShI/t7H5BPjDeGVDaNyg9BjHAj3PysKy7+8C8D137R88jnR3rFJZQB/tFgydl5sN5m7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } }, - "color-string": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", - "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", - "requires": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" + "node_modules/filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" } }, - "color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true + "node_modules/filenamify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-3.0.0.tgz", + "integrity": "sha512-5EFZ//MsvJgXjBAFJ+Bh2YaCTRF/VP1YOmGrgt+KJ4SFRLjI87EIdwLLuT6wQX0I4F9W41xutobzczjsOKlI/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.0", + "trim-repeated": "^1.0.0" + }, + "engines": { + "node": ">=6" + } }, - "colornames": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", - "integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=" + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } }, - "colors": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", - "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==" + "node_modules/filter-obj": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-2.0.2.tgz", + "integrity": "sha512-lO3ttPjHZRfjMcxWKb1j1eDhTFsu4meeR3lnMcnBFhk6RuLhvEiuALu2TlfL310ph4lCYYwgF/ElIjdP739tdg==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "colorspace": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", - "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", - "requires": { - "color": "3.0.x", - "text-hex": "1.0.x" + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" } }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "commander": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", - "dev": true + "node_modules/findup-sync": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz", + "integrity": "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==", + "dev": true, + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.3", + "micromatch": "^4.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 10.13.0" + } }, - "commandpost": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/commandpost/-/commandpost-1.4.0.tgz", - "integrity": "sha512-aE2Y4MTFJ870NuB/+2z1cXBhSBBzRydVVjzhFC4gtenEhpnj15yu0qptWGJsO9YGrcPZ3ezX8AWb1VA391MKpQ==", - "dev": true + "node_modules/fined": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-2.0.0.tgz", + "integrity": "sha512-OFRzsL6ZMHz5s0JrsEr+TpdGNCtrVtnuG3x1yzGNiQHT0yaDnXAj8V/lWcpJVrnoDpcwXcASxAZYbuXda2Y82A==", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^5.0.0", + "object.defaults": "^1.1.0", + "object.pick": "^1.3.0", + "parse-filepath": "^1.0.2" + }, + "engines": { + "node": ">= 10.13.0" + } }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true + "node_modules/fined/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "compare-module-exports": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/compare-module-exports/-/compare-module-exports-2.1.0.tgz", - "integrity": "sha512-3Lc0sTIuX1jmY2K2RrXRJOND6KsRTX2D4v3+eu1PDptsuJZVK4LZc852eZa9I+avj0NrUKlTNgqvccNOH6mbGg==", - "dev": true + "node_modules/flagged-respawn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-2.0.0.tgz", + "integrity": "sha512-Gq/a6YCi8zexmGHMuJwahTGzXlAZAOsbCVKduWXC6TlLCjjFRlExMJc4GC2NYPYZ0r/brw9P7CpRgQmlPVeOoA==", + "dev": true, + "engines": { + "node": ">= 10.13.0" + } }, - "component-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", - "dev": true + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } }, - "component-inherit": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true }, - "compress-commons": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-2.1.1.tgz", - "integrity": "sha512-eVw6n7CnEMFzc3duyFVrQEuY1BlHR3rYsSztyG32ibGMW722i3C6IizEGMFmfMU+A+fALvBIwxN3czffTcdA+Q==", + "node_modules/flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", "dev": true, - "requires": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^3.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^2.3.6" - }, "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" } }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "config-chain": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", - "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", "dev": true, - "requires": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" + "engines": { + "node": ">=0.10.0" } }, - "confusing-browser-globals": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz", - "integrity": "sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw==", - "dev": true - }, - "console-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", - "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "node_modules/for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", "dev": true, - "requires": { - "date-now": "^0.1.4" + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true - }, - "constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } }, - "contains-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", - "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", - "dev": true + "node_modules/foreground-child/node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } }, - "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "node_modules/foreground-child/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, - "requires": { - "safe-buffer": "5.1.2" + "engines": { + "node": ">=8" } }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true + "node_modules/foreground-child/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } }, - "continuation-local-storage": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz", - "integrity": "sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==", - "requires": { - "async-listener": "^0.6.0", - "emitter-listener": "^1.1.1" + "node_modules/foreground-child/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" } }, - "convert-source-map": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", - "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", - "requires": { - "safe-buffer": "~5.1.1" + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" } }, - "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", - "dev": true + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "node_modules/fromentries": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.2.0.tgz", + "integrity": "sha512-33X7H/wdfO99GdRLLgkjUrD4geAFdq/Uv0kl3HD4da6HDixd2GUg8Mw7dahLCV9r/EARkmtYBB6Tch4EEokFTQ==", "dev": true }, - "cookiejar": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "dev": true }, - "copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "dev": true, - "requires": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" - }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" } }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true + "node_modules/fs-extra/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } }, - "copy-props": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz", - "integrity": "sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==", - "dev": true, - "requires": { - "each-props": "^1.3.0", - "is-plain-object": "^2.0.1" + "node_modules/fs-extra/node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "engines": { + "node": ">= 10.0.0" } }, - "copy-webpack-plugin": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.1.1.tgz", - "integrity": "sha512-P15M5ZC8dyCjQHWwd4Ia/dm0SgVvZJMYeykVIVYXbGyqO4dWB5oyPHp9i7wjwo5LhtlhKbiBCdS2NvM07Wlybg==", + "node_modules/fs-mkdirp-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", + "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", "dev": true, - "requires": { - "cacache": "^12.0.3", - "find-cache-dir": "^2.1.0", - "glob-parent": "^3.1.0", - "globby": "^7.1.1", - "is-glob": "^4.0.1", - "loader-utils": "^1.2.3", - "minimatch": "^3.0.4", - "normalize-path": "^3.0.0", - "p-limit": "^2.2.1", - "schema-utils": "^1.0.0", - "serialize-javascript": "^2.1.2", - "webpack-log": "^2.0.0" - }, "dependencies": { - "ansi-colors": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", - "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", - "dev": true - }, - "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "serialize-javascript": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", - "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", - "dev": true - }, - "webpack-log": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", - "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", - "dev": true, - "requires": { - "ansi-colors": "^3.0.0", - "uuid": "^3.3.2" - } - } + "graceful-fs": "^4.1.11", + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" } }, - "core-js": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", - "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=", - "dev": true - }, - "core-js-compat": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.1.4.tgz", - "integrity": "sha512-Z5zbO9f1d0YrJdoaQhphVAnKPimX92D6z8lCGphH89MNRxlL1prI9ExJPqVwP0/kgkQCv8c4GJGT8X16yUncOg==", + "node_modules/fs-walk": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/fs-walk/-/fs-walk-0.0.1.tgz", + "integrity": "sha1-9/yRw64e6tB8mYvF0N1B8tvr0zU=", "dev": true, - "requires": { - "browserslist": "^4.6.2", - "core-js-pure": "3.1.4", - "semver": "^6.1.1" - }, "dependencies": { - "semver": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.2.0.tgz", - "integrity": "sha512-jdFC1VdUGT/2Scgbimf7FSx9iJLXoqfglSF+gJeuNWVpiE37OIbc1jywR/GJyFdz3mnkz2/id0L0J/cr0izR5A==", - "dev": true - } + "async": "*" } }, - "core-js-pure": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.1.4.tgz", - "integrity": "sha512-uJ4Z7iPNwiu1foygbcZYJsJs1jiXrTTCvxfLDXNhI/I+NHbSIEyr548y4fcsCEyWY0XgfAG/qqaunJ1SThHenA==", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, - "cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, - "requires": { - "object-assign": "^4", - "vary": "^1" + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "dev": true, - "requires": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "cp-file": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-6.2.0.tgz", - "integrity": "sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA==", + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "make-dir": "^2.0.0", - "nested-error-stacks": "^2.0.0", - "pify": "^4.0.1", - "safe-buffer": "^5.0.1" + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "cpx-fixed": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/cpx-fixed/-/cpx-fixed-1.6.0.tgz", - "integrity": "sha512-0X6cfGWup886yM/ma/3I2DASv+QSDhlvPoSzHi1DZrYwl7QIFvSKtk60i2kTyRvUbxzrqLwP1At/1TSD2pGCqg==", + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true, - "requires": { - "co": "^4.6.0", - "debounce": "^1.1.0", - "debug": "^3.1.0", - "duplexer": "^0.1.1", - "fs-extra": "^6.0.1", - "glob": "^7.1.2", - "glob2base": "0.0.12", - "minimatch": "^3.0.4", - "resolve": "^1.8.1", - "safe-buffer": "^5.1.2", - "shell-quote": "^1.6.1", - "subarg": "^1.0.0" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "fs-extra": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", - "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "crc": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", - "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, - "requires": { - "buffer": "^5.1.0" + "engines": { + "node": ">=6.9.0" } }, - "crc32-stream": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-3.0.1.tgz", - "integrity": "sha512-mctvpXlbzsvK+6z8kJwSJ5crm7yBwrQMTybJzMw1O4lLGJqjlDCXY2Zw7KheiA6XBEcBmfLx1D88mjRGVJtY9w==", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, - "requires": { - "crc": "^3.4.4", - "readable-stream": "^3.4.0" + "engines": { + "node": "6.* || 8.* || >= 10.*" } }, - "create-ecdh": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", - "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, - "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.0.0" + "engines": { + "node": "*" } }, - "create-emotion": { - "version": "9.2.12", - "resolved": "https://registry.npmjs.org/create-emotion/-/create-emotion-9.2.12.tgz", - "integrity": "sha512-P57uOF9NL2y98Xrbl2OuiDQUZ30GVmASsv5fbsjF4Hlraip2kyAvMm+2PoYUvFFw03Fhgtxk3RqZSm2/qHL9hA==", - "dev": true, - "requires": { - "@emotion/hash": "^0.6.2", - "@emotion/memoize": "^0.6.1", - "@emotion/stylis": "^0.7.0", - "@emotion/unitless": "^0.6.2", - "csstype": "^2.5.2", - "stylis": "^3.5.0", - "stylis-rule-sheet": "^0.0.10" + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" + "engines": { + "node": ">=8.0.0" } }, - "create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "node_modules/get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", "dev": true, - "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "create-react-context": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.3.0.tgz", - "integrity": "sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw==", - "dev": true, - "requires": { - "gud": "^1.0.0", - "warning": "^4.0.3" + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" } }, - "cross-env": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-6.0.3.tgz", - "integrity": "sha512-+KqxF6LCvfhWvADcDPqo64yVIB31gv/jQulX2NGzKS/g3GEVz6/pt4wjHFtFWsHMddebWD/sDthJemzM4MaAag==", + "node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", "dev": true, - "requires": { - "cross-spawn": "^7.0.0" - }, + "license": "MIT", "dependencies": { - "cross-spawn": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", - "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" } }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "node_modules/get-stream/node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, - "cross-spawn-async": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/cross-spawn-async/-/cross-spawn-async-2.2.5.tgz", - "integrity": "sha1-hF/wwINKPe2dFg2sptOQkGuyiMw=", + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dev": true, - "requires": { - "lru-cache": "^4.0.0", - "which": "^1.2.8" + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" - }, - "crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", "dev": true, - "requires": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - } + "optional": true }, - "crypto-js": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz", - "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==" + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "css": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", - "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", + "node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", "dev": true, - "requires": { - "inherits": "^2.0.3", - "source-map": "^0.6.1", - "source-map-resolve": "^0.5.2", - "urix": "^0.1.0" + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" } }, - "css-color-function": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/css-color-function/-/css-color-function-1.3.3.tgz", - "integrity": "sha1-jtJMLAIFBzM5+voAS8jBQfzLKC4=", + "node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, - "requires": { - "balanced-match": "0.1.0", - "color": "^0.11.0", - "debug": "^3.1.0", - "rgb": "~0.1.0" + "dependencies": { + "is-extglob": "^2.1.0" }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha512-uMbLGAP3S2aDOHUDfdoYcdIePUCfysbAd0IAoWVZbeGU/oNQ8asHVSshLDJUPWxfzj8zsCG7/XeHPHTtow0nsw==", + "dev": true, "dependencies": { - "balanced-match": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.1.0.tgz", - "integrity": "sha1-tQS9BYabOSWd0MXvw12EMXbczEo=", - "dev": true - }, - "color": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/color/-/color-0.11.4.tgz", - "integrity": "sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=", - "dev": true, - "requires": { - "clone": "^1.0.2", - "color-convert": "^1.3.0", - "color-string": "^0.3.0" - } - }, - "color-string": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", - "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=", - "dev": true, - "requires": { - "color-name": "^1.0.0" - } - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } + "extend": "^3.0.0", + "glob": "^7.1.1", + "glob-parent": "^3.1.0", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.0", + "pumpify": "^1.3.5", + "readable-stream": "^2.1.5", + "remove-trailing-separator": "^1.0.1", + "to-absolute-glob": "^2.0.0", + "unique-stream": "^2.0.2" + }, + "engines": { + "node": ">= 0.10" } }, - "css-loader": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-1.0.1.tgz", - "integrity": "sha512-+ZHAZm/yqvJ2kDtPne3uX0C+Vr3Zn5jFn2N4HywtS5ujwvsVkyg0VArEXpl3BgczDA8anieki1FIzhchX4yrDw==", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "css-selector-tokenizer": "^0.7.0", - "icss-utils": "^2.1.0", - "loader-utils": "^1.0.2", - "lodash": "^4.17.11", - "postcss": "^6.0.23", - "postcss-modules-extract-imports": "^1.2.0", - "postcss-modules-local-by-default": "^1.2.0", - "postcss-modules-scope": "^1.1.0", - "postcss-modules-values": "^1.3.0", - "postcss-value-parser": "^3.3.0", - "source-list-map": "^2.0.0" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - } - } + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true }, - "css-select": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "node_modules/glob-watcher": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-6.0.0.tgz", + "integrity": "sha512-wGM28Ehmcnk2NqRORXFOTOR064L4imSw3EeOqU5bIwUf62eXGwg89WivH6VMahL8zlQHeodzvHpXplrqzrz3Nw==", "dev": true, - "requires": { - "boolbase": "~1.0.0", - "css-what": "2.1", - "domutils": "1.5.1", - "nth-check": "~1.0.1" + "dependencies": { + "async-done": "^2.0.0", + "chokidar": "^3.5.3" }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", "dependencies": { - "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "dev": true, - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - } + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "css-selector-tokenizer": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz", - "integrity": "sha512-xYL0AMZJ4gFzJQsHUKa5jiWWi2vH77WVNg7JYRyewwj6oPh4yb/y6Y9ZCw9dsj/9UauMhtuxR+ogQd//EdEVNA==", + "node_modules/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", "dev": true, - "requires": { - "cssesc": "^0.1.0", - "fastparse": "^1.1.1", - "regexpu-core": "^1.0.0" - }, "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - }, - "regexpu-core": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", - "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", - "dev": true, - "requires": { - "regenerate": "^1.2.1", - "regjsgen": "^0.2.0", - "regjsparser": "^0.1.4" - } - }, - "regjsgen": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", - "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", - "dev": true - }, - "regjsparser": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", - "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", - "dev": true, - "requires": { - "jsesc": "~0.5.0" - } - } + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "css-unit-converter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.1.tgz", - "integrity": "sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY=", - "dev": true - }, - "css-what": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", - "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", - "dev": true - }, - "cssesc": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", - "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", - "dev": true - }, - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - }, - "cssstyle": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.3.0.tgz", - "integrity": "sha512-wXsoRfsRfsLVNaVzoKdqvEmK/5PFaEXNspVT22Ots6K/cnJdpoDKuQFw+qlMiXnmaif1OgeC466X1zISgAOcGg==", + "node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", "dev": true, - "requires": { - "cssom": "~0.3.6" + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" } }, - "csstype": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.6.tgz", - "integrity": "sha512-RpFbQGUE74iyPgvr46U9t1xoQBM8T4BL8SxrN66Le2xYAPSaDJJKeztV3awugusb3g3G9iL8StmkBBXhcbbXhg==", - "dev": true - }, - "cyclist": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", - "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", - "dev": true - }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" } }, - "d3": { - "version": "3.5.17", - "resolved": "https://registry.npmjs.org/d3/-/d3-3.5.17.tgz", - "integrity": "sha1-vEZ0gAQ3iyGjYMn8fPUjF5B2L7g=", - "dev": true - }, - "d3-array": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", - "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==", - "dev": true - }, - "d3-bboxCollide": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/d3-bboxCollide/-/d3-bboxCollide-1.0.4.tgz", - "integrity": "sha512-Sc8FKGGeejlowLW1g/0WBrVcbd++SBRW4N8OuZhVeRAfwlTL96+75JKlFfHweYdYRui1zPabfNXZrNaphBjS+w==", + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true, - "requires": { - "d3-quadtree": "1.0.1" + "engines": { + "node": ">=4" } }, - "d3-brush": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.0.6.tgz", - "integrity": "sha512-lGSiF5SoSqO5/mYGD5FAeGKKS62JdA1EV7HPrU2b5rTX4qEJJtpjaGLJngjnkewQy7UnGstnFd3168wpf5z76w==", + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "dev": true, - "requires": { - "d3-dispatch": "1", - "d3-drag": "1", - "d3-interpolate": "1", - "d3-selection": "1", - "d3-transition": "1" + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "d3-chord": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz", - "integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==", + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, - "requires": { - "d3-array": "1", - "d3-path": "1" + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "d3-cloud": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/d3-cloud/-/d3-cloud-1.2.5.tgz", - "integrity": "sha512-4s2hXZgvs0CoUIw31oBAGrHt9Kt/7P9Ik5HIVzISFiWkD0Ga2VLAuO/emO/z1tYIpE7KG2smB4PhMPfFMJpahw==", + "node_modules/glogg": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-2.2.0.tgz", + "integrity": "sha512-eWv1ds/zAlz+M1ioHsyKJomfY7jbDDPpwSkv14KQj89bycx1nvK5/2Cj/T9g7kzJcX5Bc7Yv22FjfBZS/jl94A==", "dev": true, - "requires": { - "d3-dispatch": "^1.0.3" + "dependencies": { + "sparkles": "^2.1.0" + }, + "engines": { + "node": ">= 10.13.0" } }, - "d3-collection": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", - "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==", - "dev": true - }, - "d3-color": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.2.8.tgz", - "integrity": "sha512-yeANXzP37PHk0DbSTMNPhnJD+Nn4G//O5E825bR6fAfHH43hobSBpgB9G9oWVl9+XgUaQ4yCnsX1H+l8DoaL9A==", - "dev": true + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "d3-contour": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz", - "integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==", + "node_modules/got": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", + "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==", "dev": true, - "requires": { - "d3-array": "^1.1.1" + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^0.7.0", + "cacheable-request": "^2.1.1", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "into-stream": "^3.1.0", + "is-retry-allowed": "^1.1.0", + "isurl": "^1.0.0-alpha5", + "lowercase-keys": "^1.0.0", + "mimic-response": "^1.0.0", + "p-cancelable": "^0.4.0", + "p-timeout": "^2.0.1", + "pify": "^3.0.0", + "safe-buffer": "^5.1.1", + "timed-out": "^4.0.1", + "url-parse-lax": "^3.0.0", + "url-to-options": "^1.0.1" + }, + "engines": { + "node": ">=4" } }, - "d3-delaunay": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-5.1.6.tgz", - "integrity": "sha512-VF6bxon2bn1cdXuesInEtVKlE4aUfq5IjE5y0Jl2aZP1yvLsf0QENqQxNhjS4vq95EmYKauA30ofTwvREtPSXA==", + "node_modules/got/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", "dev": true, - "requires": { - "delaunator": "4" + "license": "MIT", + "engines": { + "node": ">=4" } }, - "d3-dispatch": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.5.tgz", - "integrity": "sha512-vwKx+lAqB1UuCeklr6Jh1bvC4SZgbSqbkGBLClItFBIYH4vqDJCA7qfoy14lXmJdnBOdxndAMxjCbImJYW7e6g==", - "dev": true - }, - "d3-drag": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.3.tgz", - "integrity": "sha512-8S3HWCAg+ilzjJsNtWW1Mutl74Nmzhb9yU6igspilaJzeZVFktmY6oO9xOh5TDk+BM2KrNFjttZNoJJmDnkjkg==", + "node_modules/got/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", "dev": true, - "requires": { - "d3-dispatch": "1", - "d3-selection": "1" + "license": "MIT", + "engines": { + "node": ">=4" } }, - "d3-dsv": { - "version": "0.1.14", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-0.1.14.tgz", - "integrity": "sha1-mDPNYaWj6B4DJjoc54903lah27g=", - "dev": true + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, - "d3-ease": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.5.tgz", - "integrity": "sha512-Ct1O//ly5y5lFM9YTdu+ygq7LleSgSE4oj7vUt9tPLHUi8VCV7QoizGpdWRWAwCO9LdYzIrQDg97+hGVdsSGPQ==", + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, - "d3-force": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz", - "integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==", + "node_modules/gulp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-5.0.0.tgz", + "integrity": "sha512-S8Z8066SSileaYw1S2N1I64IUc/myI2bqe2ihOBzO6+nKpvNSg7ZcWJt/AwF8LC/NVN+/QZ560Cb/5OPsyhkhg==", "dev": true, - "requires": { - "d3-collection": "1", - "d3-dispatch": "1", - "d3-quadtree": "1", - "d3-timer": "1" + "dependencies": { + "glob-watcher": "^6.0.0", + "gulp-cli": "^3.0.0", + "undertaker": "^2.0.0", + "vinyl-fs": "^4.0.0" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">=10.13.0" } }, - "d3-format": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.3.2.tgz", - "integrity": "sha512-Z18Dprj96ExragQ0DeGi+SYPQ7pPfRMtUXtsg/ChVIKNBCzjO8XYJvRTC1usblx52lqge56V5ect+frYTQc8WQ==", - "dev": true + "node_modules/gulp-cli": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-3.0.0.tgz", + "integrity": "sha512-RtMIitkT8DEMZZygHK2vEuLPqLPAFB4sntSxg4NoDta7ciwGZ18l7JuhCTiS5deOJi2IoK0btE+hs6R4sfj7AA==", + "dev": true, + "dependencies": { + "@gulpjs/messages": "^1.1.0", + "chalk": "^4.1.2", + "copy-props": "^4.0.0", + "gulplog": "^2.2.0", + "interpret": "^3.1.1", + "liftoff": "^5.0.0", + "mute-stdout": "^2.0.0", + "replace-homedir": "^2.0.0", + "semver-greatest-satisfied-range": "^2.0.0", + "string-width": "^4.2.3", + "v8flags": "^4.0.0", + "yargs": "^16.2.0" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">=10.13.0" + } }, - "d3-geo": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.11.6.tgz", - "integrity": "sha512-z0J8InXR9e9wcgNtmVnPTj0TU8nhYT6lD/ak9may2PdKqXIeHUr8UbFLoCtrPYNsjv6YaLvSDQVl578k6nm7GA==", + "node_modules/gulp-cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "requires": { - "d3-array": "1" + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "d3-glyphedge": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/d3-glyphedge/-/d3-glyphedge-1.2.0.tgz", - "integrity": "sha512-F49fyMXMLYDHvqvxSmuGZrtIWeWLZWxar82WL1CJDBDPk4z6GUGSG4wX7rdv7N7R/YazAyMMnpOL0YQcmTLlOQ==", - "dev": true + "node_modules/gulp-cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } }, - "d3-hexbin": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/d3-hexbin/-/d3-hexbin-0.2.2.tgz", - "integrity": "sha1-nFg32s/UcasFM3qeke8Qv8T5iDE=", - "dev": true + "node_modules/gulp-cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } }, - "d3-hierarchy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.8.tgz", - "integrity": "sha512-L+GHMSZNwTpiq4rt9GEsNcpLa4M96lXMR8M/nMG9p5hBE0jy6C+3hWtyZMenPQdwla249iJy7Nx0uKt3n+u9+w==", + "node_modules/gulp-cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "d3-interpolate": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.3.2.tgz", - "integrity": "sha512-NlNKGopqaz9qM1PXh9gBF1KSCVh+jSFErrSlD/4hybwoNX/gt1d8CDbDW+3i+5UOHhjC6s6nMvRxcuoMVNgL2w==", + "node_modules/gulp-cli/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "requires": { - "d3-color": "1" + "engines": { + "node": ">=8" } }, - "d3-path": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.7.tgz", - "integrity": "sha512-q0cW1RpvA5c5ma2rch62mX8AYaiLX0+bdaSM2wxSU9tXjU4DNvkx9qiUvjkuWCj3p22UO/hlPivujqMiR9PDzA==", - "dev": true - }, - "d3-polygon": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.5.tgz", - "integrity": "sha512-RHhh1ZUJZfhgoqzWWuRhzQJvO7LavchhitSTHGu9oj6uuLFzYZVeBzaWTQ2qSO6bz2w55RMoOCf0MsLCDB6e0w==", - "dev": true + "node_modules/gulp-cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } }, - "d3-quadtree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.1.tgz", - "integrity": "sha1-E74CViTxEEBe1DU2xQaq7Bme1ZE=", - "dev": true + "node_modules/gulp-cli/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } }, - "d3-request": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-request/-/d3-request-1.0.6.tgz", - "integrity": "sha512-FJj8ySY6GYuAJHZMaCQ83xEYE4KbkPkmxZ3Hu6zA1xxG2GD+z6P+Lyp+zjdsHf0xEbp2xcluDI50rCS855EQ6w==", + "node_modules/gulp-cli/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, - "requires": { - "d3-collection": "1", - "d3-dispatch": "1", - "d3-dsv": "1", - "xmlhttprequest": "1" - }, "dependencies": { - "d3-dsv": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.1.1.tgz", - "integrity": "sha512-1EH1oRGSkeDUlDRbhsFytAXU6cAmXFzc52YUe6MRlPClmWb85MP1J5x+YJRzya4ynZWnbELdSAvATFW/MbxaXw==", - "dev": true, - "requires": { - "commander": "2", - "iconv-lite": "0.4", - "rw": "1" - } - } + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" } }, - "d3-sankey-circular": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/d3-sankey-circular/-/d3-sankey-circular-0.25.0.tgz", - "integrity": "sha512-maYak22afBAvmybeaopd1cVUNTIroEHhWCmh19gEQ+qgOhBkTav8YeP3Uw4OV/K4OksWaQrhhBOE4Rcxgc2JbQ==", + "node_modules/gulp-typescript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/gulp-typescript/-/gulp-typescript-5.0.1.tgz", + "integrity": "sha512-YuMMlylyJtUSHG1/wuSVTrZp60k1dMEFKYOvDf7OvbAJWrDtxxD4oZon4ancdWwzjj30ztiidhe4VXJniF0pIQ==", "dev": true, - "requires": { - "d3-array": "^1.2.1", - "d3-collection": "^1.0.4", - "d3-shape": "^1.2.0" + "dependencies": { + "ansi-colors": "^3.0.5", + "plugin-error": "^1.0.1", + "source-map": "^0.7.3", + "through2": "^3.0.0", + "vinyl": "^2.1.0", + "vinyl-fs": "^3.0.3" + }, + "engines": { + "node": ">= 8" + }, + "peerDependencies": { + "typescript": "~2.7.1 || >=2.8.0-dev || >=2.9.0-dev || ~3.0.0 || >=3.0.0-dev || >=3.1.0-dev || >= 3.2.0-dev || >= 3.3.0-dev" } }, - "d3-scale": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz", - "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==", + "node_modules/gulp-typescript/node_modules/ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", "dev": true, - "requires": { - "d3-array": "^1.2.0", - "d3-collection": "1", - "d3-format": "1", - "d3-interpolate": "1", - "d3-time": "1", - "d3-time-format": "2" + "engines": { + "node": ">=6" } }, - "d3-scale-chromatic": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz", - "integrity": "sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==", + "node_modules/gulp-typescript/node_modules/source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", "dev": true, - "requires": { - "d3-color": "1", - "d3-interpolate": "1" + "engines": { + "node": ">= 8" } }, - "d3-selection": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.0.tgz", - "integrity": "sha512-EYVwBxQGEjLCKF2pJ4+yrErskDnz5v403qvAid96cNdCMr8rmCYfY5RGzWz24mdIbxmDf6/4EAH+K9xperD5jg==", - "dev": true - }, - "d3-shape": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.5.tgz", - "integrity": "sha512-VKazVR3phgD+MUCldapHD7P9kcrvPcexeX/PkMJmkUov4JM8IxsSg1DvbYoYich9AtdTsa5nNk2++ImPiDiSxg==", + "node_modules/gulp-typescript/node_modules/through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", "dev": true, - "requires": { - "d3-path": "1" + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" } }, - "d3-time": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.11.tgz", - "integrity": "sha512-Z3wpvhPLW4vEScGeIMUckDW7+3hWKOQfAWg/U7PlWBnQmeKQ00gCUsTtWSYulrKNA7ta8hJ+xXc6MHrMuITwEw==", + "node_modules/gulp/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, - "d3-time-format": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.3.tgz", - "integrity": "sha512-6k0a2rZryzGm5Ihx+aFMuO1GgelgIz+7HhB4PH4OEndD5q2zGn1mDfRdNrulspOfR6JXkb2sThhDK41CSK85QA==", + "node_modules/gulp/node_modules/fs-mkdirp-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-2.0.1.tgz", + "integrity": "sha512-UTOY+59K6IA94tec8Wjqm0FSh5OVudGNB0NL/P6fB3HiE3bYOY3VYBGijsnOHNkQSwC1FKkU77pmq7xp9CskLw==", "dev": true, - "requires": { - "d3-time": "1" + "dependencies": { + "graceful-fs": "^4.2.8", + "streamx": "^2.12.0" + }, + "engines": { + "node": ">=10.13.0" } }, - "d3-timer": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.9.tgz", - "integrity": "sha512-rT34J5HnQUHhcLvhSB9GjCkN0Ddd5Y8nCwDBG2u6wQEeYxT/Lf51fTFFkldeib/sE/J0clIe0pnCfs6g/lRbyg==", - "dev": true - }, - "d3-transition": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.2.0.tgz", - "integrity": "sha512-VJ7cmX/FPIPJYuaL2r1o1EMHLttvoIuZhhuAlRoOxDzogV8iQS6jYulDm3xEU3TqL80IZIhI551/ebmCMrkvhw==", + "node_modules/gulp/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, - "requires": { - "d3-color": "1", - "d3-dispatch": "1", - "d3-ease": "1", - "d3-interpolate": "1", - "d3-selection": "^1.1.0", - "d3-timer": "1" + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" } }, - "d3-voronoi": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz", - "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==", - "dev": true + "node_modules/gulp/node_modules/glob-stream": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-8.0.2.tgz", + "integrity": "sha512-R8z6eTB55t3QeZMmU1C+Gv+t5UnNRkA55c5yo67fAVfxODxieTwsjNG7utxS/73NdP1NbDgCrhVEg2h00y4fFw==", + "dev": true, + "dependencies": { + "@gulpjs/to-absolute-glob": "^4.0.0", + "anymatch": "^3.1.3", + "fastq": "^1.13.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "is-negated-glob": "^1.0.0", + "normalize-path": "^3.0.0", + "streamx": "^2.12.5" + }, + "engines": { + "node": ">=10.13.0" + } }, - "damerau-levenshtein": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", - "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==", - "dev": true + "node_modules/gulp/node_modules/lead": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-4.0.0.tgz", + "integrity": "sha512-DpMa59o5uGUWWjruMp71e6knmwKU3jRBBn1kjuLWN9EeIOxNeSAwvHf03WIl8g/ZMR2oSQC9ej3yeLBwdDc/pg==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "^1.0.0" + "node_modules/gulp/node_modules/now-and-later": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-3.0.0.tgz", + "integrity": "sha512-pGO4pzSdaxhWTGkfSfHx3hVzJVslFPwBp2Myq9MYN/ChfJZF87ochMAXnvz6/58RJSf5ik2q9tXprBBrk2cpcg==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + }, + "engines": { + "node": ">= 10.13.0" } }, - "data-urls": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", - "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "node_modules/gulp/node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", "dev": true, - "requires": { - "abab": "^2.0.0", - "whatwg-mimetype": "^2.2.0", - "whatwg-url": "^7.0.0" + "engines": { + "node": ">= 10" } }, - "datalib": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/datalib/-/datalib-1.9.2.tgz", - "integrity": "sha512-sV49o/1J3VdtTlJpsvPYT39WfUxyZGTO2rEGhEJt2eNY7LN2Z9K7nq3fOjgYMQtbuL0dVCWvmtxT2hpCgwx9Mg==", + "node_modules/gulp/node_modules/resolve-options": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-2.0.0.tgz", + "integrity": "sha512-/FopbmmFOQCfsCx77BRFdKOniglTiHumLgwvd6IDPihy1GKkadZbgQJBcTb2lMzSR1pndzd96b1nZrreZ7+9/A==", "dev": true, - "requires": { - "d3-dsv": "0.1", - "d3-format": "0.4", - "d3-time": "0.1", - "d3-time-format": "0.2", - "request": "^2.67.0", - "sync-request": "^6.0.0", - "topojson-client": "^3.0.0" - }, "dependencies": { - "d3-format": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-0.4.2.tgz", - "integrity": "sha1-qnWcHlquX6javJq3gZxQL8a1aHU=", - "dev": true - }, - "d3-time": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-0.1.1.tgz", - "integrity": "sha1-OM4qe7R6QDFhOCPd5GiOWOiSrls=", - "dev": true - }, - "d3-time-format": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-0.2.1.tgz", - "integrity": "sha1-hG45638iZ2aS2GBAxI6fpU/Yvxg=", - "dev": true, - "requires": { - "d3-time": "~0.1.1" - } - } + "value-or-function": "^4.0.0" + }, + "engines": { + "node": ">= 10.13.0" } }, - "date-format": { + "node_modules/gulp/node_modules/to-through": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-3.0.0.tgz", - "integrity": "sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==" + "resolved": "https://registry.npmjs.org/to-through/-/to-through-3.0.0.tgz", + "integrity": "sha512-y8MN937s/HVhEoBU1SxfHC+wxCHkV1a9gW8eAdTadYh/bGyesZIVcbjI+mSpFbSVwQici/XjBjuUyri1dnXwBw==", + "dev": true, + "dependencies": { + "streamx": "^2.12.5" + }, + "engines": { + "node": ">=10.13.0" + } }, - "date-now": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", - "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", - "dev": true + "node_modules/gulp/node_modules/value-or-function": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-4.0.0.tgz", + "integrity": "sha512-aeVK81SIuT6aMJfNo9Vte8Dw0/FZINGBV8BfCraGtqVxIeLAEhJyoWs8SmvRVmXfGss2PmmOwZCuBPbZR+IYWg==", + "dev": true, + "engines": { + "node": ">= 10.13.0" + } }, - "debounce": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz", - "integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==", - "dev": true + "node_modules/gulp/node_modules/vinyl": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz", + "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", + "dev": true, + "dependencies": { + "clone": "^2.1.2", + "clone-stats": "^1.0.0", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/gulp/node_modules/vinyl-fs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-4.0.0.tgz", + "integrity": "sha512-7GbgBnYfaquMk3Qu9g22x000vbYkOex32930rBnc3qByw6HfMEAoELjCjoJv4HuEQxHAurT+nvMHm6MnJllFLw==", "dev": true, - "requires": { - "ms": "2.0.0" + "dependencies": { + "fs-mkdirp-stream": "^2.0.1", + "glob-stream": "^8.0.0", + "graceful-fs": "^4.2.11", + "iconv-lite": "^0.6.3", + "is-valid-glob": "^1.0.0", + "lead": "^4.0.0", + "normalize-path": "3.0.0", + "resolve-options": "^2.0.0", + "stream-composer": "^1.0.2", + "streamx": "^2.14.0", + "to-through": "^3.0.0", + "value-or-function": "^4.0.0", + "vinyl": "^3.0.0", + "vinyl-sourcemap": "^2.0.0" }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp/node_modules/vinyl-sourcemap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-2.0.0.tgz", + "integrity": "sha512-BAEvWxbBUXvlNoFQVFVHpybBbjW1r03WhohJzJDSfgrrK5xVYIDTan6xN14DlyImShgDRv2gl9qhM6irVMsV0Q==", + "dev": true, "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } + "convert-source-map": "^2.0.0", + "graceful-fs": "^4.2.10", + "now-and-later": "^3.0.0", + "streamx": "^2.12.5", + "vinyl": "^3.0.0", + "vinyl-contents": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" } }, - "debug-fabulous": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", - "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", + "node_modules/gulplog": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-2.2.0.tgz", + "integrity": "sha512-V2FaKiOhpR3DRXZuYdRLn/qiY0yI5XmqbTKrYbdemJ+xOh2d2MOweI/XFgMzd/9+1twdvMwllnZbWZNJ+BOm4A==", "dev": true, - "requires": { - "debug": "3.X", - "memoizee": "0.4.X", - "object-assign": "4.X" + "dependencies": { + "glogg": "^2.2.0" }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "decache": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/decache/-/decache-4.5.1.tgz", - "integrity": "sha512-5J37nATc6FmOTLbcsr9qx7Nm28qQyg1SK4xyEHqM0IBkNhWFp0Sm+vKoWYHD8wq+OUEb9jLyaKFfzzd1A9hcoA==", + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, - "requires": { - "callsite": "^1.0.0" + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" } }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } }, - "decompress": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", - "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, - "requires": { - "decompress-tar": "^4.0.0", - "decompress-tarbz2": "^4.0.0", - "decompress-targz": "^4.0.0", - "decompress-unzip": "^4.0.1", - "graceful-fs": "^4.1.10", - "make-dir": "^1.0.0", - "pify": "^2.3.0", - "strip-dirs": "^2.0.0" - }, "dependencies": { - "make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "dev": true, - "requires": { - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "requires": { - "mimic-response": "^1.0.0" + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "decompress-tar": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", - "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", + "node_modules/has-symbol-support-x": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", + "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==", "dev": true, - "requires": { - "file-type": "^5.2.0", - "is-stream": "^1.1.0", - "tar-stream": "^1.5.2" + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" }, - "dependencies": { - "file-type": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", - "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=", - "dev": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "decompress-tarbz2": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", - "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", + "node_modules/has-to-string-tag-x": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", + "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", "dev": true, - "requires": { - "decompress-tar": "^4.1.0", - "file-type": "^6.1.0", - "is-stream": "^1.1.0", - "seek-bzip": "^1.0.5", - "unbzip2-stream": "^1.0.9" + "license": "MIT", + "dependencies": { + "has-symbol-support-x": "^1.4.1" }, + "engines": { + "node": "*" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dependencies": { - "file-type": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", - "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", - "dev": true - } + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "decompress-targz": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", - "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", + "node_modules/hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", "dev": true, - "requires": { - "decompress-tar": "^4.1.1", - "file-type": "^5.2.0", - "is-stream": "^1.1.0" + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, "dependencies": { - "file-type": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", - "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=", - "dev": true - } + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" } }, - "decompress-unzip": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", - "integrity": "sha1-3qrM39FK6vhVePczroIQ+bSEj2k=", + "node_modules/hasha": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.1.0.tgz", + "integrity": "sha512-OFPDWmzPN1l7atOV1TgBVmNtBxaIysToK6Ve9DK+vT6pYuklw/nPNT+HJbZi0KDcI6vWB+9tgvZ5YD7fA3CXcA==", "dev": true, - "requires": { - "file-type": "^3.8.0", - "get-stream": "^2.2.0", - "pify": "^2.3.0", - "yauzl": "^2.4.2" - }, "dependencies": { - "file-type": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", - "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=", - "dev": true - }, - "get-stream": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", - "integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=", - "dev": true, - "requires": { - "object-assign": "^4.0.1", - "pinkie-promise": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", - "dev": true - }, - "deemon": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/deemon/-/deemon-1.4.0.tgz", - "integrity": "sha512-S0zK5tNTdVFsJZVUeKi/CYJn4zzhW0Y55lwXzv2hVxb7ajzAHf91BhE5y2xvx1X7czIZ6PHLPDj00TVAmylVXw==", - "dev": true, - "requires": { - "bl": "^4.0.2", - "tree-kill": "^1.2.2" + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" }, - "dependencies": { - "bl": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", - "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "buffer": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", - "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", - "dev": true, - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - } + "engines": { + "node": ">=8" } }, - "deep-assign": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/deep-assign/-/deep-assign-1.0.0.tgz", - "integrity": "sha1-sJJ0O+hCfcYh6gBnzex+cN0Z83s=", + "node_modules/hasha/node_modules/is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", "dev": true, - "requires": { - "is-obj": "^1.0.0" + "engines": { + "node": ">=8" } }, - "deep-diff": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-0.3.8.tgz", - "integrity": "sha1-wB3mPvsO7JeYgB1Ax+Da4ltYLIQ=", - "dev": true + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true, - "requires": { - "type-detect": "^4.0.0" + "bin": { + "he": "bin/he" } }, - "deep-equal": { + "node_modules/hmac-drbg": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" - }, - "deepmerge": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", - "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==", - "dev": true - }, - "default-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", - "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", "dev": true, - "requires": { - "kind-of": "^5.0.2" - }, "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" } }, - "default-require-extensions": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", - "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", "dev": true, - "requires": { - "strip-bom": "^4.0.0" - }, "dependencies": { - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - } + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "default-resolution": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", - "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "node_modules/http-cache-semantics": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", + "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", "dev": true, - "requires": { - "object-keys": "^1.0.12" - } + "license": "BSD-2-Clause" }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" } }, - "del": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", - "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "dev": true, - "requires": { - "globby": "^6.1.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "p-map": "^1.1.1", - "pify": "^3.0.0", - "rimraf": "^2.2.8" - }, "dependencies": { - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true } } }, - "delaunator": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-4.0.1.tgz", - "integrity": "sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag==", - "dev": true - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "delegates": { + "node_modules/https-browserify": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, - "denodeify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", - "integrity": "sha1-OjYof1A05pnnV3kBBSwubJQlFjE=", - "dev": true + "node_modules/https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } }, - "des.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", - "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, - "requires": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" + "engines": { + "node": ">=10.17.0" } }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } }, - "detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", - "dev": true + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, - "detect-indent": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.0.0.tgz", - "integrity": "sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==" + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", "dev": true }, - "detect-newline": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", - "dev": true + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "detect-port-alt": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", - "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, - "requires": { - "address": "^1.0.1", - "debug": "^2.6.0" + "license": "MIT", + "engines": { + "node": ">=4" } }, - "dfa": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", - "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==" + "node_modules/import-in-the-middle": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.4.2.tgz", + "integrity": "sha512-9WOz1Yh/cvO/p69sxRmhyQwrIGGSp7EIdcb+fFNVi7CzQGQB8U1/1XrKVSbEd/GNOAeM0peJtmi7+qphe7NvAw==", + "dependencies": { + "acorn": "^8.8.2", + "acorn-import-assertions": "^1.9.0", + "cjs-module-lexer": "^1.2.2", + "module-details-from-path": "^1.0.3" + } }, - "diagnostic-channel": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz", - "integrity": "sha1-zJmvlhLCP7H/8TYSxy8sv6qNWhc=", - "requires": { - "semver": "^5.3.0" + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "diagnostic-channel-publishers": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.3.4.tgz", - "integrity": "sha512-SZ1zMfFiEabf4Qx0Og9V1gMsRoqz3O+5ENkVcNOfI+SMJ3QhQsdEoKX99r0zvreagXot2parPxmrwwUM/ja8ug==" + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } }, - "diagnostics": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", - "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==", - "requires": { - "colorspace": "1.1.x", - "enabled": "1.0.x", - "kuler": "1.0.x" + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" } }, - "didyoumean": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.1.tgz", - "integrity": "sha1-6S7f2tplN9SE1zwBcv0eugxJdv8=", - "dev": true + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", + "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", "dev": true }, - "diff-match-patch": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.4.tgz", - "integrity": "sha512-Uv3SW8bmH9nAtHKaKSanOQmj2DnlH65fUpcrMdfdaOxUG02QQ4YGZ8AE7kKOMisF7UqvOlGKVYWRvezdncW9lg==" + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } }, - "diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "dev": true, - "requires": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" + "engines": { + "node": ">=10.13.0" } }, - "dir-glob": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", - "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "node_modules/into-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", + "integrity": "sha512-TcdjPibTksa1NQximqep2r17ISRiNE9fwlfbg3F8ANdvP5/yrFTew86VcO//jk4QTaMlbjypPBq76HN2zaKfZQ==", "dev": true, - "requires": { - "path-type": "^3.0.0" + "license": "MIT", + "dependencies": { + "from2": "^2.1.1", + "p-is-promise": "^1.1.0" + }, + "engines": { + "node": ">=4" } }, - "discontinuous-range": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", - "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=", - "dev": true + "node_modules/inversify": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/inversify/-/inversify-6.0.2.tgz", + "integrity": "sha512-i9m8j/7YIv4mDuYXUAcrpKPSaju/CIly9AHK5jvCBeoiM/2KEsuCQTTP+rzSWWpLYWRukdXFSl6ZTk2/uumbiA==" }, - "dns-packet": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.2.1.tgz", - "integrity": "sha512-JHj2yJeKOqlxzeuYpN1d56GfhzivAxavNwHj9co3qptECel27B1rLY5PifJAvubsInX5pGLDjAHuCfCUc2Zv/w==", - "requires": { - "ip": "^1.1.5" + "node_modules/is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "dependencies": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "dns-socket": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dns-socket/-/dns-socket-4.2.0.tgz", - "integrity": "sha512-4XuD3z28jht3jvHbiom6fAipgG5LkjYeDLrX5OH8cbl0AtzTyUUAxGckcW8T7z0pLfBBV5qOiuC4wUEohk6FrQ==", - "requires": { - "dns-packet": "^5.1.2" + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "doctrine": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-0.7.2.tgz", - "integrity": "sha1-fLhgNZujvpDgQLJrcpzkv6ZUxSM=", + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", "dev": true, - "requires": { - "esutils": "^1.1.6", - "isarray": "0.0.1" - }, "dependencies": { - "esutils": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz", - "integrity": "sha1-wBzKqa5LiXxtDD4hCuUvPHqEQ3U=", - "dev": true - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - } + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "dom-converter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", "dev": true, - "requires": { - "utila": "~0.4" + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "dom-helpers": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", - "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, - "requires": { - "@babel/runtime": "^7.1.2" + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" } }, - "dom-serializer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", - "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", "dev": true, - "requires": { - "domelementtype": "^1.3.0", - "entities": "^1.1.1" + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "dom-walk": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", - "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=", - "dev": true - }, - "dom4": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/dom4/-/dom4-2.1.5.tgz", - "integrity": "sha512-gJbnVGq5zaBUY0lUh0LUEVGYrtN75Ks8ZwpwOYvnVFrKy/qzXK4R/1WuLIFExWj/tBxbRAkTzZUGJHXmqsBNjQ==", + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, - "domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", - "dev": true + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "domexception": { + "node_modules/is-data-view": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", - "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", "dev": true, - "requires": { - "webidl-conversions": "^4.0.2" + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", "dev": true, - "requires": { - "domelementtype": "1" + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true, - "requires": { - "dom-serializer": "0", - "domelementtype": "1" + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "download": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/download/-/download-7.1.0.tgz", - "integrity": "sha512-xqnBTVd/E+GxJVrX5/eUJiLYjCGPwMpdL+jGhGU57BvtcA7wwhtHVbXBeUk51kOpW3S7Jn3BQbN9Q1R1Km2qDQ==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true, - "requires": { - "archive-type": "^4.0.0", - "caw": "^2.0.1", - "content-disposition": "^0.5.2", - "decompress": "^4.2.0", - "ext-name": "^5.0.0", - "file-type": "^8.1.0", - "filenamify": "^2.0.0", - "get-stream": "^3.0.0", - "got": "^8.3.1", - "make-dir": "^1.2.0", - "p-event": "^2.1.0", - "pify": "^3.0.0" - }, - "dependencies": { - "make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "dev": true, - "requires": { - "pify": "^3.0.0" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } + "engines": { + "node": ">=0.10.0" } }, - "duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", - "dev": true + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", - "requires": { - "readable-stream": "^2.0.2" - }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" - }, - "duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, - "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - }, "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "each-props": { + "node_modules/is-nan": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", - "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", "dev": true, - "requires": { - "is-plain-object": "^2.0.1", - "object.defaults": "^1.1.0" + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } + "node_modules/is-natural-number": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", + "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==", + "dev": true, + "license": "MIT" }, - "editorconfig": { - "version": "0.15.3", - "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", - "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==", + "node_modules/is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", "dev": true, - "requires": { - "commander": "^2.19.0", - "lru-cache": "^4.1.5", - "semver": "^5.6.0", - "sigmund": "^1.0.1" + "engines": { + "node": ">=0.10.0" } }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true - }, - "ejs": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", - "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==", - "dev": true + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "electron-to-chromium": { - "version": "1.3.189", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.189.tgz", - "integrity": "sha512-C26Kv6/rLNmGDaPR5HORMtTQat9aWBBKjQk9aFtN1Bk6cQBSw8cYdsel/mcrQlNlMMjt1sAKsTYqf77+sK2uTw==", - "dev": true + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } }, - "elliptic": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", - "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", "dev": true, - "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "emitter-listener": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz", - "integrity": "sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==", - "requires": { - "shimmer": "^1.2.0" + "node_modules/is-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", - "dev": true - }, - "emotion": { - "version": "9.2.12", - "resolved": "https://registry.npmjs.org/emotion/-/emotion-9.2.12.tgz", - "integrity": "sha512-hcx7jppaI8VoXxIWEhxpDW7I+B4kq9RNzQLmsrF6LY8BGKqe2N+gFAQr0EfuFucFlPs2A9HM4+xNj4NeqEWIOQ==", + "node_modules/is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", "dev": true, - "requires": { - "babel-plugin-emotion": "^9.2.11", - "create-emotion": "^9.2.12" + "engines": { + "node": ">=6" } }, - "enabled": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", - "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", - "requires": { - "env-variable": "0.0.x" + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" } }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "dev": true - }, - "encoding": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", - "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", "dev": true, - "requires": { - "iconv-lite": "~0.4.13" + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "requires": { - "once": "^1.4.0" + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "engine.io": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.4.0.tgz", - "integrity": "sha512-XCyYVWzcHnK5cMz7G4VTu2W7zJS7SM1QkcelghyIk/FmobWBtXE7fwhBusEKvCSqc3bMh8fNFMlUkCKTFRxH2w==", + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "dev": true, - "requires": { - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "0.3.1", - "debug": "~4.1.0", - "engine.io-parser": "~2.2.0", - "ws": "^7.1.2" - }, "dependencies": { - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", - "dev": true - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ws": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz", - "integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==", - "dev": true - } + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "engine.io-client": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.0.tgz", - "integrity": "sha512-a4J5QO2k99CM2a0b12IznnyQndoEvtA4UAldhGzKqnHf42I3Qs2W5SPnDvatZRcMaNZs4IevVicBPayxYt6FwA==", + "node_modules/is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", "dev": true, - "requires": { - "component-emitter": "1.2.1", - "component-inherit": "0.0.3", - "debug": "~4.1.0", - "engine.io-parser": "~2.2.0", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "ws": "~6.1.0", - "xmlhttprequest-ssl": "~1.5.4", - "yeast": "0.1.2" + "dependencies": { + "is-unc-path": "^1.0.0" }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ws": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", - "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } - } + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "engine.io-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.0.tgz", - "integrity": "sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w==", + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", "dev": true, - "requires": { - "after": "0.8.2", - "arraybuffer.slice": "~0.0.7", - "base64-arraybuffer": "0.1.5", - "blob": "0.0.5", - "has-binary2": "~1.0.2" + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "enhanced-resolve": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", - "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.4.0", - "tapable": "^1.0.0" + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", "dev": true, - "requires": { - "ansi-colors": "^4.1.1" + "dependencies": { + "has-symbols": "^1.0.2" }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", "dependencies": { - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - } + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, - "env-variable": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.5.tgz", - "integrity": "sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA==" - }, - "enzyme": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.10.0.tgz", - "integrity": "sha512-p2yy9Y7t/PFbPoTvrWde7JIYB2ZyGC+NgTNbVEGvZ5/EyoYSr9aG/2rSbVvyNvMHEhw9/dmGUJHWtfQIEiX9pg==", + "node_modules/is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", "dev": true, - "requires": { - "array.prototype.flat": "^1.2.1", - "cheerio": "^1.0.0-rc.2", - "function.prototype.name": "^1.1.0", - "has": "^1.0.3", - "html-element-map": "^1.0.0", - "is-boolean-object": "^1.0.0", - "is-callable": "^1.1.4", - "is-number-object": "^1.0.3", - "is-regex": "^1.0.4", - "is-string": "^1.0.4", - "is-subset": "^0.1.1", - "lodash.escape": "^4.0.1", - "lodash.isequal": "^4.5.0", - "object-inspect": "^1.6.0", - "object-is": "^1.0.1", - "object.assign": "^4.1.0", - "object.entries": "^1.0.4", - "object.values": "^1.0.4", - "raf": "^3.4.0", - "rst-selector-parser": "^2.2.3", - "string.prototype.trim": "^1.1.2" - }, - "dependencies": { - "object-inspect": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", - "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", - "dev": true - } + "dependencies": { + "unc-path-regex": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" } }, - "enzyme-adapter-react-16": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.14.0.tgz", - "integrity": "sha512-7PcOF7pb4hJUvjY7oAuPGpq3BmlCig3kxXGi2kFx0YzJHppqX1K8IIV9skT1IirxXlu8W7bneKi+oQ10QRnhcA==", + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true, - "requires": { - "enzyme-adapter-utils": "^1.12.0", - "has": "^1.0.3", - "object.assign": "^4.1.0", - "object.values": "^1.1.0", - "prop-types": "^15.7.2", - "react-is": "^16.8.6", - "react-test-renderer": "^16.0.0-0", - "semver": "^5.7.0" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "enzyme-adapter-utils": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.12.0.tgz", - "integrity": "sha512-wkZvE0VxcFx/8ZsBw0iAbk3gR1d9hK447ebnSYBf95+r32ezBq+XDSAvRErkc4LZosgH8J7et7H7/7CtUuQfBA==", + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "node_modules/is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", "dev": true, - "requires": { - "airbnb-prop-types": "^2.13.2", - "function.prototype.name": "^1.1.0", - "object.assign": "^4.1.0", - "object.fromentries": "^2.0.0", - "prop-types": "^15.7.2", - "semver": "^5.6.0" + "engines": { + "node": ">=0.10.0" } }, - "errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", "dev": true, - "requires": { - "prr": "~1.0.1" + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - }, - "dependencies": { - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - } + "engines": { + "node": ">=0.10.0" } }, - "es-abstract": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", - "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, - "requires": { - "es-to-primitive": "^1.2.0", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "is-callable": "^1.1.4", - "is-regex": "^1.0.4", - "object-keys": "^1.0.12" + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" } }, - "es-array-method-boxes-properly": { + "node_modules/isarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, - "es-get-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", - "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true, - "requires": { - "es-abstract": "^1.17.4", - "has-symbols": "^1.0.1", - "is-arguments": "^1.0.4", - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-string": "^1.0.5", - "isarray": "^2.0.5" - }, - "dependencies": { - "es-abstract": { - "version": "1.17.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", - "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-regex": "^1.1.0", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "is-callable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", - "dev": true - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", - "dev": true - }, - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, - "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", - "dev": true - } + "engines": { + "node": ">=0.10.0" } }, - "es-to-primitive": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", - "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "engines": { + "node": ">=8" } }, - "es5-ext": { - "version": "0.10.50", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.50.tgz", - "integrity": "sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==", - "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.1", - "next-tick": "^1.0.0" + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "dependencies": { + "append-transform": "^2.0.0" + }, + "engines": { + "node": ">=8" } }, - "es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" + "node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" } }, - "es6-map": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", - "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-set": "~0.1.5", - "es6-symbol": "~3.1.1", - "event-emitter": "~0.3.5" + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" } }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=8" + } }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "node_modules/istanbul-lib-processinfo/node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, - "requires": { - "es6-promise": "^4.0.3" + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" } }, - "es6-set": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", - "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-symbol": "3.1.1", - "event-emitter": "~0.3.5" + "node_modules/istanbul-lib-processinfo/node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "es6-symbol": { + "node_modules/istanbul-lib-processinfo/node_modules/path-key": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "requires": { - "d": "1", - "es5-ext": "~0.10.14" + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" } }, - "es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "node_modules/istanbul-lib-processinfo/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "escape-carriage": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/escape-carriage/-/escape-carriage-1.3.0.tgz", - "integrity": "sha512-ATWi5MD8QlAGQOeMgI8zTp671BG8aKvAC0M7yenlxU4CRLGO/sKthxVUyjiOFKjHdIo+6dZZUNFgHFeVEaKfGQ==", - "dev": true + "node_modules/istanbul-lib-processinfo/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "escodegen": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.1.tgz", - "integrity": "sha512-6hTjO1NAWkHnDk3OqQ4YrCuwwmGHL9S3nPlzBOUG/R44rda3wLNrfvQ5fkSGjyhHFKM7ALPKcKGrwvCLe0lC7Q==", - "requires": { - "esprima": "^3.1.3", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "eslint": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.8.1.tgz", - "integrity": "sha512-/2rX2pfhyUG0y+A123d0ccXtMm7DV7sH1m3lk9nk2DZ2LReq39FXHueR9xZwshE5MdfSf0xunSaMWRqyIA6M1w==", + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@eslint/eslintrc": "^0.1.3", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "eslint-scope": "^5.1.0", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^1.3.0", - "espree": "^7.3.0", - "esquery": "^1.2.0", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash": "^4.17.19", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^5.2.3", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "eslint-scope": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", - "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", - "dev": true, - "requires": { - "type-fest": "^0.8.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } + "optional": true } } }, - "eslint-config-airbnb": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-18.2.0.tgz", - "integrity": "sha512-Fz4JIUKkrhO0du2cg5opdyPKQXOI2MvF8KUvN2710nJMT6jaRUpRE2swrJftAjVGL7T1otLM5ieo5RqS1v9Udg==", + "node_modules/istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", "dev": true, - "requires": { - "eslint-config-airbnb-base": "^14.2.0", - "object.assign": "^4.1.0", - "object.entries": "^1.1.2" - }, "dependencies": { - "es-abstract": { - "version": "1.17.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", - "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-regex": "^1.1.0", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "is-callable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", - "dev": true - }, - "is-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", - "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", - "dev": true - }, - "object.entries": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz", - "integrity": "sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "has": "^1.0.3" - } - } + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "eslint-config-airbnb-base": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.0.tgz", - "integrity": "sha512-Snswd5oC6nJaevs3nZoLSTvGJBvzTfnBqOIArkf3cbyTyq9UD79wOk8s+RiL6bhca0p/eRO6veczhf6A/7Jy8Q==", + "node_modules/isurl": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", + "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", "dev": true, - "requires": { - "confusing-browser-globals": "^1.0.9", - "object.assign": "^4.1.0", - "object.entries": "^1.1.2" - }, + "license": "MIT", "dependencies": { - "es-abstract": { - "version": "1.17.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", - "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-regex": "^1.1.0", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "is-callable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", - "dev": true - }, - "is-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", - "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", - "dev": true - }, - "object.entries": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz", - "integrity": "sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "has": "^1.0.3" - } - } + "has-to-string-tag-x": "^1.2.0", + "is-object": "^1.0.1" + }, + "engines": { + "node": ">= 4" } }, - "eslint-config-prettier": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.11.0.tgz", - "integrity": "sha512-oB8cpLWSAjOVFEJhhyMZh6NOEOtBVziaqdDQ86+qhDHFbZXoRTM7pNSvFRfW/W/L/LrQ38C99J5CGuRBBzBsdA==", + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, - "requires": { - "get-stdin": "^6.0.0" + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "eslint-import-resolver-node": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", - "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, - "requires": { - "debug": "^2.6.9", - "resolve": "^1.13.1" - }, "dependencies": { - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - } + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" } }, - "eslint-module-utils": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", - "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "requires": { - "debug": "^2.6.9", - "pkg-dir": "^2.0.0" - }, - "dependencies": { - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - } + "engines": { + "node": ">=8" } }, - "eslint-plugin-import": { - "version": "2.22.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz", - "integrity": "sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg==", - "dev": true, - "requires": { - "array-includes": "^3.1.1", - "array.prototype.flat": "^1.2.3", - "contains-path": "^0.1.0", - "debug": "^2.6.9", - "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.3", - "eslint-module-utils": "^2.6.0", - "has": "^1.0.3", - "minimatch": "^3.0.4", - "object.values": "^1.1.1", - "read-pkg-up": "^2.0.0", - "resolve": "^1.17.0", - "tsconfig-paths": "^3.9.0" - }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, "dependencies": { - "array.prototype.flat": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", - "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } - }, - "doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" - } - }, - "es-abstract": { - "version": "1.17.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", - "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-regex": "^1.1.0", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "is-callable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", - "dev": true - }, - "is-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", - "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", - "dev": true - }, - "object.values": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", - "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" - } - }, - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "tsconfig-paths": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", - "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", - "dev": true, - "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.0", - "strip-bom": "^3.0.0" - } - } + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "eslint-plugin-jsx-a11y": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.3.1.tgz", - "integrity": "sha512-i1S+P+c3HOlBJzMFORRbC58tHa65Kbo8b52/TwCwSKLohwvpfT5rm2GjGWzOHTEuq4xxf2aRlHHTtmExDQOP+g==", + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, - "requires": { - "@babel/runtime": "^7.10.2", - "aria-query": "^4.2.2", - "array-includes": "^3.1.1", - "ast-types-flow": "^0.0.7", - "axe-core": "^3.5.4", - "axobject-query": "^2.1.2", - "damerau-levenshtein": "^1.0.6", - "emoji-regex": "^9.0.0", - "has": "^1.0.3", - "jsx-ast-utils": "^2.4.1", - "language-tags": "^1.0.5" - }, "dependencies": { - "@babel/runtime": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.5.tgz", - "integrity": "sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" - } - }, - "emoji-regex": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.0.0.tgz", - "integrity": "sha512-6p1NII1Vm62wni/VR/cUMauVQoxmLVb9csqQlvLz+hO2gk8U2UYDfXHQSUYIBKmZwAKz867IDqG7B+u0mj+M6w==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - } + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "eslint-plugin-prettier": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz", - "integrity": "sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA==", + "node_modules/js-yaml/node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, - "requires": { - "prettier-linter-helpers": "^1.0.0" + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" } }, - "eslint-plugin-react": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.20.6.tgz", - "integrity": "sha512-kidMTE5HAEBSLu23CUDvj8dc3LdBU0ri1scwHBZjI41oDv4tjsWZKU7MQccFzH1QYPYhsnTF2ovh7JlcIcmxgg==", + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true, - "requires": { - "array-includes": "^3.1.1", - "array.prototype.flatmap": "^1.2.3", - "doctrine": "^2.1.0", - "has": "^1.0.3", - "jsx-ast-utils": "^2.4.1", - "object.entries": "^1.1.2", - "object.fromentries": "^2.0.2", - "object.values": "^1.1.1", - "prop-types": "^15.7.2", - "resolve": "^1.17.0", - "string.prototype.matchall": "^4.0.2" + "bin": { + "jsesc": "bin/jsesc" }, - "dependencies": { - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "es-abstract": { - "version": "1.17.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", - "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-regex": "^1.1.0", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "is-callable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", - "dev": true - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", - "dev": true - }, - "object.entries": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz", - "integrity": "sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "has": "^1.0.3" - } - }, - "object.fromentries": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz", - "integrity": "sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, - "object.values": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", - "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - } + "engines": { + "node": ">=4" } }, - "eslint-plugin-react-hooks": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.0.0.tgz", - "integrity": "sha512-YKBY+kilK5wrwIdQnCF395Ya6nDro3EAMoe+2xFkmyklyhF16fH83TrQOo9zbZIDxBsXFgBbywta/0JKRNFDkw==", + "node_modules/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, - "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" } }, - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" }, - "espree": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz", - "integrity": "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==", + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", "dev": true, - "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.3.0" + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "dev": true, "dependencies": { - "acorn": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", - "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", - "dev": true - } + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" } }, - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" + "node_modules/jsonwebtoken/node_modules/jws": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", + "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", + "dev": true, + "dependencies": { + "jwa": "^1.4.2", + "safe-buffer": "^5.0.1" + } }, - "esquery": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", - "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "node_modules/jsx-ast-utils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz", + "integrity": "sha512-uP5vu8xfy2F9A6LGC22KO7e2/vGTS1MhP+18f++ZNlf0Ohaxbc9nIEwHAsejlJKyzfZzU5UIhe5ItYkitcZnZA==", "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } + "array-includes": "^3.1.3", + "object.assign": "^4.1.2" + }, + "engines": { + "node": ">=4.0" } }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", "dev": true, - "requires": { - "estraverse": "^4.1.0" + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" } }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + "node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true }, - "estree-is-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/estree-is-function/-/estree-is-function-1.0.0.tgz", - "integrity": "sha512-nSCWn1jkSq2QAtkaVLJZY2ezwcFO161HVc174zL1KPW3RJ+O6C3eJb8Nx7OXzvhoEv+nLgSR1g71oWUHUDTrJA==" + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "dev": true + "node_modules/keytar": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz", + "integrity": "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-addon-api": "^4.3.0", + "prebuild-install": "^7.0.1" + } }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "requires": { - "d": "1", - "es5-ext": "~0.10.14" + "node_modules/keyv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", + "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.0" } }, - "event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true, - "requires": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" + "engines": { + "node": ">=0.10.0" } }, - "events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", - "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==", + "node_modules/language-subtag-registry": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.20.tgz", + "integrity": "sha512-KPMwROklF4tEx283Xw0pNKtfTj1gZ4UByp4EsIFWLgBavJltF4TiYPc39k06zSTsLzxTVXXDSpbwaQXaFB4Qeg==", "dev": true }, - "eventsource": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-0.1.6.tgz", - "integrity": "sha1-Cs7ehJ7X3RzMMsgRuxG5RNTykjI=", + "node_modules/language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=", "dev": true, - "requires": { - "original": ">=0.0.5" + "dependencies": { + "language-subtag-registry": "~0.3.2" } }, - "evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "node_modules/last-run": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/last-run/-/last-run-2.0.0.tgz", + "integrity": "sha512-j+y6WhTLN4Itnf9j5ZQos1BGPCS8DAwmgMroR3OzfxAsBxam0hMw7J8M3KqZl0pLQJ1jNnwIexg5DYpC/ctwEQ==", "dev": true, - "requires": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" + "engines": { + "node": ">= 10.13.0" } }, - "execa": { + "node_modules/lazystream": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, "dependencies": { - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" } }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "node_modules/lead": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", + "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "flush-write-stream": "^1.0.2" + }, + "engines": { + "node": ">= 0.10" } }, - "expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1" + "engines": { + "node": ">=6" } }, - "expose-loader": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-0.7.5.tgz", - "integrity": "sha512-iPowgKUZkTPX5PznYsmifVj9Bob0w2wTHVkt/eYNPSzyebkUgIedmskf/kcfEIWpiWjg3JRjnW+a17XypySMuw==", - "dev": true - }, - "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", - "dev": true, - "requires": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", - "dev": true - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "dev": true - } + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" } }, - "ext-list": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", - "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", "dev": true, - "requires": { - "mime-db": "^1.28.0" + "dependencies": { + "immediate": "~3.0.5" } }, - "ext-name": { + "node_modules/liftoff": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", - "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-5.0.0.tgz", + "integrity": "sha512-a5BQjbCHnB+cy+gsro8lXJ4kZluzOijzJ1UVVfyJYZC+IP2pLv1h4+aysQeKuTmyO8NAqfyQAk4HWaP/HjcKTg==", "dev": true, - "requires": { - "ext-list": "^2.0.0", - "sort-keys-length": "^1.0.0" + "dependencies": { + "extend": "^3.0.2", + "findup-sync": "^5.0.0", + "fined": "^2.0.0", + "flagged-respawn": "^2.0.0", + "is-plain-object": "^5.0.0", + "rechoir": "^0.8.0", + "resolve": "^1.20.0" + }, + "engines": { + "node": ">=10.13.0" } }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + "node_modules/liftoff/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } + "uc.micro": "^1.0.1" } }, - "external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "node_modules/loader-runner": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "dev": true, - "requires": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", - "tmp": "^0.0.33" + "engines": { + "node": ">=6.11.5" }, - "dependencies": { - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "extglob": { + "node_modules/loader-utils": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" } }, - "extract-zip": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", - "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, - "requires": { - "concat-stream": "^1.6.2", - "debug": "^2.6.9", - "mkdirp": "^0.5.4", - "yauzl": "^2.10.0" + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" } }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==" }, - "falafel": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/falafel/-/falafel-2.2.0.tgz", - "integrity": "sha512-9eKUh65oZiESUxlwYhwIKntuTzWiQxlN9osHug0F+eDNekenqFFZGctlEFdhs6jXfwPncZhu8Qsly8P/t3JBxA==", - "requires": { - "acorn": "^7.1.1", - "foreach": "^2.0.5", - "isarray": "0.0.1", - "object-keys": "^1.0.6" - }, - "dependencies": { - "acorn": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", - "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==" - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - } - } + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true }, - "fancy-log": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", - "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", - "dev": true, - "requires": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "parse-node-version": "^1.0.0", - "time-stamp": "^1.0.0" - } + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true }, - "fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", "dev": true }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true }, - "fast-plist": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/fast-plist/-/fast-plist-0.1.2.tgz", - "integrity": "sha1-pFr/NFGWAG1AbKbNzQX2kFHvNbg=", + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "dev": true }, - "fast-safe-stringify": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.6.tgz", - "integrity": "sha512-q8BZ89jjc+mz08rSxROs8VsrBBcn1SIw1kq9NjolL509tkABRk9io01RAjSaEv1Xb2uFLt8VtRiZbGp5H8iDtg==" + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true }, - "fast-xml-parser": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-3.16.0.tgz", - "integrity": "sha512-U+bpScacfgnfNfIKlWHDu4u6rtOaCyxhblOLJ8sZPkhsjgGqdZmVPBhdOyvdMGCDt8CsAv+cssOP3NzQptNt2w==", + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "fastparse": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", - "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "dev": true }, - "faye-websocket": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", - "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, - "requires": { - "websocket-driver": ">=0.5.1" + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "fbjs": { - "version": "0.8.17", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", - "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=", + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "requires": { - "core-js": "^1.0.0", - "isomorphic-fetch": "^2.1.1", - "loose-envify": "^1.0.0", - "object-assign": "^4.1.0", - "promise": "^7.1.1", - "setimmediate": "^1.0.5", - "ua-parser-js": "^0.7.18" + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "requires": { - "pend": "~1.2.0" + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "fecha": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", - "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==" + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } }, - "figgy-pudding": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", - "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "node_modules/log-symbols/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" + "engines": { + "node": ">=8" } }, - "file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "requires": { - "flat-cache": "^2.0.1" + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "file-loader": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-5.1.0.tgz", - "integrity": "sha512-u/VkLGskw3Ue59nyOwUwXI/6nuBCo7KBkniB/l7ICwr/7cPNGsL1WCXUp3GB0qgOOKU1TiP49bv4DZF/LJqprg==", + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "dev": true, - "requires": { - "loader-utils": "^1.4.0", - "schema-utils": "^2.5.0" - }, "dependencies": { - "ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", - "dev": true - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "schema-utils": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.5.tgz", - "integrity": "sha512-5KXuwKziQrTVHh8j/Uxz+QUbxkaLW9X/86NBlx/gnKgtsZA2GIVMUn17qWhRFwF8jdYb3Dig5hRO/W5mZqy6SQ==", - "dev": true, - "requires": { - "ajv": "^6.12.0", - "ajv-keywords": "^3.4.1" - } - } + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" } }, - "file-type": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-8.1.0.tgz", - "integrity": "sha512-qyQ0pzAy78gVoJsmYeNgl8uH8yKhr1lVhW7JbzJmnlRi0I4R2eEDEJZVKG8agpDnLpacwNbDhLNG/LMdxHD2YQ==", - "dev": true - }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "optional": true - }, - "filemanager-webpack-plugin-fixed": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/filemanager-webpack-plugin-fixed/-/filemanager-webpack-plugin-fixed-2.0.9.tgz", - "integrity": "sha512-iet9rDGF3HjHwObDy2FwzBFtiW59e+/wImVdqYXp5siRI4ESfxJyflOhpr7zrnJ4FcTS+rrIdk5E/Zgh9XtS0Q==", + "node_modules/loupe": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", + "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", "dev": true, - "requires": { - "archiver": "^3.0.0", - "cpx-fixed": "^1.6.0", - "fs-extra": "^7.0.0", - "make-dir": "^1.1.0", - "mv": "^2.1.1", - "rimraf": "^2.6.2" - }, "dependencies": { - "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "dev": true, - "requires": { - "pify": "^3.0.0" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } + "get-func-name": "^2.0.0" } }, - "filename-reserved-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", - "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=", - "dev": true - }, - "filenamify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-2.1.0.tgz", - "integrity": "sha512-ICw7NTT6RsDp2rnYKVd8Fu4cr6ITzGy3+u4vUujPkabyaz+03F24NWEX7fs5fp+kBonlaqPH8fAO2NM+SXt/JA==", + "node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", "dev": true, - "requires": { - "filename-reserved-regex": "^2.0.0", - "strip-outer": "^1.0.0", - "trim-repeated": "^1.0.0" + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "filesize": { - "version": "3.5.11", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.5.11.tgz", - "integrity": "sha512-ZH7loueKBoDb7yG9esn1U+fgq7BzlzW6NRi5/rMdxIZ05dj7GFD/Xc5rq2CDt5Yq86CyfSYVyx4242QQNZbx1g==", - "dev": true + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" + "bin": { + "semver": "bin/semver.js" } }, - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "node_modules/make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "dev": true + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" + "engines": { + "node": ">=0.10.0" } }, - "find-index": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz", - "integrity": "sha1-Z101iyyjiS15Whq0cjL4tuLg3eQ=", + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "find-root": { + "node_modules/math-intrinsics": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", - "dev": true + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "node_modules/md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", "dev": true, - "requires": { - "locate-path": "^3.0.0" + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" } }, - "findup-sync": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", - "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", - "dev": true, - "requires": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - } - }, - "fined": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", - "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "node_modules/md5.js": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", + "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", "dev": true, - "requires": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", - "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" } }, - "flagged-respawn": { + "node_modules/mdurl": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", - "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", "dev": true }, - "flat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", - "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, - "requires": { - "is-buffer": "~2.0.3" - }, - "dependencies": { - "is-buffer": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", - "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==", - "dev": true - } + "engines": { + "node": ">= 8" } }, - "flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, - "requires": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" - }, "dependencies": { - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" } }, - "flatted": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", - "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==" - }, - "flatten": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.3.tgz", - "integrity": "sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==", - "dev": true - }, - "flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", "dev": true, - "requires": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - }, "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" } }, - "follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "requires": { - "debug": "=3.1.0" + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" } }, - "font-awesome": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", - "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=" - }, - "fontkit": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-1.8.0.tgz", - "integrity": "sha512-EFDRCca7khfQWYu1iFhsqeABpi87f03MBdkT93ZE6YhqCdMzb5Eojb6c4dlJikGv5liuhByyzA7ikpIPTSBWbQ==", - "requires": { - "babel-runtime": "^6.11.6", - "brfs": "^1.4.0", - "brotli": "^1.2.0", - "browserify-optional": "^1.0.0", - "clone": "^1.0.1", - "deep-equal": "^1.0.0", - "dfa": "^1.0.0", - "restructure": "^0.5.3", - "tiny-inflate": "^1.0.2", - "unicode-properties": "^1.0.0", - "unicode-trie": "^0.3.0" - }, - "dependencies": { - "brfs": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/brfs/-/brfs-1.6.1.tgz", - "integrity": "sha512-OfZpABRQQf+Xsmju8XE9bDjs+uU4vLREGolP7bDgcpsI17QREyZ4Bl+2KLxxx1kCgA0fAIhKQBaBYh+PEcCqYQ==", - "requires": { - "quote-stream": "^1.0.1", - "resolve": "^1.1.5", - "static-module": "^2.2.0", - "through2": "^2.0.0" - } - } + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" } }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", "dev": true }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true }, - "foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" + "node_modules/minimatch": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.8.tgz", + "integrity": "sha512-7RN35vit8DeBclkofOVmBY0eDAZZQd1HzmukRdSyz95CRh8FT54eqnbj0krQr3mrHR6sfRyYkyhwBWjoV5uqlQ==", + "dependencies": { + "brace-expansion": "^2.0.1" }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dependencies": { - "cross-spawn": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", - "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } + "balanced-match": "^1.0.0" } }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true }, - "fork-ts-checker-webpack-plugin": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-4.1.6.tgz", - "integrity": "sha512-DUxuQaKoqfNne8iikd14SAkh5uw4+8vNifp6gmA73yYNS6ywLIWSLD/n/mBzHQRpW3J7rbATEakmiA8JvkTyZw==", + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, - "requires": { - "@babel/code-frame": "^7.5.5", - "chalk": "^2.4.1", - "micromatch": "^3.1.10", - "minimatch": "^3.0.4", - "semver": "^5.6.0", - "tapable": "^1.0.0", - "worker-rpc": "^0.1.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", - "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.9.0", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - } + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" } }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" + "node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" } }, - "formidable": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", - "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==", - "dev": true + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "optional": true }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", - "dev": true + "node_modules/mocha": { + "version": "11.7.5", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.5.tgz", + "integrity": "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==", + "dev": true, + "license": "MIT", + "dependencies": { + "browser-stdout": "^1.3.1", + "chokidar": "^4.0.1", + "debug": "^4.3.5", + "diff": "^7.0.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^10.4.5", + "he": "^1.2.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^9.0.5", + "ms": "^2.1.3", + "picocolors": "^1.1.1", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^9.2.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/mocha-junit-reporter": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-2.0.2.tgz", + "integrity": "sha512-vYwWq5hh3v1lG0gdQCBxwNipBfvDiAM1PHroQRNp96+2l72e9wEUTw+mzoK+O0SudgfQ7WvTQZ9Nh3qkAYAjfg==", + "dev": true, + "dependencies": { + "debug": "^2.2.0", + "md5": "^2.1.0", + "mkdirp": "~0.5.1", + "strip-ansi": "^6.0.1", + "xml": "^1.0.0" + }, + "peerDependencies": { + "mocha": ">=2.2.5" + } }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "node_modules/mocha-multi-reporters": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/mocha-multi-reporters/-/mocha-multi-reporters-1.5.1.tgz", + "integrity": "sha512-Yb4QJOaGLIcmB0VY7Wif5AjvLMUFAdV57D2TWEva1Y0kU/3LjKpeRVmlMIfuO1SVbauve459kgtIizADqxMWPg==", "dev": true, - "requires": { - "map-cache": "^0.2.2" + "dependencies": { + "debug": "^4.1.1", + "lodash": "^4.17.15" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "mocha": ">=3.1.2" } }, - "free-style": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/free-style/-/free-style-3.1.0.tgz", - "integrity": "sha512-vJujYSIyT30iDoaoeigNAxX4yB1RUrh+N2ZMhIElMr3BvCuGXOw7XNJMEEJkDUeamK2Rnb/IKFGKRKlTWIGRWA==", - "dev": true + "node_modules/mocha-multi-reporters/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "node_modules/mocha/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", - "dev": true + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } }, - "from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "node_modules/mocha/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/mocha/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mocha/node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mocha/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true } } }, - "fromentries": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.2.0.tgz", - "integrity": "sha512-33X7H/wdfO99GdRLLgkjUrD4geAFdq/Uv0kl3HD4da6HDixd2GUg8Mw7dahLCV9r/EARkmtYBB6Tch4EEokFTQ==", - "dev": true + "node_modules/mocha/node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } }, - "fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "fs-extra": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", - "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "node_modules/mocha/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "node_modules/mocha/node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "dev": true, - "requires": { - "minipass": "^2.6.0" + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "fs-mkdirp-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", - "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", + "node_modules/mocha/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "through2": "^2.0.3" + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "fs-readdir-recursive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "node_modules/mocha/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/mocha/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "fs-walk": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/fs-walk/-/fs-walk-0.0.1.tgz", - "integrity": "sha1-9/yRw64e6tB8mYvF0N1B8tvr0zU=", + "node_modules/mocha/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, - "requires": { - "async": "*" + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "node_modules/mocha/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" + "dependencies": { + "p-limit": "^3.0.2" }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/mocha/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "fs.realpath": { + "node_modules/mocha/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/mocha/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mocha/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/module-details-from-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", + "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==" + }, + "node_modules/mrmime": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.0.tgz", + "integrity": "sha512-a70zx7zFfVO7XpnQ2IX1Myh9yY4UYvfld/dikWRnsXxbyvMcfz+u6UfgNAtH+k2QqtJuzVpv6eLTx1G2+WKZbQ==", + "dev": true, + "engines": { + "node": ">=10" + } }, - "fsevents": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.12.tgz", - "integrity": "sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q==", + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/mute-stdout": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-2.0.0.tgz", + "integrity": "sha512-32GSKM3Wyc8dg/p39lWPKYu8zci9mJFzV1Np9Of0ZEpe6Fhssn/FbI7ywAMd40uX+p3ZKh3T5EeCFv81qS3HmQ==", + "dev": true, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "node_modules/named-js-regexp": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/named-js-regexp/-/named-js-regexp-1.3.5.tgz", + "integrity": "sha512-XO0DPujDP9IWpkt690iWLreKztb/VB811DGl5N3z7BfhkMJuiVZXOi6YN/fEB9qkvtMVTgSZDW8pzdVt8vj/FA==" + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "dev": true, + "optional": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/nise": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-6.0.0.tgz", + "integrity": "sha512-K8ePqo9BFvN31HXwEtTNGzgrPpmvgciDsFz8aztFjt4LqKO/JeFD8tBOeuDiCMXrIl/m1YvfH8auSpxfaD09wg==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" + } + }, + "node_modules/node-abi": { + "version": "3.45.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.45.0.tgz", + "integrity": "sha512-iwXuFrMAcFVi/ZoZiqq8BzAdsLw9kxDfTC0HMyjXfSL/6CSDAGD5UmR7azrAgWV1zKYq7dUUMj4owusBWKLsiQ==", "dev": true, "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1", - "node-pre-gyp": "*" + "dependencies": { + "semver": "^7.3.5" }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", + "dev": true, + "optional": true + }, + "node_modules/node-has-native-dependencies": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/node-has-native-dependencies/-/node-has-native-dependencies-1.0.2.tgz", + "integrity": "sha1-MVLsl1O2ZB5NMi0YXdSTBkmto9o=", + "dev": true, "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.4", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "debug": { - "version": "3.2.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.6.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "bundled": true, - "dev": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "bundled": true, - "dev": true, - "optional": true - }, - "minipass": { - "version": "2.9.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.9.0" - } - }, - "mkdirp": { - "version": "0.5.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "needle": { - "version": "2.3.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.14.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - } - }, - "nopt": { - "version": "4.0.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } + "fs-walk": "0.0.1" + }, + "bin": { + "node-has-native-dependencies": "index.js" + } + }, + "node_modules/node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "dev": true, + "dependencies": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + } + }, + "node_modules/node-libs-browser/node_modules/buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "deprecated": "This version of 'buffer' is out-of-date. You must update to v4.9.2 or newer", + "dev": true, + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/node-libs-browser/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "node_modules/node-loader": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/node-loader/-/node-loader-1.0.3.tgz", + "integrity": "sha512-8c9ef5q24F0AjrPxUjdX7qdTlsU1zZCPeqYvSBCH1TJko3QW4qu1uA1C9KbOPdaRQwREDdbSYZgltBAlbV7l5g==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/node-polyfill-webpack-plugin": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/node-polyfill-webpack-plugin/-/node-polyfill-webpack-plugin-1.1.4.tgz", + "integrity": "sha512-Z0XTKj1wRWO8o/Vjobsw5iOJCN+Sua3EZEUc2Ziy9CyVvmHKu6o+t4gUH9GOE0czyPR94LI6ZCV/PpcM8b5yow==", + "dev": true, + "dependencies": { + "assert": "^2.0.0", + "browserify-zlib": "^0.2.0", + "buffer": "^6.0.3", + "console-browserify": "^1.2.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.12.0", + "domain-browser": "^4.19.0", + "events": "^3.3.0", + "filter-obj": "^2.0.2", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "^1.0.1", + "process": "^0.11.10", + "punycode": "^2.1.1", + "querystring-es3": "^0.2.1", + "readable-stream": "^3.6.0", + "stream-browserify": "^3.0.0", + "stream-http": "^3.2.0", + "string_decoder": "^1.3.0", + "timers-browserify": "^2.0.12", + "tty-browserify": "^0.0.1", + "url": "^0.11.0", + "util": "^0.12.4", + "vm-browserify": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "webpack": ">=5" + } + }, + "node_modules/node-polyfill-webpack-plugin/node_modules/assert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", + "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", + "dev": true, + "dependencies": { + "es6-object-assign": "^1.1.0", + "is-nan": "^1.2.1", + "object-is": "^1.0.1", + "util": "^0.12.0" + } + }, + "node_modules/node-polyfill-webpack-plugin/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true + { + "type": "patreon", + "url": "https://www.patreon.com/feross" }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/node-polyfill-webpack-plugin/node_modules/domain-browser": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-4.22.0.tgz", + "integrity": "sha512-IGBwjF7tNk3cwypFNH/7bfzBcgSCbaMOD3GsaY1AU/JRrnHnYgEM0+9kQt52iZxjNsjBtJYtao146V+f8jFZNw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/node-polyfill-webpack-plugin/node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true + }, + "node_modules/node-polyfill-webpack-plugin/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/node-polyfill-webpack-plugin/node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "dev": true, + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "node_modules/node-polyfill-webpack-plugin/node_modules/stream-http": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.2.0.tgz", + "integrity": "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==", + "dev": true, + "dependencies": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "xtend": "^4.0.2" + } + }, + "node_modules/node-polyfill-webpack-plugin/node_modules/tty-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", + "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", + "dev": true + }, + "node_modules/node-polyfill-webpack-plugin/node_modules/util": { + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz", + "integrity": "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "safe-buffer": "^5.1.2", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true + }, + "node_modules/node-stream-zip": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", + "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", + "engines": { + "node": ">=0.12.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/antelle" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", + "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "prepend-http": "^2.0.0", + "query-string": "^5.0.1", + "sort-keys": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/now-and-later": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", + "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", + "dev": true, + "dependencies": { + "once": "^1.3.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz", + "integrity": "sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/nyc/node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", + "dev": true, + "dependencies": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.entries": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", + "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.hasown": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.0.tgz", + "integrity": "sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ordered-read-streams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", + "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.1" + } + }, + "node_modules/os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "node_modules/p-cancelable": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", + "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-event": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-2.3.1.tgz", + "integrity": "sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-timeout": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-is-promise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "integrity": "sha512-zL7VE4JVS2IFSkR2GQKDSPEVxkoH43/p7oEnwpdCndKYJO0HVeRB7fA8TJwuLOTBREtK0ea8eHaxdwcpob5dmg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", + "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-asn1": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.9.tgz", + "integrity": "sha512-fIYNuZ/HastSb80baGOuPRo1O9cf4baWw5WsAp7dBuUzeTD/BoaG8sVTdlPFksBE2lF21dN+A1AnrpIjSWqHHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "asn1.js": "^4.10.1", + "browserify-aes": "^1.2.0", + "evp_bytestokey": "^1.0.3", + "pbkdf2": "^3.1.5", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse-asn1/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "wrappy": "1" - } + { + "type": "patreon", + "url": "https://www.patreon.com/feross" }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", + "dev": true, + "dependencies": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-semver": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz", + "integrity": "sha512-Eg1OuNntBMH0ojvEKSrvDSnwLmvVuUOSdylH/pSCPNMIspLlweJyIWXCE+k/5hm3cj/EBUYwmWkjhBALNP4LXQ==", + "dev": true, + "dependencies": { + "semver": "^5.1.0" + } + }, + "node_modules/parse-semver/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "node_modules/path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", + "dev": true, + "dependencies": { + "path-root-regex": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/pbkdf2": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.5.tgz", + "integrity": "sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "ripemd160": "^2.0.3", + "safe-buffer": "^5.2.1", + "sha.js": "^2.4.12", + "to-buffer": "^1.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pbkdf2/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true + { + "type": "patreon", + "url": "https://www.patreon.com/feross" }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "dev": true, + "dependencies": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postinstall-build": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/postinstall-build/-/postinstall-build-5.0.3.tgz", + "integrity": "sha512-vPvPe8TKgp4FLgY3+DfxCE5PIfoXBK2lyLfNCxsRbDsV6vS4oU5RG/IWxrblMn6heagbnMED3MemUQllQ2bQUg==", + "deprecated": "postinstall-build's behavior is now built into npm! You should migrate off of postinstall-build and use the new `prepare` lifecycle script with npm 5.0.0 or greater.", + "dev": true, + "bin": { + "postinstall-build": "cli.js" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "dev": true, + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prebuild-install/node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.2.tgz", + "integrity": "sha512-5xJQIPT8BraI7ZnaDwSbu5zLrB6vvi8hVV58yHQ+QK64qrY40dULy0HSRlQ2/2IdzeBpjhDkqdcFBnFeDEMVdg==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "dependencies": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "dev": true, + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/query-string": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", + "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "decode-uri-component": "^0.2.0", + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true + { + "type": "patreon", + "url": "https://www.patreon.com/feross" }, - "process-nextick-args": { - "version": "2.0.1", - "bundled": true, - "dev": true, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, + "node_modules/read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", + "dev": true, + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/remove-bom-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", + "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5", + "is-utf8": "^0.2.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remove-bom-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", + "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", + "dev": true, + "dependencies": { + "remove-bom-buffer": "^3.0.0", + "safe-buffer": "^5.1.0", + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "node_modules/replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/replace-homedir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-2.0.0.tgz", + "integrity": "sha512-bgEuQQ/BHW0XkkJtawzrfzHFSN70f/3cNOiHa2QsYxqrjaC30X1k74FJ6xswVBP0sr0SpGIdVFuPwfrYziVeyw==", + "dev": true, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-in-the-middle": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.2.0.tgz", + "integrity": "sha512-3TLx5TGyAY6AOqLBoXmHkNql0HIf2RGbuMgCDT2WO/uGVAPJs6h7Kl+bN6TIZGd9bWhWPwnDnTHGtW8Iu77sdw==", + "dependencies": { + "debug": "^4.1.1", + "module-details-from-path": "^1.0.3", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/require-in-the-middle/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { "optional": true + } + } + }, + "node_modules/resolve": { + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", + "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-options": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", + "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", + "dev": true, + "dependencies": { + "value-or-function": "^3.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^1.0.0" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rewiremock": { + "version": "3.14.6", + "resolved": "https://registry.npmjs.org/rewiremock/-/rewiremock-3.14.6.tgz", + "integrity": "sha512-hjpS7iQUTVVh/IHV4GE1ypg4IzlgVc34gxZBarwwVrKfnjlyqHJuQdsia6Ac7m4f4k/zxxA3tX285MOstdysRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.26.0", + "compare-module-exports": "^2.1.0", + "node-libs-browser": "^2.1.0", + "path-parse": "^1.0.5", + "wipe-node-cache": "^2.1.2", + "wipe-webpack-cache": "^2.1.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ripemd160": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.3.tgz", + "integrity": "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hash-base": "^3.1.2", + "inherits": "^2.0.4" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ripemd160/node_modules/hash-base": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.2.tgz", + "integrity": "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ripemd160/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" }, - "rc": { - "version": "1.2.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "readable-stream": { - "version": "2.3.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.7.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "dev": true, - "optional": true + { + "type": "patreon", + "url": "https://www.patreon.com/feross" }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" }, - "sax": { - "version": "1.2.4", - "bundled": true, - "dev": true, - "optional": true + { + "type": "patreon", + "url": "https://www.patreon.com/feross" }, - "semver": { - "version": "5.7.1", - "bundled": true, - "dev": true, - "optional": true + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/rxjs-compat": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs-compat/-/rxjs-compat-6.6.7.tgz", + "integrity": "sha512-szN4fK+TqBPOFBcBcsR0g2cmTTUF/vaFEOZNuSdfU8/pGFnNmmn2u8SystYXG1QMrjOPBc6XTKHMVfENDf6hHw==" + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/seek-bzip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", + "integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^2.8.1" + }, + "bin": { + "seek-bunzip": "bin/seek-bunzip", + "seek-table": "bin/seek-bzip-table" + } + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-greatest-satisfied-range": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-2.0.0.tgz", + "integrity": "sha512-lH3f6kMbwyANB7HuOWRMlLCa2itaCrZJ+SAqqkSZrZKO/cAsk2EOyaKHUtNkVLFyFW9pct22SFesFp3Z7zpA0g==", + "dev": true, + "dependencies": { + "sver": "^1.8.3" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "dev": true, + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sha.js/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true + { + "type": "patreon", + "url": "https://www.patreon.com/feross" }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shimmer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" + }, + "node_modules/shortid": { + "version": "2.2.17", + "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.17.tgz", + "integrity": "sha512-GpbM3gLF1UUXZvQw6MCyulHkWbRseNO4cyBEZresZRorwl1+SLu1ZdqgVtuwqz8mB6RpwPkm541mYSqrKyJSaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } + { + "type": "patreon", + "url": "https://www.patreon.com/feross" }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } + { + "type": "patreon", + "url": "https://www.patreon.com/feross" }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true, + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-get/node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "optional": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/simple-get/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sinon": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-18.0.0.tgz", + "integrity": "sha512-+dXDXzD1sBO6HlmZDd7mXZCR/y5ECiEiGCBSGuFD/kZ0bDTofPYc6JaeGmPSF+1j1MejGUWkORbYOLDyvqCWpA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.2.0", + "nise": "^6.0.0", + "supports-color": "^7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", + "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/sinon/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sinon/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sirv": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz", + "integrity": "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==", + "dev": true, + "dependencies": { + "@polka/url": "^1.0.0-next.20", + "mrmime": "^1.0.0", + "totalist": "^1.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sort-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", + "integrity": "sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-obj": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/sort-keys-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", + "integrity": "sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "sort-keys": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-keys-length/node_modules/sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sparkles": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-2.1.0.tgz", + "integrity": "sha512-r7iW1bDw8R/cFifrD3JnQJX0K1jqT0kprL48BiBpLZLJPmAm34zsVBsK5lc7HirZYZqMW65dOXZgbAGt/I6frg==", + "dev": true, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/stack-chain": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/stack-chain/-/stack-chain-1.3.7.tgz", + "integrity": "sha512-D8cWtWVdIe/jBA7v5p5Hwl5yOSOrmZPWDPe2KxQ5UAGD+nxbxU0lKXA4h85Ta6+qgdKVL3vUxsbIZjc1kBG7ug==" + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "engines": { + "node": "*" + } + }, + "node_modules/stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "dev": true, + "engines": { + "node": ">=4", + "npm": ">=6" + } + }, + "node_modules/stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "dev": true, + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "node_modules/stream-composer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz", + "integrity": "sha512-bnBselmwfX5K10AH6L4c8+S5lgZMWI7ZYrz2rvYjCPB2DIMC4Ig8OpxGpNJSxRZ58oti7y1IcNvjBAz9vW5m4w==", + "dev": true, + "dependencies": { + "streamx": "^2.13.2" + } + }, + "node_modules/stream-exhaust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", + "dev": true + }, + "node_modules/stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "dependencies": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + }, + "node_modules/streamx": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", + "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==", + "dev": true, + "dependencies": { + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" }, - "tar": { - "version": "4.4.13", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } + { + "type": "patreon", + "url": "https://www.patreon.com/feross" }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", + "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.1", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", + "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-natural-number": "^4.0.1" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sudo-prompt": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", + "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==" + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sver": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/sver/-/sver-1.8.4.tgz", + "integrity": "sha512-71o1zfzyawLfIWBOmw8brleKyvnbn73oVHNCsu51uPMz/HWiKkkXsI31JjHW5zqXEqnPYkIiHd8ZmL7FCimLEA==", + "dev": true, + "optionalDependencies": { + "semver": "^6.3.0" + } + }, + "node_modules/sver/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "optional": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "dev": true, + "optional": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "optional": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/tar-fs/node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/tar-fs/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tar-fs/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "optional": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/tas-client": { + "version": "0.2.33", + "resolved": "https://registry.npmjs.org/tas-client/-/tas-client-0.2.33.tgz", + "integrity": "sha512-V+uqV66BOQnWxvI6HjDnE4VkInmYZUQ4dgB7gzaDyFyFSK1i1nF/j7DpS9UbQAgV9NaF1XpcyuavnM1qOeiEIg==" + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/terser": { + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.17", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.17.tgz", + "integrity": "sha512-YR7PtUp6GMU91BgSJmlaX/rS2lGDbAF7D+Wtq7hRO+MiljNmodYvqslzCFiYVAgW+Qoaaia/QUIP4lGXufjdZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { "optional": true }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true, + "esbuild": { "optional": true }, - "yallist": { - "version": "3.1.1", - "bundled": true, - "dev": true, + "uglify-js": { "optional": true } } }, - "fstream": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", - "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" + "dependencies": { + "fast-deep-equal": "^3.1.3" }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } }, - "function.prototype.name": { + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/text-decoder": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.0.tgz", - "integrity": "sha512-Bs0VRrTz4ghD8pTmbJQD1mZ8A/mN0ur/jGz+A6FBxPDUPkm1tNfF6bhTYPA7i7aF4lZJVr+OXTNNrnnIl58Wfg==", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.0.tgz", + "integrity": "sha512-TmLJNj6UgX8xcUZo4UDStGQtDiTzF7BzWlzn9g7UWrjkpHr5uJTK1ld16wZ3LXb2vb6jH8qU89dW5whuMdXYdw==", "dev": true, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "is-callable": "^1.1.3" + "dependencies": { + "b4a": "^1.6.4" } }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "fuzzy": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/fuzzy/-/fuzzy-0.1.3.tgz", - "integrity": "sha1-THbsL/CsGjap3M+aAN+GIweNTtg=" + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } }, - "get-assigned-identifiers": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", - "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==" + "node_modules/through2-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", + "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", + "dev": true, + "dependencies": { + "through2": "~2.0.0", + "xtend": "~4.0.0" + } }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true + "node_modules/timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", - "dev": true + "node_modules/timers-browserify": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", + "dev": true, + "dependencies": { + "setimmediate": "^1.0.4" + }, + "engines": { + "node": ">=0.6.0" + } }, - "get-port": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", - "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=" + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "license": "MIT", + "engines": { + "node": ">=14.14" + } }, - "get-proxy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-2.1.0.tgz", - "integrity": "sha512-zmZIaQTWnNQb4R4fJUEp/FC51eZsc6EkErspy3xtIYStaq8EB/hDIWipxsal+E8rz0qD7f2sL/NA9Xee4RInJw==", + "node_modules/to-absolute-glob": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", "dev": true, - "requires": { - "npm-conf": "^1.1.0" + "dependencies": { + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "get-stdin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", - "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "node_modules/to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", "dev": true }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true + "node_modules/to-buffer/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "^1.0.0" - } + "node_modules/to-buffer/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" }, - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" } }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "node_modules/to-through": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", + "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", "dev": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" + "dependencies": { + "through2": "^2.0.3" }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/totalist": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", + "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "dev": true, + "license": "MIT", "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" } }, - "glob-stream": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", - "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", "dev": true, - "requires": { - "extend": "^3.0.0", - "glob": "^7.1.1", - "glob-parent": "^3.1.0", - "is-negated-glob": "^1.0.0", - "ordered-read-streams": "^1.0.0", - "pumpify": "^1.3.5", - "readable-stream": "^2.1.5", - "remove-trailing-separator": "^1.0.1", - "to-absolute-glob": "^2.0.0", - "unique-stream": "^2.0.2" + "engines": { + "node": ">=16" }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-loader": { + "version": "9.2.8", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.2.8.tgz", + "integrity": "sha512-gxSak7IHUuRtwKf3FIPSW1VpZcqF9+MBrHOvBp9cjHh+525SjtCIJKVGjRKIAfxBwDGDGCFF00rTfzB1quxdSw==", + "dev": true, "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" } }, - "glob-watcher": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.3.tgz", - "integrity": "sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg==", + "node_modules/ts-loader/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "requires": { - "anymatch": "^2.0.0", - "async-done": "^1.2.0", - "chokidar": "^2.0.0", - "is-negated-glob": "^1.0.0", - "just-debounce": "^1.0.0", - "object.defaults": "^1.1.0" + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "glob2base": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/glob2base/-/glob2base-0.0.12.tgz", - "integrity": "sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY=", + "node_modules/ts-loader/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "requires": { - "find-index": "^0.1.1" + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "global": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", - "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "node_modules/ts-loader/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "requires": { - "min-document": "^2.19.0", - "process": "^0.11.10" + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "node_modules/ts-loader/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/ts-loader/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "requires": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" + "engines": { + "node": ">=8" } }, - "global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "node_modules/ts-loader/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "requires": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true + "node_modules/ts-mockito": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/ts-mockito/-/ts-mockito-2.6.1.tgz", + "integrity": "sha512-qU9m/oEBQrKq5hwfbJ7MgmVN5Gu6lFnIGWvpxSjrqq6YYEVv+RwVFWySbZMBgazsWqv6ctAyVBpo9TmAxnOEKw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.5" + } }, - "globby": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", - "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", + "node_modules/ts-node": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", + "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", "dev": true, - "requires": { - "array-union": "^1.0.1", - "dir-glob": "^2.0.0", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" - }, "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.0", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true + "@swc/wasm": { + "optional": true } } }, - "glogg": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", - "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, - "requires": { - "sparkles": "^1.0.0" + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" } }, - "got": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", - "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==", + "node_modules/tsconfig-paths-webpack-plugin": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-3.5.2.tgz", + "integrity": "sha512-EhnfjHbzm5IYI9YPNVIxx1moxMI4bpHD2e0zTXeDNQcwjjRaGepP7IhTHJkyDBG0CAOoxRfe7jCG630Ou+C6Pw==", "dev": true, - "requires": { - "@sindresorhus/is": "^0.7.0", - "cacheable-request": "^2.1.1", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "into-stream": "^3.1.0", - "is-retry-allowed": "^1.1.0", - "isurl": "^1.0.0-alpha5", - "lowercase-keys": "^1.0.0", - "mimic-response": "^1.0.0", - "p-cancelable": "^0.4.0", - "p-timeout": "^2.0.1", - "pify": "^3.0.0", - "safe-buffer": "^5.1.1", - "timed-out": "^4.0.1", - "url-parse-lax": "^3.0.0", - "url-to-options": "^1.0.1" - }, "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } + "chalk": "^4.1.0", + "enhanced-resolve": "^5.7.0", + "tsconfig-paths": "^3.9.0" } }, - "graceful-fs": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz", - "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==" + "node_modules/tsconfig-paths-webpack-plugin/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } }, - "graceful-readlink": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true + "node_modules/tsconfig-paths-webpack-plugin/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true + "node_modules/tsconfig-paths-webpack-plugin/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } }, - "gud": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", - "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==", + "node_modules/tsconfig-paths-webpack-plugin/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "gulp": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", - "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", + "node_modules/tsconfig-paths-webpack-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "requires": { - "glob-watcher": "^5.0.3", - "gulp-cli": "^2.2.0", - "undertaker": "^1.2.1", - "vinyl-fs": "^3.0.0" - }, - "dependencies": { - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true - }, - "gulp-cli": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.2.0.tgz", - "integrity": "sha512-rGs3bVYHdyJpLqR0TUBnlcZ1O5O++Zs4bA0ajm+zr3WFCfiSLjGwoCBqFs18wzN+ZxahT9DkOK5nDf26iDsWjA==", - "dev": true, - "requires": { - "ansi-colors": "^1.0.1", - "archy": "^1.0.0", - "array-sort": "^1.0.0", - "color-support": "^1.1.3", - "concat-stream": "^1.6.0", - "copy-props": "^2.0.1", - "fancy-log": "^1.3.2", - "gulplog": "^1.0.0", - "interpret": "^1.1.0", - "isobject": "^3.0.1", - "liftoff": "^3.1.0", - "matchdep": "^2.0.0", - "mute-stdout": "^1.0.0", - "pretty-hrtime": "^1.0.0", - "replace-homedir": "^1.0.0", - "semver-greatest-satisfied-range": "^1.1.0", - "v8flags": "^3.0.1", - "yargs": "^7.1.0" - } - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - }, - "yargs": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", - "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", - "dev": true, - "requires": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^5.0.0" - } - } + "engines": { + "node": ">=8" } }, - "gulp-azure-storage": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/gulp-azure-storage/-/gulp-azure-storage-0.11.1.tgz", - "integrity": "sha512-csOwItwZV1P9GLsORVQy+CFwjYDdHNrBol89JlHdlhGx0fTgJBc1COTRZbjGRyRjgdUuVguo3YLl4ToJ10/SIQ==", + "node_modules/tsconfig-paths-webpack-plugin/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "requires": { - "azure-storage": "^2.10.2", - "delayed-stream": "0.0.6", - "event-stream": "3.3.4", - "mime": "^1.3.4", - "progress": "^1.1.8", - "queue": "^3.0.10", - "streamifier": "^0.1.1", - "vinyl": "^2.2.0", - "vinyl-fs": "^3.0.3", - "yargs": "^15.3.0" + "dependencies": { + "has-flag": "^4.0.0" }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "delayed-stream": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.6.tgz", - "integrity": "sha1-omRst+w9XXd0YUZwp6Zd4MFz7bw=", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "yargs": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", - "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.1" - } - }, - "yargs-parser": { - "version": "18.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.2.tgz", - "integrity": "sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" } }, - "gulp-chmod": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/gulp-chmod/-/gulp-chmod-2.0.0.tgz", - "integrity": "sha1-AMOQuSigeZslGsz2MaoJ4BzGKZw=", + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + }, + "node_modules/tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", "dev": true, - "requires": { - "deep-assign": "^1.0.0", - "stat-mode": "^0.2.0", - "through2": "^2.0.0" + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" } }, - "gulp-filter": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/gulp-filter/-/gulp-filter-5.1.0.tgz", - "integrity": "sha1-oF4Rr/sHz33PQafeHLe2OsN4PnM=", + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dev": true, - "requires": { - "multimatch": "^2.0.0", - "plugin-error": "^0.1.2", - "streamfilter": "^1.0.5" + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" } }, - "gulp-gunzip": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/gulp-gunzip/-/gulp-gunzip-1.1.0.tgz", - "integrity": "sha512-3INeprGyz5fUtAs75k6wVslGuRZIjKAoQp39xA7Bz350ReqkrfYaLYqjZ67XyIfLytRXdzeX04f+DnBduYhQWw==", + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, - "requires": { - "through2": "~2.0.3", - "vinyl": "~2.0.1" - }, "dependencies": { - "vinyl": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.0.2.tgz", - "integrity": "sha1-CjcT2NTpIhxY8QyhbAEWyeJe2nw=", - "dev": true, - "requires": { - "clone": "^1.0.0", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "is-stream": "^1.1.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - } - } + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" } }, - "gulp-rename": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-1.4.0.tgz", - "integrity": "sha512-swzbIGb/arEoFK89tPY58vg3Ok1bw+d35PfUNwWqdo7KM4jkmuGA78JiDNqR+JeZFaeeHnRg9N7aihX3YPmsyg==", - "dev": true - }, - "gulp-sourcemaps": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-2.6.5.tgz", - "integrity": "sha512-SYLBRzPTew8T5Suh2U8jCSDKY+4NARua4aqjj8HOysBh2tSgT9u4jc1FYirAdPx1akUxxDeK++fqw6Jg0LkQRg==", + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true, - "requires": { - "@gulp-sourcemaps/identity-map": "1.X", - "@gulp-sourcemaps/map-sources": "1.X", - "acorn": "5.X", - "convert-source-map": "1.X", - "css": "2.X", - "debug-fabulous": "1.X", - "detect-newline": "2.X", - "graceful-fs": "4.X", - "source-map": "~0.6.0", - "strip-bom-string": "1.X", - "through2": "2.X" - }, - "dependencies": { - "acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", - "dev": true - } + "engines": { + "node": ">=4" } }, - "gulp-typescript": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/gulp-typescript/-/gulp-typescript-4.0.2.tgz", - "integrity": "sha512-Hhbn5Aa2l3T+tnn0KqsG6RRJmcYEsr3byTL2nBpNBeAK8pqug9Od4AwddU4JEI+hRw7mzZyjRbB8DDWR6paGVA==", + "node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true, - "requires": { - "ansi-colors": "^1.0.1", - "plugin-error": "^0.1.2", - "source-map": "^0.6.1", - "through2": "^2.0.3", - "vinyl": "^2.1.0", - "vinyl-fs": "^3.0.0" + "engines": { + "node": ">=8" } }, - "gulp-untar": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/gulp-untar/-/gulp-untar-0.0.8.tgz", - "integrity": "sha512-mqW7v2uvrxd8IoCCwJ04sPYgWjR3Gsi6yfhVWBK3sFMDP7FuoT7GNmxrCMwkk4RWqQohx8DRv+cDq4SRDXATGA==", + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "dev": true, - "requires": { - "event-stream": "3.3.4", - "streamifier": "~0.1.1", - "tar": "^2.2.1", - "through2": "~2.0.3", - "vinyl": "^1.2.0" - }, + "license": "MIT", "dependencies": { - "clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", - "dev": true - }, - "replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", - "dev": true - }, - "tar": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", - "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", - "dev": true, - "requires": { - "block-stream": "*", - "fstream": "^1.0.12", - "inherits": "2" - } - }, - "vinyl": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", - "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", - "dev": true, - "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", - "replace-ext": "0.0.1" - } - } + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" } }, - "gulp-vinyl-zip": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/gulp-vinyl-zip/-/gulp-vinyl-zip-2.1.2.tgz", - "integrity": "sha512-wJn09jsb8PyvUeyFF7y7ImEJqJwYy40BqL9GKfJs6UGpaGW9A+N68Q+ajsIpb9AeR6lAdjMbIdDPclIGo1/b7Q==", + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", "dev": true, - "requires": { - "event-stream": "3.3.4", - "queue": "^4.2.1", - "through2": "^2.0.3", - "vinyl": "^2.0.2", - "vinyl-fs": "^3.0.3", - "yauzl": "^2.2.1", - "yazl": "^2.2.1" - }, "dependencies": { - "queue": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/queue/-/queue-4.5.1.tgz", - "integrity": "sha512-AMD7w5hRXcFSb8s9u38acBZ+309u6GsiibP4/0YacJeaurRshogB7v/ZcVPxP5gD5+zIw6ixRHdutiYUJfwKHw==", - "dev": true, - "requires": { - "inherits": "~2.0.0" - } - } + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "gulplog": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", - "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", "dev": true, - "requires": { - "glogg": "^1.0.0" + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "gzip-size": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-3.0.0.tgz", - "integrity": "sha1-VGGI6b3DN/Zzdy+BZgRks4nc5SA=", + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", "dev": true, - "requires": { - "duplexer": "^0.1.1" + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + "node_modules/typed-rest-client": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz", + "integrity": "sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==", + "dev": true, + "dependencies": { + "qs": "^6.9.1", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + } }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" } }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" + "node_modules/typemoq": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/typemoq/-/typemoq-2.1.0.tgz", + "integrity": "sha512-DtRNLb7x8yCTv/KHlwes+NI+aGb4Vl1iPC63Hhtcvk1DpxSAZzKWQv0RQFY0jX2Uqj0SDBNl8Na4e6MV6TNDgw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "circular-json": "^0.3.1", + "lodash": "^4.17.4", + "postinstall-build": "^5.0.1" + }, + "engines": { + "node": ">=6.0.0" } }, - "has-binary2": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true, - "requires": { - "isarray": "2.0.1" + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, - "dependencies": { - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - } + "engines": { + "node": ">=14.17" } }, - "has-cors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", "dev": true }, - "has-flag": { + "node_modules/uint64be": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "resolved": "https://registry.npmjs.org/uint64be/-/uint64be-3.0.0.tgz", + "integrity": "sha512-mliiCSrsE29aNBI7O9W5gGv6WmA9kBR8PtTt6Apaxns076IRdYrrtFhXHEWMj5CSum3U7cv7/pi4xmi4XsIOqg==" }, - "has-symbol-support-x": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", - "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==", - "dev": true + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "has-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", - "dev": true + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } }, - "has-to-string-tag-x": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", - "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", + "node_modules/unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", "dev": true, - "requires": { - "has-symbol-support-x": "^1.4.1" + "engines": { + "node": ">=0.10.0" } }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", "dev": true }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "node_modules/undertaker": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-2.0.0.tgz", + "integrity": "sha512-tO/bf30wBbTsJ7go80j0RzA2rcwX6o7XPBpeFcb+jzoeb4pfMM2zUeSDIkY1AWqeZabWxaQZ/h8N9t35QKDLPQ==", "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" + "dependencies": { + "bach": "^2.0.1", + "fast-levenshtein": "^3.0.0", + "last-run": "^2.0.0", + "undertaker-registry": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" } }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "node_modules/undertaker-registry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-2.0.0.tgz", + "integrity": "sha512-+hhVICbnp+rlzZMgxXenpvTxpuvA67Bfgtt+O9WOE5jo7w/dyiF1VmoZVIHvP2EkUjsyKyTwYKlLhA+j47m1Ew==", + "dev": true, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/undertaker/node_modules/fast-levenshtein": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", + "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==", "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "fastest-levenshtein": "^1.0.7" } }, - "hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicode": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/unicode/-/unicode-14.0.0.tgz", + "integrity": "sha512-BjinxTXkbm9Jomp/YBTMGusr4fxIG67fNGShHIRAL16Ur2GJTq2xvLi+sxuiJmInCmwqqev2BCFKyvbfp/yAkg==", + "engines": { + "node": ">= 0.8.x" } }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" + "node_modules/unique-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", + "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", + "dev": true, + "dependencies": { + "json-stable-stringify-without-jsonify": "^1.0.1", + "through2-filter": "^3.0.0" } }, - "hasha": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.1.0.tgz", - "integrity": "sha512-OFPDWmzPN1l7atOV1TgBVmNtBxaIysToK6Ve9DK+vT6pYuklw/nPNT+HJbZi0KDcI6vWB+9tgvZ5YD7fA3CXcA==", + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, - "requires": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, "dependencies": { - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - } + "punycode": "^2.1.0" } }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "node_modules/url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", "dev": true }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "node_modules/url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", "dev": true, - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" + "license": "MIT", + "dependencies": { + "prepend-http": "^2.0.0" + }, + "engines": { + "node": ">=4" } }, - "hoist-non-react-statics": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz", - "integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==", + "node_modules/url-to-options": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", + "integrity": "sha512-0kQLIzG4fdk/G5NONku64rSH/x32NOA39LVQqlK8Le6lvTF6GGRJpqaQFGgU+CLwySIqBSMdwYM0sYcW9f6P4A==", "dev": true, - "requires": { - "react-is": "^16.7.0" + "license": "MIT", + "engines": { + "node": ">= 4" } }, - "homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "node_modules/url/node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + }, + "node_modules/util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", "dev": true, - "requires": { - "parse-passwd": "^1.0.0" + "dependencies": { + "inherits": "2.0.3" } }, - "hoopy": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", - "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==", + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, - "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "node_modules/util/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, - "html-element-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.0.1.tgz", - "integrity": "sha512-BZSfdEm6n706/lBfXKWa4frZRZcT5k1cOusw95ijZsHlI+GdgY0v95h6IzO3iIDf2ROwq570YTwqNPqHcNMozw==", - "dev": true, - "requires": { - "array-filter": "^1.0.0" + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" } }, - "html-encoding-sniffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", - "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "node_modules/v8-compile-cache-lib": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz", + "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==", + "dev": true + }, + "node_modules/v8flags": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-4.0.1.tgz", + "integrity": "sha512-fcRLaS4H/hrZk9hYwbdRM35D0U8IYMfEClhXxCivOojl+yTRAZH3Zy2sSy6qVCiGbV9YAtPssP6jaChqC9vPCg==", "dev": true, - "requires": { - "whatwg-encoding": "^1.0.1" + "engines": { + "node": ">= 10.13.0" } }, - "html-escaper": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.0.tgz", - "integrity": "sha512-a4u9BeERWGu/S8JiWEAQcdrg9v4QArtP9keViQjGMdff20fBdd8waotXaNmODqBe6uZ3Nafi7K/ho4gCQHV3Ig==", - "dev": true + "node_modules/value-or-function": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", + "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", + "dev": true, + "engines": { + "node": ">= 0.10" + } }, - "html-minifier": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz", - "integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==", + "node_modules/vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", "dev": true, - "requires": { - "camel-case": "3.0.x", - "clean-css": "4.2.x", - "commander": "2.17.x", - "he": "1.2.x", - "param-case": "2.1.x", - "relateurl": "0.2.x", - "uglify-js": "3.4.x" - }, "dependencies": { - "commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", - "dev": true - } + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" } }, - "html-to-react": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/html-to-react/-/html-to-react-1.4.3.tgz", - "integrity": "sha512-txe09A3vxW8yEZGJXJ1is5gGDfBEVACmZDSgwDyH5EsfRdOubBwBCg63ZThZP0xBn0UE4FyvMXZXmohusCxDcg==", + "node_modules/vinyl-contents": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-contents/-/vinyl-contents-2.0.0.tgz", + "integrity": "sha512-cHq6NnGyi2pZ7xwdHSW1v4Jfnho4TEGtxZHw01cmnc8+i7jgR6bRnED/LbrKan/Q7CvVLbnvA5OepnhbpjBZ5Q==", "dev": true, - "requires": { - "domhandler": "^3.0", - "htmlparser2": "^4.1.0", - "lodash.camelcase": "^4.3.0", - "ramda": "^0.27" - }, "dependencies": { - "dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", - "dev": true, - "requires": { - "domelementtype": "^2.0.1", - "entities": "^2.0.0" - } - }, - "domelementtype": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", - "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", - "dev": true - }, - "domhandler": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.0.0.tgz", - "integrity": "sha512-eKLdI5v9m67kbXQbJSNn1zjh0SDzvzWVWtX+qEI3eMjZw8daH9k8rlj1FZY9memPwjiskQFbe7vHVVJIAqoEhw==", - "dev": true, - "requires": { - "domelementtype": "^2.0.1" - } - }, - "domutils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.1.0.tgz", - "integrity": "sha512-CD9M0Dm1iaHfQ1R/TI+z3/JWp/pgub0j4jIQKH89ARR4ATAV2nbaOQS5XxU9maJP5jHaPdDDQSEHuE2UmpUTKg==", - "dev": true, - "requires": { - "dom-serializer": "^0.2.1", - "domelementtype": "^2.0.1", - "domhandler": "^3.0.0" - } - }, - "entities": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", - "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", - "dev": true - }, - "htmlparser2": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz", - "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==", - "dev": true, - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^3.0.0", - "domutils": "^2.0.0", - "entities": "^2.0.0" - } - } + "bl": "^5.0.0", + "vinyl": "^3.0.0" + }, + "engines": { + "node": ">=10.13.0" } }, - "html-webpack-plugin": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", - "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", + "node_modules/vinyl-contents/node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", "dev": true, - "requires": { - "html-minifier": "^3.2.3", - "loader-utils": "^0.2.16", - "lodash": "^4.17.3", - "pretty-error": "^2.0.2", - "tapable": "^1.0.0", - "toposort": "^1.0.0", - "util.promisify": "1.0.0" - }, "dependencies": { - "big.js": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", - "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", - "dev": true + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/vinyl-contents/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" }, - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "dev": true + { + "type": "patreon", + "url": "https://www.patreon.com/feross" }, - "loader-utils": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", - "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", - "dev": true, - "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0", - "object-assign": "^4.0.1" - } + { + "type": "consulting", + "url": "https://feross.org/support" } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "htmlparser2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", - "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "node_modules/vinyl-contents/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, - "requires": { - "domelementtype": "^1.3.1", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.1.1" + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, - "http-basic": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz", - "integrity": "sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==", + "node_modules/vinyl-contents/node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", "dev": true, - "requires": { - "caseless": "^0.12.0", - "concat-stream": "^1.6.2", - "http-response-object": "^3.0.1", - "parse-cache-control": "^1.0.1" + "engines": { + "node": ">= 10" } }, - "http-cache-semantics": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", - "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", - "dev": true - }, - "http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "node_modules/vinyl-contents/node_modules/vinyl": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz", + "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "dependencies": { + "clone": "^2.1.2", + "clone-stats": "^1.0.0", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" } }, - "http-parser-js": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz", - "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=", - "dev": true - }, - "http-proxy-agent": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", - "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "node_modules/vinyl-fs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", + "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", "dev": true, - "requires": { - "agent-base": "4", - "debug": "3.1.0" - }, "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } + "fs-mkdirp-stream": "^1.0.0", + "glob-stream": "^6.1.0", + "graceful-fs": "^4.0.0", + "is-valid-glob": "^1.0.0", + "lazystream": "^1.0.0", + "lead": "^1.0.0", + "object.assign": "^4.0.4", + "pumpify": "^1.3.5", + "readable-stream": "^2.3.3", + "remove-bom-buffer": "^3.0.0", + "remove-bom-stream": "^1.2.0", + "resolve-options": "^1.1.0", + "through2": "^2.0.0", + "to-through": "^2.0.0", + "value-or-function": "^3.0.0", + "vinyl": "^2.0.0", + "vinyl-sourcemap": "^1.1.0" + }, + "engines": { + "node": ">= 0.10" } }, - "http-response-object": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", - "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", + "node_modules/vinyl-sourcemap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", + "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", "dev": true, - "requires": { - "@types/node": "^10.0.3" + "dependencies": { + "append-buffer": "^1.0.2", + "convert-source-map": "^1.5.0", + "graceful-fs": "^4.1.6", + "normalize-path": "^2.1.1", + "now-and-later": "^2.0.0", + "remove-bom-buffer": "^3.0.0", + "vinyl": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" } }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "node_modules/vinyl-sourcemap/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "node_modules/vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", "dev": true }, - "https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } + "node_modules/vscode-debugprotocol": { + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.35.0.tgz", + "integrity": "sha512-+OMm11R1bGYbpIJ5eQIkwoDGFF4GvBz3Ztl6/VM+/RNNb2Gjk2c0Ku+oMmfhlTmTlPCpgHBsH4JqVCbUYhu5bA==", + "deprecated": "This package has been renamed to @vscode/debugprotocol, please update to the new name" + }, + "node_modules/vscode-jsonrpc": { + "version": "9.0.0-next.5", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.5.tgz", + "integrity": "sha512-Sl/8RAJtfF/2x/TPBVRuhzRAcqYR/QDjEjNqMcoKFfqsxfVUPzikupRDQYB77Gkbt1RrW43sSuZ5uLtNAcikQQ==", + "engines": { + "node": ">=14.0.0" } }, - "husky": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/husky/-/husky-1.3.1.tgz", - "integrity": "sha512-86U6sVVVf4b5NYSZ0yvv88dRgBSSXXmHaiq5pP4KDj5JVzdwKgBjEtUPOm8hcoytezFwbU+7gotXNhpHdystlg==", - "dev": true, - "requires": { - "cosmiconfig": "^5.0.7", - "execa": "^1.0.0", - "find-up": "^3.0.0", - "get-stdin": "^6.0.0", - "is-ci": "^2.0.0", - "pkg-dir": "^3.0.0", - "please-upgrade-node": "^3.1.1", - "read-pkg": "^4.0.1", - "run-node": "^1.0.0", - "slash": "^2.0.0" - }, + "node_modules/vscode-languageclient": { + "version": "10.0.0-next.12", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-10.0.0-next.12.tgz", + "integrity": "sha512-q7cVYCcYiv+a+fJYCbjMMScOGBnX162IBeUMFg31mvnN7RHKx5/CwKaCz+r+RciJrRXMqS8y8qpEVGgeIPnbxg==", "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "read-pkg": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz", - "integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=", - "dev": true, - "requires": { - "normalize-package-data": "^2.3.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0" - } - } + "minimatch": "^9.0.3", + "semver": "^7.6.0", + "vscode-languageserver-protocol": "3.17.6-next.10" + }, + "engines": { + "vscode": "^1.91.0" } }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "node_modules/vscode-languageclient/node_modules/brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "dependencies": { + "balanced-match": "^1.0.0" } }, - "icss-replace-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", - "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", - "dev": true - }, - "icss-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-2.1.0.tgz", - "integrity": "sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI=", - "dev": true, - "requires": { - "postcss": "^6.0.1" - }, + "node_modules/vscode-languageclient/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - } + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } - }, - "ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", - "dev": true - }, - "iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", - "dev": true - }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true - }, - "ignore-walk": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", - "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", - "dev": true, - "requires": { - "minimatch": "^3.0.4" + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.6-next.10", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.10.tgz", + "integrity": "sha512-KOrrWn4NVC5jnFC5N6y/fyNKtx8rVYr67lhL/Z0P4ZBAN27aBsCnLBWAMIkYyJ1K8EZaE5r7gqdxrS9JPB6LIg==", + "dependencies": { + "vscode-jsonrpc": "9.0.0-next.5", + "vscode-languageserver-types": "3.17.6-next.5" } }, - "image-size": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", - "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", - "dev": true, - "optional": true + "node_modules/vscode-languageserver-types": { + "version": "3.17.6-next.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.6-next.5.tgz", + "integrity": "sha512-QFmf3Yl1tCgUQfA77N9Me/LXldJXkIVypQbty2rJ1DNHQkC+iwvm4Z2tXg9czSwlhvv0pD4pbF5mT7WhAglolw==" }, - "immutable": { - "version": "4.0.0-rc.12", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0-rc.12.tgz", - "integrity": "sha512-0M2XxkZLx/mi3t8NVwIm1g8nHoEmM9p9UBl/G9k4+hm0kBgOVdMV/B3CY5dQ8qG8qc80NN4gDV4HQv6FTJ5q7A==" + "node_modules/vscode-tas-client": { + "version": "0.1.84", + "resolved": "https://registry.npmjs.org/vscode-tas-client/-/vscode-tas-client-0.1.84.tgz", + "integrity": "sha512-rUTrUopV+70hvx1hW5ebdw1nd6djxubkLvVxjGdyD/r5v/wcVF41LIfiAtbm5qLZDtQdsMH1IaCuDoluoIa88w==", + "dependencies": { + "tas-client": "0.2.33" + }, + "engines": { + "vscode": "^1.85.0" + } }, - "import-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", - "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", - "dev": true, - "requires": { - "import-from": "^2.1.0" + "node_modules/watchpack": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack": { + "version": "5.105.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", + "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.19.0", + "es-module-lexer": "^2.0.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.3.1", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.16", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } } }, - "import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "node_modules/webpack-bundle-analyzer": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.5.0.tgz", + "integrity": "sha512-GUMZlM3SKwS8Z+CKeIFx7CVoHn3dXFcUAjT/dcZQQmfSZGvitPfMob2ipjai7ovFFqPvTqkEZ/leL4O0YOdAYQ==", "dev": true, - "requires": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" + "dependencies": { + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "chalk": "^4.1.0", + "commander": "^7.2.0", + "gzip-size": "^6.0.0", + "lodash": "^4.17.20", + "opener": "^1.5.2", + "sirv": "^1.0.7", + "ws": "^7.3.1" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 10.13.0" } }, - "import-from": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", - "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", + "node_modules/webpack-bundle-analyzer/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "requires": { - "resolve-from": "^3.0.0" + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "import-local": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", - "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "node_modules/webpack-bundle-analyzer/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "requires": { - "pkg-dir": "^3.0.0", - "resolve-cwd": "^2.0.0" + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" - }, - "indexes-of": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", - "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", - "dev": true - }, - "indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", - "dev": true + "node_modules/webpack-bundle-analyzer/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } }, - "infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "node_modules/webpack-bundle-analyzer/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" + "node_modules/webpack-bundle-analyzer/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" } }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true + "node_modules/webpack-bundle-analyzer/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "inquirer": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", - "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^2.0.4", - "figures": "^2.0.0", - "lodash": "^4.3.0", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rx-lite": "^4.0.8", - "rx-lite-aggregates": "^4.0.8", - "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", - "through": "^2.3.6" - }, + "node_modules/webpack-bundle-analyzer/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "internal-slot": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.2.tgz", - "integrity": "sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g==", + "node_modules/webpack-cli": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.2.tgz", + "integrity": "sha512-m3/AACnBBzK/kMTcxWHcZFPrw/eQuY4Df1TxvIWfWM2x7mRqBQCqKEd96oCUa9jkapLBaFfRce33eGDb4Pr7YQ==", "dev": true, - "requires": { - "es-abstract": "^1.17.0-next.1", - "has": "^1.0.3", - "side-channel": "^1.0.2" - }, "dependencies": { - "es-abstract": { - "version": "1.17.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", - "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-regex": "^1.1.0", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^1.1.1", + "@webpack-cli/info": "^1.4.1", + "@webpack-cli/serve": "^1.6.1", + "colorette": "^2.0.14", + "commander": "^7.0.0", + "execa": "^5.0.0", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^2.2.0", + "rechoir": "^0.7.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "webpack": "4.x.x || 5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true }, - "is-callable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", - "dev": true + "@webpack-cli/migrate": { + "optional": true }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } + "webpack-bundle-analyzer": { + "optional": true }, - "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", - "dev": true + "webpack-dev-server": { + "optional": true } } }, - "interpret": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", - "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", - "dev": true - }, - "into-stream": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", - "integrity": "sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=", + "node_modules/webpack-cli/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "dev": true, - "requires": { - "from2": "^2.1.1", - "p-is-promise": "^1.1.0" + "engines": { + "node": ">= 10" } }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "node_modules/webpack-cli/node_modules/interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", "dev": true, - "requires": { - "loose-envify": "^1.0.0" + "engines": { + "node": ">= 0.10" } }, - "inversify": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/inversify/-/inversify-4.13.0.tgz", - "integrity": "sha512-O5d8y7gKtyRwrvTLZzYET3kdFjqUy58sGpBYMARF13mzqDobpfBXVOPLH7HmnD2VR6Q+1HzZtslGvsdQfeb0SA==" + "node_modules/webpack-cli/node_modules/rechoir": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", + "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", + "dev": true, + "dependencies": { + "resolve": "^1.9.0" + }, + "engines": { + "node": ">= 0.10" + } }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "node_modules/webpack-fix-default-import-plugin": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/webpack-fix-default-import-plugin/-/webpack-fix-default-import-plugin-1.0.3.tgz", + "integrity": "sha1-iCuOTRqpPEjLj9r4Rvx52G+C8U8=", "dev": true }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + "node_modules/webpack-merge": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } }, - "ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=" + "node_modules/webpack-node-externals": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz", + "integrity": "sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==", + "dev": true, + "engines": { + "node": ">=6" + } }, - "ipaddr.js": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", - "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==", - "dev": true + "node_modules/webpack-require-from": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/webpack-require-from/-/webpack-require-from-1.8.6.tgz", + "integrity": "sha512-QmRsOkOYPKeNXp4uVc7qxnPrFQPrP4bhOc/gl4QenTFNgXdEbF1U8VC+jM/Sljb0VzJLNgyNiHlVkuHjcmDtBQ==", + "dev": true, + "peerDependencies": { + "tapable": "^2.2.0" + } }, - "is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "node_modules/webpack-sources": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", "dev": true, - "requires": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" + "engines": { + "node": ">=10.13.0" } }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "node_modules/webpack/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, - "requires": { - "kind-of": "^3.0.2" + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" } }, - "is-alphabetical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", - "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "node_modules/webpack/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, - "is-alphanumerical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", - "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "node_modules/webpack/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, - "requires": { - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0" + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "is-arguments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", - "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", - "dev": true + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } }, - "is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", "dev": true, - "requires": { - "binary-extensions": "^1.0.0" + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "is-boolean-object": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.0.tgz", - "integrity": "sha1-mPiygDBoQhmpXzdc+9iM40Bd/5M=", + "node_modules/wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", "dev": true }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + "node_modules/winreg": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.4.tgz", + "integrity": "sha1-ugZWKbepJRMOFXeRCM9UCZDpjRs=" }, - "is-callable": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "node_modules/wipe-node-cache": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/wipe-node-cache/-/wipe-node-cache-2.1.2.tgz", + "integrity": "sha512-m7NXa8qSxBGMtdQilOu53ctMaIBXy93FOP04EC1Uf4bpsE+r+adfLKwIMIvGbABsznaSNxK/ErD4xXDyY5og9w==", "dev": true }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "node_modules/wipe-webpack-cache": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wipe-webpack-cache/-/wipe-webpack-cache-2.1.0.tgz", + "integrity": "sha512-OXzQMGpA7MnQQ8AG+uMl5mWR2ezy6fw1+DMHY+wzYP1qkF1jrek87psLBmhZEj+er4efO/GD4R8jXWFierobaA==", "dev": true, - "requires": { - "ci-info": "^2.0.0" + "dependencies": { + "wipe-node-cache": "^2.1.0" } }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "node_modules/worker-loader": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.8.tgz", + "integrity": "sha512-XQyQkIFeRVC7f7uRhFdNMe/iJOdO6zxAaR3EWbDp45v3mDhrTi+++oswKNxShUNjPC/1xUp5DB29YKLhFo129g==", "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" } }, - "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true - }, - "is-decimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", - "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", - "dev": true + "node_modules/workerpool": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", + "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", + "dev": true, + "license": "Apache-2.0" }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", - "dev": true - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "requires": { - "number-is-nan": "^1.0.0" + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "requires": { - "is-extglob": "^2.1.1" + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "is-hexadecimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", - "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", - "dev": true + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" }, - "is-ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-2.0.0.tgz", - "integrity": "sha1-aO6gfooKCpTC0IDdZ0xzGrKkYas=", + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "requires": { - "ip-regex": "^2.0.0" + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "is-map": { + "node_modules/wrap-ansi/node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", - "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==", - "dev": true + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } }, - "is-natural-number": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", - "integrity": "sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=", + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "is-negated-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", - "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", - "dev": true + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, - "is-negative-zero": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", - "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=", - "dev": true + "node_modules/write-file-atomic": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.1.tgz", + "integrity": "sha512-JPStrIyyVJ6oCSz/691fAjFtefZ6q+fP6tm+OS4Qw6o+TGQxNp1ziY2PgS+X/m0V8OWhZiO/m4xSj+Pr4RrZvw==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, - "requires": { - "kind-of": "^3.0.2" + "engines": { + "node": ">=8.3.0" }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true } } }, - "is-number-object": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.3.tgz", - "integrity": "sha1-8mWrian0RQNO9q/xWo8AsA9VF5k=", - "dev": true - }, - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true - }, - "is-object": { + "node_modules/xml": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", - "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=", "dev": true }, - "is-online": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/is-online/-/is-online-8.2.1.tgz", - "integrity": "sha512-853p45I2b//EDV7n1Rbk60f/fy1KQlp1IkTjV/K2EyAD/h8qXrIAxwIbZ8c4K5p5yDsQlTWUrxocgW/aBtNfYQ==", - "requires": { - "got": "^9.6.0", - "p-any": "^2.0.0", - "p-timeout": "^3.0.0", - "public-ip": "^3.0.0" - }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", "dependencies": { - "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" - }, - "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "dependencies": { - "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", - "requires": { - "pump": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" - } - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "requires": { - "pump": "^3.0.0" - } - }, - "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - } - }, - "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" - }, - "normalize-url": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", - "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==" - }, - "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" - }, - "p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "requires": { - "p-finally": "^1.0.0" - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" } }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", - "dev": true - }, - "is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", - "dev": true, - "requires": { - "is-path-inside": "^1.0.0" + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" } }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "dev": true, - "requires": { - "path-is-inside": "^1.0.1" + "engines": { + "node": ">=0.4" } }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "node_modules/y18n": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", "dev": true }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yargs": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", + "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", "dev": true, - "requires": { - "isobject": "^3.0.1" + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.1" + }, + "engines": { + "node": ">=8" } }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true - }, - "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", "dev": true, - "requires": { - "has": "^1.0.1" + "engines": { + "node": ">=10" } }, - "is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, - "requires": { - "is-unc-path": "^1.0.0" + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" } }, - "is-retry-allowed": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", - "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=", - "dev": true - }, - "is-root": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-root/-/is-root-1.0.0.tgz", - "integrity": "sha1-B7bCM7w5TNnQK6FclmvWZg1jQtU=", - "dev": true - }, - "is-set": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", - "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==", - "dev": true - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "is-string": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.4.tgz", - "integrity": "sha1-zDqbaYV9Yh6WNyWiTK7shzuCbmQ=", - "dev": true - }, - "is-subset": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", - "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", - "dev": true - }, - "is-symbol": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", - "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.1.tgz", + "integrity": "sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA==", "dev": true, - "requires": { - "has-symbols": "^1.0.0" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "node_modules/yargs-unparser/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true, - "requires": { - "unc-path-regex": "^0.1.2" + "engines": { + "node": ">=8" } }, - "is-url": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" + "node_modules/yargs/node_modules/ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "dependencies": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true + "node_modules/yargs/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } }, - "is-valid-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", - "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", - "dev": true + "node_modules/yargs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } }, - "is-whitespace-character": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", - "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==", + "node_modules/yargs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "node_modules/yargs/node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, - "is-word-character": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz", - "integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==", + "node_modules/yargs/node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true + "node_modules/yargs/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } }, - "is2": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.1.tgz", - "integrity": "sha512-+WaJvnaA7aJySz2q/8sLjMb2Mw14KTplHmSwcSpZ/fWJPkUmqw3YTzSWbPJ7OAwRvdYTWF2Wg+yYJ1AdP5Z8CA==", - "requires": { - "deep-is": "^0.1.3", - "ip-regex": "^2.1.0", - "is-url": "^1.2.2" + "node_modules/yargs/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" } }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "node_modules/yazl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", + "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3" + } }, - "isnumeric": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/isnumeric/-/isnumeric-0.2.0.tgz", - "integrity": "sha1-ojR7o2DeGeM9D/1ZD933dVy/LmQ=", - "dev": true + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", "dev": true }, - "isomorphic-fetch": { + "@ampproject/remapping": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", - "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", "dev": true, "requires": { - "node-fetch": "^1.0.1", - "whatwg-fetch": ">=0.10.0" + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@azure/abort-controller": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz", + "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==", + "requires": { + "tslib": "^2.2.0" }, "dependencies": { - "node-fetch": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", - "dev": true, - "requires": { - "encoding": "^0.1.11", - "is-stream": "^1.0.1" - } + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" } } }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", - "dev": true - }, - "istanbul-lib-hook": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", - "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", - "dev": true, + "@azure/core-auth": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.5.0.tgz", + "integrity": "sha512-udzoBuYG1VBoHVohDTrvKjyzel34zt77Bhp7dQntVGGD0ehVq48owENbBG8fIgkHRNUBQH5k1r0hpoMu5L8+kw==", "requires": { - "append-transform": "^2.0.0" + "@azure/abort-controller": "^1.0.0", + "@azure/core-util": "^1.1.0", + "tslib": "^2.2.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } } }, - "istanbul-lib-instrument": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.0.tgz", - "integrity": "sha512-Nm4wVHdo7ZXSG30KjZ2Wl5SU/Bw7bDx1PdaiIFzEStdjs0H12mOTncn1GVYuqQSaZxpg87VGBRsVRPGD2cD1AQ==", + "@azure/core-client": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.9.2.tgz", + "integrity": "sha512-kRdry/rav3fUKHl/aDLd/pDLcB+4pOFwPPTVEExuMyaI5r+JBbMWqRbCY1pn5BniDaU3lRxO9eaQ1AmSMehl/w==", "dev": true, "requires": { - "@babel/core": "^7.7.5", - "@babel/parser": "^7.7.5", - "@babel/template": "^7.7.4", - "@babel/traverse": "^7.7.4", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-rest-pipeline": "^1.9.1", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.6.1", + "@azure/logger": "^1.0.0", + "tslib": "^2.6.2" }, "dependencies": { - "@babel/code-frame": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", - "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/core": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.7.7.tgz", - "integrity": "sha512-jlSjuj/7z138NLZALxVgrx13AOtqip42ATZP7+kYl53GvDV6+4dCek1mVUo8z8c8Xnw/mx2q3d9HWh3griuesQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.7.7", - "@babel/helpers": "^7.7.4", - "@babel/parser": "^7.7.7", - "@babel/template": "^7.7.4", - "@babel/traverse": "^7.7.4", - "@babel/types": "^7.7.4", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "json5": "^2.1.0", - "lodash": "^4.17.13", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "@babel/generator": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.7.tgz", - "integrity": "sha512-/AOIBpHh/JU1l0ZFS4kiRCBnLi6OTHzh0RPk3h9isBxkkqELtQNFi1Vr/tiG9p1yfoUdKVwISuXWQR+hwwM4VQ==", - "dev": true, - "requires": { - "@babel/types": "^7.7.4", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", - "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.7.4", - "@babel/template": "^7.7.4", - "@babel/types": "^7.7.4" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", - "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", - "dev": true, - "requires": { - "@babel/types": "^7.7.4" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz", - "integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==", - "dev": true, - "requires": { - "@babel/types": "^7.7.4" - } - }, - "@babel/helpers": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.7.4.tgz", - "integrity": "sha512-ak5NGZGJ6LV85Q1Zc9gn2n+ayXOizryhjSUBTdu5ih1tlVCJeuQENzc4ItyCVhINVXvIT/ZQ4mheGIsfBkpskg==", - "dev": true, - "requires": { - "@babel/template": "^7.7.4", - "@babel/traverse": "^7.7.4", - "@babel/types": "^7.7.4" - } - }, - "@babel/parser": { - "version": "7.7.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.7.tgz", - "integrity": "sha512-WtTZMZAZLbeymhkd/sEaPD8IQyGAhmuTuvTzLiCFM7iXiVdY0gc0IaI+cW0fh1BnSMbJSzXX6/fHllgHKwHhXw==", - "dev": true - }, - "@babel/template": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", - "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4" - } - }, - "@babel/traverse": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.4.tgz", - "integrity": "sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.7.4", - "@babel/helper-function-name": "^7.7.4", - "@babel/helper-split-export-declaration": "^7.7.4", - "@babel/parser": "^7.7.4", - "@babel/types": "^7.7.4", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "@babel/types": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", - "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", "dev": true, "requires": { - "safe-buffer": "~5.1.1" + "tslib": "^2.6.2" } }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "@azure/core-util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.9.0.tgz", + "integrity": "sha512-AfalUQ1ZppaKuxPPMsFEUdX6GZPB3d9paR9d/TTL7Ow2De8cJaC7ibi7kWVlFAVPCYo31OcnGymc0R89DX8Oaw==", "dev": true, "requires": { - "ms": "^2.1.1" + "@azure/abort-controller": "^2.0.0", + "tslib": "^2.6.2" } }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true } } }, - "istanbul-lib-processinfo": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", - "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", - "dev": true, + "@azure/core-rest-pipeline": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.10.1.tgz", + "integrity": "sha512-Kji9k6TOFRDB5ZMTw8qUf2IJ+CeJtsuMdAHox9eqpTf1cefiNMpzrfnF6sINEBZJsaVaWgQ0o48B6kcUH68niA==", "requires": { - "archy": "^1.0.0", - "cross-spawn": "^7.0.0", - "istanbul-lib-coverage": "^3.0.0-alpha.1", - "make-dir": "^3.0.0", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "uuid": "^3.3.3" + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-tracing": "^1.0.1", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "form-data": "^4.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "tslib": "^2.2.0", + "uuid": "^8.3.0" }, "dependencies": { - "cross-spawn": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", - "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "make-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", - "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==" }, - "rimraf": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz", - "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==", - "dev": true, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { - "glob": "^7.1.3" + "ms": "2.1.2" } }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "requires": { - "shebang-regex": "^3.0.0" + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" } }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "uuid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", - "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" } } }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, + "@azure/core-tracing": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.1.tgz", + "integrity": "sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==", "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" + "tslib": "^2.2.0" }, "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "make-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", - "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@azure/core-util": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.2.0.tgz", + "integrity": "sha512-ffGIw+Qs8bNKNLxz5UPkz4/VBM/EZY07mPve1ZYFqYUdPwFqRj0RPk0U7LZMOfT7GCck9YjuT1Rfp1PApNl1ng==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "tslib": "^2.2.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } + }, + "@azure/identity": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.2.1.tgz", + "integrity": "sha512-U8hsyC9YPcEIzoaObJlRDvp7KiF0MGS7xcWbyJSVvXRkC/HXo1f0oYeBYmEvVgRfacw7GHf6D6yAoh9JHz6A5Q==", + "dev": true, + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.5.0", + "@azure/core-client": "^1.4.0", + "@azure/core-rest-pipeline": "^1.1.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.3.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^3.11.1", + "@azure/msal-node": "^2.9.2", + "events": "^3.0.0", + "jws": "^4.0.0", + "open": "^8.0.0", + "stoppable": "^1.1.0", + "tslib": "^2.2.0" + }, + "dependencies": { + "@azure/core-util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.9.0.tgz", + "integrity": "sha512-AfalUQ1ZppaKuxPPMsFEUdX6GZPB3d9paR9d/TTL7Ow2De8cJaC7ibi7kWVlFAVPCYo31OcnGymc0R89DX8Oaw==", "dev": true, "requires": { - "semver": "^6.0.0" + "@azure/abort-controller": "^2.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dev": true, + "requires": { + "tslib": "^2.6.2" + } + } } }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } } } }, - "istanbul-lib-source-maps": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", - "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", - "dev": true, + "@azure/logger": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.4.tgz", + "integrity": "sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg==", "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" + "tslib": "^2.2.0" }, "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" } } }, - "istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-2osTcC8zcOSUkImzN2EWQta3Vdi4WjjKw99P2yWx5mLnigAM0Rd5uYFn1cf2i/Ois45GkNjaoTqc5CxgMSX80A==", + "@azure/msal-browser": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.14.0.tgz", + "integrity": "sha512-Un85LhOoecJ3HDTS3Uv3UWnXC9/43ZSO+Kc+anSqpZvcEt58SiO/3DuVCAe1A3I5UIBYJNMgTmZPGXQ0MVYrwA==", "dev": true, "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" + "@azure/msal-common": "14.10.0" } }, - "isurl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", - "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", + "@azure/msal-common": { + "version": "14.10.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.10.0.tgz", + "integrity": "sha512-Zk6DPDz7e1wPgLoLgAp0349Yay9RvcjPM5We/ehuenDNsz/t9QEFI7tRoHpp/e47I4p20XE3FiDlhKwAo3utDA==", + "dev": true + }, + "@azure/msal-node": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.9.2.tgz", + "integrity": "sha512-8tvi6Cos3m+0KmRbPjgkySXi+UQU/QiuVRFnrxIwt5xZlEEFa69O04RTaNESGgImyBBlYbo2mfE8/U8Bbdk1WQ==", "dev": true, "requires": { - "has-to-string-tag-x": "^1.2.0", - "is-object": "^1.0.1" + "@azure/msal-common": "14.12.0", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "dependencies": { + "@azure/msal-common": { + "version": "14.12.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.12.0.tgz", + "integrity": "sha512-IDDXmzfdwmDkv4SSmMEyAniJf6fDu3FJ7ncOjlxkDuT85uSnLEhZi3fGZpoR7T4XZpOMx9teM9GXBgrfJgyeBw==", + "dev": true + } } }, - "iterate-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.1.tgz", - "integrity": "sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw==", - "dev": true + "@azure/opentelemetry-instrumentation-azure-sdk": { + "version": "1.0.0-beta.5", + "resolved": "https://registry.npmjs.org/@azure/opentelemetry-instrumentation-azure-sdk/-/opentelemetry-instrumentation-azure-sdk-1.0.0-beta.5.tgz", + "integrity": "sha512-fsUarKQDvjhmBO4nIfaZkfNSApm1hZBzcvpNbSrXdcUBxu7lRvKsV5DnwszX7cnhLyVOW9yl1uigtRQ1yDANjA==", + "requires": { + "@azure/core-tracing": "^1.0.0", + "@azure/logger": "^1.0.0", + "@opentelemetry/api": "^1.4.1", + "@opentelemetry/core": "^1.15.2", + "@opentelemetry/instrumentation": "^0.41.2", + "tslib": "^2.2.0" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + } + } }, - "iterate-value": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz", - "integrity": "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==", + "@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, "requires": { - "es-get-iterator": "^1.0.2", - "iterate-iterator": "^1.0.1" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" } }, - "jest-docblock": { - "version": "21.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-21.2.0.tgz", - "integrity": "sha512-5IZ7sY9dBAYSV+YjQ0Ovb540Ku7AO9Z5o2Cg789xj167iQuZ2cG+z0f3Uct6WeYLbU6aQiM2pCs7sZ+4dotydw==", + "@babel/compat-data": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.6.tgz", + "integrity": "sha512-29tfsWTq2Ftu7MXmimyC0C5FDZv5DYxOZkh3XD3+QW4V/BYuv/LyEsjj3c0hqedEaDt6DBfDvexMKU8YevdqFg==", "dev": true }, - "jest-worker": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.3.0.tgz", - "integrity": "sha512-Vmpn2F6IASefL+DVBhPzI2J9/GJUsqzomdeN+P+dK8/jKxbh8R3BtFnx3FIta7wYlPU62cpJMJQo4kuOowcMnw==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" + "@babel/core": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.6.tgz", + "integrity": "sha512-HPIyDa6n+HKw5dEuway3vVAhBboYCtREBMp+IWeseZy6TFtzn6MHkCH2KKYUOC/vKKwgSMHQW4htBOrmuRPXfw==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helpers": "^7.22.6", + "@babel/parser": "^7.22.6", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.6", + "@babel/types": "^7.22.5", + "@nicolo-ribaudo/semver-v6": "^6.3.3", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2" }, "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "ms": "2.1.2" } } } }, - "jpeg-js": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.3.7.tgz", - "integrity": "sha512-9IXdWudL61npZjvLuVe/ktHiA41iE8qFyLB+4VDTblEsWBzeg8WQTlktdUK4CdncUqtUgUg0bbOmTE2bKBKaBQ==", - "dev": true - }, - "jquery": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.0.tgz", - "integrity": "sha512-Xb7SVYMvygPxbFMpTFQiHh1J7HClEaThguL15N/Gg37Lri/qKyhRGZYzHRyLH8Stq3Aow0LsHO2O2ci86fCrNQ==", - "dev": true - }, - "jquery-ui": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.12.1.tgz", - "integrity": "sha1-vLQEXI3QU5wTS8FIjN0+dop6nlE=", - "dev": true - }, - "js-levenshtein": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", - "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", - "dev": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "@babel/generator": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "dev": true, + "requires": { + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + } }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "@babel/helper-compilation-targets": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.6.tgz", + "integrity": "sha512-534sYEqWD9VfUm3IPn2SLcH4Q3P86XL+QvqdC7ZsFrzyyPF3T4XGiVghF6PTYNdWg6pXuoqXxNQAhbYeEInTzA==", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "@babel/compat-data": "^7.22.6", + "@babel/helper-validator-option": "^7.22.5", + "@nicolo-ribaudo/semver-v6": "^6.3.3", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1" }, "dependencies": { - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - } - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" - }, - "jsdom": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-15.1.1.tgz", - "integrity": "sha512-cQZRBB33arrDAeCrAEWn1U3SvrvC8XysBua9Oqg1yWrsY/gYcusloJC3RZJXuY5eehSCmws8f2YeliCqGSkrtQ==", - "dev": true, - "requires": { - "abab": "^2.0.0", - "acorn": "^6.1.1", - "acorn-globals": "^4.3.2", - "array-equal": "^1.0.0", - "cssom": "^0.3.6", - "cssstyle": "^1.2.2", - "data-urls": "^1.1.0", - "domexception": "^1.0.1", - "escodegen": "^1.11.1", - "html-encoding-sniffer": "^1.0.2", - "nwsapi": "^2.1.4", - "parse5": "5.1.0", - "pn": "^1.1.0", - "request": "^2.88.0", - "request-promise-native": "^1.0.7", - "saxes": "^3.1.9", - "symbol-tree": "^3.2.2", - "tough-cookie": "^3.0.1", - "w3c-hr-time": "^1.0.1", - "w3c-xmlserializer": "^1.1.2", - "webidl-conversions": "^4.0.2", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^7.0.0", - "ws": "^7.0.0", - "xml-name-validator": "^3.0.0" - }, - "dependencies": { - "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", - "dev": true - }, - "escodegen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.1.tgz", - "integrity": "sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==", + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "requires": { - "esprima": "^3.1.3", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" + "yallist": "^3.0.2" } }, - "parse5": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", - "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true - }, - "tough-cookie": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", - "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", - "dev": true, - "requires": { - "ip-regex": "^2.1.0", - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "ws": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.1.0.tgz", - "integrity": "sha512-Swie2C4fs7CkwlHu1glMePLYJJsWjzhl1vm3ZaLplD0h7OMkZyZ6kLTB/OagiU923bZrPFXuDTeEqaEN4NWG4g==", - "dev": true, - "requires": { - "async-limiter": "^1.0.0" - } } } }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" + "@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "requires": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + } }, - "json-edm-parser": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/json-edm-parser/-/json-edm-parser-0.1.2.tgz", - "integrity": "sha1-HmCw/vG8CvZ7wNFG393lSGzWFbQ=", + "@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, "requires": { - "jsonparse": "~1.2.0" + "@babel/types": "^7.22.5" } }, - "json-loader": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", - "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", - "dev": true + "@babel/helper-module-imports": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", + "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "@babel/helper-module-transforms": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz", + "integrity": "sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + "@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "@babel/helper-validator-option": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", + "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", + "dev": true }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "@babel/helpers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", + "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", "dev": true, "requires": { - "jsonify": "~0.0.0" + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1" } }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true + "@babel/parser": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", + "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "dev": true, + "requires": { + "@babel/types": "^7.27.1" + } }, - "json-stringify-pretty-compact": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-2.0.0.tgz", - "integrity": "sha512-WRitRfs6BGq4q8gTgOy4ek7iPFXjbra0H3PmDLKm2xnZ+Gh1HUhiKGgCZkSPNULlP7mvfu6FV/mOLhCarspADQ==", + "@babel/runtime": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", + "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==", "dev": true }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + "@babel/runtime-corejs3": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.27.1.tgz", + "integrity": "sha512-909rVuj3phpjW6y0MCXAZ5iNeORePa6ldJvp2baWGcTjwqbBDDz6xoS5JHJ7lS88NlwLYj07ImL/8IUMtDZzTA==", + "dev": true, + "requires": { + "core-js-pure": "^3.30.2" + } }, - "json2csv": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/json2csv/-/json2csv-4.5.2.tgz", - "integrity": "sha512-Te2Knce3VfLKyurH3AolM6Y781ZE+R3jQ+0YQ0HYLiubyicST/19vML24e1dpScaaEQb+1Q1t5IcGXr6esM9Lw==", + "@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, "requires": { - "commander": "^2.15.1", - "jsonparse": "^1.3.1", - "lodash.get": "^4.4.2" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + } + }, + "@babel/traverse": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", + "debug": "^4.1.0", + "globals": "^11.1.0" }, "dependencies": { - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "dev": true + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } } } }, - "json3": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", - "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "@babel/types": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + } + }, + "@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", "dev": true }, - "json5": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", - "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, "requires": { - "minimist": "^1.2.0" + "@cspotcode/source-map-consumer": "0.8.0" } }, - "jsonc-parser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.1.0.tgz", - "integrity": "sha512-n9GrT8rrr2fhvBbANa1g+xFmgGK5X91KFeDwlKQ3+SJfmH5+tKv/M/kahx/TXOMflfWHKGKqKyfHQaLKTNzJ6w==" + "@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, "requires": { - "graceful-fs": "^4.1.6" + "eslint-visitor-keys": "^3.3.0" + } + }, + "@eslint-community/regexpp": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "dev": true + }, + "@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } } }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true }, - "jsonparse": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.2.0.tgz", - "integrity": "sha1-XAxWhRBxYOcv50ib3eoLRMK8Z70=" - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } + "@gulpjs/messages": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@gulpjs/messages/-/messages-1.1.0.tgz", + "integrity": "sha512-Ys9sazDatyTgZVb4xPlDufLweJ/Os2uHWOv+Caxvy2O85JcnT4M3vc73bi8pdLWlv3fdWQz3pdI9tVwo8rQQSg==", + "dev": true }, - "jsx-ast-utils": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz", - "integrity": "sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w==", + "@gulpjs/to-absolute-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@gulpjs/to-absolute-glob/-/to-absolute-glob-4.0.0.tgz", + "integrity": "sha512-kjotm7XJrJ6v+7knhPaRgaT6q8F8K2jiafwYdNHLzmV0uGLuZY43FK6smNSHUPrhq5kX2slCUy+RGG/xGqmIKA==", "dev": true, "requires": { - "array-includes": "^3.1.1", - "object.assign": "^4.1.0" + "is-negated-glob": "^1.0.0" } }, - "just-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", - "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=", - "dev": true - }, - "just-extend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", - "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", - "dev": true - }, - "keyv": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", - "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", + "@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "dev": true, "requires": { - "json-buffer": "3.0.0" + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "dependencies": { + "debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } } }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "kuler": { + "@humanwhocodes/module-importer": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", - "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", - "requires": { - "colornames": "^1.1.1" - } - }, - "labella": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/labella/-/labella-1.1.4.tgz", - "integrity": "sha1-xsxaNA6N80DrM1YzaD6lm4KMMi0=", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true }, - "language-subtag-registry": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.20.tgz", - "integrity": "sha512-KPMwROklF4tEx283Xw0pNKtfTj1gZ4UByp4EsIFWLgBavJltF4TiYPc39k06zSTsLzxTVXXDSpbwaQXaFB4Qeg==", + "@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, - "language-tags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", - "integrity": "sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=", - "dev": true, - "requires": { - "language-subtag-registry": "~0.3.2" - } - }, - "last-run": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", - "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", - "dev": true, - "requires": { - "default-resolution": "^2.0.0", - "es6-weak-map": "^2.0.1" - } + "@iarna/toml": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-3.0.0.tgz", + "integrity": "sha512-td6ZUkz2oS3VeleBcN+m//Q6HlCFCPrnI0FZhrt/h4XqLEdOyYp2u21nd8MdsR+WJy5r9PTDaHTDDfhf4H4l6Q==" }, - "lazystream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", - "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, "requires": { - "readable-stream": "^2.0.5" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "ansi-regex": "^6.0.1" + } + }, + "wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" } } } }, - "lcid": { + "@istanbuljs/load-nyc-config": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz", + "integrity": "sha512-ZR0rq/f/E4f4XcgnDvtMWXCUJpi8eO0rssVhmztsZqLIEFA9UUP9zmpE0VxlM+kv/E1ul2I876Fwil2ayptDVg==", "dev": true, "requires": { - "invert-kv": "^1.0.0" + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" } }, - "lead": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", - "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", + "@istanbuljs/nyc-config-typescript": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/nyc-config-typescript/-/nyc-config-typescript-1.0.2.tgz", + "integrity": "sha512-iKGIyMoyJuFnJRSVTZ78POIRvNnwZaWIf8vG4ZS3rQq58MMDrqEX2nnzx0R28V2X8JvmKYiqY9FP2hlJsm8A0w==", "dev": true, "requires": { - "flush-write-stream": "^1.0.2" + "@istanbuljs/schema": "^0.1.2" } }, - "leaflet": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.5.1.tgz", - "integrity": "sha512-ekM9KAeG99tYisNBg0IzEywAlp0hYI5XRipsqRXyRTeuU8jcuntilpp+eFf5gaE0xubc9RuSNIVtByEKwqFV0w==", + "@istanbuljs/schema": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", + "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", "dev": true }, - "less": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/less/-/less-3.9.0.tgz", - "integrity": "sha512-31CmtPEZraNUtuUREYjSqRkeETFdyEHSEPAGq4erDlUXtda7pzNmctdljdIagSb589d/qXGWiiP31R5JVf+v0w==", + "@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "requires": { - "clone": "^2.1.2", - "errno": "^0.1.1", - "graceful-fs": "^4.1.2", - "image-size": "~0.5.0", - "mime": "^1.4.1", - "mkdirp": "^0.5.0", - "promise": "^7.1.1", - "request": "^2.83.0", - "source-map": "~0.6.0" - }, - "dependencies": { - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "dev": true - } + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "less-loader": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-5.0.0.tgz", - "integrity": "sha512-bquCU89mO/yWLaUq0Clk7qCsKhsF/TZpJUzETRvJa9KSVEL9SO3ovCvdEHISBhrC81OwC8QSVX7E0bzElZj9cg==", + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true + }, + "@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, "requires": { - "clone": "^2.1.1", - "loader-utils": "^1.1.0", - "pify": "^4.0.1" - }, - "dependencies": { - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "dev": true - } + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, - "less-plugin-inline-urls": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/less-plugin-inline-urls/-/less-plugin-inline-urls-1.2.0.tgz", - "integrity": "sha1-XdqwegwlcfGihVz5Kd3J78Hjisw=", + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "liftoff": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", - "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", - "dev": true, + "@microsoft/1ds-core-js": { + "version": "3.2.13", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-3.2.13.tgz", + "integrity": "sha512-CluYTRWcEk0ObG5EWFNWhs87e2qchJUn0p2D21ZUa3PWojPZfPSBs4//WIE0MYV8Qg1Hdif2ZTwlM7TbYUjfAg==", "requires": { - "extend": "^3.0.0", - "findup-sync": "^3.0.0", - "fined": "^1.0.1", - "flagged-respawn": "^1.0.0", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.0", - "rechoir": "^0.6.2", - "resolve": "^1.1.7" + "@microsoft/applicationinsights-core-js": "2.8.15", + "@microsoft/applicationinsights-shims": "^2.0.2", + "@microsoft/dynamicproto-js": "^1.1.7" } }, - "line-by-line": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/line-by-line/-/line-by-line-0.1.6.tgz", - "integrity": "sha512-MmwVPfOyp0lWnEZ3fBA8Ah4pMFvxO6WgWovqZNu7Y4J0TNnGcsV4S1LzECHbdgqk1hoHc2mFP1Axc37YUqwafg==" - }, - "linear-layout-vector": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/linear-layout-vector/-/linear-layout-vector-0.0.1.tgz", - "integrity": "sha1-OYEU1zA7bsx/1rJzr3uEAdi6nHA=", - "dev": true + "@microsoft/1ds-post-js": { + "version": "3.2.13", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-3.2.13.tgz", + "integrity": "sha512-HgS574fdD19Bo2vPguyznL4eDw7Pcm1cVNpvbvBLWiW3x4e1FCQ3VMXChWnAxCae8Hb0XqlA2sz332ZobBavTA==", + "requires": { + "@microsoft/1ds-core-js": "3.2.13", + "@microsoft/applicationinsights-shims": "^2.0.2", + "@microsoft/dynamicproto-js": "^1.1.7" + } }, - "linebreak": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.0.2.tgz", - "integrity": "sha512-bJwSRsJeAmaZYnkcwl5sCQNfSDAhBuXxb6L27tb+qkBRtUQSSTUa5bcgCPD6hFEkRNlpWHfK7nFMmcANU7ZP1w==", + "@microsoft/applicationinsights-channel-js": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.0.2.tgz", + "integrity": "sha512-jDBNKbCHsJgmpv0CKNhJ/uN9ZphvfGdb93Svk+R4LjO8L3apNNMbDDPxBvXXi0uigRmA1TBcmyBG4IRKjabGhw==", "requires": { - "base64-js": "0.0.8", - "brfs": "^2.0.2", - "unicode-trie": "^1.0.0" + "@microsoft/applicationinsights-common": "3.0.2", + "@microsoft/applicationinsights-core-js": "3.0.2", + "@microsoft/applicationinsights-shims": "3.0.1", + "@microsoft/dynamicproto-js": "^2.0.2", + "@nevware21/ts-async": ">= 0.2.4 < 2.x", + "@nevware21/ts-utils": ">= 0.9.5 < 2.x" }, "dependencies": { - "base64-js": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", - "integrity": "sha1-EQHpVE9KdrG8OybUUsqW16NeeXg=" + "@microsoft/applicationinsights-core-js": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.2.tgz", + "integrity": "sha512-WQhVhzlRlLDrQzn3OShCW/pL3BW5WC57t0oywSknX3q7lMzI3jDg7Ihh0iuIcNTzGCTbDkuqr4d6IjEDWIMtJQ==", + "requires": { + "@microsoft/applicationinsights-shims": "3.0.1", + "@microsoft/dynamicproto-js": "^2.0.2", + "@nevware21/ts-async": ">= 0.2.4 < 2.x", + "@nevware21/ts-utils": ">= 0.9.5 < 2.x" + } }, - "unicode-trie": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-1.0.0.tgz", - "integrity": "sha512-v5raLKsobbFbWLMoX9+bChts/VhPPj3XpkNr/HbqkirXR1DPk8eo9IYKyvk0MQZFkaoRsFj2Rmaqgi2rfAZYtA==", + "@microsoft/applicationinsights-shims": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", + "integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==", + "requires": { + "@nevware21/ts-utils": ">= 0.9.4 < 2.x" + } + }, + "@microsoft/dynamicproto-js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz", + "integrity": "sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg==", "requires": { - "pako": "^0.2.5", - "tiny-inflate": "^1.0.0" + "@nevware21/ts-utils": ">= 0.9.4 < 2.x" } } } }, - "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, + "@microsoft/applicationinsights-common": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-3.0.2.tgz", + "integrity": "sha512-y+WXWop+OVim954Cu1uyYMnNx6PWO8okHpZIQi/1YSqtqaYdtJVPv4P0AVzwJdohxzVfgzKvqj9nec/VWqE2Zg==", "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" + "@microsoft/applicationinsights-core-js": "3.0.2", + "@microsoft/applicationinsights-shims": "3.0.1", + "@microsoft/dynamicproto-js": "^2.0.2", + "@nevware21/ts-utils": ">= 0.9.5 < 2.x" }, "dependencies": { - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, + "@microsoft/applicationinsights-core-js": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.2.tgz", + "integrity": "sha512-WQhVhzlRlLDrQzn3OShCW/pL3BW5WC57t0oywSknX3q7lMzI3jDg7Ihh0iuIcNTzGCTbDkuqr4d6IjEDWIMtJQ==", "requires": { - "error-ex": "^1.2.0" + "@microsoft/applicationinsights-shims": "3.0.1", + "@microsoft/dynamicproto-js": "^2.0.2", + "@nevware21/ts-async": ">= 0.2.4 < 2.x", + "@nevware21/ts-utils": ">= 0.9.5 < 2.x" } }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true + "@microsoft/applicationinsights-shims": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", + "integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==", + "requires": { + "@nevware21/ts-utils": ">= 0.9.4 < 2.x" + } + }, + "@microsoft/dynamicproto-js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz", + "integrity": "sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg==", + "requires": { + "@nevware21/ts-utils": ">= 0.9.4 < 2.x" + } } } }, - "loader-runner": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", - "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", - "dev": true + "@microsoft/applicationinsights-core-js": { + "version": "2.8.15", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.15.tgz", + "integrity": "sha512-yYAs9MyjGr2YijQdUSN9mVgT1ijI1FPMgcffpaPmYbHAVbQmF7bXudrBWHxmLzJlwl5rfep+Zgjli2e67lwUqQ==", + "requires": { + "@microsoft/applicationinsights-shims": "2.0.2", + "@microsoft/dynamicproto-js": "^1.1.9" + } }, - "loader-utils": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", - "dev": true, + "@microsoft/applicationinsights-shims": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.2.tgz", + "integrity": "sha512-PoHEgsnmcqruLNHZ/amACqdJ6YYQpED0KSRe6J7gIJTtpZC1FfFU9b1fmDKDKtFoUSrPzEh1qzO3kmRZP0betg==" + }, + "@microsoft/applicationinsights-web-basic": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.0.2.tgz", + "integrity": "sha512-6Lq0DE/pZp9RvSV+weGbcxN1NDmfczj6gNPhvZKV2YSQ3RK0LZE3+wjTWLXfuStq8a+nCBdsRpWk8tOKgsoxcg==", "requires": { - "big.js": "^5.2.2", - "emojis-list": "^2.0.0", - "json5": "^1.0.1" + "@microsoft/applicationinsights-channel-js": "3.0.2", + "@microsoft/applicationinsights-common": "3.0.2", + "@microsoft/applicationinsights-core-js": "3.0.2", + "@microsoft/applicationinsights-shims": "3.0.1", + "@microsoft/dynamicproto-js": "^2.0.2", + "@nevware21/ts-async": ">= 0.2.4 < 2.x", + "@nevware21/ts-utils": ">= 0.9.5 < 2.x" }, "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, + "@microsoft/applicationinsights-core-js": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.2.tgz", + "integrity": "sha512-WQhVhzlRlLDrQzn3OShCW/pL3BW5WC57t0oywSknX3q7lMzI3jDg7Ihh0iuIcNTzGCTbDkuqr4d6IjEDWIMtJQ==", "requires": { - "minimist": "^1.2.0" + "@microsoft/applicationinsights-shims": "3.0.1", + "@microsoft/dynamicproto-js": "^2.0.2", + "@nevware21/ts-async": ">= 0.2.4 < 2.x", + "@nevware21/ts-utils": ">= 0.9.5 < 2.x" + } + }, + "@microsoft/applicationinsights-shims": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", + "integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==", + "requires": { + "@nevware21/ts-utils": ">= 0.9.4 < 2.x" + } + }, + "@microsoft/dynamicproto-js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz", + "integrity": "sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg==", + "requires": { + "@nevware21/ts-utils": ">= 0.9.4 < 2.x" } } } }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, + "@microsoft/applicationinsights-web-snippet": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-snippet/-/applicationinsights-web-snippet-1.0.1.tgz", + "integrity": "sha512-2IHAOaLauc8qaAitvWS+U931T+ze+7MNWrDHY47IENP5y2UA0vqJDu67kWZDdpCN1fFC77sfgfB+HV7SrKshnQ==" + }, + "@microsoft/dynamicproto-js": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.9.tgz", + "integrity": "sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ==" + }, + "@nevware21/ts-async": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@nevware21/ts-async/-/ts-async-0.3.0.tgz", + "integrity": "sha512-ZUcgUH12LN/F6nzN0cYd0F/rJaMLmXr0EHVTyYfaYmK55bdwE4338uue4UiVoRqHVqNW4KDUrJc49iGogHKeWA==", "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "@nevware21/ts-utils": ">= 0.10.0 < 2.x" } }, - "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + "@nevware21/ts-utils": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@nevware21/ts-utils/-/ts-utils-0.10.1.tgz", + "integrity": "sha512-pMny25NnF2/MJwdqC3Iyjm2pGIXNxni4AROpcqDeWa+td9JMUY4bUS9uU9XW+BoBRqTLUL+WURF9SOd/6OQzRg==" }, - "lodash-es": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz", - "integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==", + "@nicolo-ribaudo/semver-v6": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz", + "integrity": "sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==", "dev": true }, - "lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", - "dev": true + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } }, - "lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } }, - "lodash.curry": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", - "integrity": "sha1-JI42By7ekGUB11lmIAqG2riyMXA=", - "dev": true + "@opentelemetry/api": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.4.1.tgz", + "integrity": "sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA==" }, - "lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", - "dev": true + "@opentelemetry/core": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.15.2.tgz", + "integrity": "sha512-+gBv15ta96WqkHZaPpcDHiaz0utiiHZVfm2YOYSqFGrUaJpPkMoSuLBB58YFQGi6Rsb9EHos84X6X5+9JspmLw==", + "requires": { + "@opentelemetry/semantic-conventions": "1.15.2" + } }, - "lodash.difference": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=", - "dev": true + "@opentelemetry/instrumentation": { + "version": "0.41.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.41.2.tgz", + "integrity": "sha512-rxU72E0pKNH6ae2w5+xgVYZLzc5mlxAbGzF4shxMVK8YC2QQsfN38B2GPbj0jvrKWWNUElfclQ+YTykkNg/grw==", + "requires": { + "@types/shimmer": "^1.0.2", + "import-in-the-middle": "1.4.2", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.1", + "shimmer": "^1.2.1" + } + }, + "@opentelemetry/resources": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.15.2.tgz", + "integrity": "sha512-xmMRLenT9CXmm5HMbzpZ1hWhaUowQf8UB4jMjFlAxx1QzQcsD3KFNAVX/CAWzFPtllTyTplrA4JrQ7sCH3qmYw==", + "requires": { + "@opentelemetry/core": "1.15.2", + "@opentelemetry/semantic-conventions": "1.15.2" + } + }, + "@opentelemetry/sdk-trace-base": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.15.2.tgz", + "integrity": "sha512-BEaxGZbWtvnSPchV98qqqqa96AOcb41pjgvhfzDij10tkBhIu9m0Jd6tZ1tJB5ZHfHbTffqYVYE0AOGobec/EQ==", + "requires": { + "@opentelemetry/core": "1.15.2", + "@opentelemetry/resources": "1.15.2", + "@opentelemetry/semantic-conventions": "1.15.2" + } + }, + "@opentelemetry/semantic-conventions": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.15.2.tgz", + "integrity": "sha512-CjbOKwk2s+3xPIMcd5UNYQzsf+v94RczbdNix9/kQh38WiQkM90sUOi3if8eyHFgiBjBjhwXrA7W3ydiSQP9mw==" + }, + "@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true }, - "lodash.escape": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", - "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=", + "@polka/url": { + "version": "1.0.0-next.21", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", + "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", "dev": true }, - "lodash.escaperegexp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", - "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=", + "@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", "dev": true }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", + "@sindresorhus/is": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", + "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==", "dev": true }, - "lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", - "dev": true + "@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } }, - "lodash.flow": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", - "integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=", - "dev": true + "@sinonjs/fake-timers": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.0" + } }, - "lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", - "dev": true + "@sinonjs/samsam": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "dev": true, + "requires": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + } + } }, - "lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", + "@sinonjs/text-encoding": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", "dev": true }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "dev": true }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", "dev": true }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", "dev": true }, - "lodash.mergewith": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", "dev": true }, - "lodash.some": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", - "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=", + "@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", "dev": true }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true + "@types/bent": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@types/bent/-/bent-7.3.3.tgz", + "integrity": "sha512-5NEIhVzHiZ6wMjFBmJ3gwjxwGug6amMoAn93rtDBttwrODxm+bt63u+MJA7H9NGGM4X1m73sJrAxDapktl036Q==", + "dev": true, + "requires": { + "@types/node": "*" + } }, - "lodash.tail": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", - "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=", + "@types/chai": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz", + "integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==", "dev": true }, - "lodash.template": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", - "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "@types/chai-arrays": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/chai-arrays/-/chai-arrays-2.0.0.tgz", + "integrity": "sha512-5h5jnAC9C64YnD7WJpA5gBG7CppF/QmoWytOssJ6ysENllW49NBdpsTx6uuIBOpnzAnXThb8jBICgB62wezTLQ==", "dev": true, "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.templatesettings": "^4.0.0" + "@types/chai": "*" } }, - "lodash.templatesettings": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", - "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "@types/chai-as-promised": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.5.tgz", + "integrity": "sha512-jStwss93SITGBwt/niYrkf2C+/1KTeZCZl1LaeezTlqppAKeoQC7jxyqYuP72sxBGKCIbw7oHgbYssIRzT5FCQ==", "dev": true, "requires": { - "lodash._reinterpolate": "^3.0.0" + "@types/chai": "*" } }, - "lodash.union": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", - "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=", - "dev": true - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, - "log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "@types/decompress": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@types/decompress/-/decompress-4.2.5.tgz", + "integrity": "sha512-LdL+kbcKGs9TzvB/K+OBGzPfDoP6gwwTsykYjodlzUJUUYp/43c1p1jE5YTtz3z4Ml90iruvBXbJ6+kDvb3WSQ==", "dev": true, "requires": { - "chalk": "^2.4.2" - } - }, - "log4js": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.1.2.tgz", - "integrity": "sha512-knS4Y30pC1e0n7rfx3VxcLOdBCsEo0o6/C7PVTGxdVK+5b1TYOSGQPn9FDcrhkoQBV29qwmA2mtkznPAQKnxQg==", - "requires": { - "date-format": "^3.0.0", - "debug": "^4.1.1", - "flatted": "^2.0.1", - "rfdc": "^1.1.4", - "streamroller": "^2.2.3" - }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "^2.1.1" - } - } + "@types/node": "*" } }, - "logform": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.1.2.tgz", - "integrity": "sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ==", + "@types/download": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@types/download/-/download-8.0.3.tgz", + "integrity": "sha512-IDwXjU7zCtuFVvI0Plnb02TpXyj3RA4YeOKQvEfsjdJeWxZ9hTl6lxeNsU2bLWn0aeAS7fyMl74w/TbdOlS2KQ==", + "dev": true, "requires": { - "colors": "^1.2.1", - "fast-safe-stringify": "^2.0.4", - "fecha": "^2.3.3", - "ms": "^2.1.1", - "triple-beam": "^1.3.0" + "@types/decompress": "*", + "@types/got": "^9", + "@types/node": "*" } }, - "lolex": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", - "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", + "@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, "requires": { - "@sinonjs/commons": "^1.7.0" + "@types/estree": "*", + "@types/json-schema": "*" } }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" + "@types/eslint": "*", + "@types/estree": "*" } }, - "lower-case": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", - "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", + "@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" - }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "@types/fs-extra": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", + "integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==", + "dev": true, "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "@types/jsonfile": "*", + "@types/node": "*" } }, - "lru-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", - "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", + "@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", "dev": true, "requires": { - "es5-ext": "~0.10.2" + "@types/minimatch": "*", + "@types/node": "*" } }, - "magic-string": { - "version": "0.22.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz", - "integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==", + "@types/got": { + "version": "9.6.12", + "resolved": "https://registry.npmjs.org/@types/got/-/got-9.6.12.tgz", + "integrity": "sha512-X4pj/HGHbXVLqTpKjA2ahI4rV/nNBc9mGO2I/0CgAra+F2dKgMXnENv2SRpemScBzBAI4vMelIVYViQxlSE6xA==", + "dev": true, "requires": { - "vlq": "^0.2.2" + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + }, + "dependencies": { + "form-data": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", + "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35", + "safe-buffer": "^5.2.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } } }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, + "@types/jsonfile": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz", + "integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==", "dev": true, "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" + "@types/node": "*" } }, - "make-error": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", - "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "@types/lodash": { + "version": "4.14.181", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.181.tgz", + "integrity": "sha512-n3tyKthHJbkiWhDZs3DkhkCzt2MexYHXlX0td5iMplyfwketaOeKboEVBqzceH7juqvEg3q5oUoBFxSLu7zFag==", "dev": true }, - "make-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, + "@types/mocha": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz", + "integrity": "sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==", + "dev": true + }, + "@types/node": { + "version": "22.19.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", + "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", "dev": true, "requires": { - "kind-of": "^6.0.2" + "undici-types": "~6.21.0" } }, - "mamacro": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", - "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==", + "@types/semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==", "dev": true }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "@types/shimmer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.0.2.tgz", + "integrity": "sha512-dKkr1bTxbEsFlh2ARpKzcaAmsYixqt9UyCdoEZk8rHyE4iQYcDCyvSjDSf7JUWJHlJiTtbIoQjxKh6ViywqDAg==" + }, + "@types/shortid": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz", + "integrity": "sha1-gJPuBBam4r8qpjOBCRFLP7/6Dps=", + "dev": true + }, + "@types/sinon": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.3.tgz", + "integrity": "sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==", "dev": true, "requires": { - "p-defer": "^1.0.0" + "@types/sinonjs__fake-timers": "*" } }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "@types/sinonjs__fake-timers": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", + "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", "dev": true }, - "map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", + "@types/stack-trace": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/stack-trace/-/stack-trace-0.0.29.tgz", + "integrity": "sha512-TgfOX+mGY/NyNxJLIbDWrO9DjGoVSW9+aB8H2yy1fy32jsvxijhmyJI9fDFgvz3YP4lvJaq9DzdR/M1bOgVc9g==", "dev": true }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "@types/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha1-EHPEvIJHVK49EM+riKsCN7qWTk0=", + "dev": true + }, + "@types/tough-cookie": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.3.tgz", + "integrity": "sha512-THo502dA5PzG/sfQH+42Lw3fvmYkceefOspdCwpHRul8ik2Jv1K8I5OZz1AT3/rs46kwgMCe9bSBmDLYkkOMGg==", + "dev": true + }, + "@types/vscode": { + "version": "1.100.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.100.0.tgz", + "integrity": "sha512-4uNyvzHoraXEeCamR3+fzcBlh7Afs4Ifjs4epINyUX/jvdk0uzLnwiDY35UKDKnkCHP5Nu3dljl2H8lR6s+rQw==", + "dev": true + }, + "@types/which": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.1.tgz", + "integrity": "sha512-Jjakcv8Roqtio6w1gr0D7y6twbhx6gGgFGF5BLwajPpnOIOxFkakFhCq+LmyyeAz7BX6ULrjBOxdKaCDy+4+dQ==", + "dev": true + }, + "@types/winreg": { + "version": "1.2.31", + "resolved": "https://registry.npmjs.org/@types/winreg/-/winreg-1.2.31.tgz", + "integrity": "sha512-SDatEMEtQ1cJK3esIdH6colduWBP+42Xw9Guq1sf/N6rM3ZxgljBduvZOwBsxRps/k5+Wwf5HJun6pH8OnD2gg==", + "dev": true + }, + "@types/xml2js": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.9.tgz", + "integrity": "sha512-CHiCKIihl1pychwR2RNX5mAYmJDACgFVCMT5OArMaO3erzwXVcBqPcusr+Vl8yeeXukxZqtF8mZioqX+mpjjdw==", "dev": true, "requires": { - "object-visit": "^1.0.0" + "@types/node": "*" } }, - "markdown-escapes": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", - "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==", - "dev": true + "@typescript-eslint/eslint-plugin": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "dev": true, + "requires": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "dependencies": { + "debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } + } }, - "marked": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", - "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", - "dev": true + "@typescript-eslint/parser": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "dependencies": { + "debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } + } }, - "martinez-polygon-clipping": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/martinez-polygon-clipping/-/martinez-polygon-clipping-0.1.5.tgz", - "integrity": "sha1-gc4+soZ82RiKILkKzybyP7To7kI=", + "@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + } + }, + "@typescript-eslint/type-utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", "dev": true, "requires": { - "bintrees": "^1.0.1", - "tinyqueue": "^1.1.0" + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "dependencies": { + "debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } } }, - "matchdep": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", - "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", + "@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, "requires": { - "findup-sync": "^2.0.0", - "micromatch": "^3.0.4", - "resolve": "^1.4.0", - "stack-trace": "0.0.10" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "dependencies": { - "findup-sync": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", - "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "requires": { - "detect-file": "^1.0.0", - "is-glob": "^3.1.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" + "balanced-match": "^1.0.0" } }, - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "dev": true, "requires": { - "is-extglob": "^2.1.0" + "ms": "2.1.2" + } + }, + "minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" } } } }, - "material-colors": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", - "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==", - "dev": true - }, - "math-expression-evaluator": { - "version": "1.2.22", - "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.22.tgz", - "integrity": "sha512-L0j0tFVZBQQLeEjmWOvDLoRciIY8gQGWahvkztXUal8jH8R5Rlqo9GCvgqvXcy9LQhEWdQCVvzqAbxgYNt4blQ==", - "dev": true - }, - "md5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", - "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", - "requires": { - "charenc": "~0.0.1", - "crypt": "~0.0.1", - "is-buffer": "~1.1.1" - } - }, - "md5.js": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", - "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", + "@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "dev": true, "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "dependencies": { + "@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true + } } }, - "mdast-add-list-metadata": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdast-add-list-metadata/-/mdast-add-list-metadata-1.0.1.tgz", - "integrity": "sha512-fB/VP4MJ0LaRsog7hGPxgOrSL3gE/2uEdZyDuSEnKCv/8IkYHiDkIQSbChiJoHyxZZXZ9bzckyRk+vNxFzh8rA==", + "@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "requires": { - "unist-util-visit-parents": "1.1.2" + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" } }, - "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", - "dev": true - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "dev": true }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "dev": true, + "@vscode/extension-telemetry": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.8.4.tgz", + "integrity": "sha512-UqM9+KZDDK3MyoHTsg6XNM+XO6pweQxzCpqJz33BoBEYAGsbBviRYcVpJglgay2oReuDD2pOI1Nio3BKNDLhWA==", "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - }, - "dependencies": { - "p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true - } + "@microsoft/1ds-core-js": "^3.2.13", + "@microsoft/1ds-post-js": "^3.2.13", + "@microsoft/applicationinsights-web-basic": "^3.0.2", + "applicationinsights": "^2.7.1" } }, - "memoize-one": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz", - "integrity": "sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==", - "dev": true - }, - "memoizee": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", - "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", + "@vscode/test-electron": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.3.8.tgz", + "integrity": "sha512-b4aZZsBKtMGdDljAsOPObnAi7+VWIaYl3ylCz1jTs+oV6BZ4TNHcVNC3xUn0azPeszBmwSBDQYfFESIaUQnrOg==", "dev": true, "requires": { - "d": "1", - "es5-ext": "^0.10.45", - "es6-weak-map": "^2.0.2", - "event-emitter": "^0.3.5", - "is-promise": "^2.1", - "lru-queue": "0.1", - "next-tick": "1", - "timers-ext": "^0.1.5" + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "jszip": "^3.10.1", + "semver": "^7.5.2" } }, - "memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "@vscode/vsce": { + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.27.0.tgz", + "integrity": "sha512-FFUMBVSyyjjJpWszwqk7d4U3YllY8FdWslbUDMRki1x4ZjA3Z0hmRMfypWrjP9sptbSR9nyPFU4uqjhy2qRB/w==", "dev": true, "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" + "@azure/identity": "^4.1.0", + "@vscode/vsce-sign": "^2.0.0", + "azure-devops-node-api": "^12.5.0", + "chalk": "^2.4.2", + "cheerio": "^1.0.0-rc.9", + "cockatiel": "^3.1.2", + "commander": "^6.2.1", + "form-data": "^4.0.0", + "glob": "^7.0.6", + "hosted-git-info": "^4.0.2", + "jsonc-parser": "^3.2.0", + "keytar": "^7.7.0", + "leven": "^3.1.0", + "markdown-it": "^12.3.2", + "mime": "^1.3.4", + "minimatch": "^3.0.3", + "parse-semver": "^1.1.1", + "read": "^1.0.7", + "semver": "^7.5.2", + "tmp": "^0.2.1", + "typed-rest-client": "^1.8.4", + "url-join": "^4.0.1", + "xml2js": "^0.5.0", + "yauzl": "^2.3.1", + "yazl": "^2.2.2" }, "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", "dev": true }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "lru-cache": "^6.0.0" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "brace-expansion": "^1.1.7" } } } }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true + "@vscode/vsce-sign": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign/-/vsce-sign-2.0.4.tgz", + "integrity": "sha512-0uL32egStKYfy60IqnynAChMTbL0oqpqk0Ew0YHiIb+fayuGZWADuIPHWUcY1GCnAA+VgchOPDMxnc2R3XGWEA==", + "dev": true, + "requires": { + "@vscode/vsce-sign-alpine-arm64": "2.0.2", + "@vscode/vsce-sign-alpine-x64": "2.0.2", + "@vscode/vsce-sign-darwin-arm64": "2.0.2", + "@vscode/vsce-sign-darwin-x64": "2.0.2", + "@vscode/vsce-sign-linux-arm": "2.0.2", + "@vscode/vsce-sign-linux-arm64": "2.0.2", + "@vscode/vsce-sign-linux-x64": "2.0.2", + "@vscode/vsce-sign-win32-arm64": "2.0.2", + "@vscode/vsce-sign-win32-x64": "2.0.2" + } }, - "merge-source-map": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.0.4.tgz", - "integrity": "sha1-pd5GU42uhNQRTMXqArR3KmNGcB8=", + "@vscode/vsce-sign-alpine-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-arm64/-/vsce-sign-alpine-arm64-2.0.2.tgz", + "integrity": "sha512-E80YvqhtZCLUv3YAf9+tIbbqoinWLCO/B3j03yQPbjT3ZIHCliKZlsy1peNc4XNZ5uIb87Jn0HWx/ZbPXviuAQ==", + "dev": true, + "optional": true + }, + "@vscode/vsce-sign-alpine-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-x64/-/vsce-sign-alpine-x64-2.0.2.tgz", + "integrity": "sha512-n1WC15MSMvTaeJ5KjWCzo0nzjydwxLyoHiMJHu1Ov0VWTZiddasmOQHekA47tFRycnt4FsQrlkSCTdgHppn6bw==", + "dev": true, + "optional": true + }, + "@vscode/vsce-sign-darwin-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-arm64/-/vsce-sign-darwin-arm64-2.0.2.tgz", + "integrity": "sha512-rz8F4pMcxPj8fjKAJIfkUT8ycG9CjIp888VY/6pq6cuI2qEzQ0+b5p3xb74CJnBbSC0p2eRVoe+WgNCAxCLtzQ==", + "dev": true, + "optional": true + }, + "@vscode/vsce-sign-darwin-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-x64/-/vsce-sign-darwin-x64-2.0.2.tgz", + "integrity": "sha512-MCjPrQ5MY/QVoZ6n0D92jcRb7eYvxAujG/AH2yM6lI0BspvJQxp0o9s5oiAM9r32r9tkLpiy5s2icsbwefAQIw==", + "dev": true, + "optional": true + }, + "@vscode/vsce-sign-linux-arm": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm/-/vsce-sign-linux-arm-2.0.2.tgz", + "integrity": "sha512-Fkb5jpbfhZKVw3xwR6t7WYfwKZktVGNXdg1m08uEx1anO0oUPUkoQRsNm4QniL3hmfw0ijg00YA6TrxCRkPVOQ==", + "dev": true, + "optional": true + }, + "@vscode/vsce-sign-linux-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm64/-/vsce-sign-linux-arm64-2.0.2.tgz", + "integrity": "sha512-Ybeu7cA6+/koxszsORXX0OJk9N0GgfHq70Wqi4vv2iJCZvBrOWwcIrxKjvFtwyDgdeQzgPheH5nhLVl5eQy7WA==", + "dev": true, + "optional": true + }, + "@vscode/vsce-sign-linux-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-x64/-/vsce-sign-linux-x64-2.0.2.tgz", + "integrity": "sha512-NsPPFVtLaTlVJKOiTnO8Cl78LZNWy0Q8iAg+LlBiCDEgC12Gt4WXOSs2pmcIjDYzj2kY4NwdeN1mBTaujYZaPg==", + "dev": true, + "optional": true + }, + "@vscode/vsce-sign-win32-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-arm64/-/vsce-sign-win32-arm64-2.0.2.tgz", + "integrity": "sha512-wPs848ymZ3Ny+Y1Qlyi7mcT6VSigG89FWQnp2qRYCyMhdJxOpA4lDwxzlpL8fG6xC8GjQjGDkwbkWUcCobvksQ==", + "dev": true, + "optional": true + }, + "@vscode/vsce-sign-win32-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-x64/-/vsce-sign-win32-x64-2.0.2.tgz", + "integrity": "sha512-pAiRN6qSAhDM5SVOIxgx+2xnoVUePHbRNC7OD2aOR3WltTKxxF25OfpK8h8UQ7A0BuRkSgREbB59DBlFk4iAeg==", + "dev": true, + "optional": true + }, + "@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, "requires": { - "source-map": "^0.5.6" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - } + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", "dev": true }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", "dev": true }, - "microevent.ts": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.1.1.tgz", - "integrity": "sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g==", + "@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", "dev": true }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" } }, - "miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dev": true, "requires": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true - }, - "mime-db": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + "@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } }, - "mime-types": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, "requires": { - "mime-db": "1.40.0" + "@xtuc/long": "4.2.2" } }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", "dev": true }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" - }, - "min-document": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", - "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dev": true, "requires": { - "dom-walk": "^0.1.0" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true + "@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } }, - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dev": true, "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - }, - "dependencies": { - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - } + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" } }, - "minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "@webpack-cli/configtest": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.1.tgz", + "integrity": "sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg==", + "dev": true, + "requires": {} + }, + "@webpack-cli/info": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.1.tgz", + "integrity": "sha512-PKVGmazEq3oAo46Q63tpMr4HipI3OPfP7LiNOEJg963RMgT0rqheag28NCML0o3GIzA3DmxP1ZIAv9oTX1CUIA==", "dev": true, "requires": { - "minipass": "^3.0.0" - }, - "dependencies": { - "minipass": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", - "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } + "envinfo": "^7.7.3" } }, - "minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "@webpack-cli/serve": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.1.tgz", + "integrity": "sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw==", + "dev": true, + "requires": {} + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==" + }, + "acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "requires": {} + }, + "acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "dev": true, - "requires": { - "minipass": "^3.0.0" - }, - "dependencies": { - "minipass": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", - "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } + "requires": {} }, - "minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "requires": {} + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "requires": { - "minipass": "^3.0.0" + "debug": "4" }, "dependencies": { - "minipass": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", - "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", - "dev": true, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { - "yallist": "^4.0.0" + "ms": "2.1.2" } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true } } }, - "minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "aggregate-error": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", + "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", "dev": true, "requires": { - "minipass": "^2.9.0" + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" } }, - "mississippi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "requires": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - }, - "dependencies": { - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" + "ajv": "^8.0.0" }, "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "requires": { - "is-plain-object": "^2.0.4" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true } } }, - "mixin-object": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", - "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", "dev": true, "requires": { - "for-in": "^0.1.3", - "is-extendable": "^0.1.1" - }, - "dependencies": { - "for-in": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", - "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", - "dev": true - } + "ansi-wrap": "^0.1.0" } }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "requires": { - "minimist": "^1.2.5" + "color-convert": "^1.9.0" } }, - "mocha": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.1.1.tgz", - "integrity": "sha512-p7FuGlYH8t7gaiodlFreseLxEmxTgvyG9RgPHODFPySNhwUehu8NIb0vdSt3WFckSneswZ0Un5typYcWElk7HQ==", - "dev": true, - "requires": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.3.1", - "debug": "3.2.6", - "diff": "4.0.2", - "escape-string-regexp": "1.0.5", - "find-up": "4.1.0", - "glob": "7.1.6", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.13.1", - "log-symbols": "3.0.0", - "minimatch": "3.0.4", - "ms": "2.1.2", - "object.assign": "4.1.0", - "promise.allsettled": "1.0.2", - "serialize-javascript": "4.0.0", - "strip-json-comments": "3.0.1", - "supports-color": "7.1.0", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.0.0", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "1.6.1" - }, - "dependencies": { - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", - "dev": true - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "chokidar": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz", - "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==", - "dev": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.3.0" - } - }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, - "optional": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "readdirp": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz", - "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==", - "dev": true, - "requires": { - "picomatch": "^2.0.7" - } - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-json-comments": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", - "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "dev": true + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", + "dev": true, + "requires": { + "buffer-equal": "^1.0.0" + }, + "dependencies": { + "buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", "dev": true - }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - } - }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - } - } - }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } } } }, - "mocha-junit-reporter": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-1.23.0.tgz", - "integrity": "sha512-pmpnEO4iDTmLfrT2RKqPsc5relG4crnDSGmXPuGogdda27A7kLujDNJV4EbTbXlVBCZXggN9rQYPEWMkOv4AAA==", + "append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", "dev": true, "requires": { - "debug": "^2.2.0", - "md5": "^2.1.0", - "mkdirp": "~0.5.1", - "strip-ansi": "^4.0.0", - "xml": "^1.0.0" + "default-require-extensions": "^3.0.0" + } + }, + "applicationinsights": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-2.7.3.tgz", + "integrity": "sha512-JY8+kTEkjbA+kAVNWDtpfW2lqsrDALfDXuxOs74KLPu2y13fy/9WB52V4LfYVTVcW1/jYOXjTxNS2gPZIDh1iw==", + "requires": { + "@azure/core-auth": "^1.5.0", + "@azure/core-rest-pipeline": "1.10.1", + "@azure/core-util": "1.2.0", + "@azure/opentelemetry-instrumentation-azure-sdk": "^1.0.0-beta.5", + "@microsoft/applicationinsights-web-snippet": "^1.0.1", + "@opentelemetry/api": "^1.4.1", + "@opentelemetry/core": "^1.15.2", + "@opentelemetry/sdk-trace-base": "^1.15.2", + "@opentelemetry/semantic-conventions": "^1.15.2", + "cls-hooked": "^4.2.2", + "continuation-local-storage": "^3.2.1", + "diagnostic-channel": "1.1.1", + "diagnostic-channel-publishers": "1.0.7" + } + }, + "arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==" + }, + "archive-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/archive-type/-/archive-type-4.0.0.tgz", + "integrity": "sha512-zV4Ky0v1F8dBrdYElwTvQhweQ0P7Kwc1aluqJsYtOBP01jXcWCyW2IEfI1YiqsG+Iy7ZR+o5LF1N+PGECBxHWA==", + "dev": true, + "requires": { + "file-type": "^4.2.0" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "file-type": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-4.4.0.tgz", + "integrity": "sha512-f2UbFQEk7LXgWpi5ntcO86OeA/cC80fuDDDaX/fZ2ZGel+AF7leRQqBBW1eJNiiQkrZlAoM6P+VYP5P6bOlDEQ==", + "dev": true + } + } + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "arg": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz", + "integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "requires": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + } + }, + "array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", + "dev": true + }, + "array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + } + }, + "array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + } + }, + "array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + } + }, + "arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + } + }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "dev": true, + "requires": { + "object-assign": "^4.1.1", + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", "dev": true }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "inherits": "2.0.1" } } } }, - "mocha-multi-reporters": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/mocha-multi-reporters/-/mocha-multi-reporters-1.1.7.tgz", - "integrity": "sha1-zH8/TTL0eFIJQdhSq7ZNmYhYfYI=", + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", + "dev": true + }, + "async": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==", + "dev": true + }, + "async-done": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-2.0.0.tgz", + "integrity": "sha512-j0s3bzYq9yKIVLKGE/tWlCpa3PfFLcrDZLTSVdnnCTGagXuXBJO4SsY9Xdk/fQBirCkH4evW5xOeJXqlAQFdsw==", "dev": true, "requires": { - "debug": "^3.1.0", - "lodash": "^4.16.4" + "end-of-stream": "^1.4.4", + "once": "^1.4.0", + "stream-exhaust": "^1.0.2" + } + }, + "async-hook-jl": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/async-hook-jl/-/async-hook-jl-1.7.6.tgz", + "integrity": "sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg==", + "requires": { + "stack-chain": "^1.3.7" + } + }, + "async-listener": { + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.6.10.tgz", + "integrity": "sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw==", + "requires": { + "semver": "^5.3.0", + "shimmer": "^1.1.0" }, "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" } } }, - "moment": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" - }, - "monaco-editor": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.18.1.tgz", - "integrity": "sha512-fmL+RFZ2Hrezy+X/5ZczQW51LUmvzfcqOurnkCIRFTyjdVjzR7JvENzI6+VKBJzJdPh6EYL4RoWl92b2Hrk9fw==", - "dev": true - }, - "monaco-editor-textmate": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/monaco-editor-textmate/-/monaco-editor-textmate-2.2.1.tgz", - "integrity": "sha512-RYTNNfvyjK15M0JA8WIi9UduU10eX5724UGNKnaA8MSetehjThGENctUTuKaxPk/k3pq59QzaQ/C06A44iJd3Q==", - "dev": true + "async-settle": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-2.0.0.tgz", + "integrity": "sha512-Obu/KE8FurfQRN6ODdHN9LuXqwC+JFIM9NRyZqJJ4ZfLJmIYN9Rg0/kb+wF70VV5+fJusTMQlJ1t5rF7J/ETdg==", + "dev": true, + "requires": { + "async-done": "^2.0.0" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, - "monaco-editor-webpack-plugin": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-1.7.0.tgz", - "integrity": "sha512-oItymcnlL14Sjd7EF7q+CMhucfwR/2BxsqrXIBrWL6LQplFfAfV+grLEQRmVHeGSBZ/Gk9ptzfueXnWcoEcFuA==", + "available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, "requires": { - "@types/webpack": "^4.4.19" + "possible-typed-array-names": "^1.0.0" } }, - "monaco-textmate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/monaco-textmate/-/monaco-textmate-3.0.1.tgz", - "integrity": "sha512-ZxxY3OsqUczYP1sGqo97tu+CJmMBwuSW+dL0WEBdDhOZ5G1zntw72hvBc68ZQAirosWvbDKgN1dL5k173QtFww==", + "axe-core": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.1.tgz", + "integrity": "sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw==", + "dev": true + }, + "axobject-query": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", + "dev": true + }, + "azure-devops-node-api": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-12.5.0.tgz", + "integrity": "sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og==", "dev": true, "requires": { - "fast-plist": "^0.1.2" + "tunnel": "0.0.6", + "typed-rest-client": "^1.8.4" } }, - "moo": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/moo/-/moo-0.4.3.tgz", - "integrity": "sha512-gFD2xGCl8YFgGHsqJ9NKRVdwlioeW3mI1iqfLNYQOv0+6JRwG58Zk9DIGQgyIaffSYaO1xsKnMaYzzNr1KyIAw==", + "b4a": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", "dev": true }, - "mount-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mount-point/-/mount-point-3.0.0.tgz", - "integrity": "sha1-Zly57evoDREOZY21bDHQrvUaj5c=", + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "@sindresorhus/df": "^1.0.1", - "pify": "^2.3.0", - "pinkie-promise": "^2.0.1" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" }, "dependencies": { - "@sindresorhus/df": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@sindresorhus/df/-/df-1.0.1.tgz", - "integrity": "sha1-xptm9S9vzdKHyAffIQMF2694UA0=", + "core-js": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", + "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==", "dev": true }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", "dev": true } } }, - "move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", - "dev": true, - "requires": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, - "move-file": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/move-file/-/move-file-1.2.0.tgz", - "integrity": "sha512-USHrRmxzGowUWAGBbJPdFjHzEqtxDU03pLHY0Rfqgtnq+q8FOIs8wvkkf+Udmg77SJKs47y9sI0jJvQeYsmiCA==", + "bach": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bach/-/bach-2.0.1.tgz", + "integrity": "sha512-A7bvGMGiTOxGMpNupYl9HQTf0FFDNF4VCmks4PJpFyN1AX2pdKuxuwdvUz2Hu388wcgp+OvGFNsumBfFNkR7eg==", "dev": true, "requires": { - "cp-file": "^6.1.0", - "make-dir": "^3.0.0", - "path-exists": "^3.0.0" + "async-done": "^2.0.0", + "async-settle": "^2.0.0", + "now-and-later": "^3.0.0" }, "dependencies": { - "make-dir": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", - "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", + "now-and-later": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-3.0.0.tgz", + "integrity": "sha512-pGO4pzSdaxhWTGkfSfHx3hVzJVslFPwBp2Myq9MYN/ChfJZF87ochMAXnvz6/58RJSf5ik2q9tXprBBrk2cpcg==", "dev": true, "requires": { - "semver": "^6.0.0" + "once": "^1.4.0" } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true } } }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, - "multimatch": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", - "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=", + "bare-events": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.3.1.tgz", + "integrity": "sha512-sJnSOTVESURZ61XgEleqmP255T6zTYwHPwE4r6SssIh0U9/uDvfpdoJYpVUerJJZH2fueO+CdT8ZT+OC/7aZDA==", "dev": true, - "requires": { - "array-differ": "^1.0.0", - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "minimatch": "^3.0.0" - } + "optional": true }, - "mute-stdout": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", - "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", "dev": true }, - "mv": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", - "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", + "bent": { + "version": "7.3.12", + "resolved": "https://registry.npmjs.org/bent/-/bent-7.3.12.tgz", + "integrity": "sha512-T3yrKnVGB63zRuoco/7Ybl7BwwGZR0lceoVG5XmQyMIH9s19SV5m+a8qam4if0zQuAmOQTyPTPmsQBdAorGK3w==", "dev": true, "requires": { - "mkdirp": "~0.5.1", - "ncp": "~2.0.0", - "rimraf": "~2.4.0" + "bytesish": "^0.4.1", + "caseless": "~0.12.0", + "is-stream": "^2.0.0" }, "dependencies": { - "glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", - "dev": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "rimraf": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", - "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", - "dev": true, - "requires": { - "glob": "^6.0.1" - } + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true } } }, - "named-js-regexp": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/named-js-regexp/-/named-js-regexp-1.3.5.tgz", - "integrity": "sha512-XO0DPujDP9IWpkt690iWLreKztb/VB811DGl5N3z7BfhkMJuiVZXOi6YN/fEB9qkvtMVTgSZDW8pzdVt8vj/FA==" - }, - "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", - "dev": true - }, - "nanoid": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.0.3.tgz", - "integrity": "sha512-NbaoqdhIYmY6FXDRB4eYtDVC9Z9eCbn8TyaiC16LNKtpPv/aqa0tOPD8y6gNE4yUNnaZ7LLhYtXOev/6+cBtfw==", - "dev": true - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "dev": true }, - "ncp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", - "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, - "nearley": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.16.0.tgz", - "integrity": "sha512-Tr9XD3Vt/EujXbZBv6UAHYoLUSMQAxSsTnm9K3koXzjzNWY195NqALeyrzLZBKzAkL3gl92BcSogqrHjD8QuUg==", - "dev": true, - "requires": { - "commander": "^2.19.0", - "moo": "^0.4.3", - "railroad-diagrams": "^1.0.0", - "randexp": "0.4.6", - "semver": "^5.4.1" - } - }, - "needle": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", - "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", + "bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", "dev": true, "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - } + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" } }, - "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "dev": true - }, - "neo-async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", - "dev": true - }, - "nested-error-stacks": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz", - "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==", + "bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", "dev": true }, - "next-tick": { + "boolbase": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, - "nise": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/nise/-/nise-3.0.0.tgz", - "integrity": "sha512-EObFx5tioBMePHpU/gGczaY2YDqL255iwjmZwswu2CiwEW8xIGrr3E2xij+efIppS1nLQo9NyXSIUySGHUOhHQ==", - "dev": true, + "brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "requires": { - "@sinonjs/commons": "^1.7.0", - "@sinonjs/formatio": "^4.0.1", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "lolex": "^5.0.1", - "path-to-regexp": "^1.7.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "no-case": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", - "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "lower-case": "^1.1.1" + "fill-range": "^7.1.1" } }, - "nocache": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz", - "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==", + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", "dev": true }, - "nock": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/nock/-/nock-10.0.6.tgz", - "integrity": "sha512-b47OWj1qf/LqSQYnmokNWM8D88KvUl2y7jT0567NB3ZBAZFz2bWp2PC81Xn7u8F2/vJxzkzNZybnemeFa7AZ2w==", - "dev": true, - "requires": { - "chai": "^4.1.2", - "debug": "^4.1.0", - "deep-equal": "^1.0.0", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.5", - "mkdirp": "^0.5.0", - "propagate": "^1.0.0", - "qs": "^6.5.1", - "semver": "^5.5.0" - }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" - }, - "node-gyp-build": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.1.tgz", - "integrity": "sha512-XyCKXsqZfLqHep1hhsMncoXuUNt/cXCjg1+8CLbu69V1TKuPiOeSGbL9n+k/ByKH8UT0p4rdIX8XkTRZV0i7Sw==" + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true }, - "node-has-native-dependencies": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/node-has-native-dependencies/-/node-has-native-dependencies-1.0.2.tgz", - "integrity": "sha1-MVLsl1O2ZB5NMi0YXdSTBkmto9o=", + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { - "fs-walk": "0.0.1" + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, - "node-html-parser": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-1.1.16.tgz", - "integrity": "sha512-cfqTZIYDdp5cGh3NvCD5dcEDP7hfyni7WgyFacmDynLlIZaF3GVlRk8yMARhWp/PobWt1KaCV8VKdP5LKWiVbg==", + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", "dev": true, "requires": { - "he": "1.1.1" - }, - "dependencies": { - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - } + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" } }, - "node-libs-browser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", - "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", - "dev": true, - "requires": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "0.0.1", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.11.0", - "vm-browserify": "^1.0.1" + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.1.tgz", + "integrity": "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==", + "dev": true, + "requires": { + "bn.js": "^5.2.1", + "randombytes": "^2.1.0", + "safe-buffer": "^5.2.1" }, "dependencies": { - "buffer": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", - "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", - "dev": true, - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "bn.js": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz", + "integrity": "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==", "dev": true }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } } } }, - "node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", - "dev": true - }, - "node-pre-gyp": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", - "integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==", + "browserify-sign": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.5.tgz", + "integrity": "sha512-C2AUdAJg6rlM2W5QMp2Q4KGQMVBwR1lIimTsUnutJ8bMpW5B52pGpR2gEnNBNwijumDo5FojQ0L9JrXA8m4YEw==", "dev": true, "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" + "bn.js": "^5.2.2", + "browserify-rsa": "^4.1.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.6.1", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.9", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1" }, "dependencies": { - "nopt": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", - "dev": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } + "bn.js": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz", + "integrity": "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==", + "dev": true }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true } } }, - "node-preload": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", - "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", "dev": true, "requires": { - "process-on-spawn": "^1.0.0" + "pako": "~1.0.5" } }, - "node-releases": { - "version": "1.1.25", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.25.tgz", - "integrity": "sha512-fI5BXuk83lKEoZDdH3gRhtsNgh05/wZacuXkgbiYkceE7+QIMXOg98n9ZV7mz27B+kFHnqHcUpscZZlGRSmTpQ==", + "browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "requires": { - "semver": "^5.3.0" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" } }, - "node-stream-zip": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.8.2.tgz", - "integrity": "sha512-zwP2F/R28Oqtl0gOLItk5QjJ6jEU8XO4kaUMgeqvCyXPgdCZlm8T/5qLMiNy+moJCBCiMQAaX7aVMRhT0t2vkQ==" - }, - "nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, "requires": { - "abbrev": "1" + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", "dev": true, "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" } }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", "dev": true }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", "dev": true }, - "normalize-url": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", - "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", - "dev": true, - "requires": { - "prepend-http": "^2.0.0", - "query-string": "^5.0.1", - "sort-keys": "^2.0.0" - }, - "dependencies": { - "sort-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", - "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=", - "dev": true, - "requires": { - "is-plain-obj": "^1.0.0" - } - } - } + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true }, - "normalize.css": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.1.tgz", - "integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==", + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", "dev": true }, - "now-and-later": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", - "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", - "dev": true, - "requires": { - "once": "^1.3.2" - } + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true }, - "npm-bundled": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", - "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", "dev": true }, - "npm-conf": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", - "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==", + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "bytesish": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/bytesish/-/bytesish-0.4.4.tgz", + "integrity": "sha512-i4uu6M4zuMUiyfZN4RU2+i9+peJh//pXhd9x1oSe1LBkZ3LEbCoygu8W0bXTukU1Jme2txKuotpCZRaC3FLxcQ==", + "dev": true + }, + "cacheable-request": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", + "integrity": "sha512-vag0O2LKZ/najSoUwDbVlnlCFvhBE/7mGTY2B5FgCBDcRD+oVV1HYTOwM6JZfMg/hIcM6IwnTZ1uQQL5/X3xIQ==", "dev": true, "requires": { - "config-chain": "^1.1.11", - "pify": "^3.0.0" + "clone-response": "1.0.2", + "get-stream": "3.0.0", + "http-cache-semantics": "3.8.1", + "keyv": "3.0.0", + "lowercase-keys": "1.0.0", + "normalize-url": "2.0.1", + "responselike": "1.0.2" }, "dependencies": { - "pify": { + "get-stream": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "dev": true + }, + "lowercase-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", + "integrity": "sha512-RPlX0+PHuvxVDZ7xX+EBVAp4RsVxP/TdDSN2mJYdiq1Lc4Hz7EUSjUI7RZrKKlmrIzVhf6Jo2stj7++gVarS0A==", "dev": true } } }, - "npm-packlist": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.6.tgz", - "integrity": "sha512-u65uQdb+qwtGvEJh/DgQgW1Xg7sqeNbmxYyrvlNznaVTjV3E5P6F/EFjM+BVHXl7JJlsdG8A64M0XI8FI/IOlg==", + "caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", "dev": true, "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" } }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "dev": true, "requires": { - "path-key": "^2.0.0" + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" } }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "dev": true, + "call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" } }, - "nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dev": true, "requires": { - "boolbase": "~1.0.0" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" } }, - "num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, - "numeral": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz", - "integrity": "sha1-StCAk21EPCVhrtnyGX7//iX05QY=", + "caniuse-lite": { + "version": "1.0.30001768", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001768.tgz", + "integrity": "sha512-qY3aDRZC5nWPgHUgIB84WL+nySuo19wk0VJpp/XI9T34lrvkyhRvNVOFJOp2kxClQhiFBu+TaUSudf6oa3vkSA==", "dev": true }, - "nwsapi": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.4.tgz", - "integrity": "sha512-iGfd9Y6SFdTNldEy2L0GUhcarIutFmk+MPWIn9dmj8NMIup03G08uUF2KGbbmv/Ux4RT0VZJoP/sVbWA6d/VIw==", + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", "dev": true }, - "nyc": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.0.0.tgz", - "integrity": "sha512-qcLBlNCKMDVuKb7d1fpxjPR8sHeMVX0CHarXAVzrVWoFrigCkYR8xcrjfXSPi5HXM7EU78L6ywO7w1c5rZNCNg==", + "chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", "dev": true, "requires": { - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "caching-transform": "^4.0.0", - "convert-source-map": "^1.7.0", - "decamelize": "^1.2.0", - "find-cache-dir": "^3.2.0", - "find-up": "^4.1.0", - "foreground-child": "^2.0.0", - "glob": "^7.1.6", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-hook": "^3.0.0", - "istanbul-lib-instrument": "^4.0.0", - "istanbul-lib-processinfo": "^2.0.2", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.0", - "js-yaml": "^3.13.1", - "make-dir": "^3.0.0", - "node-preload": "^0.2.0", - "p-map": "^3.0.0", - "process-on-spawn": "^1.0.0", - "resolve-from": "^5.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "spawn-wrap": "^2.0.0", - "test-exclude": "^6.0.0", - "uuid": "^3.3.3", - "yargs": "^15.0.2" + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, + "chai-arrays": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chai-arrays/-/chai-arrays-2.2.0.tgz", + "integrity": "sha512-4awrdGI2EH8owJ9I58PXwG4N56/FiM8bsn4CVSNEgr4GKAM6Kq5JPVApUbhUBjDakbZNuRvV7quRSC38PWq/tg==", + "dev": true + }, + "chai-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "dev": true, + "requires": { + "check-error": "^1.0.2" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=", + "dev": true + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "cheerio": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz", + "integrity": "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==", + "dev": true, + "requires": { + "cheerio-select": "^1.5.0", + "dom-serializer": "^1.3.2", + "domhandler": "^4.2.0", + "htmlparser2": "^6.1.0", + "parse5": "^6.0.1", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "tslib": "^2.2.0" }, "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dom-serializer": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", + "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", "dev": true, "requires": { - "safe-buffer": "~5.1.1" + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" } }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "domelementtype": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", + "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", "dev": true }, - "find-cache-dir": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.2.0.tgz", - "integrity": "sha512-1JKclkYYsf1q9WIJKLZa9S9muC+08RIjzAlLrK4QcYLJMS6mk9yombQ9qf+zJ7H9LS800k0s44L4sDq9VYzqyg==", + "domhandler": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.0.tgz", + "integrity": "sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA==", "dev": true, "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.0", - "pkg-dir": "^4.1.0" + "domelementtype": "^2.2.0" } }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "domutils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz", + "integrity": "sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==", "dev": true, "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" } }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", "dev": true }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" } }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "dev": true }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "make-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", - "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + } + } + }, + "cheerio-select": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.5.0.tgz", + "integrity": "sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg==", + "dev": true, + "requires": { + "css-select": "^4.1.3", + "css-what": "^5.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0", + "domutils": "^2.7.0" + }, + "dependencies": { + "css-select": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", + "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==", "dev": true, "requires": { - "find-up": "^4.0.0" + "boolbase": "^1.0.0", + "css-what": "^5.0.0", + "domhandler": "^4.2.0", + "domutils": "^2.6.0", + "nth-check": "^2.0.0" } }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "css-what": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.0.1.tgz", + "integrity": "sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg==", "dev": true }, - "rimraf": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz", - "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==", + "dom-serializer": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", + "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", "dev": true, "requires": { - "glob": "^7.1.3" + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" } }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "domelementtype": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", + "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", "dev": true }, - "string-width": { + "domhandler": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.0.tgz", + "integrity": "sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA==", "dev": true, "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "domelementtype": "^2.2.0" } }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "domutils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz", + "integrity": "sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==", "dev": true, "requires": { - "ansi-regex": "^5.0.0" + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" } }, - "uuid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", - "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", - "dev": true - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", "dev": true - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "yargs": { - "version": "15.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", - "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.1" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + } + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "is-glob": "^4.0.1" } } } }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "optional": true }, - "object-component": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", - "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", - "dev": true + "chrome-trace-event": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "cipher-base": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.7.tgz", + "integrity": "sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==", "dev": true, "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.2" }, "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true } } }, - "object-inspect": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.4.1.tgz", - "integrity": "sha512-wqdhLpfCUbEsoEwl3FXwGyv8ief1k/1aUdIPCqVnupM6e8l63BEJdiF/0swtn04/8p05tG/T0FrpTlfwvljOdw==" + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true }, - "object-is": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz", - "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=", + "cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==" + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true + }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "dev": true, "requires": { - "isobject": "^3.0.0" + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" } }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" + "mimic-response": "^1.0.0" } }, - "object.defaults": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", "dev": true, "requires": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + } + }, + "cls-hooked": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/cls-hooked/-/cls-hooked-4.2.2.tgz", + "integrity": "sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw==", + "requires": { + "async-hook-jl": "^1.7.6", + "emitter-listener": "^1.0.1", + "semver": "^5.4.1" }, "dependencies": { - "for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" } } }, - "object.entries": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz", - "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.12.0", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } + "cockatiel": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.1.3.tgz", + "integrity": "sha512-xC759TpZ69d7HhfDp8m2WkRwEUiCkxY8Ee2OQH/3H6zmy2D/5Sm+zSTbPRa+V2QyjDtpMvjOIAOVjA2gp6N1kQ==", + "dev": true }, - "object.fromentries": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.0.tgz", - "integrity": "sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA==", + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.11.0", - "function-bind": "^1.1.1", - "has": "^1.0.1" + "color-name": "1.1.3" } }, - "object.getownpropertydescriptors": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", - "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", - "dev": true, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "colorette": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", + "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.1" + "delayed-stream": "~1.0.0" } }, - "object.map": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "commondir": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", - "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "compare-module-exports": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/compare-module-exports/-/compare-module-exports-2.1.0.tgz", + "integrity": "sha512-3Lc0sTIuX1jmY2K2RrXRJOND6KsRTX2D4v3+eu1PDptsuJZVK4LZc852eZa9I+avj0NrUKlTNgqvccNOH6mbGg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dev": true, "requires": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" + "safe-buffer": "5.2.1" }, "dependencies": { - "for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true } } }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "continuation-local-storage": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz", + "integrity": "sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==", + "requires": { + "async-listener": "^0.6.0", + "emitter-listener": "^1.1.1" + } + }, + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "copy-props": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-4.0.0.tgz", + "integrity": "sha512-bVWtw1wQLzzKiYROtvNlbJgxgBYt2bMJpkCbKmXM3xyijvcjjWXEk5nyrrT3bgJ7ODb19ZohE2T0Y3FgNPyoTw==", "dev": true, "requires": { - "isobject": "^3.0.1" + "each-props": "^3.0.0", + "is-plain-object": "^5.0.0" + }, + "dependencies": { + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true + } } }, - "object.reduce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", - "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", + "copy-webpack-plugin": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-9.1.0.tgz", + "integrity": "sha512-rxnR7PaGigJzhqETHGmAcxKnLZSR5u1Y3/bcIv/1FnqXedcL/E2ewK7ZCNrArJKCiSv8yVXhTqetJh8inDvfsA==", "dev": true, "requires": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" + "fast-glob": "^3.2.7", + "glob-parent": "^6.0.1", + "globby": "^11.0.3", + "normalize-path": "^3.0.0", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0" }, "dependencies": { - "for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "requires": { - "for-in": "^1.0.1" + "is-glob": "^4.0.3" } } } }, - "object.values": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", - "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", + "core-js-pure": { + "version": "3.42.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.42.0.tgz", + "integrity": "sha512-007bM04u91fF4kMgwom2I5cQxAFIy8jVulgr9eozILl/SZE53QOqnW/+vviC+wQWLv+AunBG+8Q0TLoeSsSxRQ==", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.12.0", - "function-bind": "^1.1.1", - "has": "^1.0.3" + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" } }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { - "ee-first": "1.1.1" + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" } }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, "requires": { - "wrappy": "1" + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" } }, - "one-time": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz", - "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=" - }, - "onecolor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/onecolor/-/onecolor-3.1.0.tgz", - "integrity": "sha512-YZSypViXzu3ul5LMu/m6XjJ9ol8qAy9S2VjHl5E6UlhUH1KGKWabyEJifn0Jjpw23bYDzC2ucKMPGiH5kfwSGQ==", + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", "dev": true, "requires": { - "mimic-fn": "^1.0.0" + "cross-spawn": "^7.0.1" }, "dependencies": { - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true } } }, - "onigasm": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/onigasm/-/onigasm-2.2.2.tgz", - "integrity": "sha512-TQTMk+RmPYx4sGzNAgV0q7At7PABDNHVqZBlC4aRXHg8hpCdemLOF0qq0gUCjwUbc7mhJMBOo3XpTRYwyr45Gw==", - "requires": { - "lru-cache": "^4.1.1" - } - }, - "opener": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", - "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==", - "dev": true - }, - "opn": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.2.0.tgz", - "integrity": "sha512-Jd/GpzPyHF4P2/aNOVmS3lfMSWV9J7cOhCG1s08XCEAsPkB7lp6ddiU0J7XzyQRDUh8BqJ7PchfINjR8jyofRQ==", - "dev": true, - "requires": { - "is-wsl": "^1.1.0" - } - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" - } - }, - "ordered-read-streams": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", - "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", + "cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", "dev": true, "requires": { - "readable-stream": "^2.0.1" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" }, "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "isexe": "^2.0.0" } } } }, - "original": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", - "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", + "crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=", + "dev": true + }, + "crypto-browserify": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.1.tgz", + "integrity": "sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==", "dev": true, "requires": { - "url-parse": "^1.4.3" + "browserify-cipher": "^1.0.1", + "browserify-sign": "^4.2.3", + "create-ecdh": "^4.0.4", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "diffie-hellman": "^5.0.3", + "hash-base": "~3.0.4", + "inherits": "^2.0.4", + "pbkdf2": "^3.1.2", + "public-encrypt": "^4.0.3", + "randombytes": "^2.1.0", + "randomfill": "^1.0.4" } }, - "os": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/os/-/os-0.1.1.tgz", - "integrity": "sha1-IIhF6J4ZOtTZcUdLk5R3NqVtE/M=", - "dev": true - }, - "os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "dev": true - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", "dev": true }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", "dev": true, "requires": { - "lcid": "^1.0.0" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" } }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + "data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + } }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", "dev": true, "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" } }, - "p-any": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-any/-/p-any-2.1.0.tgz", - "integrity": "sha512-JAERcaMBLYKMq+voYw36+x5Dgh47+/o7yuv2oQYuSSUml4YeqJEFznBrY2UeEkoSHqBua6hz518n/PsowTYLLg==", + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "requires": { - "p-cancelable": "^2.0.0", - "p-some": "^4.0.0", - "type-fest": "^0.3.0" + "ms": "2.0.0" }, "dependencies": { - "p-cancelable": { + "ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.0.0.tgz", - "integrity": "sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg==" - }, - "type-fest": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", - "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==" + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, - "p-cancelable": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", - "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", "dev": true }, - "p-event": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/p-event/-/p-event-2.3.1.tgz", - "integrity": "sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA==", + "decompress": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", + "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", "dev": true, "requires": { - "p-timeout": "^2.0.1" + "decompress-tar": "^4.0.0", + "decompress-tarbz2": "^4.0.0", + "decompress-targz": "^4.0.0", + "decompress-unzip": "^4.0.1", + "graceful-fs": "^4.1.10", + "make-dir": "^1.0.0", + "pify": "^2.3.0", + "strip-dirs": "^2.0.0" + }, + "dependencies": { + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true + } + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true + } } }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" - }, - "p-is-promise": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", - "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", - "dev": true - }, - "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", "dev": true, "requires": { - "p-try": "^2.0.0" + "mimic-response": "^1.0.0" } }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "decompress-tar": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", + "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", "dev": true, "requires": { - "p-limit": "^2.0.0" - } - }, - "p-map": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", - "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", - "dev": true - }, - "p-some": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-some/-/p-some-4.1.0.tgz", - "integrity": "sha512-MF/HIbq6GeBqTrTIl5OJubzkGU+qfFhAFi0gnTAK6rgEIJIknEiABHOTtQu4e6JiXjIwuMPMUFQzyHh5QjCl1g==", - "requires": { - "aggregate-error": "^3.0.0", - "p-cancelable": "^2.0.0" + "file-type": "^5.2.0", + "is-stream": "^1.1.0", + "tar-stream": "^1.5.2" }, "dependencies": { - "p-cancelable": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.0.0.tgz", - "integrity": "sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg==" + "file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "dev": true } } }, - "p-timeout": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", - "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", + "decompress-tarbz2": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", + "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", "dev": true, "requires": { - "p-finally": "^1.0.0" + "decompress-tar": "^4.1.0", + "file-type": "^6.1.0", + "is-stream": "^1.1.0", + "seek-bzip": "^1.0.5", + "unbzip2-stream": "^1.0.9" + }, + "dependencies": { + "file-type": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", + "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", + "dev": true + } } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "package-hash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", - "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "decompress-targz": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", + "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", "dev": true, "requires": { - "graceful-fs": "^4.1.15", - "hasha": "^5.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" + "decompress-tar": "^4.1.1", + "file-type": "^5.2.0", + "is-stream": "^1.1.0" + }, + "dependencies": { + "file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "dev": true + } } }, - "pako": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=" - }, - "parallel-transform": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", - "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", + "decompress-unzip": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", + "integrity": "sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==", "dev": true, "requires": { - "cyclist": "~0.2.2", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" + "file-type": "^3.8.0", + "get-stream": "^2.2.0", + "pify": "^2.3.0", + "yauzl": "^2.4.2" }, "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", "dev": true }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "get-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", + "integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true } } }, - "param-case": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", - "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "dev": true, "requires": { - "no-case": "^2.2.0" + "type-detect": "^4.0.0" } }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "optional": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "default-require-extensions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", "dev": true, "requires": { - "callsites": "^3.0.0" + "strip-bom": "^4.0.0" }, "dependencies": { - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true } } }, - "parse-asn1": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz", - "integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==", + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, - "requires": { - "asn1.js": "^4.0.0", - "browserify-aes": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" } }, - "parse-cache-control": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", - "integrity": "sha1-juqz5U+laSD+Fro493+iGqzC104=", + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", "dev": true }, - "parse-entities": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.2.2.tgz", - "integrity": "sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==", + "define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, "requires": { - "character-entities": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "character-reference-invalid": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-hexadecimal": "^1.0.0" + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" } }, - "parse-filepath": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", + "del": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", + "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", "dev": true, "requires": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" + "globby": "^11.0.1", + "graceful-fs": "^4.2.4", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.2", + "p-map": "^4.0.0", + "rimraf": "^3.0.2", + "slash": "^3.0.0" } }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "des.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", "dev": true, "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" } }, - "parse-node-version": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", - "dev": true - }, - "parse-passwd": { + "detect-file": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", "dev": true }, - "parse-semver": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz", - "integrity": "sha1-mkr9bfBj3Egm+T+6SpnPIj9mbLg=", + "detect-libc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", "dev": true, + "optional": true + }, + "diagnostic-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/diagnostic-channel/-/diagnostic-channel-1.1.1.tgz", + "integrity": "sha512-r2HV5qFkUICyoaKlBEpLKHjxMXATUf/l+h8UZPGBHGLy4DDiY2sOLcIctax4eRnTw5wH2jTMExLntGPJ8eOJxw==", "requires": { - "semver": "^5.1.0" + "semver": "^7.5.3" } }, - "parse5": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", - "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "diagnostic-channel-publishers": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/diagnostic-channel-publishers/-/diagnostic-channel-publishers-1.0.7.tgz", + "integrity": "sha512-SEECbY5AiVt6DfLkhkaHNeshg1CogdLLANA8xlG/TKvS+XUgvIKl7VspJGYiEdL5OUyzMVnr7o0AwB7f+/Mjtg==", + "requires": {} + }, + "diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "dev": true }, - "parseqs": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", - "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, "requires": { - "better-assert": "~1.0.0" + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" } }, - "parseuri": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", - "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, "requires": { - "better-assert": "~1.0.0" + "path-type": "^4.0.0" } }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, - "path-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", - "dev": true + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "dev": true }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true + "download": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/download/-/download-8.0.0.tgz", + "integrity": "sha512-ASRY5QhDk7FK+XrQtQyvhpDKanLluEEQtWl/J7Lxuf/b+i8RYh997QeXvL85xitrmRKVlx9c7eTrcRdq2GS4eA==", + "dev": true, + "requires": { + "archive-type": "^4.0.0", + "content-disposition": "^0.5.2", + "decompress": "^4.2.1", + "ext-name": "^5.0.0", + "file-type": "^11.1.0", + "filenamify": "^3.0.0", + "get-stream": "^4.1.0", + "got": "^8.3.1", + "make-dir": "^2.1.0", + "p-event": "^2.1.0", + "pify": "^4.0.1" + }, + "dependencies": { + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true + } + } }, - "path-is-absolute": { + "dunder-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" - }, - "path-posix": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/path-posix/-/path-posix-1.0.0.tgz", - "integrity": "sha1-BrJhE/Vr6rBCVFojv6iAA8ysJg8=" - }, - "path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", - "dev": true, + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "requires": { - "path-root-regex": "^0.1.0" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" } }, - "path-root-regex": { + "duplexer": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true }, - "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "duplexer3": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", + "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==", + "dev": true + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", "dev": true, "requires": { - "isarray": "0.0.1" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - } + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" } }, - "path-type": { + "each-props": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-3.0.0.tgz", + "integrity": "sha512-IYf1hpuWrdzse/s/YJOrFmU15lyhSzxelNVAHTEG3DtP4QsLTWZUzcUL3HMXmKQxXpa4EIrBPpwRgj0aehdvAw==", "dev": true, "requires": { - "pify": "^3.0.0" + "is-plain-object": "^5.0.0", + "object.defaults": "^1.1.0" }, "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", "dev": true } } }, - "pathval": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, - "pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", "dev": true, "requires": { - "through": "~2.3" + "safe-buffer": "^5.0.1" } }, - "pbkdf2": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", - "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "electron-to-chromium": { + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "dev": true + }, + "elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", "dev": true, "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" } }, - "pdfkit": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/pdfkit/-/pdfkit-0.11.0.tgz", - "integrity": "sha512-1s9gaumXkYxcVF1iRtSmLiISF2r4nHtsTgpwXiK8Swe+xwk/1pm8FJjYqN7L3x13NsWnGyUFntWcO8vfqq+wwA==", + "emitter-listener": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz", + "integrity": "sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==", "requires": { - "crypto-js": "^3.1.9-1", - "fontkit": "^1.8.0", - "linebreak": "^1.0.2", - "png-js": "^1.0.0" + "shimmer": "^1.2.0" } }, - "pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", "dev": true }, - "performance-now": { + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "enhanced-resolve": { + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + } + }, + "entities": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "dev": true }, - "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "envinfo": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", + "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", "dev": true }, - "pidusage": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-1.2.0.tgz", - "integrity": "sha512-OGo+iSOk44HRJ8q15AyG570UYxcm5u+R99DI8Khu8P3tKGkVu5EZX4ywHglWSTMNNXQ274oeGpYrvFEhDIFGPg==" + "es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + } + }, + "es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "dev": true }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "requires": { + "es-errors": "^1.3.0" + } + }, + "es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "requires": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + } + }, + "es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", "dev": true, "requires": { - "pinkie": "^2.0.0" + "hasown": "^2.0.0" } }, - "pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, "requires": { - "node-modules-regexp": "^1.0.0" + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" } }, - "pixrem": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pixrem/-/pixrem-4.0.1.tgz", - "integrity": "sha1-LaSh3m7EQjxfw3lOkwuB1EkOxoY=", - "dev": true, - "requires": { - "browserslist": "^2.0.0", - "postcss": "^6.0.0", - "reduce-css-calc": "^1.2.7" + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "es6-object-assign": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", + "integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=", + "dev": true + }, + "escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" }, "dependencies": { - "browserslist": { - "version": "2.11.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", - "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30000792", - "electron-to-chromium": "^1.3.30" + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" } }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } - } - } - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - } - }, - "playwright-chromium": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/playwright-chromium/-/playwright-chromium-0.13.0.tgz", - "integrity": "sha512-Ex9y563Vn8cnoBgMOcYLGdAVVC1xrBk/aWZ66AmOD77lk6v0x0cthLcbPX9h+tfkcWCFQQz1OWQR6V3+c3KUFA==", - "dev": true, - "requires": { - "playwright-core": "=0.13.0" - } - }, - "playwright-core": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-0.13.0.tgz", - "integrity": "sha512-bH9cOQhmbdXJnHX22PRZ79IdXv5wmLc9tHob2PmkT5qFilopT3INAIJcKkaLN1E/GqIDp0xGdB88Vr5f86g8pQ==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "extract-zip": "^2.0.0", - "https-proxy-agent": "^3.0.0", - "jpeg-js": "^0.3.6", - "mime": "^2.4.4", - "pngjs": "^3.4.0", - "progress": "^2.0.3", - "proxy-from-env": "^1.1.0", - "rimraf": "^3.0.2", - "ws": "^6.1.0" - }, - "dependencies": { + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, - "extract-zip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.0.tgz", - "integrity": "sha512-i42GQ498yibjdvIhivUsRslx608whtGoFIhF26Z7O4MYncBxp8CwalOs1lnHy21A9sIohWO2+uiE4SRtC9JXDg==", + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "requires": { - "@types/yauzl": "^2.9.1", - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" + "esutils": "^2.0.2" } }, - "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "requires": { - "pump": "^3.0.0" + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" } }, - "https-proxy-agent": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz", - "integrity": "sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==", + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" } }, - "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, - "pump": { + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "has-flag": "^4.0.0" } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true } } }, - "please-upgrade-node": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.1.1.tgz", - "integrity": "sha512-KY1uHnQ2NlQHqIJQpnh/i54rKkuxCEBx+voJIS/Mvb+L2iYd2NMotwduhKTMjfC1uKoX3VXOxLjIYG66dfJTVQ==", + "eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true, + "requires": {} + }, + "eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dev": true, "requires": { - "semver-compare": "^1.0.0" + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } } }, - "pleeease-filters": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pleeease-filters/-/pleeease-filters-4.0.0.tgz", - "integrity": "sha1-ZjKy+wVkjSdY2GU4T7zteeHMrsc=", + "eslint-module-utils": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "dev": true, + "requires": { + "debug": "^3.2.7" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-plugin-import": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "dev": true, + "requires": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", + "tsconfig-paths": "^3.15.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } + } + }, + "eslint-plugin-jsx-a11y": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.5.1.tgz", + "integrity": "sha512-sVCFKX9fllURnXT2JwLN5Qgo24Ug5NF6dxhkmxsMEUZhXRcGg+X3e1JbJ84YePQKBl5E0ZjAH5Q4rkdcGY99+g==", "dev": true, "requires": { - "onecolor": "^3.0.4", - "postcss": "^6.0.1" + "@babel/runtime": "^7.16.3", + "aria-query": "^4.2.2", + "array-includes": "^3.1.4", + "ast-types-flow": "^0.0.7", + "axe-core": "^4.3.5", + "axobject-query": "^2.2.0", + "damerau-levenshtein": "^1.0.7", + "emoji-regex": "^9.2.2", + "has": "^1.0.3", + "jsx-ast-utils": "^3.2.1", + "language-tags": "^1.0.5", + "minimatch": "^3.0.4" }, "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + } + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" + "brace-expansion": "^1.1.7" } } } }, - "plotly.js-dist": { - "version": "1.56.0", - "resolved": "https://registry.npmjs.org/plotly.js-dist/-/plotly.js-dist-1.56.0.tgz", - "integrity": "sha512-vkNePImh00Ka8zm8/2bkhL5XyIf9vjNKojSQqHd45UIgrvv1k9io31SFK+gUgz+KLYki8/QN4Qz6abifKVcu9Q==", + "eslint-plugin-no-only-tests": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-only-tests/-/eslint-plugin-no-only-tests-3.3.0.tgz", + "integrity": "sha512-brcKcxGnISN2CcVhXJ/kEQlNa0MEfGRtwKtWA16SkqXHKitaKIMrfemJKLKX1YqDU5C/5JY3PvZXd5jEW04e0Q==", "dev": true }, - "plugin-error": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", - "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", + "eslint-plugin-react": { + "version": "7.29.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.29.4.tgz", + "integrity": "sha512-CVCXajliVh509PcZYRFyu/BoUEz452+jtQJq2b3Bae4v3xBUWPLCmtmBM+ZinG4MzwmxJgJ2M5rMqhqLVn7MtQ==", "dev": true, "requires": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" + "array-includes": "^3.1.4", + "array.prototype.flatmap": "^1.2.5", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.5", + "object.fromentries": "^2.0.5", + "object.hasown": "^1.1.0", + "object.values": "^1.1.5", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.3", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.6" }, "dependencies": { - "arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "requires": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" + "brace-expansion": "^1.1.7" } }, - "arr-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", - "dev": true - }, - "array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", - "dev": true - }, - "extend-shallow": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", - "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", + "resolve": { + "version": "2.0.0-next.3", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz", + "integrity": "sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==", "dev": true, "requires": { - "kind-of": "^1.1.0" + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" } }, - "kind-of": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", - "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } }, - "pn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", - "dev": true + "eslint-plugin-react-hooks": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.4.0.tgz", + "integrity": "sha512-U3RVIfdzJaeKDQKEJbz5p3NW8/L80PCATJAfuojwbaEL+gBjfGdhUcGde+WGUW46Q5sr/NgxevsIiDtNXrvZaQ==", + "dev": true, + "requires": {} }, - "png-js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz", - "integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==" + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } }, - "pngjs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", - "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true }, - "polygon-offset": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/polygon-offset/-/polygon-offset-0.3.1.tgz", - "integrity": "sha1-aaZWXwsn+na1Jw1cB5sLosjwu6M=", + "espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "requires": { - "martinez-polygon-clipping": "^0.1.5" + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" } }, - "popper.js": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.0.tgz", - "integrity": "sha512-+G+EkOPoE5S/zChTpmBSSDYmhXJ5PsW8eMhH8cP/CQHMFPBG/kC9Y5IIw6qNYgdJ+/COf0ddY2li28iHaZRSjw==", - "dev": true - }, - "portfinder": { - "version": "1.0.25", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz", - "integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==", + "esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, "requires": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.1" + "estraverse": "^5.1.0" }, "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "requires": { - "ms": "^2.1.1" - } + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true } } }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "postcss": { - "version": "7.0.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", - "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" + "estraverse": "^5.2.0" }, "dependencies": { - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true } } }, - "postcss-apply": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/postcss-apply/-/postcss-apply-0.8.0.tgz", - "integrity": "sha1-FOVEu7XLbxweBIhXll15rgZrE0M=", + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "requires": { - "babel-runtime": "^6.23.0", - "balanced-match": "^0.4.2", - "postcss": "^6.0.0" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" }, "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", + "cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" + "shebang-regex": "^3.0.0" } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true } } }, - "postcss-attribute-case-insensitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-2.0.0.tgz", - "integrity": "sha1-lNxCLI+QmX8WvTOjZUu77AhJY7Q=", + "expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "optional": true + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", "dev": true, "requires": { - "postcss": "^6.0.0", - "postcss-selector-parser": "^2.2.3" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - } + "homedir-polyfill": "^1.0.1" } }, - "postcss-calc": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-6.0.2.tgz", - "integrity": "sha512-fiznXjEN5T42Qm7qqMCVJXS3roaj9r4xsSi+meaBVe7CJBl8t/QLOXu02Z2E6oWAMWIvCuF6JrvzFekmVEbOKA==", + "expose-loader": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-3.1.0.tgz", + "integrity": "sha512-2RExSo0yJiqP+xiUue13jQa2IHE8kLDzTI7b6kn+vUlBVvlzNSiLDzo4e5Pp5J039usvTUnxZ8sUOhv0Kg15NA==", + "dev": true, + "requires": {} + }, + "ext-list": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", + "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", "dev": true, "requires": { - "css-unit-converter": "^1.1.1", - "postcss": "^7.0.2", - "postcss-selector-parser": "^2.2.2", - "reduce-css-calc": "^2.0.0" - }, - "dependencies": { - "reduce-css-calc": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.7.tgz", - "integrity": "sha512-fDnlZ+AybAS3C7Q9xDq5y8A2z+lT63zLbynew/lur/IR24OQF5x98tfNwf79mzEdfywZ0a2wpM860FhFfMxZlA==", - "dev": true, - "requires": { - "css-unit-converter": "^1.1.1", - "postcss-value-parser": "^3.3.0" - } - } + "mime-db": "^1.28.0" } }, - "postcss-color-function": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-color-function/-/postcss-color-function-4.1.0.tgz", - "integrity": "sha512-2/fuv6mP5Lt03XbRpVfMdGC8lRP1sykme+H1bR4ARyOmSMB8LPSjcL6EAI1iX6dqUF+jNEvKIVVXhan1w/oFDQ==", + "ext-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", + "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", "dev": true, "requires": { - "css-color-function": "~1.3.3", - "postcss": "^6.0.23", - "postcss-message-helpers": "^2.0.0", - "postcss-value-parser": "^3.3.1" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - } + "ext-list": "^2.0.0", + "sort-keys-length": "^1.0.0" } }, - "postcss-color-gray": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-color-gray/-/postcss-color-gray-4.1.0.tgz", - "integrity": "sha512-L4iLKQLdqChz6ZOgGb6dRxkBNw78JFYcJmBz1orHpZoeLtuhDDGegRtX9gSyfoCIM7rWZ3VNOyiqqvk83BEN+w==", + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, "requires": { - "color": "^2.0.1", - "postcss": "^6.0.14", - "postcss-message-helpers": "^2.0.0", - "reduce-function-call": "^1.0.2" + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" }, "dependencies": { - "color": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color/-/color-2.0.1.tgz", - "integrity": "sha512-ubUCVVKfT7r2w2D3qtHakj8mbmKms+tThR8gI8zEYCbUBl8/voqFGt3kgBqGwXAopgXybnkuOq+qMYCRrp4cXw==", - "dev": true, - "requires": { - "color-convert": "^1.9.1", - "color-string": "^1.5.2" - } - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" + "is-plain-object": "^2.0.4" } } } }, - "postcss-color-hex-alpha": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-3.0.0.tgz", - "integrity": "sha1-HlPmyKyyN5Vej9CLfs2xuLgwn5U=", + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true + }, + "fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", "dev": true, "requires": { - "color": "^1.0.3", - "postcss": "^6.0.1", - "postcss-message-helpers": "^2.0.0" + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" }, "dependencies": { - "color": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/color/-/color-1.0.3.tgz", - "integrity": "sha1-5I6DLYXxTvaU+0aIEcLVz+cptV0=", - "dev": true, - "requires": { - "color-convert": "^1.8.2", - "color-string": "^1.4.0" - } - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" + "is-glob": "^4.0.1" } } } }, - "postcss-color-hsl": { + "fast-json-stable-stringify": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-hsl/-/postcss-color-hsl-2.0.0.tgz", - "integrity": "sha1-EnA2ZvoxBDDj8wpFTawThjF9WEQ=", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true + }, + "fastest-levenshtein": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", + "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", + "dev": true + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", "dev": true, "requires": { - "postcss": "^6.0.1", - "postcss-value-parser": "^3.3.0", - "units-css": "^0.4.0" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - } + "reusify": "^1.0.4" } }, - "postcss-color-hwb": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-hwb/-/postcss-color-hwb-3.0.0.tgz", - "integrity": "sha1-NAKxnvTYSXVAwftQcr6YY8qVVx4=", + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", "dev": true, "requires": { - "color": "^1.0.3", - "postcss": "^6.0.1", - "postcss-message-helpers": "^2.0.0", - "reduce-function-call": "^1.0.2" - }, - "dependencies": { - "color": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/color/-/color-1.0.3.tgz", - "integrity": "sha1-5I6DLYXxTvaU+0aIEcLVz+cptV0=", - "dev": true, - "requires": { - "color-convert": "^1.8.2", - "color-string": "^1.4.0" - } - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - } + "pend": "~1.2.0" } }, - "postcss-color-rebeccapurple": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-3.1.0.tgz", - "integrity": "sha512-212hJUk9uSsbwO5ECqVjmh/iLsmiVL1xy9ce9TVf+X3cK/ZlUIlaMdoxje/YpsL9cmUH3I7io+/G2LyWx5rg1g==", + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "requires": { - "postcss": "^6.0.22", - "postcss-values-parser": "^1.5.0" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - } + "flat-cache": "^3.0.4" } }, - "postcss-color-rgb": { + "file-type": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-11.1.0.tgz", + "integrity": "sha512-rM0UO7Qm9K7TWTtA6AShI/t7H5BPjDeGVDaNyg9BjHAj3PysKy7+8C8D137R88jnR3rFJZQB/tFgydl5sN5m7g==", + "dev": true + }, + "filename-reserved-regex": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-rgb/-/postcss-color-rgb-2.0.0.tgz", - "integrity": "sha1-FFOcinExSUtILg3RzCZf9lFLUmM=", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", + "dev": true + }, + "filenamify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-3.0.0.tgz", + "integrity": "sha512-5EFZ//MsvJgXjBAFJ+Bh2YaCTRF/VP1YOmGrgt+KJ4SFRLjI87EIdwLLuT6wQX0I4F9W41xutobzczjsOKlI/g==", "dev": true, "requires": { - "postcss": "^6.0.1", - "postcss-value-parser": "^3.3.0" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - } + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.0", + "trim-repeated": "^1.0.0" } }, - "postcss-color-rgba-fallback": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-rgba-fallback/-/postcss-color-rgba-fallback-3.0.0.tgz", - "integrity": "sha1-N9XJNToHoJJwkSqCYGu0Kg1wLAQ=", + "fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { - "postcss": "^6.0.6", - "postcss-value-parser": "^3.3.0", - "rgb-hex": "^2.1.0" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - } + "to-regex-range": "^5.0.1" } }, - "postcss-cssnext": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-cssnext/-/postcss-cssnext-3.1.0.tgz", - "integrity": "sha512-awPDhI4OKetcHCr560iVCoDuP6e/vn0r6EAqdWPpAavJMvkBSZ6kDpSN4b3mB3Ti57hQMunHHM8Wvx9PeuYXtA==", - "dev": true, - "requires": { - "autoprefixer": "^7.1.1", - "caniuse-api": "^2.0.0", - "chalk": "^2.0.1", - "pixrem": "^4.0.0", - "pleeease-filters": "^4.0.0", - "postcss": "^6.0.5", - "postcss-apply": "^0.8.0", - "postcss-attribute-case-insensitive": "^2.0.0", - "postcss-calc": "^6.0.0", - "postcss-color-function": "^4.0.0", - "postcss-color-gray": "^4.0.0", - "postcss-color-hex-alpha": "^3.0.0", - "postcss-color-hsl": "^2.0.0", - "postcss-color-hwb": "^3.0.0", - "postcss-color-rebeccapurple": "^3.0.0", - "postcss-color-rgb": "^2.0.0", - "postcss-color-rgba-fallback": "^3.0.0", - "postcss-custom-media": "^6.0.0", - "postcss-custom-properties": "^6.1.0", - "postcss-custom-selectors": "^4.0.1", - "postcss-font-family-system-ui": "^3.0.0", - "postcss-font-variant": "^3.0.0", - "postcss-image-set-polyfill": "^0.3.5", - "postcss-initial": "^2.0.0", - "postcss-media-minmax": "^3.0.0", - "postcss-nesting": "^4.0.1", - "postcss-pseudo-class-any-link": "^4.0.0", - "postcss-pseudoelements": "^5.0.0", - "postcss-replace-overflow-wrap": "^2.0.0", - "postcss-selector-matches": "^3.0.1", - "postcss-selector-not": "^3.0.1" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - } + "filter-obj": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-2.0.2.tgz", + "integrity": "sha512-lO3ttPjHZRfjMcxWKb1j1eDhTFsu4meeR3lnMcnBFhk6RuLhvEiuALu2TlfL310ph4lCYYwgF/ElIjdP739tdg==", + "dev": true + }, + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" } }, - "postcss-custom-media": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-6.0.0.tgz", - "integrity": "sha1-vlMnhBEOyylQRPtTlaGABushpzc=", + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "postcss": "^6.0.1" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - } + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, - "postcss-custom-properties": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-6.3.1.tgz", - "integrity": "sha512-zoiwn4sCiUFbr4KcgcNZLFkR6gVQom647L+z1p/KBVHZ1OYwT87apnS42atJtx6XlX2yI7N5fjXbFixShQO2QQ==", + "findup-sync": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz", + "integrity": "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==", "dev": true, "requires": { - "balanced-match": "^1.0.0", - "postcss": "^6.0.18" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - } + "detect-file": "^1.0.0", + "is-glob": "^4.0.3", + "micromatch": "^4.0.4", + "resolve-dir": "^1.0.1" } }, - "postcss-custom-selectors": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-4.0.1.tgz", - "integrity": "sha1-eBOC+UxS5yfvXKR3bqKt9JphE4I=", + "fined": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-2.0.0.tgz", + "integrity": "sha512-OFRzsL6ZMHz5s0JrsEr+TpdGNCtrVtnuG3x1yzGNiQHT0yaDnXAj8V/lWcpJVrnoDpcwXcASxAZYbuXda2Y82A==", "dev": true, "requires": { - "postcss": "^6.0.1", - "postcss-selector-matches": "^3.0.0" + "expand-tilde": "^2.0.2", + "is-plain-object": "^5.0.0", + "object.defaults": "^1.1.0", + "object.pick": "^1.3.0", + "parse-filepath": "^1.0.2" }, "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true } } }, - "postcss-font-family-system-ui": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-font-family-system-ui/-/postcss-font-family-system-ui-3.0.0.tgz", - "integrity": "sha512-58G/hTxMSSKlIRpcPUjlyo6hV2MEzvcVO2m4L/T7Bb2fJTG4DYYfQjQeRvuimKQh1V1sOzCIz99g+H2aFNtlQw==", + "flagged-respawn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-2.0.0.tgz", + "integrity": "sha512-Gq/a6YCi8zexmGHMuJwahTGzXlAZAOsbCVKduWXC6TlLCjjFRlExMJc4GC2NYPYZ0r/brw9P7CpRgQmlPVeOoA==", + "dev": true + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, "requires": { - "postcss": "^6.0" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - } + "flatted": "^3.1.0", + "rimraf": "^3.0.2" } }, - "postcss-font-variant": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-3.0.0.tgz", - "integrity": "sha1-CMzIj2BQuoLtjvLMdsDGprQfGD4=", + "flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", "dev": true, "requires": { - "postcss": "^6.0.1" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - } + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" } }, - "postcss-image-set-polyfill": { + "for-each": { "version": "0.3.5", - "resolved": "https://registry.npmjs.org/postcss-image-set-polyfill/-/postcss-image-set-polyfill-0.3.5.tgz", - "integrity": "sha1-Dxk0E3AM8fgr05Bm7wFtZaShgYE=", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, "requires": { - "postcss": "^6.0.1", - "postcss-media-query-parser": "^0.2.3" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - } + "is-callable": "^1.2.7" } }, - "postcss-import": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-12.0.1.tgz", - "integrity": "sha512-3Gti33dmCjyKBgimqGxL3vcV8w9+bsHwO5UrBawp796+jdardbcFl4RP5w/76BwNL7aGzpKstIfF9I+kdE8pTw==", + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "dev": true + }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", "dev": true, "requires": { - "postcss": "^7.0.1", - "postcss-value-parser": "^3.2.3", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" + "for-in": "^1.0.1" } }, - "postcss-initial": { + "foreground-child": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-2.0.0.tgz", - "integrity": "sha1-cnFfczbgu3k1HZnuZcSiU6hEG6Q=", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", "dev": true, "requires": { - "lodash.template": "^4.2.4", - "postcss": "^6.0.1" + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" }, "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" + "shebang-regex": "^3.0.0" } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true } } }, - "postcss-load-config": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", - "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", - "dev": true, - "requires": { - "cosmiconfig": "^5.0.0", - "import-cwd": "^2.0.0" - } - }, - "postcss-loader": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", - "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", - "dev": true, + "form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "requires": { - "loader-utils": "^1.1.0", - "postcss": "^7.0.0", - "postcss-load-config": "^2.0.0", - "schema-utils": "^1.0.0" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" } }, - "postcss-media-minmax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-3.0.0.tgz", - "integrity": "sha1-Z1JWA3pD70C8Twdgv9BtTcadSNI=", + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", "dev": true, "requires": { - "postcss": "^6.0.1" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - } + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" } }, - "postcss-media-query-parser": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", - "integrity": "sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ=", + "fromentries": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.2.0.tgz", + "integrity": "sha512-33X7H/wdfO99GdRLLgkjUrD4geAFdq/Uv0kl3HD4da6HDixd2GUg8Mw7dahLCV9r/EARkmtYBB6Tch4EEokFTQ==", "dev": true }, - "postcss-message-helpers": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz", - "integrity": "sha1-pPL0+rbk/gAvCu0ABHjN9S+bpg4=", + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "dev": true }, - "postcss-modules-extract-imports": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.1.tgz", - "integrity": "sha512-6jt9XZwUhwmRUhb/CkyJY020PYaPJsCyt3UjbaWo6XEbH/94Hmv6MP7fG2C5NDU/BcHzyGYxNtHvM+LTf9HrYw==", - "dev": true, + "fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", "requires": { - "postcss": "^6.0.1" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" } } }, - "postcss-modules-local-by-default": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", - "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", + "fs-mkdirp-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", + "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", "dev": true, "requires": { - "css-selector-tokenizer": "^0.7.0", - "postcss": "^6.0.1" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - } + "graceful-fs": "^4.1.11", + "through2": "^2.0.3" } }, - "postcss-modules-scope": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", - "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", + "fs-walk": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/fs-walk/-/fs-walk-0.0.1.tgz", + "integrity": "sha1-9/yRw64e6tB8mYvF0N1B8tvr0zU=", "dev": true, "requires": { - "css-selector-tokenizer": "^0.7.0", - "postcss": "^6.0.1" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - } + "async": "*" } }, - "postcss-modules-values": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz", - "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, - "requires": { - "icss-replace-symbols": "^1.1.0", - "postcss": "^6.0.1" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - } - } + "optional": true }, - "postcss-nesting": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-4.2.1.tgz", - "integrity": "sha512-IkyWXICwagCnlaviRexi7qOdwPw3+xVVjgFfGsxmztvRVaNxAlrypOIKqDE5mxY+BVxnId1rnUKBRQoNE2VDaA==", + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", "dev": true, "requires": { - "postcss": "^6.0.11" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - } + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" } }, - "postcss-pseudo-class-any-link": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-4.0.0.tgz", - "integrity": "sha1-kVKgYT00UHIFE+iJKFS65C0O5o4=", - "dev": true, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true + }, + "get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + } + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "dev": true + }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "requires": { - "postcss": "^6.0.1", - "postcss-selector-parser": "^2.2.3" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - } + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" } }, - "postcss-pseudoelements": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-pseudoelements/-/postcss-pseudoelements-5.0.0.tgz", - "integrity": "sha1-7vGU6NUkZFylIKlJ6V5RjoEkAss=", + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", "dev": true, "requires": { - "postcss": "^6.0.0" + "pump": "^3.0.0" }, "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", "dev": true, "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } } } }, - "postcss-replace-overflow-wrap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-2.0.0.tgz", - "integrity": "sha1-eU22+qVPjbEAhUOSqTr0V2i04ls=", + "get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dev": true, "requires": { - "postcss": "^6.0.1" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - } + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" } }, - "postcss-selector-matches": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-3.0.1.tgz", - "integrity": "sha1-5WNAEeE5UIgYYbvdWMLQER/8lqs=", + "github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", "dev": true, + "optional": true + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "requires": { - "balanced-match": "^0.4.2", - "postcss": "^6.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", - "dev": true - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, + "minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" + "brace-expansion": "^1.1.7" } } } }, - "postcss-selector-not": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-3.0.1.tgz", - "integrity": "sha1-Lk2y8JZTNsAefOx9tsYN/3ZzNdk=", + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", "dev": true, "requires": { - "balanced-match": "^0.4.2", - "postcss": "^6.0.1" + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" }, "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", - "dev": true - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" + "is-extglob": "^2.1.0" } } } }, - "postcss-selector-parser": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz", - "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=", + "glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha512-uMbLGAP3S2aDOHUDfdoYcdIePUCfysbAd0IAoWVZbeGU/oNQ8asHVSshLDJUPWxfzj8zsCG7/XeHPHTtow0nsw==", "dev": true, "requires": { - "flatten": "^1.0.2", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" + "extend": "^3.0.0", + "glob": "^7.1.1", + "glob-parent": "^3.1.0", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.0", + "pumpify": "^1.3.5", + "readable-stream": "^2.1.5", + "remove-trailing-separator": "^1.0.1", + "to-absolute-glob": "^2.0.0", + "unique-stream": "^2.0.2" } }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true }, - "postcss-values-parser": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-1.5.0.tgz", - "integrity": "sha512-3M3p+2gMp0AH3da530TlX8kiO1nxdTnc3C6vr8dMxRLIlh8UYkz0/wcwptSXjhtx2Fr0TySI7a+BHDQ8NL7LaQ==", + "glob-watcher": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-6.0.0.tgz", + "integrity": "sha512-wGM28Ehmcnk2NqRORXFOTOR064L4imSw3EeOqU5bIwUf62eXGwg89WivH6VMahL8zlQHeodzvHpXplrqzrz3Nw==", "dev": true, "requires": { - "flatten": "^1.0.2", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" + "async-done": "^2.0.0", + "chokidar": "^3.5.3" } }, - "postinstall-build": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/postinstall-build/-/postinstall-build-5.0.3.tgz", - "integrity": "sha512-vPvPe8TKgp4FLgY3+DfxCE5PIfoXBK2lyLfNCxsRbDsV6vS4oU5RG/IWxrblMn6heagbnMED3MemUQllQ2bQUg==", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" - }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" - }, - "prettier": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.2.tgz", - "integrity": "sha512-5xJQIPT8BraI7ZnaDwSbu5zLrB6vvi8hVV58yHQ+QK64qrY40dULy0HSRlQ2/2IdzeBpjhDkqdcFBnFeDEMVdg==", - "dev": true - }, - "prettier-linter-helpers": { + "global-modules": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", "dev": true, "requires": { - "fast-diff": "^1.1.2" + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" } }, - "pretty-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", - "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", "dev": true, "requires": { - "renderkid": "^2.0.1", - "utila": "~0.4" + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "dependencies": { + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, - "pretty-format": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", - "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "dev": true, "requires": { - "@jest/types": "^24.9.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" + "define-properties": "^1.2.1", + "gopd": "^1.0.1" } }, - "pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", - "dev": true - }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "dev": true - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true - }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } }, - "process-on-spawn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", - "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "glogg": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-2.2.0.tgz", + "integrity": "sha512-eWv1ds/zAlz+M1ioHsyKJomfY7jbDDPpwSkv14KQj89bycx1nvK5/2Cj/T9g7kzJcX5Bc7Yv22FjfBZS/jl94A==", "dev": true, "requires": { - "fromentries": "^1.2.0" + "sparkles": "^2.1.0" } }, - "progress": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", - "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", - "dev": true + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" }, - "promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "got": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", + "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==", "dev": true, "requires": { - "asap": "~2.0.3" + "@sindresorhus/is": "^0.7.0", + "cacheable-request": "^2.1.1", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "into-stream": "^3.1.0", + "is-retry-allowed": "^1.1.0", + "isurl": "^1.0.0-alpha5", + "lowercase-keys": "^1.0.0", + "mimic-response": "^1.0.0", + "p-cancelable": "^0.4.0", + "p-timeout": "^2.0.1", + "pify": "^3.0.0", + "safe-buffer": "^5.1.1", + "timed-out": "^4.0.1", + "url-parse-lax": "^3.0.0", + "url-to-options": "^1.0.1" + }, + "dependencies": { + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true + } } }, - "promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, - "promise.allsettled": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.2.tgz", - "integrity": "sha512-UpcYW5S1RaNKT6pd+s9jp9K9rlQge1UXKskec0j6Mmuq7UJCvlS2J2/s/yuPN8ehftf9HXMxWlKiPbGGUzpoRg==", + "gulp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-5.0.0.tgz", + "integrity": "sha512-S8Z8066SSileaYw1S2N1I64IUc/myI2bqe2ihOBzO6+nKpvNSg7ZcWJt/AwF8LC/NVN+/QZ560Cb/5OPsyhkhg==", "dev": true, "requires": { - "array.prototype.map": "^1.0.1", - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "iterate-value": "^1.0.0" + "glob-watcher": "^6.0.0", + "gulp-cli": "^3.0.0", + "undertaker": "^2.0.0", + "vinyl-fs": "^4.0.0" }, "dependencies": { - "es-abstract": { - "version": "1.17.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", - "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "fs-mkdirp-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-2.0.1.tgz", + "integrity": "sha512-UTOY+59K6IA94tec8Wjqm0FSh5OVudGNB0NL/P6fB3HiE3bYOY3VYBGijsnOHNkQSwC1FKkU77pmq7xp9CskLw==", "dev": true, "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-regex": "^1.1.0", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" + "graceful-fs": "^4.2.8", + "streamx": "^2.12.0" } }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "is-glob": "^4.0.3" } }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true + "glob-stream": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-8.0.2.tgz", + "integrity": "sha512-R8z6eTB55t3QeZMmU1C+Gv+t5UnNRkA55c5yo67fAVfxODxieTwsjNG7utxS/73NdP1NbDgCrhVEg2h00y4fFw==", + "dev": true, + "requires": { + "@gulpjs/to-absolute-glob": "^4.0.0", + "anymatch": "^3.1.3", + "fastq": "^1.13.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "is-negated-glob": "^1.0.0", + "normalize-path": "^3.0.0", + "streamx": "^2.12.5" + } }, - "is-callable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "lead": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-4.0.0.tgz", + "integrity": "sha512-DpMa59o5uGUWWjruMp71e6knmwKU3jRBBn1kjuLWN9EeIOxNeSAwvHf03WIl8g/ZMR2oSQC9ej3yeLBwdDc/pg==", "dev": true }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "now-and-later": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-3.0.0.tgz", + "integrity": "sha512-pGO4pzSdaxhWTGkfSfHx3hVzJVslFPwBp2Myq9MYN/ChfJZF87ochMAXnvz6/58RJSf5ik2q9tXprBBrk2cpcg==", "dev": true, "requires": { - "has-symbols": "^1.0.1" + "once": "^1.4.0" } }, - "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", "dev": true - } - } - }, - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, - "prop-types-exact": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/prop-types-exact/-/prop-types-exact-1.2.0.tgz", - "integrity": "sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA==", - "dev": true, - "requires": { - "has": "^1.0.3", - "object.assign": "^4.1.0", - "reflect.ownkeys": "^0.2.0" - } - }, - "propagate": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/propagate/-/propagate-1.0.0.tgz", - "integrity": "sha1-AMLa7t2iDofjeCs0Stuhzd1q1wk=", - "dev": true - }, - "proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", - "dev": true - }, - "proxy-addr": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", - "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", - "dev": true, - "requires": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.9.0" - } - }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true - }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" - }, - "psl": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz", - "integrity": "sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA==" - }, - "public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" + }, + "resolve-options": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-2.0.0.tgz", + "integrity": "sha512-/FopbmmFOQCfsCx77BRFdKOniglTiHumLgwvd6IDPihy1GKkadZbgQJBcTb2lMzSR1pndzd96b1nZrreZ7+9/A==", + "dev": true, + "requires": { + "value-or-function": "^4.0.0" + } + }, + "to-through": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-3.0.0.tgz", + "integrity": "sha512-y8MN937s/HVhEoBU1SxfHC+wxCHkV1a9gW8eAdTadYh/bGyesZIVcbjI+mSpFbSVwQici/XjBjuUyri1dnXwBw==", + "dev": true, + "requires": { + "streamx": "^2.12.5" + } + }, + "value-or-function": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-4.0.0.tgz", + "integrity": "sha512-aeVK81SIuT6aMJfNo9Vte8Dw0/FZINGBV8BfCraGtqVxIeLAEhJyoWs8SmvRVmXfGss2PmmOwZCuBPbZR+IYWg==", + "dev": true + }, + "vinyl": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz", + "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", + "dev": true, + "requires": { + "clone": "^2.1.2", + "clone-stats": "^1.0.0", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + } + }, + "vinyl-fs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-4.0.0.tgz", + "integrity": "sha512-7GbgBnYfaquMk3Qu9g22x000vbYkOex32930rBnc3qByw6HfMEAoELjCjoJv4HuEQxHAurT+nvMHm6MnJllFLw==", + "dev": true, + "requires": { + "fs-mkdirp-stream": "^2.0.1", + "glob-stream": "^8.0.0", + "graceful-fs": "^4.2.11", + "iconv-lite": "^0.6.3", + "is-valid-glob": "^1.0.0", + "lead": "^4.0.0", + "normalize-path": "3.0.0", + "resolve-options": "^2.0.0", + "stream-composer": "^1.0.2", + "streamx": "^2.14.0", + "to-through": "^3.0.0", + "value-or-function": "^4.0.0", + "vinyl": "^3.0.0", + "vinyl-sourcemap": "^2.0.0" + } + }, + "vinyl-sourcemap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-2.0.0.tgz", + "integrity": "sha512-BAEvWxbBUXvlNoFQVFVHpybBbjW1r03WhohJzJDSfgrrK5xVYIDTan6xN14DlyImShgDRv2gl9qhM6irVMsV0Q==", + "dev": true, + "requires": { + "convert-source-map": "^2.0.0", + "graceful-fs": "^4.2.10", + "now-and-later": "^3.0.0", + "streamx": "^2.12.5", + "vinyl": "^3.0.0", + "vinyl-contents": "^2.0.0" + } + } } }, - "public-ip": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/public-ip/-/public-ip-3.2.0.tgz", - "integrity": "sha512-DBq4o955zhrhESG4z6GkLN9mtY9NT/JOjEV8pvnYy3bjVQOQF0J5lJNwWLbEWwNstyNFJlY7JxCPFq4bdXSabw==", + "gulp-cli": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-3.0.0.tgz", + "integrity": "sha512-RtMIitkT8DEMZZygHK2vEuLPqLPAFB4sntSxg4NoDta7ciwGZ18l7JuhCTiS5deOJi2IoK0btE+hs6R4sfj7AA==", + "dev": true, "requires": { - "dns-socket": "^4.2.0", - "got": "^9.6.0", - "is-ip": "^3.1.0" + "@gulpjs/messages": "^1.1.0", + "chalk": "^4.1.2", + "copy-props": "^4.0.0", + "gulplog": "^2.2.0", + "interpret": "^3.1.1", + "liftoff": "^5.0.0", + "mute-stdout": "^2.0.0", + "replace-homedir": "^2.0.0", + "semver-greatest-satisfied-range": "^2.0.0", + "string-width": "^4.2.3", + "v8flags": "^4.0.0", + "yargs": "^16.2.0" }, "dependencies": { - "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" - }, - "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "dependencies": { - "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", - "requires": { - "pump": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" - } + "color-convert": "^2.0.1" } }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "requires": { - "pump": "^3.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" + "color-name": "~1.1.4" } }, - "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, - "ip-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.1.0.tgz", - "integrity": "sha512-pKnZpbgCTfH/1NLIlOduP/V+WRXzC2MOz3Qo8xmxk8C5GudJLgK5QyLVXOSWy3ParAH7Eemurl3xjv/WXYFvMA==" + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, - "is-ip": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-3.1.0.tgz", - "integrity": "sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==", + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "requires": { - "ip-regex": "^4.0.0" + "has-flag": "^4.0.0" } }, - "normalize-url": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", - "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==" - }, - "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" } } } }, - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "gulp-typescript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/gulp-typescript/-/gulp-typescript-5.0.1.tgz", + "integrity": "sha512-YuMMlylyJtUSHG1/wuSVTrZp60k1dMEFKYOvDf7OvbAJWrDtxxD4oZon4ancdWwzjj30ztiidhe4VXJniF0pIQ==", "dev": true, "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "ansi-colors": "^3.0.5", + "plugin-error": "^1.0.1", + "source-map": "^0.7.3", + "through2": "^3.0.0", + "vinyl": "^2.1.0", + "vinyl-fs": "^3.0.3" + }, + "dependencies": { + "ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", + "dev": true + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + }, + "through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + } } }, - "pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "gulplog": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-2.2.0.tgz", + "integrity": "sha512-V2FaKiOhpR3DRXZuYdRLn/qiY0yI5XmqbTKrYbdemJ+xOh2d2MOweI/XFgMzd/9+1twdvMwllnZbWZNJ+BOm4A==", "dev": true, "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" + "glogg": "^2.2.0" } }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "pure-color": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz", - "integrity": "sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4=", - "dev": true - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "query-string": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", - "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", "dev": true, "requires": { - "decode-uri-component": "^0.2.0", - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" + "duplexer": "^0.1.2" } }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true - }, - "querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "dev": true - }, - "querystringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", - "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==" - }, - "queue": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/queue/-/queue-3.1.0.tgz", - "integrity": "sha1-bEnQHwCeIlZ4h4nyv/rGuLmZBYU=", + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { - "inherits": "~2.0.0" + "function-bind": "^1.1.1" } }, - "quote-stream": { + "has-bigints": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/quote-stream/-/quote-stream-1.0.2.tgz", - "integrity": "sha1-hJY/jJwmuULhU/7rU6rnRlK34LI=", - "requires": { - "buffer-equal": "0.0.1", - "minimist": "^1.1.3", - "through2": "^2.0.0" - } + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true }, - "raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "requires": { - "performance-now": "^2.1.0" + "es-define-property": "^1.0.0" } }, - "railroad-diagrams": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", - "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=", + "has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "dev": true }, - "ramda": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.0.tgz", - "integrity": "sha512-pVzZdDpWwWqEVVLshWUHjNwuVP7SfcmPraYuqocJp1yo2U1R7P+5QAfDhdItkuoGqIBnBYrtPp7rEPqDn9HlZA==", + "has-symbol-support-x": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", + "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==", "dev": true }, - "randexp": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", - "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", + "has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" + }, + "has-to-string-tag-x": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", + "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", "dev": true, "requires": { - "discontinuous-range": "1.0.0", - "ret": "~0.1.10" + "has-symbol-support-x": "^1.4.1" } }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, + "has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "requires": { - "safe-buffer": "^5.1.0" + "has-symbols": "^1.0.3" } }, - "randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", "dev": true, "requires": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" } }, - "range-inclusive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/range-inclusive/-/range-inclusive-1.0.2.tgz", - "integrity": "sha1-Rs2KsjevVZKKXDjzpQoXiSDhpQk=", - "dev": true - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } }, - "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "hasha": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.1.0.tgz", + "integrity": "sha512-OFPDWmzPN1l7atOV1TgBVmNtBxaIysToK6Ve9DK+vT6pYuklw/nPNT+HJbZi0KDcI6vWB+9tgvZ5YD7fA3CXcA==", "dev": true, "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" }, "dependencies": { - "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", "dev": true } } }, - "raw-loader": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-0.5.1.tgz", - "integrity": "sha1-DD0L6u2KAclm2Xh793goElKpeao=", + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", "dev": true, "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" } }, - "react": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react/-/react-16.8.6.tgz", - "integrity": "sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw==", + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", "dev": true, "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.13.6" + "parse-passwd": "^1.0.0" } }, - "react-annotation": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/react-annotation/-/react-annotation-2.1.6.tgz", - "integrity": "sha512-r/WT9ylhhTXAbYS/8MJOy/oKgO/G8DQ02fDmeEVPUtPq3VEG1Q7BUGYIJLWgWwNSRmgTLGIo4RIqYbQ9t1f0aA==", + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "http-cache-semantics": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", + "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", + "dev": true + }, + "http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", "dev": true, "requires": { - "prop-types": "15.6.2", - "viz-annotation": "0.0.3" + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" }, "dependencies": { - "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "dev": true, "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" + "ms": "2.1.2" } } } }, - "react-base16-styling": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.5.3.tgz", - "integrity": "sha1-OFjyTpxN2MvT9wLz901YHKKRcmk=", - "dev": true, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", "requires": { - "base16": "^1.0.0", - "lodash.curry": "^4.0.1", - "lodash.flow": "^3.3.0", - "pure-color": "^1.2.0" + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "requires": { + "ms": "2.1.2" + } + } } }, - "react-color": { - "version": "2.17.3", - "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.17.3.tgz", - "integrity": "sha512-1dtO8LqAVotPIChlmo6kLtFS1FP89ll8/OiA8EcFRDR+ntcK+0ukJgByuIQHRtzvigf26dV5HklnxDIvhON9VQ==", - "dev": true, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "requires": { - "@icons/material": "^0.2.4", - "lodash": "^4.17.11", - "material-colors": "^1.2.1", - "prop-types": "^15.5.10", - "reactcss": "^1.2.0", - "tinycolor2": "^1.4.1" + "safer-buffer": ">= 2.1.2 < 3.0.0" } }, - "react-data-grid": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/react-data-grid/-/react-data-grid-6.1.0.tgz", - "integrity": "sha512-N1UtiHvsowEPzhx0VPqQKvGgSza/YNljczbisFDGMjawiGApS2taMv7h+EDXDx49CdaA6ur4eYS0z10x63IUpw==", + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true + }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true + }, + "import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "requires": { - "object-assign": "^4.1.1", - "react-is-deprecated": "^0.1.2", - "shallowequal": "^1.1.0" - } - }, - "react-dev-utils": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-5.0.3.tgz", - "integrity": "sha512-Mvs6ofsc2xTjeZIrMaIfbXfsPVrbdVy/cVqq6SAacnqfMlcBpDuivhWZ1ODGeJ8HgmyWTLH971PYjj/EPCDVAw==", - "dev": true, - "requires": { - "address": "1.0.3", - "babel-code-frame": "6.26.0", - "chalk": "1.1.3", - "cross-spawn": "5.1.0", - "detect-port-alt": "1.1.6", - "escape-string-regexp": "1.0.5", - "filesize": "3.5.11", - "global-modules": "1.0.0", - "gzip-size": "3.0.0", - "inquirer": "3.3.0", - "is-root": "1.0.0", - "opn": "5.2.0", - "react-error-overlay": "^4.0.1", - "recursive-readdir": "2.2.1", - "shell-quote": "1.6.1", - "sockjs-client": "1.1.5", - "strip-ansi": "3.0.1", - "text-table": "0.2.0" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true } } }, - "react-dom": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.6.tgz", - "integrity": "sha512-1nL7PIq9LTL3fthPqwkvr2zY7phIPjYrT0jp4HjyEQrEROnw4dG41VVwi/wfoCneoleqrNX7iAD+pXebJZwrwA==", - "dev": true, + "import-in-the-middle": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.4.2.tgz", + "integrity": "sha512-9WOz1Yh/cvO/p69sxRmhyQwrIGGSp7EIdcb+fFNVi7CzQGQB8U1/1XrKVSbEd/GNOAeM0peJtmi7+qphe7NvAw==", "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.13.6" + "acorn": "^8.8.2", + "acorn-import-assertions": "^1.9.0", + "cjs-module-lexer": "^1.2.2", + "module-details-from-path": "^1.0.3" } }, - "react-draggable": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.2.tgz", - "integrity": "sha512-zLQs4R4bnBCGnCVTZiD8hPsHtkiJxgMpGDlRESM+EHQo8ysXhKJ2GKdJ8UxxLJdRVceX1j19jy+hQS2wHislPQ==", + "import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, "requires": { - "classnames": "^2.2.5", - "prop-types": "^15.6.0" + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" } }, - "react-error-overlay": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-4.0.1.tgz", - "integrity": "sha512-xXUbDAZkU08aAkjtUvldqbvI04ogv+a1XdHxvYuHPYKIVk/42BIOD0zSKTHAWV4+gDy3yGm283z2072rA2gdtw==", + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, - "react-hot-loader": { - "version": "4.12.6", - "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.12.6.tgz", - "integrity": "sha512-tRXWgF5MhQSEXX3EHIplCOWCzSg+ye7ddHeQLt7Z+CaZMeEfeCL2/uSGITIzWXOQYhefnLX8IZtr2cff4xIrww==", - "dev": true, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { - "fast-levenshtein": "^2.0.6", - "global": "^4.3.0", - "hoist-non-react-statics": "^3.3.0", - "loader-utils": "^1.1.0", - "prop-types": "^15.6.1", - "react-lifecycles-compat": "^3.0.4", - "shallowequal": "^1.0.2", - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } + "once": "^1.3.0", + "wrappy": "1" } }, - "react-is": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", - "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==" + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "react-is-deprecated": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/react-is-deprecated/-/react-is-deprecated-0.1.2.tgz", - "integrity": "sha1-MBFI+G6kKP6OZz7KejchYLdXnb0=", + "ini": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", + "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", "dev": true }, - "react-json-tree": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/react-json-tree/-/react-json-tree-0.11.2.tgz", - "integrity": "sha512-aYhUPj1y5jR3ZQ+G3N7aL8FbTyO03iLwnVvvEikLcNFqNTyabdljo9xDftZndUBFyyyL0aK3qGO9+8EilILHUw==", + "internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "dev": true, "requires": { - "babel-runtime": "^6.6.1", - "prop-types": "^15.5.8", - "react-base16-styling": "^0.5.1" + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" } }, - "react-lifecycles-compat": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", + "interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "dev": true }, - "react-markdown": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-4.3.1.tgz", - "integrity": "sha512-HQlWFTbDxTtNY6bjgp3C3uv1h2xcjCSi1zAEzfBW9OwJJvENSYiLXWNXN5hHLsoqai7RnZiiHzcnWdXk2Splzw==", + "into-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", + "integrity": "sha512-TcdjPibTksa1NQximqep2r17ISRiNE9fwlfbg3F8ANdvP5/yrFTew86VcO//jk4QTaMlbjypPBq76HN2zaKfZQ==", "dev": true, "requires": { - "html-to-react": "^1.3.4", - "mdast-add-list-metadata": "1.0.1", - "prop-types": "^15.7.2", - "react-is": "^16.8.6", - "remark-parse": "^5.0.0", - "unified": "^6.1.5", - "unist-util-visit": "^1.3.0", - "xtend": "^4.0.1" + "from2": "^2.1.1", + "p-is-promise": "^1.1.0" } }, - "react-motion": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/react-motion/-/react-motion-0.5.2.tgz", - "integrity": "sha512-9q3YAvHoUiWlP3cK0v+w1N5Z23HXMj4IF4YuvjvWegWqNPfLXsOBE/V7UvQGpXxHFKRQQcNcVQE31g9SB/6qgQ==", + "inversify": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/inversify/-/inversify-6.0.2.tgz", + "integrity": "sha512-i9m8j/7YIv4mDuYXUAcrpKPSaju/CIly9AHK5jvCBeoiM/2KEsuCQTTP+rzSWWpLYWRukdXFSl6ZTk2/uumbiA==" + }, + "is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", "dev": true, "requires": { - "performance-now": "^0.2.0", - "prop-types": "^15.5.8", - "raf": "^3.1.0" - }, - "dependencies": { - "performance-now": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", - "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=", - "dev": true - } + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" } }, - "react-move": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/react-move/-/react-move-2.9.1.tgz", - "integrity": "sha512-5qKYsJrKKpSypEaaYyR2HBbBgX65htRqKDa8o5OGDkq2VfklmTCbLawtYFpdmcJRqbz4jCYpzo2Rrsazq9HA8Q==", + "is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", "dev": true, "requires": { - "@babel/runtime": "^7.2.0", - "d3-interpolate": "^1.3.2", - "d3-timer": "^1.0.9", - "prop-types": "^15.6.2", - "react-lifecycles-compat": "^3.0.4" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" } }, - "react-popper": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.7.tgz", - "integrity": "sha512-nmqYTx7QVjCm3WUZLeuOomna138R1luC4EqkW3hxJUrAe+3eNz3oFCLYdnPwILfn0mX1Ew2c3wctrjlUMYYUww==", + "is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", "dev": true, "requires": { - "@babel/runtime": "^7.1.2", - "create-react-context": "^0.3.0", - "deep-equal": "^1.1.1", - "popper.js": "^1.14.4", - "prop-types": "^15.6.1", - "typed-styles": "^0.0.7", - "warning": "^4.0.2" - }, - "dependencies": { - "deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", - "dev": true, - "requires": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - } - } + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" } }, - "react-redux": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.1.1.tgz", - "integrity": "sha512-QsW0vcmVVdNQzEkrgzh2W3Ksvr8cqpAv5FhEk7tNEft+5pp7rXxAudTz3VOPawRkLIepItpkEIyLcN/VVXzjTg==", + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", "dev": true, "requires": { - "@babel/runtime": "^7.5.5", - "hoist-non-react-statics": "^3.3.0", - "invariant": "^2.2.4", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-is": "^16.9.0" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.3.tgz", - "integrity": "sha512-kq6anf9JGjW8Nt5rYfEuGRaEAaH1mkv3Bbu6rYvLOpPh/RusSJXuKPEAoZ7L7gybZkchE8+NV5g9vKF4AGAtsA==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.2" - } - }, - "react-is": { - "version": "16.11.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.11.0.tgz", - "integrity": "sha512-gbBVYR2p8mnriqAwWx9LbuUrShnAuSCNnuPGyc7GJrMVQtPDAh8iLpv7FRuMPFb56KkaVZIYSz1PrjI9q0QPCw==", - "dev": true - } + "has-bigints": "^1.0.1" } }, - "react-svg-pan-zoom": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/react-svg-pan-zoom/-/react-svg-pan-zoom-3.1.0.tgz", - "integrity": "sha512-hmDUarqhNnCwuZumV9Pw7o5inW7lda4sX2U1vDK2B2slrSfNu1jbelOp6aaOEyUF7WzMA1xrpH6NBWvk4UeUTQ==", + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "requires": { - "prop-types": "^15.7.2", - "transformation-matrix": "^2.0.0" + "binary-extensions": "^2.0.0" } }, - "react-svgmt": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/react-svgmt/-/react-svgmt-1.1.8.tgz", - "integrity": "sha512-3xu7iWuHIbqM2hv4eMsAN1mZKz6EnXTPAcE4mMX/NwuYY5uUKJBoKDAmxY+I6KXHx2SpYJtKAqe1a5jEehteZg==", + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", "dev": true, "requires": { - "d3-ease": "^1.0.3", - "react-motion": "^0.5.2", - "react-move": "^2.7.0" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, + "is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "requires": { + "hasown": "^2.0.2" } }, - "react-table": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/react-table/-/react-table-6.10.0.tgz", - "integrity": "sha512-s/mQLI1+mNvlae45MfAZyZ04YIT3jUzWJqx34s0tfwpDdgJkpeK6vyzwMUkKFCpGODBxpjBOekYZzcEmk+2FiQ==", + "is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", "dev": true, "requires": { - "classnames": "^2.2.5" + "is-typed-array": "^1.1.13" } }, - "react-table-hoc-fixed-columns": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/react-table-hoc-fixed-columns/-/react-table-hoc-fixed-columns-1.0.2.tgz", - "integrity": "sha512-0i2IEhGgFOibxoA1FOvABmuxcA7kCcw2lB5UPMX3RzS2wah1gvq6U128JNjvnG/P+yKvx54X+lwEEQ7ovjzMcA==", + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", "dev": true, "requires": { - "classnames": "^2.2.6", - "emotion": "^9.2.3", - "uniqid": "^5.0.3" + "has-tostringtag": "^1.0.0" } }, - "react-test-renderer": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.8.6.tgz", - "integrity": "sha512-H2srzU5IWYT6cZXof6AhUcx/wEyJddQ8l7cLM/F7gDXYyPr4oq+vCIxJYXVGhId1J706sqziAjuOEjyNkfgoEw==", + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", "dev": true, "requires": { - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "react-is": "^16.8.6", - "scheduler": "^0.13.6" + "has-tostringtag": "^1.0.0" } }, - "react-transition-group": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", - "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "requires": { - "dom-helpers": "^3.4.0", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2", - "react-lifecycles-compat": "^3.0.4" + "is-extglob": "^2.1.1" } }, - "react-virtualized": { - "version": "9.21.1", - "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.21.1.tgz", - "integrity": "sha512-E53vFjRRMCyUTEKuDLuGH1ld/9TFzjf/fFW816PE4HFXWZorESbSTYtiZz1oAjra0MminaUU1EnvUxoGuEFFPA==", + "is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "clsx": "^1.0.1", - "dom-helpers": "^2.4.0 || ^3.0.0", - "linear-layout-vector": "0.0.1", - "loose-envify": "^1.3.0", - "prop-types": "^15.6.0", - "react-lifecycles-compat": "^3.0.4" + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "is-natural-number": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", + "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==", + "dev": true + }, + "is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" } }, - "reactcss": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", - "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==", + "is-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "dev": true + }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { - "lodash": "^4.0.1" + "isobject": "^3.0.1" } }, - "read": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "dev": true, "requires": { - "mute-stream": "~0.0.4" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" } }, - "read-cache": { + "is-relative": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", "dev": true, "requires": { - "pify": "^2.3.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } + "is-unc-path": "^1.0.0" } }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - }, - "dependencies": { - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } + "is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "dev": true }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "dev": true, "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - } + "call-bind": "^1.0.7" } }, - "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", "dev": true, "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } + "has-tostringtag": "^1.0.0" } }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", "dev": true, "requires": { - "resolve": "^1.1.6" + "has-symbols": "^1.0.2" } }, - "recursive-readdir": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.1.tgz", - "integrity": "sha1-kO8jHQd4xc4JPJpI105cVCLROpk=", + "is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, "requires": { - "minimatch": "3.0.3" - }, - "dependencies": { - "minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q=", - "dev": true, - "requires": { - "brace-expansion": "^1.0.0" - } - } + "which-typed-array": "^1.1.16" } }, - "reduce-css-calc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", - "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", - "dev": true, - "requires": { - "balanced-match": "^0.4.2", - "math-expression-evaluator": "^1.2.14", - "reduce-function-call": "^1.0.1" - }, - "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", - "dev": true - } - } + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true }, - "reduce-function-call": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.3.tgz", - "integrity": "sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ==", + "is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", "dev": true, "requires": { - "balanced-match": "^1.0.0" + "unc-path-regex": "^0.1.2" } }, - "redux": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.4.tgz", - "integrity": "sha512-vKv4WdiJxOWKxK0yRoaK3Y4pxxB0ilzVx6dszU2W8wLxlb2yikRph4iV/ymtdJ6ZxpBLFbyrxklnT5yBbQSl3Q==", + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", + "dev": true + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", "dev": true, "requires": { - "loose-envify": "^1.4.0", - "symbol-observable": "^1.2.0" - }, - "dependencies": { - "symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", - "dev": true - } + "call-bind": "^1.0.2" } }, - "redux-logger": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/redux-logger/-/redux-logger-3.0.6.tgz", - "integrity": "sha1-91VZZvMJjzyIYExEnPC69XeCdL8=", + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, "requires": { - "deep-diff": "^0.3.5" + "is-docker": "^2.0.0" } }, - "reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" - }, - "reflect.ownkeys": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz", - "integrity": "sha1-dJrO7H8/34tj+SegSAnpDFwLNGA=", + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, - "regenerate": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", - "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", - "dev": true + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, - "regenerate-unicode-properties": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz", - "integrity": "sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==", - "dev": true, - "requires": { - "regenerate": "^1.4.0" - } + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true }, - "regenerator-runtime": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", - "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==" + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true }, - "regenerator-transform": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.0.tgz", - "integrity": "sha512-rtOelq4Cawlbmq9xuMR5gdFmv7ku/sFoB7sRiywx7aq53bc52b4j6zvH7Te1Vt/X2YveDKnCGUbioieU7FEL3w==", + "istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", "dev": true, "requires": { - "private": "^0.1.6" + "append-transform": "^2.0.0" } }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", "dev": true, "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } } }, - "regexp-tree": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.11.tgz", - "integrity": "sha512-7/l/DgapVVDzZobwMCCgMlqiqyLFJ0cduo/j+3BcDJIB+yJdsYCfKuI3l/04NV+H/rfNRdPIDbXNZHM9XvQatg==", - "dev": true - }, - "regexp.prototype.flags": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", - "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "istanbul-lib-processinfo": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^8.3.2" }, "dependencies": { - "es-abstract": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.0.tgz", - "integrity": "sha512-yYkE07YF+6SIBmg1MsJ9dlub5L48Ek7X0qz+c/CPCHS9EBXfESorzng4cJQjJW5/pB6vDF41u7F8vUhLVDqIug==", + "cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", "dev": true, "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "aggregate-error": "^3.0.0" } }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, - "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "has": "^1.0.3" + "shebang-regex": "^3.0.0" } }, - "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true } } }, - "regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", - "dev": true - }, - "regexpu-core": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.5.4.tgz", - "integrity": "sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ==", - "dev": true, - "requires": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.0.2", - "regjsgen": "^0.5.0", - "regjsparser": "^0.6.0", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.1.0" - } - }, - "regjsgen": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz", - "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==", - "dev": true - }, - "regjsparser": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", - "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", "dev": true, "requires": { - "jsesc": "~0.5.0" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" }, "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, - "regression": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regression/-/regression-2.0.1.tgz", - "integrity": "sha1-jSnD6CJKEIUMNeM36FqLL6w7DIc=", - "dev": true - }, - "relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", - "dev": true - }, - "relative": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/relative/-/relative-3.0.2.tgz", - "integrity": "sha1-Dc2OxUpdNaPBXhBFA9ZTdbWlNn8=", + "istanbul-lib-source-maps": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", "dev": true, "requires": { - "isobject": "^2.0.0" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" }, "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { - "isarray": "1.0.0" + "ms": "2.1.2" } } } }, - "release-zalgo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", "dev": true, "requires": { - "es6-error": "^4.0.1" - } - }, - "remark-parse": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-5.0.0.tgz", - "integrity": "sha512-b3iXszZLH1TLoyUzrATcTQUZrwNl1rE70rVdSruJFlDaJ9z5aMkhrG43Pp68OgfHndL/ADz6V69Zow8cTQu+JA==", - "dev": true, - "requires": { - "collapse-white-space": "^1.0.2", - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-whitespace-character": "^1.0.0", - "is-word-character": "^1.0.0", - "markdown-escapes": "^1.0.0", - "parse-entities": "^1.1.0", - "repeat-string": "^1.5.4", - "state-toggle": "^1.0.0", - "trim": "0.0.1", - "trim-trailing-lines": "^1.0.0", - "unherit": "^1.0.4", - "unist-util-remove-position": "^1.0.0", - "vfile-location": "^2.0.0", - "xtend": "^4.0.1" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" } }, - "remove-bom-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", - "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "isurl": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", + "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", "dev": true, "requires": { - "is-buffer": "^1.1.5", - "is-utf8": "^0.2.1" + "has-to-string-tag-x": "^1.2.0", + "is-object": "^1.0.1" } }, - "remove-bom-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", - "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", + "jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "requires": { - "remove-bom-buffer": "^3.0.0", - "safe-buffer": "^5.1.0", - "through2": "^2.0.3" + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" } }, - "remove-files-webpack-plugin": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/remove-files-webpack-plugin/-/remove-files-webpack-plugin-1.4.0.tgz", - "integrity": "sha512-qlHn4EHNjWi6LiNV6aWCXROKjQg28sF/VxJ2FK+p5pPUQyLIBGlBAs/TUMxdVeToHSxq2RuA2XGgxcaDeTRnUg==", + "jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, "requires": { - "@types/webpack": "^4.41.6", - "trash": "^6.1.1" + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "dependencies": { - "@types/webpack": { - "version": "4.41.7", - "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.7.tgz", - "integrity": "sha512-OQG9viYwO0V1NaNV7d0n79V+n6mjOV30CwgFPIfTzwmk8DHbt+C4f2aBGdCYbo3yFyYD6sjXfqqOjwkl1j+ulA==", + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "requires": { - "@types/anymatch": "*", - "@types/node": "*", - "@types/tapable": "*", - "@types/uglify-js": "*", - "@types/webpack-sources": "*", - "source-map": "^0.6.0" + "has-flag": "^4.0.0" } } } }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, - "renderkid": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.3.tgz", - "integrity": "sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA==", + "js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "requires": { - "css-select": "^1.1.0", - "dom-converter": "^0.2", - "htmlparser2": "^3.3.0", - "strip-ansi": "^3.0.0", - "utila": "^0.4.0" + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } } } }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, - "replace-homedir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", - "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1", - "is-absolute": "^1.0.0", - "remove-trailing-separator": "^1.1.0" - } - }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "request-progress": { + "json-buffer": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", - "integrity": "sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4=", - "requires": { - "throttleit": "^1.0.0" - } - }, - "request-promise-core": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", - "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", - "dev": true, - "requires": { - "lodash": "^4.17.11" - } - }, - "request-promise-native": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", - "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", - "dev": true, - "requires": { - "request-promise-core": "1.1.2", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", "dev": true }, - "requirejs": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz", - "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==", + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" - }, - "resize-observer-polyfill": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", - "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, - "resolve": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", - "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==", - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", - "dev": true, - "requires": { - "resolve-from": "^3.0.0" - } - }, - "resolve-dir": { + "json-stable-stringify-without-jsonify": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", - "dev": true, - "requires": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, - "resolve-options": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", - "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", - "dev": true, - "requires": { - "value-or-function": "^3.0.0" - } - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, - "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "requires": { - "lowercase-keys": "^1.0.0" - } + "jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", "dev": true, "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } - }, - "restructure": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/restructure/-/restructure-0.5.4.tgz", - "integrity": "sha1-9U591WNZD7NP1r9Vh2EJrsyyjeg=", - "requires": { - "browserify-optional": "^1.0.0" + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "dependencies": { + "jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "dev": true, + "requires": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", + "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", + "dev": true, + "requires": { + "jwa": "^1.4.2", + "safe-buffer": "^5.0.1" + } + } } }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, - "rewiremock": { - "version": "3.13.7", - "resolved": "https://registry.npmjs.org/rewiremock/-/rewiremock-3.13.7.tgz", - "integrity": "sha512-U6iFfdXPiNtIBDcJWmspl/nhVk1EANkXLq2GM78T3ZfegvO5EW0TgNzExLh5iHXFJKQr//SmH9iloK/s4O7UqA==", + "jsx-ast-utils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz", + "integrity": "sha512-uP5vu8xfy2F9A6LGC22KO7e2/vGTS1MhP+18f++ZNlf0Ohaxbc9nIEwHAsejlJKyzfZzU5UIhe5ItYkitcZnZA==", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "compare-module-exports": "^2.1.0", - "lodash.some": "^4.6.0", - "lodash.template": "^4.4.0", - "node-libs-browser": "^2.1.0", - "path-parse": "^1.0.5", - "wipe-node-cache": "^2.1.0", - "wipe-webpack-cache": "^2.1.0" + "array-includes": "^3.1.3", + "object.assign": "^4.1.2" } }, - "rfdc": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz", - "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==" - }, - "rgb": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/rgb/-/rgb-0.1.0.tgz", - "integrity": "sha1-vieykej+/+rBvZlylyG/pA/AN7U=", - "dev": true - }, - "rgb-hex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/rgb-hex/-/rgb-hex-2.1.0.tgz", - "integrity": "sha1-x3PF/iJoolV42SU5qCp6XOU77aY=", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", "dev": true, "requires": { - "glob": "^7.1.3" + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" } }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } + "just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true }, - "roughjs-es5": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/roughjs-es5/-/roughjs-es5-0.1.0.tgz", - "integrity": "sha512-NMjzoBgSYk8qEYLSxzxytS20sfdQV7zg119FZjFDjIDwaqodFcf7QwzKbqM64VeAYF61qogaPLk3cs8Gb+TqZA==", + "jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "dev": true, "requires": { - "babel-runtime": "^6.26.0" + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" } }, - "rst-selector-parser": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", - "integrity": "sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=", + "jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "dev": true, "requires": { - "lodash.flattendeep": "^4.4.0", - "nearley": "^2.7.10" + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" } }, - "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "keytar": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz", + "integrity": "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==", "dev": true, + "optional": true, "requires": { - "is-promise": "^2.1.0" - } - }, - "run-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/run-node/-/run-node-1.0.0.tgz", - "integrity": "sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A==", - "dev": true + "node-addon-api": "^4.3.0", + "prebuild-install": "^7.0.1" + } }, - "run-queue": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "keyv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", + "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", "dev": true, "requires": { - "aproba": "^1.1.1" + "json-buffer": "3.0.0" } }, - "rw": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=", + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, - "rx-lite": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", - "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", + "language-subtag-registry": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.20.tgz", + "integrity": "sha512-KPMwROklF4tEx283Xw0pNKtfTj1gZ4UByp4EsIFWLgBavJltF4TiYPc39k06zSTsLzxTVXXDSpbwaQXaFB4Qeg==", "dev": true }, - "rx-lite-aggregates": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", - "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=", "dev": true, "requires": { - "rx-lite": "*" + "language-subtag-registry": "~0.3.2" } }, - "rxjs": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", - "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", + "last-run": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/last-run/-/last-run-2.0.0.tgz", + "integrity": "sha512-j+y6WhTLN4Itnf9j5ZQos1BGPCS8DAwmgMroR3OzfxAsBxam0hMw7J8M3KqZl0pLQJ1jNnwIexg5DYpC/ctwEQ==", + "dev": true + }, + "lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "dev": true, "requires": { - "tslib": "^1.9.0" + "readable-stream": "^2.0.5" } }, - "rxjs-compat": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/rxjs-compat/-/rxjs-compat-6.5.4.tgz", - "integrity": "sha512-rkn+lbOHUQOurdd74J/hjmDsG9nFx0z66fvnbs8M95nrtKvNqCKdk7iZqdY51CGmDemTQk+kUPy4s8HVOHtkfA==" - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "lead": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", + "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", "dev": true, "requires": { - "ret": "~0.1.10" + "flush-write-stream": "^1.0.2" } }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true }, - "sanitize-filename": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", - "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, "requires": { - "truncate-utf8-bytes": "^1.0.0" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" } }, - "sanitize-html": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.20.1.tgz", - "integrity": "sha512-txnH8TQjaQvg2Q0HY06G6CDJLVYCpbnxrdO0WN8gjCKaU5J0KbyGYhZxx5QJg3WLZ1lB7XU9kDkfrCXUozqptA==", + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", "dev": true, "requires": { - "chalk": "^2.4.1", - "htmlparser2": "^3.10.0", - "lodash.clonedeep": "^4.5.0", - "lodash.escaperegexp": "^4.1.2", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.mergewith": "^4.6.1", - "postcss": "^7.0.5", - "srcset": "^1.0.0", - "xtend": "^4.0.1" + "immediate": "~3.0.5" } }, - "sass-loader": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.1.0.tgz", - "integrity": "sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w==", + "liftoff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-5.0.0.tgz", + "integrity": "sha512-a5BQjbCHnB+cy+gsro8lXJ4kZluzOijzJ1UVVfyJYZC+IP2pLv1h4+aysQeKuTmyO8NAqfyQAk4HWaP/HjcKTg==", "dev": true, "requires": { - "clone-deep": "^2.0.1", - "loader-utils": "^1.0.1", - "lodash.tail": "^4.1.1", - "neo-async": "^2.5.0", - "pify": "^3.0.0", - "semver": "^5.5.0" + "extend": "^3.0.2", + "findup-sync": "^5.0.0", + "fined": "^2.0.0", + "flagged-respawn": "^2.0.0", + "is-plain-object": "^5.0.0", + "rechoir": "^0.8.0", + "resolve": "^1.20.0" }, "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", "dev": true } } }, - "sax": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz", - "integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE=" - }, - "saxes": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.11.tgz", - "integrity": "sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g==", + "linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", "dev": true, "requires": { - "xmlchars": "^2.1.1" + "uc.micro": "^1.0.1" } }, - "scheduler": { - "version": "0.13.6", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz", - "integrity": "sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ==", + "loader-runner": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "dev": true + }, + "loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "dev": true, "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" } }, - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" + "p-locate": "^4.1.0" } }, - "scope-analyzer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/scope-analyzer/-/scope-analyzer-2.0.5.tgz", - "integrity": "sha512-+U5H0417mnTEstCD5VwOYO7V4vYuSqwqjFap40ythe67bhMFL5C3UgPwyBv7KDJsqUBIKafOD57xMlh1rN7eaw==", - "requires": { - "array-from": "^2.1.1", - "es6-map": "^0.1.5", - "es6-set": "^0.1.5", - "es6-symbol": "^3.1.1", - "estree-is-function": "^1.0.0", - "get-assigned-identifiers": "^1.1.0" - } + "lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==" }, - "seek-bzip": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz", - "integrity": "sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w=", + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, "requires": { - "commander": "~2.8.1" + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" }, "dependencies": { - "commander": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", - "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "graceful-readlink": ">= 1.0.0" + "color-convert": "^2.0.1" } - } - } - }, - "semiotic": { - "version": "1.19.11", - "resolved": "https://registry.npmjs.org/semiotic/-/semiotic-1.19.11.tgz", - "integrity": "sha512-TIiVTnKFonApiWZTN7AfJ9ltAUOl0wBYK5tFd1NCMFQJcbqDFAGrjfle1VHM2ARovNEbYKnl2a3N6zRwSKfJlg==", - "dev": true, - "requires": { - "@mapbox/polylabel": "1", - "d3-array": "^1.2.0", - "d3-bboxCollide": "^1.0.3", - "d3-brush": "^1.0.6", - "d3-chord": "^1.0.4", - "d3-collection": "^1.0.1", - "d3-contour": "^1.1.1", - "d3-force": "^1.0.2", - "d3-glyphedge": "^1.2.0", - "d3-hexbin": "^0.2.2", - "d3-hierarchy": "^1.1.3", - "d3-interpolate": "^1.1.5", - "d3-polygon": "^1.0.5", - "d3-sankey-circular": "0.25.0", - "d3-scale": "^1.0.3", - "d3-selection": "^1.1.0", - "d3-shape": "^1.2.0", - "d3-voronoi": "^1.0.2", - "json2csv": "^4.5.1", - "labella": "1.1.4", - "memoize-one": "4.0.0", - "object-assign": "4.1.1", - "polygon-offset": "0.3.1", - "promise": "8.0.1", - "prop-types": "15.6.0", - "react-annotation": "^2.1.6", - "regression": "^2.0.1", - "roughjs-es5": "0.1.0", - "semiotic-mark": "0.3.1", - "svg-path-bounding-box": "1.0.4" - }, - "dependencies": { - "d3-scale": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-1.0.7.tgz", - "integrity": "sha512-KvU92czp2/qse5tUfGms6Kjig0AhHOwkzXG0+PqIJB3ke0WUv088AHMZI0OssO9NCkXt4RP8yju9rpH8aGB7Lw==", + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { - "d3-array": "^1.2.0", - "d3-collection": "1", - "d3-color": "1", - "d3-format": "1", - "d3-interpolate": "1", - "d3-time": "1", - "d3-time-format": "2" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "memoize-one": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-4.0.0.tgz", - "integrity": "sha512-wdpOJ4XBejprGn/xhd1i2XR8Dv1A25FJeIvR7syQhQlz9eXsv+06llcvcmBxlWVGv4C73QBsWA8kxvZozzNwiQ==", - "dev": true - }, - "promise": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.0.1.tgz", - "integrity": "sha1-5F1osAoXZHttpxG/he1u1HII9FA=", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "asap": "~2.0.3" + "color-name": "~1.1.4" } }, - "prop-types": { - "version": "15.6.0", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz", - "integrity": "sha1-zq8IMCL8RrSjX2nhPvda7Q1jmFY=", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "fbjs": "^0.8.16", - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" + "has-flag": "^4.0.0" } } } }, - "semiotic-mark": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/semiotic-mark/-/semiotic-mark-0.3.1.tgz", - "integrity": "sha512-j7CsNannyJi68Yg5DXDZJrw3wEssBTaeGEvGMaTqPuBlM1kPFXYWvS0dpRzsT/Yopn/kRRyooDR+l6zQCwV+EQ==", + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "dev": true, "requires": { - "d3-interpolate": "^1.1.5", - "d3-scale": "^1.0.3", - "d3-selection": "^1.1.0", - "d3-shape": "^1.2.0", - "d3-transition": "^1.0.3", - "prop-types": "^15.6.0", - "roughjs-es5": "0.1.0" - }, - "dependencies": { - "d3-scale": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-1.0.7.tgz", - "integrity": "sha512-KvU92czp2/qse5tUfGms6Kjig0AhHOwkzXG0+PqIJB3ke0WUv088AHMZI0OssO9NCkXt4RP8yju9rpH8aGB7Lw==", - "dev": true, - "requires": { - "d3-array": "^1.2.0", - "d3-collection": "1", - "d3-color": "1", - "d3-format": "1", - "d3-interpolate": "1", - "d3-time": "1", - "d3-time-format": "2" - } - } + "js-tokens": "^3.0.0 || ^4.0.0" } }, - "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + "loupe": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", + "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "dev": true, + "requires": { + "get-func-name": "^2.0.0" + } }, - "semver-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", "dev": true }, - "semver-greatest-satisfied-range": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", - "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", - "dev": true, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "requires": { - "sver-compat": "^1.5.0" + "yallist": "^4.0.0" } }, - "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "semver": "^6.0.0" }, "dependencies": { - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } }, - "serialize-javascript": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz", - "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==", + "make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "dev": true + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "dev": true + }, + "markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", "dev": true, "requires": { - "randombytes": "^2.1.0" + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + } } }, - "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" + }, + "md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", "dev": true, "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" } }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "md5.js": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", + "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "hash-base": "^3.0.0", + "inherits": "^2.0.1" } }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", "dev": true }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "braces": "^3.0.3", + "picomatch": "^2.3.1" } }, - "shallow-clone": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-1.0.0.tgz", - "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==", + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", "dev": true, "requires": { - "is-extendable": "^0.1.1", - "kind-of": "^5.0.0", - "mixin-object": "^2.0.1" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } + "bn.js": "^4.0.0", + "brorand": "^1.0.1" } }, - "shallow-copy": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/shallow-copy/-/shallow-copy-0.0.1.tgz", - "integrity": "sha1-QV9CcC1z2BAzApLMXuhurhoRoXA=" - }, - "shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "requires": { - "shebang-regex": "^1.0.0" + "mime-db": "1.52.0" } }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, - "shell-quote": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", - "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=", - "dev": true, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.8.tgz", + "integrity": "sha512-7RN35vit8DeBclkofOVmBY0eDAZZQd1HzmukRdSyz95CRh8FT54eqnbj0krQr3mrHR6sfRyYkyhwBWjoV5uqlQ==", "requires": { - "array-filter": "~0.0.0", - "array-map": "~0.0.0", - "array-reduce": "~0.0.0", - "jsonify": "~0.0.0" + "brace-expansion": "^2.0.1" }, "dependencies": { - "array-filter": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", - "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=", - "dev": true + "brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "requires": { + "balanced-match": "^1.0.0" + } } } }, - "shimmer": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", - "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true }, - "shortid": { - "version": "2.2.14", - "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.14.tgz", - "integrity": "sha512-4UnZgr9gDdA1kaKj/38IiudfC3KHKhDc1zi/HSxd9FQDR0VLwH3/y79tZJLsVYPsJgIjeHjqIWaWVRJUj9qZOQ==", + "minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", "dev": true, "requires": { - "nanoid": "^2.0.0" + "minimist": "^1.2.5" } }, - "side-channel": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.3.tgz", - "integrity": "sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g==", + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "dev": true, - "requires": { - "es-abstract": "^1.18.0-next.0", - "object-inspect": "^1.8.0" - }, - "dependencies": { - "es-abstract": { - "version": "1.18.0-next.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.0.tgz", - "integrity": "sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ==", + "optional": true + }, + "mocha": { + "version": "11.7.5", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.5.tgz", + "integrity": "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==", + "dev": true, + "requires": { + "browser-stdout": "^1.3.1", + "chokidar": "^4.0.1", + "debug": "^4.3.5", + "diff": "^7.0.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^10.4.5", + "he": "^1.2.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^9.0.5", + "ms": "^2.1.3", + "picocolors": "^1.1.1", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^9.2.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", + "yargs-unparser": "^2.0.0" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-negative-zero": "^2.0.0", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" + "readdirp": "^4.0.1" } }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" } }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", "dev": true }, - "is-callable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + } + }, + "glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "requires": { - "has-symbols": "^1.0.1" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" } }, - "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true - } - } - }, - "sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", - "dev": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true - }, - "simple-concat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", - "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=", - "dev": true - }, - "simple-get": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", - "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", - "dev": true, - "requires": { - "decompress-response": "^4.2.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - }, - "dependencies": { - "decompress-response": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", - "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + }, + "js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "requires": { - "mimic-response": "^2.0.0" + "argparse": "^2.0.1" } }, - "mimic-response": { + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.2" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true + }, + "shebang-command": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.0.0.tgz", - "integrity": "sha512-8ilDoEapqA4uQ3TwS0jakGONKXVJqpy+RpM+3b7pLdOjghCrEiGp9SRkFbUHAmZW9vdnrENWHjaweIoTIJExSQ==", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true } } }, - "simple-html-tokenizer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/simple-html-tokenizer/-/simple-html-tokenizer-0.1.1.tgz", - "integrity": "sha1-BcLuxXn//+FFoDCsJs/qYbmA+r4=", - "dev": true - }, - "simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "mocha-junit-reporter": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-2.0.2.tgz", + "integrity": "sha512-vYwWq5hh3v1lG0gdQCBxwNipBfvDiAM1PHroQRNp96+2l72e9wEUTw+mzoK+O0SudgfQ7WvTQZ9Nh3qkAYAjfg==", + "dev": true, "requires": { - "is-arrayish": "^0.3.1" + "debug": "^2.2.0", + "md5": "^2.1.0", + "mkdirp": "~0.5.1", + "strip-ansi": "^6.0.1", + "xml": "^1.0.0" } }, - "sinon": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-8.0.1.tgz", - "integrity": "sha512-vbXMHBszVioyPsuRDLEiPEgvkZnbjfdCFvLYV4jONNJqZNLWTwZ/gYSNh3SuiT1w9MRXUz+S7aX0B4Ar2XI8iw==", + "mocha-multi-reporters": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/mocha-multi-reporters/-/mocha-multi-reporters-1.5.1.tgz", + "integrity": "sha512-Yb4QJOaGLIcmB0VY7Wif5AjvLMUFAdV57D2TWEva1Y0kU/3LjKpeRVmlMIfuO1SVbauve459kgtIizADqxMWPg==", "dev": true, "requires": { - "@sinonjs/commons": "^1.7.0", - "@sinonjs/formatio": "^4.0.1", - "@sinonjs/samsam": "^4.0.1", - "diff": "^4.0.1", - "lolex": "^5.1.2", - "nise": "^3.0.0", - "supports-color": "^7.1.0" + "debug": "^4.1.1", + "lodash": "^4.17.15" }, "dependencies": { - "diff": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", - "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "ms": "2.1.2" } } } }, - "slash": { + "module-details-from-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", + "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==" + }, + "mrmime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.0.tgz", + "integrity": "sha512-a70zx7zFfVO7XpnQ2IX1Myh9yY4UYvfld/dikWRnsXxbyvMcfz+u6UfgNAtH+k2QqtJuzVpv6eLTx1G2+WKZbQ==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "mute-stdout": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-2.0.0.tgz", + "integrity": "sha512-32GSKM3Wyc8dg/p39lWPKYu8zci9mJFzV1Np9Of0ZEpe6Fhssn/FbI7ywAMd40uX+p3ZKh3T5EeCFv81qS3HmQ==", "dev": true }, - "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "named-js-regexp": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/named-js-regexp/-/named-js-regexp-1.3.5.tgz", + "integrity": "sha512-XO0DPujDP9IWpkt690iWLreKztb/VB811DGl5N3z7BfhkMJuiVZXOi6YN/fEB9qkvtMVTgSZDW8pzdVt8vj/FA==" + }, + "nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true + }, + "napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "dev": true, + "optional": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "nise": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-6.0.0.tgz", + "integrity": "sha512-K8ePqo9BFvN31HXwEtTNGzgrPpmvgciDsFz8aztFjt4LqKO/JeFD8tBOeuDiCMXrIl/m1YvfH8auSpxfaD09wg==", "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - } + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" } }, - "slickgrid": { - "version": "2.4.17", - "resolved": "https://registry.npmjs.org/slickgrid/-/slickgrid-2.4.17.tgz", - "integrity": "sha512-saxVD9URoBD2M/Sl+7fLWE125/Cp1j0YhkRMPke4Hwdk31q/lihNv8I2o70cM5GRmoeWJKW7tnhNraDEe89jEg==", + "node-abi": { + "version": "3.45.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.45.0.tgz", + "integrity": "sha512-iwXuFrMAcFVi/ZoZiqq8BzAdsLw9kxDfTC0HMyjXfSL/6CSDAGD5UmR7azrAgWV1zKYq7dUUMj4owusBWKLsiQ==", "dev": true, + "optional": true, "requires": { - "jquery": ">=1.8.0", - "jquery-ui": ">=1.8.0" + "semver": "^7.3.5" } }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", + "dev": true, + "optional": true + }, + "node-has-native-dependencies": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/node-has-native-dependencies/-/node-has-native-dependencies-1.0.2.tgz", + "integrity": "sha1-MVLsl1O2ZB5NMi0YXdSTBkmto9o=", "dev": true, "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } + "fs-walk": "0.0.1" } }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", "dev": true, "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" }, "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { - "kind-of": "^6.0.0" + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" } }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true } } }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "node-loader": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/node-loader/-/node-loader-1.0.3.tgz", + "integrity": "sha512-8c9ef5q24F0AjrPxUjdX7qdTlsU1zZCPeqYvSBCH1TJko3QW4qu1uA1C9KbOPdaRQwREDdbSYZgltBAlbV7l5g==", "dev": true, "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" } }, - "socket.io": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz", - "integrity": "sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==", + "node-polyfill-webpack-plugin": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/node-polyfill-webpack-plugin/-/node-polyfill-webpack-plugin-1.1.4.tgz", + "integrity": "sha512-Z0XTKj1wRWO8o/Vjobsw5iOJCN+Sua3EZEUc2Ziy9CyVvmHKu6o+t4gUH9GOE0czyPR94LI6ZCV/PpcM8b5yow==", "dev": true, "requires": { - "debug": "~4.1.0", - "engine.io": "~3.4.0", - "has-binary2": "~1.0.2", - "socket.io-adapter": "~1.1.0", - "socket.io-client": "2.3.0", - "socket.io-parser": "~3.4.0" + "assert": "^2.0.0", + "browserify-zlib": "^0.2.0", + "buffer": "^6.0.3", + "console-browserify": "^1.2.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.12.0", + "domain-browser": "^4.19.0", + "events": "^3.3.0", + "filter-obj": "^2.0.2", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "^1.0.1", + "process": "^0.11.10", + "punycode": "^2.1.1", + "querystring-es3": "^0.2.1", + "readable-stream": "^3.6.0", + "stream-browserify": "^3.0.0", + "stream-http": "^3.2.0", + "string_decoder": "^1.3.0", + "timers-browserify": "^2.0.12", + "tty-browserify": "^0.0.1", + "url": "^0.11.0", + "util": "^0.12.4", + "vm-browserify": "^1.1.2" }, "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "assert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", + "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", "dev": true, "requires": { - "ms": "^2.1.1" + "es6-object-assign": "^1.1.0", + "is-nan": "^1.2.1", + "object-is": "^1.0.1", + "util": "^0.12.0" } - } - } - }, - "socket.io-adapter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", - "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==", - "dev": true - }, - "socket.io-client": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz", - "integrity": "sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==", - "dev": true, - "requires": { - "backo2": "1.0.2", - "base64-arraybuffer": "0.1.5", - "component-bind": "1.0.0", - "component-emitter": "1.2.1", - "debug": "~4.1.0", - "engine.io-client": "~3.4.0", - "has-binary2": "~1.0.2", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "object-component": "0.0.3", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "socket.io-parser": "~3.3.0", - "to-array": "0.1.4" - }, - "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, "requires": { - "ms": "^2.1.1" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "domain-browser": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-4.22.0.tgz", + "integrity": "sha512-IGBwjF7tNk3cwypFNH/7bfzBcgSCbaMOD3GsaY1AU/JRrnHnYgEM0+9kQt52iZxjNsjBtJYtao146V+f8jFZNw==", "dev": true }, - "socket.io-parser": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz", - "integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==", + "path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "requires": { - "component-emitter": "1.2.1", - "debug": "~3.1.0", - "isarray": "2.0.1" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } - } - } - }, - "socket.io-parser": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.0.tgz", - "integrity": "sha512-/G/VOI+3DBp0+DJKW4KesGnQkQPFmUCbA/oO2QGT6CWxU7hLGWqU3tyuzeSK/dqcyeHsQg1vTe9jiZI8GU9SCQ==", - "dev": true, - "requires": { - "component-emitter": "1.2.1", - "debug": "~4.1.0", - "isarray": "2.0.1" - }, - "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", "dev": true, "requires": { - "ms": "^2.1.1" + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" } }, - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "stream-http": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.2.0.tgz", + "integrity": "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==", + "dev": true, + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "xtend": "^4.0.2" + } + }, + "tty-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", + "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", "dev": true + }, + "util": { + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz", + "integrity": "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "safe-buffer": "^5.1.2", + "which-typed-array": "^1.1.2" + } } } }, - "sockjs-client": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.1.5.tgz", - "integrity": "sha1-G7fA9yIsQPQq3xT0RCy9Eml3GoM=", + "node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", "dev": true, "requires": { - "debug": "^2.6.6", - "eventsource": "0.1.6", - "faye-websocket": "~0.11.0", - "inherits": "^2.0.1", - "json3": "^3.3.2", - "url-parse": "^1.1.8" + "process-on-spawn": "^1.0.0" } }, - "sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", + "node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true + }, + "node-stream-zip": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", + "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==" + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "normalize-url": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", + "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", "dev": true, "requires": { - "is-plain-obj": "^1.0.0" + "prepend-http": "^2.0.0", + "query-string": "^5.0.1", + "sort-keys": "^2.0.0" } }, - "sort-keys-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", - "integrity": "sha1-nLb09OnkgVWmqgZx7dM2/xR5oYg=", + "now-and-later": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", + "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", "dev": true, "requires": { - "sort-keys": "^1.0.0" + "once": "^1.3.2" } }, - "source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "requires": { - "atob": "^2.1.1", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" + "path-key": "^3.0.0" + }, + "dependencies": { + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + } } }, - "source-map-support": { - "version": "0.5.12", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", - "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "nth-check": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz", + "integrity": "sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==", "dev": true, "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "boolbase": "^1.0.0" } }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, - "sparkles": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", - "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", - "dev": true - }, - "spawn-wrap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", - "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", "dev": true, "requires": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", "foreground-child": "^2.0.0", - "is-windows": "^1.0.2", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", "rimraf": "^3.0.0", "signal-exit": "^3.0.2", - "which": "^2.0.1" + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" }, "dependencies": { - "make-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", - "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "rimraf": { + "p-map": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz", - "integrity": "sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", "dev": true, "requires": { - "isexe": "^2.0.0" + "aggregate-error": "^3.0.0" } } } }, - "spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true + }, + "object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", "dev": true, "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" } }, - "spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", "dev": true, "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" } }, - "spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", - "dev": true + "object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", + "dev": true, + "requires": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + } }, - "split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "object.entries": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", + "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", "dev": true, "requires": { - "through": "2" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" } }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", "dev": true, "requires": { - "extend-shallow": "^3.0.0" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" } }, - "sprintf-js": { + "object.groupby": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "srcset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/srcset/-/srcset-1.0.0.tgz", - "integrity": "sha1-pWad4StC87HV6D7QPHEEb8SPQe8=", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", "dev": true, "requires": { - "array-uniq": "^1.0.2", - "number-is-nan": "^1.0.0" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" } }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "object.hasown": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.0.tgz", + "integrity": "sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg==", + "dev": true, "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" } }, - "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", "dev": true, "requires": { - "figgy-pudding": "^3.5.1" + "isobject": "^3.0.1" } }, - "stack-chain": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/stack-chain/-/stack-chain-1.3.7.tgz", - "integrity": "sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU=" + "object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + } }, - "stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } }, - "stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha1-Gsig2Ug4SNFpXkGLbQMaPDzmjjs=", - "dev": true + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } }, - "stat-mode": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-0.2.2.tgz", - "integrity": "sha1-5sgLYjEj19gM8TLOU480YokHJQI=", - "dev": true + "open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } }, - "state-toggle": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", - "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==", + "opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", "dev": true }, - "static-eval": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz", - "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==", + "optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, "requires": { - "escodegen": "^1.8.1" + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" } }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "ordered-read-streams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", + "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", "dev": true, "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "static-module": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/static-module/-/static-module-2.2.5.tgz", - "integrity": "sha512-D8vv82E/Kpmz3TXHKG8PPsCPg+RAX6cbCOyvjM6x04qZtQ47EtJFVwRsdov3n5d6/6ynrOY9XB4JkaZwB2xoRQ==", - "requires": { - "concat-stream": "~1.6.0", - "convert-source-map": "^1.5.1", - "duplexer2": "~0.1.4", - "escodegen": "~1.9.0", - "falafel": "^2.1.0", - "has": "^1.0.1", - "magic-string": "^0.22.4", - "merge-source-map": "1.0.4", - "object-inspect": "~1.4.0", - "quote-stream": "~1.0.2", - "readable-stream": "~2.3.3", - "shallow-copy": "~0.0.1", - "static-eval": "^2.0.0", - "through2": "~2.0.3" - }, - "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } + "readable-stream": "^2.0.1" } }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", "dev": true }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "p-cancelable": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", + "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", "dev": true }, - "stream-browserify": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", - "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "p-event": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-2.3.1.tgz", + "integrity": "sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA==", "dev": true, "requires": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } + "p-timeout": "^2.0.1" } }, - "stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", - "dev": true, - "requires": { - "duplexer": "~0.1.1" - } + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true }, - "stream-each": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", - "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "p-is-promise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "integrity": "sha512-zL7VE4JVS2IFSkR2GQKDSPEVxkoH43/p7oEnwpdCndKYJO0HVeRB7fA8TJwuLOTBREtK0ea8eHaxdwcpob5dmg==", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" + "p-try": "^2.0.0" } }, - "stream-events": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", - "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "stubs": "^3.0.0" + "p-limit": "^2.2.0" } }, - "stream-exhaust": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", - "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", - "dev": true - }, - "stream-http": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", - "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "dev": true, "requires": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" - }, - "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } + "aggregate-error": "^3.0.0" } }, - "stream-shift": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", - "dev": true - }, - "streamfilter": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/streamfilter/-/streamfilter-1.0.7.tgz", - "integrity": "sha512-Gk6KZM+yNA1JpW0KzlZIhjo3EaBJDkYfXtYSbOwNIQ7Zd6006E6+sCFlW1NDvFG/vnXhKmw6TJJgiEQg/8lXfQ==", + "p-timeout": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", + "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", "dev": true, "requires": { - "readable-stream": "^2.0.2" - }, - "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } + "p-finally": "^1.0.0" } }, - "streamifier": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/streamifier/-/streamifier-0.1.1.tgz", - "integrity": "sha1-l+mNj6TRBdYqJpHR3AfoINuN/E8=", + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, - "streamroller": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-2.2.3.tgz", - "integrity": "sha512-AegmvQsscTRhHVO46PhCDerjIpxi7E+d2GxgUDu+nzw/HuLnUdxHWr6WQ+mVn/4iJgMKKFFdiUwFcFRDvcjCtw==", + "package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, "requires": { - "date-format": "^2.1.0", - "debug": "^4.1.1", - "fs-extra": "^8.1.0" - }, - "dependencies": { - "date-format": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", - "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==" - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "^2.1.1" - } - }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - } + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" } }, - "strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", + "package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true }, - "string-argv": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", - "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==" - }, - "string-hash": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", - "integrity": "sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs=", + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "dev": true }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } + "callsites": "^3.0.0" } }, - "string.prototype.matchall": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz", - "integrity": "sha512-N/jp6O5fMf9os0JU3E72Qhf590RSRZU/ungsL/qJUYVTNv7hTG0P/dbPjxINVN9jpscu3nzYwKESU3P3RY5tOg==", + "parse-asn1": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.9.tgz", + "integrity": "sha512-fIYNuZ/HastSb80baGOuPRo1O9cf4baWw5WsAp7dBuUzeTD/BoaG8sVTdlPFksBE2lF21dN+A1AnrpIjSWqHHg==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0", - "has-symbols": "^1.0.1", - "internal-slot": "^1.0.2", - "regexp.prototype.flags": "^1.3.0", - "side-channel": "^1.0.2" + "asn1.js": "^4.10.1", + "browserify-aes": "^1.2.0", + "evp_bytestokey": "^1.0.3", + "pbkdf2": "^3.1.5", + "safe-buffer": "^5.2.1" }, "dependencies": { - "es-abstract": { - "version": "1.17.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", - "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-regex": "^1.1.0", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "is-callable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", - "dev": true - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true } } }, - "string.prototype.trim": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz", - "integrity": "sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=", + "parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.0", - "function-bind": "^1.0.2" + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" } }, - "string.prototype.trimend": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", - "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "dev": true + }, + "parse-semver": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz", + "integrity": "sha512-Eg1OuNntBMH0ojvEKSrvDSnwLmvVuUOSdylH/pSCPNMIspLlweJyIWXCE+k/5hm3cj/EBUYwmWkjhBALNP4LXQ==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" + "semver": "^5.1.0" }, "dependencies": { - "es-abstract": { - "version": "1.17.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", - "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-regex": "^1.1.0", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "is-callable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", - "dev": true - }, - "is-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", - "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true } } }, - "string.prototype.trimleft": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", - "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1" - } - }, - "string.prototype.trimright": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", - "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1" - } - }, - "string.prototype.trimstart": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", - "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" + "parse5": "^6.0.1" }, "dependencies": { - "es-abstract": { - "version": "1.17.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", - "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-regex": "^1.1.0", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "is-callable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", - "dev": true - }, - "is-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", - "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "dev": true } } }, - "string_decoder": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", - "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", - "requires": { - "safe-buffer": "~5.1.0" - } + "path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", "dev": true, "requires": { - "is-utf8": "^0.2.0" + "path-root-regex": "^0.1.0" } }, - "strip-bom-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", - "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=", + "path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", "dev": true }, - "strip-dirs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", - "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", + "path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "requires": { - "is-natural-number": "^4.0.1" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + } } }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", "dev": true }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, - "strip-outer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", - "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.2" - } - }, - "stubs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", - "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=", + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true }, - "style-loader": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz", - "integrity": "sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg==", - "dev": true, - "requires": { - "loader-utils": "^1.1.0", - "schema-utils": "^1.0.0" - } - }, - "styled-jsx": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-3.2.1.tgz", - "integrity": "sha512-gM/WOrWYRpWReivzQqetEGohUc/TJSvUoZ5T/UJxJZIsVIPlRQLnp7R8Oue4q49sI08EBRQjQl2oBL3sfdrw2g==", + "pbkdf2": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.5.tgz", + "integrity": "sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ==", "dev": true, "requires": { - "babel-plugin-syntax-jsx": "6.18.0", - "babel-types": "6.26.0", - "convert-source-map": "1.6.0", - "loader-utils": "1.2.3", - "source-map": "0.7.3", - "string-hash": "1.1.3", - "stylis": "3.5.4", - "stylis-rule-sheet": "0.0.10" + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "ripemd160": "^2.0.3", + "safe-buffer": "^5.2.1", + "sha.js": "^2.4.12", + "to-buffer": "^1.2.1" }, "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true } } }, - "stylis": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz", - "integrity": "sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==", + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", "dev": true }, - "stylis-rule-sheet": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz", - "integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw==", + "picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true }, - "subarg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", - "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", - "dev": true, - "requires": { - "minimist": "^1.1.0" - } + "picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true }, - "sudo-prompt": { - "version": "8.2.5", - "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-8.2.5.tgz", - "integrity": "sha512-rlBo3HU/1zAJUrkY6jNxDOC9eVYliG6nS4JA8u8KAshITd07tafMc/Br7xQwCSseXwJ2iCcHCE8SNWX3q8Z+kw==" + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true }, - "superagent": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", - "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", "dev": true, "requires": { - "component-emitter": "^1.2.0", - "cookiejar": "^2.1.0", - "debug": "^3.1.0", - "extend": "^3.0.0", - "form-data": "^2.3.1", - "formidable": "^1.2.0", - "methods": "^1.1.1", - "mime": "^1.4.1", - "qs": "^6.5.1", - "readable-stream": "^2.3.5" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } + "pinkie": "^2.0.0" } }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "find-up": "^4.0.0" } }, - "sver-compat": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", - "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", + "plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", "dev": true, "requires": { - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" } }, - "svg-inline-loader": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/svg-inline-loader/-/svg-inline-loader-0.8.0.tgz", - "integrity": "sha512-rynplY2eXFrdNomL1FvyTFQlP+dx0WqbzHglmNtA9M4IHRC3no2aPAl3ny9lUpJzFzFMZfWRK5YIclNU+FRePA==", + "possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true + }, + "postinstall-build": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/postinstall-build/-/postinstall-build-5.0.3.tgz", + "integrity": "sha512-vPvPe8TKgp4FLgY3+DfxCE5PIfoXBK2lyLfNCxsRbDsV6vS4oU5RG/IWxrblMn6heagbnMED3MemUQllQ2bQUg==", + "dev": true + }, + "prebuild-install": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", "dev": true, + "optional": true, "requires": { - "loader-utils": "^0.2.11", - "object-assign": "^4.0.1", - "simple-html-tokenizer": "^0.1.1" + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" }, "dependencies": { - "big.js": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", - "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", - "dev": true - }, - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "dev": true - }, - "loader-utils": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", - "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, + "optional": true, "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0", - "object-assign": "^4.0.1" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } } } }, - "svg-inline-react": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/svg-inline-react/-/svg-inline-react-3.1.0.tgz", - "integrity": "sha512-c39AIRQOUXLMD8fQ2rHmK1GOSO3tVuZk61bAXqIT05uhhm3z4VtQFITQSwyhL0WA2uxoJAIhPd2YV0CYQOolSA==", + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", + "dev": true + }, + "prettier": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.2.tgz", + "integrity": "sha512-5xJQIPT8BraI7ZnaDwSbu5zLrB6vvi8hVV58yHQ+QK64qrY40dULy0HSRlQ2/2IdzeBpjhDkqdcFBnFeDEMVdg==", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", "dev": true, "requires": { - "prop-types": "^15.5.0" + "fromentries": "^1.2.0" } }, - "svg-path-bounding-box": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/svg-path-bounding-box/-/svg-path-bounding-box-1.0.4.tgz", - "integrity": "sha1-7XPfODyLR4abZQjwWPV0j4gzwHA=", + "prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", "dev": true, "requires": { - "svgpath": "^2.0.0" + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" } }, - "svg-to-pdfkit": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/svg-to-pdfkit/-/svg-to-pdfkit-0.1.8.tgz", - "integrity": "sha512-QItiGZBy5TstGy+q8mjQTMGRlDDOARXLxH+sgVm1n/LYeo0zFcQlcCh8m4zi8QxctrxB9Kue/lStc/RD5iLadQ==", + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, "requires": { - "pdfkit": ">=0.8.1" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, - "svgpath": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/svgpath/-/svgpath-2.2.2.tgz", - "integrity": "sha512-7cXFbkZvPkZpKLC+3QIfyUd3/Un/CvJONjTD3Gz5qLuEa73StPOt8kZjTi9apxO6zwCaza0bPNnmzTyrQ4qQlw==", - "dev": true + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true }, - "sync-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-6.1.0.tgz", - "integrity": "sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==", + "qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", "dev": true, "requires": { - "http-response-object": "^3.0.1", - "sync-rpc": "^1.2.1", - "then-request": "^6.0.0" + "side-channel": "^1.1.0" } }, - "sync-rpc": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/sync-rpc/-/sync-rpc-1.3.6.tgz", - "integrity": "sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==", + "query-string": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", + "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", "dev": true, "requires": { - "get-port": "^3.1.0" + "decode-uri-component": "^0.2.0", + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" } }, - "table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, "requires": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" - }, - "dependencies": { - "ajv": { - "version": "6.12.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", - "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - } + "safe-buffer": "^5.1.0" } }, - "tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "dev": true - }, - "tar": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", - "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", "dev": true, "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - }, - "dependencies": { - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - } + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" } }, - "tar-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", - "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, + "optional": true, "requires": { - "bl": "^1.0.0", - "buffer-alloc": "^1.2.0", - "end-of-stream": "^1.0.0", - "fs-constants": "^1.0.0", - "readable-stream": "^2.3.0", - "to-buffer": "^1.1.1", - "xtend": "^4.0.0" + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" }, "dependencies": { - "process-nextick-args": { + "strip-json-comments": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } + "optional": true } } }, - "tas-client": { - "version": "0.0.950", - "resolved": "https://registry.npmjs.org/tas-client/-/tas-client-0.0.950.tgz", - "integrity": "sha512-AvCNjvfouxJyKln+TsobOBO5KmXklL9+FlxrEPlIgaixy1TxCC2v2Vs/MflCiyHlGl+BeIStP4oAVPqo5c0pIA==", - "requires": { - "axios": "^0.19.0" - } - }, - "tcp-port-used": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.1.tgz", - "integrity": "sha512-rwi5xJeU6utXoEIiMvVBMc9eJ2/ofzB+7nLOdnZuFTmNCLqRiQh2sMG9MqCxHU/69VC/Fwp5dV9306Qd54ll1Q==", - "requires": { - "debug": "4.1.0", - "is2": "2.0.1" - }, - "dependencies": { - "debug": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", - "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", - "requires": { - "ms": "^2.1.1" - } - } - } + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true }, - "teeny-request": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-6.0.1.tgz", - "integrity": "sha512-TAK0c9a00ELOqLrZ49cFxvPVogMUFaWY8dUsQc/0CuQPGF+BOxOQzXfE413BAk2kLomwNplvdtMpeaeGWmoc2g==", + "read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", "dev": true, "requires": { - "http-proxy-agent": "^4.0.0", - "https-proxy-agent": "^4.0.0", - "node-fetch": "^2.2.0", - "stream-events": "^1.0.5", - "uuid": "^3.3.2" - }, - "dependencies": { - "agent-base": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", - "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", - "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", - "dev": true, - "requires": { - "agent-base": "5", - "debug": "4" - }, - "dependencies": { - "agent-base": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", - "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", - "dev": true - } - } - } + "mute-stream": "~0.0.4" } }, - "terser-webpack-plugin": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-3.1.0.tgz", - "integrity": "sha512-cjdZte66fYkZ65rQ2oJfrdCAkkhJA7YLYk5eGOcGCSGlq0ieZupRdjedSQXYknMPo2IveQL+tPdrxUkERENCFA==", + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "requires": { - "cacache": "^15.0.5", - "find-cache-dir": "^3.3.1", - "jest-worker": "^26.2.1", - "p-limit": "^3.0.2", - "schema-utils": "^2.6.6", - "serialize-javascript": "^4.0.0", - "source-map": "^0.6.1", - "terser": "^4.8.0", - "webpack-sources": "^1.4.3" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" }, "dependencies": { - "ajv": { - "version": "6.12.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", - "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "cacache": { - "version": "15.0.5", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.5.tgz", - "integrity": "sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==", - "dev": true, - "requires": { - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.0", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" - } - }, - "chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "minipass": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", - "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "minizlib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz", - "integrity": "sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==", - "dev": true, - "requires": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "p-limit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", - "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - }, - "dependencies": { - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - } - } - }, - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "ssri": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", - "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", - "dev": true, - "requires": { - "minipass": "^3.1.1" - } - }, - "tar": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.2.tgz", - "integrity": "sha512-Glo3jkRtPcvpDlAs/0+hozav78yoXKFr+c4wgw62NNMO3oo4AaJdCo21Uu7lcwr55h39W2XD1LMERc64wtbItg==", - "dev": true, - "requires": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.0", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - } - }, - "terser": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", - "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", - "dev": true, - "requires": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - } - }, - "webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" + "safe-buffer": "~5.1.0" } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true } } }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" + "picomatch": "^2.2.1" } }, - "text-hex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + "rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "requires": { + "resolve": "^1.20.0" + } }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true + "reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" }, - "then-request": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.2.tgz", - "integrity": "sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==", + "regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "dev": true, "requires": { - "@types/concat-stream": "^1.6.0", - "@types/form-data": "0.0.33", - "@types/node": "^8.0.0", - "@types/qs": "^6.2.31", - "caseless": "~0.12.0", - "concat-stream": "^1.6.0", - "form-data": "^2.2.0", - "http-basic": "^8.1.1", - "http-response-object": "^3.0.1", - "promise": "^8.0.0", - "qs": "^6.4.0" - }, - "dependencies": { - "@types/form-data": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", - "integrity": "sha1-yayFsqX9GENbjIXZ7LUObWyJP/g=", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/node": { - "version": "8.10.58", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.58.tgz", - "integrity": "sha512-NNcUk/rAdR7Pie7WiA5NHp345dTkD62qaxqscQXVIjCjog/ZXsrG8Wo7dZMZAzE7PSpA+qR2S3TYTeFCKuBFxQ==", - "dev": true - }, - "promise": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.0.3.tgz", - "integrity": "sha512-HeRDUL1RJiLhyA0/grn+PTShlBAcLuh/1BJGtrvjwbvRDCTLLMEz9rOGCV+R3vHY4MixIuoMEd9Yq/XvsTPcjw==", - "dev": true, - "requires": { - "asap": "~2.0.6" - } - } + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" } }, - "thread-loader": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/thread-loader/-/thread-loader-2.1.3.tgz", - "integrity": "sha512-wNrVKH2Lcf8ZrWxDF/khdlLlsTMczdcwPA9VEK4c2exlEPynYWxi9op3nPTo5lAnDIkE0rQEB3VBP+4Zncc9Hg==", + "release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "requires": { + "es6-error": "^4.0.1" + } + }, + "remove-bom-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", + "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5", + "is-utf8": "^0.2.1" + } + }, + "remove-bom-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", + "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", "dev": true, "requires": { - "loader-runner": "^2.3.1", - "loader-utils": "^1.1.0", - "neo-async": "^2.6.0" + "remove-bom-buffer": "^3.0.0", + "safe-buffer": "^5.1.0", + "through2": "^2.0.3" } }, - "throttleit": { + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "replace-ext": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", - "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=" + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + "replace-homedir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-2.0.0.tgz", + "integrity": "sha512-bgEuQQ/BHW0XkkJtawzrfzHFSN70f/3cNOiHa2QsYxqrjaC30X1k74FJ6xswVBP0sr0SpGIdVFuPwfrYziVeyw==", + "dev": true }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "require-in-the-middle": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.2.0.tgz", + "integrity": "sha512-3TLx5TGyAY6AOqLBoXmHkNql0HIf2RGbuMgCDT2WO/uGVAPJs6h7Kl+bN6TIZGd9bWhWPwnDnTHGtW8Iu77sdw==", "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "debug": "^4.1.1", + "module-details-from-path": "^1.0.3", + "resolve": "^1.22.1" }, "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { - "safe-buffer": "~5.1.0" + "ms": "2.1.2" } } } }, - "through2-filter": { + "resolve": { + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", + "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", + "requires": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-cwd": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", - "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, "requires": { - "through2": "~2.0.0", - "xtend": "~4.0.0" + "resolve-from": "^5.0.0" } }, - "time-stamp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", - "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", - "dev": true + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", + "dev": true, + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + } }, - "timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, - "timers-browserify": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", - "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", + "resolve-options": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", + "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", "dev": true, "requires": { - "setimmediate": "^1.0.4" + "value-or-function": "^3.0.0" } }, - "timers-ext": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", - "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", "dev": true, "requires": { - "es5-ext": "~0.10.46", - "next-tick": "1" + "lowercase-keys": "^1.0.0" } }, - "tiny-inflate": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", - "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==" - }, - "tinycolor2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", - "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=", - "dev": true - }, - "tinyqueue": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-1.2.3.tgz", - "integrity": "sha512-Qz9RgWuO9l8lT+Y9xvbzhPT2efIUIFd69N7eF7tJ9lnQl0iLj1M7peK7IoUGZL9DJHw9XftqLreccfxcQgYLxA==", + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true }, - "tmp": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", - "integrity": "sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=", + "rewiremock": { + "version": "3.14.6", + "resolved": "https://registry.npmjs.org/rewiremock/-/rewiremock-3.14.6.tgz", + "integrity": "sha512-hjpS7iQUTVVh/IHV4GE1ypg4IzlgVc34gxZBarwwVrKfnjlyqHJuQdsia6Ac7m4f4k/zxxA3tX285MOstdysRQ==", + "dev": true, "requires": { - "os-tmpdir": "~1.0.1" + "babel-runtime": "^6.26.0", + "compare-module-exports": "^2.1.0", + "node-libs-browser": "^2.1.0", + "path-parse": "^1.0.5", + "wipe-node-cache": "^2.1.2", + "wipe-webpack-cache": "^2.1.0" } }, - "to-absolute-glob": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", - "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { - "is-absolute": "^1.0.0", - "is-negated-glob": "^1.0.0" + "glob": "^7.1.3" } }, - "to-array": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", - "dev": true + "ripemd160": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.3.tgz", + "integrity": "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==", + "dev": true, + "requires": { + "hash-base": "^3.1.2", + "inherits": "^2.0.4" + }, + "dependencies": { + "hash-base": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.2.tgz", + "integrity": "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } }, - "to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", - "dev": true + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } }, - "to-buffer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", - "dev": true + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "requires": { + "tslib": "^1.9.0" + } }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true + "rxjs-compat": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs-compat/-/rxjs-compat-6.6.7.tgz", + "integrity": "sha512-szN4fK+TqBPOFBcBcsR0g2cmTTUF/vaFEOZNuSdfU8/pGFnNmmn2u8SystYXG1QMrjOPBc6XTKHMVfENDf6hHw==" }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", "dev": true, "requires": { - "kind-of": "^3.0.2" + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" }, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true } } }, - "to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==" + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "requires": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "seek-bzip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", + "integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==", "dev": true, "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" + "commander": "^2.8.1" + } + }, + "semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "requires": { + "lru-cache": "^6.0.0" } }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "semver-greatest-satisfied-range": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-2.0.0.tgz", + "integrity": "sha512-lH3f6kMbwyANB7HuOWRMlLCa2itaCrZJ+SAqqkSZrZKO/cAsk2EOyaKHUtNkVLFyFW9pct22SFesFp3Z7zpA0g==", "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "sver": "^1.8.3" } }, - "to-through": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", - "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", + "serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "requires": { - "through2": "^2.0.3" + "randombytes": "^2.1.0" } }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, - "topojson-client": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", - "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==", + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dev": true, "requires": { - "commander": "2" + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" } }, - "toposort": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz", - "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=", - "dev": true - }, - "touch": { + "set-function-name": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/touch/-/touch-2.0.2.tgz", - "integrity": "sha512-qjNtvsFXTRq7IuMLweVgFxmEuQ6gLbRs2jQxL80TtZ31dEKWYIxRXquij6w6VimyDek5hD3PytljHmEtAs2u0A==", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, "requires": { - "nopt": "~1.0.10" + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" } }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "dev": true, "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" }, "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true } } }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", "dev": true, "requires": { - "punycode": "^2.1.0" + "kind-of": "^6.0.2" } }, - "transform-loader": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/transform-loader/-/transform-loader-0.2.4.tgz", - "integrity": "sha1-5ch4d7qW1R0/IlNoWHtG4ibRzsk=", + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { - "loader-utils": "^1.0.2" + "shebang-regex": "^1.0.0" } }, - "transformation-matrix": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/transformation-matrix/-/transformation-matrix-2.0.5.tgz", - "integrity": "sha512-S6L67Z8V3WEyPm2/zDh3I3bO0OQwv88dh7IY2dIOVBfIZJ4WQGdEKOsh7phTgYkvfAmHRxfOPNt1ixN/zR6D/A==", + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, - "trash": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/trash/-/trash-6.1.1.tgz", - "integrity": "sha512-4i56lCmz2RG6WZN018hf4L75L5HboaFuKkHx3wDG/ihevI99e0OgFyl8w6G4ioqBm62V4EJqCy5xw3vQSNXU8A==", + "shimmer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" + }, + "shortid": { + "version": "2.2.17", + "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.17.tgz", + "integrity": "sha512-GpbM3gLF1UUXZvQw6MCyulHkWbRseNO4cyBEZresZRorwl1+SLu1ZdqgVtuwqz8mB6RpwPkm541mYSqrKyJSaA==", "dev": true, "requires": { - "@stroncium/procfs": "^1.0.0", - "globby": "^7.1.1", - "is-path-inside": "^3.0.2", - "make-dir": "^3.0.0", - "move-file": "^1.1.0", - "p-map": "^3.0.0", - "p-try": "^2.2.0", - "uuid": "^3.3.2", - "xdg-trashdir": "^2.1.1" - }, - "dependencies": { - "is-path-inside": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", - "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==", - "dev": true - }, - "make-dir": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", - "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } + "nanoid": "^3.3.8" } }, - "tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==" - }, - "trim": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", - "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=", - "dev": true + "side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + } }, - "trim-repeated": { + "side-channel-list": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", - "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "dev": true, "requires": { - "escape-string-regexp": "^1.0.2" + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" } }, - "trim-right": { + "side-channel-map": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", - "dev": true - }, - "trim-trailing-lines": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.3.tgz", - "integrity": "sha512-4ku0mmjXifQcTVfYDfR5lpgV7zVqPg6zV9rdZmwOPqq0+Zq19xDqEgagqVbc4pOOShbncuAOIs59R3+3gcF3ZA==", - "dev": true - }, - "triple-beam": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", - "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" - }, - "trough": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", - "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", - "dev": true - }, - "truncate-utf8-bytes": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", - "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, "requires": { - "utf8-byte-length": "^1.0.1" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" } }, - "tryer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", - "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==", - "dev": true - }, - "ts-loader": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-5.4.5.tgz", - "integrity": "sha512-XYsjfnRQCBum9AMRZpk2rTYSVpdZBpZK+kDh0TeT3kxmQNBDVIeUjdPjY5RZry4eIAb8XHc4gYSUiUWPYvzSRw==", + "side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, "requires": { - "chalk": "^2.3.0", - "enhanced-resolve": "^4.0.0", - "loader-utils": "^1.0.2", - "micromatch": "^3.1.4", - "semver": "^5.0.1" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" } }, - "ts-mock-imports": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-mock-imports/-/ts-mock-imports-1.3.0.tgz", - "integrity": "sha512-cCrVcRYsp84eDvPict0ZZD/D7ppQ0/JSx4ve6aEU8DjlsaWRJWV6ADMovp2sCuh6pZcduLFoIYhKTDU2LARo7Q==", + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, - "ts-mockito": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/ts-mockito/-/ts-mockito-2.5.0.tgz", - "integrity": "sha512-b3qUeMfghRq5k5jw3xNJcnU9RKhqKnRn0k9v9QkN+YpuawrFuMIiGwzFZCpdi5MHy26o7YPnK8gag2awURl3nA==", + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", "dev": true, - "requires": { - "lodash": "^4.17.5" - } + "optional": true }, - "ts-node": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.3.0.tgz", - "integrity": "sha512-dyNS/RqyVTDcmNM4NIBAeDMpsAdaQ+ojdf0GOLqE6nwJOgzEkdRNzJywhDfwnuvB10oa6NLVG1rUJQCpRN7qoQ==", + "simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", "dev": true, + "optional": true, "requires": { - "arg": "^4.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.6", - "yn": "^3.0.0" + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" }, "dependencies": { - "diff": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", - "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", - "dev": true + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "optional": true, + "requires": { + "mimic-response": "^3.1.0" + } + }, + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "optional": true } } }, - "tsconfig-paths": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.8.0.tgz", - "integrity": "sha512-zZEYFo4sjORK8W58ENkRn9s+HmQFkkwydDG7My5s/fnfr2YYCaiyXe/HBUcIgU8epEKOXwiahOO+KZYjiXlWyQ==", + "sinon": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-18.0.0.tgz", + "integrity": "sha512-+dXDXzD1sBO6HlmZDd7mXZCR/y5ECiEiGCBSGuFD/kZ0bDTofPYc6JaeGmPSF+1j1MejGUWkORbYOLDyvqCWpA==", "dev": true, "requires": { - "@types/json5": "^0.0.29", - "deepmerge": "^2.0.1", - "json5": "^1.0.1", - "minimist": "^1.2.0", - "strip-bom": "^3.0.0" + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.2.0", + "nise": "^6.0.0", + "supports-color": "^7" }, "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "diff": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", + "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "minimist": "^1.2.0" + "has-flag": "^4.0.0" } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true } } }, - "tsconfig-paths-webpack-plugin": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-3.2.0.tgz", - "integrity": "sha512-S/gOOPOkV8rIL4LurZ1vUdYCVgo15iX9ZMJ6wx6w2OgcpT/G4wMyHB6WM+xheSqGMrWKuxFul+aXpCju3wmj/g==", + "sirv": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz", + "integrity": "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==", "dev": true, "requires": { - "chalk": "^2.3.0", - "enhanced-resolve": "^4.0.0", - "tsconfig-paths": "^3.4.0" + "@polka/url": "^1.0.0-next.20", + "mrmime": "^1.0.0", + "totalist": "^1.0.0" } }, - "tslib": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", - "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true }, - "tslint": { - "version": "5.20.1", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", - "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", + "sort-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", + "integrity": "sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "builtin-modules": "^1.1.1", - "chalk": "^2.3.0", - "commander": "^2.12.1", - "diff": "^4.0.1", - "glob": "^7.1.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "resolve": "^1.3.2", - "semver": "^5.3.0", - "tslib": "^1.8.0", - "tsutils": "^2.29.0" + "is-plain-obj": "^1.0.0" + } + }, + "sort-keys-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", + "integrity": "sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==", + "dev": true, + "requires": { + "sort-keys": "^1.0.0" }, "dependencies": { - "tsutils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", - "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", "dev": true, "requires": { - "tslib": "^1.8.1" + "is-plain-obj": "^1.0.0" } } } }, - "tslint-config-prettier": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/tslint-config-prettier/-/tslint-config-prettier-1.18.0.tgz", - "integrity": "sha512-xPw9PgNPLG3iKRxmK7DWr+Ea/SzrvfHtjFt5LBl61gk2UBG/DB9kCXRjv+xyIU1rUtnayLeMUVJBcMX8Z17nDg==", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, - "tslint-eslint-rules": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/tslint-eslint-rules/-/tslint-eslint-rules-5.4.0.tgz", - "integrity": "sha512-WlSXE+J2vY/VPgIcqQuijMQiel+UtmXS+4nvK4ZzlDiqBfXse8FAvkNnTcYhnQyOTW5KFM+uRRGXxYhFpuBc6w==", + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "requires": { - "doctrine": "0.7.2", - "tslib": "1.9.0", - "tsutils": "^3.0.0" - }, - "dependencies": { - "tslib": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.0.tgz", - "integrity": "sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ==", - "dev": true - }, - "tsutils": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.14.0.tgz", - "integrity": "sha512-SmzGbB0l+8I0QwsPgjooFRaRvHLBLNYM8SeQ0k6rtNDru5sCGeLJcZdwilNndN+GysuFjF5EIYgN8GfFG6UeUw==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - } + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "tslint-microsoft-contrib": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/tslint-microsoft-contrib/-/tslint-microsoft-contrib-5.2.1.tgz", - "integrity": "sha512-PDYjvpo0gN9IfMULwKk0KpVOPMhU6cNoT9VwCOLeDl/QS8v8W2yspRpFFuUS7/c5EIH/n8ApMi8TxJAz1tfFUA==", + "sparkles": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-2.1.0.tgz", + "integrity": "sha512-r7iW1bDw8R/cFifrD3JnQJX0K1jqT0kprL48BiBpLZLJPmAm34zsVBsK5lc7HirZYZqMW65dOXZgbAGt/I6frg==", + "dev": true + }, + "spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", "dev": true, "requires": { - "tsutils": "^2.27.2 <2.29.0" - }, - "dependencies": { - "tsutils": { - "version": "2.28.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.28.0.tgz", - "integrity": "sha512-bh5nAtW0tuhvOJnx1GLRn5ScraRLICGyJV5wJhtRWOLsxW70Kk5tZtpK3O/hW6LDnqKS9mlUMPZj9fEMJ0gxqA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - } + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" } }, - "tslint-plugin-prettier": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslint-plugin-prettier/-/tslint-plugin-prettier-2.3.0.tgz", - "integrity": "sha512-F9e4K03yc9xuvv+A0v1EmjcnDwpz8SpCD8HzqSDe0eyg34cBinwn9JjmnnRrNAs4HdleRQj7qijp+P/JTxt4vA==", + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "stack-chain": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/stack-chain/-/stack-chain-1.3.7.tgz", + "integrity": "sha512-D8cWtWVdIe/jBA7v5p5Hwl5yOSOrmZPWDPe2KxQ5UAGD+nxbxU0lKXA4h85Ta6+qgdKVL3vUxsbIZjc1kBG7ug==" + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" + }, + "stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "dev": true + }, + "stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", "dev": true, "requires": { - "eslint-plugin-prettier": "^2.2.0", - "lines-and-columns": "^1.1.6", - "tslib": "^1.7.1" - }, - "dependencies": { - "eslint-plugin-prettier": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-2.7.0.tgz", - "integrity": "sha512-CStQYJgALoQBw3FsBzH0VOVDRnJ/ZimUlpLm226U8qgqYJfPOY/CPK6wyRInMxh73HSKg5wyRwdS4BVYYHwokA==", - "dev": true, - "requires": { - "fast-diff": "^1.1.1", - "jest-docblock": "^21.0.0" - } - } + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" } }, - "tsutils": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", - "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "stream-composer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz", + "integrity": "sha512-bnBselmwfX5K10AH6L4c8+S5lgZMWI7ZYrz2rvYjCPB2DIMC4Ig8OpxGpNJSxRZ58oti7y1IcNvjBAz9vW5m4w==", "dev": true, "requires": { - "tslib": "^1.8.1" + "streamx": "^2.13.2" } }, - "tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", - "dev": true - }, - "tunnel": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.4.tgz", - "integrity": "sha1-LTeFoVjBdMmhbcLARuxfxfF0IhM=", + "stream-exhaust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", "dev": true }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, "requires": { - "safe-buffer": "^5.0.1" + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" } }, - "tv4": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.3.0.tgz", - "integrity": "sha1-0CDIRvrdUMhVq7JeuuzGj8EPeWM=", + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", "dev": true }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" - }, - "type": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/type/-/type-1.0.1.tgz", - "integrity": "sha512-MAM5dBMJCJNKs9E7JXo4CXRAansRfG0nlJxW7Wf6GZzSOvH31zClSaHdIMWLehe/EGMBkqeC55rrkaOr5Oo7Nw==" - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "streamx": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", + "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==", + "dev": true, "requires": { - "prelude-ls": "~1.1.2" + "bare-events": "^2.2.0", + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" } }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==", "dev": true }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typed-react-markdown": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/typed-react-markdown/-/typed-react-markdown-0.1.0.tgz", - "integrity": "sha1-HDra9CvB8NjGoJsKyAhfNt8KNn8=", + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "requires": { - "@types/react": "^0.14.44" + "safe-buffer": "~5.2.0" }, "dependencies": { - "@types/react": { - "version": "0.14.57", - "resolved": "https://registry.npmjs.org/@types/react/-/react-0.14.57.tgz", - "integrity": "sha1-GHioZU+v3R04G4RXKStkM0mMW2I=", + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true } } }, - "typed-rest-client": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.2.0.tgz", - "integrity": "sha512-FrUshzZ1yxH8YwGR29PWWnfksLEILbWJydU7zfIRkyH7kAEzB62uMAl2WY6EyolWpLpVHeJGgQm45/MaruaHpw==", + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { - "tunnel": "0.0.4", - "underscore": "1.8.3" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } }, - "typed-styles": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz", - "integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q==", - "dev": true - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { - "is-typedarray": "^1.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } }, - "typemoq": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/typemoq/-/typemoq-2.1.0.tgz", - "integrity": "sha512-DtRNLb7x8yCTv/KHlwes+NI+aGb4Vl1iPC63Hhtcvk1DpxSAZzKWQv0RQFY0jX2Uqj0SDBNl8Na4e6MV6TNDgw==", + "string.prototype.matchall": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", + "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", "dev": true, "requires": { - "circular-json": "^0.3.1", - "lodash": "^4.17.4", - "postinstall-build": "^5.0.1" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.1", + "side-channel": "^1.0.4" } }, - "typescript": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", - "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", - "dev": true - }, - "typescript-char": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/typescript-char/-/typescript-char-0.0.0.tgz", - "integrity": "sha1-VY/tpzfHZaYQtzfu+7F3Xum8jas=" - }, - "typescript-formatter": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/typescript-formatter/-/typescript-formatter-7.2.2.tgz", - "integrity": "sha512-V7vfI9XArVhriOTYHPzMU2WUnm5IMdu9X/CPxs8mIMGxmTBFpDABlbkBka64PZJ9/xgQeRpK8KzzAG4MPzxBDQ==", + "string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", "dev": true, "requires": { - "commandpost": "^1.0.0", - "editorconfig": "^0.15.0" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" } }, - "typestyle": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/typestyle/-/typestyle-2.1.0.tgz", - "integrity": "sha512-6uCYPdG4xWLeEcl9O0GtNFnNGhami+irKiLsXSuvWHC/aTS7wdj49WeikWAKN+xHN3b1hm+9v0svwwgSBhCsNA==", + "string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", "dev": true, "requires": { - "csstype": "2.6.9", - "free-style": "3.1.0" - }, - "dependencies": { - "csstype": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.9.tgz", - "integrity": "sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q==", - "dev": true - } + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" } }, - "ua-parser-js": { - "version": "0.7.20", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.20.tgz", - "integrity": "sha512-8OaIKfzL5cpx8eCMAhhvTlft8GYF8b2eQr6JkCyVdrgjcytyOmPCXrqXFcUnhonRpLlh5yxEZVohm6mzaowUOw==", - "dev": true - }, - "uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "dev": true - }, - "uglify-js": { - "version": "3.4.10", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", - "integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==", + "string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dev": true, "requires": { - "commander": "~2.19.0", - "source-map": "~0.6.1" - }, - "dependencies": { - "commander": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", - "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", - "dev": true - } + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" } }, - "uint64be": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uint64be/-/uint64be-1.0.1.tgz", - "integrity": "sha1-H3FUIC8qG4rzU4cd2mUb80zpPpU=" - }, - "unbzip2-stream": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.3.3.tgz", - "integrity": "sha512-fUlAF7U9Ah1Q6EieQ4x4zLNejrRvDWUYmxXUpN3uziFYCHapjWFaCAnreY9bGgxzaMCFAPPpYNng57CypwJVhg==", + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "buffer": "^5.2.1", - "through": "^2.3.8" + "ansi-regex": "^5.0.1" } }, - "unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", - "dev": true - }, - "underscore": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", - "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" - }, - "undertaker": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.1.tgz", - "integrity": "sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA==", + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "bach": "^1.0.0", - "collection-map": "^1.0.0", - "es6-weak-map": "^2.0.1", - "last-run": "^1.1.0", - "object.defaults": "^1.0.0", - "object.reduce": "^1.0.0", - "undertaker-registry": "^1.0.0" + "ansi-regex": "^5.0.1" } }, - "undertaker-registry": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", - "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", - "dev": true - }, - "unherit": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", - "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==", + "strip-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", + "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", "dev": true, "requires": { - "inherits": "^2.0.0", - "xtend": "^4.0.0" + "is-natural-number": "^4.0.1" } }, - "unicode": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/unicode/-/unicode-10.0.0.tgz", - "integrity": "sha1-5dUcHbk7bHGguHngsMSvfm/faI4=" - }, - "unicode-canonical-property-names-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", - "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true }, - "unicode-match-property-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", - "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", - "dev": true, - "requires": { - "unicode-canonical-property-names-ecmascript": "^1.0.4", - "unicode-property-aliases-ecmascript": "^1.0.4" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz", - "integrity": "sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==", + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, - "unicode-properties": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.3.1.tgz", - "integrity": "sha512-nIV3Tf3LcUEZttY/2g4ZJtGXhWwSkuLL+rCu0DIAMbjyVPj+8j5gNVz4T/sVbnQybIsd5SFGkPKg/756OY6jlA==", + "strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, "requires": { - "base64-js": "^1.3.0", - "unicode-trie": "^2.0.0" - }, - "dependencies": { - "unicode-trie": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", - "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", - "requires": { - "pako": "^0.2.5", - "tiny-inflate": "^1.0.0" - } - } + "escape-string-regexp": "^1.0.2" } }, - "unicode-property-aliases-ecmascript": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz", - "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==", - "dev": true - }, - "unicode-trie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-0.3.1.tgz", - "integrity": "sha1-1nHd3YkQGgi6w3tqUWEBBgIFIIU=", - "requires": { - "pako": "^0.2.5", - "tiny-inflate": "^1.0.0" - } + "sudo-prompt": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", + "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==" }, - "unified": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/unified/-/unified-6.2.0.tgz", - "integrity": "sha512-1k+KPhlVtqmG99RaTbAv/usu85fcSRu3wY8X+vnsEhIxNP5VbVIDiXnLqyKIG+UMdyTg0ZX9EI6k2AfjJkHPtA==", + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "bail": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^1.1.0", - "trough": "^1.0.0", - "vfile": "^2.0.0", - "x-is-string": "^0.1.0" + "has-flag": "^3.0.0" } }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, + "sver": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/sver/-/sver-1.8.4.tgz", + "integrity": "sha512-71o1zfzyawLfIWBOmw8brleKyvnbn73oVHNCsu51uPMz/HWiKkkXsI31JjHW5zqXEqnPYkIiHd8ZmL7FCimLEA==", "dev": true, "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "optional": true + } } }, - "uniq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", - "dev": true - }, - "uniqid": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/uniqid/-/uniqid-5.0.3.tgz", - "integrity": "sha512-R2qx3X/LYWSdGRaluio4dYrPXAJACTqyUjuyXHoJLBUOIfmMcnYOyY2d6Y4clZcIz5lK6ZaI0Zzmm0cPfsIqzQ==", + "tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "dev": true }, - "unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", "dev": true, + "optional": true, "requires": { - "unique-slug": "^2.0.0" + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + }, + "dependencies": { + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "optional": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "optional": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "optional": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "optional": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + } } }, - "unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", "dev": true, "requires": { - "imurmurhash": "^0.1.4" + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" } }, - "unique-stream": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", - "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", + "tas-client": { + "version": "0.2.33", + "resolved": "https://registry.npmjs.org/tas-client/-/tas-client-0.2.33.tgz", + "integrity": "sha512-V+uqV66BOQnWxvI6HjDnE4VkInmYZUQ4dgB7gzaDyFyFSK1i1nF/j7DpS9UbQAgV9NaF1XpcyuavnM1qOeiEIg==" + }, + "teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", "dev": true, "requires": { - "json-stable-stringify-without-jsonify": "^1.0.1", - "through2-filter": "^3.0.0" + "streamx": "^2.12.5" } }, - "unist-util-is": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-3.0.0.tgz", - "integrity": "sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==", - "dev": true - }, - "unist-util-remove-position": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.4.tgz", - "integrity": "sha512-tLqd653ArxJIPnKII6LMZwH+mb5q+n/GtXQZo6S6csPRs5zB0u79Yw8ouR3wTw8wxvdJFhpP6Y7jorWdCgLO0A==", + "terser": { + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", "dev": true, "requires": { - "unist-util-visit": "^1.1.0" + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" } }, - "unist-util-stringify-position": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz", - "integrity": "sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ==", - "dev": true - }, - "unist-util-visit": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", - "integrity": "sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==", + "terser-webpack-plugin": { + "version": "5.3.17", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.17.tgz", + "integrity": "sha512-YR7PtUp6GMU91BgSJmlaX/rS2lGDbAF7D+Wtq7hRO+MiljNmodYvqslzCFiYVAgW+Qoaaia/QUIP4lGXufjdZw==", "dev": true, "requires": { - "unist-util-visit-parents": "^2.0.0" + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" }, "dependencies": { - "unist-util-visit-parents": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz", - "integrity": "sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==", + "ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "requires": { - "unist-util-is": "^3.0.0" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" } - } - } - }, - "unist-util-visit-parents": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-1.1.2.tgz", - "integrity": "sha512-yvo+MMLjEwdc3RhhPYSximset7rwjMrdt9E41Smmvg25UQIenzrN83cRnF1JMzoMi9zZOQeYXHSDf7p+IQkW3Q==", - "dev": true - }, - "units-css": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/units-css/-/units-css-0.4.0.tgz", - "integrity": "sha1-1iKGU6UZg9fBb/KPi53Dsf/tOgc=", - "dev": true, - "requires": { - "isnumeric": "^0.2.0", - "viewport-dimensions": "^0.2.0" - } - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } + "fast-deep-equal": "^3.1.3" } }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true + }, + "schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + } } } }, - "untildify": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.3.tgz", - "integrity": "sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==" - }, - "upath": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", - "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==", - "dev": true - }, - "upper-case": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", - "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", - "dev": true - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "requires": { - "punycode": "^2.1.0" - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" }, "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true + "minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } } } }, - "url-join": { + "text-decoder": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-1.1.0.tgz", - "integrity": "sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg=", - "dev": true - }, - "url-loader": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-1.1.2.tgz", - "integrity": "sha512-dXHkKmw8FhPqu8asTc1puBfe3TehOCo2+RmOOev5suNCIYBcT626kxiWg1NBVkwc4rO8BGa7gP70W7VXuqHrjg==", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.0.tgz", + "integrity": "sha512-TmLJNj6UgX8xcUZo4UDStGQtDiTzF7BzWlzn9g7UWrjkpHr5uJTK1ld16wZ3LXb2vb6jH8qU89dW5whuMdXYdw==", "dev": true, "requires": { - "loader-utils": "^1.1.0", - "mime": "^2.0.3", - "schema-utils": "^1.0.0" - }, - "dependencies": { - "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", - "dev": true - } - } - }, - "url-parse": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", - "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "requires": { - "prepend-http": "^2.0.0" + "b4a": "^1.6.4" } }, - "url-to-options": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", - "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=", - "dev": true - }, - "urlgrey": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/urlgrey/-/urlgrey-0.4.4.tgz", - "integrity": "sha1-iS/pWWCAXoVRnxzUOJ8stMu3ZS8=", + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, - "user-home": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", - "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", - "dev": true, - "requires": { - "os-homedir": "^1.0.0" - } - }, - "utf8-byte-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", - "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=" - }, - "util": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", - "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { - "inherits": "2.0.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "util.promisify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", - "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "through2-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", + "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "object.getownpropertydescriptors": "^2.0.3" + "through2": "~2.0.0", + "xtend": "~4.0.0" } }, - "utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", - "dev": true - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "dev": true - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" - }, - "v8-compile-cache": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz", - "integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==", + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==", "dev": true }, - "v8flags": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz", - "integrity": "sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w==", + "timers-browserify": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", "dev": true, "requires": { - "homedir-polyfill": "^1.0.1" + "setimmediate": "^1.0.4" } }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==" + }, + "to-absolute-glob": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", "dev": true, "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" } }, - "validator": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/validator/-/validator-9.4.1.tgz", - "integrity": "sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA==" - }, - "value-or-function": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", - "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", - "dev": true - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true - }, - "vega": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/vega/-/vega-5.7.3.tgz", - "integrity": "sha512-HCg5qgykM9drXPpjo9eJB9VWa/vHoDr5lXkjdk5iZBZI3fTajvAF/oFw6nM1l5P+eWibNlg9TFXn5vF4438uJw==", - "dev": true, - "requires": { - "vega-crossfilter": "^4.0.1", - "vega-dataflow": "^5.4.1", - "vega-encode": "^4.4.1", - "vega-event-selector": "^2.0.1", - "vega-expression": "^2.6.2", - "vega-force": "^4.0.3", - "vega-functions": "^5.4.1", - "vega-geo": "^4.1.0", - "vega-hierarchy": "^4.0.3", - "vega-loader": "^4.1.2", - "vega-parser": "^5.10.1", - "vega-projection": "^1.3.0", - "vega-regression": "^1.0.1", - "vega-runtime": "^5.0.2", - "vega-scale": "^4.1.3", - "vega-scenegraph": "^4.3.1", - "vega-statistics": "^1.6.1", - "vega-transforms": "^4.4.3", - "vega-typings": "^0.10.2", - "vega-util": "^1.12.0", - "vega-view": "^5.3.1", - "vega-view-transforms": "^4.4.1", - "vega-voronoi": "^4.1.1", - "vega-wordcloud": "^4.0.2" - } - }, - "vega-canvas": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/vega-canvas/-/vega-canvas-1.2.1.tgz", - "integrity": "sha512-k/S3EPeJ37D7fYDhv4sEg7fNWVpLheQY7flfLyAmJU7aSwCMgw8cZJi0CKHchJeculssfH+41NCqvRB1QtaJnw==", + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", "dev": true }, - "vega-crossfilter": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/vega-crossfilter/-/vega-crossfilter-4.0.1.tgz", - "integrity": "sha512-wLNS4JzKaOLj8EAzI/v8XBJjUWMRWYSu6EeQF4o9Opq/78u87Ol9Lc5I27UHsww5dNNH/tHubAV4QPIXnGOp5Q==", + "to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", "dev": true, "requires": { - "d3-array": "^2.0.3", - "vega-dataflow": "^5.1.0", - "vega-util": "^1.8.0" + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" }, "dependencies": { - "d3-array": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.3.3.tgz", - "integrity": "sha512-syv3wp0U5aB6toP2zb2OdBkhTy1MWDsCAaYk6OXJZv+G4u7bSWEmYgxLoFyc88RQUhZYGCebW9a9UD1gFi5+MQ==", + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true } } }, - "vega-dataflow": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/vega-dataflow/-/vega-dataflow-5.4.1.tgz", - "integrity": "sha512-NZASrIGel2ZD+HiJsozMPO7qNB3INLFWQez6KI+gPpKQIhsz7jWzG/TBK57A8NOLJYPc6VBLiygCmdJbr5E+sA==", - "dev": true, - "requires": { - "vega-loader": "^4.0.0", - "vega-util": "^1.11.0" - } - }, - "vega-embed": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/vega-embed/-/vega-embed-4.2.5.tgz", - "integrity": "sha512-3iUv5oU5y/sa7jC+shw79hPmHMpWMhMTGSovtl3+O98hLq7LQgordWKgoxKcqwhSIHMIgj+cInTNPWM4kru7Ug==", + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { - "d3-selection": "^1.4.0", - "json-stringify-pretty-compact": "^2.0.0", - "semver": "^6.3.0", - "vega-schema-url-parser": "^1.1.0", - "vega-themes": "^2.3.2", - "vega-tooltip": "^0.18.1" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } + "is-number": "^7.0.0" } }, - "vega-encode": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/vega-encode/-/vega-encode-4.4.1.tgz", - "integrity": "sha512-PtfH+k7Hie7T0ywrg/nI/rhMnpNr8Rg627XR8pzSrM1CbDoMbfFmQsw33tXUT8k/8u/dPLR1klgAtCHUCh7jjA==", + "to-through": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", + "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", "dev": true, "requires": { - "d3-array": "^2.3.1", - "d3-format": "^1.4.1", - "d3-interpolate": "^1.3.2", - "d3-time-format": "^2.1.3", - "vega-dataflow": "^5.4.0", - "vega-scale": "^4.1.2", - "vega-util": "^1.11.2" - }, - "dependencies": { - "d3-array": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.3.3.tgz", - "integrity": "sha512-syv3wp0U5aB6toP2zb2OdBkhTy1MWDsCAaYk6OXJZv+G4u7bSWEmYgxLoFyc88RQUhZYGCebW9a9UD1gFi5+MQ==", - "dev": true - }, - "d3-format": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.1.tgz", - "integrity": "sha512-TUswGe6hfguUX1CtKxyG2nymO+1lyThbkS1ifLX0Sr+dOQtAD5gkrffpHnx+yHNKUZ0Bmg5T4AjUQwugPDrm0g==", - "dev": true - } + "through2": "^2.0.3" } }, - "vega-event-selector": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/vega-event-selector/-/vega-event-selector-2.0.1.tgz", - "integrity": "sha512-FGU1PefYhW9An6zVs6TE5f/XGYsIispxFErG/p9KThxL22IC90WVZzMQXKN9M8OcARq5OyWjHg3qa9Qp/Z6OJw==", + "totalist": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", + "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", "dev": true }, - "vega-expression": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/vega-expression/-/vega-expression-2.6.2.tgz", - "integrity": "sha512-vh8GVkAL/KtsgcdrdKdEnysZn/InIuRrkF7U+CG1eAmupMucPY/Rpu0nCdYb4CLC/xNRHx/NMFidLztQUjZJQg==", + "trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", "dev": true, "requires": { - "vega-util": "^1.11.0" + "escape-string-regexp": "^1.0.2" } }, - "vega-force": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/vega-force/-/vega-force-4.0.3.tgz", - "integrity": "sha512-4stItN4jD9H1CENaCz4jXRNS1Bi9cozMOUjX2824FeJENi2RZSiAZAaGbscgerZQ/jbNcOHD8PHpC2pWldEvGA==", + "ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "requires": {} + }, + "ts-loader": { + "version": "9.2.8", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.2.8.tgz", + "integrity": "sha512-gxSak7IHUuRtwKf3FIPSW1VpZcqF9+MBrHOvBp9cjHh+525SjtCIJKVGjRKIAfxBwDGDGCFF00rTfzB1quxdSw==", "dev": true, "requires": { - "d3-force": "^2.0.1", - "vega-dataflow": "^5.4.0", - "vega-util": "^1.11.0" + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4" }, "dependencies": { - "d3-force": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-2.0.1.tgz", - "integrity": "sha512-zh73/N6+MElRojiUG7vmn+3vltaKon7iD5vB/7r9nUaBeftXMzRo5IWEG63DLBCto4/8vr9i3m9lwr1OTJNiCg==", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "d3-dispatch": "1", - "d3-quadtree": "1", - "d3-timer": "1" + "color-name": "~1.1.4" } - } - } - }, - "vega-functions": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/vega-functions/-/vega-functions-5.4.1.tgz", - "integrity": "sha512-f/smrE+ujMZgSWiTNVmdSNu3npDZMrHGc+MjjQ2pjJixcQRKTz1SbnmpogFhCgvKa6qBSw7hrZX8/TKGh4nuiA==", - "dev": true, - "requires": { - "d3-array": "^2.3.2", - "d3-color": "^1.4.0", - "d3-format": "^1.4.1", - "d3-geo": "^1.11.6", - "d3-time-format": "^2.1.3", - "vega-dataflow": "^5.4.1", - "vega-expression": "^2.6.2", - "vega-scale": "^4.1.3", - "vega-scenegraph": "^4.3.1", - "vega-selections": "^5.0.1", - "vega-statistics": "^1.6.1", - "vega-util": "^1.12.0" - }, - "dependencies": { - "d3-array": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.3.3.tgz", - "integrity": "sha512-syv3wp0U5aB6toP2zb2OdBkhTy1MWDsCAaYk6OXJZv+G4u7bSWEmYgxLoFyc88RQUhZYGCebW9a9UD1gFi5+MQ==", - "dev": true }, - "d3-color": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.0.tgz", - "integrity": "sha512-TzNPeJy2+iEepfiL92LAAB7fvnp/dV2YwANPVHdDWmYMm23qIJBYww3qT8I8C1wXrmrg4UWs7BKc2tKIgyjzHg==", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "d3-format": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.1.tgz", - "integrity": "sha512-TUswGe6hfguUX1CtKxyG2nymO+1lyThbkS1ifLX0Sr+dOQtAD5gkrffpHnx+yHNKUZ0Bmg5T4AjUQwugPDrm0g==", + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, - "vega-geo": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/vega-geo/-/vega-geo-4.1.0.tgz", - "integrity": "sha512-3xo0hpmfVdhbdB0MWAppBu/DGTHn7+4g+psUVBu9LRii4XUNlmi+YmJzO4Ph6tv2zxrfZfIKMXid9+V5MzWn+g==", + "ts-mockito": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/ts-mockito/-/ts-mockito-2.6.1.tgz", + "integrity": "sha512-qU9m/oEBQrKq5hwfbJ7MgmVN5Gu6lFnIGWvpxSjrqq6YYEVv+RwVFWySbZMBgazsWqv6ctAyVBpo9TmAxnOEKw==", "dev": true, "requires": { - "d3-array": "^2.3.1", - "d3-contour": "^1.3.2", - "d3-geo": "^1.11.6", - "vega-dataflow": "^5.1.1", - "vega-projection": "^1.3.0", - "vega-util": "^1.11.2" - }, - "dependencies": { - "d3-array": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.3.3.tgz", - "integrity": "sha512-syv3wp0U5aB6toP2zb2OdBkhTy1MWDsCAaYk6OXJZv+G4u7bSWEmYgxLoFyc88RQUhZYGCebW9a9UD1gFi5+MQ==", - "dev": true - } + "lodash": "^4.17.5" } }, - "vega-hierarchy": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/vega-hierarchy/-/vega-hierarchy-4.0.3.tgz", - "integrity": "sha512-9wNe+KyKqZW1S4++jCC38HuAhZbqNhfY7gOvwiMLjsp65tMtRETrtvYfHkULClm3UokUIX54etAXREAGW7znbw==", - "dev": true, - "requires": { - "d3-hierarchy": "^1.1.8", - "vega-dataflow": "^5.4.0", - "vega-util": "^1.11.0" + "ts-node": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", + "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.0", + "yn": "3.1.1" } }, - "vega-lite": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/vega-lite/-/vega-lite-3.4.0.tgz", - "integrity": "sha512-RJg9uBNh5g0hA8xTzAcALUfNx0cEq7E7xx+vxPEGSMgI8z+A5KlE9u4jUx6nKu7Mjg1qZO8WOyWCmBS1kdFWPg==", + "tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "requires": { - "@types/clone": "~0.1.30", - "@types/fast-json-stable-stringify": "^2.0.0", - "clone": "~2.1.2", - "fast-deep-equal": "~2.0.1", - "fast-json-stable-stringify": "~2.0.0", - "json-stringify-pretty-compact": "~2.0.0", - "tslib": "~1.10.0", - "vega-event-selector": "~2.0.0", - "vega-expression": "~2.6.0", - "vega-typings": "0.7.2", - "vega-util": "~1.10.0", - "yargs": "~13.3.0" + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" }, "dependencies": { - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" + "minimist": "^1.2.0" } }, - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + } + } + }, + "tsconfig-paths-webpack-plugin": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-3.5.2.tgz", + "integrity": "sha512-EhnfjHbzm5IYI9YPNVIxx1moxMI4bpHD2e0zTXeDNQcwjjRaGepP7IhTHJkyDBG0CAOoxRfe7jCG630Ou+C6Pw==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.7.0", + "tsconfig-paths": "^3.9.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "color-convert": "^2.0.1" } }, - "vega-typings": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/vega-typings/-/vega-typings-0.7.2.tgz", - "integrity": "sha512-BReB2qRERA/Ke+QoxKDQ7fES25A9Q3qKRm1CJxwvpLGhAl4k5cGDORx6yW+J3rFHMzpJlmdRM+kb489EuphxZQ==", + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { - "vega-util": "^1.10.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "vega-util": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/vega-util/-/vega-util-1.10.0.tgz", - "integrity": "sha512-fTGnTG7FhtTG9tiYDL3k5s8YHqB71Ml5+aC9B7eaBygeB8GKXBrcbTXLOzoCRxT3Jr5cRhr99PMBu0AkqmhBog==", - "dev": true - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" + "color-name": "~1.1.4" } }, - "yargs": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", - "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.1" - } + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "has-flag": "^4.0.0" } } } }, - "vega-loader": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/vega-loader/-/vega-loader-4.1.2.tgz", - "integrity": "sha512-Zuxvl0K3jmLWyns+8ACajtp7oGfV+M7IeSQybq3xf7BvlGca2h8XA3qiBUXzYULkwZ4PstB56XoJ0tur2Rgs6A==", + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dev": true, + "optional": true, "requires": { - "d3-dsv": "^1.1.1", - "d3-time-format": "^2.1.3", - "node-fetch": "^2.6.0", - "topojson-client": "^3.0.1", - "vega-util": "^1.11.0" - }, - "dependencies": { - "d3-dsv": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.1.1.tgz", - "integrity": "sha512-1EH1oRGSkeDUlDRbhsFytAXU6cAmXFzc52YUe6MRlPClmWb85MP1J5x+YJRzya4ynZWnbELdSAvATFW/MbxaXw==", - "dev": true, - "requires": { - "commander": "2", - "iconv-lite": "0.4", - "rw": "1" - } - }, - "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", - "dev": true - } + "safe-buffer": "^5.0.1" } }, - "vega-parser": { - "version": "5.10.1", - "resolved": "https://registry.npmjs.org/vega-parser/-/vega-parser-5.10.1.tgz", - "integrity": "sha512-NtjFhuXEGlc6ZOc+LeG7Gecmwal/UPu8WDI5wCjBWDogGgpeuRfQQ1haLz2KMsZhq5e2aUmk7iTuH/CuUoeGEA==", + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "requires": { - "vega-dataflow": "^5.4.1", - "vega-event-selector": "^2.0.1", - "vega-expression": "^2.6.2", - "vega-functions": "^5.4.1", - "vega-scale": "^4.1.3", - "vega-util": "^1.12.0" + "prelude-ls": "^1.2.1" } }, - "vega-projection": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/vega-projection/-/vega-projection-1.3.0.tgz", - "integrity": "sha512-BFOc/XSVVW96WIAAyiUcppCeegniibiKGX0OLbGpQ5WIbeDHsbCXqnkeBpD5wsjvPXaiQRHTZ0PZ8VvCoCQV+g==", + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "dev": true, "requires": { - "d3-geo": "^1.11.6" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" } }, - "vega-regression": { + "typed-array-byte-length": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/vega-regression/-/vega-regression-1.0.1.tgz", - "integrity": "sha512-eeQnLccWHAs2rovu2x3G50reF3Die9QoUGy/dMAO6sbDDA7B5s5qW3uq1NNnG93l3Ch84lO71qytxDBTdaQThA==", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", "dev": true, "requires": { - "d3-array": "^2.3.1", - "vega-dataflow": "^5.2.1", - "vega-statistics": "^1.4.0", - "vega-util": "^1.11.0" - }, - "dependencies": { - "d3-array": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.3.3.tgz", - "integrity": "sha512-syv3wp0U5aB6toP2zb2OdBkhTy1MWDsCAaYk6OXJZv+G4u7bSWEmYgxLoFyc88RQUhZYGCebW9a9UD1gFi5+MQ==", - "dev": true - } + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" } }, - "vega-runtime": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/vega-runtime/-/vega-runtime-5.0.2.tgz", - "integrity": "sha512-Cuv+RY6kprH+vtNERg6xP4dgcdYGD2ZnxPxJNEtGi7dmtQQTBa1s7jQ0VDXTolsO6lKJ3B7np2GzKJYwevgj1A==", + "typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + } + }, + "typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + } + }, + "typed-rest-client": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz", + "integrity": "sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==", + "dev": true, + "requires": { + "qs": "^6.9.1", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + } + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "typemoq": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/typemoq/-/typemoq-2.1.0.tgz", + "integrity": "sha512-DtRNLb7x8yCTv/KHlwes+NI+aGb4Vl1iPC63Hhtcvk1DpxSAZzKWQv0RQFY0jX2Uqj0SDBNl8Na4e6MV6TNDgw==", + "dev": true, + "requires": { + "circular-json": "^0.3.1", + "lodash": "^4.17.4", + "postinstall-build": "^5.0.1" + } + }, + "typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true + }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, + "uint64be": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/uint64be/-/uint64be-3.0.0.tgz", + "integrity": "sha512-mliiCSrsE29aNBI7O9W5gGv6WmA9kBR8PtTt6Apaxns076IRdYrrtFhXHEWMj5CSum3U7cv7/pi4xmi4XsIOqg==" + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, + "unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", "dev": true, "requires": { - "vega-dataflow": "^5.1.1", - "vega-util": "^1.11.0" + "buffer": "^5.2.1", + "through": "^2.3.8" } }, - "vega-scale": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/vega-scale/-/vega-scale-4.1.3.tgz", - "integrity": "sha512-hpLrEFntN18e+eRAxa8b8malSbNVQyziKmUMGI1Za8ZB64cYj+A/G87ePE0ExSymfrvc/Xulh4VQZNxkPJll4w==", + "unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", + "dev": true + }, + "underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "dev": true + }, + "undertaker": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-2.0.0.tgz", + "integrity": "sha512-tO/bf30wBbTsJ7go80j0RzA2rcwX6o7XPBpeFcb+jzoeb4pfMM2zUeSDIkY1AWqeZabWxaQZ/h8N9t35QKDLPQ==", "dev": true, "requires": { - "d3-array": "^2.3.2", - "d3-interpolate": "^1.3.2", - "d3-scale": "^3.1.0", - "d3-time": "^1.1.0", - "vega-util": "^1.11.0" + "bach": "^2.0.1", + "fast-levenshtein": "^3.0.0", + "last-run": "^2.0.0", + "undertaker-registry": "^2.0.0" }, "dependencies": { - "d3-array": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.3.3.tgz", - "integrity": "sha512-syv3wp0U5aB6toP2zb2OdBkhTy1MWDsCAaYk6OXJZv+G4u7bSWEmYgxLoFyc88RQUhZYGCebW9a9UD1gFi5+MQ==", - "dev": true - }, - "d3-scale": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.2.0.tgz", - "integrity": "sha512-1RnLYPmH3f2E96hSsCr3ok066myuAxoH3+pnlJAedeMOp7jeW7A+GZHAyVWWaStfphyPEBiDoLFA9zl+DcnC2Q==", + "fast-levenshtein": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", + "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==", "dev": true, "requires": { - "d3-array": "1.2.0 - 2", - "d3-format": "1", - "d3-interpolate": "1", - "d3-time": "1", - "d3-time-format": "2" + "fastest-levenshtein": "^1.0.7" } - }, - "d3-time": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", - "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==", - "dev": true } } }, - "vega-scenegraph": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/vega-scenegraph/-/vega-scenegraph-4.3.1.tgz", - "integrity": "sha512-+hzfFFvvZKgGg7L7+RlRMY/II35/Ty8lw7CiEXRyKA2jxsp3eIWuppiaPgB0IQeVooJh6z4UNcKbNq3SUAvgDA==", - "dev": true, - "requires": { - "d3-path": "^1.0.8", - "d3-shape": "^1.3.5", - "vega-canvas": "^1.2.1", - "vega-loader": "^4.1.2", - "vega-util": "^1.11.2" - }, - "dependencies": { - "d3-path": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.8.tgz", - "integrity": "sha512-J6EfUNwcMQ+aM5YPOB8ZbgAZu6wc82f/0WFxrxwV6Ll8wBwLaHLKCqQ5Imub02JriCVVdPjgI+6P3a4EWJCxAg==", - "dev": true - } - } + "undertaker-registry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-2.0.0.tgz", + "integrity": "sha512-+hhVICbnp+rlzZMgxXenpvTxpuvA67Bfgtt+O9WOE5jo7w/dyiF1VmoZVIHvP2EkUjsyKyTwYKlLhA+j47m1Ew==", + "dev": true }, - "vega-schema-url-parser": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vega-schema-url-parser/-/vega-schema-url-parser-1.1.0.tgz", - "integrity": "sha512-Tc85J2ofMZZOsxiqDM9sbvfsa+Vdo3GwNLjEEsPOsCDeYqsUHKAlc1IpbbhPLZ6jusyM9Lk0e1izF64GGklFDg==", + "undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true }, - "vega-selections": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/vega-selections/-/vega-selections-5.0.1.tgz", - "integrity": "sha512-cSqsH8Jg2QirQVLRfXfK0Odz+LOMo8iDtL9NANkv6/JehbqXAof0TfhgLu5gmCgzL3z+FUmzj2C4PizQGgkTtw==", + "unicode": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/unicode/-/unicode-14.0.0.tgz", + "integrity": "sha512-BjinxTXkbm9Jomp/YBTMGusr4fxIG67fNGShHIRAL16Ur2GJTq2xvLi+sxuiJmInCmwqqev2BCFKyvbfp/yAkg==" + }, + "unique-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", + "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", "dev": true, "requires": { - "vega-expression": "^2.6.1", - "vega-util": "^1.11.0" + "json-stable-stringify-without-jsonify": "^1.0.1", + "through2-filter": "^3.0.0" } }, - "vega-statistics": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/vega-statistics/-/vega-statistics-1.6.1.tgz", - "integrity": "sha512-EX+IkELoYLnygcUXcFhcdWwzmalHzKLvSVTx9deGMyPOsCZRd0Lwu/SGwrIctoYa5r9DBR0/bGxQAC0t/UoSdA==", + "update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "requires": { - "d3-array": "^2.3.2" - }, - "dependencies": { - "d3-array": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.3.3.tgz", - "integrity": "sha512-syv3wp0U5aB6toP2zb2OdBkhTy1MWDsCAaYk6OXJZv+G4u7bSWEmYgxLoFyc88RQUhZYGCebW9a9UD1gFi5+MQ==", - "dev": true - } + "escalade": "^3.2.0", + "picocolors": "^1.1.1" } }, - "vega-themes": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/vega-themes/-/vega-themes-2.5.0.tgz", - "integrity": "sha512-mkyYhcRhmMBWLfvCBPTVx0S/OnxeIfVY/TmFfYP5sPdW8X1kMyHtLI34bMhzosPrkhNyHsC8FNHJyU/dOQnX4A==", - "dev": true - }, - "vega-tooltip": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/vega-tooltip/-/vega-tooltip-0.18.1.tgz", - "integrity": "sha512-g/i69QLTVhGeHNT8k646Qr8SFss9kbnt6XmU9ujjqgaW5B/p1FPUrMzFh/88rMF704EHYyBH7Aj3t0ds1cCHbQ==", + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "dev": true, "requires": { - "vega-util": "^1.10.0" + "punycode": "^2.1.0" } }, - "vega-transforms": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/vega-transforms/-/vega-transforms-4.4.3.tgz", - "integrity": "sha512-8GxMm1CecRzNG89zr4JroOfPsWCkb9lWL5owSBsB+AalyJaFjxYwKuCM6L2OsAKID3su3aeQ0oSBQTR3v0nqPA==", + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", "dev": true, "requires": { - "d3-array": "^2.3.2", - "vega-dataflow": "^5.4.1", - "vega-statistics": "^1.6.1", - "vega-util": "^1.12.0" + "punycode": "1.3.2", + "querystring": "0.2.0" }, "dependencies": { - "d3-array": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.3.3.tgz", - "integrity": "sha512-syv3wp0U5aB6toP2zb2OdBkhTy1MWDsCAaYk6OXJZv+G4u7bSWEmYgxLoFyc88RQUhZYGCebW9a9UD1gFi5+MQ==", + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", "dev": true } } }, - "vega-typings": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/vega-typings/-/vega-typings-0.10.2.tgz", - "integrity": "sha512-W/V6oyoBizlXt0FJnuY5/Y46/dsmfcPTxRaUCtFBf0nyoWBsmO66EYj24xCHJ6pgfHEEbBRLPfrfrvlKiRPaMQ==", + "url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true + }, + "url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", "dev": true, "requires": { - "vega-util": "^1.11.0" + "prepend-http": "^2.0.0" } }, - "vega-util": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/vega-util/-/vega-util-1.12.0.tgz", - "integrity": "sha512-eN1PAQVDyEOcwild2Fk1gbkzkqgDHNujG2/akYRtBzkhtz2EttrVIDwBkWqV/Q+VvEINEksb7TI3Wv7qVQFR5g==", + "url-to-options": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", + "integrity": "sha512-0kQLIzG4fdk/G5NONku64rSH/x32NOA39LVQqlK8Le6lvTF6GGRJpqaQFGgU+CLwySIqBSMdwYM0sYcW9f6P4A==", "dev": true }, - "vega-view": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/vega-view/-/vega-view-5.3.1.tgz", - "integrity": "sha512-pKbpmR1uz9YOsqocx6KWEHD1EwwpOVIfRH6h8g5xQLiUQP37bohfKZ2vL05MQCupXN2dNYdp51iyETbMJMj7nQ==", + "util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", "dev": true, "requires": { - "d3-array": "^2.3.1", - "d3-timer": "^1.0.9", - "vega-dataflow": "^5.2.1", - "vega-functions": "^5.3.1", - "vega-runtime": "^5.0.1", - "vega-scenegraph": "^4.2.0", - "vega-util": "^1.11.0" + "inherits": "2.0.3" }, "dependencies": { - "d3-array": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.3.3.tgz", - "integrity": "sha512-syv3wp0U5aB6toP2zb2OdBkhTy1MWDsCAaYk6OXJZv+G4u7bSWEmYgxLoFyc88RQUhZYGCebW9a9UD1gFi5+MQ==", + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true } } }, - "vega-view-transforms": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/vega-view-transforms/-/vega-view-transforms-4.4.1.tgz", - "integrity": "sha512-BgjGsHDLHGTN2FgVRcepuvAcKnMUUtXZVemmJHPrY4LI5iJiCS9+HkZPVcVWD1+vheSNXMmZV7Skn7qn7zAcRg==", - "dev": true, - "requires": { - "vega-dataflow": "^5.1.1", - "vega-scenegraph": "^4.3.0", - "vega-util": "^1.11.2" - } - }, - "vega-voronoi": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/vega-voronoi/-/vega-voronoi-4.1.1.tgz", - "integrity": "sha512-agLmr+UGxJs5KB9D8GeZqxgeWWGoER/eVHPcFFPgVuoNBsrqf2bdoltmIkRnpiRsQnGCibGixhFEDCc9GGNAww==", - "dev": true, - "requires": { - "d3-delaunay": "^5.1.3", - "vega-dataflow": "^5.1.1", - "vega-util": "^1.11.0" - } - }, - "vega-wordcloud": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/vega-wordcloud/-/vega-wordcloud-4.0.2.tgz", - "integrity": "sha512-nV9bRKjRGcmcQV5wXvOvWes4T5937t3RF+Rm1d03YVAzZpOcVKk9uBuVSeFYBLX2XcDBVe4HK54qDoOTFftHMw==", - "dev": true, - "requires": { - "vega-canvas": "^1.2.0", - "vega-dataflow": "^5.1.1", - "vega-scale": "^4.0.0", - "vega-statistics": "^1.2.5", - "vega-util": "^1.8.0" - } - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true }, - "vfile": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-2.3.0.tgz", - "integrity": "sha512-ASt4mBUHcTpMKD/l5Q+WJXNtshlWxOogYyGYYrg4lt/vuRjC1EFQtlAofL5VmtVNIZJzWYFJjzGWZ0Gw8pzW1w==", - "dev": true, - "requires": { - "is-buffer": "^1.1.4", - "replace-ext": "1.0.0", - "unist-util-stringify-position": "^1.0.0", - "vfile-message": "^1.0.0" - } + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, - "vfile-location": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.6.tgz", - "integrity": "sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA==", + "v8-compile-cache-lib": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz", + "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==", "dev": true }, - "vfile-message": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-1.1.1.tgz", - "integrity": "sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA==", - "dev": true, - "requires": { - "unist-util-stringify-position": "^1.1.1" - } + "v8flags": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-4.0.1.tgz", + "integrity": "sha512-fcRLaS4H/hrZk9hYwbdRM35D0U8IYMfEClhXxCivOojl+yTRAZH3Zy2sSy6qVCiGbV9YAtPssP6jaChqC9vPCg==", + "dev": true }, - "viewport-dimensions": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/viewport-dimensions/-/viewport-dimensions-0.2.0.tgz", - "integrity": "sha1-3nQHR9tTh/0XJfUXXpG6x2r982w=", + "value-or-function": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", + "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", "dev": true }, "vinyl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", - "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", "dev": true, "requires": { "clone": "^2.1.1", @@ -27006,13 +26360,68 @@ "cloneable-readable": "^1.0.0", "remove-trailing-separator": "^1.0.1", "replace-ext": "^1.0.0" + } + }, + "vinyl-contents": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-contents/-/vinyl-contents-2.0.0.tgz", + "integrity": "sha512-cHq6NnGyi2pZ7xwdHSW1v4Jfnho4TEGtxZHw01cmnc8+i7jgR6bRnED/LbrKan/Q7CvVLbnvA5OepnhbpjBZ5Q==", + "dev": true, + "requires": { + "bl": "^5.0.0", + "vinyl": "^3.0.0" }, "dependencies": { - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dev": true, + "requires": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", "dev": true + }, + "vinyl": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz", + "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", + "dev": true, + "requires": { + "clone": "^2.1.2", + "clone-stats": "^1.0.0", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + } } } }, @@ -27039,38 +26448,6 @@ "value-or-function": "^3.0.0", "vinyl": "^2.0.0", "vinyl-sourcemap": "^1.1.0" - }, - "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "vinyl-sourcemap": { @@ -27099,561 +26476,269 @@ } } }, - "viz-annotation": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/viz-annotation/-/viz-annotation-0.0.3.tgz", - "integrity": "sha512-kkZ+mZHx+zfAZhBuA/PGzA7ZpKUZFjtgBqVcbPfCrOIrHjERcHyrNhxYfx1ZZu9uyZLO0i/qcoMOtY5HpavSlg==", - "dev": true, - "requires": { - "d3-shape": "~1.0.4" - }, - "dependencies": { - "d3-shape": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.0.6.tgz", - "integrity": "sha1-sJ4wXPDHxrmpjJDmtC9i2sS8/Vs=", - "dev": true, - "requires": { - "d3-path": "1" - } - } - } - }, - "vlq": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz", - "integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==" - }, "vm-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz", - "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==", - "dev": true - }, - "vsce": { - "version": "1.65.0", - "resolved": "https://registry.npmjs.org/vsce/-/vsce-1.65.0.tgz", - "integrity": "sha512-1bGyeoaxjhNVz9fVAqUzGWc1e5CxsxZFpVSnS/anRVyZju0y4DJCPi685WkBsRYU/lfp3AI1smpatuSfb2Lllg==", - "dev": true, - "requires": { - "azure-devops-node-api": "^7.2.0", - "chalk": "^2.4.2", - "cheerio": "^1.0.0-rc.1", - "commander": "^2.8.1", - "denodeify": "^1.2.1", - "didyoumean": "^1.2.1", - "glob": "^7.0.6", - "lodash": "^4.17.10", - "markdown-it": "^8.3.1", - "mime": "^1.3.4", - "minimatch": "^3.0.3", - "osenv": "^0.1.3", - "parse-semver": "^1.1.1", - "read": "^1.0.7", - "semver": "^5.1.0", - "tmp": "0.0.29", - "typed-rest-client": "1.2.0", - "url-join": "^1.1.0", - "yauzl": "^2.3.1", - "yazl": "^2.2.2" - }, - "dependencies": { - "linkify-it": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", - "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", - "dev": true, - "requires": { - "uc.micro": "^1.0.1" - } - }, - "markdown-it": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.4.2.tgz", - "integrity": "sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "entities": "~1.1.1", - "linkify-it": "^2.0.0", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - } - } - } - }, - "vscode-debugadapter": { - "version": "1.35.0", - "resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.35.0.tgz", - "integrity": "sha512-Au90Iowj6TuD5uDMaTnxOjl/9hQN0Yoky1TV1Cjjr7jPdxTQpALBRW09Y2LzkIXUVICXlAqxWL9zL8BpzI30jg==", - "requires": { - "mkdirp": "^0.5.1", - "vscode-debugprotocol": "1.35.0" - } - }, - "vscode-debugadapter-testsupport": { - "version": "1.35.0", - "resolved": "https://registry.npmjs.org/vscode-debugadapter-testsupport/-/vscode-debugadapter-testsupport-1.35.0.tgz", - "integrity": "sha512-4emLt6JOk4iKqC2aWNJupOtrK6JwYAZ6KppqvKASN6B1s063VoqI18QhUB1CeoKwNaN1LIG3VPv2xM8HKOjyDA==", - "dev": true, - "requires": { - "vscode-debugprotocol": "1.35.0" - } - }, - "vscode-debugprotocol": { - "version": "1.35.0", - "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.35.0.tgz", - "integrity": "sha512-+OMm11R1bGYbpIJ5eQIkwoDGFF4GvBz3Ztl6/VM+/RNNb2Gjk2c0Ku+oMmfhlTmTlPCpgHBsH4JqVCbUYhu5bA==" - }, - "vscode-extension-telemetry": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/vscode-extension-telemetry/-/vscode-extension-telemetry-0.1.4.tgz", - "integrity": "sha512-9U2pUZ/YwZBfA8CkBrHwMxjnq9Ab+ng8daJWJzEQ6CAxlZyRhmck23bx2lqqpEwGWJCiuceQy4k0Me6llEB4zw==", - "requires": { - "applicationinsights": "1.7.4" - } - }, - "vscode-jsonrpc": { - "version": "6.0.0-next.5", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0-next.5.tgz", - "integrity": "sha512-IAgsltQPwg/pXOPsdXgbUTCaO9VSKZwirZN5SGtkdYQ/R3VjeC4v00WTVvoNayWMZpoC3O9u0ogqmsKzKhVasQ==" - }, - "vscode-languageclient": { - "version": "7.0.0-next.9", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-7.0.0-next.9.tgz", - "integrity": "sha512-lFO+rN/i72CM2va6iKXq1lD7pJg8J93KEXf0w0boWVqU+DJhWzLrV3pXl8Xk1nCv//qOAyhlc/nx2KZCTeRF/A==", - "requires": { - "semver": "^6.3.0", - "vscode-languageserver-protocol": "3.16.0-next.7" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, - "vscode-languageserver": { - "version": "7.0.0-next.7", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-7.0.0-next.7.tgz", - "integrity": "sha512-6La+usoCF0OnCYIbOLBTmqgzaZSAyIBpnsLjS/g7AnI8Pv9v6yzC38VhTvBoip4/ZBwf1U8vpdwatD/qCSKeWA==", - "requires": { - "vscode-languageserver-protocol": "3.16.0-next.7" - } - }, - "vscode-languageserver-protocol": { - "version": "3.16.0-next.7", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0-next.7.tgz", - "integrity": "sha512-tOjrg+K3RddJ547zpC9/LAgTbzadkPuHlqJFFWIcKjVhiJOh73XyY+Ngcu9wukGaTsuSGjJ0W8rlmwanixa0FQ==", - "requires": { - "vscode-jsonrpc": "6.0.0-next.5", - "vscode-languageserver-types": "3.16.0-next.3" - } + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "dev": true }, - "vscode-languageserver-types": { - "version": "3.16.0-next.3", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0-next.3.tgz", - "integrity": "sha512-s/z5ZqSe7VpoXJ6JQcvwRiPPA3nG0nAcJ/HH03zoU6QaFfnkcgPK+HshC3WKPPnC2G08xA0iRB6h7kmyBB5Adg==" + "vscode-debugprotocol": { + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.35.0.tgz", + "integrity": "sha512-+OMm11R1bGYbpIJ5eQIkwoDGFF4GvBz3Ztl6/VM+/RNNb2Gjk2c0Ku+oMmfhlTmTlPCpgHBsH4JqVCbUYhu5bA==" }, - "vscode-tas-client": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/vscode-tas-client/-/vscode-tas-client-0.1.4.tgz", - "integrity": "sha512-sC+kvLUwb6ecC7+ZoxzDtvvktVUJ3jZq6mvJpfYHeLlbj4hUpNsZ79u65/mukoO8E8C7UQUCLdWdyn/evp+oNA==", - "requires": { - "tas-client": "0.0.950" - } + "vscode-jsonrpc": { + "version": "9.0.0-next.5", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.5.tgz", + "integrity": "sha512-Sl/8RAJtfF/2x/TPBVRuhzRAcqYR/QDjEjNqMcoKFfqsxfVUPzikupRDQYB77Gkbt1RrW43sSuZ5uLtNAcikQQ==" }, - "vscode-test": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/vscode-test/-/vscode-test-1.2.3.tgz", - "integrity": "sha512-mKRTNso33NaUULiPBFg6zRjyntjcCpIgkrogyPQuKlvoQREQR8jLKN5UD4L5rkTSD+oBhcKtaLR2/g34FexURw==", - "dev": true, + "vscode-languageclient": { + "version": "10.0.0-next.12", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-10.0.0-next.12.tgz", + "integrity": "sha512-q7cVYCcYiv+a+fJYCbjMMScOGBnX162IBeUMFg31mvnN7RHKx5/CwKaCz+r+RciJrRXMqS8y8qpEVGgeIPnbxg==", "requires": { - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.4", - "rimraf": "^2.6.3" + "minimatch": "^9.0.3", + "semver": "^7.6.0", + "vscode-languageserver-protocol": "3.17.6-next.10" }, "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, + "brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "requires": { - "glob": "^7.1.3" + "brace-expansion": "^2.0.2" } } } }, - "vsls": { - "version": "0.3.1291", - "resolved": "https://registry.npmjs.org/vsls/-/vsls-0.3.1291.tgz", - "integrity": "sha512-8yJPN9p7k+XYyczOVtQmpun4K1CRDsw/hdnIzT/c40r5bIkpptfsBlHmmLemoIV+CAHvrTLdWKEf5OtRvdcn9A==" - }, - "w3c-hr-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", - "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", - "dev": true, - "requires": { - "browser-process-hrtime": "^0.1.2" - } - }, - "w3c-xmlserializer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz", - "integrity": "sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==", - "dev": true, + "vscode-languageserver-protocol": { + "version": "3.17.6-next.10", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.10.tgz", + "integrity": "sha512-KOrrWn4NVC5jnFC5N6y/fyNKtx8rVYr67lhL/Z0P4ZBAN27aBsCnLBWAMIkYyJ1K8EZaE5r7gqdxrS9JPB6LIg==", "requires": { - "domexception": "^1.0.1", - "webidl-conversions": "^4.0.2", - "xml-name-validator": "^3.0.0" + "vscode-jsonrpc": "9.0.0-next.5", + "vscode-languageserver-types": "3.17.6-next.5" } }, - "wait-for-expect": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/wait-for-expect/-/wait-for-expect-3.0.1.tgz", - "integrity": "sha512-3Ha7lu+zshEG/CeHdcpmQsZnnZpPj/UsG3DuKO8FskjuDbkx3jE3845H+CuwZjA2YWYDfKMU2KhnCaXMLd3wVw==", - "dev": true + "vscode-languageserver-types": { + "version": "3.17.6-next.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.6-next.5.tgz", + "integrity": "sha512-QFmf3Yl1tCgUQfA77N9Me/LXldJXkIVypQbty2rJ1DNHQkC+iwvm4Z2tXg9czSwlhvv0pD4pbF5mT7WhAglolw==" }, - "warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "dev": true, + "vscode-tas-client": { + "version": "0.1.84", + "resolved": "https://registry.npmjs.org/vscode-tas-client/-/vscode-tas-client-0.1.84.tgz", + "integrity": "sha512-rUTrUopV+70hvx1hW5ebdw1nd6djxubkLvVxjGdyD/r5v/wcVF41LIfiAtbm5qLZDtQdsMH1IaCuDoluoIa88w==", "requires": { - "loose-envify": "^1.0.0" + "tas-client": "0.2.33" } }, "watchpack": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.1.tgz", - "integrity": "sha512-+IF9hfUFOrYOOaKyfaI7h7dquUIOgyEMoQMLA7OP5FxegKA2+XdXThAZ9TU2kucfhDH7rfMHs1oPYziVGWRnZA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "dev": true, "requires": { - "chokidar": "^2.1.8", - "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0" + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" } }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - }, "webpack": { - "version": "4.35.3", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.35.3.tgz", - "integrity": "sha512-xggQPwr9ILlXzz61lHzjvgoqGU08v5+Wnut19Uv3GaTtzN4xBTcwnobodrXE142EL1tOiS5WVEButooGzcQzTA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.8.5", - "@webassemblyjs/helper-module-context": "1.8.5", - "@webassemblyjs/wasm-edit": "1.8.5", - "@webassemblyjs/wasm-parser": "1.8.5", - "acorn": "^6.2.0", - "ajv": "^6.1.0", - "ajv-keywords": "^3.1.0", - "chrome-trace-event": "^1.0.0", - "enhanced-resolve": "^4.1.0", - "eslint-scope": "^4.0.0", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^2.3.0", - "loader-utils": "^1.1.0", - "memory-fs": "~0.4.1", - "micromatch": "^3.1.8", - "mkdirp": "~0.5.0", - "neo-async": "^2.5.0", - "node-libs-browser": "^2.0.0", - "schema-utils": "^1.0.0", - "tapable": "^1.1.0", - "terser-webpack-plugin": "^1.1.0", - "watchpack": "^1.5.0", - "webpack-sources": "^1.3.0" - }, - "dependencies": { - "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", - "dev": true - }, - "lru-cache": { - "version": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "requires": { - "yallist": "^3.0.2" - } - }, - "pump": { - "version": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "version": "5.105.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", + "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", + "dev": true, + "requires": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.19.0", + "es-module-lexer": "^2.0.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.3.1", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.16", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.3" + }, + "dependencies": { + "ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "requires": { - "randombytes": "^2.1.0" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" } }, - "terser-webpack-plugin": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", - "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "requires": { - "cacache": "^12.0.2", - "find-cache-dir": "^2.1.0", - "is-wsl": "^1.1.0", - "schema-utils": "^1.0.0", - "serialize-javascript": "^4.0.0", - "source-map": "^0.6.1", - "terser": "^4.1.2", - "webpack-sources": "^1.4.0", - "worker-farm": "^1.7.0" - }, - "dependencies": { - "terser": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", - "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", - "dev": true, - "requires": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - } - }, - "webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "dev": true, - "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - } - } + "fast-deep-equal": "^3.1.3" } }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - } - } - }, - "webpack-bundle-analyzer": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.6.0.tgz", - "integrity": "sha512-orUfvVYEfBMDXgEKAKVvab5iQ2wXneIEorGNsyuOyVYpjYrI7CUOhhXNDd3huMwQ3vNNWWlGP+hzflMFYNzi2g==", - "dev": true, - "requires": { - "acorn": "^6.0.7", - "acorn-walk": "^6.1.1", - "bfj": "^6.1.1", - "chalk": "^2.4.1", - "commander": "^2.18.0", - "ejs": "^2.6.1", - "express": "^4.16.3", - "filesize": "^3.6.1", - "gzip-size": "^5.0.0", - "lodash": "^4.17.15", - "mkdirp": "^0.5.1", - "opener": "^1.5.1", - "ws": "^6.0.0" - }, - "dependencies": { - "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", - "dev": true - }, - "filesize": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", - "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==", + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, - "gzip-size": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", - "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", + "schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, "requires": { - "duplexer": "^0.1.1", - "pify": "^4.0.1" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" } } } }, - "webpack-cli": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.5.tgz", - "integrity": "sha512-w0j/s42c5UhchwTmV/45MLQnTVwRoaUTu9fM5LuyOd/8lFoCNCELDogFoecx5NzRUndO0yD/gF2b02XKMnmAWQ==", + "webpack-bundle-analyzer": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.5.0.tgz", + "integrity": "sha512-GUMZlM3SKwS8Z+CKeIFx7CVoHn3dXFcUAjT/dcZQQmfSZGvitPfMob2ipjai7ovFFqPvTqkEZ/leL4O0YOdAYQ==", "dev": true, "requires": { - "chalk": "2.4.2", - "cross-spawn": "6.0.5", - "enhanced-resolve": "4.1.0", - "findup-sync": "3.0.0", - "global-modules": "2.0.0", - "import-local": "2.0.0", - "interpret": "1.2.0", - "loader-utils": "1.2.3", - "supports-color": "6.1.0", - "v8-compile-cache": "2.0.3", - "yargs": "13.2.4" + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "chalk": "^4.1.0", + "commander": "^7.2.0", + "gzip-size": "^6.0.0", + "lodash": "^4.17.20", + "opener": "^1.5.2", + "sirv": "^1.0.7", + "ws": "^7.3.1" }, "dependencies": { - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" + "color-convert": "^2.0.1" } }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { - "global-prefix": "^3.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" + "color-name": "~1.1.4" } }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "dev": true }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } - }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "dev": true, - "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - } - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + } + } + }, + "webpack-cli": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.2.tgz", + "integrity": "sha512-m3/AACnBBzK/kMTcxWHcZFPrw/eQuY4Df1TxvIWfWM2x7mRqBQCqKEd96oCUa9jkapLBaFfRce33eGDb4Pr7YQ==", + "dev": true, + "requires": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^1.1.1", + "@webpack-cli/info": "^1.4.1", + "@webpack-cli/serve": "^1.6.1", + "colorette": "^2.0.14", + "commander": "^7.0.0", + "execa": "^5.0.0", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^2.2.0", + "rechoir": "^0.7.0", + "webpack-merge": "^5.7.3" + }, + "dependencies": { + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "dev": true }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - } - }, - "yargs": { - "version": "13.2.4", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz", - "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "os-locale": "^3.1.0", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.0" - } + "interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "dev": true }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "rechoir": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", + "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", "dev": true, "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "resolve": "^1.9.0" } } } @@ -27665,251 +26750,183 @@ "dev": true }, "webpack-merge": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.1.tgz", - "integrity": "sha512-4p8WQyS98bUJcCvFMbdGZyZmsKuWjWVnVHnAS3FFg0HDaRVrPbkivx2RYCre8UiemD67RsiFFLfn4JhLAin8Vw==", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", "dev": true, "requires": { - "lodash": "^4.17.5" + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" } }, "webpack-node-externals": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-1.7.2.tgz", - "integrity": "sha512-ajerHZ+BJKeCLviLUUmnyd5B4RavLF76uv3cs6KNuO8W+HuQaEs0y0L7o40NQxdPy5w0pcv8Ew7yPUAQG0UdCg==", - "dev": true - }, - "webpack-require-from": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/webpack-require-from/-/webpack-require-from-1.8.0.tgz", - "integrity": "sha512-4vaPWQZD3vl3WM2mnjWunyx56uUbPj44ZKlpPUd+Ro2jrOtZQOaB2I5FE222uIChzeFfS7A7rtcWRLraPHE7TA==", - "dev": true - }, - "webpack-sources": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", - "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", - "dev": true, - "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - } - }, - "websocket-driver": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz", - "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==", - "dev": true, - "requires": { - "http-parser-js": ">=0.4.0 <0.4.11", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - } - }, - "websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-fetch": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", - "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==", - "dev": true - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz", + "integrity": "sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==", "dev": true }, - "whatwg-url": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", - "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "webpack-require-from": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/webpack-require-from/-/webpack-require-from-1.8.6.tgz", + "integrity": "sha512-QmRsOkOYPKeNXp4uVc7qxnPrFQPrP4bhOc/gl4QenTFNgXdEbF1U8VC+jM/Sljb0VzJLNgyNiHlVkuHjcmDtBQ==", "dev": true, - "requires": { - "isexe": "^2.0.0" - } + "requires": {} }, - "which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "webpack-sources": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", "dev": true }, - "why-is-node-running": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.1.0.tgz", - "integrity": "sha512-oLmJ1uZOaKra+GDmYcUHMnVhi4CnZnlt4IE3J05ZDSEAiejeB5dMoR4a4rGcMWRy1Avx24dGTw8yxJ/+EmwPBQ==", - "dev": true, - "requires": { - "stackback": "0.0.2" - } - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "winreg": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.4.tgz", - "integrity": "sha1-ugZWKbepJRMOFXeRCM9UCZDpjRs=" - }, - "winston": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", - "integrity": "sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw==", - "requires": { - "async": "^2.6.1", - "diagnostics": "^1.1.1", - "is-stream": "^1.1.0", - "logform": "^2.1.1", - "one-time": "0.0.4", - "readable-stream": "^3.1.1", - "stack-trace": "0.0.x", - "triple-beam": "^1.3.0", - "winston-transport": "^4.3.0" - } - }, - "winston-transport": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.3.0.tgz", - "integrity": "sha512-B2wPuwUi3vhzn/51Uukcao4dIduEiPOcOt9HJ3QeaXgkJ5Z7UwpBzxS4ZGNHtrxrUvTwemsQiSys0ihOf8Mp1A==", - "requires": { - "readable-stream": "^2.3.6", - "triple-beam": "^1.2.0" - }, - "dependencies": { - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "^2.0.0" } }, - "wipe-node-cache": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wipe-node-cache/-/wipe-node-cache-2.1.0.tgz", - "integrity": "sha512-Vdash0WV9Di/GeYW9FJrAZcPjGK4dO7M/Be/sJybguEgcM7As0uwLyvewZYqdlepoh7Rj4ZJKEdo8uX83PeNIw==", - "dev": true + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } }, - "wipe-webpack-cache": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wipe-webpack-cache/-/wipe-webpack-cache-2.1.0.tgz", - "integrity": "sha512-OXzQMGpA7MnQQ8AG+uMl5mWR2ezy6fw1+DMHY+wzYP1qkF1jrek87psLBmhZEj+er4efO/GD4R8jXWFierobaA==", + "which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", "dev": true, "requires": { - "wipe-node-cache": "^2.1.0" + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" } }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", "dev": true }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" + "winreg": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.4.tgz", + "integrity": "sha1-ugZWKbepJRMOFXeRCM9UCZDpjRs=" }, - "worker-farm": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", - "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "wipe-node-cache": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/wipe-node-cache/-/wipe-node-cache-2.1.2.tgz", + "integrity": "sha512-m7NXa8qSxBGMtdQilOu53ctMaIBXy93FOP04EC1Uf4bpsE+r+adfLKwIMIvGbABsznaSNxK/ErD4xXDyY5og9w==", + "dev": true + }, + "wipe-webpack-cache": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wipe-webpack-cache/-/wipe-webpack-cache-2.1.0.tgz", + "integrity": "sha512-OXzQMGpA7MnQQ8AG+uMl5mWR2ezy6fw1+DMHY+wzYP1qkF1jrek87psLBmhZEj+er4efO/GD4R8jXWFierobaA==", "dev": true, "requires": { - "errno": "~0.1.7" + "wipe-node-cache": "^2.1.0" } }, - "worker-rpc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/worker-rpc/-/worker-rpc-0.1.1.tgz", - "integrity": "sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg==", + "worker-loader": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.8.tgz", + "integrity": "sha512-XQyQkIFeRVC7f7uRhFdNMe/iJOdO6zxAaR3EWbDp45v3mDhrTi+++oswKNxShUNjPC/1xUp5DB29YKLhFo129g==", "dev": true, "requires": { - "microevent.ts": "~0.1.1" + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" } }, "workerpool": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.0.tgz", - "integrity": "sha512-fU2OcNA/GVAJLLyKUoHkAgIhKb0JoCpSjLC/G2vYKxUjVmQwGbRVeoPJ1a8U4pnVofz4AQV5Y/NEw8oKqxEBtA==", + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", + "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", "dev": true }, "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true + } + } + }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "color-name": "~1.1.4" } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true } } }, @@ -27918,15 +26935,6 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - }, "write-file-atomic": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.1.tgz", @@ -27940,54 +26948,11 @@ } }, "ws": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", - "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", - "requires": { - "async-limiter": "~1.0.0" - } - }, - "wtfnode": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/wtfnode/-/wtfnode-0.8.0.tgz", - "integrity": "sha512-A5jm/0REykxUac1q4Q5kv+hDIiacvqVpwIoXzCQcRL7syeEKucVVOxyLLrt+jIiZoXfla3lnsxUw/cmWXIaGWA==", - "dev": true - }, - "x-is-string": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/x-is-string/-/x-is-string-0.1.0.tgz", - "integrity": "sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=", - "dev": true - }, - "xdg-basedir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-2.0.0.tgz", - "integrity": "sha1-7byQPMOF/ARSPZZqM1UEtVBNG9I=", - "dev": true, - "requires": { - "os-homedir": "^1.0.0" - } - }, - "xdg-trashdir": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/xdg-trashdir/-/xdg-trashdir-2.1.1.tgz", - "integrity": "sha512-KcVhPaOu2ZurYNHSRTf1+ZHORkTZGCQ+u0JHN17QixRISJq4pXOnjt/lQcehvtHL5QAKhSzKgyjrcNnPdkPBHA==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, - "requires": { - "@sindresorhus/df": "^2.1.0", - "mount-point": "^3.0.0", - "pify": "^2.2.0", - "user-home": "^2.0.0", - "xdg-basedir": "^2.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } + "requires": {} }, "xml": { "version": "1.0.1", @@ -27995,66 +26960,36 @@ "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=", "dev": true }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, "xml2js": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", - "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", "requires": { "sax": ">=0.6.0", - "xmlbuilder": "~9.0.1" - }, - "dependencies": { - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - } + "xmlbuilder": "~11.0.0" } }, "xmlbuilder": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", - "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" - }, - "xmlchars": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.1.1.tgz", - "integrity": "sha512-7hew1RPJ1iIuje/Y01bGD/mXokXxegAgVS+e+E0wSi2ILHQkYAH1+JXARwTjZSM4Z4Z+c73aKspEcqj+zPPL/w==", - "dev": true - }, - "xmlhttprequest": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", - "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=", - "dev": true - }, - "xmlhttprequest-ssl": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", - "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", - "dev": true + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true }, "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", "dev": true }, "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yargs": { "version": "15.3.1", @@ -28075,12 +27010,6 @@ "yargs-parser": "^18.1.1" }, "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -28117,84 +27046,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", @@ -28225,120 +27082,40 @@ } }, "yargs-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", - "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", - "dev": true, - "requires": { - "camelcase": "^3.0.0" - }, - "dependencies": { - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true - } - } + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true }, "yargs-unparser": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.1.tgz", - "integrity": "sha512-qZV14lK9MWsGCmcr7u5oXGH0dbGqZAIxTDrWXZDo5zUr6b6iUmelNKO6x6R1dQT24AH3LgRxJpr8meWy2unolA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, "requires": { - "camelcase": "^5.3.1", - "decamelize": "^1.2.0", - "flat": "^4.1.0", - "is-plain-obj": "^1.1.0", - "yargs": "^14.2.3" + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" }, "dependencies": { - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "camelcase": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.1.tgz", + "integrity": "sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA==", "dev": true }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true - }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - } - }, - "yargs": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz", - "integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^15.0.1" - } - }, - "yargs-parser": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz", - "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } } } }, @@ -28361,36 +27138,17 @@ "buffer-crc32": "~0.2.3" } }, - "yeast": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", - "dev": true - }, "yn": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.0.tgz", - "integrity": "sha512-kKfnnYkbTfrAdd0xICNFw7Atm8nKpLcLv9AZGEt+kczL/WQVai4e2V6ZN8U/O+iI6WrNuJjNNOyu4zfhl9D3Hg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true }, - "zeromq": { - "version": "6.0.0-beta.6", - "resolved": "https://registry.npmjs.org/zeromq/-/zeromq-6.0.0-beta.6.tgz", - "integrity": "sha512-wLf6M7pBHijl+BRltUL2VoDpgbQcOZetiX8UzycHL8CcYFxYnRrpoG5fi3UX3+Umavz1lk4/dGaQez8qiDgr/Q==", - "requires": { - "node-gyp-build": "^4.1.0" - } - }, - "zip-stream": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-2.1.3.tgz", - "integrity": "sha512-EkXc2JGcKhO5N5aZ7TmuNo45budRaFGHOmz24wtJR7znbNqDPmdZtUauKX6et8KAVseAMBOyWJqEpXcHTBsh7Q==", - "dev": true, - "requires": { - "archiver-utils": "^2.1.0", - "compress-commons": "^2.1.1", - "readable-stream": "^3.4.0" - } + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true } } } diff --git a/package.datascience-ui.dependencies.json b/package.datascience-ui.dependencies.json deleted file mode 100644 index c42689044d43..000000000000 --- a/package.datascience-ui.dependencies.json +++ /dev/null @@ -1,307 +0,0 @@ -[ - "@babel/runtime", - "@babel/runtime-corejs2", - "@blueprintjs/core", - "@blueprintjs/icons", - "@blueprintjs/select", - "@emotion/hash", - "@emotion/memoize", - "@emotion/stylis", - "@emotion/unitless", - "@icons/material", - "@jupyter-widgets/controls", - "@jupyterlab/services", - "@loadable/component", - "@mapbox/polylabel", - "@nteract/markdown", - "@nteract/mathjax", - "@nteract/octicons", - "@nteract/styled-blueprintjsx", - "@nteract/transform-dataresource", - "@nteract/transform-geojson", - "@nteract/transform-model-debug", - "@nteract/transform-plotly", - "@nteract/transform-vdom", - "@nteract/transform-vega", - "@nteract/transforms", - "@nteract/vega-embed-v2", - "@nteract/vega-embed-v3", - "@phosphor/algorithm", - "@phosphor/coreutils", - "@phosphor/properties", - "@phosphor/signaling", - "ajv", - "anser", - "ansi-regex", - "ansi-to-html", - "ansi-to-react", - "asn1.js", - "babel-polyfill", - "babel-runtime", - "bail", - "base16", - "base64-js", - "bintrees", - "bn.js", - "bootstrap-less", - "brorand", - "browserify-aes", - "browserify-cipher", - "browserify-des", - "browserify-rsa", - "browserify-sign", - "buffer-xor", - "canvas", - "character-entities-legacy", - "character-reference-invalid", - "cipher-base", - "classnames", - "clsx", - "collapse-white-space", - "core-util-is", - "create-ecdh", - "create-emotion", - "create-hash", - "create-hmac", - "create-react-context", - "crypto-browserify", - "css-loader", - "d3", - "d3-array", - "d3-bboxCollide", - "d3-brush", - "d3-chord", - "d3-cloud", - "d3-collection", - "d3-color", - "d3-contour", - "d3-delaunay", - "d3-dispatch", - "d3-drag", - "d3-dsv", - "d3-ease", - "d3-force", - "d3-format", - "d3-geo", - "d3-glyphedge", - "d3-hexbin", - "d3-hierarchy", - "d3-interpolate", - "d3-path", - "d3-polygon", - "d3-quadtree", - "d3-request", - "d3-sankey-circular", - "d3-scale", - "d3-scale-chromatic", - "d3-selection", - "d3-shape", - "d3-time", - "d3-time-format", - "d3-timer", - "d3-transition", - "d3-voronoi", - "datalib", - "define-properties", - "delaunator", - "des.js", - "diffie-hellman", - "dom-helpers", - "dom4", - "elliptic", - "emotion", - "entities", - "escape-carriage", - "events", - "evp_bytestokey", - "extend", - "fast-deep-equal", - "fast-json-stable-stringify", - "fast-plist", - "function-bind", - "gud", - "has", - "hash-base", - "hash.js", - "hmac-drbg", - "hoist-non-react-statics", - "ieee754", - "inherits", - "invariant", - "is-alphabetical", - "is-alphanumerical", - "is-arguments", - "is-buffer", - "is-date-object", - "is-decimal", - "is-hexadecimal", - "is-online", - "is-plain-obj", - "is-regex", - "is-whitespace-character", - "is-word-character", - "isarray", - "json-schema-traverse", - "json-stable-stringify", - "json-stringify-pretty-compact", - "json2csv", - "json5", - "jsonify", - "labella", - "leaflet", - "linear-layout-vector", - "lodash", - "lodash.curry", - "lodash.flow", - "lru-cache", - "markdown-escapes", - "martinez-polygon-clipping", - "material-colors", - "md5.js", - "mdast-add-list-metadata", - "miller-rabin", - "minimalistic-assert", - "minimalistic-crypto-utils", - "minimist", - "moment", - "monaco-editor", - "monaco-editor-textmate", - "monaco-textmate", - "node-libs-browser", - "numeral", - "object-assign", - "object-is", - "object-keys", - "onigasm", - "parse-asn1", - "parse-entities", - "path-browserify", - "path-posix", - "pbkdf2", - "plotly.js-dist", - "polygon-offset", - "popper.js", - "process", - "prop-types", - "pseudomap", - "public-encrypt", - "public-ip", - "pure-color", - "querystringify", - "randombytes", - "randomfill", - "react", - "react-annotation", - "react-base16-styling", - "react-color", - "react-data-grid", - "react-dom", - "react-draggable", - "react-hot-loader", - "react-is", - "react-json-tree", - "react-lifecycles-compat", - "react-markdown", - "react-popper", - "react-redux", - "react-svg-pan-zoom", - "react-svgmt", - "react-table", - "react-table-hoc-fixed-columns", - "react-transition-group", - "react-virtualized", - "reactcss", - "redux", - "redux-logger", - "regexp.prototype.flags", - "regression", - "remark-parse", - "repeat-string", - "replace-ext", - "requires-port", - "resize-observer-polyfill", - "ripemd160", - "roughjs-es5", - "rxjs", - "rxjs-compat", - "safe-buffer", - "scheduler", - "semiotic", - "semiotic-mark", - "semver", - "setimmediate", - "sha.js", - "slickgrid", - "state-toggle", - "stream-browserify", - "string-hash", - "string_decoder", - "style-loader", - "styled-jsx", - "stylis-rule-sheet", - "svg-inline-react", - "svg-path-bounding-box", - "svgpath", - "timers-browserify", - "tinycolor2", - "tinyqueue", - "topojson-client", - "transformation-matrix", - "trim", - "trim-trailing-lines", - "trough", - "tslib", - "unherit", - "unified", - "uniqid", - "unist-util-is", - "unist-util-remove-position", - "unist-util-stringify-position", - "unist-util-visit", - "unist-util-visit-parents", - "uri-js", - "url-parse", - "util", - "util-deprecate", - "uuid", - "vega", - "vega-canvas", - "vega-crossfilter", - "vega-dataflow", - "vega-embed", - "vega-encode", - "vega-event-selector", - "vega-expression", - "vega-force", - "vega-functions", - "vega-geo", - "vega-hierarchy", - "vega-lite", - "vega-loader", - "vega-parser", - "vega-projection", - "vega-regression", - "vega-runtime", - "vega-scale", - "vega-scenegraph", - "vega-schema-url-parser", - "vega-selections", - "vega-statistics", - "vega-themes", - "vega-tooltip", - "vega-transforms", - "vega-util", - "vega-view", - "vega-view-transforms", - "vega-voronoi", - "vega-wordcloud", - "vfile", - "vfile-location", - "vfile-message", - "viz-annotation", - "vm-browserify", - "warning", - "x-is-string", - "xtend", - "yallist" -] diff --git a/package.json b/package.json index 36efaeb2c20c..2a27cddc0976 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,33 @@ { "name": "python", "displayName": "Python", - "description": "Linting, Debugging (multi-threaded, remote), Intellisense, Jupyter Notebooks, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2020.10.0-dev", + "description": "Python language support with extension access points for IntelliSense (Pylance), Debugging (Python Debugger), linting, formatting, refactoring, unit tests, and more.", + "version": "2026.5.0-dev", "featureFlags": { "usingNewInterpreterStorage": true }, - "languageServerVersion": "0.5.30", + "capabilities": { + "untrustedWorkspaces": { + "supported": false, + "description": "The Python extension is not available in untrusted workspaces. Use Pylance to get partial IntelliSense support for Python files." + }, + "virtualWorkspaces": { + "supported": "limited", + "description": "Only Partial IntelliSense supported." + } + }, "publisher": "ms-python", - "enableProposedApi": true, + "enabledApiProposals": [ + "contribEditorContentMenu", + "quickPickSortByLabel", + "testObserver", + "quickPickItemTooltip", + "terminalDataWriteEvent", + "terminalExecuteCommandEvent", + "codeActionAI", + "notebookReplDocument", + "notebookVariableProvider" + ], "author": { "name": "Microsoft Corporation" }, @@ -21,15 +40,16 @@ "bugs": { "url": "https://github.com/Microsoft/vscode-python/issues" }, - "qna": "https://stackoverflow.com/questions/tagged/visual-studio-code+python", + "qna": "https://github.com/microsoft/vscode-python/discussions/categories/q-a", "icon": "icon.png", "galleryBanner": { "color": "#1e415e", "theme": "dark" }, "engines": { - "vscode": "^1.48.0" + "vscode": "^1.95.0" }, + "enableTelemetry": false, "keywords": [ "python", "django", @@ -39,3332 +59,1435 @@ "categories": [ "Programming Languages", "Debuggers", - "Linters", - "Snippets", - "Formatters", "Other", - "Extension Packs", "Data Science", - "Machine Learning", - "Notebooks" + "Machine Learning" ], "activationEvents": [ + "onDebugInitialConfigurations", "onLanguage:python", - "onLanguage:jupyter", "onDebugResolve:python", - "onCommand:python.execInTerminal", - "onCommand:python.sortImports", - "onCommand:python.runtests", - "onCommand:python.debugtests", - "onCommand:python.setInterpreter", - "onCommand:python.setShebangInterpreter", - "onCommand:python.viewTestUI", - "onCommand:python.viewLanguageServerOutput", - "onCommand:python.viewTestOutput", - "onCommand:python.viewOutput", - "onCommand:python.datascience.viewJupyterOutput", - "onCommand:python.datascience.export", - "onCommand:python.datascience.exportAsPythonScript", - "onCommand:python.datascience.exportToHTML", - "onCommand:python.datascience.exportToPDF", - "onCommand:python.selectAndRunTestMethod", - "onCommand:python.selectAndDebugTestMethod", - "onCommand:python.selectAndRunTestFile", - "onCommand:python.runCurrentTestFile", - "onCommand:python.runFailedTests", - "onCommand:python.execSelectionInTerminal", - "onCommand:python.execSelectionInDjangoShell", - "onCommand:python.buildWorkspaceSymbols", - "onCommand:python.startREPL", - "onCommand:python.goToPythonObject", - "onCommand:python.setLinter", - "onCommand:python.enableLinting", - "onCommand:python.createTerminal", - "onCommand:python.discoverTests", - "onCommand:python.configureTests", - "onCommand:python.switchOffInsidersChannel", - "onCommand:python.switchToDailyChannel", - "onCommand:python.switchToWeeklyChannel", - "onCommand:python.clearWorkspaceInterpreter", - "onCommand:python.resetInterpreterSecurityStorage", - "onCommand:python.datascience.createnewnotebook", - "onCommand:python.startPage.open", - "onCommand:python.datascience.createnewinteractive", - "onCommand:python.datascience.importnotebook", - "onCommand:python.datascience.importnotebookfile", - "onCommand:python.datascience.opennotebook", - "onCommand:python.datascience.opennotebookInPreviewEditor", - "onCommand:python.datascience.selectjupyteruri", - "onCommand:python.datascience.exportfileasnotebook", - "onCommand:python.datascience.exportfileandoutputasnotebook", - "onCommand:python.datascience.selectJupyterInterpreter", - "onCommand:python.datascience.selectjupytercommandline", - "onCommand:python.enableSourceMapSupport", - "onNotebook:jupyter-notebook", + "onCommand:python.copilotSetupTests", "workspaceContains:mspythonconfig.json", "workspaceContains:pyproject.toml", - "onCustomEditor:ms-python.python.notebook.ipynb" + "workspaceContains:Pipfile", + "workspaceContains:setup.py", + "workspaceContains:requirements.txt", + "workspaceContains:pylock.toml", + "workspaceContains:**/pylock.*.toml", + "workspaceContains:manage.py", + "workspaceContains:app.py", + "workspaceContains:.venv", + "workspaceContains:.conda", + "onLanguageModelTool:get_python_environment_details", + "onLanguageModelTool:get_python_executable_details", + "onLanguageModelTool:install_python_packages", + "onLanguageModelTool:configure_python_environment", + "onLanguageModelTool:create_virtual_environment", + "onTerminalShellIntegration:python" ], "main": "./out/client/extension", + "browser": "./dist/extension.browser.js", + "l10n": "./l10n", "contributes": { - "snippets": [ + "problemMatchers": [ { - "language": "python", - "path": "./snippets/python.json" + "name": "python", + "owner": "python", + "source": "python", + "fileLocation": "autoDetect", + "pattern": [ + { + "regexp": "^.*File \\\"([^\\\"]|.*)\\\", line (\\d+).*", + "file": 1, + "line": 2 + }, + { + "regexp": "^\\s*(.*)\\s*$" + }, + { + "regexp": "^\\s*(.*Error.*)$", + "message": 1 + } + ] } ], - "keybindings": [ - { - "command": "python.execSelectionInTerminal", - "key": "shift+enter", - "when": "editorTextFocus && editorLangId == python && !findInputFocussed && !replaceInputFocussed && !python.datascience.ownsSelection && !notebookEditorFocused" - }, - { - "command": "python.datascience.execSelectionInteractive", - "key": "shift+enter", - "when": "editorTextFocus && editorLangId == python && !findInputFocussed && !replaceInputFocussed && python.datascience.ownsSelection && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "command": "python.datascience.runcurrentcelladvance", - "key": "shift+enter", - "when": "editorTextFocus && !editorHasSelection && python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "command": "python.datascience.runcurrentcell", - "key": "ctrl+enter", - "when": "editorTextFocus && !editorHasSelection && python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "command": "python.datascience.runcurrentcellandaddbelow", - "key": "alt+enter", - "when": "editorTextFocus && !editorHasSelection && python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "mac": "F", - "win": "F", - "linux": "F", - "key": "F", - "when": "notebookEditorFocused && !inputFocus && notebookViewType == jupyter-notebook", - "command": "notebook.find" - }, - { - "mac": "K", - "win": "K", - "linux": "K", - "key": "K", - "when": "notebookEditorFocused && !inputFocus && notebookViewType == jupyter-notebook", - "command": "list.focusUp" - }, - { - "mac": "J", - "win": "J", - "linux": "J", - "key": "J", - "when": "notebookEditorFocused && !inputFocus && notebookViewType == jupyter-notebook", - "command": "list.focusDown" - }, - { - "mac": "A", - "win": "A", - "linux": "A", - "key": "A", - "when": "notebookEditorFocused && !inputFocus && notebookViewType == jupyter-notebook", - "command": "notebook.cell.insertCodeCellAbove" - }, - { - "mac": "B", - "win": "B", - "linux": "B", - "key": "B", - "when": "notebookEditorFocused && !inputFocus && notebookViewType == jupyter-notebook", - "command": "notebook.cell.insertCodeCellBelow" - }, - { - "mac": "D D", - "win": "D D", - "linux": "D D", - "key": "D D", - "when": "notebookEditorFocused && !inputFocus && notebookViewType == jupyter-notebook", - "command": "notebook.cell.delete" - }, - { - "mac": "Z", - "win": "Z", - "linux": "Z", - "key": "Z", - "when": "notebookEditorFocused && !inputFocus && notebookViewType == jupyter-notebook", - "command": "notebook.undo" - }, - { - "mac": "C", - "win": "C", - "linux": "C", - "key": "C", - "when": "notebookEditorFocused && !inputFocus && notebookViewType == jupyter-notebook", - "command": "notebook.cell.copy" - }, - { - "mac": "X", - "win": "X", - "linux": "X", - "key": "X", - "when": "notebookEditorFocused && !inputFocus && notebookViewType == jupyter-notebook", - "command": "notebook.cell.cut" - }, - { - "mac": "V", - "win": "V", - "linux": "V", - "key": "V", - "when": "notebookEditorFocused && !inputFocus && notebookViewType == jupyter-notebook", - "command": "notebook.cell.paste" - }, - { - "mac": "ctrl+shift+-", - "win": "ctrl+shift+-", - "linux": "ctrl+shift+-", - "when": "editorTextFocus && inputFocus && notebookEditorFocused && notebookViewType == jupyter-notebook", - "command": "notebook.cell.split" - }, - { - "command": "python.datascience.insertCellBelowPosition", - "key": "ctrl+; s", - "when": "editorTextFocus && python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "command": "python.datascience.insertCellBelow", - "key": "ctrl+; b", - "when": "editorTextFocus && python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "command": "python.datascience.insertCellAbove", - "key": "ctrl+; a", - "when": "editorTextFocus && python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "command": "python.datascience.deleteCells", - "key": "ctrl+; x", - "when": "editorTextFocus && python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "command": "python.datascience.extendSelectionByCellAbove", - "key": "ctrl+alt+shift+[", - "when": "editorTextFocus && python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "command": "python.datascience.extendSelectionByCellBelow", - "key": "ctrl+alt+shift+]", - "when": "editorTextFocus && python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "command": "python.datascience.moveCellsUp", - "key": "ctrl+; u", - "when": "editorTextFocus && python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" - }, + "walkthroughs": [ { - "command": "python.datascience.moveCellsDown", - "key": "ctrl+; d", - "when": "editorTextFocus && python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" + "id": "pythonWelcome", + "title": "%walkthrough.pythonWelcome.title%", + "description": "%walkthrough.pythonWelcome.description%", + "when": "workspacePlatform != webworker", + "steps": [ + { + "id": "python.createPythonFolder", + "title": "%walkthrough.step.python.createPythonFolder.title%", + "description": "%walkthrough.step.python.createPythonFolder.description%", + "media": { + "svg": "resources/walkthrough/open-folder.svg", + "altText": "%walkthrough.step.python.createPythonFile.altText%" + }, + "when": "workspaceFolderCount = 0" + }, + { + "id": "python.createPythonFile", + "title": "%walkthrough.step.python.createPythonFile.title%", + "description": "%walkthrough.step.python.createPythonFile.description%", + "media": { + "svg": "resources/walkthrough/open-folder.svg", + "altText": "%walkthrough.step.python.createPythonFile.altText%" + } + }, + { + "id": "python.installPythonWin8", + "title": "%walkthrough.step.python.installPythonWin8.title%", + "description": "%walkthrough.step.python.installPythonWin8.description%", + "media": { + "markdown": "resources/walkthrough/install-python-windows-8.md" + }, + "when": "workspacePlatform == windows && showInstallPythonTile" + }, + { + "id": "python.installPythonMac", + "title": "%walkthrough.step.python.installPythonMac.title%", + "description": "%walkthrough.step.python.installPythonMac.description%", + "media": { + "markdown": "resources/walkthrough/install-python-macos.md" + }, + "when": "workspacePlatform == mac && showInstallPythonTile", + "command": "workbench.action.terminal.new" + }, + { + "id": "python.installPythonLinux", + "title": "%walkthrough.step.python.installPythonLinux.title%", + "description": "%walkthrough.step.python.installPythonLinux.description%", + "media": { + "markdown": "resources/walkthrough/install-python-linux.md" + }, + "when": "workspacePlatform == linux && showInstallPythonTile", + "command": "workbench.action.terminal.new" + }, + { + "id": "python.createEnvironment", + "title": "%walkthrough.step.python.createEnvironment.title%", + "description": "%walkthrough.step.python.createEnvironment.description%", + "media": { + "svg": "resources/walkthrough/create-environment.svg", + "altText": "%walkthrough.step.python.createEnvironment.altText%" + } + }, + { + "id": "python.runAndDebug", + "title": "%walkthrough.step.python.runAndDebug.title%", + "description": "%walkthrough.step.python.runAndDebug.description%", + "media": { + "svg": "resources/walkthrough/rundebug2.svg", + "altText": "%walkthrough.step.python.runAndDebug.altText%" + } + }, + { + "id": "python.learnMoreWithDS", + "title": "%walkthrough.step.python.learnMoreWithDS.title%", + "description": "%walkthrough.step.python.learnMoreWithDS.description%", + "media": { + "altText": "%walkthrough.step.python.learnMoreWithDS.altText%", + "svg": "resources/walkthrough/learnmore.svg" + } + } + ] }, { - "command": "python.datascience.changeCellToMarkdown", - "key": "ctrl+; m", - "when": "editorTextFocus && python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" - }, + "id": "pythonDataScienceWelcome", + "title": "%walkthrough.pythonDataScienceWelcome.title%", + "description": "%walkthrough.pythonDataScienceWelcome.description%", + "when": "false", + "steps": [ + { + "id": "python.installJupyterExt", + "title": "%walkthrough.step.python.installJupyterExt.title%", + "description": "%walkthrough.step.python.installJupyterExt.description%", + "media": { + "svg": "resources/walkthrough/data-science.svg", + "altText": "%walkthrough.step.python.installJupyterExt.altText%" + } + }, + { + "id": "python.createNewNotebook", + "title": "%walkthrough.step.python.createNewNotebook.title%", + "description": "%walkthrough.step.python.createNewNotebook.description%", + "media": { + "svg": "resources/walkthrough/create-notebook.svg", + "altText": "%walkthrough.step.python.createNewNotebook.altText%" + }, + "completionEvents": [ + "onCommand:jupyter.createnewnotebook", + "onCommand:workbench.action.files.openFolder", + "onCommand:workbench.action.files.openFileFolder" + ] + }, + { + "id": "python.openInteractiveWindow", + "title": "%walkthrough.step.python.openInteractiveWindow.title%", + "description": "%walkthrough.step.python.openInteractiveWindow.description%", + "media": { + "svg": "resources/walkthrough/interactive-window.svg", + "altText": "%walkthrough.step.python.openInteractiveWindow.altText%" + }, + "completionEvents": [ + "onCommand:jupyter.createnewinteractive" + ] + }, + { + "id": "python.dataScienceLearnMore", + "title": "%walkthrough.step.python.dataScienceLearnMore.title%", + "description": "%walkthrough.step.python.dataScienceLearnMore.description%", + "media": { + "svg": "resources/walkthrough/learnmore.svg", + "altText": "%walkthrough.step.python.dataScienceLearnMore.altText%" + } + } + ] + } + ], + "breakpoints": [ { - "command": "python.datascience.changeCellToCode", - "key": "ctrl+; c", - "when": "editorTextFocus && python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" + "language": "html" }, { - "command": "python.datascience.gotoNextCellInFile", - "key": "ctrl+alt+]", - "when": "editorTextFocus && python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" + "language": "jinja" }, { - "command": "python.datascience.gotoPrevCellInFile", - "key": "ctrl+alt+[", - "when": "editorTextFocus && python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" + "language": "python" }, { - "command": "python.datascience.selectCellContents", - "key": "ctrl+alt+\\", - "when": "editorTextFocus && python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" + "language": "django-html" }, { - "command": "python.datascience.selectCell", - "key": "ctrl+alt+shift+\\", - "when": "editorTextFocus && python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" + "language": "django-txt" } ], "commands": [ { - "command": "python.enableSourceMapSupport", - "title": "%python.command.python.enableSourceMapSupport.title%", - "category": "Python" - }, - { - "command": "python.sortImports", - "title": "%python.command.python.sortImports.title%", - "category": "Python Refactor" - }, - { - "command": "python.startREPL", - "title": "%python.command.python.startREPL.title%", - "category": "Python" - }, - { - "command": "python.createTerminal", - "title": "%python.command.python.createTerminal.title%", - "category": "Python" - }, - { - "command": "python.buildWorkspaceSymbols", - "title": "%python.command.python.buildWorkspaceSymbols.title%", - "category": "Python" - }, - { - "command": "python.openTestNodeInEditor", - "title": "Open", - "icon": { - "light": "resources/light/open-file.svg", - "dark": "resources/dark/open-file.svg" - } - }, - { - "command": "python.runTestNode", - "title": "Run", - "icon": { - "light": "resources/light/start.svg", - "dark": "resources/dark/start.svg" - } - }, - { - "command": "python.debugTestNode", - "title": "Debug", - "icon": { - "light": "resources/light/debug.svg", - "dark": "resources/dark/debug.svg" - } - }, - { - "command": "python.runtests", - "title": "%python.command.python.runtests.title%", + "title": "%python.command.python.createNewFile.title%", + "shortTitle": "%python.menu.createNewFile.title%", "category": "Python", - "icon": { - "light": "resources/light/run-tests.svg", - "dark": "resources/dark/run-tests.svg" - } + "command": "python.createNewFile" }, { - "command": "python.debugtests", - "title": "%python.command.python.debugtests.title%", "category": "Python", - "icon": { - "light": "resources/light/debug.svg", - "dark": "resources/dark/debug.svg" - } - }, - { - "command": "python.execInTerminal", - "title": "%python.command.python.execInTerminal.title%", - "category": "Python" + "command": "python.copyTestId", + "title": "%python.command.python.testing.copyTestId.title%" }, { - "command": "python.execInTerminal-icon", - "title": "%python.command.python.execInTerminal.title%", "category": "Python", - "icon": { - "light": "resources/light/run-file.svg", - "dark": "resources/dark/run-file.svg" - } - }, - { - "command": "python.setInterpreter", - "title": "%python.command.python.setInterpreter.title%", - "category": "Python" - }, - { - "command": "python.switchOffInsidersChannel", - "title": "%python.command.python.switchOffInsidersChannel.title%", - "category": "Python" - }, - { - "command": "python.switchToDailyChannel", - "title": "%python.command.python.switchToDailyChannel.title%", - "category": "Python" + "command": "python.analysis.restartLanguageServer", + "title": "%python.command.python.analysis.restartLanguageServer.title%" }, { - "command": "python.switchToWeeklyChannel", - "title": "%python.command.python.switchToWeeklyChannel.title%", - "category": "Python" + "category": "Python", + "command": "python.clearCacheAndReload", + "title": "%python.command.python.clearCacheAndReload.title%" }, { + "category": "Python", "command": "python.clearWorkspaceInterpreter", - "title": "%python.command.python.clearWorkspaceInterpreter.title%", - "category": "Python" - }, - { - "command": "python.resetInterpreterSecurityStorage", - "title": "%python.command.python.resetInterpreterSecurityStorage.title%", - "category": "Python" - }, - { - "command": "python.refactorExtractVariable", - "title": "%python.command.python.refactorExtractVariable.title%", - "category": "Python Refactor" - }, - { - "command": "python.refactorExtractMethod", - "title": "%python.command.python.refactorExtractMethod.title%", - "category": "Python Refactor" + "title": "%python.command.python.clearWorkspaceInterpreter.title%" }, { - "command": "python.viewTestOutput", - "title": "%python.command.python.viewTestOutput.title%", "category": "Python", - "icon": { - "light": "resources/light/repl.svg", - "dark": "resources/dark/repl.svg" - } - }, - { - "command": "python.datascience.viewJupyterOutput", - "title": "%python.command.python.datascience.viewJupyterOutput.title%", - "category": "Python" + "command": "python.configureTests", + "title": "%python.command.python.configureTests.title%" }, { - "command": "python.datascience.export", - "title": "%DataScience.notebookExportAs%", "category": "Python", - "icon": { - "light": "resources/light/export_to_python.svg", - "dark": "resources/dark/export_to_python.svg" - }, - "enablement": "notebookViewType == jupyter-notebook && python.datascience.isnotebooktrusted" - }, - { - "command": "python.datascience.exportAsPythonScript", - "title": "%python.command.python.datascience.exportAsPythonScript.title%", - "category": "Python" - }, - { - "command": "python.datascience.exportToHTML", - "title": "%python.command.python.datascience.exportToHTML.title%", - "category": "Python" - }, - { - "command": "python.datascience.exportToPDF", - "title": "%python.command.python.datascience.exportToPDF.title%", - "category": "Python" - }, - { - "command": "python.datascience.selectJupyterInterpreter", - "title": "%python.command.python.datascience.selectJupyterInterpreter.title%", - "category": "Python" + "command": "python.createTerminal", + "title": "%python.command.python.createTerminal.title%" }, { - "command": "python.viewLanguageServerOutput", - "title": "%python.command.python.viewLanguageServerOutput.title%", "category": "Python", - "enablement": "python.hasLanguageServerOutputChannel" + "command": "python.createEnvironment", + "title": "%python.command.python.createEnvironment.title%" }, { - "command": "python.viewOutput", - "title": "%python.command.python.viewOutput.title%", "category": "Python", - "icon": { - "light": "resources/light/repl.svg", - "dark": "resources/dark/repl.svg" - } - }, - { - "command": "python.selectAndRunTestMethod", - "title": "%python.command.python.selectAndRunTestMethod.title%", - "category": "Python" - }, - { - "command": "python.selectAndDebugTestMethod", - "title": "%python.command.python.selectAndDebugTestMethod.title%", - "category": "Python" + "command": "python.createEnvironment-button", + "title": "%python.command.python.createEnvironment.title%" }, { - "command": "python.selectAndRunTestFile", - "title": "%python.command.python.selectAndRunTestFile.title%", - "category": "Python" - }, - { - "command": "python.runCurrentTestFile", - "title": "%python.command.python.runCurrentTestFile.title%", - "category": "Python" - }, - { - "command": "python.runFailedTests", - "title": "%python.command.python.runFailedTests.title%", "category": "Python", - "icon": { - "light": "resources/light/run-failed-tests.svg", - "dark": "resources/dark/run-failed-tests.svg" - } + "command": "python.execInTerminal", + "title": "%python.command.python.execInTerminal.title%" }, { - "command": "python.discoverTests", - "title": "%python.command.python.discoverTests.title%", "category": "Python", - "icon": { - "light": "resources/light/refresh.svg", - "dark": "resources/dark/refresh.svg" - } + "command": "python.execInTerminal-icon", + "icon": "$(play)", + "title": "%python.command.python.execInTerminalIcon.title%" }, { - "command": "python.discoveringTests", - "title": "%python.command.python.discoveringTests.title%", "category": "Python", - "icon": { - "light": "resources/light/discovering-tests.svg", - "dark": "resources/dark/discovering-tests.svg" - } + "command": "python.execInDedicatedTerminal", + "icon": "$(play)", + "title": "%python.command.python.execInDedicatedTerminal.title%" }, { - "command": "python.stopTests", - "title": "%python.command.python.stopTests.title%", "category": "Python", - "icon": { - "light": "resources/light/stop.svg", - "dark": "resources/dark/stop.svg" - } - }, - { - "command": "python.configureTests", - "title": "%python.command.python.configureTests.title%", - "category": "Python" + "command": "python.execSelectionInDjangoShell", + "title": "%python.command.python.execSelectionInDjangoShell.title%" }, { + "category": "Python", "command": "python.execSelectionInTerminal", "title": "%python.command.python.execSelectionInTerminal.title%", - "category": "Python" - }, - { - "command": "python.execSelectionInDjangoShell", - "title": "%python.command.python.execSelectionInDjangoShell.title%", - "category": "Python" - }, - { - "command": "python.goToPythonObject", - "title": "%python.command.python.goToPythonObject.title%", - "category": "Python" - }, - { - "command": "python.setLinter", - "title": "%python.command.python.setLinter.title%", - "category": "Python" - }, - { - "command": "python.enableLinting", - "title": "%python.command.python.enableLinting.title%", - "category": "Python" - }, - { - "command": "python.runLinting", - "title": "%python.command.python.runLinting.title%", - "category": "Python" - }, - { - "command": "python.datascience.runcurrentcell", - "title": "%python.command.python.datascience.runcurrentcell.title%", - "category": "Python" - }, - { - "command": "python.datascience.debugcell", - "title": "%python.command.python.datascience.debugcell.title%", - "category": "Python" - }, - { - "command": "python.datascience.debugstepover", - "title": "%python.command.python.datascience.debugstepover.title%", - "category": "Python" - }, - { - "command": "python.datascience.debugstop", - "title": "%python.command.python.datascience.debugstop.title%", - "category": "Python" - }, - { - "command": "python.datascience.debugcontinue", - "title": "%python.command.python.datascience.debugcontinue.title%", - "category": "Python" - }, - { - "command": "python.datascience.insertCellBelowPosition", - "title": "%python.command.python.datascience.insertCellBelowPosition.title%", - "category": "Python" - }, - { - "command": "python.datascience.insertCellBelow", - "title": "%python.command.python.datascience.insertCellBelow.title%", - "category": "Python" - }, - { - "command": "python.datascience.insertCellAbove", - "title": "%python.command.python.datascience.insertCellAbove.title%", - "category": "Python" - }, - { - "command": "python.datascience.deleteCells", - "title": "%python.command.python.datascience.deleteCells.title%", - "category": "Python" + "shortTitle": "%python.command.python.execSelectionInTerminal.shortTitle%" }, { - "command": "python.datascience.selectCell", - "title": "%python.command.python.datascience.selectCell.title%", - "category": "Python" - }, - { - "command": "python.datascience.selectCellContents", - "title": "%python.command.python.datascience.selectCellContents.title%", - "category": "Python" - }, - { - "command": "python.datascience.extendSelectionByCellAbove", - "title": "%python.command.python.datascience.extendSelectionByCellAbove.title%", - "category": "Python" - }, - { - "command": "python.datascience.extendSelectionByCellBelow", - "title": "%python.command.python.datascience.extendSelectionByCellBelow.title%", - "category": "Python" - }, - { - "command": "python.datascience.moveCellsUp", - "title": "%python.command.python.datascience.moveCellsUp.title%", - "category": "Python" - }, - { - "command": "python.datascience.moveCellsDown", - "title": "%python.command.python.datascience.moveCellsDown.title%", - "category": "Python" - }, - { - "command": "python.datascience.changeCellToMarkdown", - "title": "%python.command.python.datascience.changeCellToMarkdown.title%", - "category": "Python" - }, - { - "command": "python.datascience.changeCellToCode", - "title": "%python.command.python.datascience.changeCellToCode.title%", - "category": "Python" - }, - { - "command": "python.datascience.gotoNextCellInFile", - "title": "%python.command.python.datascience.gotoNextCellInFile.title%", - "category": "Python" - }, - { - "command": "python.datascience.gotoPrevCellInFile", - "title": "%python.command.python.datascience.gotoPrevCellInFile.title%", - "category": "Python" + "category": "Python", + "command": "python.execInREPL", + "title": "%python.command.python.execInREPL.title%" }, { - "command": "python.datascience.runcurrentcelladvance", - "title": "%python.command.python.datascience.runcurrentcelladvance.title%", - "category": "Python" + "category": "Python", + "command": "python.reportIssue", + "title": "%python.command.python.reportIssue.title%" }, { - "command": "python.datascience.runcurrentcellandallbelow.palette", - "title": "%python.command.python.datascience.runcurrentcellandallbelow.palette.title%", - "category": "Python" + "category": "Test", + "command": "testing.reRunFailTests", + "icon": "$(run-errors)", + "title": "%python.command.testing.rerunFailedTests.title%" }, { - "command": "python.datascience.runallcellsabove.palette", - "title": "%python.command.python.datascience.runallcellsabove.palette.title%", - "category": "Python" + "category": "Python", + "command": "python.setInterpreter", + "title": "%python.command.python.setInterpreter.title%" }, { - "command": "python.datascience.debugcurrentcell.palette", - "title": "%python.command.python.datascience.debugcurrentcell.palette.title%", - "category": "Python" + "category": "Python", + "command": "python.startREPL", + "title": "%python.command.python.startTerminalREPL.title%" }, { - "command": "python.datascience.execSelectionInteractive", - "title": "%python.command.python.datascience.execSelectionInteractive.title%", - "category": "Python" + "category": "Python", + "command": "python.startNativeREPL", + "title": "%python.command.python.startNativeREPL.title%" }, { - "command": "python.datascience.createnewinteractive", - "title": "%python.command.python.datascience.createnewinteractive.title%", - "category": "Python" + "category": "Python", + "command": "python.viewLanguageServerOutput", + "enablement": "python.hasLanguageServerOutputChannel", + "title": "%python.command.python.viewLanguageServerOutput.title%" }, { - "command": "python.datascience.runFileInteractive", - "title": "%python.command.python.datascience.runFileInteractive.title%", - "category": "Python" + "category": "Python", + "command": "python.viewOutput", + "icon": { + "dark": "resources/dark/repl.svg", + "light": "resources/light/repl.svg" + }, + "title": "%python.command.python.viewOutput.title%" }, { - "command": "python.datascience.debugFileInteractive", - "title": "%python.command.python.datascience.debugFileInteractive.title%", - "category": "Python" - }, - { - "command": "python.datascience.runallcells", - "title": "%python.command.python.datascience.runallcells.title%", - "category": "Python" - }, - { - "command": "python.datascience.runallcellsabove", - "title": "%python.command.python.datascience.runallcellsabove.title%", - "category": "Python" - }, - { - "command": "python.datascience.runcellandallbelow", - "title": "%python.command.python.datascience.runcellandallbelow.title%", - "category": "Python" - }, - { - "command": "python.datascience.runcell", - "title": "%python.command.python.datascience.runcell.title%", - "category": "Python" - }, - { - "command": "python.datascience.runtoline", - "title": "%python.command.python.datascience.runtoline.title%", - "category": "Python" - }, - { - "command": "python.datascience.runfromline", - "title": "%python.command.python.datascience.runfromline.title%", - "category": "Python" - }, - { - "command": "python.datascience.selectjupyteruri", - "title": "%python.command.python.datascience.selectjupyteruri.title%", - "category": "Python", - "when": "python.datascience.featureenabled" - }, - { - "command": "python.datascience.selectjupytercommandline", - "title": "%python.command.python.datascience.selectjupytercommandline.title%", - "category": "Python", - "when": "python.datascience.featureenabled" - }, - { - "command": "python.datascience.importnotebook", - "title": "%python.command.python.datascience.importnotebook.title%", - "category": "Python" - }, - { - "command": "python.datascience.importnotebookfile", - "title": "%python.command.python.datascience.importnotebookfile.title%", - "category": "Python" - }, - { - "command": "python.datascience.opennotebook", - "title": "%python.command.python.datascience.opennotebook.title%", - "category": "Python" - }, - { - "command": "python.datascience.opennotebookInPreviewEditor", - "title": "%python.command.python.datascience.opennotebookInPreviewEditor.title%", - "category": "Python" - }, - { - "command": "python.datascience.exportoutputasnotebook", - "title": "%python.command.python.datascience.exportoutputasnotebook.title%", - "category": "Python" - }, - { - "command": "python.datascience.exportfileasnotebook", - "title": "%python.command.python.datascience.exportfileasnotebook.title%", - "category": "Python" - }, - { - "command": "python.datascience.exportfileandoutputasnotebook", - "title": "%python.command.python.datascience.exportfileandoutputasnotebook.title%", - "category": "Python" - }, - { - "command": "python.datascience.undocells", - "title": "%python.command.python.datascience.undocells.title%", - "category": "Python" - }, - { - "command": "python.datascience.redocells", - "title": "%python.command.python.datascience.redocells.title%", - "category": "Python" - }, - { - "command": "python.datascience.notebookeditor.undocells", - "title": "%python.command.python.datascience.undocells.title%", - "category": "Python" - }, - { - "command": "python.datascience.notebookeditor.redocells", - "title": "%python.command.python.datascience.redocells.title%", - "category": "Python" - }, - { - "command": "python.datascience.removeallcells", - "title": "%python.command.python.datascience.removeallcells.title%", - "category": "Python" - }, - { - "command": "python.datascience.interruptkernel", - "title": "%python.command.python.datascience.interruptkernel.title%", - "category": "Python" - }, - { - "command": "python.datascience.restartkernel", - "title": "%python.command.python.datascience.restartkernel.title%", - "category": "Python" - }, - { - "command": "python.datascience.notebookeditor.removeallcells", - "title": "%python.command.python.datascience.notebookeditor.removeallcells.title%", - "category": "Python" - }, - { - "command": "python.datascience.notebookeditor.interruptkernel", - "title": "%python.command.python.datascience.interruptkernel.title%", - "category": "Python" - }, - { - "command": "python.datascience.notebookeditor.restartkernel", - "title": "%python.command.python.datascience.restartkernel.title%", - "category": "Python", - "icon": { - "light": "resources/light/restart-kernel.svg", - "dark": "resources/dark/restart-kernel.svg" - }, - "enablement": "python.datascience.notebookeditor.canrestartNotebookkernel" - }, - { - "command": "python.datascience.notebookeditor.trust", - "title": "%DataScience.trustNotebookCommandTitle%", - "category": "Python", - "icon": { - "light": "resources/light/un-trusted.svg", - "dark": "resources/dark/un-trusted.svg" - }, - "enablement": "notebookViewType == jupyter-notebook && !python.datascience.isnotebooktrusted && python.datascience.trustfeatureenabled" - }, - { - "command": "python.datascience.notebookeditor.runallcells", - "title": "%python.command.python.datascience.notebookeditor.runallcells.title%", - "category": "Python" - }, - { - "command": "python.datascience.notebookeditor.runselectedcell", - "title": "%python.command.python.datascience.notebookeditor.runselectedcell.title%", - "category": "Python" - }, - { - "command": "python.datascience.notebookeditor.addcellbelow", - "title": "%python.command.python.datascience.notebookeditor.addcellbelow.title%", - "category": "Python" - }, - { - "command": "python.datascience.notebookeditor.removeallcells", - "title": "%python.command.python.datascience.notebookeditor.removeallcells.title%", - "category": "Python" - }, - { - "command": "python.datascience.notebookeditor.expandallcells", - "title": "%python.command.python.datascience.notebookeditor.expandallcells.title%", - "category": "Python" - }, - { - "command": "python.datascience.notebookeditor.collapseallcells", - "title": "%python.command.python.datascience.notebookeditor.collapseallcells.title%", - "category": "Python" - }, - { - "command": "python.datascience.expandallcells", - "title": "%python.command.python.datascience.expandallcells.title%", - "category": "Python" - }, - { - "command": "python.datascience.collapseallcells", - "title": "%python.command.python.datascience.collapseallcells.title%", - "category": "Python" - }, - { - "command": "python.datascience.addcellbelow", - "title": "%python.command.python.datascience.addcellbelow.title%", - "category": "Python" - }, - { - "command": "python.datascience.createnewnotebook", - "title": "%python.command.python.datascience.createnewnotebook.title%", - "category": "Python" - }, - { - "command": "python.startPage.open", - "title": "%python.command.python.startPage.open.title%", - "category": "Python" - }, - { - "command": "python.datascience.scrolltocell", - "title": "%python.command.python.datascience.scrolltocell.title%", - "category": "Python" - }, - { - "command": "python.analysis.clearCache", - "title": "%python.command.python.analysis.clearCache.title%", - "category": "Python" - }, - { - "command": "python.datascience.switchKernel", - "title": "%DataScience.selectKernel%", "category": "Python", - "enablement": "python.datascience.isnativeactive" - }, - { - "command": "python.datascience.gatherquality", - "title": "DataScience.gatherQuality", - "category": "Python" - }, - { - "command": "python.datascience.latestExtension", - "title": "DataScience.latestExtension", - "category": "Python" - }, - { - "command": "python.analysis.restartLanguageServer", - "title": "%python.command.python.analysis.restartLanguageServer.title%", - "category": "Python" + "command": "python.installJupyter", + "title": "%python.command.python.installJupyter.title%" } ], - "menus": { - "editor/context": [ - { - "command": "python.refactorExtractVariable", - "title": "Refactor: Extract Variable", - "group": "Refactor", - "when": "editorHasSelection && editorLangId == python && !notebookEditorFocused" - }, - { - "command": "python.refactorExtractMethod", - "title": "Refactor: Extract Method", - "group": "Refactor", - "when": "editorHasSelection && editorLangId == python && !notebookEditorFocused" + "configuration": { + "properties": { + "python.activeStateToolPath": { + "default": "state", + "description": "%python.activeStateToolPath.description%", + "scope": "machine-overridable", + "type": "string" }, - { - "command": "python.sortImports", - "title": "Refactor: Sort Imports", - "group": "Refactor", - "when": "editorLangId == python && !notebookEditorFocused" + "python.autoComplete.extraPaths": { + "default": [], + "description": "%python.autoComplete.extraPaths.description%", + "scope": "resource", + "type": "array", + "uniqueItems": true }, - { - "command": "python.execSelectionInTerminal", - "group": "Python", - "when": "editorFocus && editorLangId == python" + "python.createEnvironment.contentButton": { + "default": "hide", + "markdownDescription": "%python.createEnvironment.contentButton.description%", + "scope": "machine-overridable", + "type": "string", + "enum": [ + "show", + "hide" + ] }, - { - "command": "python.execSelectionInDjangoShell", - "group": "Python", - "when": "editorHasSelection && editorLangId == python && python.isDjangoProject" + "python.createEnvironment.trigger": { + "default": "prompt", + "markdownDescription": "%python.createEnvironment.trigger.description%", + "scope": "machine-overridable", + "type": "string", + "enum": [ + "off", + "prompt" + ] }, - { - "when": "resourceLangId == python", - "command": "python.execInTerminal", - "group": "Python" + "python.condaPath": { + "default": "", + "description": "%python.condaPath.description%", + "scope": "machine", + "type": "string" }, - { - "when": "resourceLangId == python", - "command": "python.runCurrentTestFile", - "group": "Python" + "python.defaultInterpreterPath": { + "default": "python", + "markdownDescription": "%python.defaultInterpreterPath.description%", + "scope": "machine-overridable", + "type": "string" }, - { - "when": "editorFocus && editorLangId == python && python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused", - "command": "python.datascience.runallcells", - "group": "Python2" + "python.envFile": { + "default": "${workspaceFolder}/.env", + "description": "%python.envFile.description%", + "scope": "resource", + "type": "string" }, - { - "when": "editorFocus && editorLangId == python && python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused", - "command": "python.datascience.runcurrentcell", - "group": "Python2" + "python.useEnvironmentsExtension": { + "default": false, + "description": "%python.useEnvironmentsExtension.description%", + "scope": "machine-overridable", + "type": "boolean", + "tags": [ + "onExP", + "preview" + ] }, - { - "when": "editorFocus && editorLangId == python && python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused", - "command": "python.datascience.runcurrentcelladvance", - "group": "Python2" + "python.experiments.enabled": { + "default": true, + "description": "%python.experiments.enabled.description%", + "scope": "window", + "type": "boolean" }, - { - "command": "python.datascience.runFileInteractive", - "group": "Python2", - "when": "editorFocus && editorLangId == python && python.datascience.featureenabled && !notebookEditorFocused" + "python.experiments.optInto": { + "default": [], + "markdownDescription": "%python.experiments.optInto.description%", + "items": { + "enum": [ + "All", + "pythonSurveyNotification", + "pythonPromptNewToolsExt", + "pythonTerminalEnvVarActivation", + "pythonDiscoveryUsingWorkers", + "pythonTestAdapter" + ], + "enumDescriptions": [ + "%python.experiments.All.description%", + "%python.experiments.pythonSurveyNotification.description%", + "%python.experiments.pythonPromptNewToolsExt.description%", + "%python.experiments.pythonTerminalEnvVarActivation.description%", + "%python.experiments.pythonDiscoveryUsingWorkers.description%", + "%python.experiments.pythonTestAdapter.description%" + ] + }, + "scope": "window", + "type": "array", + "uniqueItems": true }, - { - "command": "python.datascience.runfromline", - "group": "Python2", - "when": "editorFocus && editorLangId == python && python.datascience.ownsSelection && python.datascience.featureenabled && !notebookEditorFocused" + "python.experiments.optOutFrom": { + "default": [], + "markdownDescription": "%python.experiments.optOutFrom.description%", + "items": { + "enum": [ + "All", + "pythonSurveyNotification", + "pythonPromptNewToolsExt", + "pythonTerminalEnvVarActivation", + "pythonDiscoveryUsingWorkers", + "pythonTestAdapter" + ], + "enumDescriptions": [ + "%python.experiments.All.description%", + "%python.experiments.pythonSurveyNotification.description%", + "%python.experiments.pythonPromptNewToolsExt.description%", + "%python.experiments.pythonTerminalEnvVarActivation.description%", + "%python.experiments.pythonDiscoveryUsingWorkers.description%", + "%python.experiments.pythonTestAdapter.description%" + ] + }, + "scope": "window", + "type": "array", + "uniqueItems": true }, - { - "command": "python.datascience.runtoline", - "group": "Python2", - "when": "editorFocus && editorLangId == python && python.datascience.ownsSelection && python.datascience.featureenabled && !notebookEditorFocused" + "python.globalModuleInstallation": { + "default": false, + "description": "%python.globalModuleInstallation.description%", + "scope": "resource", + "type": "boolean" }, - { - "command": "python.datascience.execSelectionInteractive", - "group": "Python2", - "when": "editorFocus && editorLangId == python && python.datascience.featureenabled && python.datascience.ownsSelection && !notebookEditorFocused" + "python.languageServer": { + "default": "Default", + "description": "%python.languageServer.description%", + "enum": [ + "Default", + "Jedi", + "Pylance", + "None" + ], + "enumDescriptions": [ + "%python.languageServer.defaultDescription%", + "%python.languageServer.jediDescription%", + "%python.languageServer.pylanceDescription%", + "%python.languageServer.noneDescription%" + ], + "scope": "window", + "type": "string" }, - { - "when": "editorFocus && editorLangId == python && resourceLangId == jupyter && python.datascience.featureenabled && !notebookEditorFocused", - "command": "python.datascience.importnotebook", - "group": "Python3@1" + "python.interpreter.infoVisibility": { + "default": "onPythonRelated", + "description": "%python.interpreter.infoVisibility.description%", + "enum": [ + "never", + "onPythonRelated", + "always" + ], + "enumDescriptions": [ + "%python.interpreter.infoVisibility.never.description%", + "%python.interpreter.infoVisibility.onPythonRelated.description%", + "%python.interpreter.infoVisibility.always.description%" + ], + "scope": "machine", + "type": "string" }, - { - "when": "editorFocus && editorLangId == python && python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused", - "command": "python.datascience.exportfileasnotebook", - "group": "Python3@2" + "python.logging.level": { + "default": "error", + "deprecationMessage": "%python.logging.level.deprecation%", + "description": "%python.logging.level.description%", + "enum": [ + "debug", + "error", + "info", + "off", + "warn" + ], + "scope": "machine", + "type": "string" }, - { - "when": "editorFocus && editorLangId == python && python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused", - "command": "python.datascience.exportfileandoutputasnotebook", - "group": "Python3@3" - } - ], - "editor/title": [ - { - "command": "python.execInTerminal-icon", - "title": "%python.command.python.execInTerminal.title%", - "group": "navigation", - "when": "resourceLangId == python && python.showPlayIcon" + "python.missingPackage.severity": { + "default": "Hint", + "description": "%python.missingPackage.severity.description%", + "enum": [ + "Error", + "Hint", + "Information", + "Warning" + ], + "scope": "resource", + "type": "string" }, - { - "command": "python.datascience.notebookeditor.restartkernel", - "title": "%python.command.python.datascience.restartkernel.title%", - "group": "navigation", - "when": "resourceLangId == jupyter && notebookViewType == 'jupyter-notebook'" + "python.locator": { + "default": "js", + "description": "%python.locator.description%", + "enum": [ + "js", + "native" + ], + "tags": [ + "onExP", + "preview" + ], + "scope": "machine", + "type": "string" }, - { - "command": "python.datascience.notebookeditor.trust", - "title": "%DataScience.trustNotebookCommandTitle%", - "group": "navigation@1", - "when": "resourceLangId == jupyter && notebookViewType == 'jupyter-notebook' && !python.datascience.isnotebooktrusted && python.datascience.trustfeatureenabled" + "python.pipenvPath": { + "default": "pipenv", + "description": "%python.pipenvPath.description%", + "scope": "machine-overridable", + "type": "string" }, - { - "command": "python.datascience.export", - "title": "%DataScience.notebookExportAs%", - "group": "navigation", - "when": "resourceLangId == jupyter && notebookViewType == 'jupyter-notebook' && python.datascience.isnotebooktrusted" - } - ], - "explorer/context": [ - { - "when": "resourceLangId == python && !busyTests && !notebookEditorFocused", - "command": "python.runtests", - "group": "Python" + "python.poetryPath": { + "default": "poetry", + "description": "%python.poetryPath.description%", + "scope": "machine-overridable", + "type": "string" }, - { - "when": "resourceLangId == python && !busyTests && !notebookEditorFocused", - "command": "python.debugtests", - "group": "Python" + "python.pixiToolPath": { + "default": "pixi", + "description": "%python.pixiToolPath.description%", + "scope": "machine-overridable", + "type": "string" }, - { - "when": "resourceLangId == python", - "command": "python.execInTerminal", - "group": "Python" + "python.terminal.activateEnvInCurrentTerminal": { + "default": false, + "description": "%python.terminal.activateEnvInCurrentTerminal.description%", + "scope": "resource", + "type": "boolean" }, - { - "when": "resourceLangId == python && python.datascience.featureenabled && !notebookEditorFocused", - "command": "python.datascience.runFileInteractive", - "group": "Python2" + "python.terminal.activateEnvironment": { + "default": true, + "description": "%python.terminal.activateEnvironment.description%", + "scope": "resource", + "type": "boolean" }, - { - "when": "resourceLangId == jupyter", - "command": "python.datascience.opennotebook", - "group": "Python" + "python.terminal.executeInFileDir": { + "default": false, + "description": "%python.terminal.executeInFileDir.description%", + "scope": "resource", + "type": "boolean" }, - { - "when": "resourceLangId == jupyter && python.vscode.channel == 'insiders'", - "command": "python.datascience.opennotebookInPreviewEditor", - "group": "Python" + "python.terminal.focusAfterLaunch": { + "default": false, + "description": "%python.terminal.focusAfterLaunch.description%", + "scope": "resource", + "type": "boolean" }, - { - "when": "resourceLangId == jupyter", - "command": "python.datascience.importnotebookfile", - "group": "Python" - } - ], - "commandPalette": [ - { - "command": "python.datascience.exportAsPythonScript", - "title": "%python.command.python.datascience.exportAsPythonScript.title%", - "category": "Python", - "when": "python.datascience.isnativeactive && python.datascience.featureenabled && python.datascience.isnotebooktrusted" + "python.terminal.launchArgs": { + "default": [], + "description": "%python.terminal.launchArgs.description%", + "scope": "resource", + "type": "array" }, - { - "command": "python.datascience.exportToHTML", - "title": "%python.command.python.datascience.exportToHTML.title%", - "category": "Python", - "when": "python.datascience.isnativeactive && python.datascience.featureenabled && python.datascience.isnotebooktrusted" + "python.terminal.shellIntegration.enabled": { + "default": true, + "markdownDescription": "%python.terminal.shellIntegration.enabled.description%", + "scope": "resource", + "type": "boolean", + "tags": [ + "preview" + ] }, - { - "command": "python.datascience.exportToPDF", - "title": "%python.command.python.datascience.exportToPDF.title%", - "category": "Python", - "when": "python.datascience.isnativeactive && python.datascience.featureenabled && python.datascience.isnotebooktrusted" + "python.REPL.enableREPLSmartSend": { + "default": true, + "description": "%python.EnableREPLSmartSend.description%", + "scope": "resource", + "type": "boolean" }, - { - "command": "python.switchOffInsidersChannel", - "title": "%python.command.python.switchOffInsidersChannel.title%", - "category": "Python", - "when": "config.python.insidersChannel != 'default'" + "python.REPL.sendToNativeREPL": { + "default": false, + "description": "%python.REPL.sendToNativeREPL.description%", + "scope": "resource", + "type": "boolean" }, - { - "command": "python.switchToDailyChannel", - "title": "%python.command.python.switchToDailyChannel.title%", - "category": "Python", - "when": "config.python.insidersChannel != 'daily'" - }, - { - "command": "python.switchToWeeklyChannel", - "title": "%python.command.python.switchToWeeklyChannel.title%", - "category": "Python", - "when": "config.python.insidersChannel != 'weekly'" - }, - { - "command": "python.clearWorkspaceInterpreter", - "title": "%python.command.python.clearWorkspaceInterpreter.title%", - "category": "Python" - }, - { - "command": "python.resetInterpreterSecurityStorage", - "title": "%python.command.python.resetInterpreterSecurityStorage.title%", - "category": "Python" - }, - { - "command": "python.viewOutput", - "title": "%python.command.python.viewOutput.title%", - "category": "Python" - }, - { - "command": "python.runTestNode", - "title": "Run", - "category": "Python", - "when": "config.noExists" - }, - { - "command": "python.discoveringTests", - "category": "Python", - "when": "config.noExists" - }, - { - "command": "python.stopTests", - "category": "Python", - "when": "config.noExists" - }, - { - "command": "python.debugTestNode", - "title": "Debug", - "category": "Python", - "when": "config.noExists" - }, - { - "command": "python.openTestNodeInEditor", - "title": "Open", - "category": "Python", - "when": "config.noExists" - }, - { - "command": "python.datascience.insertCellBelowPosition", - "title": "%python.command.python.datascience.insertCellBelowPosition.title%", - "category": "Python", - "when": "python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "command": "python.datascience.insertCellBelow", - "title": "%python.command.python.datascience.insertCellBelow.title%", - "category": "Python", - "when": "python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "command": "python.datascience.insertCellAbove", - "title": "%python.command.python.datascience.insertCellAbove.title%", - "category": "Python", - "when": "python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "command": "python.datascience.deleteCells", - "title": "%python.command.python.datascience.deleteCells.title%", - "category": "Python", - "when": "python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "command": "python.datascience.selectCell", - "title": "%python.command.python.datascience.selectCell.title%", - "category": "Python", - "when": "python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "command": "python.datascience.selectCellContents", - "title": "%python.command.python.datascience.selectCellContents.title%", - "category": "Python", - "when": "python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "command": "python.datascience.extendSelectionByCellAbove", - "title": "%python.command.python.datascience.extendSelectionByCellAbove.title%", - "category": "Python", - "when": "python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "command": "python.datascience.extendSelectionByCellBelow", - "title": "%python.command.python.datascience.extendSelectionByCellBelow.title%", - "category": "Python", - "when": "python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "command": "python.datascience.moveCellsUp", - "title": "%python.command.python.datascience.moveCellsUp.title%", - "category": "Python", - "when": "python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "command": "python.datascience.moveCellsDown", - "title": "%python.command.python.datascience.moveCellsDown.title%", - "category": "Python", - "when": "python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "command": "python.datascience.changeCellToMarkdown", - "title": "%python.command.python.datascience.changeCellToMarkdown.title%", - "category": "Python", - "when": "python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "command": "python.datascience.changeCellToCode", - "title": "%python.command.python.datascience.changeCellToCode.title%", - "category": "Python", - "when": "python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "command": "python.datascience.gotoNextCellInFile", - "title": "%python.command.python.datascience.gotoNextCellInFile.title%", - "category": "Python", - "when": "python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "command": "python.datascience.gotoPrevCellInFile", - "title": "%python.command.python.datascience.gotoPrevCellInFile.title%", - "category": "Python", - "when": "python.datascience.hascodecells && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "command": "python.datascience.runcurrentcell", - "title": "%python.command.python.datascience.runcurrentcell.title%", - "category": "Python", - "when": "python.datascience.hascodecells && python.datascience.featureenabled && python.datascience.ispythonornativeactive" - }, - { - "command": "python.datascience.runcurrentcelladvance", - "title": "%python.command.python.datascience.runcurrentcelladvance.title%", - "category": "Python", - "when": "python.datascience.hascodecells && python.datascience.featureenabled && python.datascience.ispythonornativeactive" - }, - { - "command": "python.datascience.runcurrentcellandallbelow.palette", - "title": "%python.command.python.datascience.runcurrentcellandallbelow.palette.title%", - "category": "Python", - "when": "python.datascience.hascodecells && python.datascience.featureenabled && python.datascience.ispythonornativeactive" - }, - { - "command": "python.datascience.runallcellsabove.palette", - "title": "%python.command.python.datascience.runallcellsabove.palette.title%", - "category": "Python", - "when": "python.datascience.hascodecells && python.datascience.featureenabled && python.datascience.ispythonornativeactive" - }, - { - "command": "python.datascience.debugcurrentcell.palette", - "title": "%python.command.python.datascience.debugcurrentcell.palette.title%", - "category": "Python", - "when": "editorLangId == python && python.datascience.hascodecells && python.datascience.featureenabled" - }, - { - "command": "python.datascience.createnewinteractive", - "title": "%python.command.python.datascience.createnewinteractive.title%", - "category": "Python", - "when": "python.datascience.featureenabled" - }, - { - "command": "python.datascience.runallcells", - "title": "%python.command.python.datascience.runallcells.title%", - "category": "Python", - "when": "python.datascience.hascodecells && python.datascience.featureenabled && python.datascience.ispythonornativeactive" - }, - { - "command": "python.datascience.scrolltocell", - "title": "%python.command.python.datascience.scrolltocell.title%", - "category": "Python", - "when": "false" - }, - { - "command": "python.datascience.debugcell", - "title": "%python.command.python.datascience.debugcell.title%", - "category": "Python", - "when": "config.noExists" - }, - { - "command": "python.datascience.runcell", - "title": "%python.command.python.datascience.runcell.title%", - "category": "Python", - "when": "config.noExists" - }, - { - "command": "python.datascience.runFileInteractive", - "title": "%python.command.python.datascience.runFileInteractive.title%", - "category": "Python", - "when": "editorLangId == python && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "command": "python.datascience.debugFileInteractive", - "title": "%python.command.python.datascience.debugFileInteractive.title%", - "category": "Python", - "when": "editorLangId == python && python.datascience.featureenabled && !notebookEditorFocused" - }, - { - "command": "python.datascience.importnotebook", - "title": "%python.command.python.datascience.importnotebook.title%", - "category": "Python" - }, - { - "command": "python.datascience.opennotebook", - "title": "%python.command.python.datascience.opennotebook.title%", - "category": "Python" - }, - { - "command": "python.datascience.exportfileasnotebook", - "title": "%python.command.python.datascience.exportfileasnotebook.title%", - "category": "Python", - "when": "python.datascience.hascodecells && python.datascience.featureenabled && python.datascience.ispythonorinteractiveeactive && !notebookEditorFocused" - }, - { - "command": "python.datascience.exportfileandoutputasnotebook", - "title": "%python.command.python.datascience.exportfileandoutputasnotebook.title%", - "category": "Python", - "when": "python.datascience.hascodecells && python.datascience.featureenabled && python.datascience.ispythonorinteractiveeactive && !notebookEditorFocused" - }, - { - "command": "python.datascience.undocells", - "title": "%python.command.python.datascience.undocells.title%", - "category": "Python", - "when": "python.datascience.haveinteractivecells && python.datascience.featureenabled && python.datascience.ispythonorinteractiveeactive && !notebookEditorFocused" - }, - { - "command": "python.datascience.redocells", - "title": "%python.command.python.datascience.redocells.title%", - "category": "Python", - "when": "python.datascience.haveredoablecells && python.datascience.featureenabled && python.datascience.ispythonorinteractiveornativeeactive && !notebookEditorFocused" - }, - { - "command": "python.datascience.removeallcells", - "title": "%python.command.python.datascience.removeallcells.title%", - "category": "Python", - "when": "python.datascience.haveinteractivecells && python.datascience.featureenabled && python.datascience.ispythonorinteractiveeactive" - }, - { - "command": "python.datascience.interruptkernel", - "title": "%python.command.python.datascience.interruptkernel.title%", - "category": "Python", - "when": "python.datascience.haveinteractive && python.datascience.featureenabled && python.datascience.ispythonorinteractiveeactive" - }, - { - "command": "python.datascience.restartkernel", - "title": "%python.command.python.datascience.restartkernel.title%", - "category": "Python", - "when": "python.datascience.haveinteractive && python.datascience.featureenabled && python.datascience.ispythonorinteractiveeactive" - }, - { - "command": "python.datascience.notebookeditor.undocells", - "title": "%python.command.python.datascience.undocells.title%", - "category": "Python", - "when": "python.datascience.haveinteractivecells && python.datascience.featureenabled && python.datascience.isnativeactive && !notebookEditorFocused && python.datascience.isnotebooktrusted" - }, - { - "command": "python.datascience.notebookeditor.redocells", - "title": "%python.command.python.datascience.redocells.title%", - "category": "Python", - "when": "python.datascience.havenativeredoablecells && python.datascience.featureenabled && python.datascience.isnativeactive && !notebookEditorFocused&& python.datascience.isnotebooktrusted" - }, - { - "command": "python.datascience.notebookeditor.removeallcells", - "title": "%python.command.python.datascience.notebookeditor.removeallcells.title%", - "category": "Python", - "when": "python.datascience.havenativecells && python.datascience.featureenabled && python.datascience.isnativeactive && python.datascience.isnotebooktrusted" - }, - { - "command": "python.datascience.notebookeditor.interruptkernel", - "title": "%python.command.python.datascience.interruptkernel.title%", - "category": "Python", - "when": "python.datascience.isnativeactive && python.datascience.featureenabled && python.datascience.isnotebooktrusted" - }, - { - "command": "python.datascience.notebookeditor.restartkernel", - "title": "%python.command.python.datascience.restartkernel.title%", - "category": "Python", - "when": "python.datascience.isnativeactive && python.datascience.featureenabled && python.datascience.isnotebooktrusted" - }, - { - "command": "python.datascience.notebookeditor.trust", - "title": "%DataScience.trustNotebookCommandTitle%", - "category": "Python", - "when": "python.datascience.featureenabled && notebookEditorFocused && !python.datascience.isnotebooktrusted && python.datascience.trustfeatureenabled" - }, - { - "command": "python.datascience.notebookeditor.runallcells", - "title": "%python.command.python.datascience.notebookeditor.runallcells.title%", - "category": "Python", - "when": "python.datascience.isnativeactive && python.datascience.featureenabled && python.datascience.isnotebooktrusted" - }, - { - "command": "python.datascience.notebookeditor.runselectedcell", - "title": "%python.command.python.datascience.notebookeditor.runselectedcell.title%", - "category": "Python", - "when": "python.datascience.isnativeactive && python.datascience.featureenabled && python.datascience.havecellselected && !notebookEditorFocused && python.datascience.isnotebooktrusted" - }, - { - "command": "python.datascience.notebookeditor.runselectedcell", - "title": "%python.command.python.datascience.notebookeditor.runselectedcell.title%", - "category": "Python", - "when": "python.datascience.isnativeactive && python.datascience.featureenabled && notebookEditorFocused && python.datascience.isnotebooktrusted" - }, - { - "command": "python.datascience.notebookeditor.addcellbelow", - "title": "%python.command.python.datascience.notebookeditor.addcellbelow.title%", - "category": "Python", - "when": "python.datascience.isnativeactive && python.datascience.featureenabled && python.datascience.isnotebooktrusted" - }, - { - "command": "python.datascience.notebookeditor.removeallcells", - "title": "%python.command.python.datascience.removeallcells.title%", - "category": "Python", - "when": "python.datascience.featureenabled && notebookEditorFocused" - }, - { - "command": "python.datascience.notebookeditor.expandallcells", - "title": "%python.command.python.datascience.expandallcells.title%", - "category": "Python", - "when": "python.datascience.featureenabled && notebookEditorFocused" - }, - { - "command": "python.datascience.notebookeditor.collapseallcells", - "title": "%python.command.python.datascience.collapseallcells.title%", - "category": "Python", - "when": "python.datascience.featureenabled && notebookEditorFocused" - }, - { - "command": "python.datascience.expandallcells", - "title": "%python.command.python.datascience.expandallcells.title%", - "category": "Python", - "when": "python.datascience.isinteractiveactive && python.datascience.featureenabled" - }, - { - "command": "python.datascience.collapseallcells", - "title": "%python.command.python.datascience.collapseallcells.title%", - "category": "Python", - "when": "python.datascience.isinteractiveactive && python.datascience.featureenabled" - }, - { - "command": "python.datascience.exportoutputasnotebook", - "title": "%python.command.python.datascience.exportoutputasnotebook.title%", - "category": "Python", - "when": "python.datascience.isinteractiveactive && python.datascience.featureenabled" - }, - { - "command": "python.datascience.runcellandallbelow", - "category": "Python", - "when": "config.noExists" - }, - { - "command": "python.datascience.runallcellsabove", - "category": "Python", - "when": "config.noExists" + "python.REPL.provideVariables": { + "default": true, + "description": "%python.REPL.provideVariables.description%", + "scope": "resource", + "type": "boolean" }, - { - "command": "python.datascience.debugcontinue", - "category": "Python", - "when": "config.noExists" + "python.testing.autoTestDiscoverOnSaveEnabled": { + "default": true, + "description": "%python.testing.autoTestDiscoverOnSaveEnabled.description%", + "scope": "resource", + "type": "boolean" }, - { - "command": "python.datascience.debugstop", - "category": "Python", - "when": "config.noExists" + "python.testing.autoTestDiscoverOnSavePattern": { + "default": "**/*.py", + "description": "%python.testing.autoTestDiscoverOnSavePattern.description%", + "scope": "resource", + "type": "string" }, - { - "command": "python.datascience.debugstepover", - "category": "Python", - "when": "config.noExists" + "python.testing.cwd": { + "default": null, + "description": "%python.testing.cwd.description%", + "scope": "resource", + "type": "string" }, - { - "command": "python.datascience.debugcell", - "category": "Python", - "when": "config.noExists" + "python.testing.debugPort": { + "default": 3000, + "description": "%python.testing.debugPort.description%", + "scope": "resource", + "type": "number" }, - { - "command": "python.datascience.addcellbelow", - "title": "%python.command.python.datascience.addcellbelow.title%", - "category": "Python", - "when": "python.datascience.hascodecells && python.datascience.featureenabled && python.datascience.ispythonornativeactive" + "python.testing.promptToConfigure": { + "default": true, + "description": "%python.testing.promptToConfigure.description%", + "scope": "resource", + "type": "boolean" }, - { - "command": "python.datascience.createnewnotebook", - "title": "%python.command.python.datascience.createnewnotebook.title%", - "category": "Python" + "python.testing.pytestArgs": { + "default": [], + "description": "%python.testing.pytestArgs.description%", + "items": { + "type": "string" + }, + "scope": "resource", + "type": "array" }, - { - "command": "python.startPage.open", - "title": "%python.command.python.startPage.open.title%", - "category": "Python" + "python.testing.pytestEnabled": { + "default": false, + "description": "%python.testing.pytestEnabled.description%", + "scope": "resource", + "type": "boolean" }, - { - "command": "python.datascience.runtoline", - "category": "Python", - "when": "config.noExists" + "python.testing.pytestPath": { + "default": "pytest", + "description": "%python.testing.pytestPath.description%", + "scope": "machine-overridable", + "type": "string" }, - { - "command": "python.datascience.runfromline", - "category": "Python", - "when": "config.noExists" + "python.testing.unittestArgs": { + "default": [ + "-v", + "-s", + ".", + "-p", + "*test*.py" + ], + "description": "%python.testing.unittestArgs.description%", + "items": { + "type": "string" + }, + "scope": "resource", + "type": "array" }, - { - "command": "python.datascience.execSelectionInteractive", - "category": "Python", - "when": "editorLangId == python && python.datascience.featureenabled && !notebookEditorFocused" + "python.testing.unittestEnabled": { + "default": false, + "description": "%python.testing.unittestEnabled.description%", + "scope": "resource", + "type": "boolean" }, - { - "command": "python.datascience.switchKernel", - "title": "%DataScience.selectKernel%", - "category": "Python", - "when": "python.datascience.isnativeactive" + "python.venvFolders": { + "default": [], + "description": "%python.venvFolders.description%", + "items": { + "type": "string" + }, + "scope": "machine", + "type": "array", + "uniqueItems": true }, - { - "command": "python.datascience.gatherquality", - "title": "%DataScience.gatherQuality%", - "category": "Python", - "when": "false" - }, - { - "command": "python.datascience.latestExtension", - "title": "%DataScience.latestExtension%", - "category": "Python", - "when": "false" - }, - { - "command": "python.datascience.export", - "title": "%DataScience.notebookExportAs%", - "category": "Python", - "when": "false" - } - ], - "view/title": [ - { - "command": "python.debugtests", - "when": "view == python_tests && !busyTests", - "group": "navigation@3" - }, - { - "command": "python.runtests", - "when": "view == python_tests && !busyTests", - "group": "navigation@1" - }, - { - "command": "python.stopTests", - "when": "view == python_tests && busyTests", - "group": "navigation@1" - }, - { - "command": "python.discoverTests", - "when": "view == python_tests && !busyTests", - "group": "navigation@4" - }, - { - "command": "python.discoveringTests", - "when": "view == python_tests && discoveringTests", - "group": "navigation@4" - }, - { - "command": "python.runFailedTests", - "when": "view == python_tests && hasFailedTests && !busyTests", - "group": "navigation@2" - }, - { - "command": "python.viewTestOutput", - "when": "view == python_tests", - "group": "navigation@5" - } - ], - "view/item/context": [ - { - "command": "python.runtests", - "when": "view == python_tests && viewItem == testWorkspaceFolder && !busyTests", - "group": "inline@0" - }, - { - "command": "python.debugtests", - "when": "view == python_tests && viewItem == testWorkspaceFolder && !busyTests", - "group": "inline@1" - }, - { - "command": "python.discoverTests", - "when": "view == python_tests && viewItem == testWorkspaceFolder && !busyTests", - "group": "inline@2" - }, - { - "command": "python.openTestNodeInEditor", - "when": "view == python_tests && viewItem == function", - "group": "inline@2" - }, - { - "command": "python.debugTestNode", - "when": "view == python_tests && viewItem == function && !busyTests", - "group": "inline@1" - }, - { - "command": "python.runTestNode", - "when": "view == python_tests && viewItem == function && !busyTests", - "group": "inline@0" - }, - { - "command": "python.openTestNodeInEditor", - "when": "view == python_tests && viewItem == file", - "group": "inline@2" - }, - { - "command": "python.debugTestNode", - "when": "view == python_tests && viewItem == file && !busyTests", - "group": "inline@1" - }, - { - "command": "python.runTestNode", - "when": "view == python_tests && viewItem == file && !busyTests", - "group": "inline@0" - }, - { - "command": "python.openTestNodeInEditor", - "when": "view == python_tests && viewItem == suite", - "group": "inline@2" - }, - { - "command": "python.debugTestNode", - "when": "view == python_tests && viewItem == suite && !busyTests", - "group": "inline@1" - }, - { - "command": "python.runTestNode", - "when": "view == python_tests && viewItem == suite && !busyTests", - "group": "inline@0" + "python.venvPath": { + "default": "", + "description": "%python.venvPath.description%", + "scope": "machine", + "type": "string" } - ] - }, - "breakpoints": [ - { - "language": "python" - }, - { - "language": "html" }, - { - "language": "jinja" - } - ], + "title": "Python", + "type": "object" + }, "debuggers": [ { - "type": "python", - "label": "Python", - "languages": [ - "python" - ], - "variables": { - "pickProcess": "python.pickLocalProcess" - }, - "configurationSnippets": [], "configurationAttributes": { - "launch": { + "attach": { "properties": { - "module": { - "type": "string", - "description": "Name of the module to be debugged.", - "default": "" - }, - "program": { - "type": "string", - "description": "Absolute path to the program.", - "default": "${file}" - }, - "pythonPath": { - "type": "string", - "description": "Path (fully qualified) to python executable. Defaults to the value in settings", - "default": "${command:python.interpreterPath}" - }, - "pythonArgs": { - "type": "array", - "description": "Command-line arguments passed to the Python interpreter. To pass arguments to the debug target, use \"args\".", - "default": [], - "items": { - "type": "string" - } + "connect": { + "label": "Attach by connecting to debugpy over a socket.", + "properties": { + "host": { + "default": "127.0.0.1", + "description": "Hostname or IP address to connect to.", + "type": "string" + }, + "port": { + "description": "Port to connect to.", + "type": "number" + } + }, + "required": [ + "port" + ], + "type": "object" }, - "args": { - "type": "array", - "description": "Command line arguments passed to the program", - "default": [], - "items": { - "type": "string" - } + "debugAdapterPath": { + "description": "Path (fully qualified) to the python debug adapter executable.", + "type": "string" }, - "stopOnEntry": { - "type": "boolean", - "description": "Automatically stop after launch.", - "default": false + "django": { + "default": false, + "description": "Django debugging.", + "type": "boolean" }, - "showReturnValue": { - "type": "boolean", - "description": "Show return value of functions when stepping.", - "default": true + "host": { + "default": "127.0.0.1", + "description": "Hostname or IP address to connect to.", + "type": "string" }, - "console": { + "jinja": { + "default": null, + "description": "Jinja template debugging (e.g. Flask).", "enum": [ - "internalConsole", - "integratedTerminal", - "externalTerminal" - ], - "description": "Where to launch the debug target: internal console, integrated terminal, or external terminal.", - "default": "integratedTerminal" - }, - "cwd": { - "type": "string", - "description": "Absolute path to the working directory of the program being debugged. Default is the root directory of the file (leave empty).", - "default": "${workspaceFolder}" - }, - "env": { - "type": "object", - "description": "Environment variables defined as a key value pair. Property ends up being the Environment Variable and the value of the property ends up being the value of the Env Variable.", - "default": {}, - "additionalProperties": { - "type": "string" - } + false, + null, + true + ] }, - "envFile": { - "type": "string", - "description": "Absolute path to a file containing environment variable definitions.", - "default": "${workspaceFolder}/.env" + "justMyCode": { + "default": true, + "description": "If true, show and debug only user-written code. If false, show and debug all code, including library calls.", + "type": "boolean" }, - "port": { - "type": "number", - "description": "Debug port (default is 0, resulting in the use of a dynamic port).", - "default": 0 + "listen": { + "label": "Attach by listening for incoming socket connection from debugpy", + "properties": { + "host": { + "default": "127.0.0.1", + "description": "Hostname or IP address of the interface to listen on.", + "type": "string" + }, + "port": { + "description": "Port to listen on.", + "type": "number" + } + }, + "required": [ + "port" + ], + "type": "object" }, - "host": { - "type": "string", - "description": "IP address of the of the local debug server (default is localhost).", - "default": "localhost" + "logToFile": { + "default": false, + "description": "Enable logging of debugger events to a log file.", + "type": "boolean" }, "pathMappings": { - "type": "array", - "label": "Path mappings.", + "default": [], "items": { - "type": "object", "label": "Path mapping", - "required": [ - "localRoot", - "remoteRoot" - ], "properties": { "localRoot": { - "type": "string", + "default": "${workspaceFolder}", "label": "Local source root.", - "default": "${workspaceFolder}" + "type": "string" }, "remoteRoot": { - "type": "string", + "default": "", "label": "Remote source root.", - "default": "" + "type": "string" } - } + }, + "required": [ + "localRoot", + "remoteRoot" + ], + "type": "object" }, - "default": [] + "label": "Path mappings.", + "type": "array" }, - "logToFile": { - "type": "boolean", - "description": "Enable logging of debugger events to a log file.", - "default": false + "port": { + "description": "Port to connect to.", + "type": "number" + }, + "processId": { + "anyOf": [ + { + "default": "${command:pickProcess}", + "description": "Use process picker to select a process to attach, or Process ID as integer.", + "enum": [ + "${command:pickProcess}" + ] + }, + { + "description": "ID of the local process to attach to.", + "type": "integer" + } + ] }, "redirectOutput": { - "type": "boolean", + "default": true, "description": "Redirect output.", - "default": true - }, - "justMyCode": { - "type": "boolean", - "description": "Debug only user-written code.", - "default": true - }, - "gevent": { - "type": "boolean", - "description": "Enable debugging of gevent monkey-patched code.", - "default": false - }, - "django": { - "type": "boolean", - "description": "Django debugging.", - "default": false + "type": "boolean" }, - "jinja": { - "enum": [ - true, - false, - null - ], - "description": "Jinja template debugging (e.g. Flask).", - "default": null - }, - "sudo": { - "type": "boolean", - "description": "Running debug program under elevated permissions (on Unix).", - "default": false - }, - "pyramid": { - "type": "boolean", - "description": "Whether debugging Pyramid applications", - "default": false + "showReturnValue": { + "default": true, + "description": "Show return value of functions when stepping.", + "type": "boolean" }, "subProcess": { - "type": "boolean", + "default": false, "description": "Whether to enable Sub Process debugging", - "default": false + "type": "boolean" } } }, - "test": { + "launch": { "properties": { - "pythonPath": { - "type": "string", - "description": "Path (fully qualified) to python executable. Defaults to the value in settings", - "default": "${command:python.interpreterPath}" - }, - "stopOnEntry": { - "type": "boolean", - "description": "Automatically stop after launch.", - "default": false + "args": { + "default": [], + "description": "Command line arguments passed to the program.", + "items": { + "type": "string" + }, + "type": [ + "array", + "string" + ] }, - "showReturnValue": { - "type": "boolean", - "description": "Show return value of functions when stepping.", - "default": true + "autoReload": { + "default": {}, + "description": "Configures automatic reload of code on edit.", + "properties": { + "enable": { + "default": false, + "description": "Automatically reload code on edit.", + "type": "boolean" + }, + "exclude": { + "default": [ + "**/.git/**", + "**/.metadata/**", + "**/__pycache__/**", + "**/node_modules/**", + "**/site-packages/**" + ], + "description": "Glob patterns of paths to exclude from auto reload.", + "items": { + "type": "string" + }, + "type": "array" + }, + "include": { + "default": [ + "**/*.py", + "**/*.pyw" + ], + "description": "Glob patterns of paths to include in auto reload.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" }, "console": { + "default": "integratedTerminal", + "description": "Where to launch the debug target: internal console, integrated terminal, or external terminal.", "enum": [ - "internalConsole", + "externalTerminal", "integratedTerminal", - "externalTerminal" - ], - "description": "Where to launch the debug target: internal console, integrated terminal, or external terminal.", - "default": "internalConsole" + "internalConsole" + ] + }, + "consoleTitle": { + "default": "Python Debug Console", + "description": "Display name of the debug console or terminal" }, "cwd": { - "type": "string", + "default": "${workspaceFolder}", "description": "Absolute path to the working directory of the program being debugged. Default is the root directory of the file (leave empty).", - "default": "${workspaceFolder}" + "type": "string" + }, + "debugAdapterPath": { + "description": "Path (fully qualified) to the python debug adapter executable.", + "type": "string" + }, + "django": { + "default": false, + "description": "Django debugging.", + "type": "boolean" }, "env": { - "type": "object", - "description": "Environment variables defined as a key value pair. Property ends up being the Environment Variable and the value of the property ends up being the value of the Env Variable.", - "default": {}, "additionalProperties": { "type": "string" - } + }, + "default": {}, + "description": "Environment variables defined as a key value pair. Property ends up being the Environment Variable and the value of the property ends up being the value of the Env Variable.", + "type": "object" }, "envFile": { - "type": "string", + "default": "${workspaceFolder}/.env", "description": "Absolute path to a file containing environment variable definitions.", - "default": "${workspaceFolder}/.env" + "type": "string" }, - "redirectOutput": { - "type": "boolean", - "description": "Redirect output.", - "default": true + "gevent": { + "default": false, + "description": "Enable debugging of gevent monkey-patched code.", + "type": "boolean" + }, + "host": { + "default": "localhost", + "description": "IP address of the of the local debug server (default is localhost).", + "type": "string" + }, + "jinja": { + "default": null, + "description": "Jinja template debugging (e.g. Flask).", + "enum": [ + false, + null, + true + ] }, "justMyCode": { - "type": "boolean", + "default": true, "description": "Debug only user-written code.", - "default": true - } - } - }, - "attach": { - "properties": { - "connect": { - "type": "object", - "label": "Attach by connecting to debugpy over a socket.", - "properties": { - "port": { - "type": "number", - "description": "Port to connect to." - }, - "host": { - "type": "string", - "description": "Hostname or IP address to connect to.", - "default": "127.0.0.1" - } - }, - "required": [ - "port" - ] - }, - "listen": { - "type": "object", - "label": "Attach by listening for incoming socket connection from debugpy", - "properties": { - "port": { - "type": "number", - "description": "Port to listen on." - }, - "host": { - "type": "string", - "description": "Hostname or IP address of the interface to listen on.", - "default": "127.0.0.1" - } - }, - "required": [ - "port" - ] + "type": "boolean" }, - "port": { - "type": "number", - "description": "Port to connect to." + "logToFile": { + "default": false, + "description": "Enable logging of debugger events to a log file.", + "type": "boolean" }, - "host": { - "type": "string", - "description": "Hostname or IP address to connect to.", - "default": "127.0.0.1" + "module": { + "default": "", + "description": "Name of the module to be debugged.", + "type": "string" }, "pathMappings": { - "type": "array", - "label": "Path mappings.", + "default": [], "items": { - "type": "object", "label": "Path mapping", - "required": [ - "localRoot", - "remoteRoot" - ], "properties": { "localRoot": { - "type": "string", + "default": "${workspaceFolder}", "label": "Local source root.", - "default": "${workspaceFolder}" + "type": "string" }, "remoteRoot": { - "type": "string", + "default": "", "label": "Remote source root.", - "default": "" + "type": "string" } - } + }, + "required": [ + "localRoot", + "remoteRoot" + ], + "type": "object" }, - "default": [] + "label": "Path mappings.", + "type": "array" }, - "logToFile": { - "type": "boolean", - "description": "Enable logging of debugger events to a log file.", - "default": false + "port": { + "default": 0, + "description": "Debug port (default is 0, resulting in the use of a dynamic port).", + "type": "number" }, - "redirectOutput": { - "type": "boolean", - "description": "Redirect output.", - "default": true + "program": { + "default": "${file}", + "description": "Absolute path to the program.", + "type": "string" }, - "justMyCode": { - "type": "boolean", - "description": "Debug only user-written code.", - "default": true + "purpose": { + "default": [], + "description": "Tells extension to use this configuration for test debugging, or when using debug-in-terminal command.", + "items": { + "enum": [ + "debug-test", + "debug-in-terminal" + ], + "enumDescriptions": [ + "Use this configuration while debugging tests using test view or test debug commands.", + "Use this configuration while debugging a file using debug in terminal button in the editor." + ] + }, + "type": "array" }, - "django": { - "type": "boolean", - "description": "Django debugging.", - "default": false + "pyramid": { + "default": false, + "description": "Whether debugging Pyramid applications", + "type": "boolean" }, - "jinja": { - "enum": [ - true, - false, - null - ], - "description": "Jinja template debugging (e.g. Flask).", - "default": null + "python": { + "default": "${command:python.interpreterPath}", + "description": "Absolute path to the Python interpreter executable; overrides workspace configuration if set.", + "type": "string" }, - "subProcess": { - "type": "boolean", - "description": "Whether to enable Sub Process debugging", - "default": false + "pythonArgs": { + "default": [], + "description": "Command-line arguments passed to the Python interpreter. To pass arguments to the debug target, use \"args\".", + "items": { + "type": "string" + }, + "type": "array" + }, + "redirectOutput": { + "default": true, + "description": "Redirect output.", + "type": "boolean" }, "showReturnValue": { - "type": "boolean", + "default": true, "description": "Show return value of functions when stepping.", - "default": true + "type": "boolean" }, - "processId": { - "anyOf": [ - { - "enum": [ - "${command:pickProcess}" - ], - "description": "Use process picker to select a process to attach, or Process ID as integer.", - "default": "${command:pickProcess}" - }, - { - "type": "integer", - "description": "ID of the local process to attach to." - } - ] + "stopOnEntry": { + "default": false, + "description": "Automatically stop after launch.", + "type": "boolean" + }, + "subProcess": { + "default": false, + "description": "Whether to enable Sub Process debugging", + "type": "boolean" + }, + "sudo": { + "default": false, + "description": "Running debug program under elevated permissions (on Unix).", + "type": "boolean" } } } - } - } - ], - "configuration": { - "type": "object", - "title": "Python", - "properties": { - "python.diagnostics.sourceMapsEnabled": { - "type": "boolean", - "default": false, - "description": "Enable source map support for meaningful stack traces in error logs.", - "scope": "application" - }, - "python.autoComplete.addBrackets": { - "type": "boolean", - "default": false, - "description": "Automatically add brackets for functions.", - "scope": "resource" - }, - "python.autoComplete.extraPaths": { - "type": "array", - "default": [], - "description": "List of paths to libraries and the like that need to be imported by auto complete engine. E.g. when using Google App SDK, the paths are not in system path, hence need to be added into this list.", - "scope": "resource" - }, - "python.autoComplete.showAdvancedMembers": { - "type": "boolean", - "default": true, - "description": "Controls appearance of methods with double underscores in the completion list.", - "scope": "resource" - }, - "python.autoComplete.typeshedPaths": { - "type": "array", - "items": { - "type": "string" - }, - "default": [], - "description": "Specifies paths to local typeshed repository clone(s) for the Python language server.", - "scope": "resource" - }, - "python.autoUpdateLanguageServer": { - "type": "boolean", - "default": true, - "description": "Automatically update the language server.", - "scope": "application" }, - "python.logging.level": { - "type": "string", - "default": "error", - "enum": [ - "off", - "error", - "warn", - "info", - "debug" - ], - "description": "The logging level the extension logs at, defaults to 'error'", - "scope": "machine" - }, - "python.experiments.enabled": { - "type": "boolean", - "default": true, - "description": "Enables/disables A/B tests.", - "scope": "machine" - }, - "python.defaultInterpreterPath": { - "type": "string", - "default": "python", - "description": "Path to Python, you can use a custom version of Python by modifying this setting to include the full path.", - "scope": "machine" - }, - "python.experiments.optInto": { - "type": "array", - "default": [], - "items": { - "enum": [ - "AlwaysDisplayTestExplorer - experiment", - "ShowExtensionSurveyPrompt - enabled", - "Reload - experiment", - "AA_testing - experiment", - "LocalZMQKernel - experiment", - "NativeNotebook - experiment", - "CustomEditorSupport - experiment", - "UseTerminalToGetActivatedEnvVars - experiment", - "CollectLSRequestTiming - experiment", - "CollectNodeLSRequestTiming - experiment", - "DeprecatePythonPath - experiment", - "RunByLine - experiment", - "tryPylance", - "jediLSP", - "All" - ] - }, - "description": "List of experiment to opt into. If empty, user is assigned the default experiment groups. See https://github.com/microsoft/vscode-python/wiki/Experiments for more details.", - "scope": "machine" - }, - "python.experiments.optOutFrom": { - "type": "array", - "default": [], - "items": { - "enum": [ - "AlwaysDisplayTestExplorer - experiment", - "ShowExtensionSurveyPrompt - enabled", - "Reload - experiment", - "AA_testing - experiment", - "LocalZMQKernel - experiment", - "NativeNotebook - experiment", - "CustomEditorSupport - experiment", - "UseTerminalToGetActivatedEnvVars - experiment", - "CollectLSRequestTiming - experiment", - "CollectNodeLSRequestTiming - experiment", - "DeprecatePythonPath - experiment", - "RunByLine - experiment", - "tryPylance", - "jediLSP", - "All" - ] - }, - "description": "List of experiment to opt out of. If empty, user is assigned the default experiment groups. See https://github.com/microsoft/vscode-python/wiki/Experiments for more details.", - "scope": "machine" - }, - "python.dataScience.allowImportFromNotebook": { - "type": "boolean", - "default": true, - "description": "Allows a user to import a jupyter notebook into a python file anytime one is opened.", - "scope": "resource" - }, - "python.dataScience.widgetScriptSources": { - "type": "array", - "default": [], - "items": { - "type": "string", - "enum": [ - "jsdelivr.com", - "unpkg.com" - ], - "enumDescriptions": [ - "Loads widget (javascript) scripts from https://www.jsdelivr.com/", - "Loads widget (javascript) scripts from https://unpkg.com/" - ] - }, - "uniqueItems": true, - "markdownDescription": "Defines the location and order of the sources where scripts files for Widgets are downloaded from (e.g. ipywidgest, bqplot, beakerx, ipyleaflet, etc). Not selecting any of these could result in widgets not rendering or function correctly. See [here](https://aka.ms/PVSCIPyWidgets) for more information. Once updated you will need to restart the Kernel.", - "scope": "machine" - }, - "python.dataScience.askForLargeDataFrames": { - "type": "boolean", - "default": true, - "description": "Warn the user before trying to open really large data frames.", - "scope": "application" - }, - "python.dataScience.askForKernelRestart": { - "type": "boolean", - "default": true, - "description": "Warn the user before restarting a kernel.", - "scope": "application" - }, - "python.dataScience.enabled": { - "type": "boolean", - "default": true, - "description": "Enable the experimental data science features in the python extension.", - "scope": "resource" - }, - "python.dataScience.exportWithOutputEnabled": { - "type": "boolean", - "default": false, - "description": "Enable exporting a python file into a jupyter notebook and run all cells when doing so.", - "scope": "resource" - }, - "python.dataScience.jupyterLaunchTimeout": { - "type": "number", - "default": 60000, - "description": "Amount of time (in ms) to wait for the Jupyter Notebook server to start.", - "scope": "resource" - }, - "python.dataScience.jupyterLaunchRetries": { - "type": "number", - "default": 3, - "description": "Number of times to attempt to connect to the Jupyter Notebook", - "scope": "resource" - }, - "python.dataScience.jupyterServerURI": { - "type": "string", - "default": "local", - "description": "When a Notebook Editor or Interactive Window session is started, create the kernel on the specified Jupyter server. Select 'local' to create a new Jupyter server on this local machine.", - "scope": "resource" - }, - "python.dataScience.jupyterCommandLineArguments": { - "type": "array", - "default": [], - "description": "When a Notebook Editor or Interactive Window Jupyter server is started, these arguments will be passed to it. By default this list is generated by the Python Extension.", - "scope": "resource" - }, - "python.dataScience.notebookFileRoot": { - "type": "string", - "default": "${fileDirname}", - "description": "Set the root directory for loading files for the Python Interactive window.", - "scope": "resource" - }, - "python.dataScience.searchForJupyter": { - "type": "boolean", - "default": true, - "description": "Search all installed Python interpreters for a Jupyter installation when starting the Python Interactive window", - "scope": "resource" - }, - "python.dataScience.changeDirOnImportExport": { - "type": "boolean", - "default": false, - "description": "When importing or exporting a Jupyter Notebook add a directory change command to allow relative path loading to work.", - "scope": "resource" - }, - "python.dataScience.useDefaultConfigForJupyter": { - "type": "boolean", - "default": true, - "description": "When running Jupyter locally, create a default empty Jupyter config for the Python Interactive window", - "scope": "resource" - }, - "python.dataScience.jupyterInterruptTimeout": { - "type": "number", - "default": 10000, - "description": "Amount of time (in ms) to wait for an interrupt before asking to restart the Jupyter kernel.", - "scope": "resource" - }, - "python.dataScience.allowInput": { - "type": "boolean", - "default": true, - "description": "Allow the inputting of python code directly into the Python Interactive window" - }, - "python.dataScience.showCellInputCode": { - "type": "boolean", - "default": true, - "description": "Show cell input code.", - "scope": "resource" - }, - "python.dataScience.collapseCellInputCodeByDefault": { - "type": "boolean", - "default": true, - "description": "Collapse cell input code by default.", - "scope": "resource" - }, - "python.dataScience.maxOutputSize": { - "type": "number", - "default": 400, - "description": "Maximum size (in pixels) of text output in the Notebook Editor before a scrollbar appears. First enable scrolling for cell outputs in settings.", - "scope": "resource" - }, - "python.dataScience.alwaysScrollOnNewCell": { - "type": "boolean", - "default": false, - "description": "Automatically scroll the interactive window to show the output of the last statement executed. If false, the interactive window will only automatically scroll if the bottom of the prior cell is visible.", - "scope": "resource" - }, - "python.dataScience.enableScrollingForCellOutputs": { - "type": "boolean", - "default": true, - "description": "Enables scrolling for large cell outputs in the Notebook Editor. This setting does not apply to the Python Interactive Window.", - "scope": "resource" - }, - "python.dataScience.errorBackgroundColor": { - "type": "string", - "default": "#FFFFFF", - "description": "Background color (in hex) for exception messages in the Python Interactive window.", - "scope": "resource", - "deprecationMessage": "No longer necessary as the theme colors are used for error messages" - }, - "python.dataScience.sendSelectionToInteractiveWindow": { - "type": "boolean", - "default": false, - "description": "Determines if selected code in a python file will go to the terminal or the Python Interactive window when hitting shift+enter", - "scope": "resource" - }, - "python.dataScience.showJupyterVariableExplorer": { - "type": "boolean", - "default": true, - "description": "Show the variable explorer in the Python Interactive window.", - "deprecationMessage": "This setting no longer applies. It is ignored.", - "scope": "resource" - }, - "python.dataScience.variableExplorerExclude": { - "type": "string", - "default": "module;function;builtin_function_or_method", - "description": "Types to exclude from showing in the Python Interactive variable explorer", - "scope": "resource" - }, - "python.dataScience.codeRegularExpression": { - "type": "string", - "default": "^(#\\s*%%|#\\s*\\|#\\s*In\\[\\d*?\\]|#\\s*In\\[ \\])", - "description": "Regular expression used to identify code cells. All code until the next match is considered part of this cell. \nDefaults to '^(#\\s*%%|#\\s*\\|#\\s*In\\[\\d*?\\]|#\\s*In\\[ \\])' if left blank", - "scope": "resource" - }, - "python.dataScience.defaultCellMarker": { - "type": "string", - "default": "# %%", - "description": "Cell marker used for delineating a cell in a python file.", - "scope": "resource" - }, - "python.dataScience.markdownRegularExpression": { - "type": "string", - "default": "^(#\\s*%%\\s*\\[markdown\\]|#\\s*\\)", - "description": "Regular expression used to identify markdown cells. All comments after this expression are considered part of the markdown. \nDefaults to '^(#\\s*%%\\s*\\[markdown\\]|#\\s*\\)' if left blank", - "scope": "resource" - }, - "python.dataScience.allowLiveShare": { - "type": "boolean", - "default": true, - "description": "Allow the Python Interactive window to be shared during a Live Share session", - "scope": "resource" - }, - "python.dataScience.ignoreVscodeTheme": { - "type": "boolean", - "default": false, - "description": "Don't use the VS Code theme in the Python Interactive window (requires reload of VS Code). This forces the Python Interactive window to use 'Light +(default light)' and disables matplotlib defaults.", - "scope": "resource" - }, - "python.dataScience.themeMatplotlibPlots": { - "type": "boolean", - "default": false, - "description": "In the Python Interactive window and Notebook Editor theme matplotlib outputs to match the VS Code editor theme.", - "scope": "resource" - }, - "python.dataScience.liveShareConnectionTimeout": { - "type": "number", - "default": 1000, - "description": "Amount of time to wait for guest connections to verify they have the Python extension installed.", - "scope": "application" - }, - "python.dataScience.decorateCells": { - "type": "boolean", - "default": true, - "description": "Draw a highlight behind the currently active cell.", - "scope": "resource" + "deprecated": "%python.debugger.deprecatedMessage%", + "configurationSnippets": [], + "label": "Python", + "languages": [ + "python" + ], + "type": "python", + "variables": { + "pickProcess": "python.pickLocalProcess" }, - "python.dataScience.enableCellCodeLens": { - "type": "boolean", - "default": true, - "description": "Enables code lens for 'cells' in a python file.", - "scope": "resource" - }, - "python.dataScience.enableAutoMoveToNextCell": { - "type": "boolean", - "default": true, - "description": "Enables moving to the next cell when clicking on a 'Run Cell' code lens.", - "scope": "resource" - }, - "python.dataScience.autoPreviewNotebooksInInteractivePane": { - "type": "boolean", - "deprecationMessage": "No longer supported. Notebooks open directly in their own editor now.", - "default": false, - "description": "When opening ipynb files, automatically preview the contents in the Python Interactive window.", - "scope": "resource" - }, - "python.dataScience.useNotebookEditor": { - "type": "boolean", - "default": true, - "description": "Automatically open .ipynb files in the Notebook Editor.", - "scope": "resource" - }, - "python.dataScience.allowUnauthorizedRemoteConnection": { - "type": "boolean", - "default": false, - "description": "Allow for connecting the Python Interactive window to a https Jupyter server that does not have valid certificates. This can be a security risk, so only use for known and trusted servers.", - "scope": "resource" - }, - "python.dataScience.enablePlotViewer": { - "type": "boolean", - "default": true, - "description": "Modify plot output so that it can be expanded into a plot viewer window.", - "scope": "resource" - }, - "python.dataScience.gatherToScript": { - "type": "boolean", - "default": false, - "description": "Gather code to a python script rather than a notebook.", - "scope": "resource" - }, - "python.dataScience.gatherSpecPath": { - "type": "string", - "default": "", - "description": "This setting specifies a folder that contains additional or replacement spec files used for analysis.", - "scope": "resource" - }, - "python.dataScience.codeLenses": { - "type": "string", - "default": "python.datascience.runcell, python.datascience.runallcellsabove, python.datascience.debugcell", - "description": "Set of commands to put as code lens above a cell. Defaults to 'python.datascience.runcell, python.datascience.runallcellsabove, python.datascience.debugcell'", - "scope": "resource" - }, - "python.dataScience.debugCodeLenses": { - "type": "string", - "default": "python.datascience.debugcontinue, python.datascience.debugstop, python.datascience.debugstepover", - "description": "Set of debug commands to put as code lens above a cell while debugging.", - "scope": "resource" - }, - "python.dataScience.debugpyDistPath": { - "type": "string", - "default": "", - "description": "Path to debugpy bits for debugging cells.", - "scope": "resource" - }, - "python.dataScience.stopOnFirstLineWhileDebugging": { - "type": "boolean", - "default": true, - "description": "When debugging a cell, stop on the first line.", - "scope": "resource" - }, - "python.dataScience.remoteDebuggerPort": { - "type": "number", - "default": -1, - "description": "When debugging a cell, open this port on the remote box. If -1 is specified, a random port between 8889 and 9000 will be attempted.", - "scope": "resource" - }, - "python.dataScience.disableJupyterAutoStart": { - "type": "boolean", - "default": false, - "description": "When true, disables Jupyter from being automatically started for you. You must instead run a cell to start Jupyter.", - "scope": "resource" - }, - "python.dataScience.textOutputLimit": { - "type": "number", - "default": 20000, - "description": "Limit the amount of text in Python Interactive cell text output to this value. 0 to allow any amount of characters.", - "scope": "resource" - }, - "python.dataScience.colorizeInputBox": { - "type": "boolean", - "default": true, - "description": "Whether or not to use the theme's peek color as the background for the input box.", - "scope": "resource" - }, - "python.dataScience.stopOnError": { - "type": "boolean", - "default": true, - "description": "Stop running cells if a cell throws an exception.", - "scope": "resource" - }, - "python.dataScience.addGotoCodeLenses": { - "type": "boolean", - "default": true, - "description": "After running a cell, add a 'Goto' code lens on the cell. Note, disabling all code lenses disables this code lens as well.", - "scope": "resource" - }, - "python.dataScience.variableQueries": { - "type": "array", - "description": "Language to query mapping for returning the list of active variables in a Jupyter kernel. Used by the Variable Explorer in both the Interactive Window and Notebooks. Example: \n'[\n{\n \"language\": \"python\",\n \"query\": \"%who_ls\",\n \"parseExpr\": \"'(\\\\w+)'\"\n}\n]'", - "scope": "machine", - "examples": [ - [ - { - "language": "python", - "query": "_rwho_ls = %who_ls\\nprint(_rwho_ls)", - "parseExpr": "'(\\w+)'" - }, - { - "language": "julia", - "query": "whos", - "parseExpr": "'(\\w+)'" - } - ] - ] - }, - "python.dataScience.interactiveWindowMode": { - "type": "string", - "enum": [ - "perFile", - "single", - "multiple" - ], - "scope": "resource", - "description": "Behavior of the Python Interactive Window. 'perFile' will create a new interactive window for every file that runs a cell. 'single' allows a single window. 'multiple' allows the creation of multiple.", - "default": "multiple" - }, - "python.disableInstallationCheck": { - "type": "boolean", - "default": false, - "description": "Whether to check if Python is installed (also warn when using the macOS-installed Python).", - "scope": "resource" - }, - "python.envFile": { - "type": "string", - "description": "Absolute path to a file containing environment variable definitions.", - "default": "${workspaceFolder}/.env", - "scope": "resource" - }, - "python.formatting.autopep8Args": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.formatting.autopep8Path": { - "type": "string", - "default": "autopep8", - "description": "Path to autopep8, you can use a custom version of autopep8 by modifying this setting to include the full path.", - "scope": "resource" - }, - "python.formatting.provider": { - "type": "string", - "default": "autopep8", - "description": "Provider for formatting. Possible options include 'autopep8', 'black', and 'yapf'.", - "enum": [ - "autopep8", - "black", - "yapf", - "none" - ], - "scope": "resource" - }, - "python.formatting.blackArgs": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.formatting.blackPath": { - "type": "string", - "default": "black", - "description": "Path to Black, you can use a custom version of Black by modifying this setting to include the full path.", - "scope": "resource" - }, - "python.formatting.yapfArgs": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.formatting.yapfPath": { - "type": "string", - "default": "yapf", - "description": "Path to yapf, you can use a custom version of yapf by modifying this setting to include the full path.", - "scope": "resource" - }, - "python.globalModuleInstallation": { - "type": "boolean", - "default": false, - "description": "Whether to install Python modules globally when not using an environment.", - "scope": "resource" - }, - "python.jediMemoryLimit": { - "type": "number", - "default": 0, - "description": "Memory limit for the Jedi completion engine in megabytes. Zero (default) means 1024 MB. -1 means unlimited (disable memory limit check)", - "scope": "resource" - }, - "python.jediPath": { - "type": "string", - "default": "", - "description": "Path to directory containing the Jedi library (this path will contain the 'Jedi' sub directory). Note: since Jedi depends on Parso, if using this setting you will need to ensure a suitable version of Parso is available.", - "scope": "resource" - }, - "python.languageServer": { - "type": "string", - "enum": [ - "Jedi", - "Pylance", - "Microsoft", - "None" - ], - "default": "Jedi", - "description": "Defines type of the language server.", - "scope": "window" - }, - "python.analysis.diagnosticPublishDelay": { - "type": "integer", - "default": 1000, - "description": "Delay before diagnostic messages are transferred to the problems list (in milliseconds).", - "scope": "resource" - }, - "python.analysis.errors": { - "type": "array", - "default": [], - "items": { - "type": "string" - }, - "description": "List of diagnostics messages to be shown as errors.", - "scope": "resource" - }, - "python.analysis.warnings": { - "type": "array", - "default": [], - "items": { - "type": "string" - }, - "description": "List of diagnostics messages to be shown as warnings.", - "scope": "resource" - }, - "python.analysis.information": { - "type": "array", - "default": [], - "items": { - "type": "string" - }, - "description": "List of diagnostics messages to be shown as information.", - "scope": "resource" - }, - "python.analysis.disabled": { - "type": "array", - "default": [], - "items": { - "type": "string" - }, - "description": "List of suppressed diagnostic messages.", - "scope": "resource" - }, - "python.analysis.typeshedPaths": { - "type": "array", - "default": [], - "items": { - "type": "string" - }, - "description": "Paths to Typeshed stub folders. Default is Typeshed installed with the language server. Change requires restart.", - "scope": "resource" - }, - "python.analysis.cacheFolderPath": { - "type": "string", - "description": "Path to a writable folder where analyzer can cache its data. Change requires restart.", - "scope": "resource" - }, - "python.analysis.memory.keepLibraryAst": { - "type": "boolean", - "default": false, - "description": "Allows code analysis to keep parser trees in memory. Increases memory consumption but may improve performance with large library analysis.", - "scope": "resource" - }, - "python.analysis.logLevel": { - "type": "string", - "enum": [ - "Error", - "Warning", - "Information", - "Trace" - ], - "default": "Error", - "description": "Defines type of log messages language server writes into the output window.", - "scope": "resource" - }, - "python.analysis.symbolsHierarchyDepthLimit": { - "type": "integer", - "default": 10, - "description": "Limits depth of the symbol tree in the document outline.", - "scope": "resource" - }, - "python.linting.enabled": { - "type": "boolean", - "default": true, - "description": "Whether to lint Python files.", - "scope": "resource" - }, - "python.linting.flake8Args": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.linting.flake8CategorySeverity.E": { - "type": "string", - "default": "Error", - "description": "Severity of Flake8 message type 'E'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], - "scope": "resource" - }, - "python.linting.flake8CategorySeverity.F": { - "type": "string", - "default": "Error", - "description": "Severity of Flake8 message type 'F'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], - "scope": "resource" - }, - "python.linting.flake8CategorySeverity.W": { - "type": "string", - "default": "Warning", - "description": "Severity of Flake8 message type 'W'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], - "scope": "resource" - }, - "python.linting.flake8Enabled": { - "type": "boolean", - "default": false, - "description": "Whether to lint Python files using flake8", - "scope": "resource" - }, - "python.linting.flake8Path": { - "type": "string", - "default": "flake8", - "description": "Path to flake8, you can use a custom version of flake8 by modifying this setting to include the full path.", - "scope": "resource" - }, - "python.linting.ignorePatterns": { - "type": "array", - "description": "Patterns used to exclude files or folders from being linted.", - "default": [ - ".vscode/*.py", - "**/site-packages/**/*.py" - ], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.linting.lintOnSave": { - "type": "boolean", - "default": true, - "description": "Whether to lint Python files when saved.", - "scope": "resource" - }, - "python.linting.maxNumberOfProblems": { - "type": "number", - "default": 100, - "description": "Controls the maximum number of problems produced by the server.", - "scope": "resource" - }, - "python.linting.banditArgs": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.linting.banditEnabled": { - "type": "boolean", - "default": false, - "description": "Whether to lint Python files using bandit.", - "scope": "resource" - }, - "python.linting.banditPath": { - "type": "string", - "default": "bandit", - "description": "Path to bandit, you can use a custom version of bandit by modifying this setting to include the full path.", - "scope": "resource" - }, - "python.linting.mypyArgs": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [ - "--ignore-missing-imports", - "--follow-imports=silent", - "--show-column-numbers" - ], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.linting.mypyCategorySeverity.error": { - "type": "string", - "default": "Error", - "description": "Severity of Mypy message type 'Error'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], - "scope": "resource" - }, - "python.linting.mypyCategorySeverity.note": { - "type": "string", - "default": "Information", - "description": "Severity of Mypy message type 'Note'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], - "scope": "resource" - }, - "python.linting.mypyEnabled": { - "type": "boolean", - "default": false, - "description": "Whether to lint Python files using mypy.", - "scope": "resource" - }, - "python.linting.mypyPath": { - "type": "string", - "default": "mypy", - "description": "Path to mypy, you can use a custom version of mypy by modifying this setting to include the full path.", - "scope": "resource" - }, - "python.linting.pycodestyleArgs": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.linting.pycodestyleCategorySeverity.E": { - "type": "string", - "default": "Error", - "description": "Severity of pycodestyle message type 'E'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], - "scope": "resource" - }, - "python.linting.pycodestyleCategorySeverity.W": { - "type": "string", - "default": "Warning", - "description": "Severity of pycodestyle message type 'W'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], - "scope": "resource" - }, - "python.linting.pycodestyleEnabled": { - "type": "boolean", - "default": false, - "description": "Whether to lint Python files using pycodestyle", - "scope": "resource" - }, - "python.linting.pycodestylePath": { - "type": "string", - "default": "pycodestyle", - "description": "Path to pycodestyle, you can use a custom version of pycodestyle by modifying this setting to include the full path.", - "scope": "resource" - }, - "python.linting.prospectorArgs": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.linting.prospectorEnabled": { - "type": "boolean", - "default": false, - "description": "Whether to lint Python files using prospector.", - "scope": "resource" - }, - "python.linting.prospectorPath": { - "type": "string", - "default": "prospector", - "description": "Path to Prospector, you can use a custom version of prospector by modifying this setting to include the full path.", - "scope": "resource" - }, - "python.linting.pydocstyleArgs": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.linting.pydocstyleEnabled": { - "type": "boolean", - "default": false, - "description": "Whether to lint Python files using pydocstyle", - "scope": "resource" - }, - "python.linting.pydocstylePath": { - "type": "string", - "default": "pydocstyle", - "description": "Path to pydocstyle, you can use a custom version of pydocstyle by modifying this setting to include the full path.", - "scope": "resource" - }, - "python.linting.pylamaArgs": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.linting.pylamaEnabled": { - "type": "boolean", - "default": false, - "description": "Whether to lint Python files using pylama.", - "scope": "resource" - }, - "python.linting.pylamaPath": { - "type": "string", - "default": "pylama", - "description": "Path to pylama, you can use a custom version of pylama by modifying this setting to include the full path.", - "scope": "resource" - }, - "python.linting.pylintArgs": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.linting.pylintCategorySeverity.convention": { - "type": "string", - "default": "Information", - "description": "Severity of Pylint message type 'Convention/C'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], - "scope": "resource" - }, - "python.linting.pylintCategorySeverity.error": { - "type": "string", - "default": "Error", - "description": "Severity of Pylint message type 'Error/E'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], - "scope": "resource" - }, - "python.linting.pylintCategorySeverity.fatal": { - "type": "string", - "default": "Error", - "description": "Severity of Pylint message type 'Fatal/F'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], - "scope": "resource" - }, - "python.linting.pylintCategorySeverity.refactor": { - "type": "string", - "default": "Hint", - "description": "Severity of Pylint message type 'Refactor/R'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], - "scope": "resource" - }, - "python.linting.pylintCategorySeverity.warning": { - "type": "string", - "default": "Warning", - "description": "Severity of Pylint message type 'Warning/W'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], - "scope": "resource" - }, - "python.linting.pylintEnabled": { - "type": "boolean", - "default": true, - "description": "Whether to lint Python files using pylint.", - "scope": "resource" - }, - "python.linting.pylintPath": { - "type": "string", - "default": "pylint", - "description": "Path to Pylint, you can use a custom version of pylint by modifying this setting to include the full path.", - "scope": "resource" - }, - "python.linting.pylintUseMinimalCheckers": { - "type": "boolean", - "default": true, - "description": "Whether to run Pylint with minimal set of rules.", - "scope": "resource" - }, - "python.pythonPath": { - "type": "string", - "default": "python", - "description": "Path to Python, you can use a custom version of Python by modifying this setting to include the full path.", - "scope": "resource" - }, - "python.condaPath": { - "type": "string", - "default": "", - "description": "Path to the conda executable to use for activation (version 4.4+).", - "scope": "resource" - }, - "python.pipenvPath": { - "type": "string", - "default": "pipenv", - "description": "Path to the pipenv executable to use for activation.", - "scope": "resource" - }, - "python.poetryPath": { - "type": "string", - "default": "poetry", - "description": "Path to the poetry executable.", - "scope": "resource" - }, - "python.sortImports.args": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.sortImports.path": { - "type": "string", - "description": "Path to isort script, default using inner version", - "default": "", - "scope": "resource" - }, - "python.terminal.activateEnvironment": { - "type": "boolean", - "default": true, - "description": "Activate Python Environment in Terminal created using the Extension.", - "scope": "resource" - }, - "python.terminal.executeInFileDir": { - "type": "boolean", - "default": false, - "description": "When executing a file in the terminal, whether to use execute in the file's directory, instead of the current open folder.", - "scope": "resource" - }, - "python.terminal.launchArgs": { - "type": "array", - "default": [], - "description": "Python launch arguments to use when executing a file in the terminal.", - "scope": "resource" - }, - "python.terminal.activateEnvInCurrentTerminal": { - "type": "boolean", - "default": false, - "description": "Activate Python Environment in the current Terminal on load of the Extension.", - "scope": "resource" - }, - "python.testing.cwd": { - "type": "string", - "default": null, - "description": "Optional working directory for tests.", - "scope": "resource" - }, - "python.testing.debugPort": { - "type": "number", - "default": 3000, - "description": "Port number used for debugging of tests.", - "scope": "resource" - }, - "python.testing.nosetestArgs": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.testing.nosetestsEnabled": { - "type": "boolean", - "default": false, - "description": "Enable testing using nosetests.", - "scope": "resource" - }, - "python.testing.nosetestPath": { - "type": "string", - "default": "nosetests", - "description": "Path to nosetests, you can use a custom version of nosetests by modifying this setting to include the full path.", - "scope": "resource" - }, - "python.testing.promptToConfigure": { - "type": "boolean", - "default": true, - "description": "Prompt to configure a test framework if potential tests directories are discovered.", - "scope": "resource" - }, - "python.testing.pytestArgs": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.testing.pytestEnabled": { - "type": "boolean", - "default": false, - "description": "Enable testing using pytest.", - "scope": "resource" - }, - "python.testing.pytestPath": { - "type": "string", - "default": "pytest", - "description": "Path to pytest (pytest), you can use a custom version of pytest by modifying this setting to include the full path.", - "scope": "resource" - }, - "python.testing.unittestArgs": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [ - "-v", - "-s", - ".", - "-p", - "*test*.py" - ], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.testing.unittestEnabled": { - "type": "boolean", - "default": false, - "description": "Enable testing using unittest.", - "scope": "resource" - }, - "python.testing.autoTestDiscoverOnSaveEnabled": { - "type": "boolean", - "default": true, - "description": "Enable auto run test discovery when saving a test file.", - "scope": "resource" - }, - "python.venvFolders": { - "type": "array", - "default": [], - "description": "Folders in your home directory to look into for virtual environments (supports pyenv, direnv and virtualenvwrapper by default).", - "scope": "resource", - "items": { - "type": "string" - } - }, - "python.venvPath": { - "type": "string", - "default": "", - "description": "Path to folder with a list of Virtual Environments (e.g. ~/.pyenv, ~/Envs, ~/.virtualenvs).", - "scope": "resource" - }, - "python.workspaceSymbols.ctagsPath": { - "type": "string", - "default": "ctags", - "description": "Fully qualified path to the ctags executable (else leave as ctags, assuming it is in current path).", - "scope": "resource" - }, - "python.workspaceSymbols.enabled": { - "type": "boolean", - "default": false, - "description": "Set to 'true' to enable ctags to provide Workspace Symbols.", - "scope": "resource" - }, - "python.workspaceSymbols.exclusionPatterns": { - "type": "array", - "default": [ - "**/site-packages/**" - ], - "items": { - "type": "string" - }, - "description": "Pattern used to exclude files and folders from ctags See http://ctags.sourceforge.net/ctags.html.", - "scope": "resource" - }, - "python.workspaceSymbols.rebuildOnFileSave": { - "type": "boolean", - "default": true, - "description": "Whether to re-build the tags file on when changes made to python files are saved.", - "scope": "resource" - }, - "python.workspaceSymbols.rebuildOnStart": { - "type": "boolean", - "default": true, - "description": "Whether to re-build the tags file on start (defaults to true).", - "scope": "resource" - }, - "python.workspaceSymbols.tagFilePath": { - "type": "string", - "default": "${workspaceFolder}/.vscode/tags", - "description": "Fully qualified path to tag file (exuberant ctag file), used to provide workspace symbols.", - "scope": "resource" - }, - "python.dataScience.magicCommandsAsComments": { - "type": "boolean", - "default": false, - "description": "Uncomment shell assignments (#!), line magic (#!%) and cell magic (#!%%) when parsing code cells.", - "scope": "resource" - }, - "python.dataScience.runMagicCommands": { - "type": "string", - "default": "", - "deprecationMessage": "This setting has been deprecated in favor of 'runStartupCommands'.", - "description": "A series of Python instructions or iPython magic commands separated by '\\n' that will be executed when the interactive window loads.", - "scope": "application" - }, - "python.dataScience.runStartupCommands": { - "type": "array", - "default": "", - "description": "A series of Python instructions or iPython magic commands. Can be either an array of strings or a single string with commands separated by '\\n'. Commands will be silently executed whenever the interactive window loads. For instance, set this to '%load_ext autoreload\\n%autoreload 2' to automatically reload changes made to imported files without having to restart the interactive session.", - "scope": "application" - }, - "python.dataScience.debugJustMyCode": { - "type": "boolean", - "default": true, - "description": "When debugging, debug just my code.", - "scope": "resource" - }, - "python.dataScience.alwaysTrustNotebooks": { - "type": "boolean", - "default": false, - "markdownDescription": "Enabling this setting will automatically trust any opened notebook and therefore display markdown and render code cells. You will no longer be prompted to trust individual notebooks and harmful code could automatically run. \n\n[Learn more.](https://aka.ms/trusted-notebooks)", - "scope": "machine" - }, - "python.insidersChannel": { - "type": "string", - "default": "off", - "description": "Set to \"weekly\" or \"daily\" to automatically download and install the latest Insiders builds of the python extension, which include upcoming features and bug fixes.", - "enum": [ - "off", - "weekly", - "daily" - ], - "scope": "application" - }, - "python.showStartPage": { - "type": "boolean", - "default": true, - "description": "Show the Python Start Page when a new update is released.", - "scope": "application" - } + "when": "!virtualWorkspace && shellExecutionSupported", + "hiddenWhen": "true" + } + ], + "grammars": [ + { + "language": "pip-requirements", + "path": "./syntaxes/pip-requirements.tmLanguage.json", + "scopeName": "source.pip-requirements" + } + ], + "jsonValidation": [ + { + "fileMatch": ".condarc", + "url": "./schemas/condarc.json" + }, + { + "fileMatch": "environment.yml", + "url": "./schemas/conda-environment.json" + }, + { + "fileMatch": "meta.yaml", + "url": "./schemas/conda-meta.json" } - }, + ], + "keybindings": [ + { + "command": "python.execSelectionInTerminal", + "key": "shift+enter", + "when": "editorTextFocus && editorLangId == python && !findInputFocussed && !replaceInputFocussed && !jupyter.ownsSelection && !notebookEditorFocused && !isCompositeNotebook" + }, + { + "command": "python.execInREPL", + "key": "shift+enter", + "when": "config.python.REPL.sendToNativeREPL && editorLangId == python && editorTextFocus && !jupyter.ownsSelection && !notebookEditorFocused && !isCompositeNotebook" + }, + { + "command": "python.execInREPLEnter", + "key": "enter", + "when": "!config.interactiveWindow.executeWithShiftEnter && isCompositeNotebook && activeEditor == 'workbench.editor.repl' && !inlineChatFocused && !notebookCellListFocused" + }, + { + "command": "python.execInInteractiveWindowEnter", + "key": "enter", + "when": "!config.interactiveWindow.executeWithShiftEnter && isCompositeNotebook && activeEditor == 'workbench.editor.interactive' && !inlineChatFocused && !notebookCellListFocused" + } + ], "languages": [ { - "id": "pip-requirements", + "aliases": [ + "Jinja" + ], + "extensions": [ + ".j2", + ".jinja2" + ], + "id": "jinja" + }, + { "aliases": [ "pip requirements", "requirements.txt" ], + "configuration": "./languages/pip-requirements.json", + "filenamePatterns": [ + "**/*requirements*.{txt, in}", + "**/*constraints*.txt", + "**/requirements/*.{txt,in}", + "**/constraints/*.txt" + ], "filenames": [ - "requirements.txt", "constraints.txt", - "requirements.in" - ], - "filenamePatterns": [ - "*-requirements.txt", - "requirements-*.txt", - "constraints-*.txt", - "*-constraints.txt", - "*-requirements.in", - "requirements-*.in" + "requirements.in", + "requirements.txt" ], - "configuration": "./languages/pip-requirements.json" + "id": "pip-requirements" }, { - "id": "yaml", "filenames": [ ".condarc" - ] - }, - { - "id": "toml", - "filenames": [ - "poetry.lock", - "Pipfile" - ] + ], + "id": "yaml" }, { - "id": "json", "filenames": [ - "Pipfile.lock" - ] + ".flake8", + ".pep8", + ".pylintrc", + ".pypirc" + ], + "id": "ini" }, { - "id": "ini", "filenames": [ - ".flake8" - ] - }, - { - "id": "jinja", - "extensions": [ - ".jinja2", - ".j2" + "Pipfile", + "poetry.lock", + "uv.lock" ], - "aliases": [ - "Jinja" - ] + "id": "toml" }, { - "id": "jupyter", - "aliases": [ - "Jupyter", - "Notebook" + "filenames": [ + "Pipfile.lock" ], - "extensions": [ - ".ipynb" - ] - } - ], - "grammars": [ - { - "language": "pip-requirements", - "scopeName": "source.pip-requirements", - "path": "./syntaxes/pip-requirements.tmLanguage.json" + "id": "json" } ], - "jsonValidation": [ + "menus": { + "issue/reporter": [ + { + "command": "python.reportIssue" + } + ], + "testing/item/context": [ + { + "command": "python.copyTestId", + "group": "navigation", + "when": "controllerId == 'python-tests'" + } + ], + "testing/item/gutter": [ + { + "command": "python.copyTestId", + "group": "navigation", + "when": "controllerId == 'python-tests'" + } + ], + "commandPalette": [ + { + "category": "Python", + "command": "python.analysis.restartLanguageServer", + "title": "%python.command.python.analysis.restartLanguageServer.title%", + "when": "!virtualWorkspace && shellExecutionSupported && (editorLangId == python || notebookType == jupyter-notebook)" + }, + { + "category": "Python", + "command": "python.clearCacheAndReload", + "title": "%python.command.python.clearCacheAndReload.title%", + "when": "!virtualWorkspace && shellExecutionSupported" + }, + { + "category": "Python", + "command": "python.clearWorkspaceInterpreter", + "title": "%python.command.python.clearWorkspaceInterpreter.title%", + "when": "!virtualWorkspace && shellExecutionSupported" + }, + { + "category": "Python", + "command": "python.configureTests", + "title": "%python.command.python.configureTests.title%", + "when": "!virtualWorkspace && shellExecutionSupported" + }, + { + "category": "Python", + "command": "python.createEnvironment", + "title": "%python.command.python.createEnvironment.title%", + "when": "!virtualWorkspace && shellExecutionSupported" + }, + { + "category": "Python", + "command": "python.createEnvironment-button", + "title": "%python.command.python.createEnvironment.title%", + "when": "false" + }, + { + "category": "Python", + "command": "python.createTerminal", + "title": "%python.command.python.createTerminal.title%", + "when": "!virtualWorkspace && shellExecutionSupported" + }, + { + "category": "Python", + "command": "python.execInTerminal", + "title": "%python.command.python.execInTerminal.title%", + "when": "!virtualWorkspace && shellExecutionSupported && editorLangId == python" + }, + { + "category": "Python", + "command": "python.execInTerminal-icon", + "icon": "$(play)", + "title": "%python.command.python.execInTerminalIcon.title%", + "when": "false" + }, + { + "category": "Python", + "command": "python.execInDedicatedTerminal", + "icon": "$(play)", + "title": "%python.command.python.execInDedicatedTerminal.title%", + "when": "false" + }, + { + "category": "Python", + "command": "python.execSelectionInDjangoShell", + "title": "%python.command.python.execSelectionInDjangoShell.title%", + "when": "!virtualWorkspace && shellExecutionSupported && editorLangId == python" + }, + { + "category": "Python", + "command": "python.execSelectionInTerminal", + "title": "%python.command.python.execSelectionInTerminal.title%", + "when": "!virtualWorkspace && shellExecutionSupported && editorLangId == python" + }, + { + "category": "Python", + "command": "python.copyTestId", + "title": "%python.command.python.testing.copyTestId.title%", + "when": "false" + }, + { + "category": "Python", + "command": "python.execInREPL", + "title": "%python.command.python.execInREPL.title%", + "when": "false" + }, + { + "category": "Python", + "command": "python.reportIssue", + "title": "%python.command.python.reportIssue.title%", + "when": "!virtualWorkspace && shellExecutionSupported" + }, + { + "category": "Test", + "command": "testing.reRunFailTests", + "icon": "$(run-errors)", + "title": "%python.command.testing.rerunFailedTests.title%", + "when": "!virtualWorkspace && shellExecutionSupported" + }, + { + "category": "Python", + "command": "python.setInterpreter", + "title": "%python.command.python.setInterpreter.title%", + "when": "!virtualWorkspace && shellExecutionSupported" + }, + { + "category": "Python", + "command": "python.startREPL", + "title": "%python.command.python.startTerminalREPL.title%", + "when": "!virtualWorkspace && shellExecutionSupported" + }, + { + "category": "Python", + "command": "python.startNativeREPL", + "title": "%python.command.python.startNativeREPL.title%", + "when": "!virtualWorkspace && shellExecutionSupported" + }, + { + "category": "Python", + "command": "python.viewLanguageServerOutput", + "enablement": "python.hasLanguageServerOutputChannel", + "title": "%python.command.python.viewLanguageServerOutput.title%", + "when": "!virtualWorkspace && shellExecutionSupported" + }, + { + "category": "Python", + "command": "python.viewOutput", + "title": "%python.command.python.viewOutput.title%", + "when": "!virtualWorkspace && shellExecutionSupported" + } + ], + "editor/content": [ + { + "group": "Python", + "command": "python.createEnvironment-button", + "when": "showCreateEnvButton && resourceLangId == pip-requirements && !virtualWorkspace && shellExecutionSupported && !inDiffEditor && !isMergeResultEditor && pythonDepsNotInstalled" + }, + { + "group": "Python", + "command": "python.createEnvironment-button", + "when": "showCreateEnvButton && resourceFilename == pyproject.toml && pipInstallableToml && !virtualWorkspace && shellExecutionSupported && !inDiffEditor && !isMergeResultEditor && pythonDepsNotInstalled" + } + ], + "editor/context": [ + { + "submenu": "python.run", + "group": "Python", + "when": "editorLangId == python && !virtualWorkspace && shellExecutionSupported && isWorkspaceTrusted && !inChat && notebookType != jupyter-notebook" + }, + { + "submenu": "python.runFileInteractive", + "group": "Jupyter2", + "when": "editorLangId == python && !virtualWorkspace && shellExecutionSupported && !isJupyterInstalled && isWorkspaceTrusted && !inChat" + } + ], + "python.runFileInteractive": [ + { + "command": "python.installJupyter", + "group": "Jupyter2", + "when": "resourceLangId == python && !virtualWorkspace && shellExecutionSupported" + } + ], + "python.run": [ + { + "command": "python.execInTerminal", + "group": "Python", + "when": "resourceLangId == python && !virtualWorkspace && shellExecutionSupported" + }, + { + "command": "python.execSelectionInDjangoShell", + "group": "Python", + "when": "editorHasSelection && editorLangId == python && python.isDjangoProject && !virtualWorkspace && shellExecutionSupported" + }, + { + "command": "python.execSelectionInTerminal", + "group": "Python", + "when": "!config.python.REPL.sendToNativeREPL && editorFocus && editorLangId == python && !virtualWorkspace && shellExecutionSupported" + }, + { + "command": "python.execInREPL", + "group": "Python", + "when": "editorFocus && editorLangId == python && !virtualWorkspace && shellExecutionSupported && config.python.REPL.sendToNativeREPL" + } + ], + "editor/title/run": [ + { + "command": "python.execInTerminal-icon", + "group": "navigation@0", + "title": "%python.command.python.execInTerminalIcon.title%", + "when": "resourceLangId == python && !isInDiffEditor && !virtualWorkspace && shellExecutionSupported" + }, + { + "command": "python.execInDedicatedTerminal", + "group": "navigation@0", + "title": "%python.command.python.execInDedicatedTerminal.title%", + "when": "resourceLangId == python && !isInDiffEditor && !virtualWorkspace && shellExecutionSupported" + } + ], + "explorer/context": [ + { + "command": "python.execInTerminal", + "group": "Python", + "when": "resourceLangId == python && !virtualWorkspace && shellExecutionSupported" + } + ], + "file/newFile": [ + { + "command": "python.createNewFile", + "group": "file", + "when": "!virtualWorkspace" + } + ], + "view/title": [ + { + "command": "testing.reRunFailTests", + "when": "view == workbench.view.testing && hasFailedTests && !virtualWorkspace && shellExecutionSupported", + "group": "navigation@1" + } + ] + }, + "submenus": [ { - "fileMatch": ".condarc", - "url": "./schemas/condarc.json" + "id": "python.run", + "label": "%python.editor.context.submenu.runPython%", + "icon": "$(play)" }, { - "fileMatch": "environment.yml", - "url": "./schemas/conda-environment.json" - }, + "id": "python.runFileInteractive", + "label": "%python.editor.context.submenu.runPythonInteractive%" + } + ], + "viewsWelcome": [ { - "fileMatch": "meta.yaml", - "url": "./schemas/conda-meta.json" + "view": "testing", + "contents": "Configure a test framework to see your tests here.\n[Configure Python Tests](command:python.configureTests)", + "when": "!virtualWorkspace && shellExecutionSupported" } ], "yamlValidation": [ @@ -3381,412 +1504,310 @@ "url": "./schemas/conda-meta.json" } ], - "views": { - "test": [ - { - "id": "python_tests", - "name": "Python", - "when": "testsDiscovered" + "languageModelTools": [ + { + "name": "get_python_environment_details", + "displayName": "Get Python Environment Info", + "userDescription": "%python.languageModelTools.get_python_environment_details.userDescription%", + "modelDescription": "This tool will retrieve the details of the Python Environment for the specified file or workspace. The details returned include the 1. Type of Python Environment (conda, venv, etc), 2. Version of Python, 3. List of all installed Python packages with their versions. ALWAYS call configure_python_environment before using this tool. IMPORTANT: This tool is only for Python environments (venv, virtualenv, conda, pipenv, poetry, pyenv, pixi, or any other Python environment manager). Do not use this tool for npm packages, system packages, Ruby gems, or any other non-Python dependencies.", + "toolReferenceName": "getPythonEnvironmentInfo", + "tags": [ + "python", + "python environment", + "extension_installed_by_tool", + "enable_other_tool_configure_python_environment" + ], + "icon": "$(snake)", + "canBeReferencedInPrompt": true, + "inputSchema": { + "type": "object", + "properties": { + "resourcePath": { + "type": "string", + "description": "The path to the Python file or workspace to get the environment information for." + } + }, + "required": [] } - ] - }, - "notebookOutputRenderer": [ + }, { - "id": "jupyter-notebook-renderer", - "entrypoint": "./out/datascience-ui/renderers/renderers.js", - "displayName": "Jupyter Notebook Renderer", - "mimeTypes": [ - "application/geo+json", - "application/vdom.v1+json", - "application/vnd.dataresource+json", - "application/vnd.plotly.v1+json", - "application/vnd.vega.v2+json", - "application/vnd.vega.v3+json", - "application/vnd.vega.v4+json", - "application/vnd.vega.v5+json", - "application/vnd.vegalite.v1+json", - "application/vnd.vegalite.v2+json", - "application/vnd.vegalite.v3+json", - "application/vnd.vegalite.v4+json", - "application/x-nteract-model-debug+json", - "image/gif", - "image/png", - "image/jpeg", - "text/latex", - "text/vnd.plotly.v1+html" + "name": "get_python_executable_details", + "displayName": "Get Python Executable", + "userDescription": "%python.languageModelTools.get_python_executable_details.userDescription%", + "modelDescription": "This tool will retrieve the details of the Python Environment for the specified file or workspace. ALWAYS use this tool before executing any Python command in the terminal. This tool returns the details of how to construct the fully qualified path and or command including details such as arguments required to run Python in a terminal. Note: Instead of executing `python --version` or `python -c 'import sys; print(sys.executable)'`, use this tool to get the Python executable path to replace the `python` command. E.g. instead of using `python -c 'import sys; print(sys.executable)'`, use this tool to build the command `conda run -n -c 'import sys; print(sys.executable)'`. ALWAYS call configure_python_environment before using this tool. IMPORTANT: This tool is only for Python environments (venv, virtualenv, conda, pipenv, poetry, pyenv, pixi, or any other Python environment manager). Do not use this tool for npm packages, system packages, Ruby gems, or any other non-Python dependencies.", + "toolReferenceName": "getPythonExecutableCommand", + "tags": [ + "python", + "python environment", + "extension_installed_by_tool", + "enable_other_tool_configure_python_environment" ], - "viewType": "jupyter-notebook" - } - ], - "notebookProvider": [ + "icon": "$(terminal)", + "canBeReferencedInPrompt": true, + "inputSchema": { + "type": "object", + "properties": { + "resourcePath": { + "type": "string", + "description": "The path to the Python file or workspace to get the executable information for. If not provided, the current workspace will be used. Where possible pass the path to the file or workspace." + } + }, + "required": [] + } + }, { - "viewType": "jupyter-notebook", - "displayName": "Jupyter Notebook (preview)", - "selector": [ - { - "filenamePattern": "*.ipynb" - } + "name": "install_python_packages", + "displayName": "Install Python Package", + "userDescription": "%python.languageModelTools.install_python_packages.userDescription%", + "modelDescription": "Installs Python packages in the given workspace. Use this tool to install Python packages in the user's chosen Python environment. ALWAYS call configure_python_environment before using this tool. IMPORTANT: This tool should only be used to install Python packages using package managers like pip or conda (works with any Python environment: venv, virtualenv, pipenv, poetry, pyenv, pixi, conda, etc.). Do not use this tool to install npm packages, system packages (apt/brew/yum), Ruby gems, or any other non-Python dependencies.", + "toolReferenceName": "installPythonPackage", + "tags": [ + "python", + "python environment", + "install python package", + "extension_installed_by_tool", + "enable_other_tool_configure_python_environment" ], - "priority": "option" - } - ], - "customEditors": [ + "icon": "$(package)", + "canBeReferencedInPrompt": true, + "inputSchema": { + "type": "object", + "properties": { + "packageList": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The list of Python packages to install." + }, + "resourcePath": { + "type": "string", + "description": "The path to the Python file or workspace into which the packages are installed. If not provided, the current workspace will be used. Where possible pass the path to the file or workspace." + } + }, + "required": [ + "packageList" + ] + } + }, { - "viewType": "ms-python.python.notebook.ipynb", - "displayName": "Jupyter Notebook", - "selector": [ - { - "filenamePattern": "*.ipynb" - } + "name": "configure_python_environment", + "displayName": "Configure Python Environment", + "modelDescription": "This tool configures a Python environment in the given workspace. ALWAYS Use this tool to set up the user's chosen environment and ALWAYS call this tool before using any other Python related tools or running any Python command in the terminal. IMPORTANT: This tool is only for Python environments (venv, virtualenv, conda, pipenv, poetry, pyenv, pixi, or any other Python environment manager). Do not use this tool for npm packages, system packages, Ruby gems, or any other non-Python dependencies.", + "userDescription": "%python.languageModelTools.configure_python_environment.userDescription%", + "toolReferenceName": "configurePythonEnvironment", + "tags": [ + "python", + "python environment", + "extension_installed_by_tool" ], - "priority": "option" + "icon": "$(gear)", + "canBeReferencedInPrompt": true, + "inputSchema": { + "type": "object", + "properties": { + "resourcePath": { + "type": "string", + "description": "The path to the Python file or workspace for which a Python Environment needs to be configured." + } + }, + "required": [] + } + }, + { + "name": "create_virtual_environment", + "displayName": "Create a Virtual Environment", + "modelDescription": "This tool will create a Virual Environment", + "tags": [], + "canBeReferencedInPrompt": false, + "inputSchema": { + "type": "object", + "properties": { + "packageList": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The list of packages to install." + }, + "resourcePath": { + "type": "string", + "description": "The path to the Python file or workspace for which a Python Environment needs to be configured." + } + }, + "required": [] + }, + "when": "false" + }, + { + "name": "selectEnvironment", + "displayName": "Select a Python Environment", + "modelDescription": "This tool will prompt the user to select an existing Python Environment", + "tags": [], + "canBeReferencedInPrompt": false, + "inputSchema": { + "type": "object", + "properties": { + "resourcePath": { + "type": "string", + "description": "The path to the Python file or workspace for which a Python Environment needs to be configured." + } + }, + "required": [] + }, + "when": "false" } ] }, + "copilot": { + "tests": { + "getSetupConfirmation": "python.copilotSetupTests" + } + }, "scripts": { "package": "gulp clean && gulp prePublishBundle && vsce package -o ms-python-insiders.vsix", + "prePublish": "gulp clean && gulp prePublishNonBundle", "compile": "tsc -watch -p ./", + "compileApi": "node ./node_modules/typescript/lib/tsc.js -b ./pythonExtensionApi/tsconfig.json", "compiled": "deemon npm run compile", "kill-compiled": "deemon --kill npm run compile", - "compile-webviews-watch": "gulp compile-ipywidgets && gulp compile-renderers && cross-env NODE_OPTIONS=--max_old_space_size=9096 webpack --config ./build/webpack/webpack.datascience-ui.config.js --watch", - "compile-webviews-watchd": "deemon npm run compile-webviews-watch", - "kill-compile-webviews-watchd": "deemon --kill npm run compile-webviews-watch", - "build-ipywidgets": "npm run build-ipywidgets-clean && npm run build-ipywidgets-compile && npm run build-ipywidgets-webpack", - "build-ipywidgets-clean": "node ./src/ipywidgets/scripts/clean.js", - "build-ipywidgets-compile": "tsc -p ./src/ipywidgets && rimraf ./out/tsconfig.tsbuildinfo && node ./src/ipywidgets/scripts/copyfiles.js", - "build-ipywidgets-webpack": "cross-env NODE_OPTIONS=--max_old_space_size=9096 webpack --config ./src/ipywidgets/webpack.config.js", "checkDependencies": "gulp checkDependencies", - "postinstall": "node ./build/ci/postInstall.js", "test": "node ./out/test/standardTest.js && node ./out/test/multiRootTest.js", - "test:unittests": "mocha --config ./build/.mocha.unittests.js.json", - "test:unittests:cover": "nyc --no-clean --nycrc-path build/.nycrc mocha --config ./build/.mocha.unittests.ts.json", + "test:unittests": "mocha --config ./build/.mocha.unittests.json", + "test:unittests:cover": "nyc --no-clean --nycrc-path ./build/.nycrc mocha --config ./build/.mocha.unittests.json", "test:functional": "mocha --require source-map-support/register --config ./build/.mocha.functional.json", "test:functional:perf": "node --inspect-brk ./node_modules/mocha/bin/_mocha --require source-map-support/register --config ./build/.mocha.functional.perf.json", "test:functional:memleak": "node --inspect-brk ./node_modules/mocha/bin/_mocha --require source-map-support/register --config ./build/.mocha.functional.json", - "test:functional:cover": "npm run test:functional", - "test:functional:parallel": "node ./build/ci/scripts/runFunctionalTests.js", - "test:cover:report": "nyc --nycrc-path build/.nycrc report --reporter=text --reporter=html --reporter=text-summary --reporter=cobertura", + "test:functional:cover": "nyc --no-clean --nycrc-path ./build/.nycrc mocha --require source-map-support/register --config ./build/.mocha.functional.json", + "test:cover:report": "nyc --nycrc-path ./build/.nycrc report --reporter=text --reporter=html --reporter=text-summary --reporter=cobertura", "testDebugger": "node ./out/test/testBootstrap.js ./out/test/debuggerTest.js", + "testDebugger:cover": "nyc --no-clean --use-spawn-wrap --nycrc-path ./build/.nycrc --require source-map-support/register node ./out/test/debuggerTest.js", "testSingleWorkspace": "node ./out/test/testBootstrap.js ./out/test/standardTest.js", + "testSingleWorkspace:cover": "nyc --no-clean --use-spawn-wrap --nycrc-path ./build/.nycrc --require source-map-support/register node ./out/test/standardTest.js", "preTestJediLSP": "node ./out/test/languageServers/jedi/lspSetup.js", "testJediLSP": "node ./out/test/languageServers/jedi/lspSetup.js && cross-env CODE_TESTS_WORKSPACE=src/test VSC_PYTHON_CI_TEST_GREP='Language Server:' node ./out/test/testBootstrap.js ./out/test/standardTest.js && node ./out/test/languageServers/jedi/lspTeardown.js", - "pretestDataScience": "node ./out/test/datascience/dsTestSetup.js", - "testDataScience": "cross-env CODE_TESTS_WORKSPACE=src/test/datascience VSC_PYTHON_CI_TEST_VSC_CHANNEL=insiders TEST_FILES_SUFFIX=native.vscode.test VSC_PYTHON_FORCE_LOGGING=1 VSC_PYTHON_LOAD_EXPERIMENTS_FROM_FILE=true node ./out/test/testBootstrap.js ./out/test/standardTest.js", - "pretestDataScienceInVSCode": "node ./out/test/datascience/dsTestSetup.js", - "testDataScienceInVSCode": "cross-env CODE_TESTS_WORKSPACE=src/test/datascience VSC_PYTHON_CI_TEST_VSC_CHANNEL=stable TEST_FILES_SUFFIX=vscode.test VSC_PYTHON_FORCE_LOGGING=1 VSC_PYTHON_LOAD_EXPERIMENTS_FROM_FILE=true node ./out/test/testBootstrap.js ./out/test/standardTest.js", "testMultiWorkspace": "node ./out/test/testBootstrap.js ./out/test/multiRootTest.js", "testPerformance": "node ./out/test/testBootstrap.js ./out/test/performanceTest.js", - "testSmoke": "node ./out/test/smokeTest.js", + "testSmoke": "cross-env INSTALL_JUPYTER_EXTENSION=true \"node ./out/test/smokeTest.js\"", + "testInsiders": "cross-env VSC_PYTHON_CI_TEST_VSC_CHANNEL=insiders INSTALL_PYLANCE_EXTENSION=true TEST_FILES_SUFFIX=insiders.test CODE_TESTS_WORKSPACE=src/testMultiRootWkspc/smokeTests \"node ./out/test/standardTest.js\"", "lint-staged": "node gulpfile.js", - "lint": "tslint src/**/*.ts -t verbose", - "prettier-fix": "prettier 'src/**/*.ts*' --write && prettier 'build/**/*.js' --write", + "lint": "eslint src build pythonExtensionApi", + "lint-fix": "eslint --fix src build pythonExtensionApi gulpfile.js", + "format-check": "prettier --check 'src/**/*.ts' 'build/**/*.js' '.github/**/*.yml' gulpfile.js", + "format-fix": "prettier --write 'src/**/*.ts' 'build/**/*.js' '.github/**/*.yml' gulpfile.js", + "check-python": "npm run check-python:ruff && npm run check-python:pyright", + "check-python:ruff": "cd python_files && python -m pip install -U ruff && python -m ruff check . && python -m ruff format --check", + "check-python:pyright": "cd python_files && npx --yes pyright@1.1.308 .", "clean": "gulp clean", + "addExtensionPackDependencies": "gulp addExtensionPackDependencies", "updateBuildNumber": "gulp updateBuildNumber", "verifyBundle": "gulp verifyBundle", "webpack": "webpack" }, "dependencies": { - "@jupyter-widgets/schema": "^0.4.0", - "@jupyterlab/coreutils": "^3.1.0", - "@jupyterlab/services": "^4.2.0", - "@loadable/component": "^5.12.0", - "@nteract/messaging": "^7.0.0", - "@types/tcp-port-used": "^1.0.0", - "ansi-regex": "^4.1.0", + "@iarna/toml": "^3.0.0", + "@vscode/extension-telemetry": "^0.8.4", "arch": "^2.1.0", - "azure-storage": "^2.10.3", - "detect-indent": "^6.0.0", - "diff-match-patch": "^1.0.0", - "fast-deep-equal": "^2.0.1", - "font-awesome": "^4.7.0", - "fs-extra": "^4.0.3", - "fuzzy": "^0.1.3", - "get-port": "^3.2.0", - "glob": "^7.1.2", - "hash.js": "^1.1.7", - "iconv-lite": "^0.4.21", - "inversify": "^4.11.1", - "is-online": "^8.2.1", - "jsonc-parser": "^2.0.3", - "line-by-line": "^0.1.6", - "lodash": "^4.17.19", - "log4js": "^6.1.2", - "md5": "^2.2.1", - "minimatch": "^3.0.4", + "fs-extra": "^11.2.0", + "glob": "^7.2.0", + "iconv-lite": "^0.6.3", + "inversify": "^6.0.2", + "jsonc-parser": "^3.0.0", + "lodash": "^4.18.1", + "minimatch": "^5.1.8", "named-js-regexp": "^1.3.3", - "node-fetch": "^2.6.1", "node-stream-zip": "^1.6.0", - "onigasm": "^2.2.2", - "pdfkit": "^0.11.0", - "pidusage": "^1.2.0", - "portfinder": "^1.0.25", - "react-draggable": "^4.4.2", - "reflect-metadata": "^0.1.12", - "request": "^2.87.0", - "request-progress": "^3.0.0", + "reflect-metadata": "^0.2.2", "rxjs": "^6.5.4", "rxjs-compat": "^6.5.4", - "sanitize-filename": "^1.6.3", - "semver": "^5.5.0", + "semver": "^7.5.2", "stack-trace": "0.0.10", - "string-argv": "^0.3.1", - "strip-ansi": "^5.2.0", - "sudo-prompt": "^8.2.0", - "svg-to-pdfkit": "^0.1.8", - "tcp-port-used": "^1.0.1", - "tmp": "^0.0.29", - "tree-kill": "^1.2.2", - "typescript-char": "^0.0.0", - "uint64be": "^1.0.1", - "unicode": "^10.0.0", - "untildify": "^3.0.2", - "vscode-debugadapter": "^1.28.0", + "sudo-prompt": "^9.2.1", + "tmp": "^0.2.5", + "uint64be": "^3.0.0", + "unicode": "^14.0.0", "vscode-debugprotocol": "^1.28.0", - "vscode-extension-telemetry": "0.1.4", - "vscode-jsonrpc": "6.0.0-next.5", - "vscode-languageclient": "7.0.0-next.9", - "vscode-languageserver": "7.0.0-next.7", - "vscode-languageserver-protocol": "3.16.0-next.7", - "vscode-tas-client": "^0.1.4", - "vsls": "^0.3.1291", + "vscode-jsonrpc": "^9.0.0-next.5", + "vscode-languageclient": "^10.0.0-next.12", + "vscode-languageserver-protocol": "^3.17.6-next.10", + "vscode-tas-client": "^0.1.84", + "which": "^2.0.2", "winreg": "^1.2.4", - "winston": "^3.2.1", - "ws": "^6.0.0", - "xml2js": "^0.4.19", - "zeromq": "^6.0.0-beta.6" + "xml2js": "^0.5.0" }, "devDependencies": { - "@babel/cli": "^7.8.4", - "@babel/core": "^7.4.4", - "@babel/plugin-transform-runtime": "^7.4.4", - "@babel/polyfill": "^7.4.4", - "@babel/preset-env": "^7.1.0", - "@babel/preset-react": "^7.0.0", - "@babel/register": "^7.9.0", - "@blueprintjs/select": "^3.11.2", - "@enonic/fnv-plus": "^1.3.0", - "@istanbuljs/nyc-config-typescript": "^0.1.3", - "@jupyter-widgets/base": "^2.0.1", - "@jupyter-widgets/controls": "^1.5.2", - "@jupyter-widgets/jupyterlab-manager": "^1.0.2", - "@jupyter-widgets/output": "^2.0.1", - "@nteract/transform-dataresource": "^4.3.5", - "@nteract/transform-geojson": "^3.2.3", - "@nteract/transform-model-debug": "^3.2.3", - "@nteract/transform-plotly": "^6.0.0", - "@nteract/transform-vega": "^6.0.3", - "@nteract/transforms": "^4.4.7", - "@phosphor/widgets": "^1.9.3", - "@sinonjs/fake-timers": "^6.0.1", - "@testing-library/react": "^9.4.0", - "@types/ansi-regex": "^4.0.0", + "@istanbuljs/nyc-config-typescript": "^1.0.2", + "@types/bent": "^7.3.0", "@types/chai": "^4.1.2", - "@types/chai-arrays": "^1.0.2", + "@types/chai-arrays": "^2.0.0", "@types/chai-as-promised": "^7.1.0", - "@types/copy-webpack-plugin": "^4.4.2", - "@types/cors": "^2.8.6", - "@types/debug": "^4.1.5", - "@types/dedent": "^0.7.0", - "@types/del": "^3.0.0", - "@types/diff-match-patch": "^1.0.32", - "@types/download": "^6.2.2", - "@types/enzyme": "^3.1.14", - "@types/enzyme-adapter-react-16": "^1.0.3", - "@types/event-stream": "^3.3.33", - "@types/fs-extra": "^5.0.1", - "@types/get-port": "^3.2.0", - "@types/glob": "^5.0.35", - "@types/html-webpack-plugin": "^3.2.0", - "@types/iconv-lite": "^0.0.1", - "@types/jsdom": "^11.12.0", - "@types/loadable__component": "^5.10.0", - "@types/loader-utils": "^1.1.3", + "@types/download": "^8.0.1", + "@types/fs-extra": "^11.0.4", + "@types/glob": "^7.2.0", "@types/lodash": "^4.14.104", - "@types/md5": "^2.1.32", - "@types/memoize-one": "^4.1.1", - "@types/mocha": "^5.2.7", - "@types/nock": "^10.0.3", - "@types/node": "^10.14.18", - "@types/node-fetch": "^2.5.7", - "@types/pdfkit": "^0.7.36", - "@types/promisify-node": "^0.4.0", - "@types/react": "^16.4.14", - "@types/react-dom": "^16.0.8", - "@types/react-json-tree": "^0.6.8", - "@types/react-redux": "^7.1.5", - "@types/react-virtualized": "^9.21.2", - "@types/redux-logger": "^3.0.7", - "@types/request": "^2.47.0", + "@types/mocha": "^9.1.0", + "@types/node": "^22.19.1", "@types/semver": "^5.5.0", "@types/shortid": "^0.0.29", - "@types/sinon": "^7.5.1", - "@types/sinonjs__fake-timers": "^6.0.1", - "@types/socket.io": "^2.1.4", + "@types/sinon": "^17.0.3", "@types/stack-trace": "0.0.29", - "@types/temp": "^0.8.32", - "@types/tmp": "0.0.33", - "@types/untildify": "^3.0.0", - "@types/uuid": "^3.4.3", - "@types/vscode": "^1.47.0", - "@types/vscode-notebook-renderer": "^1.48.0", - "@types/webpack-bundle-analyzer": "^2.13.0", + "@types/tmp": "^0.0.33", + "@types/vscode": "^1.95.0", + "@types/which": "^2.0.1", "@types/winreg": "^1.2.30", - "@types/ws": "^6.0.1", "@types/xml2js": "^0.4.2", - "@typescript-eslint/eslint-plugin": "^3.7.0", - "@typescript-eslint/parser": "^3.7.0", - "acorn": "^6.4.1", - "ansi-to-html": "^0.6.7", - "babel-loader": "^8.0.3", - "babel-plugin-inline-json-import": "^0.3.1", - "babel-plugin-transform-runtime": "^6.23.0", - "babel-polyfill": "^6.26.0", - "bootstrap": "^4.3.1", - "bootstrap-less": "^3.3.8", - "brfs": "^2.0.2", - "cache-loader": "^4.1.0", - "canvas": "^2.6.0", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", + "@vscode/test-electron": "^2.3.8", + "@vscode/vsce": "^2.27.0", + "bent": "^7.3.12", "chai": "^4.1.2", "chai-arrays": "^2.0.0", "chai-as-promised": "^7.1.1", - "chai-http": "^4.3.0", - "codecov": "^3.7.1", - "colors": "^1.2.1", - "copy-webpack-plugin": "^5.1.1", - "cors": "^2.8.5", - "cross-env": "^6.0.3", + "copy-webpack-plugin": "^9.1.0", + "cross-env": "^7.0.3", "cross-spawn": "^6.0.5", - "css-loader": "^1.0.1", - "dedent": "^0.7.0", - "deemon": "^1.4.0", - "del": "^3.0.0", - "download": "^7.0.0", - "enzyme": "^3.7.0", - "enzyme-adapter-react-16": "^1.6.0", - "eslint": "^7.2.0", - "eslint-config-airbnb": "^18.2.0", - "eslint-config-prettier": "^6.9.0", - "eslint-plugin-import": "^2.22.0", + "del": "^6.0.0", + "download": "^8.0.0", + "eslint": "^8.57.1", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.3.1", - "eslint-plugin-prettier": "^3.1.2", + "eslint-plugin-no-only-tests": "^3.3.0", "eslint-plugin-react": "^7.20.3", "eslint-plugin-react-hooks": "^4.0.0", - "event-stream": "3.3.4", - "expose-loader": "^0.7.5", - "express": "^4.17.1", - "extract-zip": "^1.6.7", - "fast-xml-parser": "^3.16.0", - "file-loader": "^5.1.0", - "filemanager-webpack-plugin-fixed": "^2.0.9", - "flat": "^4.0.0", - "fork-ts-checker-webpack-plugin": "^4.1.6", - "gulp": "^4.0.0", - "gulp-azure-storage": "^0.11.1", - "gulp-chmod": "^2.0.0", - "gulp-filter": "^5.1.0", - "gulp-gunzip": "^1.1.0", - "gulp-rename": "^1.4.0", - "gulp-sourcemaps": "^2.6.4", - "gulp-typescript": "^4.0.1", - "gulp-untar": "0.0.8", - "gulp-vinyl-zip": "^2.1.2", - "html-webpack-plugin": "^3.2.0", - "husky": "^1.1.2", - "immutable": "^4.0.0-rc.12", - "jsdom": "^15.0.0", - "json-loader": "^0.5.7", - "less": "^3.9.0", - "less-loader": "^5.0.0", - "less-plugin-inline-urls": "^1.2.0", - "loader-utils": "^1.1.0", - "lolex": "^5.1.2", - "memoize-one": "^5.1.1", - "mocha": "^8.1.1", - "mocha-junit-reporter": "^1.17.0", + "expose-loader": "^3.1.0", + "flat": "^5.0.2", + "get-port": "^5.1.1", + "gulp": "^5.0.0", + "gulp-typescript": "^5.0.0", + "mocha": "^11.1.0", + "mocha-junit-reporter": "^2.0.2", "mocha-multi-reporters": "^1.1.7", - "monaco-editor": "0.18.1", - "monaco-editor-textmate": "^2.2.1", - "monaco-editor-webpack-plugin": "^1.7.0", - "monaco-textmate": "^3.0.1", - "nocache": "^2.1.0", - "nock": "^10.0.6", "node-has-native-dependencies": "^1.0.2", - "node-html-parser": "^1.1.13", + "node-loader": "^1.0.2", + "node-polyfill-webpack-plugin": "^1.1.4", "nyc": "^15.0.0", - "playwright-chromium": "^0.13.0", - "plotly.js-dist": "^1.56.0", - "postcss": "^7.0.27", - "postcss-cssnext": "^3.1.0", - "postcss-import": "^12.0.1", - "postcss-loader": "^3.0.0", "prettier": "^2.0.2", - "range-inclusive": "^1.0.2", - "raw-loader": "^0.5.1", - "react": "^16.5.2", - "react-data-grid": "^6.0.2-0", - "react-dev-utils": "^5.0.2", - "react-dom": "^16.5.2", - "react-json-tree": "^0.11.0", - "react-redux": "^7.1.1", - "react-svg-pan-zoom": "^3.1.0", - "react-svgmt": "^1.1.8", - "react-virtualized": "^9.21.1", - "redux": "^4.0.4", - "redux-logger": "^3.0.6", - "relative": "^3.0.2", - "remove-files-webpack-plugin": "^1.4.0", - "requirejs": "^2.3.6", "rewiremock": "^3.13.0", - "rimraf": "^3.0.2", - "sass-loader": "^7.1.0", - "serialize-javascript": "^3.1.0", "shortid": "^2.2.8", - "sinon": "^8.0.1", - "slickgrid": "^2.4.17", - "socket.io": "^2.3.0", + "sinon": "^18.0.0", "source-map-support": "^0.5.12", - "style-loader": "^0.23.1", - "styled-jsx": "^3.1.0", - "svg-inline-loader": "^0.8.0", - "svg-inline-react": "^3.1.0", - "terser-webpack-plugin": "^3.1.0", - "thread-loader": "^2.1.3", - "transform-loader": "^0.2.4", - "ts-loader": "^5.3.0", - "ts-mock-imports": "^1.3.0", + "ts-loader": "^9.2.8", "ts-mockito": "^2.5.0", - "ts-node": "^8.3.0", + "ts-node": "^10.7.0", "tsconfig-paths-webpack-plugin": "^3.2.0", - "tslint": "^5.20.1", - "tslint-config-prettier": "^1.18.0", - "tslint-eslint-rules": "^5.1.0", - "tslint-microsoft-contrib": "^5.0.3", - "tslint-plugin-prettier": "^2.1.0", - "typed-react-markdown": "^0.1.0", "typemoq": "^2.1.0", - "typescript": "^4.0.2", - "typescript-formatter": "^7.1.0", - "unicode-properties": "^1.3.1", - "url-loader": "^1.1.2", - "uuid": "^3.3.2", - "vinyl-fs": "^3.0.3", - "vsce": "^1.59.0", - "vscode-debugadapter-testsupport": "^1.27.0", - "vscode-test": "^1.2.3", - "webpack": "^4.33.0", - "webpack-bundle-analyzer": "^3.6.0", - "webpack-cli": "^3.1.2", + "typescript": "~5.2", + "uuid": "^8.3.2", + "webpack": "^5.105.0", + "webpack-bundle-analyzer": "^4.5.0", + "webpack-cli": "^4.9.2", "webpack-fix-default-import-plugin": "^1.0.3", - "webpack-merge": "^4.1.4", - "webpack-node-externals": "^1.7.2", - "webpack-require-from": "^1.8.0", - "why-is-node-running": "^2.0.3", - "wtfnode": "^0.8.0", + "webpack-merge": "^5.8.0", + "webpack-node-externals": "^3.0.0", + "webpack-require-from": "^1.8.6", + "worker-loader": "^3.0.8", "yargs": "^15.3.1" - }, - "__metadata": { - "id": "f1f59ae4-9318-4f3c-a9b5-81b2eaa5f8a5", - "publisherDisplayName": "Microsoft", - "publisherId": "998b010b-e2af-44a5-a6cd-0b5fd3b9b6f8" } } diff --git a/package.nls.de.json b/package.nls.de.json deleted file mode 100644 index 77ec14b3ee9c..000000000000 --- a/package.nls.de.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "python.command.python.sortImports.title": "Sortieren der Importe", - "python.command.python.startREPL.title": "Starten des REPL", - "python.command.python.createTerminal.title": "Terminal erstellen", - "python.command.python.buildWorkspaceSymbols.title": "Arbeitsplatz-Symbole erstellen", - "python.command.python.runtests.title": "Alle Unittests ausführen", - "python.command.python.debugtests.title": "Alle Unittests debuggen", - "python.command.python.execInTerminal.title": "Python-Datei im Terminal ausführen", - "python.command.python.setInterpreter.title": "Interpreter auswählen", - "python.command.python.refactorExtractVariable.title": "Variable extrahieren", - "python.command.python.refactorExtractMethod.title": "Methode extrahieren", - "python.command.python.viewTestOutput.title": "Unittest-Ausgabe anzeigen", - "python.command.python.selectAndRunTestMethod.title": "Unittest-Methode ausführen ...", - "python.command.python.selectAndDebugTestMethod.title": "Unittest-Debug-Methode ausführen ...", - "python.command.python.selectAndRunTestFile.title": "Unittest-Datei ausführen ...", - "python.command.python.runCurrentTestFile.title": "Ausgewählte Unittest-Datei ausführen", - "python.command.python.runFailedTests.title": "Fehlerhafte Unittests ausführen", - "python.command.python.discoverTests.title": "Unittests durchsuchen", - "python.command.python.execSelectionInTerminal.title": "Selektion/Reihe in Python-Terminal ausführen", - "python.command.python.execSelectionInDjangoShell.title": "Selektion/Reihe in Django-Shell ausführen", - "python.command.python.goToPythonObject.title": "Gehe zu Python-Objekt", - "python.command.python.setLinter.title": "Linter auswählen", - "python.command.python.enableLinting.title": "Linting aktivieren", - "python.command.python.runLinting.title": "Linting ausführen", - "python.snippet.launch.standard.label": "Python: Aktuelle Datei", - "python.snippet.launch.module.label": "Python: Modul", - "python.snippet.launch.django.label": "Python: Django", - "python.snippet.launch.flask.label": "Python: Flask", - "python.snippet.launch.pyramid.label": "Python: Pyramid-Anwendung", - "python.snippet.launch.attach.label": "Python: Anfügen" -} diff --git a/package.nls.es.json b/package.nls.es.json deleted file mode 100644 index 227e268dbc8d..000000000000 --- a/package.nls.es.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "python.command.python.sortImports.title": "Ordenar importaciones", - "python.command.python.startREPL.title": "Nuevo REPL", - "python.command.python.createTerminal.title": "Nueva terminal", - "python.command.python.buildWorkspaceSymbols.title": "Compilar símbolos del área de trabajo", - "python.command.python.runtests.title": "Ejecutar todas las pruebas unitarias", - "python.command.python.debugtests.title": "Depurar todas las pruebas unitarias", - "python.command.python.execInTerminal.title": "Ejecutar archivo Python en la terminal", - "python.command.python.setInterpreter.title": "Seleccionar intérprete", - "python.command.python.refactorExtractVariable.title": "Extraer variable", - "python.command.python.refactorExtractMethod.title": "Extraer método", - "python.command.python.viewTestOutput.title": "Mostrar resultados de la prueba unitaria", - "python.command.python.selectAndRunTestMethod.title": "Método de ejecución de pruebas unitarias ...", - "python.command.python.selectAndDebugTestMethod.title": "Método de depuración de pruebas unitarias ...", - "python.command.python.selectAndRunTestFile.title": "Ejecutar archivo de prueba unitaria ...", - "python.command.python.runCurrentTestFile.title": "Ejecutar archivo de prueba unitaria actual", - "python.command.python.runFailedTests.title": "Ejecutar pruebas unitarias fallidas", - "python.command.python.discoverTests.title": "Encontrar pruebas unitarias", - "python.command.python.execSelectionInTerminal.title": "Ejecutar línea/selección en la terminal", - "python.command.python.execSelectionInDjangoShell.title": "Ejecutar línea/selección en el intérprete de Django", - "python.command.python.goToPythonObject.title": "Ir al objeto de Python", - "python.command.python.setLinter.title": "Seleccionar Linter", - "python.command.python.enableLinting.title": "Habilitar Linting", - "python.command.python.runLinting.title": "Ejecutar Linting", - "python.snippet.launch.standard.label": "Python: Archivo actual", - "python.snippet.launch.module.label": "Python: Módulo", - "python.snippet.launch.django.label": "Python: Django", - "python.snippet.launch.flask.label": "Python: Flask", - "python.snippet.launch.pyramid.label": "Python: Pyramid", - "python.snippet.launch.attach.label": "Python: Adjuntar" -} diff --git a/package.nls.fr.json b/package.nls.fr.json deleted file mode 100644 index ad28a94d6e09..000000000000 --- a/package.nls.fr.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "python.command.python.sortImports.title": "Trier les imports", - "python.command.python.startREPL.title": "Démarrer la console interactive", - "python.command.python.createTerminal.title": "Créer un terminal", - "python.command.python.buildWorkspaceSymbols.title": "Construire les symboles de l'espace de travail", - "python.command.python.runtests.title": "Exécuter tous les tests unitaires", - "python.command.python.debugtests.title": "Déboguer tous les tests unitaires", - "python.command.python.execInTerminal.title": "Exécuter le script Python dans un terminal", - "python.command.python.setInterpreter.title": "Sélectionner l'interpreteur", - "python.command.python.refactorExtractVariable.title": "Extraire la variable", - "python.command.python.refactorExtractMethod.title": "Extraire la méthode", - "python.command.python.viewTestOutput.title": "Afficher la sortie des tests unitaires", - "python.command.python.selectAndRunTestMethod.title": "Exécuter la méthode de test unitaire ...", - "python.command.python.selectAndDebugTestMethod.title": "Déboguer la méthode de test unitaire ...", - "python.command.python.selectAndRunTestFile.title": "Exécuter le fichier de test unitaire ...", - "python.command.python.runCurrentTestFile.title": "Exécuter le fichier de test unitaire courant", - "python.command.python.runFailedTests.title": "Exécuter les derniers test unitaires échoués", - "python.command.python.execSelectionInTerminal.title": "Exécuter la ligne/sélection dans un terminal Python", - "python.command.python.execSelectionInDjangoShell.title": "Exécuter la ligne/sélection dans un shell Django", - "python.command.python.goToPythonObject.title": "Se rendre à l'objet Python", - "python.command.python.setLinter.title": "Sélectionner le linter", - "python.command.python.enableLinting.title": "Activer le linting", - "python.command.python.runLinting.title": "Exécuter le linting", - "python.snippet.launch.standard.label": "Python : Fichier actuel", - "python.snippet.launch.module.label": "Python: Module", - "python.snippet.launch.django.label": "Python : Django", - "python.snippet.launch.flask.label": "Python : Flask", - "python.snippet.launch.pyramid.label": "Python : application Pyramid", - "python.snippet.launch.attach.label": "Python: Attacher" -} diff --git a/package.nls.it.json b/package.nls.it.json deleted file mode 100644 index c16d6ce74241..000000000000 --- a/package.nls.it.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "python.command.python.sortImports.title": "Ordina gli import", - "python.command.python.startREPL.title": "Apri nuova REPL", - "python.command.python.createTerminal.title": "Apri nuovo terminale", - "python.command.python.buildWorkspaceSymbols.title": "Compila simboli dello spazio di lavoro", - "python.command.python.runtests.title": "Esegui tutti i test", - "python.command.python.debugtests.title": "Esegui debug di tutti i test", - "python.command.python.execInTerminal.title": "Esegui file Python nel terminale", - "python.command.python.setInterpreter.title": "Seleziona interprete", - "python.command.python.refactorExtractVariable.title": "Estrai variable", - "python.command.python.refactorExtractMethod.title": "Estrai metodo", - "python.command.python.viewTestOutput.title": "Mostra output dei test", - "python.command.python.selectAndRunTestMethod.title": "Esegui metodo di test ...", - "python.command.python.selectAndDebugTestMethod.title": "Esegui debug del metodo di test ...", - "python.command.python.selectAndRunTestFile.title": "Esegui file di test ...", - "python.command.python.runCurrentTestFile.title": "Esegui file di test attuale", - "python.command.python.runFailedTests.title": "Esegui test falliti", - "python.command.python.execSelectionInTerminal.title": "Esegui selezione/linea nel terminale di Python", - "python.command.python.execSelectionInDjangoShell.title": "Esegui selezione/linea nella shell Django", - "python.command.python.goToPythonObject.title": "Vai a oggetto Python", - "python.command.python.setLinter.title": "Selezione Linter", - "python.command.python.enableLinting.title": "Attiva Linting", - "python.command.python.runLinting.title": "Esegui Linting", - "python.snippet.launch.standard.label": "Python: File corrente", - "python.snippet.launch.module.label": "Python: Modulo", - "python.snippet.launch.django.label": "Python: Django", - "python.snippet.launch.flask.label": "Python: Flask", - "python.snippet.launch.pyramid.label": "Python: Applicazione Pyramid", - "python.snippet.launch.attach.label": "Python: Allega", - "ExtensionSurveyBanner.bannerLabelYes": "Sì, prenderò il sondaggio ora" -} diff --git a/package.nls.ja.json b/package.nls.ja.json deleted file mode 100644 index a2db0a823636..000000000000 --- a/package.nls.ja.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "python.command.python.sortImports.title": "import 文を並び替える", - "python.command.python.startREPL.title": "REPL を開始", - "python.command.python.buildWorkspaceSymbols.title": "ワークスペースのシンボルをビルド", - "python.command.python.runtests.title": "すべての単体テストを実行", - "python.command.python.debugtests.title": "すべての単体テストをデバッグ", - "python.command.python.execInTerminal.title": "ターミナルで Python ファイルを実行", - "python.command.python.setInterpreter.title": "インタープリターを選択", - "python.command.python.refactorExtractVariable.title": "変数を抽出", - "python.command.python.refactorExtractMethod.title": "メソッドを抽出", - "python.command.python.viewTestOutput.title": "単体テストの出力を表示", - "python.command.python.selectAndRunTestMethod.title": "単体テストメソッドを実行...", - "python.command.python.selectAndDebugTestMethod.title": "単体テストメソッドをデバッグ...", - "python.command.python.selectAndRunTestFile.title": "単体テストファイルを実行...", - "python.command.python.runCurrentTestFile.title": "現在の単体テストファイルを実行", - "python.command.python.runFailedTests.title": "失敗した単体テストを実行", - "python.command.python.execSelectionInTerminal.title": "Python ターミナルで選択範囲/行を実行", - "python.command.python.execSelectionInDjangoShell.title": "Django シェルで選択範囲/行を実行", - "python.command.python.goToPythonObject.title": "Python オブジェクトに移動", - "python.snippet.launch.standard.label": "Python: Current File", - "python.snippet.launch.module.label": "Python: モジュール", - "python.snippet.launch.django.label": "Python: Django", - "python.snippet.launch.flask.label": "Python: Flask", - "python.snippet.launch.pyramid.label": "Python: Pyramid アプリケーション", - "python.snippet.launch.attach.label": "Python: アタッチ" -} diff --git a/package.nls.json b/package.nls.json index d916bd2600e3..57f2ed95b2c0 100644 --- a/package.nls.json +++ b/package.nls.json @@ -1,602 +1,177 @@ { - "python.command.python.sortImports.title": "Sort Imports", - "python.command.python.startREPL.title": "Start REPL", + "python.command.python.startTerminalREPL.title": "Start Terminal REPL", + "python.languageModelTools.get_python_environment_details.userDescription": "Get information for a Python Environment, such as Type, Version, Packages, and more.", + "python.languageModelTools.install_python_packages.userDescription": "Installs Python packages in a Python Environment.", + "python.languageModelTools.get_python_executable_details.userDescription": "Get executable info for a Python Environment", + "python.languageModelTools.configure_python_environment.userDescription": "Configure a Python Environment for a workspace", + "python.command.python.startNativeREPL.title": "Start Native Python REPL", + "python.command.python.createEnvironment.title": "Create Environment...", + "python.command.python.createNewFile.title": "New Python File", "python.command.python.createTerminal.title": "Create Terminal", - "python.command.python.buildWorkspaceSymbols.title": "Build Workspace Symbols", - "python.command.python.runtests.title": "Run All Tests", - "python.command.python.debugtests.title": "Debug All Tests", "python.command.python.execInTerminal.title": "Run Python File in Terminal", + "python.command.python.execInTerminalIcon.title": "Run Python File", + "python.command.python.execInDedicatedTerminal.title": "Run Python File in Dedicated Terminal", "python.command.python.setInterpreter.title": "Select Interpreter", - "python.command.python.switchOffInsidersChannel.title": "Switch to Default Channel", - "python.command.python.switchToDailyChannel.title": "Switch to Insiders Daily Channel", - "python.command.python.switchToWeeklyChannel.title": "Switch to Insiders Weekly Channel", "python.command.python.clearWorkspaceInterpreter.title": "Clear Workspace Interpreter Setting", - "python.command.python.resetInterpreterSecurityStorage.title": "Reset Stored Info for Untrusted Interpreters", - "python.command.python.refactorExtractVariable.title": "Extract Variable", - "python.command.python.refactorExtractMethod.title": "Extract Method", "python.command.python.viewOutput.title": "Show Output", - "python.command.python.viewTestOutput.title": "Show Test Output", - "python.command.python.datascience.viewJupyterOutput.title": "Show Jupyter Output", - "python.command.python.datascience.exportAsPythonScript.title": "Export as Python Script", - "python.command.python.datascience.exportToHTML.title": "Export to HTML", - "python.command.python.datascience.exportToPDF.title": "Export to PDF", - "DataScience.checkingIfImportIsSupported": "Checking if import is supported", - "DataScience.installingMissingDependencies": "Installing missing dependencies", - "DataScience.exportNotebookToPython": "Exporting Notebook to Python", - "DataScience.performingExport": "Performing Export", - "DataScience.convertingToPDF": "Converting to PDF", - "DataScience.exportHTMLQuickPickLabel": "HTML", - "DataScience.exportPDFQuickPickLabel": "PDF", - "DataScience.openExportedFileMessage": "Would you like to open the exported file?", - "DataScience.openExportFileYes": "Yes", - "DataScience.openExportFileNo": "No", - "DataScience.failedExportMessage": "Export failed.", - "DataScience.exportFailedGeneralMessage": "Please check the 'Python' [output](command:python.viewOutput) panel for further details.", - "DataScience.exportToPDFDependencyMessage": "If you have not installed xelatex (TeX) you will need to do so before you can export to PDF, for further instructions go to https://nbconvert.readthedocs.io/en/latest/install.html#installing-tex. \r\nTo avoid installing xelatex (TeX) you might want to try exporting to HTML and using your browsers \"Print to PDF\" feature.", - "DataScience.launchNotebookTrustPrompt": "A notebook could execute harmful code when opened. Some outputs have been hidden. Do you trust this notebook? [Learn more.](https://aka.ms/trusted-notebooks)", - "DataScience.launchNotebookTrustPrompt.yes": "Trust", - "DataScience.launchNotebookTrustPrompt.no": "Do not trust", - "DataScience.launchNotebookTrustPrompt.trustAllNotebooks": "Trust all notebooks", - "DataScience.insecureSessionMessage": "Connecting over HTTP without a token may be an insecure connection. Do you want to connect to a possibly insecure server?", - "DataScience.insecureSessionDenied": "Denied connection to insecure server.", + "python.command.python.installJupyter.title": "Install the Jupyter extension", "python.command.python.viewLanguageServerOutput.title": "Show Language Server Output", - "python.command.python.selectAndRunTestMethod.title": "Run Test Method ...", - "python.command.python.selectAndDebugTestMethod.title": "Debug Test Method ...", - "python.command.python.selectAndRunTestFile.title": "Run Test File ...", - "python.command.python.runCurrentTestFile.title": "Run Current Test File", - "python.command.python.runFailedTests.title": "Run Failed Tests", - "python.command.python.discoverTests.title": "Discover Tests", - "python.command.python.discoveringTests.title": "Discovering...", - "python.command.python.stopTests.title": "Stop", "python.command.python.configureTests.title": "Configure Tests", + "python.command.testing.rerunFailedTests.title": "Rerun Failed Tests", "python.command.python.execSelectionInTerminal.title": "Run Selection/Line in Python Terminal", + "python.command.python.execSelectionInTerminal.shortTitle": "Run Selection/Line", + "python.command.python.execInREPL.title": "Run Selection/Line in Native Python REPL", "python.command.python.execSelectionInDjangoShell.title": "Run Selection/Line in Django Shell", - "python.command.python.goToPythonObject.title": "Go to Python Object", - "python.command.python.setLinter.title": "Select Linter", - "python.command.python.enableLinting.title": "Enable Linting", - "python.command.python.runLinting.title": "Run Linting", - "python.command.python.datascience.runFileInteractive.title": "Run Current File in Python Interactive Window", - "python.command.python.datascience.debugFileInteractive.title": "Debug Current File in Python Interactive Window", - "python.command.python.datascience.runallcells.title": "Run All Cells", - "python.command.python.datascience.notebookeditor.runallcells.title": "Run All Notebook Cells", - "python.command.python.datascience.notebookeditor.expandallcells.title": "Expand All Notebook Cells", - "python.command.python.datascience.notebookeditor.collapseallcells.title": "Collapse All Notebook Cells", - "python.command.python.datascience.runallcellsabove.title": "Run Above", - "python.command.python.datascience.runcellandallbelow.title": "Run Below", - "python.command.python.datascience.runallcellsabove.palette.title": "Run Cells Above Current Cell", - "python.command.python.datascience.runcurrentcellandallbelow.palette.title": "Run Current Cell and Below", - "python.command.python.datascience.debugcurrentcell.palette.title": "Debug Current Cell", - "python.command.python.datascience.debugcell.title": "Debug Cell", - "python.command.python.datascience.debugstepover.title": "Step Over", - "python.command.python.datascience.debugcontinue.title": "Continue", - "python.command.python.datascience.debugstop.title": "Stop", - "python.command.python.datascience.runtoline.title": "Run To Line in Python Interactive Window", - "python.command.python.datascience.runfromline.title": "Run From Line in Python Interactive Window", - "python.command.python.datascience.runcurrentcell.title": "Run Current Cell", - "python.command.python.datascience.runcurrentcelladvance.title": "Run Current Cell And Advance", - "python.command.python.datascience.execSelectionInteractive.title": "Run Selection/Line in Python Interactive Window", - "python.command.python.datascience.runcell.title": "Run Cell", - "python.command.python.datascience.insertCellBelowPosition.title": "Insert Cell Below Position", - "python.command.python.datascience.insertCellBelow.title": "Insert Cell Below", - "python.command.python.datascience.insertCellAbove.title": "Insert Cell Above", - "python.command.python.datascience.deleteCells.title": "Delete Selected Cells", - "python.command.python.datascience.selectCell.title": "Select Cell", - "python.command.python.datascience.selectCellContents.title": "Select Cell Contents", - "python.command.python.datascience.extendSelectionByCellAbove.title": "Extend Selection By Cell Above", - "python.command.python.datascience.extendSelectionByCellBelow.title": "Extend Selection By Cell Below", - "python.command.python.datascience.moveCellsUp.title": "Move Selected Cells Up", - "python.command.python.datascience.moveCellsDown.title": "Move Selected Cells Down", - "python.command.python.datascience.changeCellToMarkdown.title": "Change Cell to Markdown", - "python.command.python.datascience.changeCellToCode.title": "Change Cell to Code", - "python.command.python.datascience.gotoNextCellInFile.title": "Go to Next Cell", - "python.command.python.datascience.gotoPrevCellInFile.title": "Go to Previous Cell", - "python.command.python.datascience.showhistorypane.title": "Show Python Interactive Window", - "python.command.python.datascience.createnewinteractive.title": "Create Python Interactive Window", - "python.command.python.datascience.selectjupyteruri.title": "Specify local or remote Jupyter server for connections", - "python.command.python.datascience.selectjupytercommandline.title": "Specify Jupyter command line arguments", - "python.command.python.datascience.importnotebook.title": "Import Jupyter Notebook", - "python.command.python.datascience.opennotebook.title": "Open in Notebook Editor", - "python.command.python.datascience.opennotebookInPreviewEditor.title": "Open in preview Notebook Editor", - "python.command.python.datascience.importnotebookfile.title": "Convert to Python Script", - "python.command.python.enableSourceMapSupport.title": "Enable Source Map Support For Extension Debugging", - "python.command.python.datascience.exportoutputasnotebook.title": "Export Python Interactive Window as Jupyter Notebook", - "python.command.python.datascience.exportfileasnotebook.title": "Export Current Python File as Jupyter Notebook", - "python.command.python.datascience.exportfileandoutputasnotebook.title": "Export Current Python File and Output as Jupyter Notebook", - "python.command.python.datascience.undocells.title": "Undo Last Python Interactive Action", - "python.command.python.datascience.redocells.title": "Redo Last Python Interactive Action", - "python.command.python.datascience.removeallcells.title": "Delete All Python Interactive Cells", - "python.command.python.datascience.notebookeditor.removeallcells.title": "Delete All Notebook Editor Cells", - "python.command.python.datascience.notebookeditor.runselectedcell.title": "Run Selected Notebook Cell", - "python.command.python.datascience.notebookeditor.addcellbelow.title": "Add Empty Cell to Notebook File", - "python.command.python.datascience.interruptkernel.title": "Interrupt IPython Kernel", - "python.command.python.datascience.restartkernel.title": "Restart IPython Kernel", - "python.command.python.datascience.expandallcells.title": "Expand All Python Interactive Cells", - "python.command.python.datascience.collapseallcells.title": "Collapse All Python Interactive Cells", - "python.command.python.datascience.addcellbelow.title": "Add Empty Cell to File", - "python.command.python.datascience.scrolltocell.title": "Scroll Cell Into View", - "python.command.python.datascience.createnewnotebook.title": "Create New Blank Jupyter Notebook", - "python.command.python.startPage.open.title": "Open Start Page", - "python.command.python.datascience.selectJupyterInterpreter.title": "Select Interpreter to start Jupyter server", - "Datascience.currentlySelectedJupyterInterpreterForPlaceholder": "current: {0}", - "python.command.python.analysis.clearCache.title": "Clear Module Analysis Cache", + "python.command.python.reportIssue.title": "Report Issue...", + "python.command.python.clearCacheAndReload.title": "Clear Cache and Reload Window", "python.command.python.analysis.restartLanguageServer.title": "Restart Language Server", - "python.snippet.launch.standard.label": "Python: Current File", - "python.snippet.launch.module.label": "Python: Module", - "python.snippet.launch.module.default": "enter-your-module-name", - "python.snippet.launch.attach.label": "Python: Remote Attach", - "python.snippet.launch.attachpid.label": "Python: Attach using Process Id", - "python.snippet.launch.django.label": "Python: Django", - "python.snippet.launch.flask.label": "Python: Flask", - "python.snippet.launch.pyramid.label": "Python: Pyramid Application", - "Pylance.proposePylanceMessage": "Try out a new faster, feature-rich language server for Python by Microsoft, Pylance! Install the extension now.", - "Pylance.tryItNow": "Try it now", - "Pylance.remindMeLater": "Remind me later", - "Pylance.installPylanceMessage": "Pylance extension is not installed. Click Yes to open Pylance installation page.", - "Pylance.pylanceNotInstalledMessage": "Pylance extension is not installed.", - "Pylance.pylanceInstalledReloadPromptMessage": "Pylance extension is now installed. Reload window to activate?", - "DataScience.unknownMimeTypeFormat": "Mime type {0} is not currently supported.", - "DataScience.historyTitle": "Python Interactive", - "DataScience.dataExplorerTitle": "Data Viewer", - "DataScience.badWebPanelFormatString": "

{0} is not a valid file name

", - "DataScience.sessionDisposed": "Cannot execute code, session has been disposed.", - "DataScience.rawKernelProcessNotStarted": "Raw kernel process was not able to start.", - "DataScience.rawKernelProcessExitBeforeConnect": "Raw kernel process exited before connecting.", - "DataScience.passwordFailure": "Failed to connect to password protected server. Check that password is correct.", - "DataScience.exportDialogTitle": "Export to Jupyter Notebook", - "DataScience.exportDialogFilter": "Jupyter Notebooks", - "DataScience.exportDialogComplete": "Notebook written to {0}", - "DataScience.exportDialogFailed": "Failed to export notebook. {0}", - "DataScience.exportOpenQuestion": "Open in browser", - "DataScience.exportOpenQuestion1": "Open in editor", - "DataScience.notebookExportAs": "Export As", - "DataScience.exportAsQuickPickPlaceholder": "Export As...", - "DataScience.exportPythonQuickPickLabel": "Python Script", - "DataScience.collapseInputTooltip": "Collapse input block", - "DataScience.collapseVariableExplorerTooltip": "Hide variables active in jupyter kernel", - "DataScience.collapseVariableExplorerLabel": "Variables", - "DataScience.expandVariableExplorerTooltip": "Show variables active in jupyter kernel", - "DataScience.close": "Close", - "DataScience.variableLoadingValue": "Loading...", - "DataScience.importDialogTitle": "Import Jupyter Notebook", - "DataScience.importDialogFilter": "Jupyter Notebooks", - "DataScience.notebookCheckForImportYes": "Import", - "DataScience.reloadRequired": "Please reload the window for new settings to take effect.", - "DataScience.notebookCheckForImportNo": "Later", - "DataScience.notebookCheckForImportDontAskAgain": "Don't Ask Again", - "DataScience.notebookCheckForImportTitle": "Do you want to import the Jupyter Notebook into Python code?", - "DataScience.jupyterNotSupported": "Jupyter cannot be started. Error attempting to locate jupyter: {0}", - "DataScience.jupyterNotSupportedBecauseOfEnvironment": "Activating {0} to run Jupyter failed with {1}.", - "DataScience.jupyterNbConvertNotSupported": "Importing notebooks requires Jupyter nbconvert to be installed.", - "DataScience.jupyterLaunchNoURL": "Failed to find the URL of the launched Jupyter notebook server", - "DataScience.jupyterLaunchTimedOut": "The Jupyter notebook server failed to launch in time", - "DataScience.jupyterSelfCertFail": "The security certificate used by server {0} was not issued by a trusted certificate authority.\r\nThis may indicate an attempt to steal your information.\r\nDo you want to enable the Allow Unauthorized Remote Connection setting for this workspace to allow you to connect?", - "DataScience.jupyterSelfCertEnable": "Yes, connect anyways", - "DataScience.jupyterSelfCertClose": "No, close the connection", - "DataScience.jupyterServerCrashed": "Jupyter server crashed. Unable to connect. \r\nError code from jupyter: {0}", - "DataScience.rawConnectionDisplayName": "Direct kernel connection", - "DataScience.rawConnectionBrokenError": "Direct kernel connection broken", - "DataScience.pythonInteractiveHelpLink": "Get more help", - "DataScience.markdownHelpInstallingMissingDependencies": "See [https://aka.ms/pyaiinstall](https://aka.ms/pyaiinstall) for help on installing Jupyter and related dependencies.", - "DataScience.importingFormat": "Importing {0}", - "DataScience.startingJupyter": "Starting Jupyter server", - "DataScience.connectingToJupyter": "Connecting to Jupyter server", - "DataScience.connectingToIPyKernel": "Connecting to IPython kernel", - "DataScience.connectedToIPyKernel": "Connected.", - "DataScience.connected": "Connected", - "DataScience.disconnected": "Disconnected", - "Experiments.inGroup": "User belongs to experiment group '{0}'", - "Interpreters.RefreshingInterpreters": "Refreshing Python Interpreters", - "Interpreters.entireWorkspace": "Entire workspace", - "Interpreters.pythonInterpreterPath": "Python interpreter path: {0}", - "Interpreters.LoadingInterpreters": "Loading Python Interpreters", - "Interpreters.unsafeInterpreterMessage": "We found a Python environment in this workspace. Do you want to select it to start up the features in the Python extension? Only accept if you trust this environment.", - "Interpreters.condaInheritEnvMessage": "We noticed you're using a conda environment. If you are experiencing issues with this environment in the integrated terminal, we recommend that you let the Python extension change \"terminal.integrated.inheritEnv\" to false in your user settings.", - "Logging.CurrentWorkingDirectory": "cwd:", - "InterpreterQuickPickList.quickPickListPlaceholder": "Current: {0}", - "InterpreterQuickPickList.enterPath.detail": "Enter path or find an existing interpreter", - "InterpreterQuickPickList.enterPath.label": "Enter interpreter path...", - "InterpreterQuickPickList.enterPath.placeholder": "Enter path to a Python interpreter.", - "InterpreterQuickPickList.browsePath.label": "Find...", - "InterpreterQuickPickList.browsePath.detail": "Browse your file system to find a Python interpreter.", - "InterpreterQuickPickList.browsePath.title": "Select Python interpreter", - "diagnostics.upgradeCodeRunner": "Please update the Code Runner extension for it to be compatible with the Python extension.", - "Common.bannerLabelYes": "Yes", - "Common.bannerLabelNo": "No", - "Common.doNotShowAgain": "Do not show again", - "Common.reload": "Reload", - "Common.moreInfo": "More Info", - "Common.and": "and", - "Common.ok": "Ok", - "Common.install": "Install", - "Common.learnMore": "Learn more", - "Common.reportThisIssue": "Report this issue", - "CommonSurvey.remindMeLaterLabel": "Remind me later", - "CommonSurvey.yesLabel": "Yes, take survey now", - "CommonSurvey.noLabel": "No, thanks", - "OutputChannelNames.languageServer": "Python Language Server", - "OutputChannelNames.python": "Python", - "OutputChannelNames.pythonTest": "Python Test Log", - "OutputChannelNames.jupyter": "Jupyter", - "ExtensionSurveyBanner.bannerMessage": "Can you please take 2 minutes to tell us how the Python extension is working for you?", - "ExtensionSurveyBanner.bannerLabelYes": "Yes, take survey now", - "ExtensionSurveyBanner.bannerLabelNo": "No, thanks", - "ExtensionSurveyBanner.maybeLater": "Maybe later", - "ExtensionChannels.installingInsidersMessage": "Installing Insiders... ", - "ExtensionChannels.installingStableMessage": "Installing Stable... ", - "ExtensionChannels.installationCompleteMessage": "complete.", - "ExtensionChannels.downloadingInsidersMessage": "Downloading Insiders Extension... ", - "ExtensionChannels.yesWeekly": "Yes, weekly", - "ExtensionChannels.yesDaily": "Yes, daily", - "ExtensionChannels.promptMessage": "We noticed you are using Visual Studio Code Insiders. Would you like to use the Insiders build of the Python extension?", - "ExtensionChannels.reloadToUseInsidersMessage": "Please reload Visual Studio Code to use the insiders build of the Python extension.", - "ExtensionChannels.downloadCompletedOutputMessage": "Insiders build download complete.", - "ExtensionChannels.startingDownloadOutputMessage": "Starting download for Insiders build.", - "Interpreters.environmentPromptMessage": "We noticed a new virtual environment has been created. Do you want to select it for the workspace folder?", - "DataScience.reloadAfterChangingJupyterServerConnection": "Please reload VS Code when changing the Jupyter Server connection.", - "DataScience.restartKernelMessage": "Do you want to restart the IPython kernel? All variables will be lost.", - "DataScience.restartKernelMessageYes": "Yes", - "DataScience.restartKernelMessageNo": "No", - "DataScience.restartingKernelFailed": "Kernel restart failed. Jupyter server is hung. Please reload VS Code.", - "DataScience.interruptingKernelFailed": "Kernel interrupt failed. Jupyter server is hung. Please reload VS Code.", - "DataScienceSurveyBanner.bannerMessage": "Can you please take 2 minutes to tell us how the Python Data Science features are working for you?", - "DataScienceSurveyBanner.bannerLabelYes": "Yes, take survey now", - "DataScienceSurveyBanner.bannerLabelNo": "No, thanks", - "InteractiveShiftEnterBanner.bannerMessage": "Would you like to run code in the 'Python Interactive' window (an IPython console) for 'shift-enter'? Select 'No' to continue to run code in the Python Terminal. This can be changed later in settings.", - "DataScience.restartingKernelStatus": "Restarting IPython Kernel", - "DataScience.executingCode": "Executing Cell", - "DataScience.collapseAll": "Collapse all cell inputs", - "DataScience.expandAll": "Expand all cell inputs", - "DataScience.export": "Export as Jupyter notebook", - "DataScience.restartServer": "Restart IPython kernel", - "DataScience.undo": "Undo", - "DataScience.redo": "Redo", - "DataScience.clearAll": "Remove all cells", - "DataScience.pythonVersionHeader": "Python version:", - "DataScience.pythonRestartHeader": "Restarted kernel:", - "DataScience.pythonNewHeader": "Started new kernel:", - "DataScience.pythonConnectHeader": "Connected to kernel:", - "DataScience.executingCodeFailure": "Executing code failed : {0}", - "DataScience.inputWatermark": "Type code here and press shift-enter to run", - "DataScience.deleteButtonTooltip": "Remove cell", - "DataScience.gotoCodeButtonTooltip": "Go to code", - "DataScience.copyBackToSourceButtonTooltip": "Paste code into file", - "DataScience.copyToClipboardButtonTooltip": "Copy source to clipboard", - "DataScience.plotOpen": "Expand image", - "Linter.enableLinter": "Enable {0}", - "Linter.enablePylint": "You have a pylintrc file in your workspace. Do you want to enable pylint?", - "Linter.replaceWithSelectedLinter": "Multiple linters are enabled in settings. Replace with '{0}'?", - "Installer.noCondaOrPipInstaller": "There is no Conda or Pip installer available in the selected environment.", - "Installer.noPipInstaller": "There is no Pip installer available in the selected environment.", - "Installer.searchForHelp": "Search for help", - "DataScience.libraryNotInstalled": "Data Science library {0} is not installed. Install?", - "DataScience.libraryRequiredToLaunchJupyterNotInstalled": "Data Science library {0} is not installed.", - "DataScience.librariesRequiredToLaunchJupyterNotInstalled": "Data Science libraries {0} are not installed.", - "DataScience.libraryRequiredToLaunchJupyterNotInstalledInterpreter": "Data Science library {1} is not installed in interpreter {0}.", - "DataScience.librariesRequiredToLaunchJupyterNotInstalledInterpreter": "Data Science libraries {1} are not installed in interpreter {0}.", - "DataScience.jupyterInstall": "Install", - "DataScience.jupyterSelectURIPrompt": "Enter the URI of the running Jupyter server", - "DataScience.jupyterSelectURIInvalidURI": "Invalid URI specified", - "DataScience.jupyterSelectUserAndPasswordTitle": "Enter your user name and password to connect to Jupyter Hub", - "DataScience.jupyterSelectUserPrompt": "Enter your user name", - "DataScience.jupyterSelectPasswordPrompt": "Enter your password", - "DataScience.jupyterNotebookFailure": "Jupyter notebook failed to launch. \r\n{0}", - "DataScience.jupyterNotebookConnectFailed": "Failed to connect to Jupyter notebook. \r\n{0}\r\n{1}", - "DataScience.jupyterNotebookRemoteConnectFailed": "Failed to connect to remote Jupyter notebook.\r\nCheck that the Jupyter Server URI setting has a valid running server specified.\r\n{0}\r\n{1}", - "DataScience.jupyterNotebookRemoteConnectSelfCertsFailed": "Failed to connect to remote Jupyter notebook.\r\nSpecified server is using self signed certs. Enable Allow Unauthorized Remote Connection setting to connect anyways\r\n{0}\r\n{1}", - "DataScience.notebookVersionFormat": "Jupyter Notebook Version: {0}", - "DataScience.jupyterKernelSpecNotFound": "Cannot create a Jupyter kernel spec and none are available for use", - "DataScience.jupyterKernelSpecModuleNotFound": "'Kernelspec' module not installed in the selected interpreter ({0}).\n Please re-install or update 'jupyter'.", - "DataScience.jupyterGetVariablesBadResults": "Failed to fetch variable info from the Jupyter server.", - "DataScience.liveShareConnectFailure": "Cannot connect to host Jupyter session. URI not found.", - "DataScience.liveShareCannotSpawnNotebooks": "Spawning Jupyter notebooks is not supported over a live share connection", - "DataScience.liveShareCannotImportNotebooks": "Importing notebooks is not currently supported over a live share connection", - "DataScience.liveShareHostFormat": "{0} Jupyter Server", - "DataScience.liveShareSyncFailure": "Synchronization failure during live share startup.", - "DataScience.liveShareServiceFailure": "Failure starting '{0}' service during live share connection.", - "DataScience.documentMismatch": "Cannot run cells, duplicate documents for {0} found.", - "DataScience.pythonInteractiveCreateFailed": "Failure to create a 'Python Interactive' window. Try reinstalling the Python extension.", - "diagnostics.removedPythonPathFromSettings": "We removed the \"python.pythonPath\" setting from your settings.json file as the setting is no longer used by the Python extension. You can get the path of your selected interpreter in the Python output channel. [Learn more](https://aka.ms/AA7jfor).", - "diagnostics.warnSourceMaps": "Source map support is enabled in the Python Extension, this will adversely impact performance of the extension.", - "diagnostics.disableSourceMaps": "Disable Source Map Support", - "diagnostics.warnBeforeEnablingSourceMaps": "Enabling source map support in the Python Extension will adversely impact performance of the extension.", - "diagnostics.enableSourceMapsAndReloadVSC": "Enable and reload Window", - "diagnostics.lsNotSupported": "Your operating system does not meet the minimum requirements of the Python Language Server. Reverting to the alternative autocompletion provider, Jedi.", - "diagnostics.invalidPythonPathInDebuggerSettings": "You need to select a Python interpreter before you start debugging.\n\nTip: click on \"Select Python Interpreter\" in the status bar.", - "diagnostics.invalidPythonPathInDebuggerLaunch": "The Python path in your debug configuration is invalid.", - "diagnostics.invalidDebuggerTypeDiagnostic": "Your launch.json file needs to be updated to change the \"pythonExperimental\" debug configurations to use the \"python\" debugger type, otherwise Python debugging may not work. Would you like to automatically update your launch.json file now?", - "diagnostics.consoleTypeDiagnostic": "Your launch.json file needs to be updated to change the console type string from \"none\" to \"internalConsole\", otherwise Python debugging may not work. Would you like to automatically update your launch.json file now?", - "diagnostics.justMyCodeDiagnostic": "Configuration \"debugStdLib\" in launch.json is no longer supported. It's recommended to replace it with \"justMyCode\", which is the exact opposite of using \"debugStdLib\". Would you like to automatically update your launch.json file to do that?", - "diagnostics.checkIsort5UpgradeGuide": "We found outdated configuration for sorting imports in this workspace. Check the [isort upgrade guide](https://aka.ms/AA9j5x4) to update your settings.", - "diagnostics.yesUpdateLaunch": "Yes, update launch.json", - "diagnostics.invalidTestSettings": "Your settings needs to be updated to change the setting \"python.unitTest.\" to \"python.testing.\", otherwise testing Python code using the extension may not work. Would you like to automatically update your settings now?", - "DataScience.interruptKernel": "Interrupt IPython kernel", - "DataScience.clearAllOutput": "Clear All Output", - "DataScience.exportingFormat": "Exporting {0}", - "DataScience.exportCancel": "Cancel", - "Common.canceled": "Canceled", - "Common.cancel": "Cancel", - "Common.yesPlease": "Yes, please", - "DataScience.importChangeDirectoryComment": "{0} Change working directory from the workspace root to the ipynb file location. Turn this addition off with the DataScience.changeDirOnImportExport setting", - "DataScience.exportChangeDirectoryComment": "# Change directory to VSCode workspace root so that relative path loads work correctly. Turn this addition off with the DataScience.changeDirOnImportExport setting", - "DataScience.interruptKernelStatus": "Interrupting IPython Kernel", - "DataScience.restartKernelAfterInterruptMessage": "Interrupting the kernel timed out. Do you want to restart the kernel instead? All variables will be lost.", - "DataScience.pythonInterruptFailedHeader": "Keyboard interrupt crashed the kernel. Kernel restarted.", - "DataScience.sysInfoURILabel": "Jupyter Server URI: ", - "DataScience.jupyterStartTimedout": "Starting Jupyter has timedout. Please check the 'Jupyter' output panel for further details.", - "DataScience.startingJupyterLogMessage": "Starting Jupyter from {0} with command line {1}", - "DataScience.jupyterCommandLineDefaultLabel": "Default (recommended)", - "DataScience.jupyterCommandLineDefaultDetail": "The Python extension will determine the appropriate command line for Jupyter", - "DataScience.jupyterCommandLineCustomLabel": "Custom", - "DataScience.jupyterCommandLineCustomDetail": "Customize the command line passed to Jupyter on startup", - "DataScience.jupyterCommandLineReloadQuestion": "Please reload the window when changing the Jupyter command line.", - "DataScience.jupyterCommandLineReloadAnswer": "Reload", - "DataScience.jupyterCommandLineQuickPickPlaceholder": "Choose an option", - "DataScience.jupyterCommandLineQuickPickTitle": "Pick command line for Jupyter", - "DataScience.jupyterCommandLinePrompt": "Enter your custom command line for Jupyter", - "Common.loadingPythonExtension": "Python extension loading...", - "debug.selectConfigurationTitle": "Select a debug configuration", - "debug.selectConfigurationPlaceholder": "Debug Configuration", - "debug.launchJsonConfigurationsCompletionLabel": "Python", - "debug.launchJsonConfigurationsCompletionDescription": "Select a Python debug configuration", - "debug.debugFileConfigurationLabel": "Python File", - "debug.debugFileConfigurationDescription": "Debug the currently active Python file", - "debug.debugModuleConfigurationLabel": "Module", - "debug.debugModuleConfigurationDescription": "Debug a Python module by invoking it with '-m'", - "debug.moduleEnterModuleTitle": "Debug Module", - "debug.moduleEnterModulePrompt": "Enter a Python module/package name", - "debug.moduleEnterModuleDefault": "enter-your-module-name", - "debug.moduleEnterModuleInvalidNameError": "Enter a valid module name", - "debug.remoteAttachConfigurationLabel": "Remote Attach", - "debug.remoteAttachConfigurationDescription": "Attach to a remote debug server", - "debug.attachRemoteHostTitle": "Remote Debugging", - "debug.attachRemoteHostPrompt": "Enter the host name", - "debug.attachRemoteHostValidationError": "Enter a valid host name or IP address", - "debug.attachRemotePortTitle": "Remote Debugging", - "debug.attachRemotePortPrompt": "Enter the port number that the debug server is listening on", - "debug.attachRemotePortValidationError": "Enter a valid port number", - "debug.attachPidConfigurationLabel": "Attach using Process ID", - "debug.attachPidConfigurationDescription": "Attach to a local process", - "debug.debugDjangoConfigurationLabel": "Django", - "debug.debugDjangoConfigurationDescription": "Launch and debug a Django web application", - "debug.djangoEnterManagePyPathTitle": "Debug Django", - "debug.djangoEnterManagePyPathPrompt": "Enter the path to manage.py ('${workspaceFolderToken}' points to the root of the current workspace folder)", - "debug.djangoEnterManagePyPathInvalidFilePathError": "Enter a valid Python file path", - "debug.debugFlaskConfigurationLabel": "Flask", - "debug.debugFlaskConfigurationDescription": "Launch and debug a Flask web application", - "debug.flaskEnterAppPathOrNamePathTitle": "Debug Flask", - "debug.flaskEnterAppPathOrNamePathPrompt": "Enter the path to the application, e.g. 'app.py' or 'app'", - "debug.flaskEnterAppPathOrNamePathInvalidNameError": "Enter a valid name", - "debug.debugPyramidConfigurationLabel": "Pyramid", - "debug.debugPyramidConfigurationDescription": "Web Application", - "debug.pyramidEnterDevelopmentIniPathTitle": "Debug Pyramid", - "debug.pyramidEnterDevelopmentIniPathPrompt": "`Enter the path to development.ini ('${workspaceFolderToken}' points to the root of the current workspace folder)`", - "debug.pyramidEnterDevelopmentIniPathInvalidFilePathError": "Enter a valid file path", - "Testing.testErrorDiagnosticMessage": "Error", - "Testing.testFailDiagnosticMessage": "Fail", - "Testing.testSkippedDiagnosticMessage": "Skipped", - "Testing.configureTests": "Configure Test Framework", - "Testing.disableTests": "Disable Tests", - "Common.openOutputPanel": "Show output", - "LanguageService.lsFailedToStart": "We encountered an issue starting the language server. Reverting to Jedi language engine. Check the Python output panel for details.", - "LanguageService.lsFailedToDownload": "We encountered an issue downloading the language server. Reverting to Jedi language engine. Check the Python output panel for details.", - "LanguageService.lsFailedToExtract": "We encountered an issue extracting the language server. Reverting to Jedi language engine. Check the Python output panel for details.", - "LanguageService.downloadFailedOutputMessage": "Language server download failed", - "LanguageService.extractionFailedOutputMessage": "Language server extraction failed", - "LanguageService.extractionCompletedOutputMessage": "Language server download complete", - "LanguageService.extractionDoneOutputMessage": "done", - "LanguageService.reloadVSCodeIfSeachPathHasChanged": "Search paths have changed for this Python interpreter. Please reload the extension to ensure that the IntelliSense works correctly", - "LanguageService.startingJedi": "Starting Jedi Python language engine.", - "LanguageService.startingMicrosoft": "Starting Microsoft Python language server.", - "LanguageService.startingPylance": "Starting Pylance language server.", - "LanguageService.startingNone": "Editor support is inactive since language server is set to None.", - "LanguageService.reloadAfterLanguageServerChange": "Please reload the window switching between language servers.", - "AttachProcess.unsupportedOS": "Operating system '{0}' not supported.", - "AttachProcess.attachTitle": "Attach to process", - "AttachProcess.selectProcessPlaceholder": "Select the process to attach to", - "AttachProcess.noProcessSelected": "No process selected", - "AttachProcess.refreshList": "Refresh process list", - "DataScience.variableExplorerNameColumn": "Name", - "DataScience.variableExplorerTypeColumn": "Type", - "DataScience.variableExplorerCountColumn": "Size", - "DataScience.variableExplorerValueColumn": "Value", - "DataScience.showDataExplorerTooltip": "Show variable in data viewer.", - "DataScience.dataExplorerInvalidVariableFormat": "'{0}' is not an active variable.", - "DataScience.jupyterGetVariablesExecutionError": "Failure during variable extraction:\r\n{0}", - "DataScience.loadingMessage": "loading ...", - "DataScience.fetchingDataViewer": "Fetching data ...", - "DataScience.noRowsInDataViewer": "No rows match current filter", - "DataScience.jupyterServer": "Jupyter Server", - "DataScience.trustNotebookCommandTitle": "Trust notebook", - "DataScience.notebookIsTrusted": "Trusted", - "DataScience.notebookIsNotTrusted": "Not Trusted", - "DataScience.noKernel": "No Kernel", - "DataScience.serverNotStarted": "Not Started", - "DataScience.localJupyterServer": "local", - "DataScience.pandasTooOldForViewingFormat": "Python package 'pandas' is version {0}. Version 0.20 or greater is required for viewing data.", - "DataScience.pandasRequiredForViewing": "Python package 'pandas' is required for viewing data.", - "DataScience.valuesColumn": "values", - "DataScience.liveShareInvalid": "One or more guests in the session do not have the Python [extension](https://marketplace.visualstudio.com/itemdetails?itemName=ms-python.python) installed.\r\nYour Live Share session cannot continue and will be closed.", - "diagnostics.updateSettings": "Yes, update settings", - "Common.noIWillDoItLater": "No, I will do it later", - "Common.notNow": "Not now", - "Common.gotIt": "Got it!", - "Interpreters.selectInterpreterTip": "Tip: you can change the Python interpreter used by the Python extension by clicking on the Python version in the status bar", - "DataScience.noRowsInVariableExplorer": "No variables defined", - "DataScience.tooManyColumnsMessage": "Variables with over a 1000 columns may take a long time to display. Are you sure you wish to continue?", - "DataScience.tooManyColumnsYes": "Yes", - "DataScience.tooManyColumnsNo": "No", - "DataScience.tooManyColumnsDontAskAgain": "Don't Ask Again", - "DataScience.filterRowsButton": "Filter Rows", - "DataScience.filterRowsTooltip": "Allows filtering multiple rows. Use =, >, or < signs to filter numeric values.", - "DataScience.previewHeader": "--- Begin preview of {0} ---", - "DataScience.previewFooter": "--- End preview of {0} ---", - "DataScience.previewStatusMessage": "Generating preview of {0}", - "DataScience.plotViewerTitle": "Plots", - "DataScience.exportPlotTitle": "Save plot image", - "DataScience.pdfFilter": "PDF", - "DataScience.pngFilter": "PNG", - "DataScience.svgFilter": "SVG", - "DataScience.previousPlot": "Previous", - "DataScience.nextPlot": "Next", - "DataScience.panPlot": "Pan", - "DataScience.zoomInPlot": "Zoom in", - "DataScience.zoomOutPlot": "Zoom out", - "DataScience.exportPlot": "Export to different formats", - "DataScience.deletePlot": "Remove", - "DataScience.collapseSingle": "Collapse", - "DataScience.expandSingle": "Expand", - "DataScience.editSection": "Input new cells here.", - "DataScience.restartKernelMessageDontAskAgain": "Yes, and Don't Ask Again", - "DataScience.selectedImageListLabel": "Selected Image", - "DataScience.imageListLabel": "Image", - "DataScience.exportImageFailed": "Error exporting image: {0}", - "downloading.file": "Downloading {0}...", - "downloading.file.progress": "{0}{1} of {2} KB ({3}%)", - "DataScience.jupyterDataRateExceeded": "Cannot view variable because data rate exceeded. Please restart your server with a higher data rate limit. For example, --NotebookApp.iopub_data_rate_limit=10000000000.0", - "DataScience.addCellBelowCommandTitle": "Add cell", - "DataScience.debugCellCommandTitle": "Debug Cell", - "DataScience.debugStepOverCommandTitle": "Step over", - "DataScience.debugContinueCommandTitle": "Continue", - "DataScience.debugStopCommandTitle": "Stop", - "DataScience.runCurrentCellAndAddBelow": "Run current and add cell below", - "DataScience.variableExplorerDisabledDuringDebugging": "Please see the Debug Side Bar's VARIABLES section.", - "DataScience.jupyterDebuggerNotInstalledError": "Pip module {0} is required for debugging cells. You will need to install it to debug cells.", - "DataScience.jupyterDebuggerOutputParseError": "Unable to parse {0} output, please log an issue with https://github.com/microsoft/vscode-python", - "DataScience.jupyterDebuggerPortNotAvailableError": "Port {0} cannot be opened for debugging. Please specify a different port in the remoteDebuggerPort setting.", - "DataScience.jupyterDebuggerPortBlockedError": "Port {0} cannot be connected to for debugging. Please let port {0} through your firewall.", - "DataScience.jupyterDebuggerPortNotAvailableSearchError": "Ports in the range {0}-{1} cannot be found for debugging. Please specify a port in the remoteDebuggerPort setting.", - "DataScience.jupyterDebuggerPortBlockedSearchError": "A port cannot be connected to for debugging. Please let ports {0}-{1} through your firewall.", - "DataScience.jupyterDebuggerInstallNew": "Pip module {0} is required for debugging cells. Install {0} and continue to debug cell?", - "DataScience.jupyterDebuggerInstallNewRunByLine": "Pip module {0} is required for running by line. Install {0} and continue to run by line?", - "DataScience.jupyterDebuggerInstallUpdate": "The version of {0} installed does not support debugging cells. Update {0} to newest version and continue to debug cell?", - "DataScience.jupyterDebuggerInstallUpdateRunByLine": "The version of {0} installed does not support running by line. Update {0} to newest version and continue to run by line?", - "DataScience.jupyterDebuggerInstallYes": "Yes", - "DataScience.jupyterDebuggerInstallNo": "No", - "DataScience.cellStopOnErrorFormatMessage": "{0} cells were canceled due to an error in the previous cell.", - "DataScience.instructionComments": "# To add a new cell, type '{0}'\n# To add a new markdown cell, type '{0} [markdown]'\n", - "DataScience.scrollToCellTitleFormatMessage": "Go to [{0}]", - "DataScience.remoteDebuggerNotSupported": "Debugging while attached to a remote server is not currently supported.", - "DataScience.save": "Save notebook", - "DataScience.invalidNotebookFileError": "{0} is not a valid notebook file. Check the file for correct json.", - "DataScience.nativeEditorTitle": "Notebook Editor", - "DataScience.untitledNotebookFileName": "Untitled", - "DataScience.dirtyNotebookMessage1": "Do you want to save the changes you made to {0}?", - "DataScience.dirtyNotebookMessage2": "Your changes will be lost if you don't save them.", - "DataScience.dirtyNotebookYes": "Save", - "DataScience.dirtyNotebookNo": "Don't Save", - "DataScience.dirtyNotebookCancel": "Cancel", - "DataScience.dirtyNotebookDialogTitle": "Save", - "DataScience.dirtyNotebookDialogFilter": "Jupyter Notebooks", - "DataScience.exportAsPythonFileTooltip": "Convert and save to a python script", - "DataScience.exportAsPythonFileTitle": "Save as Python File", - "DataScience.runCell": "Run cell", - "DataScience.deleteCell": "Delete cell", - "DataScience.moveCellUp": "Move cell up", - "DataScience.moveCellDown": "Move cell down", - "DataScience.moveSelectedCellUp": "Move selected cell up", - "DataScience.insertBelow": "Insert cell below", - "DataScience.insertAbove": "Insert cell above", - "DataScience.addCell": "Add cell", - "DataScience.runAll": "Run all cells", - "DataScience.convertingToPythonFile": "Converting ipynb to python file", - "DataScience.untitledNotebookMessage": "Your changes will be lost if you don't save them.", - "DataScience.untitledNotebookYes": "Save", - "DataScience.untitledNotebookNo": "Cancel", - "DataScience.noInterpreter": "No python selected", - "DataScience.notebookNotFound": "python -m jupyter notebook --version is not running", - "DataScience.findJupyterCommandProgress": "Active interpreter does not support {0}. Searching for the best available interpreter.", - "DataScience.findJupyterCommandProgressCheckInterpreter": "Checking {0}.", - "DataScience.findJupyterCommandProgressSearchCurrentPath": "Searching current path.", - "DataScience.gatherError": "Gather internal error", - "DataScience.gatheredScriptDescription": "# This file was generated by the Gather Extension.\n# It requires version 2020.7.94776 (or newer) of the Python Extension.\n#\n# The intent is that it contains only the code required to produce\n# the same results as the cell originally selected for gathering.\n# Please note that the Python analysis is quite conservative, so if\n# it is unsure whether a line of code is necessary for execution, it\n# will err on the side of including it.\n#\n# Please let us know if you are satisfied with what was gathered here:\n# https://aka.ms/gatherfeedback\n\n", - "DataScience.gatheredNotebookDescriptionInMarkdown": "## Gathered Notebook\nGathered from ```{0}```\n\n| | |\n|---|---|\n|   |This notebook was generated by the Gather Extension. It requires version 2020.7.94776 (or newer) of the Python Extension, please update [here](https://command:python.datascience.latestExtension). The intent is that it contains only the code and cells required to produce the same results as the cell originally selected for gathering. Please note that the Python analysis is quite conservative, so if it is unsure whether a line of code is necessary for execution, it will err on the side of including it.|\n\n**Are you satisfied with the code that was gathered?**\n\n[Yes](https://command:python.datascience.gatherquality?yes) [No](https://command:python.datascience.gatherquality?no)", - "DataScience.savePngTitle": "Save Image", - "DataScience.jupyterSelectURIQuickPickTitle": "Pick how to connect to Jupyter", - "DataScience.jupyterSelectURIQuickPickPlaceholder": "Choose an option", - "DataScience.jupyterSelectURILocalLabel": "Default", - "DataScience.jupyterSelectURILocalDetail": "VS Code will automatically start a server for you on the localhost", - "DataScience.jupyterSelectURINewLabel": "Existing", - "DataScience.jupyterSelectURINewDetail": "Specify the URI of an existing server", - "DataScience.jupyterSelectURIMRUDetail": "Last Connection: {0}", - "DataScience.jupyterSelectURIRunningDetailFormat": "Last activity {0}. {1} existing connections.", - "DataScience.jupyterSelectURINotRunningDetail": "Cannot connect at this time. Status unknown.", - "DataScience.fallbackToUseActiveInterpeterAsKernel": "Couldn't find kernel '{0}' that the notebook was created with. Using the current interpreter.", - "DataScience.fallBackToRegisterAndUseActiveInterpeterAsKernel": "Couldn't find kernel '{0}' that the notebook was created with. Registering a new kernel using the current interpreter.", - "DataScience.fallBackToPromptToUseActiveInterpreterOrSelectAKernel": "Couldn't find kernel '{0}' that the notebook was created with.", - "DataScience.selectKernel": "Select a Kernel", - "DataScience.selectDifferentKernel": "Select a different Kernel", - "DataScience.selectDifferentJupyterInterpreter": "Select a different Interpreter", - "DataScience.selectJupyterInterpreter": "Select an Interpreter to start Jupyter", - "products.installingModule": "Installing {0}", - "DataScience.switchingKernelProgress": "Switching kernel to '{0}'", - "DataScience.sessionStartFailedWithKernel": "Failed to start a session for the Kernel '{0}'. \nView Jupyter [log](command:{1}) for further details.", - "DataScience.waitingForJupyterSessionToBeIdle": "Waiting for Jupyter Session to be idle", - "DataScience.gettingListOfKernelsForLocalConnection": "Fetching Kernels", - "DataScience.gettingListOfKernelsForRemoteConnection": "Fetching Kernels", - "DataScience.gettingListOfKernelSpecs": "Fetching Kernel specs", - "DataScience.startingJupyterNotebook": "Starting Jupyter Notebook", - "DataScience.registeringKernel": "Registering Kernel", - "DataScience.trimmedOutput": "Output was trimmed for performance reasons.\nTo see the full output set the setting \"python.dataScience.textOutputLimit\" to 0.", - "DataScience.connectingToJupyterUri": "Connecting to Jupyter server at {0}", - "DataScience.createdNewNotebook": "{0}: Creating new notebook ", - "DataScience.createdNewKernel": "{0}: Kernel started: {1}", - "DataScience.kernelInvalid": "Kernel {0} is not usable. Check the Jupyter output tab for more information.", - "OutdatedDebugger.updateDebuggerMessage": "We noticed you are attaching to ptvsd (Python debugger), which was deprecated on May 1st, 2020. Please switch to [debugpy](https://aka.ms/migrateToDebugpy).", - "DataScience.nativeDependencyFail": "We cannot launch a jupyter server because your OS is not supported. Select an already running server if you wish to continue. {0}", - "DataScience.selectNewServer": "Pick Running Server", - "DataScience.jupyterSelectURIRemoteLabel": "Existing", - "DataScience.jupyterSelectURIQuickPickTitleRemoteOnly": "Pick an already running jupyter server", - "DataScience.jupyterSelectURIRemoteDetail": "Specify the URI of an existing server", - "DataScience.gatherQuality": "Did gather work as desired?", - "DataScience.latestExtension": "Download the latest version of the Python Extension", - "DataScience.loadClassFailedWithNoInternet": "Error loading {0}:{1}. Internet connection required for loading 3rd party widgets.", - "DataScience.useCDNForWidgets": "Widgets require us to download supporting files from a 3rd party website. Click [here](https://aka.ms/PVSCIPyWidgets) for more information.", - "DataScience.loadThirdPartyWidgetScriptsPostEnabled": "Please restart the Kernel when changing the setting 'python.dataScience.widgetScriptSources'.", - "DataScience.enableCDNForWidgetsSetting": "Widgets require us to download supporting files from a 3rd party website. Click here to enable this or click here for more information. (Error loading {0}:{1}).", - "DataScience.widgetScriptNotFoundOnCDNWidgetMightNotWork": "Unable to load a compatible version of the widget '{0}'. Expected behavior may be affected.", - "DataScience.unhandledMessage": "Unhandled kernel message from a widget: {0} : {1}", - "DataScience.qgridWidgetScriptVersionCompatibilityWarning": "Unable to load a compatible version of the widget 'qgrid'. Consider downgrading to version 1.1.1.", - "DataScience.kernelStarted": "Started kernel {0}", - "StartPage.getStarted": "Python - Get Started", - "StartPage.pythonExtensionTitle": "Python Extension", - "StartPage.createJupyterNotebook": "Create a Jupyter Notebook", - "StartPage.notebookDescription": "- Run \"\" in the Command Palette (
Shift + Command + P
)
- Explore our
sample notebook
to learn about notebook features", - "StartPage.createAPythonFile": "Create a Python File", - "StartPage.pythonFileDescription": "- Create a
new file
with a .py extension", - "StartPage.openInteractiveWindow": "Use the Interactive Window to develop Python Scripts", - "StartPage.interactiveWindowDesc": "- You can create cells on a Python file by typing \"#%%\"
- Use \"
Shift + Enter
\" to run a cell, the output will be shown in the interactive window", - "StartPage.releaseNotes": "Take a look at our Release Notes to learn more about the latest features.", - "StartPage.tutorialAndDoc": "Explore more features in our Tutorials or check Documentation for tips and troubleshooting.", - "StartPage.dontShowAgain": "Don't show this page again", - "StartPage.helloWorld": "Hello world", - "StartPage.sampleNotebook": "Notebooks intro", - "StartPage.openFolder": "Open a Folder or Workspace", - "StartPage.folderDesc": "- Open a
Folder

- Open a
Workspace
", - "DataScience.libraryRequiredToLaunchJupyterKernelNotInstalledInterpreter": "{0} requires {1} to be installed.", - "DataScience.runByLine": "Run by line (F10)", - "DataScience.stopRunByLine": "Stop", - "DataScience.couldNotInstallLibrary": "Could not install {0}. If pip is not available, please use the package manager of your choice to manually install this library into your Python environment.", - "DataScience.rawKernelSessionFailed": "Unable to start session for kernel {0}. Select another kernel to launch with.", - "DataScience.rawKernelConnectingSession": "Connecting to kernel.", - "DataScience.reloadCustomEditor": "Please reload VS Code to use the custom editor API", - "DataScience.reloadVSCodeNotebookEditor": "Please reload VS Code to use the Notebook Editor", - "DataScience.step": "Run next line (F10)", - "DataScience.usingPreviewNotebookWithOtherNotebookWarning": "Opening the same file in the Preview Notebook Editor and stable Notebook Editor is not recommended. Doing so could result in data loss or corruption of notebooks.", - "DataScience.previewNotebookOnlySupportedInVSCInsiders": "The Preview Notebook Editor is supported only in the Insiders version of Visual Studio Code.", - "DataScienceNotebookSurveyBanner.bannerMessage": "Can you please take 2 minutes to tell us how the Preview Notebook Editor is working for you?", - "DataScience.unknownServerUri": "Server URI cannot be used. Did you uninstall an extension that provided a Jupyter server connection?", - "DataScienceRendererExtension.installingExtension": "Installing Notebook Renderers extension...", - "DataScienceRendererExtension.installationCompleteMessage": "complete.", - "DataScienceRendererExtension.startingDownloadOutputMessage": "Starting download of Notebook Renderers extension.", - "DataScienceRendererExtension.downloadingMessage": "Downloading Notebook Renderers Extension...", - "DataScienceRendererExtension.downloadCompletedOutputMessage": "Notebook Renderers extension download complete.", - "DataScience.uriProviderDescriptionFormat": "{0} (From {1} extension)", - "DataScience.unknownPackage": "unknown", - "DataScience.interactiveWindowTitle": "Python Interactive", - "DataScience.interactiveWindowTitleFormat": "Python Interactive - {0}", - "DataScience.interactiveWindowModeBannerTitle": "Do you want to open a new Python Interactive window for this file? [More Information](command:workbench.action.openSettings?%5B%22python.dataScience.interactiveWindowMode%22%5D).", - "DataScience.interactiveWindowModeBannerSwitchYes": "Yes", - "DataScience.interactiveWindowModeBannerSwitchAlways": "Always", - "DataScience.interactiveWindowModeBannerSwitchNo": "No", - "DataScience.ipykernelNotInstalled": "IPyKernel not installed into interpreter {0}" + "python.command.python.launchTensorBoard.title": "Launch TensorBoard", + "python.command.python.refreshTensorBoard.title": "Refresh TensorBoard", + "python.command.python.testing.copyTestId.title": "Copy Test Id", + "python.createEnvironment.contentButton.description": "Show or hide Create Environment button in the editor for `requirements.txt` or other dependency files.", + "python.createEnvironment.trigger.description": "Detect if environment creation is required for the current project", + "python.menu.createNewFile.title": "Python File", + "python.editor.context.submenu.runPython": "Run Python", + "python.editor.context.submenu.runPythonInteractive": "Run in Interactive window", + "python.activeStateToolPath.description": "Path to the State Tool executable for ActiveState runtimes (version 0.36+).", + "python.autoComplete.extraPaths.description": "List of paths to libraries and the like that need to be imported by auto complete engine. E.g. when using Google App SDK, the paths are not in system path, hence need to be added into this list.", + "python.condaPath.description": "Path to the conda executable to use for activation (version 4.4+).", + "python.debugger.deprecatedMessage": "This configuration will be deprecated soon. Please replace `python` with `debugpy` to use the new Python Debugger extension.", + "python.defaultInterpreterPath.description": "Path to default Python to use when extension loads up for the first time, no longer used once an interpreter is selected for the workspace. See [here](https://aka.ms/AAfekmf) to understand when this is used", + "python.envFile.description": "Absolute path to a file containing environment variable definitions.", + "python.useEnvironmentsExtension.description": "Enables the Python Environments extension. Requires window reload on change.", + "python.experiments.enabled.description": "Enables A/B tests experiments in the Python extension. If enabled, you may get included in proposed enhancements and/or features.", + "python.experiments.optInto.description": "List of experiments to opt into. If empty, user is assigned the default experiment groups. See [here](https://github.com/microsoft/vscode-python/wiki/AB-Experiments) for more details.", + "python.experiments.optOutFrom.description": "List of experiments to opt out of. If empty, user is assigned the default experiment groups. See [here](https://github.com/microsoft/vscode-python/wiki/AB-Experiments) for more details.", + "python.experiments.All.description": "Combined list of all experiments.", + "python.experiments.pythonSurveyNotification.description": "Denotes the Python Survey Notification experiment.", + "python.experiments.pythonPromptNewToolsExt.description": "Denotes the Python Prompt New Tools Extension experiment.", + "python.experiments.pythonTerminalEnvVarActivation.description": "Enables use of environment variables to activate terminals instead of sending activation commands.", + "python.experiments.pythonDiscoveryUsingWorkers.description": "Enables use of worker threads to do heavy computation when discovering interpreters.", + "python.experiments.pythonTestAdapter.description": "Denotes the Python Test Adapter experiment.", + "python.experiments.pythonRecommendTensorboardExt.description": "Denotes the Tensorboard Extension recommendation experiment.", + "python.globalModuleInstallation.description": "Whether to install Python modules globally when not using an environment.", + "python.languageServer.description": "Defines type of the language server.", + "python.languageServer.defaultDescription": "Automatically select a language server: Pylance if installed and available, otherwise fallback to Jedi.", + "python.languageServer.jediDescription": "Use Jedi behind the Language Server Protocol (LSP) as a language server.", + "python.languageServer.pylanceDescription": "Use Pylance as a language server.", + "python.languageServer.noneDescription": "Disable language server capabilities.", + "python.interpreter.infoVisibility.description": "Controls when to display information of selected interpreter in the status bar.", + "python.interpreter.infoVisibility.never.description": "Never display information.", + "python.interpreter.infoVisibility.onPythonRelated.description": "Only display information if Python-related files are opened.", + "python.interpreter.infoVisibility.always.description": "Always display information.", + "python.logging.level.description": "The logging level the extension logs at, defaults to 'error'", + "python.logging.level.deprecation": "This setting is deprecated. Please use command `Developer: Set Log Level...` to set logging level.", + "python.missingPackage.severity.description": "Set severity of missing packages in requirements.txt or pyproject.toml", + "python.locator.description": "[Experimental] Select implementation of environment locators. This is an experimental setting while we test native environment location.", + "python.pipenvPath.description": "Path to the pipenv executable to use for activation.", + "python.poetryPath.description": "Path to the poetry executable.", + "python.pixiToolPath.description": "Path to the pixi executable.", + "python.EnableREPLSmartSend.description": "Toggle Smart Send for the Python REPL. Smart Send enables sending the smallest runnable block of code to the REPL on Shift+Enter and moves the cursor accordingly.", + "python.REPL.sendToNativeREPL.description": "Toggle to send code to Python REPL instead of the terminal on execution. Turning this on will change the behavior for both Smart Send and Run Selection/Line in the Context Menu.", + "python.REPL.provideVariables.description": "Toggle to provide variables for the REPL variable view for the native REPL.", + "python.tensorBoard.logDirectory.description": "Set this setting to your preferred TensorBoard log directory to skip log directory prompt when starting TensorBoard.", + "python.tensorBoard.logDirectory.markdownDeprecationMessage": "Tensorboard support has been moved to the extension [Tensorboard extension](https://marketplace.visualstudio.com/items?itemName=ms-toolsai.tensorboard). Instead use the setting `tensorBoard.logDirectory`.", + "python.tensorBoard.logDirectory.deprecationMessage": "Tensorboard support has been moved to the extension Tensorboard extension. Instead use the setting `tensorBoard.logDirectory`.", + "python.terminal.shellIntegration.enabled.description": "Enable [shell integration](https://code.visualstudio.com/docs/terminal/shell-integration) for the terminals running python. Shell integration enhances the terminal experience by enabling command decorations, run recent command, improving accessibility among other things. Note: PyREPL (available in Python 3.13+) is automatically disabled when shell integration is enabled to avoid cursor indentation issues.", + "python.terminal.activateEnvInCurrentTerminal.description": "Activate Python Environment in the current Terminal on load of the Extension.", + "python.terminal.activateEnvironment.description": "Activate Python Environment in all Terminals created.", + "python.terminal.executeInFileDir.description": "When executing a file in the terminal, whether to use execute in the file's directory, instead of the current open folder.", + "python.terminal.focusAfterLaunch.description": "When launching a python terminal, whether to focus the cursor on the terminal.", + "python.terminal.launchArgs.description": "Python launch arguments to use when executing a file in the terminal.", + "python.testing.autoTestDiscoverOnSaveEnabled.description": "Enable auto run test discovery when saving a test file.", + "python.testing.autoTestDiscoverOnSavePattern.description": "Glob pattern used to determine which files are used by autoTestDiscoverOnSaveEnabled.", + "python.testing.cwd.description": "Optional working directory for tests.", + "python.testing.debugPort.description": "Port number used for debugging of tests.", + "python.testing.promptToConfigure.description": "Prompt to configure a test framework if potential tests directories are discovered.", + "python.testing.pytestArgs.description": "Arguments passed in. Each argument is a separate item in the array.", + "python.testing.pytestEnabled.description": "Enable testing using pytest.", + "python.testing.pytestPath.description": "Path to pytest. You can use a custom version of pytest by modifying this setting to include the full path.", + "python.testing.unittestArgs.description": "Arguments passed in. Each argument is a separate item in the array.", + "python.testing.unittestEnabled.description": "Enable testing using unittest.", + "python.venvFolders.description": "Folders in your home directory to look into for virtual environments (supports pyenv, direnv and virtualenvwrapper by default).", + "python.venvPath.description": "Path to folder with a list of Virtual Environments (e.g. ~/.pyenv, ~/Envs, ~/.virtualenvs).", + "walkthrough.pythonWelcome.title": "Get Started with Python Development", + "walkthrough.pythonWelcome.description": "Your first steps to set up a Python project with all the powerful tools and features that the Python extension has to offer!", + "walkthrough.step.python.createPythonFile.title": "Create a Python file", + "walkthrough.step.python.createPythonFolder.title": "Open a Python project folder", + "walkthrough.step.python.createPythonFile.description": { + "message": "[Open](command:toSide:workbench.action.files.openFile) or [create](command:toSide:workbench.action.files.newUntitledFile?%7B%22languageId%22%3A%22python%22%7D) a Python file - make sure to save it as \".py\".\n[Create Python File](command:toSide:workbench.action.files.newUntitledFile?%7B%22languageId%22%3A%22python%22%7D)", + "comment": [ + "{Locked='](command:toSide:workbench.action.files.newUntitledFile?%7B%22languageId%22%3A%22python%22%7D'}", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", + "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" + ] + }, + "walkthrough.step.python.createPythonFolder.description": { + "message": "[Open](command:workbench.action.files.openFolder) or create a project folder.\n[Open Project Folder](command:workbench.action.files.openFolder)", + "comment": [ + "{Locked='](command:workbench.action.files.openFolder'}", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", + "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" + ] + }, + "walkthrough.step.python.installPythonWin8.title": "Install Python", + "walkthrough.step.python.installPythonWin8.description": "The Python Extension requires Python to be installed. Install Python [from python.org](https://www.python.org/downloads).\n\n[Install Python](https://www.python.org/downloads)\n", + "walkthrough.step.python.installPythonMac.title": "Install Python", + "walkthrough.step.python.installPythonMac.description": { + "message": "The Python Extension requires Python to be installed. Install Python 3 through the terminal.\n[Install Python via Brew](command:python.installPythonOnMac)\n", + "comment": [ + "{Locked='](command:python.installPythonOnMac'}", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", + "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" + ] + }, + "walkthrough.step.python.installPythonLinux.title": "Install Python", + "walkthrough.step.python.installPythonLinux.description": { + "message": "The Python Extension requires Python to be installed. Install Python 3 through the terminal.\n[Install Python via terminal](command:python.installPythonOnLinux)\n", + "comment": [ + "{Locked='](command:python.installPythonOnLinux'}", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", + "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" + ] + }, + "walkthrough.step.python.selectInterpreter.title": "Select a Python Interpreter", + "walkthrough.step.python.createEnvironment.title": "Select or create a Python environment", + "walkthrough.step.python.createEnvironment.description": { + "message": "Create an environment for your Python project or use [Select Python Interpreter](command:python.setInterpreter) to select an existing one.\n[Create Environment](command:python.createEnvironment)\n**Tip**: Run the ``Python: Create Environment`` command in the [Command Palette](command:workbench.action.showCommands).", + "comment": [ + "{Locked='](command:python.createEnvironment'}", + "{Locked='](command:workbench.action.showCommands'}", + "{Locked='](command:python.setInterpreter'}", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", + "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" + ] + }, + "walkthrough.step.python.runAndDebug.title": "Run and debug your Python file", + "walkthrough.step.python.runAndDebug.description": "Open your Python file and click on the play button on the top right of the editor, or press F5 when on the file and select \"Python File\" to run with the debugger. \n \n[Learn more](https://code.visualstudio.com/docs/python/python-tutorial#_run-hello-world)", + "walkthrough.step.python.learnMoreWithDS.title": "Keep exploring!", + "walkthrough.step.python.learnMoreWithDS.description": { + "message": "🎨 Explore all the features the Python extension has to offer by looking for \"Python\" in the [Command Palette](command:workbench.action.showCommands). \n 📈 Learn more about getting started with [data science](command:workbench.action.openWalkthrough?%7B%22category%22%3A%22ms-python.python%23pythonDataScienceWelcome%22%2C%22step%22%3A%22ms-python.python%23python.createNewNotebook%22%7D) in Python. \n ✨ Take a look at our [Release Notes](https://aka.ms/AA8dxtb) to learn more about the latest features. \n \n[Follow along with the Python Tutorial](https://aka.ms/AA8dqti)", + "comment": [ + "{Locked='](command:workbench.action.showCommands'}", + "{Locked='](command:workbench.action.openWalkthrough?%7B%22category%22%3A%22ms-python.python%23pythonDataScienceWelcome%22%2C%22step%22%3A%22ms-python.python%23python.createNewNotebook%22%7D'}", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", + "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" + ] + }, + "walkthrough.pythonDataScienceWelcome.title": "Get Started with Python for Data Science", + "walkthrough.pythonDataScienceWelcome.description": "Your first steps to getting started with a Data Science project with Python!", + "walkthrough.step.python.installJupyterExt.title": "Install Jupyter extension", + "walkthrough.step.python.installJupyterExt.description": "If you haven't already, install the [Jupyter extension](command:workbench.extensions.search?\"ms-toolsai.jupyter\") to take full advantage of notebooks experiences in VS Code!\n \n[Search Jupyter extension](command:workbench.extensions.search?\"ms-toolsai.jupyter\")", + "walkthrough.step.python.createNewNotebook.title": "Create or open a Jupyter Notebook", + "walkthrough.step.python.createNewNotebook.description": "Right click in the file explorer and create a new file with an .ipynb extension. Or, open the [Command Palette](command:workbench.action.showCommands) and run the command \n``Jupyter: Create New Blank Notebook``.\n[Create new Jupyter Notebook](command:toSide:jupyter.createnewnotebook)\n If you have an existing project, you can also [open a folder](command:workbench.action.files.openFolder) and/or clone a project from GitHub: [clone a Git repository](command:git.clone).", + "walkthrough.step.python.openInteractiveWindow.title": "Open the Python Interactive Window", + "walkthrough.step.python.openInteractiveWindow.description": "The Python Interactive Window is a Python shell where you can execute and view the results of your Python code. You can create cells on a Python file by typing ``#%%``.\n \nTo open the interactive window anytime, open the [Command Palette](command:workbench.action.showCommands) and run the command \n``Jupyter: Create Interactive Window``.\n[Open Interactive Window](command:jupyter.createnewinteractive)", + "walkthrough.step.python.dataScienceLearnMore.title": "Find out more!", + "walkthrough.step.python.dataScienceLearnMore.description": "📒 Take a look into the [Jupyter extension](command:workbench.extensions.search?\"ms-toolsai.jupyter\") features, by looking for \"Jupyter\" in the [Command Palette](command:workbench.action.showCommands). \n 🏃🏻 Find out more features in our [Tutorials](https://aka.ms/AAdjzpd). \n[Learn more](https://aka.ms/AAdar6q)", + "walkthrough.step.python.createPythonFile.altText": "Open a Python file or a folder with a Python project.", + "walkthrough.step.python.selectInterpreter.altText": "Selecting a Python interpreter from the status bar", + "walkthrough.step.python.createEnvironment.altText": "Creating a Python environment from the Command Palette", + "walkthrough.step.python.runAndDebug.altText": "How to run and debug in VS Code with F5 or the play button on the top right.", + "walkthrough.step.python.learnMoreWithDS.altText": "Image representing our documentation page and mailing list resources.", + "walkthrough.step.python.installJupyterExt.altText": "Creating a new Jupyter notebook", + "walkthrough.step.python.createNewNotebook.altText": "Creating a new Jupyter notebook", + "walkthrough.step.python.openInteractiveWindow.altText": "Opening Python interactive window", + "walkthrough.step.python.dataScienceLearnMore.altText": "Image representing our documentation page and mailing list resources." } diff --git a/package.nls.ko-kr.json b/package.nls.ko-kr.json deleted file mode 100644 index 5309a9b07b81..000000000000 --- a/package.nls.ko-kr.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "python.command.python.sortImports.title": "Import문 정렬", - "python.command.python.startREPL.title": "REPL 시작", - "python.command.python.buildWorkspaceSymbols.title": "작업 영역 기호 빌드", - "python.command.python.runtests.title": "모든 단위 테스트 실행", - "python.command.python.debugtests.title": "모든 단위 테스트 디버그", - "python.command.python.execInTerminal.title": "터미널에서 Python 파일 실행", - "python.command.python.setInterpreter.title": "인터프리터 선택", - "python.command.python.refactorExtractVariable.title": "변수 추출", - "python.command.python.refactorExtractMethod.title": "메서드 추출", - "python.command.python.viewTestOutput.title": "단위 테스트 결과 보기", - "python.command.python.selectAndRunTestMethod.title": "단위 테스트 메서드 실행 ...", - "python.command.python.selectAndDebugTestMethod.title": "단위 테스트 메서드 디버그 ...", - "python.command.python.selectAndRunTestFile.title": "단위 테스트 파일 실행 ...", - "python.command.python.runCurrentTestFile.title": "현재 단위 테스트 파일 실행", - "python.command.python.runFailedTests.title": "실패한 단위 테스트 실행", - "python.command.python.execSelectionInTerminal.title": "Python 터미널에서 선택 영역/줄 실행", - "python.command.python.execSelectionInDjangoShell.title": "Django 셸에서 선택 영역/줄 실행", - "python.command.python.goToPythonObject.title": " Python 객체로 이동", - "python.snippet.launch.standard.label": "Python: Current File", - "python.snippet.launch.module.label": "Python: 모듈", - "python.snippet.launch.django.label": "Python: Django", - "python.snippet.launch.flask.label": "Python: Flask", - "python.snippet.launch.pyramid.label": "Python: Pyramid 응용 프로그램", - "python.snippet.launch.attach.label": "Python: 연결" -} diff --git a/package.nls.nl.json b/package.nls.nl.json deleted file mode 100644 index ba4968e78535..000000000000 --- a/package.nls.nl.json +++ /dev/null @@ -1,153 +0,0 @@ -{ - "python.command.python.sortImports.title": "Import sorteren", - "python.command.python.startREPL.title": "REPL starten", - "python.command.python.createTerminal.title": "Terminal aanmaken", - "python.command.python.buildWorkspaceSymbols.title": "Werkruimte-symbolen aanmaken", - "python.command.python.runtests.title": "Alle unittests uitvoeren", - "python.command.python.debugtests.title": "Alle unittests debuggen", - "python.command.python.execInTerminal.title": "Python-bestand in terminal uitvoeren", - "python.command.python.setInterpreter.title": "Interpreter selecteren", - "python.command.python.refactorExtractVariable.title": "Variabelen selecteren", - "python.command.python.refactorExtractMethod.title": "Methode selecteren", - "python.command.python.viewTestOutput.title": "Unittest-resultaat laten zien", - "python.command.python.selectAndRunTestMethod.title": "Unittest-methode uitvoeren ...", - "python.command.python.selectAndDebugTestMethod.title": "Unittest-methode debuggen ...", - "python.command.python.selectAndRunTestFile.title": "Unittest-bestand uitvoeren ...", - "python.command.python.runCurrentTestFile.title": "Huidige unittest-bestand uitvoeren", - "python.command.python.runFailedTests.title": "Gefaalde unittests uitvoeren", - "python.command.python.discoverTests.title": "Unittests doorzoeken", - "python.command.python.execSelectionInTerminal.title": "Selectie/rij in Python-terminal uitvoeren", - "python.command.python.execSelectionInDjangoShell.title": "Selectie/rij in Django-shell uitvoeren", - "python.command.python.goToPythonObject.title": "Naar Python-object gaan", - "python.command.python.setLinter.title": "Linter selecteren", - "python.command.python.enableLinting.title": "Linting activeren", - "python.command.python.runLinting.title": "Linting uitvoeren", - "python.command.python.datascience.runallcells.title": "Alle cellen uitvoeren", - "python.command.python.datascience.runcurrentcell.title": "Huidige cel uitvoeren", - "python.command.python.datascience.runcurrentcelladvance.title": "Huidige cel uitvoeren en doorgaan", - "python.command.python.datascience.runcell.title": "Cel uitvoeren", - "python.command.python.datascience.selectjupyteruri.title": "Jupyter-server-URI specificeren", - "python.command.python.datascience.importnotebook.title": "Jupyter-notebook importeren", - "python.command.python.datascience.importnotebookonfile.title": "Jupyter-notebook importeren", - "python.command.python.enableSourceMapSupport.title": "Bronkaartondersteuning voor extensie-debugging inschakelen", - "python.command.python.datascience.exportoutputasnotebook.title": "Interactief Python-venster als Jupyter-notebook exporteren", - "python.command.python.datascience.exportfileasnotebook.title": "Huidige Python-bestand als Jupyter-notebook exporteren", - "python.command.python.datascience.exportfileandoutputasnotebook.title": "Huidige Python-bestand exporteren en outputten als Jupyter-notebook", - "python.command.python.datascience.undocells.title": "Laatste interactieve Python-actie ongedaan maken", - "python.command.python.datascience.redocells.title": "Laatste interactieve Python-actie opnieuw uitvoeren", - "python.command.python.datascience.removeallcells.title": "Alle interactieve Python-cellen verwijderen", - "python.command.python.datascience.interruptkernel.title": "IPython-kernel onderbreken", - "python.command.python.datascience.restartkernel.title": "IPython-kernel herstarten", - "python.command.python.datascience.expandallcells.title": "Alle interactieve Python-vensters openen", - "python.command.python.datascience.collapseallcells.title": "Alle interactieve Python-vensters sluiten", - "python.snippet.launch.standard.label": "Python: Huidige bestand", - "python.snippet.launch.module.label": "Python: Module", - "python.snippet.launch.django.label": "Python: Django", - "python.snippet.launch.flask.label": "Python: Flask", - "python.snippet.launch.pyramid.label": "Python: Pyramid-applicatie", - "python.snippet.launch.attach.label": "Python: aankoppelen", - "ExtensionSurveyBanner.bannerLabelYes": "Ja, neem nu deel aan het onderzoek", - "ExtensionSurveyBanner.bannerLabelNo": "Nee, bedankt", - "LanguageService.lsFailedToStart": "We zijn een probleem tegengekomen bij het starten van de language server. Aan het terugschakelen naar het alternatief, Jedi. Bekijk het weergavepaneel voor details.", - "LanguageService.lsFailedToDownload": "We zijn een probleem tegengekomen bij het downloaden van de language server. Aan het terugschakelen naar het alternatief, Jedi. Bekijk het weergavepaneel voor details.", - "LanguageService.lsFailedToExtract": "We zijn een probleem tegengekomen bij het uitpakken van de language server. Aan het terugschakelen naar het alternatief, Jedi. Bekijk het weergavepaneel voor details.", - "DataScience.unknownMimeTypeFormat": "Mime type {0} wordt momenteel niet ondersteund.", - "DataScience.historyTitle": "Interactieve Python", - "DataScience.badWebPanelFormatString": "

{0} is geen geldige bestandsnaam

", - "DataScience.sessionDisposed": "Kan code niet uitvoeren, de sessie is gesloten.", - "DataScience.exportDialogTitle": "Exporteren naar Jupyter-notebook", - "DataScience.exportDialogFilter": "Jupyter-notebooks", - "DataScience.exportDialogComplete": "Notebook geschreven naar {0}", - "DataScience.exportDialogFailed": "Niet gelukt om te exporteren naar Jupyter-notebook. {0}", - "DataScience.exportOpenQuestion": "In browser openen", - "DataScience.collapseInputTooltip": "Invoerblok sluiten", - "DataScience.importDialogTitle": "Jupyter-notebook importeren", - "DataScience.importDialogFilter": "Jupyter-notebooks", - "DataScience.notebookCheckForImportYes": "Importeer", - "DataScience.notebookCheckForImportNo": "Later", - "DataScience.notebookCheckForImportDontAskAgain": "Niet opnieuw vragen", - "DataScience.notebookCheckForImportTitle": "Wil je het Jupyter-notebook importeren als Python-code?", - "DataScience.jupyterNbConvertNotSupported": "Notebooks importeren vereist Jupyter-nbconvert geinstalleerd.", - "DataScience.jupyterLaunchTimedOut": "Het is niet gelukt de Jupyter-notebook-server op tijd te starten", - "DataScience.jupyterLaunchNoURL": "Het is niet gelukt de URL van de gestarte Jupyter-notebook-server te vinden", - "DataScience.pythonInteractiveHelpLink": "Krijg meer hulp", - "DataScience.importingFormat": "Aan het importeren {0}", - "DataScience.startingJupyter": "Jupyter-server starten", - "DataScience.connectingToJupyter": "Met Jupyter-server verbinden", - "Interpreters.RefreshingInterpreters": "Python-Interpreters verversen", - "Interpreters.LoadingInterpreters": "Python-Interpreters laden", - "DataScience.restartKernelMessage": "Wil je de IPython-kernel herstarten? Alle variabelen zullen verloren gaan.", - "DataScience.restartKernelMessageYes": "Herstarten", - "DataScience.restartKernelMessageNo": "Annuleren", - "DataScienceSurveyBanner.bannerMessage": "Zou je alsjeblieft 2 minuten kunnen nemen om ons te vertellen hoe de Python-data-science-functionaliteiten voor jou werkt?", - "DataScienceSurveyBanner.bannerLabelYes": "Ja, neem nu deel aan het onderzoek", - "DataScienceSurveyBanner.bannerLabelNo": "Nee, bedankt", - "DataScience.restartingKernelStatus": "IPython-Kernel herstarten", - "DataScience.executingCode": "Cel aan het uitvoeren", - "DataScience.collapseAll": "Alle cel-invoeren sluiten", - "DataScience.expandAll": "Alle cel-invoeren openen", - "DataScience.export": "Als Jupyter-Notebook exporteren", - "DataScience.restartServer": "IPython-kernel herstarten", - "DataScience.undo": "Herhaal", - "DataScience.redo": "Opnieuw uitvoeren", - "DataScience.clearAll": "Alle cellen verwijderen", - "DataScience.pythonVersionHeader": "Python versie:", - "DataScience.pythonRestartHeader": "Kernel herstart:", - "Linter.InstalledButNotEnabled": "Linter {0} is geinstalleerd maar niet ingeschakeld.", - "Linter.replaceWithSelectedLinter": "Meerdere linters zijn ingeschakeld in de instellingen. Vervangen met '{0}'?", - "DataScience.jupyterNotebookFailure": "Jupyter-notebook kon niet starten. \r\n{0}", - "DataScience.jupyterNotebookConnectFailed": "Verbinden met Jupiter-notebook is niet gelukt. \r\n{0}\r\n{1}", - "DataScience.notebookVersionFormat": "Jupyter-notebook versie: {0}", - "DataScience.jupyterKernelSpecNotFound": "Kan geen Jupyter-kernel-spec aanmaken en er zijn er geen beschikbaar voor gebruik", - "diagnostics.warnSourceMaps": "Bronkaartondersteuning is ingeschakeld in de Python-extensie, dit zal een ongunstige impact hebben op de uitvoering van de extensie.", - "diagnostics.disableSourceMaps": "Bronkaartondersteuning uitschakelen", - "diagnostics.warnBeforeEnablingSourceMaps": "Bronkaartondersteuning inschakelen in de Python-extensie zal een ongunstige impact hebben op de uitvoering van de extensie.", - "diagnostics.enableSourceMapsAndReloadVSC": "Venster inschakelen en herladen", - "diagnostics.lsNotSupported": "Uw besturingssysteem voldoet niet aan de minimumeisen van de language server. Aan het terugschakelen naar het alternatief, Jedi.", - "DataScience.interruptKernel": "IPython-kernel onderbreken", - "DataScience.exportingFormat": "Aan het exporteren {0}", - "DataScience.exportCancel": "Annuleren", - "Common.canceled": "Geannuleerd", - "DataScience.importChangeDirectoryComment": "{0} De werkmap van de werkruimte root naar de ipynb-bestandslocatie veranderen. Schakel deze toevoeging uit met de instelling DataScience.changeDirOnImportExport", - "DataScience.exportChangeDirectoryComment": "# De map wijzigen naar de VSCode-werktuimte root zodat de relatieve pad-ladingen correct werken. Schakel deze toevoeging uit met de instelling DataScience.changeDirOnImportExport", - "DataScience.interruptKernelStatus": "IPython-kernel onderbreken", - "DataScience.restartKernelAfterInterruptMessage": "Het onderbreken van de kernel duurde te lang. Wil je de kernel in plaats daarvan herstarten? Alle variabelen zullen verloren gaan.", - "DataScience.pythonInterruptFailedHeader": "Toetsenbord-interrupt liet de kernel crashen. Kernel herstart.", - "DataScience.sysInfoURILabel": "Jupyter-server URI: ", - "Common.loadingPythonExtension": "Python-extensie aan het laden...", - "debug.selectConfigurationTitle": "Een debug-configuratie selecteren", - "debug.selectConfigurationPlaceholder": "Debug-configuratie", - "debug.debugFileConfigurationLabel": "Python-bestand", - "debug.debugFileConfigurationDescription": "Python-bestand debuggen", - "debug.debugModuleConfigurationLabel": "Module", - "debug.debugModuleConfigurationDescription": "Python module/package debuggen", - "debug.remoteAttachConfigurationLabel": "Extern aankoppelen", - "debug.remoteAttachConfigurationDescription": "Een externe Python-applicatie debuggen", - "debug.debugDjangoConfigurationLabel": "Django", - "debug.debugDjangoConfigurationDescription": "Web-applicatie", - "debug.debugFlaskConfigurationLabel": "Flask", - "debug.debugFlaskConfigurationDescription": "Web-applicatie", - "debug.debugPyramidConfigurationLabel": "Pyramid", - "debug.debugPyramidConfigurationDescription": "Web-applicatie", - "debug.djangoEnterManagePyPathTitle": "Django debuggen", - "debug.djangoEnterManagePyPathPrompt": "Voer een pad in naar manage.py ('${workspaceFolderToken}' verwijzen naar de root van de huidige werkruimtemap)", - "debug.djangoEnterManagePyPathInvalidFilePathError": "Voer een geldig Python-bestandspad in", - "debug.flaskEnterAppPathOrNamePathTitle": "Flask debuggen", - "debug.flaskEnterAppPathOrNamePathPrompt": "Voer een pad in naar een applicatie, bijvoorbeeld 'app.py' of 'app'", - "debug.flaskEnterAppPathOrNamePathInvalidNameError": "Voer een geldige naam in", - "debug.moduleEnterModuleTitle": "Module debuggen", - "debug.moduleEnterModulePrompt": "Voer Python module/package naam in", - "debug.moduleEnterModuleInvalidNameError": "Voer een geldige naam in", - "debug.pyramidEnterDevelopmentIniPathTitle": "Pyramid debuggen", - "debug.pyramidEnterDevelopmentIniPathPrompt": "`Voer een pad in naar development.ini ('${workspaceFolderToken}' verwijzen naar de root van de huidige werkruimtemap)`", - "debug.pyramidEnterDevelopmentIniPathInvalidFilePathError": "Voer een geldig bestandspad in", - "debug.attachRemotePortTitle": "Extern debuggen", - "debug.attachRemotePortPrompt": "Voer een port-nummer in", - "debug.attachRemotePortValidationError": "Voer een geldig port-nummer in", - "debug.attachRemoteHostTitle": "Extern debuggen", - "debug.attachRemoteHostPrompt": "Voer een hostname of IP-adres in", - "debug.attachRemoteHostValidationError": "Voer een geldige hostname of IP-adres in", - "Testing.testErrorDiagnosticMessage": "Error", - "Testing.testFailDiagnosticMessage": "Mislukt", - "Testing.testSkippedDiagnosticMessage": "Overgeslagen" -} diff --git a/package.nls.pl.json b/package.nls.pl.json deleted file mode 100644 index 0e6c5a0ec3d7..000000000000 --- a/package.nls.pl.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "python.command.python.sortImports.title": "Sortuj importy", - "python.command.python.startREPL.title": "Uruchom REPL", - "python.command.python.createTerminal.title": "Otwórz Terminal", - "python.command.python.buildWorkspaceSymbols.title": "Zbuduj symbole dla przestrzeni roboczej", - "python.command.python.runtests.title": "Uruchom wszystkie testy jednostkowe", - "python.command.python.debugtests.title": "Debuguj wszystkie testy jednostkowe", - "python.command.python.execInTerminal.title": "Uruchom plik pythonowy w terminalu", - "python.command.python.setInterpreter.title": "Wybierz wersję interpretera", - "python.command.python.refactorExtractVariable.title": "Wyodrębnij zmienną", - "python.command.python.refactorExtractMethod.title": "Wyodrębnij metodę", - "python.command.python.viewOutput.title": "Pokaż wyniki", - "python.command.python.viewTestOutput.title": "Pokaż wyniki testów jednostkowych", - "python.command.python.selectAndRunTestMethod.title": "Uruchom metodę testów jednostkowych ...", - "python.command.python.selectAndDebugTestMethod.title": "Debuguj metodę testów jednostkowych ...", - "python.command.python.selectAndRunTestFile.title": "Uruchom plik z testami jednostkowymi ...", - "python.command.python.runCurrentTestFile.title": "Uruchom bieżący plik z testami jednostkowymi", - "python.command.python.runFailedTests.title": "Uruchom testy jednostkowe, które się nie powiodły", - "python.command.python.discoverTests.title": "Wyszukaj testy jednostkowe", - "python.command.python.configureTests.title": "Konfiguruj testy jednostkowe", - "python.command.python.execSelectionInTerminal.title": "Uruchom zaznaczony obszar w interpreterze Pythona", - "python.command.python.execSelectionInDjangoShell.title": "Uruchom zaznaczony obszar w powłoce Django", - "python.command.python.goToPythonObject.title": "Idź do obiektu pythonowego", - "python.command.python.setLinter.title": "Wybierz linter", - "python.command.python.enableLinting.title": "Włącz linting", - "python.command.python.runLinting.title": "Uruchom linting", - "python.command.python.datascience.runallcells.title": "Uruchom wszystkie komórki", - "python.command.python.datascience.runcurrentcell.title": "Uruchom bieżącą komórkę", - "python.command.python.datascience.runcurrentcelladvance.title": "Uruchom bieżące komórki i pokaż", - "python.command.python.datascience.execSelectionInteractive.title": "Uruchom zaznaczony obszar w oknie IPythona", - "python.command.python.datascience.runcell.title": "Uruchom komórki", - "python.command.python.datascience.selectjupyteruri.title": "Podaj identyfikator URI serwera Jupyter", - "python.command.python.datascience.importnotebook.title": "Importuj notatnik Jupyter", - "python.command.python.datascience.importnotebookonfile.title": "Importuj notatnik Jupyter", - "python.command.python.enableSourceMapSupport.title": "Włącz obsługę map źródłowych do debugowania rozszerzeń", - "python.command.python.datascience.exportoutputasnotebook.title": "Eksportuj okno IPython jako notatnik Jupyter", - "python.command.python.datascience.exportfileasnotebook.title": "Eksportuj bieżący plik Pythona jako notatnik Jupytera", - "python.command.python.datascience.exportfileandoutputasnotebook.title": "Eksportuj bieżący plik Pythona i jego wyniki jako notatnik Jupytera", - "python.command.python.datascience.undocells.title": "Cofnij ostatnią akcję IPythona", - "python.command.python.datascience.redocells.title": "Ponów ostatnią akcję IPythona", - "python.command.python.datascience.removeallcells.title": "Usuń wszystkie komórki IPythona", - "python.command.python.datascience.interruptkernel.title": "Przerwij IPython Kernel", - "python.command.python.datascience.restartkernel.title": "Restartuj IPython Kernel", - "python.command.python.datascience.expandallcells.title": "Rozwiń wszystkie komórki IPythona", - "python.command.python.datascience.collapseallcells.title": "Zwiń wszystkie komórki IPythona" -} diff --git a/package.nls.pt-br.json b/package.nls.pt-br.json deleted file mode 100644 index 1acc94053ca0..000000000000 --- a/package.nls.pt-br.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "python.command.python.sortImports.title": "Ordenar Importações", - "python.command.python.startREPL.title": "Iniciar REPL", - "python.command.python.createTerminal.title": "Criar Terminal", - "python.command.python.buildWorkspaceSymbols.title": "Construir Símbolos da Área de Trabalho", - "python.command.python.runtests.title": "Executar Todos os Testes Unitários", - "python.command.python.debugtests.title": "Depurar Todos os Testes Unitários", - "python.command.python.execInTerminal.title": "Executar Arquivo no Terminal", - "python.command.python.setInterpreter.title": "Selecionar Interpretador", - "python.command.python.refactorExtractVariable.title": "Extrair Variável", - "python.command.python.refactorExtractMethod.title": "Extrair Método", - "python.command.python.viewTestOutput.title": "Exibir Resultados dos Testes Unitários", - "python.command.python.selectAndRunTestMethod.title": "Executar Testes Unitários do Método ...", - "python.command.python.selectAndDebugTestMethod.title": "Depurar Testes Unitários do Método ...", - "python.command.python.selectAndRunTestFile.title": "Executar Arquivo de Testes Unitários ...", - "python.command.python.runCurrentTestFile.title": "Executar o Arquivo de Testes Unitários Atual", - "python.command.python.runFailedTests.title": "Executar Testes Unitários com Falhas", - "python.command.python.discoverTests.title": "Descobrir Testes Unitários", - "python.command.python.execSelectionInTerminal.title": "Executar Seleção/Linha no Terminal", - "python.command.python.execSelectionInDjangoShell.title": "Executar Seleção/Linha no Django Shell", - "python.command.python.goToPythonObject.title": "Ir para Objeto Python", - "python.command.python.setLinter.title": "Selecionar Linter", - "python.command.python.enableLinting.title": "Habilitar Linting", - "python.command.python.runLinting.title": "Executar Linting", - "python.snippet.launch.standard.label": "Python: Arquivo Atual", - "python.snippet.launch.module.label": "Python: Módulo", - "python.snippet.launch.django.label": "Python: Django", - "python.snippet.launch.flask.label": "Python: Flask", - "python.snippet.launch.pyramid.label": "Python: Aplicação Pyramid", - "python.snippet.launch.attach.label": "Python: Anexar" -} diff --git a/package.nls.ru.json b/package.nls.ru.json deleted file mode 100644 index 9b6b0a5661da..000000000000 --- a/package.nls.ru.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "python.command.python.sortImports.title": "Отсортировать Imports", - "python.command.python.startREPL.title": "Открыть REPL", - "python.command.python.buildWorkspaceSymbols.title": "Собрать символы рабочего пространства", - "python.command.python.runtests.title": "Запустить все тесты", - "python.command.python.debugtests.title": "Запустить все тесты под отладчиком", - "python.command.python.execInTerminal.title": "Выполнить файл в консоли", - "python.command.python.setInterpreter.title": "Выбрать интерпретатор", - "python.command.python.refactorExtractVariable.title": "Извлечь в переменную", - "python.command.python.refactorExtractMethod.title": "Извлечь в метод", - "python.command.python.viewTestOutput.title": "Показать вывод теста", - "python.command.python.selectAndRunTestMethod.title": "Запусть тестовый метод...", - "python.command.python.selectAndDebugTestMethod.title": "Отладить тестовый метод...", - "python.command.python.selectAndRunTestFile.title": "Запустить тестовый файл...", - "python.command.python.runCurrentTestFile.title": "Запустить текущий тестовый файл", - "python.command.python.runFailedTests.title": "Запустить непрошедшие тесты", - "python.command.python.discoverTests.title": "Обнаружить тесты", - "python.command.python.execSelectionInTerminal.title": "Выполнить выбранный текст или текущую строку в консоли", - "python.command.python.execSelectionInDjangoShell.title": "Выполнить выбранный текст или текущую строку в оболочке Django", - "python.command.python.goToPythonObject.title": "Перейти к объекту Python", - "python.command.python.setLinter.title": "Выбрать анализатор кода", - "python.command.python.enableLinting.title": "Включить анализатор кода", - "python.command.python.runLinting.title": "Выполнить анализ кода", - "python.snippet.launch.standard.label": "Python: Текущий файл", - "python.snippet.launch.module.label": "Python: Модуль", - "python.snippet.launch.django.label": "Python: Django", - "python.snippet.launch.flask.label": "Python: Flask", - "python.snippet.launch.pyramid.label": "Python: Приложение Pyramid", - "python.snippet.launch.attach.label": "Python: Подключить отладчик", - "ExtensionSurveyBanner.bannerLabelYes": "Да, открыть опрос сейчас", - "ExtensionSurveyBanner.bannerLabelNo": "Нет, спасибо", - "ExtensionSurveyBanner.maybeLater": "Может быть, позже", - "ExtensionSurveyBanner.bannerMessage": "Не могли бы вы потратить пару минут на опрос о языковом сервере Pylance?", - "Pylance.proposePylanceMessage": "Попробуйте новый языковый сервер для Python от Microsoft: Pylance! Установите расширение Pylance.", - "Pylance.tryItNow": "Да, хочу", - "Pylance.remindMeLater": "Напомните позже", - "Pylance.installPylanceMessage": "Расширение Pylance не установлено. Нажмите Да чтобы открыть страницу установки Pylance.", - "Pylance.pylanceNotInstalledMessage": "Расширение Pylance не установлено.", - "Pylance.pylanceInstalledReloadPromptMessage": "Расширение Pylance установлено. Перезагрузить окно для его активации?", - "LanguageService.reloadAfterLanguageServerChange": "Пожалуйста, перезагрузите окно после смены типа языкового сервера." -} diff --git a/package.nls.tr.json b/package.nls.tr.json deleted file mode 100644 index 0e648bb38fdf..000000000000 --- a/package.nls.tr.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "python.command.python.sortImports.title": "Import İfadelerini Sırala", - "python.command.python.startREPL.title": "REPL Başlat", - "python.command.python.createTerminal.title": "Terminal Oluştur", - "python.command.python.buildWorkspaceSymbols.title": "Çalışma Alanındaki Sembolleri Derle", - "python.command.python.runtests.title": "Testleri Çalıştır", - "python.command.python.debugtests.title": "Testleri Debug Et", - "python.command.python.execInTerminal.title": "Terminalde Çalıştır", - "python.command.python.setInterpreter.title": "Bir Interpreter Seçin", - "python.command.python.refactorExtractVariable.title": "Değişken Çıkar", - "python.command.python.refactorExtractMethod.title": "Metot Çıkar", - "python.command.python.viewTestOutput.title": "Test Çıktısını Görüntüle", - "python.command.python.selectAndRunTestMethod.title": "Test Metodu Çalıştır", - "python.command.python.selectAndDebugTestMethod.title": "Test Metodu Debug Et", - "python.command.python.selectAndRunTestFile.title": "Bir Test Dosyası Seç ve Çalıştır", - "python.command.python.runCurrentTestFile.title": "Aktif Test Dosyasını Çalıştır", - "python.command.python.runFailedTests.title": "Başarısız Testleri Çalıştır", - "python.command.python.discoverTests.title": "Testleri Keşfet", - "python.command.python.discoveringTests.title": "Testler Keşfediliyor...", - "python.command.python.execSelectionInTerminal.title": "Seçimi/Satırı Terminalde Çalıştır", - "python.command.python.execSelectionInDjangoShell.title": "Seçimi/Satırı Django Shell'inde Çalıştır", - "python.command.python.goToPythonObject.title": "Python Nesnesine Git", - "python.command.python.setLinter.title": "Bir Linter Seç", - "python.command.python.enableLinting.title": "Linting'i Aktifleştir", - "python.command.python.runLinting.title": "Linter Çalıştır", - "python.snippet.launch.standard.label": "Python: Geçerli Dosya", - "python.snippet.launch.module.label": "Python: Modül", - "python.snippet.launch.module.default": "modül-adını-yazın", - "python.snippet.launch.attach.label": "Python: Remote Attach", - "python.snippet.launch.django.label": "Python: Django", - "python.snippet.launch.flask.label": "Python: Flask", - "python.snippet.launch.pyramid.label": "Python: Pyramid Uygulaması" -} diff --git a/package.nls.zh-cn.json b/package.nls.zh-cn.json deleted file mode 100644 index aaf5460a3c55..000000000000 --- a/package.nls.zh-cn.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "python.command.python.sortImports.title": "排序 import 语句", - "python.command.python.startREPL.title": "启动 REPL", - "python.command.python.createTerminal.title": "创建终端", - "python.command.python.buildWorkspaceSymbols.title": "构建工作区符号", - "python.command.python.runtests.title": "运行所有单元测试", - "python.command.python.debugtests.title": "调试所有单元测试", - "python.command.python.execInTerminal.title": "在终端中运行 Python 文件", - "python.command.python.setInterpreter.title": "选择解析器", - "python.command.python.refactorExtractVariable.title": "提取变量", - "python.command.python.refactorExtractMethod.title": "提取方法", - "python.command.python.viewTestOutput.title": "显示单元测试输出", - "python.command.python.selectAndRunTestMethod.title": "运行单元测试方法...", - "python.command.python.selectAndDebugTestMethod.title": "调试单元测试方法...", - "python.command.python.selectAndRunTestFile.title": "运行单元测试文件...", - "python.command.python.runCurrentTestFile.title": "运行当前单元测试文件", - "python.command.python.runFailedTests.title": "运行失败的单元测试", - "python.command.python.discoverTests.title": "检测单元测试", - "python.command.python.execSelectionInTerminal.title": "在 Python 终端中运行选定内容/行", - "python.command.python.execSelectionInDjangoShell.title": "在 Django Shell 中运行选定内容/行", - "python.command.python.goToPythonObject.title": "转到 Python 对象", - "python.command.python.setLinter.title": "选择 Linter 插件", - "python.command.python.enableLinting.title": "启用 Linting", - "python.command.python.runLinting.title": "运行 Linting", - "python.snippet.launch.standard.label": "Python: 当前文件", - "python.snippet.launch.module.label": "Python: 模块", - "python.snippet.launch.django.label": "Python: Django", - "python.snippet.launch.flask.label": "Python: Flask", - "python.snippet.launch.pyramid.label": "Python: Pyramid 应用", - "python.snippet.launch.attach.label": "Python: 附加" -} diff --git a/package.nls.zh-tw.json b/package.nls.zh-tw.json deleted file mode 100644 index 96eb10983a9a..000000000000 --- a/package.nls.zh-tw.json +++ /dev/null @@ -1,159 +0,0 @@ -{ - "python.command.python.sortImports.title": "排序 Import 語句", - "python.command.python.startREPL.title": "啟動 REPL", - "python.command.python.createTerminal.title": "建立終端機", - "python.command.python.buildWorkspaceSymbols.title": "建構工作區符號", - "python.command.python.runtests.title": "執行所有單元測試", - "python.command.python.debugtests.title": "偵錯所有單元測試", - "python.command.python.execInTerminal.title": "在終端機中執行 Python 檔案", - "python.command.python.setInterpreter.title": "選擇直譯器", - "python.command.python.refactorExtractVariable.title": "提取變數", - "python.command.python.refactorExtractMethod.title": "提取方法", - "python.command.python.viewTestOutput.title": "顯示單元測試輸出", - "python.command.python.selectAndRunTestMethod.title": "執行單元測試方法…", - "python.command.python.selectAndDebugTestMethod.title": "偵錯單元測試方法…", - "python.command.python.selectAndRunTestFile.title": "執行單元測試檔案…", - "python.command.python.runCurrentTestFile.title": "執行目前單元測試檔案", - "python.command.python.runFailedTests.title": "執行失敗的單元測試", - "python.command.python.execSelectionInTerminal.title": "在 Python 終端機中執行選定內容/行", - "python.command.python.execSelectionInDjangoShell.title": "在 Django Shell 中執行選定內容/行", - "python.command.python.goToPythonObject.title": "跳至 Python 物件", - "python.command.python.setLinter.title": "選擇 Linter", - "python.command.python.enableLinting.title": "啟用 Linting", - "python.command.python.runLinting.title": "執行 Linting", - "python.snippet.launch.standard.label": "Python: 目前檔案", - "python.snippet.launch.module.label": "Python: 模組", - "python.snippet.launch.django.label": "Python: Django", - "python.snippet.launch.flask.label": "Python: Flask", - "python.snippet.launch.pyramid.label": "Python: Pyramid 程式", - "python.snippet.launch.attach.label": "Python: 附加", - "python.command.python.discoverTests.title": "探索 Unit 測試項目", - "python.command.python.switchOffInsidersChannel.title": "切換至預設頻道", - "python.command.python.switchToDailyChannel.title": "切換至 Insiders 每日頻道", - "python.command.python.switchToWeeklyChannel.title": "切換至 Insiders 每週頻道", - "python.command.python.viewOutput.title": "顯示輸出", - "python.command.python.datascience.viewJupyterOutput.title": "顯示 Jupyter 輸出", - "python.command.python.viewLanguageServerOutput.title": "顯示語言伺服器輸出", - "python.command.python.discoveringTests.title": "正在探索...", - "python.command.python.stopTests.title": "停止", - "python.command.python.configureTests.title": "設定測試", - "python.command.python.enableSourceMapSupport.title": "啟用供偵錯延伸模組的原始碼映射 (Source Map) 支援", - "python.command.python.analysis.clearCache.title": "清除模組分析快取", - "python.snippet.launch.module.default": "請輸入-模組-名稱", - "python.snippet.launch.attachpid.label": "Python: 使用處理程序 ID 連結", - "LanguageService.lsFailedToStart": "啟動語言伺服器時遇到問題。改回使用替代方案 \"Jedi\"。請檢查 Python 輸出面板以取得更多資訊。", - "LanguageService.lsFailedToDownload": "下載語言伺服器時遇到問題。改回使用替代方案 \"Jedi\"。請檢查 Python 輸出面板以取得更多資訊。", - "LanguageService.lsFailedToExtract": "擷取語言伺服器時遇到問題。改回使用替代方案 \"Jedi\"。請檢查 Python 輸出面板以取得更多資訊。", - "Experiments.inGroup": "使用者屬於 \"{0}\" 實驗性群組", - "Interpreters.RefreshingInterpreters": "正在重新整理 Python 解譯器", - "Interpreters.LoadingInterpreters": "正在載入 Python 解譯器", - "Interpreters.condaInheritEnvMessage": "我們發覺到您在使用 conda 環境。如果你在整合式終端器中使用這個環境時遇到問題,建議您讓 Python 延伸模組變更使用者設定中的 \"terminal.integrated.inheritEnv\" 為 false。", - "Logging.CurrentWorkingDirectory": "cwd:", - "Common.doNotShowAgain": "不再顯示", - "Common.reload": "重新載入", - "Common.moreInfo": "更多資訊", - "OutputChannelNames.languageServer": "Python 語言伺服器", - "OutputChannelNames.python": "Python", - "OutputChannelNames.pythonTest": "Python 測試記錄", - "OutputChannelNames.jupyter": "Jupyter", - "ExtensionSurveyBanner.bannerMessage": "請問您是否可以用兩分鐘的時間,告訴我們 Python 延伸模組在您環境中的運作情況?", - "ExtensionSurveyBanner.bannerLabelYes": "是,現在填寫調查", - "ExtensionSurveyBanner.bannerLabelNo": "不了,謝謝", - "ExtensionSurveyBanner.maybeLater": "等一下", - "ExtensionChannels.installingInsidersMessage": "正在安裝 Insiders... ", - "ExtensionChannels.installingStableMessage": "正在安裝穩定版... ", - "ExtensionChannels.installationCompleteMessage": "完成。", - "ExtensionChannels.downloadingInsidersMessage": "正在下載 Insiders 延伸模組... ", - "ExtensionChannels.yesWeekly": "是,每週", - "ExtensionChannels.yesDaily": "是,每天", - "ExtensionChannels.promptMessage": "我們發覺到您在使用 Visual Studio Code Insiders。請問您是否想使用 Python 延伸模組的 Insiders 組建?", - "ExtensionChannels.reloadToUseInsidersMessage": "請重新載入 Visual Studio Code 以使用 Python 延伸模組的 Insiders 組建。", - "ExtensionChannels.downloadCompletedOutputMessage": "Insiders 組建下載完成。", - "ExtensionChannels.startingDownloadOutputMessage": "開始下載 Insiders 組建。", - "Interpreters.environmentPromptMessage": "We noticed a new virtual environment has been created. Do you want to select it for the workspace folder?", - "InteractiveShiftEnterBanner.bannerMessage": "Would you like to run code in the 'Python Interactive' window (an IPython console) for 'shift-enter'? Select 'No' to continue to run code in the Python Terminal. This can be changed later in settings.", - "InteractiveShiftEnterBanner.bannerLabelYes": "是", - "InteractiveShiftEnterBanner.bannerLabelNo": "否", - "Linter.enableLinter": "啟用 {0}", - "Linter.enablePylint": "您的工作區有 pylintrc 檔案。是否啟用 pylint?", - "Linter.replaceWithSelectedLinter": "設定中啟用了多個 Linter。是否用 '{0}' 取代?", - "Installer.noCondaOrPipInstaller": "選取環境中沒有可用的 Conda 或 Pip 安裝工具。", - "Installer.noPipInstaller": "選取環境中沒有可用的 Pip 安裝工具。", - "Installer.searchForHelp": "搜尋說明", - "diagnostics.warnSourceMaps": "已在 Python 延伸模組中啟用原始碼映射 (Source Map) 支援,這會降低延伸模組效能。", - "diagnostics.disableSourceMaps": "停用原始碼映射 (Source Map) 支援", - "diagnostics.warnBeforeEnablingSourceMaps": "在 Python 延伸模組中啟用原始碼映射 (Source Map) 支援會降低延伸模組效能。", - "diagnostics.enableSourceMapsAndReloadVSC": "啟用並重新載入視窗", - "diagnostics.lsNotSupported": "您的作業系統不符合 Python 語言伺服器的最低需求。改回使用替代自動完成提供者 \"Jedi\"。", - "diagnostics.invalidPythonPathInDebuggerSettings": "開始偵錯前,您需要選取 Python 解譯器。\n\n小提示:按一下狀態列的 \"選擇 Python 解譯器\"。", - "diagnostics.invalidPythonPathInDebuggerLaunch": "偵錯設定檔的 Python 路徑無效。", - "diagnostics.invalidDebuggerTypeDiagnostic": "Your launch.json file needs to be updated to change the \"pythonExperimental\" debug configurations to use the \"python\" debugger type, otherwise Python debugging may not work. Would you like to automatically update your launch.json file now?", - "diagnostics.consoleTypeDiagnostic": "Your launch.json file needs to be updated to change the console type string from \"none\" to \"internalConsole\", otherwise Python debugging may not work. Would you like to automatically update your launch.json file now?", - "diagnostics.justMyCodeDiagnostic": "Configuration \"debugStdLib\" in launch.json is no longer supported. It's recommended to replace it with \"justMyCode\", which is the exact opposite of using \"debugStdLib\". Would you like to automatically update your launch.json file to do that?", - "diagnostics.yesUpdateLaunch": "是,更新 launch.json", - "diagnostics.invalidTestSettings": "Your settings needs to be updated to change the setting \"python.unitTest.\" to \"python.testing.\", otherwise testing Python code using the extension may not work. Would you like to automatically update your settings now?", - "Common.canceled": "已取消", - "Common.cancel": "取消", - "Common.loadingPythonExtension": "正在載入 Python 延伸模組...", - "debug.selectConfigurationTitle": "選擇偵錯設定檔", - "debug.selectConfigurationPlaceholder": "偵錯設定檔", - "debug.launchJsonConfigurationsCompletionLabel": "Python", - "debug.launchJsonConfigurationsCompletionDescription": "選取 Python 偵錯設定檔", - "debug.debugFileConfigurationLabel": "Python 檔案", - "debug.debugFileConfigurationDescription": "偵錯目前使用中的 Python 檔案", - "debug.debugModuleConfigurationLabel": "模組", - "debug.debugModuleConfigurationDescription": "使用 '-m' 叫用以偵錯 Python 模組", - "debug.moduleEnterModuleTitle": "偵錯模組", - "debug.moduleEnterModulePrompt": "輸入 Python 模組 / 套件名稱", - "debug.moduleEnterModuleDefault": "輸入-模組-名稱", - "debug.moduleEnterModuleInvalidNameError": "請輸入有效的模組名稱", - "debug.remoteAttachConfigurationLabel": "遠端連結", - "debug.remoteAttachConfigurationDescription": "連結到遠端偵錯伺服器", - "debug.attachRemoteHostTitle": "遠端偵錯", - "debug.attachRemoteHostPrompt": "輸入主機名稱", - "debug.attachRemoteHostValidationError": "請輸入有效的主機名稱或 IP 位址", - "debug.attachRemotePortTitle": "遠端偵錯", - "debug.attachRemotePortPrompt": "輸入偵錯伺服器正在監聽的連線埠號。", - "debug.attachRemotePortValidationError": "請輸入有效的連線埠號。", - "debug.attachPidConfigurationLabel": "使用處理程序 ID 連結", - "debug.attachPidConfigurationDescription": "連結至本機處理程序", - "debug.debugDjangoConfigurationLabel": "Django", - "debug.debugDjangoConfigurationDescription": "執行並偵錯 Django 網路應用程式", - "debug.djangoEnterManagePyPathTitle": "偵錯 Django", - "debug.djangoEnterManagePyPathPrompt": "請輸入 manage.py 的路徑 ('${workspaceFolderToken}' 指向目前工作區資料夾的根目錄)", - "debug.djangoEnterManagePyPathInvalidFilePathError": "請輸入有效的 Python 檔案路徑", - "debug.debugFlaskConfigurationLabel": "Flask", - "debug.debugFlaskConfigurationDescription": "執行並偵錯 Flask 網路應用程式", - "debug.flaskEnterAppPathOrNamePathTitle": "偵錯 Flask", - "debug.flaskEnterAppPathOrNamePathPrompt": "請輸入應用程式路徑。例如:'app.py' 或 'app'", - "debug.flaskEnterAppPathOrNamePathInvalidNameError": "請輸入有效名稱", - "debug.debugPyramidConfigurationLabel": "Pyramid", - "debug.debugPyramidConfigurationDescription": "網路應用程式", - "debug.pyramidEnterDevelopmentIniPathTitle": "偵錯 Pyramid", - "debug.pyramidEnterDevelopmentIniPathPrompt": "`請輸入 development.ini 的路徑 ('${workspaceFolderToken}' 指向目前工作區資料夾的根目錄)`", - "debug.pyramidEnterDevelopmentIniPathInvalidFilePathError": "請輸入有效的檔案路徑", - "Testing.testErrorDiagnosticMessage": "錯誤", - "Testing.testFailDiagnosticMessage": "失敗", - "Testing.testSkippedDiagnosticMessage": "略過", - "Testing.configureTests": "設定測試框架", - "Testing.disableTests": "停用測試", - "Common.openOutputPanel": "顯示輸出", - "LanguageService.downloadFailedOutputMessage": "下載語言伺服器失敗", - "LanguageService.extractionFailedOutputMessage": "擷取語言伺服器失敗", - "LanguageService.extractionCompletedOutputMessage": "下載語言伺服器完成", - "LanguageService.extractionDoneOutputMessage": "完成", - "LanguageService.reloadVSCodeIfSeachPathHasChanged": "已為此 Python 解譯器變更搜尋路徑。請重新載入延伸模組以確保 IntelliSense 能夠正常運作", - "AttachProcess.unsupportedOS": "不支援 '{0}' 作業系統。", - "AttachProcess.attachTitle": "連結至處理程序", - "AttachProcess.selectProcessPlaceholder": "選擇要連結的處理程序", - "AttachProcess.noProcessSelected": "沒有選取的處理程序", - "AttachProcess.refreshList": "重新整理處理程序列表", - "diagnostics.updateSettings": "是,更新設定", - "Common.noIWillDoItLater": "否,我稍候再做", - "Common.notNow": "先不要", - "Common.gotIt": "懂了!", - "Interpreters.selectInterpreterTip": "小提示:您能透過按下狀態列中的 Python 版本,變更 Python 延伸模組使用的 Python 解譯器。", - "downloading.file": "正在下載 {0}...", - "downloading.file.progress": "目前 {0}{1},總共 {2} KB ({3}%)", - "products.installingModule": "正在安裝 {0}" -} diff --git a/pvsc.code-workspace b/pvsc.code-workspace deleted file mode 100644 index 45bddaa1b591..000000000000 --- a/pvsc.code-workspace +++ /dev/null @@ -1,53 +0,0 @@ -{ - "folders": [ - { - "path": ".", - "name": "vscode-python" - }, - { - "path": "pythonFiles" - }, - { - "path": "src/ipywidgets" - }, - { - "path": "../vscode-notebook-renderers", - "name": "vscode-notebook-renderers" - } - ], - "settings": { - "typescript.tsdk": "./node_modules/typescript/lib", - "search.exclude": { - "**/node_modules/**": true, - "**/.vscode test/insider/**": true, - "**/.vscode test/stable/**": true, - "**/.vscode-test/insider/**": true, - "**/.vscode-test/stable/**": true, - "**/out/**": true - } - }, - "launch": { - "configurations": [ - // This configuration allows one to debug multiple extensions at a time. - // The assumption here is that vscode-notebook-renderers is in the same folder as the python extension. - // User is expected to start the compile tasks for both extensions before using this launch config. - { - "type": "extensionHost", - "request": "launch", - "name": "Python + Renderer Extension", - "args": [ - "--enable-proposed-api", - "--extensionDevelopmentPath=${workspaceFolder:vscode-python}", - "--extensionDevelopmentPath=${workspaceFolder:vscode-notebook-renderers}" - ], - "outFiles": [ - "${workspaceFolder:vscode-python}/out/**/*.js", - "!${workspaceFolder:vscode-python}/**/node_modules**/*", - "${workspaceFolder:vscode-notebook-renderers}/out/**/*.js", - "!${workspaceFolder:vscode-notebook-renderers}/**/node_modules**/*" - ] - } - ], - "compounds": [] - } -} diff --git a/pythonExtensionApi/.eslintrc b/pythonExtensionApi/.eslintrc new file mode 100644 index 000000000000..8828c49002ed --- /dev/null +++ b/pythonExtensionApi/.eslintrc @@ -0,0 +1,11 @@ +{ + "overrides": [ + { + "files": ["**/main.d.ts"], + "rules": { + "@typescript-eslint/no-explicit-any": "off", + "padding-line-between-statements": ["error", { "blankLine": "always", "prev": "export", "next": "*" }] + } + } + ] +} diff --git a/pythonExtensionApi/.npmignore b/pythonExtensionApi/.npmignore new file mode 100644 index 000000000000..283d589ea5fe --- /dev/null +++ b/pythonExtensionApi/.npmignore @@ -0,0 +1,8 @@ +example/** +dist/ +out/**/*.map +out/**/*.tsbuildInfo +src/ +.eslintrc* +.eslintignore +tsconfig*.json diff --git a/pythonExtensionApi/LICENSE.md b/pythonExtensionApi/LICENSE.md new file mode 100644 index 000000000000..767f4076ba05 --- /dev/null +++ b/pythonExtensionApi/LICENSE.md @@ -0,0 +1,21 @@ +Copyright (c) Microsoft Corporation. All rights reserved. + +MIT License + +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. diff --git a/pythonExtensionApi/README.md b/pythonExtensionApi/README.md new file mode 100644 index 000000000000..5208d90cdfa5 --- /dev/null +++ b/pythonExtensionApi/README.md @@ -0,0 +1,55 @@ +# Python extension's API + +This npm module implements an API facade for the Python extension in VS Code. + +## Example + +First we need to define a `package.json` for the extension that wants to use the API: + +```jsonc +{ + "name": "...", + ... + // depend on the Python extension + "extensionDependencies": [ + "ms-python.python" + ], + // Depend on the Python extension facade npm module to get easier API access to the + // core extension. + "dependencies": { + "@vscode/python-extension": "...", + "@types/vscode": "..." + }, +} +``` + +Update `"@types/vscode"` to [a recent version](https://code.visualstudio.com/updates/) of VS Code, say `"^1.81.0"` for VS Code version `"1.81"`, in case there are any conflicts. + +The actual source code to get the active environment to run some script could look like this: + +```typescript +// Import the API +import { PythonExtension } from '@vscode/python-extension'; + +... + +// Load the Python extension API +const pythonApi: PythonExtension = await PythonExtension.api(); + +// This will return something like /usr/bin/python +const environmentPath = pythonApi.environments.getActiveEnvironmentPath(); + +// `environmentPath.path` carries the value of the setting. Note that this path may point to a folder and not the +// python binary. Depends entirely on how the env was created. +// E.g., `conda create -n myenv python` ensures the env has a python binary +// `conda create -n myenv` does not include a python binary. +// Also, the path specified may not be valid, use the following to get complete details for this environment if +// need be. + +const environment = await pythonApi.environments.resolveEnvironment(environmentPath); +if (environment) { + // run your script here. +} +``` + +Check out [the wiki](https://aka.ms/pythonEnvironmentApi) for many more examples and usage. diff --git a/pythonExtensionApi/SECURITY.md b/pythonExtensionApi/SECURITY.md new file mode 100644 index 000000000000..a050f362c152 --- /dev/null +++ b/pythonExtensionApi/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). + + diff --git a/pythonExtensionApi/package-lock.json b/pythonExtensionApi/package-lock.json new file mode 100644 index 000000000000..e462fc1c888a --- /dev/null +++ b/pythonExtensionApi/package-lock.json @@ -0,0 +1,157 @@ +{ + "name": "@vscode/python-extension", + "version": "1.0.6", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@vscode/python-extension", + "version": "1.0.6", + "license": "MIT", + "devDependencies": { + "@types/vscode": "^1.93.0", + "source-map": "^0.8.0-beta.0", + "typescript": "~5.2" + }, + "engines": { + "node": ">=22.17.0", + "vscode": "^1.93.0" + } + }, + "node_modules/@types/vscode": { + "version": "1.94.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.94.0.tgz", + "integrity": "sha512-UyQOIUT0pb14XSqJskYnRwD2aG0QrPVefIfrW1djR+/J4KeFQ0i1+hjZoaAmeNf3Z2jleK+R2hv+EboG/m8ruw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "dev": true + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "dev": true, + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + } + }, + "dependencies": { + "@types/vscode": { + "version": "1.94.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.94.0.tgz", + "integrity": "sha512-UyQOIUT0pb14XSqJskYnRwD2aG0QrPVefIfrW1djR+/J4KeFQ0i1+hjZoaAmeNf3Z2jleK+R2hv+EboG/m8ruw==", + "dev": true + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "dev": true + }, + "punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true + }, + "source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "dev": true, + "requires": { + "whatwg-url": "^7.0.0" + } + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + } + } +} diff --git a/pythonExtensionApi/package.json b/pythonExtensionApi/package.json new file mode 100644 index 000000000000..11e0445aa8da --- /dev/null +++ b/pythonExtensionApi/package.json @@ -0,0 +1,43 @@ +{ + "name": "@vscode/python-extension", + "description": "An API facade for the Python extension in VS Code", + "version": "1.0.6", + "author": { + "name": "Microsoft Corporation" + }, + "keywords": [ + "Python", + "VSCode", + "API" + ], + "main": "./out/main.js", + "types": "./out/main.d.ts", + "engines": { + "node": ">=22.21.1", + "vscode": "^1.93.0" + }, + "license": "MIT", + "homepage": "https://github.com/microsoft/vscode-python/tree/main/pythonExtensionApi", + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/vscode-python" + }, + "bugs": { + "url": "https://github.com/Microsoft/vscode-python/issues" + }, + "devDependencies": { + "typescript": "~5.2", + "@types/vscode": "^1.102.0", + "source-map": "^0.8.0-beta.0" + }, + "scripts": { + "prepublishOnly": "echo \"⛔ Can only publish from a secure pipeline ⛔\" && node ../build/fail", + "prepack": "npm run all:publish", + "compile": "node ./node_modules/typescript/lib/tsc.js -b ./tsconfig.json", + "clean": "node ../node_modules/rimraf/bin.js out", + "lint": "node ../node_modules/eslint/bin/eslint.js --ext ts src", + "all": "npm run clean && npm run compile", + "formatTypings": "node ../node_modules/eslint/bin/eslint.js --fix ./out/main.d.ts", + "all:publish": "git clean -xfd . && npm install && npm run compile && npm run formatTypings" + } +} diff --git a/pythonExtensionApi/src/main.ts b/pythonExtensionApi/src/main.ts new file mode 100644 index 000000000000..2173245cbb28 --- /dev/null +++ b/pythonExtensionApi/src/main.ts @@ -0,0 +1,348 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { CancellationToken, Event, Uri, WorkspaceFolder, extensions } from 'vscode'; + +/* + * Do not introduce any breaking changes to this API. + * This is the public API for other extensions to interact with this extension. + */ +export interface PythonExtension { + /** + * Promise indicating whether all parts of the extension have completed loading or not. + */ + ready: Promise; + debug: { + /** + * Generate an array of strings for commands to pass to the Python executable to launch the debugger for remote debugging. + * Users can append another array of strings of what they want to execute along with relevant arguments to Python. + * E.g `['/Users/..../pythonVSCode/python_files/lib/python/debugpy', '--listen', 'localhost:57039', '--wait-for-client']` + * @param host + * @param port + * @param waitUntilDebuggerAttaches Defaults to `true`. + */ + getRemoteLauncherCommand(host: string, port: number, waitUntilDebuggerAttaches: boolean): Promise; + + /** + * Gets the path to the debugger package used by the extension. + */ + getDebuggerPackagePath(): Promise; + }; + + /** + * These APIs provide a way for extensions to work with by python environments available in the user's machine + * as found by the Python extension. See + * https://github.com/microsoft/vscode-python/wiki/Python-Environment-APIs for usage examples and more. + */ + readonly environments: { + /** + * Returns the environment configured by user in settings. Note that this can be an invalid environment, use + * {@link resolveEnvironment} to get full details. + * @param resource : Uri of a file or workspace folder. This is used to determine the env in a multi-root + * scenario. If `undefined`, then the API returns what ever is set for the workspace. + */ + getActiveEnvironmentPath(resource?: Resource): EnvironmentPath; + /** + * Sets the active environment path for the python extension for the resource. Configuration target will always + * be the workspace folder. + * @param environment : If string, it represents the full path to environment folder or python executable + * for the environment. Otherwise it can be {@link Environment} or {@link EnvironmentPath} itself. + * @param resource : [optional] File or workspace to scope to a particular workspace folder. + */ + updateActiveEnvironmentPath( + environment: string | EnvironmentPath | Environment, + resource?: Resource, + ): Promise; + /** + * This event is triggered when the active environment setting changes. + */ + readonly onDidChangeActiveEnvironmentPath: Event; + /** + * Carries environments known to the extension at the time of fetching the property. Note this may not + * contain all environments in the system as a refresh might be going on. + * + * Only reports environments in the current workspace. + */ + readonly known: readonly Environment[]; + /** + * This event is triggered when the known environment list changes, like when a environment + * is found, existing environment is removed, or some details changed on an environment. + */ + readonly onDidChangeEnvironments: Event; + /** + * This API will trigger environment discovery, but only if it has not already happened in this VSCode session. + * Useful for making sure env list is up-to-date when the caller needs it for the first time. + * + * To force trigger a refresh regardless of whether a refresh was already triggered, see option + * {@link RefreshOptions.forceRefresh}. + * + * Note that if there is a refresh already going on then this returns the promise for that refresh. + * @param options Additional options for refresh. + * @param token A cancellation token that indicates a refresh is no longer needed. + */ + refreshEnvironments(options?: RefreshOptions, token?: CancellationToken): Promise; + /** + * Returns details for the given environment, or `undefined` if the env is invalid. + * @param environment : If string, it represents the full path to environment folder or python executable + * for the environment. Otherwise it can be {@link Environment} or {@link EnvironmentPath} itself. + */ + resolveEnvironment( + environment: Environment | EnvironmentPath | string, + ): Promise; + /** + * Returns the environment variables used by the extension for a resource, which includes the custom + * variables configured by user in `.env` files. + * @param resource : Uri of a file or workspace folder. This is used to determine the env in a multi-root + * scenario. If `undefined`, then the API returns what ever is set for the workspace. + */ + getEnvironmentVariables(resource?: Resource): EnvironmentVariables; + /** + * This event is fired when the environment variables for a resource change. Note it's currently not + * possible to detect if environment variables in the system change, so this only fires if custom + * environment variables are updated in `.env` files. + */ + readonly onDidEnvironmentVariablesChange: Event; + }; +} + +export type RefreshOptions = { + /** + * When `true`, force trigger a refresh regardless of whether a refresh was already triggered. Note this can be expensive so + * it's best to only use it if user manually triggers a refresh. + */ + forceRefresh?: boolean; +}; + +/** + * Details about the environment. Note the environment folder, type and name never changes over time. + */ +export type Environment = EnvironmentPath & { + /** + * Carries details about python executable. + */ + readonly executable: { + /** + * Uri of the python interpreter/executable. Carries `undefined` in case an executable does not belong to + * the environment. + */ + readonly uri: Uri | undefined; + /** + * Bitness if known at this moment. + */ + readonly bitness: Bitness | undefined; + /** + * Value of `sys.prefix` in sys module if known at this moment. + */ + readonly sysPrefix: string | undefined; + }; + /** + * Carries details if it is an environment, otherwise `undefined` in case of global interpreters and others. + */ + readonly environment: + | { + /** + * Type of the environment. + */ + readonly type: EnvironmentType; + /** + * Name to the environment if any. + */ + readonly name: string | undefined; + /** + * Uri of the environment folder. + */ + readonly folderUri: Uri; + /** + * Any specific workspace folder this environment is created for. + */ + readonly workspaceFolder: WorkspaceFolder | undefined; + } + | undefined; + /** + * Carries Python version information known at this moment, carries `undefined` for envs without python. + */ + readonly version: + | (VersionInfo & { + /** + * Value of `sys.version` in sys module if known at this moment. + */ + readonly sysVersion: string | undefined; + }) + | undefined; + /** + * Tools/plugins which created the environment or where it came from. First value in array corresponds + * to the primary tool which manages the environment, which never changes over time. + * + * Array is empty if no tool is responsible for creating/managing the environment. Usually the case for + * global interpreters. + */ + readonly tools: readonly EnvironmentTools[]; +}; + +/** + * Derived form of {@link Environment} where certain properties can no longer be `undefined`. Meant to represent an + * {@link Environment} with complete information. + */ +export type ResolvedEnvironment = Environment & { + /** + * Carries complete details about python executable. + */ + readonly executable: { + /** + * Uri of the python interpreter/executable. Carries `undefined` in case an executable does not belong to + * the environment. + */ + readonly uri: Uri | undefined; + /** + * Bitness of the environment. + */ + readonly bitness: Bitness; + /** + * Value of `sys.prefix` in sys module. + */ + readonly sysPrefix: string; + }; + /** + * Carries complete Python version information, carries `undefined` for envs without python. + */ + readonly version: + | (ResolvedVersionInfo & { + /** + * Value of `sys.version` in sys module if known at this moment. + */ + readonly sysVersion: string; + }) + | undefined; +}; + +export type EnvironmentsChangeEvent = { + readonly env: Environment; + /** + * * "add": New environment is added. + * * "remove": Existing environment in the list is removed. + * * "update": New information found about existing environment. + */ + readonly type: 'add' | 'remove' | 'update'; +}; + +export type ActiveEnvironmentPathChangeEvent = EnvironmentPath & { + /** + * Resource the environment changed for. + */ + readonly resource: Resource | undefined; +}; + +/** + * Uri of a file inside a workspace or workspace folder itself. + */ +export type Resource = Uri | WorkspaceFolder; + +export type EnvironmentPath = { + /** + * The ID of the environment. + */ + readonly id: string; + /** + * Path to environment folder or path to python executable that uniquely identifies an environment. Environments + * lacking a python executable are identified by environment folder paths, whereas other envs can be identified + * using python executable path. + */ + readonly path: string; +}; + +/** + * Tool/plugin where the environment came from. It can be {@link KnownEnvironmentTools} or custom string which + * was contributed. + */ +export type EnvironmentTools = KnownEnvironmentTools | string; +/** + * Tools or plugins the Python extension currently has built-in support for. Note this list is expected to shrink + * once tools have their own separate extensions. + */ +export type KnownEnvironmentTools = + | 'Conda' + | 'Pipenv' + | 'Poetry' + | 'VirtualEnv' + | 'Venv' + | 'VirtualEnvWrapper' + | 'Pyenv' + | 'Unknown'; + +/** + * Type of the environment. It can be {@link KnownEnvironmentTypes} or custom string which was contributed. + */ +export type EnvironmentType = KnownEnvironmentTypes | string; +/** + * Environment types the Python extension is aware of. Note this list is expected to shrink once tools have their + * own separate extensions, in which case they're expected to provide the type themselves. + */ +export type KnownEnvironmentTypes = 'VirtualEnvironment' | 'Conda' | 'Unknown'; + +/** + * Carries bitness for an environment. + */ +export type Bitness = '64-bit' | '32-bit' | 'Unknown'; + +/** + * The possible Python release levels. + */ +export type PythonReleaseLevel = 'alpha' | 'beta' | 'candidate' | 'final'; + +/** + * Release information for a Python version. + */ +export type PythonVersionRelease = { + readonly level: PythonReleaseLevel; + readonly serial: number; +}; + +export type VersionInfo = { + readonly major: number | undefined; + readonly minor: number | undefined; + readonly micro: number | undefined; + readonly release: PythonVersionRelease | undefined; +}; + +export type ResolvedVersionInfo = { + readonly major: number; + readonly minor: number; + readonly micro: number; + readonly release: PythonVersionRelease; +}; + +/** + * A record containing readonly keys. + */ +export type EnvironmentVariables = { readonly [key: string]: string | undefined }; + +export type EnvironmentVariablesChangeEvent = { + /** + * Workspace folder the environment variables changed for. + */ + readonly resource: WorkspaceFolder | undefined; + /** + * Updated value of environment variables. + */ + readonly env: EnvironmentVariables; +}; + +export const PVSC_EXTENSION_ID = 'ms-python.python'; + +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace PythonExtension { + /** + * Returns the API exposed by the Python extension in VS Code. + */ + export async function api(): Promise { + const extension = extensions.getExtension(PVSC_EXTENSION_ID); + if (extension === undefined) { + throw new Error(`Python extension is not installed or is disabled`); + } + if (!extension.isActive) { + await extension.activate(); + } + const pythonApi: PythonExtension = extension.exports; + return pythonApi; + } +} diff --git a/pythonExtensionApi/tsconfig.json b/pythonExtensionApi/tsconfig.json new file mode 100644 index 000000000000..9ab7617023df --- /dev/null +++ b/pythonExtensionApi/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "*": ["types/*"] + }, + "module": "commonjs", + "target": "es2018", + "outDir": "./out", + "lib": [ + "es6", + "es2018", + "dom", + "ES2019", + "ES2020" + ], + "sourceMap": true, + "rootDir": "src", + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "noImplicitAny": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "resolveJsonModule": true, + "declaration": true + }, + "exclude": [ + "node_modules", + "out" + ] +} diff --git a/pythonFiles/Notebooks intro.ipynb b/pythonFiles/Notebooks intro.ipynb deleted file mode 100644 index 209e4fb6e2ba..000000000000 --- a/pythonFiles/Notebooks intro.ipynb +++ /dev/null @@ -1,203 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Creating a new notebook\n", - "\n", - "\n", - "\n", - "1.

Open the command palette with the shortcut: ", - " \n", - " Ctrl/Command\n", - " +\n", - " Shift\n", - " +\n", - " P\n", - "

\n", - "\n", - "2.

Search for the command Create New Blank Jupyter Notebook

\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---\n", - "# How to get back to the start page\n", - "\n", - "1.

Open the command palette with the shortcut:\n", - " \n", - " Ctrl/Command\n", - " +\n", - " Shift\n", - " +\n", - " P\n", - "

\n", - "\n", - "2.

Search for the command Python: Open Start Page

\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---\n", - "# Getting started\n", - "\n", - "You are currently viewing what we call our Notebook Editor. It is an interactive document based on Jupyter Notebooks that supports the intermixing of code, outputs and markdown documentation. \n", - "\n", - "This cell is a markdown cell. To edit the text in this cell, simply double click on the cell to change it into edit mode.\n", - "\n", - "

The next cell below is a code cell. You can switch a cell between code and markdown by clicking on the code \n", - "\n", - "/markdown \n", - "\n", - " icons or using the keyboard shortcut\n", - " \n", - " M\n", - " \n", - "and\n", - " \n", - " Y\n", - " \n", - "respectively.

\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print('hello world')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "*

To execute the code in the cell above, click on the cell to select it and then either press the play \n", - "\n", - "\n", - " button in the cell toolbar, or use the keyboard shortcut\n", - " \n", - " Ctrl/Command\n", - " +\n", - " Enter\n", - " \n", - "

\n", - "* To edit the code, just click in cell and start editing.\n", - "*

To add a new cell below, click the Add Cell icon \n", - "\n", - "\n", - " \n", - "at the bottom left of the cell or enter command mode with the\n", - " \n", - " ESC\n", - " \n", - "Key and then use the keyboard shortcut\n", - " \n", - " B\n", - " \n", - "to create the new cell below.

\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---\n", - "# Features\n", - "\n", - "**Variable explorer**\n", - "\n", - "

To view all your active variables and their current values in the notebook, click on the variable explorer icon \n", - "\n", - "\n", - "\n", - "in the top toolbar.

\n", - "\n", - "\n", - "\n", - "**Data Viewer**\n", - "\n", - "

To view your data frame in a more visual \"Excel\" like format, open the variable explorer and to the left of any dataframe object, you will see the data viewer icon\n", - "\n", - "\n", - "\n", - "which you can click to open the data viewer.

\n", - "\n", - "\n", - "\n", - "**Convert to Python File**\n", - "\n", - "

To export your notebook to a Python file (.py), click on the Convert to Python script icon \n", - "\n", - "\n", - "\n", - "\n", - "in the top toolbar \n", - "

\n", - "\n", - "\n", - "\n", - "**Plot Viewer**\n", - "\n", - "

If you have a graph (such as matplotlib) in your output, you'll notice if you hover over the graph, the Plot Viewer icon \n", - "\n", - "\n", - "\n", - "will appear in the top left. Click the icon to open up the graph in the Plotviewer which allows you to zoom on your plots and export it in formats such as png and jpeg.

\n", - "\n", - "\n", - "\n", - "**Switching Kernels**\n", - "\n", - "The notebook editor will detect all kernels in your system by default. To change your notebook kernel, click on the kernel status in the top toolbar at the far right. For example, your kernel status may say \"Python 3: Idle\". This will open up the kernel selector where you can choose your desired kernel.\n", - "\n", - "\n", - "\n", - "**Remote Jupyter Server**\n", - "\n", - "

To connect to a remote Jupyter server, open the command prompt and search for the command Specify remote or local Jupyter server for connections. Then select Existing and enter the remote Jupyter server URL. Afterwards, you'll be prompted to reload the window and the Notebook will be opened connected to the remote Jupyter server.

\n", - "\n", - "", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "-Rh3-Vt9Nev9" - }, - "source": [ - "---\n", - "## More Resources\n", - "\n", - "- [Data science tutorial for Visual Studio Code](https://code.visualstudio.com/docs/python/data-science-tutorial)\n", - "- [Jupyter Notebooks in Visual Studio Code documentation](https://code.visualstudio.com/docs/python/jupyter-support)\n" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/pythonFiles/completion.py b/pythonFiles/completion.py deleted file mode 100644 index fd8a15d8df28..000000000000 --- a/pythonFiles/completion.py +++ /dev/null @@ -1,694 +0,0 @@ -import os -import os.path -import io -import re -import sys -import json -import traceback -import platform - -jediPreview = False - - -class RedirectStdout(object): - def __init__(self, new_stdout=None): - """If stdout is None, redirect to /dev/null""" - self._new_stdout = new_stdout or open(os.devnull, "w") - - def __enter__(self): - sys.stdout.flush() - self.oldstdout_fno = os.dup(sys.stdout.fileno()) - os.dup2(self._new_stdout.fileno(), 1) - - def __exit__(self, exc_type, exc_value, traceback): - self._new_stdout.flush() - os.dup2(self.oldstdout_fno, 1) - os.close(self.oldstdout_fno) - - -class JediCompletion(object): - basic_types = { - "module": "import", - "instance": "variable", - "statement": "value", - "param": "variable", - } - - def __init__(self): - self.default_sys_path = sys.path - self._input = io.open(sys.stdin.fileno(), encoding="utf-8") - if (os.path.sep == "/") and (platform.uname()[2].find("Microsoft") > -1): - # WSL; does not support UNC paths - self.drive_mount = "/mnt/" - elif sys.platform == "cygwin": - # cygwin - self.drive_mount = "/cygdrive/" - else: - # Do no normalization, e.g. Windows build of Python. - # Could add additional test: ((os.path.sep == '/') and os.path.isdir('/mnt/c')) - # However, this may have more false positives trying to identify Windows/*nix hybrids - self.drive_mount = "" - - def _get_definition_type(self, definition): - # if definition.type not in ['import', 'keyword'] and is_built_in(): - # return 'builtin' - try: - if definition.type in ["statement"] and definition.name.isupper(): - return "constant" - return self.basic_types.get(definition.type, definition.type) - except Exception: - return "builtin" - - def _additional_info(self, completion): - """Provide additional information about the completion object.""" - if not hasattr(completion, "_definition") or completion._definition is None: - return "" - if completion.type == "statement": - nodes_to_display = ["InstanceElement", "String", "Node", "Lambda", "Number"] - return "".join( - c.get_code() - for c in completion._definition.children - if type(c).__name__ in nodes_to_display - ).replace("\n", "") - return "" - - @classmethod - def _get_top_level_module(cls, path): - """Recursively walk through directories looking for top level module. - - Jedi will use current filepath to look for another modules at same - path, but it will not be able to see modules **above**, so our goal - is to find the higher python module available from filepath. - """ - _path, _ = os.path.split(path) - if os.path.isfile(os.path.join(_path, "__init__.py")): - return cls._get_top_level_module(_path) - return path - - def _generate_signature(self, completion): - """Generate signature with function arguments.""" - if completion.type in ["module"] or not hasattr(completion, "params"): - return "" - return "%s(%s)" % ( - completion.name, - ", ".join(p.description[6:] for p in completion.params if p), - ) - - def _get_call_signatures(self, script): - """Extract call signatures from jedi.api.Script object in failsafe way. - - Returns: - Tuple with original signature object, name and value. - """ - _signatures = [] - try: - call_signatures = script.call_signatures() - except KeyError: - call_signatures = [] - except: - call_signatures = [] - for signature in call_signatures: - for pos, param in enumerate(signature.params): - if not param.name: - continue - - name = self._get_param_name(param) - if param.name == "self" and pos == 0: - continue - if name.startswith("*"): - continue - - value = self._get_param_value(param) - _signatures.append((signature, name, value)) - return _signatures - - def _get_param_name(self, p): - if p.name.startswith("param "): - return p.name[6:] # drop leading 'param ' - return p.name - - def _get_param_value(self, p): - pair = p.description.split("=") - if len(pair) > 1: - return pair[1] - return None - - def _get_call_signatures_with_args(self, script): - """Extract call signatures from jedi.api.Script object in failsafe way. - - Returns: - Array with dictionary - """ - _signatures = [] - try: - call_signatures = script.call_signatures() - except KeyError: - call_signatures = [] - for signature in call_signatures: - sig = { - "name": "", - "description": "", - "docstring": "", - "paramindex": 0, - "params": [], - "bracketstart": [], - } - sig["description"] = signature.description - try: - sig["docstring"] = signature.docstring() - sig["raw_docstring"] = signature.docstring(raw=True) - except Exception: - sig["docstring"] = "" - sig["raw_docstring"] = "" - - sig["name"] = signature.name - sig["paramindex"] = signature.index - sig["bracketstart"].append(signature.index) - - _signatures.append(sig) - for pos, param in enumerate(signature.params): - if not param.name: - continue - - name = self._get_param_name(param) - if param.name == "self" and pos == 0: - continue - - value = self._get_param_value(param) - paramDocstring = "" - try: - paramDocstring = param.docstring() - except Exception: - paramDocstring = "" - - sig["params"].append( - { - "name": name, - "value": value, - "docstring": paramDocstring, - "description": param.description, - } - ) - return _signatures - - def _serialize_completions(self, script, identifier=None, prefix=""): - """Serialize response to be read from VSCode. - - Args: - script: Instance of jedi.api.Script object. - identifier: Unique completion identifier to pass back to VSCode. - prefix: String with prefix to filter function arguments. - Used only when fuzzy matcher turned off. - - Returns: - Serialized string to send to VSCode. - """ - _completions = [] - - for signature, name, value in self._get_call_signatures(script): - if not self.fuzzy_matcher and not name.lower().startswith(prefix.lower()): - continue - _completion = { - "type": "property", - "raw_type": "", - "rightLabel": self._additional_info(signature), - } - _completion["description"] = "" - _completion["raw_docstring"] = "" - - # we pass 'text' here only for fuzzy matcher - if value: - _completion["snippet"] = "%s=${1:%s}$0" % (name, value) - _completion["text"] = "%s=" % (name) - else: - _completion["snippet"] = "%s=$1$0" % name - _completion["text"] = name - _completion["displayText"] = name - _completions.append(_completion) - - try: - completions = script.completions() - except KeyError: - completions = [] - except: - completions = [] - for completion in completions: - try: - _completion = { - "text": completion.name, - "type": self._get_definition_type(completion), - "raw_type": completion.type, - "rightLabel": self._additional_info(completion), - } - except Exception: - continue - - for c in _completions: - if c["text"] == _completion["text"]: - c["type"] = _completion["type"] - c["raw_type"] = _completion["raw_type"] - - if any( - [c["text"].split("=")[0] == _completion["text"] for c in _completions] - ): - # ignore function arguments we already have - continue - _completions.append(_completion) - return json.dumps({"id": identifier, "results": _completions}) - - def _serialize_methods(self, script, identifier=None, prefix=""): - _methods = [] - try: - completions = script.completions() - except KeyError: - return [] - - for completion in completions: - if completion.name == "__autocomplete_python": - instance = completion.parent().name - break - else: - instance = "self.__class__" - - for completion in completions: - params = [] - if hasattr(completion, "params"): - params = [p.description for p in completion.params if p] - if completion.parent().type == "class": - _methods.append( - { - "parent": completion.parent().name, - "instance": instance, - "name": completion.name, - "params": params, - "moduleName": completion.module_name, - "fileName": completion.module_path, - "line": completion.line, - "column": completion.column, - } - ) - return json.dumps({"id": identifier, "results": _methods}) - - def _serialize_arguments(self, script, identifier=None): - """Serialize response to be read from VSCode. - - Args: - script: Instance of jedi.api.Script object. - identifier: Unique completion identifier to pass back to VSCode. - - Returns: - Serialized string to send to VSCode. - """ - return json.dumps( - {"id": identifier, "results": self._get_call_signatures_with_args(script)} - ) - - def _top_definition(self, definition): - for d in definition.goto_assignments(): - if d == definition: - continue - if d.type == "import": - return self._top_definition(d) - else: - return d - return definition - - def _extract_range_jedi_0_11_1(self, definition): - from parso.utils import split_lines - - # get the scope range - try: - if definition.type in ["class", "function"]: - tree_name = definition._name.tree_name - scope = tree_name.get_definition() - start_line = scope.start_pos[0] - 1 - start_column = scope.start_pos[1] - # get the lines - code = scope.get_code(include_prefix=False) - lines = split_lines(code) - # trim the lines - lines = "\n".join(lines).rstrip().split("\n") - end_line = start_line + len(lines) - 1 - end_column = len(lines[-1]) - 1 - else: - symbol = definition._name.tree_name - start_line = symbol.start_pos[0] - 1 - start_column = symbol.start_pos[1] - end_line = symbol.end_pos[0] - 1 - end_column = symbol.end_pos[1] - return { - "start_line": start_line, - "start_column": start_column, - "end_line": end_line, - "end_column": end_column, - } - except Exception as e: - return { - "start_line": definition.line - 1, - "start_column": definition.column, - "end_line": definition.line - 1, - "end_column": definition.column, - } - - def _extract_range(self, definition): - """Provides the definition range of a given definition - - For regular symbols it returns the start and end location of the - characters making up the symbol. - - For scoped containers it will return the entire definition of the - scope. - - The scope that jedi provides ends with the first character of the next - scope so it's not ideal. For vscode we need the scope to end with the - last character of actual code. That's why we extract the lines that - make up our scope and trim the trailing whitespace. - """ - return self._extract_range_jedi_0_11_1(definition) - - def _get_definitionsx(self, definitions, identifier=None, ignoreNoModulePath=False): - """Serialize response to be read from VSCode. - - Args: - definitions: List of jedi.api.classes.Definition objects. - identifier: Unique completion identifier to pass back to VSCode. - - Returns: - Serialized string to send to VSCode. - """ - _definitions = [] - for definition in definitions: - try: - if definition.type == "import": - definition = self._top_definition(definition) - definitionRange = { - "start_line": 0, - "start_column": 0, - "end_line": 0, - "end_column": 0, - } - module_path = "" - if hasattr(definition, "module_path") and definition.module_path: - module_path = definition.module_path - definitionRange = self._extract_range(definition) - else: - if not ignoreNoModulePath: - continue - try: - parent = definition.parent() - container = parent.name if parent.type != "module" else "" - except Exception: - container = "" - - try: - docstring = definition.docstring() - rawdocstring = definition.docstring(raw=True) - except Exception: - docstring = "" - rawdocstring = "" - _definition = { - "text": definition.name, - "type": self._get_definition_type(definition), - "raw_type": definition.type, - "fileName": module_path, - "container": container, - "range": definitionRange, - "description": definition.description, - "docstring": docstring, - "raw_docstring": rawdocstring, - "signature": self._generate_signature(definition), - } - _definitions.append(_definition) - except Exception as e: - pass - return _definitions - - def _serialize_definitions(self, definitions, identifier=None): - """Serialize response to be read from VSCode. - - Args: - definitions: List of jedi.api.classes.Definition objects. - identifier: Unique completion identifier to pass back to VSCode. - - Returns: - Serialized string to send to VSCode. - """ - _definitions = [] - for definition in definitions: - try: - if definition.module_path: - if definition.type == "import": - definition = self._top_definition(definition) - if not definition.module_path: - continue - try: - parent = definition.parent() - container = parent.name if parent.type != "module" else "" - except Exception: - container = "" - - try: - docstring = definition.docstring() - rawdocstring = definition.docstring(raw=True) - except Exception: - docstring = "" - rawdocstring = "" - _definition = { - "text": definition.name, - "type": self._get_definition_type(definition), - "raw_type": definition.type, - "fileName": definition.module_path, - "container": container, - "range": self._extract_range(definition), - "description": definition.description, - "docstring": docstring, - "raw_docstring": rawdocstring, - } - _definitions.append(_definition) - except Exception as e: - pass - return json.dumps({"id": identifier, "results": _definitions}) - - def _serialize_tooltip(self, definitions, identifier=None): - _definitions = [] - for definition in definitions: - signature = definition.name - description = None - if definition.type in ["class", "function"]: - signature = self._generate_signature(definition) - try: - description = definition.docstring(raw=True).strip() - except Exception: - description = "" - if not description and not hasattr(definition, "get_line_code"): - # jedi returns an empty string for compiled objects - description = definition.docstring().strip() - if definition.type == "module": - signature = definition.full_name - try: - description = definition.docstring(raw=True).strip() - except Exception: - description = "" - if not description and hasattr(definition, "get_line_code"): - # jedi returns an empty string for compiled objects - description = definition.docstring().strip() - _definition = { - "type": self._get_definition_type(definition), - "text": definition.name, - "description": description, - "docstring": description, - "signature": signature, - } - _definitions.append(_definition) - return json.dumps({"id": identifier, "results": _definitions}) - - def _serialize_usages(self, usages, identifier=None): - _usages = [] - for usage in usages: - _usages.append( - { - "name": usage.name, - "moduleName": usage.module_name, - "fileName": usage.module_path, - "line": usage.line, - "column": usage.column, - } - ) - return json.dumps({"id": identifier, "results": _usages}) - - def _deserialize(self, request): - """Deserialize request from VSCode. - - Args: - request: String with raw request from VSCode. - - Returns: - Python dictionary with request data. - """ - return json.loads(request) - - def _set_request_config(self, config): - """Sets config values for current request. - - This includes sys.path modifications which is getting restored to - default value on each request so each project should be isolated - from each other. - - Args: - config: Dictionary with config values. - """ - sys.path = self.default_sys_path - self.use_snippets = config.get("useSnippets") - self.show_doc_strings = config.get("showDescriptions", True) - self.fuzzy_matcher = config.get("fuzzyMatcher", False) - jedi.settings.case_insensitive_completion = config.get( - "caseInsensitiveCompletion", True - ) - for path in config.get("extraPaths", []): - if path and path not in sys.path: - sys.path.insert(0, path) - - def _normalize_request_path(self, request): - """Normalize any Windows paths received by a *nix build of - Python. Does not alter the reverse os.path.sep=='\\', - i.e. *nix paths received by a Windows build of Python. - """ - if "path" in request: - if not self.drive_mount: - return - newPath = request["path"].replace("\\", "/") - if newPath[0:1] == "/": - # is absolute path with no drive letter - request["path"] = newPath - elif newPath[1:2] == ":": - # is path with drive letter, only absolute can be mapped - request["path"] = self.drive_mount + newPath[0:1].lower() + newPath[2:] - else: - # is relative path - request["path"] = newPath - - def _process_request(self, request): - """Accept serialized request from VSCode and write response.""" - request = self._deserialize(request) - - self._set_request_config(request.get("config", {})) - - self._normalize_request_path(request) - path = self._get_top_level_module(request.get("path", "")) - if len(path) > 0 and path not in sys.path: - sys.path.insert(0, path) - lookup = request.get("lookup", "completions") - - if lookup == "names": - return self._serialize_definitions( - jedi.api.names( - source=request.get("source", None), - path=request.get("path", ""), - all_scopes=True, - ), - request["id"], - ) - - script = jedi.Script( - source=request.get("source", None), - line=request["line"] + 1, - column=request["column"], - path=request.get("path", ""), - project=jedi.get_default_project(os.path.dirname(path)), - sys_path=sys.path, - ) - - if lookup == "definitions": - defs = self._get_definitionsx( - script.goto_assignments(follow_imports=True), request["id"] - ) - return json.dumps({"id": request["id"], "results": defs}) - if lookup == "tooltip": - if jediPreview: - defs = [] - try: - defs = self._get_definitionsx( - script.goto_definitions(), request["id"], True - ) - except: - pass - try: - if len(defs) == 0: - defs = self._get_definitionsx( - script.goto_assignments(), request["id"], True - ) - except: - pass - return json.dumps({"id": request["id"], "results": defs}) - else: - try: - return self._serialize_tooltip( - script.goto_definitions(), request["id"] - ) - except: - return json.dumps({"id": request["id"], "results": []}) - elif lookup == "arguments": - return self._serialize_arguments(script, request["id"]) - elif lookup == "usages": - return self._serialize_usages(script.usages(), request["id"]) - elif lookup == "methods": - return self._serialize_methods( - script, request["id"], request.get("prefix", "") - ) - else: - return self._serialize_completions( - script, request["id"], request.get("prefix", "") - ) - - def _write_response(self, response): - sys.stdout.write(response + "\n") - sys.stdout.flush() - - def watch(self): - while True: - try: - rq = self._input.readline() - if len(rq) == 0: - # Reached EOF - indication our parent process is gone. - sys.stderr.write( - "Received EOF from the standard input,exiting" + "\n" - ) - sys.stderr.flush() - return - with RedirectStdout(): - response = self._process_request(rq) - self._write_response(response) - - except Exception: - sys.stderr.write(traceback.format_exc() + "\n") - sys.stderr.flush() - - -if __name__ == "__main__": - cachePrefix = "v" - modulesToLoad = "" - if len(sys.argv) > 2 and sys.argv[1] == "custom": - jediPath = sys.argv[2] - jediPreview = True - cachePrefix = "custom_v" - if len(sys.argv) > 3: - modulesToLoad = sys.argv[3] - else: - # release - jediPath = os.path.join(os.path.dirname(__file__), "lib", "python") - if len(sys.argv) > 1: - modulesToLoad = sys.argv[1] - - sys.path.insert(0, jediPath) - import jedi - - if jediPreview: - jedi.settings.cache_directory = os.path.join( - jedi.settings.cache_directory, - cachePrefix + jedi.__version__.replace(".", ""), - ) - # remove jedi from path after we import it so it will not be completed - sys.path.pop(0) - if len(modulesToLoad) > 0: - jedi.preload_module(*modulesToLoad.split(",")) - JediCompletion().watch() diff --git a/pythonFiles/install_debugpy.py b/pythonFiles/install_debugpy.py deleted file mode 100644 index cb21efd91927..000000000000 --- a/pythonFiles/install_debugpy.py +++ /dev/null @@ -1,63 +0,0 @@ -import io -import json -import os -import urllib.request as url_lib -import zipfile -from packaging.version import parse as version_parser - - -EXTENSION_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -DEBUGGER_DEST = os.path.join(EXTENSION_ROOT, "pythonFiles", "lib", "python") -DEBUGGER_PACKAGE = "debugpy" -DEBUGGER_PYTHON_VERSIONS = ("cp37",) - - -def _contains(s, parts=()): - return any(p for p in parts if p in s) - - -def _get_package_data(): - json_uri = "https://pypi.org/pypi/{0}/json".format(DEBUGGER_PACKAGE) - # Response format: https://warehouse.readthedocs.io/api-reference/json/#project - # Release metadata format: https://github.com/pypa/interoperability-peps/blob/master/pep-0426-core-metadata.rst - with url_lib.urlopen(json_uri) as response: - return json.loads(response.read()) - - -def _get_debugger_wheel_urls(data, version): - return list( - r["url"] - for r in data["releases"][version] - if _contains(r["url"], DEBUGGER_PYTHON_VERSIONS) - ) - - -def _download_and_extract(root, url, version): - root = os.getcwd() if root is None or root == "." else root - prefix = os.path.join("debugpy-{0}.data".format(version), "purelib") - with url_lib.urlopen(url) as response: - # Extract only the contents of the purelib subfolder (parent folder of debugpy), - # since debugpy files rely on the presence of a 'debugpy' folder. - with zipfile.ZipFile(io.BytesIO(response.read()), "r") as wheel: - for zip_info in wheel.infolist(): - # Ignore dist info since we are merging multiple wheels - if ".dist-info" in zip_info.filename: - continue - # Normalize path for Windows, the wheel folder structure - # uses forward slashes. - normalized = os.path.normpath(zip_info.filename) - # Flatten the folder structure. - zip_info.filename = normalized.split(prefix)[-1] - wheel.extract(zip_info, root) - - -def main(root): - data = _get_package_data() - latest_version = max(data["releases"].keys(), key=version_parser) - - for url in _get_debugger_wheel_urls(data, latest_version): - _download_and_extract(root, url, latest_version) - - -if __name__ == "__main__": - main(DEBUGGER_DEST) diff --git a/pythonFiles/normalizeForInterpreter.py b/pythonFiles/normalizeForInterpreter.py deleted file mode 100644 index 34b31d56b398..000000000000 --- a/pythonFiles/normalizeForInterpreter.py +++ /dev/null @@ -1,154 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -import ast -import io -import operator -import os -import sys -import textwrap -import token -import tokenize - - -class Visitor(ast.NodeVisitor): - def __init__(self, lines): - self._lines = lines - self.line_numbers_with_nodes = set() - self.line_numbers_with_statements = [] - - def generic_visit(self, node): - if ( - hasattr(node, "col_offset") - and hasattr(node, "lineno") - and node.col_offset == 0 - ): - self.line_numbers_with_nodes.add(node.lineno) - if isinstance(node, ast.stmt): - self.line_numbers_with_statements.append(node.lineno) - - ast.NodeVisitor.generic_visit(self, node) - - -def _tokenize(source): - """Tokenize Python source code.""" - # Using an undocumented API as the documented one in Python 2.7 does not work as needed - # cross-version. - if sys.version_info < (3,) and isinstance(source, str): - source = source.decode() - return tokenize.generate_tokens(io.StringIO(source).readline) - - -def _indent_size(line): - for index, char in enumerate(line): - if not char.isspace(): - return index - - -def _get_global_statement_blocks(source, lines): - """Return a list of all global statement blocks. - - The list comprises of 3-item tuples that contain the starting line number, - ending line number and whether the statement is a single line. - - """ - tree = ast.parse(source) - visitor = Visitor(lines) - visitor.visit(tree) - - statement_ranges = [] - for index, line_number in enumerate(visitor.line_numbers_with_statements): - remaining_line_numbers = visitor.line_numbers_with_statements[index + 1 :] - end_line_number = ( - len(lines) - if len(remaining_line_numbers) == 0 - else min(remaining_line_numbers) - 1 - ) - current_statement_is_oneline = line_number == end_line_number - - if len(statement_ranges) == 0: - statement_ranges.append( - (line_number, end_line_number, current_statement_is_oneline) - ) - continue - - previous_statement = statement_ranges[-1] - previous_statement_is_oneline = previous_statement[2] - if previous_statement_is_oneline and current_statement_is_oneline: - statement_ranges[-1] = previous_statement[0], end_line_number, True - else: - statement_ranges.append( - (line_number, end_line_number, current_statement_is_oneline) - ) - - return statement_ranges - - -def normalize_lines(source): - """Normalize blank lines for sending to the terminal. - - Blank lines within a statement block are removed to prevent the REPL - from thinking the block is finished. Newlines are added to separate - top-level statements so that the REPL does not think there is a syntax - error. - - """ - # Ensure to dedent the code (#2837) - lines = textwrap.dedent(source).splitlines(False) - # If we have two blank lines, then add two blank lines. - # Do not trim the spaces, if we have blank lines with spaces, its possible - # we have indented code. - if (len(lines) > 1 and len("".join(lines[-2:])) == 0) or source.endswith( - ("\n\n", "\r\n\r\n") - ): - trailing_newline = "\n" * 2 - # Find out if we have any trailing blank lines - elif len(lines[-1].strip()) == 0 or source.endswith(("\n", "\r\n")): - trailing_newline = "\n" - else: - trailing_newline = "" - - # Step 1: Remove empty lines. - tokens = _tokenize(source) - newlines_indexes_to_remove = ( - spos[0] - for (toknum, tokval, spos, epos, line) in tokens - if len(line.strip()) == 0 - and token.tok_name[toknum] == "NL" - and spos[0] == epos[0] - ) - - for line_number in reversed(list(newlines_indexes_to_remove)): - del lines[line_number - 1] - - # Step 2: Add blank lines between each global statement block. - # A consecutive single lines blocks of code will be treated as a single statement, - # just to ensure we do not unnecessarily add too many blank lines. - source = "\n".join(lines) - tokens = _tokenize(source) - dedent_indexes = ( - spos[0] - for (toknum, tokval, spos, epos, line) in tokens - if toknum == token.DEDENT and _indent_size(line) == 0 - ) - - global_statement_ranges = _get_global_statement_blocks(source, lines) - start_positions = map(operator.itemgetter(0), reversed(global_statement_ranges)) - for line_number in filter(lambda x: x > 1, start_positions): - lines.insert(line_number - 1, "") - - sys.stdout.write("\n".join(lines) + trailing_newline) - sys.stdout.flush() - - -if __name__ == "__main__": - contents = sys.argv[1] - try: - default_encoding = sys.getdefaultencoding() - encoded_contents = contents.encode(default_encoding, "surrogateescape") - contents = encoded_contents.decode(default_encoding, "replace") - except (UnicodeError, LookupError): - pass - if isinstance(contents, bytes): - contents = contents.decode("utf8") - normalize_lines(contents) diff --git a/pythonFiles/printEnvVariablesToFile.py b/pythonFiles/printEnvVariablesToFile.py deleted file mode 100644 index be966bcac28c..000000000000 --- a/pythonFiles/printEnvVariablesToFile.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -import os -import json -import sys - - -# Last argument is the target file into which we'll write the env variables as json. -json_file = sys.argv[-1] - -with open(json_file, "w") as outfile: - json.dump(dict(os.environ), outfile) diff --git a/pythonFiles/pyproject.toml b/pythonFiles/pyproject.toml deleted file mode 100644 index 52c7c96d11e7..000000000000 --- a/pythonFiles/pyproject.toml +++ /dev/null @@ -1,11 +0,0 @@ -[tool.black] -exclude = ''' - -( - /( - .data - | .vscode - | lib - )/ -) -''' diff --git a/pythonFiles/pyvsc-run-isolated.py b/pythonFiles/pyvsc-run-isolated.py deleted file mode 100644 index 92447e1fc5ff..000000000000 --- a/pythonFiles/pyvsc-run-isolated.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -if __name__ != "__main__": - raise Exception("{} cannot be imported".format(__name__)) - -import os -import os.path -import runpy -import sys - - -def normalize(path): - return os.path.normcase(os.path.normpath(path)) - - -# We "isolate" the script/module (sys.argv[1]) by removing current working -# directory or '' in sys.path and then sending the target on to runpy. -cwd = normalize(os.getcwd()) -sys.path[:] = (p for p in sys.path if p != "" and normalize(p) != cwd) -del sys.argv[0] -module = sys.argv[0] -if module == "-c": - ns = {} - for code in sys.argv[1:]: - exec(code, ns, ns) -elif module.startswith("-"): - raise NotImplementedError(sys.argv) -elif module.endswith(".py"): - runpy.run_path(module, run_name="__main__") -else: - runpy.run_module(module, run_name="__main__", alter_sys=True) diff --git a/pythonFiles/refactor.py b/pythonFiles/refactor.py deleted file mode 100644 index 9e578906b3c2..000000000000 --- a/pythonFiles/refactor.py +++ /dev/null @@ -1,395 +0,0 @@ -# Arguments are: -# 1. Working directory. -# 2. Rope folder - -import difflib -import io -import json -import os -import sys -import traceback - -try: - import rope - from rope.base import libutils - from rope.refactor.rename import Rename - from rope.refactor.extract import ExtractMethod, ExtractVariable - import rope.base.project - import rope.base.taskhandle -except: - jsonMessage = { - "error": True, - "message": "Rope not installed", - "traceback": "", - "type": "ModuleNotFoundError", - } - sys.stderr.write(json.dumps(jsonMessage)) - sys.stderr.flush() - -WORKSPACE_ROOT = sys.argv[1] -ROPE_PROJECT_FOLDER = ".vscode/.ropeproject" - - -class RefactorProgress: - """ - Refactor progress information - """ - - def __init__(self, name="Task Name", message=None, percent=0): - self.name = name - self.message = message - self.percent = percent - - -class ChangeType: - """ - Change Type Enum - """ - - EDIT = 0 - NEW = 1 - DELETE = 2 - - -class Change: - """""" - - EDIT = 0 - NEW = 1 - DELETE = 2 - - def __init__(self, filePath, fileMode=ChangeType.EDIT, diff=""): - self.filePath = filePath - self.diff = diff - self.fileMode = fileMode - - -def get_diff(changeset): - """This is a copy of the code form the ChangeSet.get_description method found in Rope.""" - new = changeset.new_contents - old = changeset.old_contents - if old is None: - if changeset.resource.exists(): - old = changeset.resource.read() - else: - old = "" - - # Ensure code has a trailing empty lines, before generating a diff. - # https://github.com/Microsoft/vscode-python/issues/695. - old_lines = old.splitlines(True) - if not old_lines[-1].endswith("\n"): - old_lines[-1] = old_lines[-1] + os.linesep - new = new + os.linesep - - result = difflib.unified_diff( - old_lines, - new.splitlines(True), - "a/" + changeset.resource.path, - "b/" + changeset.resource.path, - ) - return "".join(list(result)) - - -class BaseRefactoring(object): - """ - Base class for refactorings - """ - - def __init__(self, project, resource, name="Refactor", progressCallback=None): - self._progressCallback = progressCallback - self._handle = rope.base.taskhandle.TaskHandle(name) - self._handle.add_observer(self._update_progress) - self.project = project - self.resource = resource - self.changes = [] - - def _update_progress(self): - jobset = self._handle.current_jobset() - if jobset and not self._progressCallback is None: - progress = RefactorProgress() - # getting current job set name - if jobset.get_name() is not None: - progress.name = jobset.get_name() - # getting active job name - if jobset.get_active_job_name() is not None: - progress.message = jobset.get_active_job_name() - # adding done percent - percent = jobset.get_percent_done() - if percent is not None: - progress.percent = percent - if not self._progressCallback is None: - self._progressCallback(progress) - - def stop(self): - self._handle.stop() - - def refactor(self): - try: - self.onRefactor() - except rope.base.exceptions.InterruptedTaskError: - # we can ignore this exception, as user has cancelled refactoring - pass - - def onRefactor(self): - """ - To be implemented by each base class - """ - pass - - -class RenameRefactor(BaseRefactoring): - def __init__( - self, - project, - resource, - name="Rename", - progressCallback=None, - startOffset=None, - newName="new_Name", - ): - BaseRefactoring.__init__(self, project, resource, name, progressCallback) - self._newName = newName - self.startOffset = startOffset - - def onRefactor(self): - renamed = Rename(self.project, self.resource, self.startOffset) - changes = renamed.get_changes(self._newName, task_handle=self._handle) - for item in changes.changes: - if isinstance(item, rope.base.change.ChangeContents): - self.changes.append( - Change(item.resource.real_path, ChangeType.EDIT, get_diff(item)) - ) - else: - raise Exception("Unknown Change") - - -class ExtractVariableRefactor(BaseRefactoring): - def __init__( - self, - project, - resource, - name="Extract Variable", - progressCallback=None, - startOffset=None, - endOffset=None, - newName="new_Name", - similar=False, - global_=False, - ): - BaseRefactoring.__init__(self, project, resource, name, progressCallback) - self._newName = newName - self._startOffset = startOffset - self._endOffset = endOffset - self._similar = similar - self._global = global_ - - def onRefactor(self): - renamed = ExtractVariable( - self.project, self.resource, self._startOffset, self._endOffset - ) - changes = renamed.get_changes(self._newName, self._similar, self._global) - for item in changes.changes: - if isinstance(item, rope.base.change.ChangeContents): - self.changes.append( - Change(item.resource.real_path, ChangeType.EDIT, get_diff(item)) - ) - else: - raise Exception("Unknown Change") - - -class ExtractMethodRefactor(ExtractVariableRefactor): - def __init__( - self, - project, - resource, - name="Extract Method", - progressCallback=None, - startOffset=None, - endOffset=None, - newName="new_Name", - similar=False, - global_=False, - ): - ExtractVariableRefactor.__init__( - self, - project, - resource, - name, - progressCallback, - startOffset=startOffset, - endOffset=endOffset, - newName=newName, - similar=similar, - global_=global_, - ) - - def onRefactor(self): - renamed = ExtractMethod( - self.project, self.resource, self._startOffset, self._endOffset - ) - changes = renamed.get_changes(self._newName, self._similar, self._global) - for item in changes.changes: - if isinstance(item, rope.base.change.ChangeContents): - self.changes.append( - Change(item.resource.real_path, ChangeType.EDIT, get_diff(item)) - ) - else: - raise Exception("Unknown Change") - - -class RopeRefactoring(object): - def __init__(self): - self.default_sys_path = sys.path - self._input = io.open(sys.stdin.fileno(), encoding="utf-8") - - def _rename(self, filePath, start, newName, indent_size): - """ - Renames a variable - """ - project = rope.base.project.Project( - WORKSPACE_ROOT, - ropefolder=ROPE_PROJECT_FOLDER, - save_history=False, - indent_size=indent_size, - ) - resourceToRefactor = libutils.path_to_resource(project, filePath) - refactor = RenameRefactor( - project, resourceToRefactor, startOffset=start, newName=newName - ) - refactor.refactor() - changes = refactor.changes - project.close() - valueToReturn = [] - for change in changes: - valueToReturn.append({"diff": change.diff}) - return valueToReturn - - def _extractVariable(self, filePath, start, end, newName, indent_size): - """ - Extracts a variable - """ - project = rope.base.project.Project( - WORKSPACE_ROOT, - ropefolder=ROPE_PROJECT_FOLDER, - save_history=False, - indent_size=indent_size, - ) - resourceToRefactor = libutils.path_to_resource(project, filePath) - refactor = ExtractVariableRefactor( - project, - resourceToRefactor, - startOffset=start, - endOffset=end, - newName=newName, - similar=True, - ) - refactor.refactor() - changes = refactor.changes - project.close() - valueToReturn = [] - for change in changes: - valueToReturn.append({"diff": change.diff}) - return valueToReturn - - def _extractMethod(self, filePath, start, end, newName, indent_size): - """ - Extracts a method - """ - project = rope.base.project.Project( - WORKSPACE_ROOT, - ropefolder=ROPE_PROJECT_FOLDER, - save_history=False, - indent_size=indent_size, - ) - resourceToRefactor = libutils.path_to_resource(project, filePath) - refactor = ExtractMethodRefactor( - project, - resourceToRefactor, - startOffset=start, - endOffset=end, - newName=newName, - similar=True, - ) - refactor.refactor() - changes = refactor.changes - project.close() - valueToReturn = [] - for change in changes: - valueToReturn.append({"diff": change.diff}) - return valueToReturn - - def _serialize(self, identifier, results): - """ - Serializes the refactor results - """ - return json.dumps({"id": identifier, "results": results}) - - def _deserialize(self, request): - """Deserialize request from VSCode. - - Args: - request: String with raw request from VSCode. - - Returns: - Python dictionary with request data. - """ - return json.loads(request) - - def _process_request(self, request): - """Accept serialized request from VSCode and write response.""" - request = self._deserialize(request) - lookup = request.get("lookup", "") - - if lookup == "": - pass - elif lookup == "rename": - changes = self._rename( - request["file"], - int(request["start"]), - request["name"], - int(request["indent_size"]), - ) - return self._write_response(self._serialize(request["id"], changes)) - elif lookup == "extract_variable": - changes = self._extractVariable( - request["file"], - int(request["start"]), - int(request["end"]), - request["name"], - int(request["indent_size"]), - ) - return self._write_response(self._serialize(request["id"], changes)) - elif lookup == "extract_method": - changes = self._extractMethod( - request["file"], - int(request["start"]), - int(request["end"]), - request["name"], - int(request["indent_size"]), - ) - return self._write_response(self._serialize(request["id"], changes)) - - def _write_response(self, response): - sys.stdout.write(response + "\n") - sys.stdout.flush() - - def watch(self): - self._write_response("STARTED") - while True: - try: - self._process_request(self._input.readline()) - except: - exc_type, exc_value, exc_tb = sys.exc_info() - tb_info = traceback.extract_tb(exc_tb) - jsonMessage = { - "error": True, - "message": str(exc_value), - "traceback": str(tb_info), - "type": str(exc_type), - } - sys.stderr.write(json.dumps(jsonMessage)) - sys.stderr.flush() - - -if __name__ == "__main__": - RopeRefactoring().watch() diff --git a/pythonFiles/runJediLanguageServer.py b/pythonFiles/runJediLanguageServer.py deleted file mode 100644 index a473bf76b3a8..000000000000 --- a/pythonFiles/runJediLanguageServer.py +++ /dev/null @@ -1,13 +0,0 @@ -import re -import sys -import os - -# Add the lib path to our sys path so jedi_language_server can find its references -EXTENSION_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -sys.path.append(os.path.join(EXTENSION_ROOT, "pythonFiles", "lib", "python")) - -from jedi_language_server.cli import cli - -# Trick language server into thinking it started from 'jedi-language-server.exe' -sys.argv[0] = "jedi-language-server.exe" -sys.exit(cli()) diff --git a/pythonFiles/sortImports.py b/pythonFiles/sortImports.py deleted file mode 100644 index 070f7883fd66..000000000000 --- a/pythonFiles/sortImports.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -import io -import os -import os.path -import sys - -isort_path = os.path.join(os.path.dirname(__file__), "lib", "python") -sys.path.insert(0, isort_path) - -import isort.main - -isort.main.main() diff --git a/pythonFiles/symbolProvider.py b/pythonFiles/symbolProvider.py deleted file mode 100644 index 033ce4b99900..000000000000 --- a/pythonFiles/symbolProvider.py +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -import ast -import json -import sys - - -class Visitor(ast.NodeVisitor): - def __init__(self): - self.symbols = {"classes": [], "methods": [], "functions": []} - - def visit_Module(self, node): - self.visitChildren(node) - - def visitChildren(self, node, namespace=""): - for child in node.body: - if isinstance(child, ast.FunctionDef): - self.visitDef(child, namespace) - if isinstance(child, ast.ClassDef): - self.visitClassDef(child, namespace) - try: - if isinstance(child, ast.AsyncFunctionDef): - self.visitDef(child, namespace) - except Exception: - pass - - def visitDef(self, node, namespace=""): - end_position = self.getEndPosition(node) - symbol = "functions" if namespace == "" else "methods" - self.symbols[symbol].append(self.getDataObject(node, namespace)) - - def visitClassDef(self, node, namespace=""): - end_position = self.getEndPosition(node) - self.symbols["classes"].append(self.getDataObject(node, namespace)) - - if len(namespace) > 0: - namespace = "{0}::{1}".format(namespace, node.name) - else: - namespace = node.name - self.visitChildren(node, namespace) - - def getDataObject(self, node, namespace=""): - end_position = self.getEndPosition(node) - return { - "namespace": namespace, - "name": node.name, - "range": { - "start": {"line": node.lineno - 1, "character": node.col_offset}, - "end": {"line": end_position[0], "character": end_position[1]}, - }, - } - - def getEndPosition(self, node): - if not hasattr(node, "body") or len(node.body) == 0: - return (node.lineno - 1, node.col_offset) - return self.getEndPosition(node.body[-1]) - - -def provide_symbols(source): - """Provides a list of all symbols in provided code. - - The list comprises of 3-item tuples that contain the starting line number, - ending line number and whether the statement is a single line. - - """ - tree = ast.parse(source) - visitor = Visitor() - visitor.visit(tree) - sys.stdout.write(json.dumps(visitor.symbols)) - sys.stdout.flush() - - -if __name__ == "__main__": - if len(sys.argv) == 3: - contents = sys.argv[2] - else: - with open(sys.argv[1], "r") as source: - contents = source.read() - - try: - default_encoding = sys.getdefaultencoding() - encoded_contents = contents.encode(default_encoding, "surrogateescape") - contents = encoded_contents.decode(default_encoding, "replace") - except (UnicodeError, LookupError): - pass - if isinstance(contents, bytes): - contents = contents.decode("utf8") - provide_symbols(contents) diff --git a/pythonFiles/testing_tools/adapter/__main__.py b/pythonFiles/testing_tools/adapter/__main__.py deleted file mode 100644 index 5857c63db049..000000000000 --- a/pythonFiles/testing_tools/adapter/__main__.py +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -from __future__ import absolute_import - -import argparse -import sys - -from . import pytest, report -from .errors import UnsupportedToolError, UnsupportedCommandError - - -TOOLS = { - "pytest": { - "_add_subparser": pytest.add_cli_subparser, - "discover": pytest.discover, - }, -} -REPORTERS = { - "discover": report.report_discovered, -} - - -def parse_args( - # the args to parse - argv=sys.argv[1:], - # the program name - prog=sys.argv[0], -): - """ - Return the subcommand & tool to run, along with its args. - - This defines the standard CLI for the different testing frameworks. - """ - parser = argparse.ArgumentParser( - description="Run Python testing operations.", - prog=prog, - # ... - ) - cmdsubs = parser.add_subparsers(dest="cmd") - - # Add "run" and "debug" subcommands when ready. - for cmdname in ["discover"]: - sub = cmdsubs.add_parser(cmdname) - subsubs = sub.add_subparsers(dest="tool") - for toolname in sorted(TOOLS): - try: - add_subparser = TOOLS[toolname]["_add_subparser"] - except KeyError: - continue - subsub = add_subparser(cmdname, toolname, subsubs) - if cmdname == "discover": - subsub.add_argument("--simple", action="store_true") - subsub.add_argument( - "--no-hide-stdio", dest="hidestdio", action="store_false" - ) - subsub.add_argument("--pretty", action="store_true") - - # Parse the args! - if "--" in argv: - sep_index = argv.index("--") - toolargs = argv[sep_index + 1 :] - argv = argv[:sep_index] - else: - toolargs = [] - args = parser.parse_args(argv) - ns = vars(args) - - cmd = ns.pop("cmd") - if not cmd: - parser.error("missing command") - - tool = ns.pop("tool") - if not tool: - parser.error("missing tool") - - return tool, cmd, ns, toolargs - - -def main( - toolname, - cmdname, - subargs, - toolargs, - # internal args (for testing): - _tools=TOOLS, - _reporters=REPORTERS, -): - try: - tool = _tools[toolname] - except KeyError: - raise UnsupportedToolError(toolname) - - try: - run = tool[cmdname] - report_result = _reporters[cmdname] - except KeyError: - raise UnsupportedCommandError(cmdname) - - parents, result = run(toolargs, **subargs) - report_result(result, parents, **subargs) - - -if __name__ == "__main__": - tool, cmd, subargs, toolargs = parse_args() - main(tool, cmd, subargs, toolargs) diff --git a/pythonFiles/testing_tools/adapter/discovery.py b/pythonFiles/testing_tools/adapter/discovery.py deleted file mode 100644 index 798aea1e93f1..000000000000 --- a/pythonFiles/testing_tools/adapter/discovery.py +++ /dev/null @@ -1,117 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -from __future__ import absolute_import, print_function - -import re - -from .util import fix_fileid, DIRNAME, NORMCASE -from .info import ParentInfo - - -FILE_ID_RE = re.compile( - r""" - ^ - (?: - ( .* [.] (?: py | txt ) \b ) # .txt for doctest files - ( [^.] .* )? - ) - $ - """, - re.VERBOSE, -) - - -def fix_nodeid( - nodeid, - kind, - rootdir=None, - # *, - _fix_fileid=fix_fileid, -): - if not nodeid: - raise ValueError("missing nodeid") - if nodeid == ".": - return nodeid - - fileid = nodeid - remainder = "" - if kind not in ("folder", "file"): - m = FILE_ID_RE.match(nodeid) - if m: - fileid, remainder = m.groups() - elif len(nodeid) > 1: - fileid = nodeid[:2] - remainder = nodeid[2:] - fileid = _fix_fileid(fileid, rootdir) - return fileid + (remainder or "") - - -class DiscoveredTests(object): - """A container for the discovered tests and their parents.""" - - def __init__(self): - self.reset() - - def __len__(self): - return len(self._tests) - - def __getitem__(self, index): - return self._tests[index] - - @property - def parents(self): - return sorted( - self._parents.values(), - # Sort by (name, id). - key=lambda p: (NORMCASE(p.root or p.name), p.id), - ) - - def reset(self): - """Clear out any previously discovered tests.""" - self._parents = {} - self._tests = [] - - def add_test(self, test, parents): - """Add the given test and its parents.""" - parentid = self._ensure_parent(test.path, parents) - # Updating the parent ID and the test ID aren't necessary if the - # provided test and parents (from the test collector) are - # properly generated. However, we play it safe here. - test = test._replace( - # Clean up the ID. - id=fix_nodeid(test.id, "test", test.path.root), - parentid=parentid, - ) - self._tests.append(test) - - def _ensure_parent( - self, - path, - parents, - # *, - _dirname=DIRNAME, - ): - rootdir = path.root - relpath = path.relfile - - _parents = iter(parents) - nodeid, name, kind = next(_parents) - # As in add_test(), the node ID *should* already be correct. - nodeid = fix_nodeid(nodeid, kind, rootdir) - _parentid = nodeid - for parentid, parentname, parentkind in _parents: - # As in add_test(), the parent ID *should* already be correct. - parentid = fix_nodeid(parentid, kind, rootdir) - if kind in ("folder", "file"): - info = ParentInfo(nodeid, kind, name, rootdir, relpath, parentid) - relpath = _dirname(relpath) - else: - info = ParentInfo(nodeid, kind, name, rootdir, None, parentid) - self._parents[(rootdir, nodeid)] = info - nodeid, name, kind = parentid, parentname, parentkind - assert nodeid == "." - info = ParentInfo(nodeid, kind, name=rootdir) - self._parents[(rootdir, nodeid)] = info - - return _parentid diff --git a/pythonFiles/testing_tools/adapter/errors.py b/pythonFiles/testing_tools/adapter/errors.py deleted file mode 100644 index 3e6ae5189cb8..000000000000 --- a/pythonFiles/testing_tools/adapter/errors.py +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - - -class UnsupportedToolError(ValueError): - def __init__(self, tool): - msg = "unsupported tool {!r}".format(tool) - super(UnsupportedToolError, self).__init__(msg) - self.tool = tool - - -class UnsupportedCommandError(ValueError): - def __init__(self, cmd): - msg = "unsupported cmd {!r}".format(cmd) - super(UnsupportedCommandError, self).__init__(msg) - self.cmd = cmd diff --git a/pythonFiles/testing_tools/adapter/info.py b/pythonFiles/testing_tools/adapter/info.py deleted file mode 100644 index f99ce0b6f9a2..000000000000 --- a/pythonFiles/testing_tools/adapter/info.py +++ /dev/null @@ -1,120 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -from collections import namedtuple - - -class SingleTestPath(namedtuple("TestPath", "root relfile func sub")): - """Where to find a single test.""" - - def __new__(cls, root, relfile, func, sub=None): - self = super(SingleTestPath, cls).__new__( - cls, - str(root) if root else None, - str(relfile) if relfile else None, - str(func) if func else None, - [str(s) for s in sub] if sub else None, - ) - return self - - def __init__(self, *args, **kwargs): - if self.root is None: - raise TypeError("missing id") - if self.relfile is None: - raise TypeError("missing kind") - # self.func may be None (e.g. for doctests). - # self.sub may be None. - - -class ParentInfo(namedtuple("ParentInfo", "id kind name root relpath parentid")): - - KINDS = ("folder", "file", "suite", "function", "subtest") - - def __new__(cls, id, kind, name, root=None, relpath=None, parentid=None): - self = super(ParentInfo, cls).__new__( - cls, - id=str(id) if id else None, - kind=str(kind) if kind else None, - name=str(name) if name else None, - root=str(root) if root else None, - relpath=str(relpath) if relpath else None, - parentid=str(parentid) if parentid else None, - ) - return self - - def __init__(self, *args, **kwargs): - if self.id is None: - raise TypeError("missing id") - if self.kind is None: - raise TypeError("missing kind") - if self.kind not in self.KINDS: - raise ValueError("unsupported kind {!r}".format(self.kind)) - if self.name is None: - raise TypeError("missing name") - if self.root is None: - if self.parentid is not None or self.kind != "folder": - raise TypeError("missing root") - if self.relpath is not None: - raise TypeError("unexpected relpath {}".format(self.relpath)) - elif self.parentid is None: - raise TypeError("missing parentid") - elif self.relpath is None and self.kind in ("folder", "file"): - raise TypeError("missing relpath") - - -class SingleTestInfo( - namedtuple("TestInfo", "id name path source markers parentid kind") -): - """Info for a single test.""" - - MARKERS = ("skip", "skip-if", "expected-failure") - KINDS = ("function", "doctest") - - def __new__(cls, id, name, path, source, markers, parentid, kind="function"): - self = super(SingleTestInfo, cls).__new__( - cls, - str(id) if id else None, - str(name) if name else None, - path or None, - str(source) if source else None, - [str(marker) for marker in markers or ()], - str(parentid) if parentid else None, - str(kind) if kind else None, - ) - return self - - def __init__(self, *args, **kwargs): - if self.id is None: - raise TypeError("missing id") - if self.name is None: - raise TypeError("missing name") - if self.path is None: - raise TypeError("missing path") - if self.source is None: - raise TypeError("missing source") - else: - srcfile, _, lineno = self.source.rpartition(":") - if not srcfile or not lineno or int(lineno) < 0: - raise ValueError("bad source {!r}".format(self.source)) - if self.markers: - badmarkers = [m for m in self.markers if m not in self.MARKERS] - if badmarkers: - raise ValueError("unsupported markers {!r}".format(badmarkers)) - if self.parentid is None: - raise TypeError("missing parentid") - if self.kind is None: - raise TypeError("missing kind") - elif self.kind not in self.KINDS: - raise ValueError("unsupported kind {!r}".format(self.kind)) - - @property - def root(self): - return self.path.root - - @property - def srcfile(self): - return self.source.rpartition(":")[0] - - @property - def lineno(self): - return int(self.source.rpartition(":")[-1]) diff --git a/pythonFiles/testing_tools/adapter/pytest/__init__.py b/pythonFiles/testing_tools/adapter/pytest/__init__.py deleted file mode 100644 index e894f7bcdb8e..000000000000 --- a/pythonFiles/testing_tools/adapter/pytest/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -from __future__ import absolute_import - -from ._cli import add_subparser as add_cli_subparser -from ._discovery import discover diff --git a/pythonFiles/testing_tools/adapter/pytest/_cli.py b/pythonFiles/testing_tools/adapter/pytest/_cli.py deleted file mode 100644 index 3d3eec09a199..000000000000 --- a/pythonFiles/testing_tools/adapter/pytest/_cli.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -from __future__ import absolute_import - -from ..errors import UnsupportedCommandError - - -def add_subparser(cmd, name, parent): - """Add a new subparser to the given parent and add args to it.""" - parser = parent.add_parser(name) - if cmd == "discover": - # For now we don't have any tool-specific CLI options to add. - pass - else: - raise UnsupportedCommandError(cmd) - return parser diff --git a/pythonFiles/testing_tools/adapter/pytest/_discovery.py b/pythonFiles/testing_tools/adapter/pytest/_discovery.py deleted file mode 100644 index 51c94527302d..000000000000 --- a/pythonFiles/testing_tools/adapter/pytest/_discovery.py +++ /dev/null @@ -1,109 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -from __future__ import absolute_import, print_function - -import sys - -import pytest - -from .. import util, discovery -from ._pytest_item import parse_item - - -def discover( - pytestargs=None, - hidestdio=False, - # *, - _pytest_main=pytest.main, - _plugin=None, - **_ignored -): - """Return the results of test discovery.""" - if _plugin is None: - _plugin = TestCollector() - - pytestargs = _adjust_pytest_args(pytestargs) - # We use this helper rather than "-pno:terminal" due to possible - # platform-dependent issues. - with (util.hide_stdio() if hidestdio else util.noop_cm()) as stdio: - ec = _pytest_main(pytestargs, [_plugin]) - # See: https://docs.pytest.org/en/latest/usage.html#possible-exit-codes - if ec == 5: - # No tests were discovered. - pass - elif ec != 0: - print( - "equivalent command: {} -m pytest {}".format( - sys.executable, util.shlex_unsplit(pytestargs) - ) - ) - if hidestdio: - print(stdio.getvalue(), file=sys.stderr) - sys.stdout.flush() - raise Exception("pytest discovery failed (exit code {})".format(ec)) - if not _plugin._started: - print( - "equivalent command: {} -m pytest {}".format( - sys.executable, util.shlex_unsplit(pytestargs) - ) - ) - if hidestdio: - print(stdio.getvalue(), file=sys.stderr) - sys.stdout.flush() - raise Exception("pytest discovery did not start") - return ( - _plugin._tests.parents, - list(_plugin._tests), - ) - - -def _adjust_pytest_args(pytestargs): - """Return a corrected copy of the given pytest CLI args.""" - pytestargs = list(pytestargs) if pytestargs else [] - # Duplicate entries should be okay. - pytestargs.insert(0, "--collect-only") - # TODO: pull in code from: - # src/client/testing/pytest/services/discoveryService.ts - # src/client/testing/pytest/services/argsService.ts - return pytestargs - - -class TestCollector(object): - """This is a pytest plugin that collects the discovered tests.""" - - @classmethod - def parse_item(cls, item): - return parse_item(item) - - def __init__(self, tests=None): - if tests is None: - tests = discovery.DiscoveredTests() - self._tests = tests - self._started = False - - # Relevant plugin hooks: - # https://docs.pytest.org/en/latest/reference.html#collection-hooks - - def pytest_collection_modifyitems(self, session, config, items): - self._started = True - self._tests.reset() - for item in items: - test, parents = self.parse_item(item) - if test is not None: - self._tests.add_test(test, parents) - - # This hook is not specified in the docs, so we also provide - # the "modifyitems" hook just in case. - def pytest_collection_finish(self, session): - self._started = True - try: - items = session.items - except AttributeError: - # TODO: Is there an alternative? - return - self._tests.reset() - for item in items: - test, parents = self.parse_item(item) - if test is not None: - self._tests.add_test(test, parents) diff --git a/pythonFiles/testing_tools/adapter/pytest/_pytest_item.py b/pythonFiles/testing_tools/adapter/pytest/_pytest_item.py deleted file mode 100644 index f9ed1fe0f289..000000000000 --- a/pythonFiles/testing_tools/adapter/pytest/_pytest_item.py +++ /dev/null @@ -1,604 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -""" -During "collection", pytest finds all the tests it supports. These are -called "items". The process is top-down, mostly tracing down through -the file system. Aside from its own machinery, pytest supports hooks -that find tests. Effectively, pytest starts with a set of "collectors"; -objects that can provide a list of tests and sub-collectors. All -collectors in the resulting tree are visited and the tests aggregated. -For the most part, each test's (and collector's) parent is identified -as the collector that collected it. - -Collectors and items are collectively identified as "nodes". The pytest -API relies on collector and item objects providing specific methods and -attributes. In addition to corresponding base classes, pytest provides -a number of concrete implementations. - -The following are the known pytest node types: - - Node - Collector - FSCollector - Session (the top-level collector) - File - Module - Package - DoctestTextfile - DoctestModule - PyCollector - (Module) - (...) - Class - UnitTestCase - Instance - Item - Function - TestCaseFunction - DoctestItem - -Here are the unique attrs for those classes: - - Node - name - nodeid (readonly) - config - session - (parent) - the parent node - (fspath) - the file from which the node was collected - ---- - own_marksers - explicit markers (e.g. with @pytest.mark()) - keywords - extra_keyword_matches - - Item - location - where the actual test source code is: (relfspath, lno, fullname) - user_properties - - PyCollector - module - class - instance - obj - - Function - module - class - instance - obj - function - (callspec) - (fixturenames) - funcargs - originalname - w/o decorations, e.g. [...] for parameterized - - DoctestItem - dtest - obj - -When parsing an item, we make use of the following attributes: - -* name -* nodeid -* __class__ - + __name__ -* fspath -* location -* function - + __name__ - + __code__ - + __closure__ -* own_markers -""" - -from __future__ import absolute_import, print_function - -import sys - -import pytest -import _pytest.doctest -import _pytest.unittest - -from ..info import SingleTestInfo, SingleTestPath -from ..util import fix_fileid, PATH_SEP, NORMCASE - - -def should_never_reach_here(item, **extra): - """Indicates a code path we should never reach.""" - print("The Python extension has run into an unexpected situation") - print("while processing a pytest node during test discovery. Please") - print("Please open an issue at:") - print(" https://github.com/microsoft/vscode-python/issues") - print("and paste the following output there.") - print() - for field, info in _summarize_item(item): - print("{}: {}".format(field, info)) - if extra: - print() - print("extra info:") - for name, info in extra.items(): - print("{:10}".format(name + ":"), end="") - if isinstance(info, str): - print(info) - else: - try: - print(*info) - except TypeError: - print(info) - print() - print("traceback:") - import traceback - - traceback.print_stack() - - msg = "Unexpected pytest node (see printed output)." - exc = NotImplementedError(msg) - exc.item = item - return exc - - -def parse_item( - item, - # *, - _get_item_kind=(lambda *a: _get_item_kind(*a)), - _parse_node_id=(lambda *a: _parse_node_id(*a)), - _split_fspath=(lambda *a: _split_fspath(*a)), - _get_location=(lambda *a: _get_location(*a)), -): - """Return (TestInfo, [suite ID]) for the given item. - - The suite IDs, if any, are in parent order with the item's direct - parent at the beginning. The parent of the last suite ID (or of - the test if there are no suites) is the file ID, which corresponds - to TestInfo.path. - - """ - # _debug_item(item, showsummary=True) - kind, _ = _get_item_kind(item) - # Skip plugin generated tests - if kind is None: - return None, None - (nodeid, parents, fileid, testfunc, parameterized) = _parse_node_id( - item.nodeid, kind - ) - # Note: testfunc does not necessarily match item.function.__name__. - # This can result from importing a test function from another module. - - # Figure out the file. - testroot, relfile = _split_fspath(str(item.fspath), fileid, item) - location, fullname = _get_location(item, testroot, relfile) - if kind == "function": - if testfunc and fullname != testfunc + parameterized: - raise should_never_reach_here( - item, - fullname=fullname, - testfunc=testfunc, - parameterized=parameterized, - # ... - ) - elif kind == "doctest": - if testfunc and fullname != testfunc and fullname != "[doctest] " + testfunc: - raise should_never_reach_here( - item, - fullname=fullname, - testfunc=testfunc, - # ... - ) - testfunc = None - - # Sort out the parent. - if parents: - parentid, _, _ = parents[0] - else: - parentid = None - - # Sort out markers. - # See: https://docs.pytest.org/en/latest/reference.html#marks - markers = set() - for marker in getattr(item, "own_markers", []): - if marker.name == "parameterize": - # We've already covered these. - continue - elif marker.name == "skip": - markers.add("skip") - elif marker.name == "skipif": - markers.add("skip-if") - elif marker.name == "xfail": - markers.add("expected-failure") - # We can add support for other markers as we need them? - - test = SingleTestInfo( - id=nodeid, - name=item.name, - path=SingleTestPath( - root=testroot, - relfile=relfile, - func=testfunc, - sub=[parameterized] if parameterized else None, - ), - source=location, - markers=sorted(markers) if markers else None, - parentid=parentid, - ) - if parents and parents[-1] == (".", None, "folder"): # This should always be true? - parents[-1] = (".", testroot, "folder") - return test, parents - - -def _split_fspath( - fspath, - fileid, - item, - # *, - _normcase=NORMCASE, -): - """Return (testroot, relfile) for the given fspath. - - "relfile" will match "fileid". - """ - # "fileid" comes from nodeid and is always relative to the testroot - # (with a "./" prefix). There are no guarantees about casing, so we - # normcase just be to sure. - relsuffix = fileid[1:] # Drop (only) the "." prefix. - if not _normcase(fspath).endswith(_normcase(relsuffix)): - raise should_never_reach_here( - item, - fspath=fspath, - fileid=fileid, - # ... - ) - testroot = fspath[: -len(fileid) + 1] # Ignore the "./" prefix. - relfile = "." + fspath[-len(fileid) + 1 :] # Keep the pathsep. - return testroot, relfile - - -def _get_location( - item, - testroot, - relfile, - # *, - _matches_relfile=(lambda *a: _matches_relfile(*a)), - _is_legacy_wrapper=(lambda *a: _is_legacy_wrapper(*a)), - _unwrap_decorator=(lambda *a: _unwrap_decorator(*a)), - _pathsep=PATH_SEP, -): - """Return (loc str, fullname) for the given item.""" - # When it comes to normcase, we favor relfile (from item.fspath) - # over item.location in this function. - - srcfile, lineno, fullname = item.location - if _matches_relfile(srcfile, testroot, relfile): - srcfile = relfile - else: - # pytest supports discovery of tests imported from other - # modules. This is reflected by a different filename - # in item.location. - - if _is_legacy_wrapper(srcfile): - srcfile = relfile - unwrapped = _unwrap_decorator(item.function) - if unwrapped is None: - # It was an invalid legacy wrapper so we just say - # "somewhere in relfile". - lineno = None - else: - _srcfile, lineno = unwrapped - if not _matches_relfile(_srcfile, testroot, relfile): - # For legacy wrappers we really expect the wrapped - # function to be in relfile. So here we ignore any - # other file and just say "somewhere in relfile". - lineno = None - elif _matches_relfile(srcfile, testroot, relfile): - srcfile = relfile - # Otherwise we just return the info from item.location as-is. - - if not srcfile.startswith("." + _pathsep): - srcfile = "." + _pathsep + srcfile - - if lineno is None: - lineno = -1 # i.e. "unknown" - - # from pytest, line numbers are 0-based - location = "{}:{}".format(srcfile, int(lineno) + 1) - return location, fullname - - -def _matches_relfile( - srcfile, - testroot, - relfile, - # *, - _normcase=NORMCASE, - _pathsep=PATH_SEP, -): - """Return True if "srcfile" matches the given relfile.""" - testroot = _normcase(testroot) - srcfile = _normcase(srcfile) - relfile = _normcase(relfile) - if srcfile == relfile: - return True - elif srcfile == relfile[len(_pathsep) + 1 :]: - return True - elif srcfile == testroot + relfile[1:]: - return True - else: - return False - - -def _is_legacy_wrapper( - srcfile, - # *, - _pathsep=PATH_SEP, - _pyversion=sys.version_info, -): - """Return True if the test might be wrapped. - - In Python 2 unittest's decorators (e.g. unittest.skip) do not wrap - properly, so we must manually unwrap them. - """ - if _pyversion > (3,): - return False - if (_pathsep + "unittest" + _pathsep + "case.py") not in srcfile: - return False - return True - - -def _unwrap_decorator(func): - """Return (filename, lineno) for the func the given func wraps. - - If the wrapped func cannot be identified then return None. Likewise - for the wrapped filename. "lineno" is None if it cannot be found - but the filename could. - """ - try: - func = func.__closure__[0].cell_contents - except (IndexError, AttributeError): - return None - else: - if not callable(func): - return None - try: - filename = func.__code__.co_filename - except AttributeError: - return None - else: - try: - lineno = func.__code__.co_firstlineno - 1 - except AttributeError: - return (filename, None) - else: - return filename, lineno - - -def _parse_node_id( - testid, - kind, - # *, - _iter_nodes=(lambda *a: _iter_nodes(*a)), -): - """Return the components of the given node ID, in heirarchical order.""" - nodes = iter(_iter_nodes(testid, kind)) - - testid, name, kind = next(nodes) - parents = [] - parameterized = None - if kind == "doctest": - parents = list(nodes) - fileid, _, _ = parents[0] - return testid, parents, fileid, name, parameterized - elif kind is None: - fullname = None - else: - if kind == "subtest": - node = next(nodes) - parents.append(node) - funcid, funcname, _ = node - parameterized = testid[len(funcid) :] - elif kind == "function": - funcname = name - else: - raise should_never_reach_here( - testid, - kind=kind, - # ... - ) - fullname = funcname - - for node in nodes: - parents.append(node) - parentid, name, kind = node - if kind == "file": - fileid = parentid - break - elif fullname is None: - # We don't guess how to interpret the node ID for these tests. - continue - elif kind == "suite": - fullname = name + "." + fullname - else: - raise should_never_reach_here( - testid, - node=node, - # ... - ) - else: - fileid = None - parents.extend(nodes) # Add the rest in as-is. - - return ( - testid, - parents, - fileid, - fullname, - parameterized or "", - ) - - -def _iter_nodes( - testid, - kind, - # *, - _normalize_test_id=(lambda *a: _normalize_test_id(*a)), - _normcase=NORMCASE, - _pathsep=PATH_SEP, -): - """Yield (nodeid, name, kind) for the given node ID and its parents.""" - nodeid, testid = _normalize_test_id(testid, kind) - if len(nodeid) > len(testid): - testid = "." + _pathsep + testid - - if kind == "function" and nodeid.endswith("]"): - funcid, sep, parameterized = nodeid.partition("[") - if not sep: - raise should_never_reach_here( - nodeid, - # ... - ) - yield (nodeid, sep + parameterized, "subtest") - nodeid = funcid - - parentid, _, name = nodeid.rpartition("::") - if not parentid: - if kind is None: - # This assumes that plugins can generate nodes that do not - # have a parent. All the builtin nodes have one. - yield (nodeid, name, kind) - return - # We expect at least a filename and a name. - raise should_never_reach_here( - nodeid, - # ... - ) - yield (nodeid, name, kind) - - # Extract the suites. - while "::" in parentid: - suiteid = parentid - parentid, _, name = parentid.rpartition("::") - yield (suiteid, name, "suite") - - # Extract the file and folders. - fileid = parentid - raw = testid[: len(fileid)] - _parentid, _, filename = _normcase(fileid).rpartition(_pathsep) - parentid = fileid[: len(_parentid)] - raw, name = raw[: len(_parentid)], raw[-len(filename) :] - yield (fileid, name, "file") - # We're guaranteed at least one (the test root). - while _pathsep in _normcase(parentid): - folderid = parentid - _parentid, _, foldername = _normcase(folderid).rpartition(_pathsep) - parentid = folderid[: len(_parentid)] - raw, name = raw[: len(parentid)], raw[-len(foldername) :] - yield (folderid, name, "folder") - # We set the actual test root later at the bottom of parse_item(). - testroot = None - yield (parentid, testroot, "folder") - - -def _normalize_test_id( - testid, - kind, - # *, - _fix_fileid=fix_fileid, - _pathsep=PATH_SEP, -): - """Return the canonical form for the given node ID.""" - while "::()::" in testid: - testid = testid.replace("::()::", "::") - if kind is None: - return testid, testid - orig = testid - - # We need to keep the testid as-is, or else pytest won't recognize - # it when we try to use it later (e.g. to run a test). The only - # exception is that we add a "./" prefix for relative paths. - # Note that pytest always uses "/" as the path separator in IDs. - fileid, sep, remainder = testid.partition("::") - fileid = _fix_fileid(fileid) - if not fileid.startswith("./"): # Absolute "paths" not expected. - raise should_never_reach_here( - testid, - fileid=fileid, - # ... - ) - testid = fileid + sep + remainder - - return testid, orig - - -def _get_item_kind(item): - """Return (kind, isunittest) for the given item.""" - if isinstance(item, _pytest.doctest.DoctestItem): - return "doctest", False - elif isinstance(item, _pytest.unittest.TestCaseFunction): - return "function", True - elif isinstance(item, pytest.Function): - # We *could* be more specific, e.g. "method", "subtest". - return "function", False - else: - return None, False - - -############################# -# useful for debugging - -_FIELDS = [ - "nodeid", - "kind", - "class", - "name", - "fspath", - "location", - "function", - "markers", - "user_properties", - "attrnames", -] - - -def _summarize_item(item): - if not hasattr(item, "nodeid"): - yield "nodeid", item - return - - for field in _FIELDS: - try: - if field == "kind": - yield field, _get_item_kind(item) - elif field == "class": - yield field, item.__class__.__name__ - elif field == "markers": - yield field, item.own_markers - # yield field, list(item.iter_markers()) - elif field == "attrnames": - yield field, dir(item) - else: - yield field, getattr(item, field, "") - except Exception as exc: - yield field, "".format(exc) - - -def _debug_item(item, showsummary=False): - item._debugging = True - try: - summary = dict(_summarize_item(item)) - finally: - item._debugging = False - - if showsummary: - print(item.nodeid) - for key in ( - "kind", - "class", - "name", - "fspath", - "location", - "func", - "markers", - "props", - ): - print(" {:12} {}".format(key, summary[key])) - print() - - return summary diff --git a/pythonFiles/testing_tools/adapter/report.py b/pythonFiles/testing_tools/adapter/report.py deleted file mode 100644 index bacdef7b9a00..000000000000 --- a/pythonFiles/testing_tools/adapter/report.py +++ /dev/null @@ -1,94 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -from __future__ import print_function - -import json - - -def report_discovered( - tests, - parents, - # *, - pretty=False, - simple=False, - _send=print, - **_ignored -): - """Serialize the discovered tests and write to stdout.""" - if simple: - data = [ - { - "id": test.id, - "name": test.name, - "testroot": test.path.root, - "relfile": test.path.relfile, - "lineno": test.lineno, - "testfunc": test.path.func, - "subtest": test.path.sub or None, - "markers": test.markers or [], - } - for test in tests - ] - else: - byroot = {} - for parent in parents: - rootdir = parent.name if parent.root is None else parent.root - try: - root = byroot[rootdir] - except KeyError: - root = byroot[rootdir] = { - "id": rootdir, - "parents": [], - "tests": [], - } - if not parent.root: - root["id"] = parent.id - continue - root["parents"].append( - { - # "id" must match what the testing framework recognizes. - "id": parent.id, - "kind": parent.kind, - "name": parent.name, - "parentid": parent.parentid, - } - ) - if parent.relpath is not None: - root["parents"][-1]["relpath"] = parent.relpath - for test in tests: - # We are guaranteed that the parent was added. - root = byroot[test.path.root] - testdata = { - # "id" must match what the testing framework recognizes. - "id": test.id, - "name": test.name, - # TODO: Add a "kind" field - # (e.g. "unittest", "function", "doctest") - "source": test.source, - "markers": test.markers or [], - "parentid": test.parentid, - } - root["tests"].append(testdata) - data = [ - { - "rootid": byroot[root]["id"], - "root": root, - "parents": byroot[root]["parents"], - "tests": byroot[root]["tests"], - } - for root in sorted(byroot) - ] - - kwargs = {} - if pretty: - # human-formatted - kwargs = dict( - sort_keys=True, - indent=4, - separators=(",", ": "), - # ... - ) - serialized = json.dumps(data, **kwargs) - - _send(serialized) diff --git a/pythonFiles/testing_tools/adapter/util.py b/pythonFiles/testing_tools/adapter/util.py deleted file mode 100644 index 77778c5b6126..000000000000 --- a/pythonFiles/testing_tools/adapter/util.py +++ /dev/null @@ -1,287 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -import contextlib -import io - -try: - from io import StringIO -except ImportError: - from StringIO import StringIO # 2.7 -import os -import os.path -import sys -import tempfile - - -@contextlib.contextmanager -def noop_cm(): - yield - - -def group_attr_names(attrnames): - grouped = { - "dunder": [], - "private": [], - "constants": [], - "classes": [], - "vars": [], - "other": [], - } - for name in attrnames: - if name.startswith("__") and name.endswith("__"): - group = "dunder" - elif name.startswith("_"): - group = "private" - elif name.isupper(): - group = "constants" - elif name.islower(): - group = "vars" - elif name == name.capitalize(): - group = "classes" - else: - group = "other" - grouped[group].append(name) - return grouped - - -if sys.version_info < (3,): - _str_to_lower = lambda val: val.decode().lower() -else: - _str_to_lower = str.lower - - -############################# -# file paths - -_os_path = os.path -# Uncomment to test Windows behavior on non-windows OS: -# import ntpath as _os_path -PATH_SEP = _os_path.sep -NORMCASE = _os_path.normcase -DIRNAME = _os_path.dirname -BASENAME = _os_path.basename -IS_ABS_PATH = _os_path.isabs -PATH_JOIN = _os_path.join - - -def fix_path( - path, - # *, - _pathsep=PATH_SEP, -): - """Return a platform-appropriate path for the given path.""" - if not path: - return "." - return path.replace("/", _pathsep) - - -def fix_relpath( - path, - # *, - _fix_path=fix_path, - _path_isabs=IS_ABS_PATH, - _pathsep=PATH_SEP, -): - """Return a ./-prefixed, platform-appropriate path for the given path.""" - path = _fix_path(path) - if path in (".", ".."): - return path - if not _path_isabs(path): - if not path.startswith("." + _pathsep): - path = "." + _pathsep + path - return path - - -def _resolve_relpath( - path, - rootdir=None, - # *, - _path_isabs=IS_ABS_PATH, - _normcase=NORMCASE, - _pathsep=PATH_SEP, -): - # "path" is expected to use "/" for its path separator, regardless - # of the provided "_pathsep". - - if path.startswith("./"): - return path[2:] - if not _path_isabs(path): - return path - - # Deal with root-dir-as-fileid. - _, sep, relpath = path.partition("/") - if sep and not relpath.replace("/", ""): - return "" - - if rootdir is None: - return None - rootdir = _normcase(rootdir) - if not rootdir.endswith(_pathsep): - rootdir += _pathsep - - if not _normcase(path).startswith(rootdir): - return None - return path[len(rootdir) :] - - -def fix_fileid( - fileid, - rootdir=None, - # *, - normalize=False, - strictpathsep=None, - _pathsep=PATH_SEP, - **kwargs -): - """Return a pathsep-separated file ID ("./"-prefixed) for the given value. - - The file ID may be absolute. If so and "rootdir" is - provided then make the file ID relative. If absolute but "rootdir" - is not provided then leave it absolute. - """ - if not fileid or fileid == ".": - return fileid - - # We default to "/" (forward slash) as the final path sep, since - # that gives us a consistent, cross-platform result. (Windows does - # actually support "/" as a path separator.) Most notably, node IDs - # from pytest use "/" as the path separator by default. - _fileid = fileid.replace(_pathsep, "/") - - relpath = _resolve_relpath( - _fileid, - rootdir, - _pathsep=_pathsep, - # ... - **kwargs - ) - if relpath: # Note that we treat "" here as an absolute path. - _fileid = "./" + relpath - - if normalize: - if strictpathsep: - raise ValueError("cannot normalize *and* keep strict path separator") - _fileid = _str_to_lower(_fileid) - elif strictpathsep: - # We do not use _normcase since we want to preserve capitalization. - _fileid = _fileid.replace("/", _pathsep) - return _fileid - - -############################# -# stdio - - -@contextlib.contextmanager -def _replace_fd(file, target): - """ - Temporarily replace the file descriptor for `file`, - for which sys.stdout or sys.stderr is passed. - """ - try: - fd = file.fileno() - except (AttributeError, io.UnsupportedOperation): - # `file` does not have fileno() so it's been replaced from the - # default sys.stdout, etc. Return with noop. - yield - return - target_fd = target.fileno() - - # Keep the original FD to be restored in the finally clause. - dup_fd = os.dup(fd) - try: - # Point the FD at the target. - os.dup2(target_fd, fd) - try: - yield - finally: - # Point the FD back at the original. - os.dup2(dup_fd, fd) - finally: - os.close(dup_fd) - - -@contextlib.contextmanager -def _replace_stdout(target): - orig = sys.stdout - sys.stdout = target - try: - yield orig - finally: - sys.stdout = orig - - -@contextlib.contextmanager -def _replace_stderr(target): - orig = sys.stderr - sys.stderr = target - try: - yield orig - finally: - sys.stderr = orig - - -if sys.version_info < (3,): - _coerce_unicode = lambda s: unicode(s) -else: - _coerce_unicode = lambda s: s - - -@contextlib.contextmanager -def _temp_io(): - sio = StringIO() - with tempfile.TemporaryFile("r+") as tmp: - try: - yield sio, tmp - finally: - tmp.seek(0) - buff = tmp.read() - sio.write(_coerce_unicode(buff)) - - -@contextlib.contextmanager -def hide_stdio(): - """Swallow stdout and stderr.""" - with _temp_io() as (sio, fileobj): - with _replace_fd(sys.stdout, fileobj): - with _replace_stdout(fileobj): - with _replace_fd(sys.stderr, fileobj): - with _replace_stderr(fileobj): - yield sio - - -############################# -# shell - - -def shlex_unsplit(argv): - """Return the shell-safe string for the given arguments. - - This effectively the equivalent of reversing shlex.split(). - """ - argv = [_quote_arg(a) for a in argv] - return " ".join(argv) - - -try: - from shlex import quote as _quote_arg -except ImportError: - - def _quote_arg(arg): - parts = None - for i, c in enumerate(arg): - if c.isspace(): - pass - elif c == '"': - pass - elif c == "'": - c = "'\"'\"'" - else: - continue - if parts is None: - parts = list(arg) - parts[i] = c - if parts is not None: - arg = "'" + "".join(parts) + "'" - return arg diff --git a/pythonFiles/testing_tools/run_adapter.py b/pythonFiles/testing_tools/run_adapter.py deleted file mode 100644 index 1eeef194f8f5..000000000000 --- a/pythonFiles/testing_tools/run_adapter.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -# Replace the "." entry. -import os.path -import sys - -sys.path.insert( - 1, - os.path.dirname( # pythonFiles - os.path.dirname( # pythonFiles/testing_tools - os.path.abspath(__file__) # this file - ) - ), -) - -from testing_tools.adapter.__main__ import parse_args, main - - -if __name__ == "__main__": - tool, cmd, subargs, toolargs = parse_args() - main(tool, cmd, subargs, toolargs) diff --git a/pythonFiles/testlauncher.py b/pythonFiles/testlauncher.py deleted file mode 100644 index 95ca800b9885..000000000000 --- a/pythonFiles/testlauncher.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -import os -import sys - - -def parse_argv(): - """Parses arguments for use with the test launcher. - Arguments are: - 1. Working directory. - 2. Test runner, `pytest` or `nose` - 3. Rest of the arguments are passed into the test runner. - """ - - return (sys.argv[1], sys.argv[2], sys.argv[3:]) - - -def run(cwd, testRunner, args): - """Runs the test - cwd -- the current directory to be set - testRunner -- test runner to be used `pytest` or `nose` - args -- arguments passed into the test runner - """ - - sys.path[0] = os.getcwd() - os.chdir(cwd) - - try: - if testRunner == "pytest": - import pytest - - pytest.main(args) - else: - import nose - - nose.run(argv=args) - sys.exit(0) - finally: - pass - - -if __name__ == "__main__": - cwd, testRunner, args = parse_argv() - run(cwd, testRunner, args) diff --git a/pythonFiles/tests/__init__.py b/pythonFiles/tests/__init__.py deleted file mode 100644 index e2e6976acd68..000000000000 --- a/pythonFiles/tests/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -import os.path - -TEST_ROOT = os.path.dirname(__file__) -SRC_ROOT = os.path.dirname(TEST_ROOT) -PROJECT_ROOT = os.path.dirname(SRC_ROOT) -IPYTHON_ROOT = os.path.join(SRC_ROOT, "ipython") -TESTING_TOOLS_ROOT = os.path.join(SRC_ROOT, "testing_tools") -DEBUG_ADAPTER_ROOT = os.path.join(SRC_ROOT, "debug_adapter") - -PYTHONFILES = os.path.join(SRC_ROOT, "lib", "python") diff --git a/pythonFiles/tests/__main__.py b/pythonFiles/tests/__main__.py deleted file mode 100644 index 14086978c9af..000000000000 --- a/pythonFiles/tests/__main__.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -import argparse -import sys - -import pytest - -from . import DEBUG_ADAPTER_ROOT, IPYTHON_ROOT, SRC_ROOT, TEST_ROOT, TESTING_TOOLS_ROOT - - -def parse_args(): - parser = argparse.ArgumentParser() - # To mark a test as functional: (decorator) @pytest.mark.functional - parser.add_argument( - "--functional", dest="markers", action="append_const", const="functional" - ) - parser.add_argument( - "--no-functional", dest="markers", action="append_const", const="not functional" - ) - args, remainder = parser.parse_known_args() - - ns = vars(args) - - return ns, remainder - - -def main(pytestargs, markers=None): - sys.path.insert(1, IPYTHON_ROOT) - sys.path.insert(1, TESTING_TOOLS_ROOT) - sys.path.insert(1, DEBUG_ADAPTER_ROOT) - - pytestargs = ["--rootdir", SRC_ROOT, TEST_ROOT] + pytestargs - for marker in reversed(markers or ()): - pytestargs.insert(0, marker) - pytestargs.insert(0, "-m") - - ec = pytest.main(pytestargs) - return ec - - -if __name__ == "__main__": - mainkwargs, pytestargs = parse_args() - ec = main(pytestargs, **mainkwargs) - sys.exit(ec) diff --git a/pythonFiles/tests/debug_adapter/test_install_debugpy.py b/pythonFiles/tests/debug_adapter/test_install_debugpy.py deleted file mode 100644 index 19565c19675c..000000000000 --- a/pythonFiles/tests/debug_adapter/test_install_debugpy.py +++ /dev/null @@ -1,37 +0,0 @@ -import os -import pytest -import subprocess -import sys - - -def _check_binaries(dir_path): - expected_endswith = ( - "win_amd64.pyd", - "win32.pyd", - "darwin.so", - "i386-linux-gnu.so", - "x86_64-linux-gnu.so", - ) - - binaries = list(p for p in os.listdir(dir_path) if p.endswith(expected_endswith)) - - assert len(binaries) == len(expected_endswith) - - -@pytest.mark.skipif( - sys.version_info[:2] != (3, 7), - reason="DEBUGPY wheels shipped for Python 3.7 only", -) -def test_install_debugpy(tmpdir): - import install_debugpy - - install_debugpy.main(str(tmpdir)) - dir_path = os.path.join( - str(tmpdir), "debugpy", "_vendored", "pydevd", "_pydevd_bundle" - ) - _check_binaries(dir_path) - - dir_path = os.path.join( - str(tmpdir), "debugpy", "_vendored", "pydevd", "_pydevd_frame_eval" - ) - _check_binaries(dir_path) diff --git a/pythonFiles/tests/ipython/getJupyterVariableList.py b/pythonFiles/tests/ipython/getJupyterVariableList.py deleted file mode 100644 index d9b0778fa2d2..000000000000 --- a/pythonFiles/tests/ipython/getJupyterVariableList.py +++ /dev/null @@ -1,38 +0,0 @@ -# Query Jupyter server for defined variables list -# Tested on 2.7 and 3.6 -from sys import getsizeof as _VSCODE_getsizeof -import json as _VSCODE_json -from IPython import get_ipython as _VSCODE_get_ipython - -# _VSCode_supportsDataExplorer will contain our list of data explorer supported types -_VSCode_supportsDataExplorer = "['list', 'Series', 'dict', 'ndarray', 'DataFrame']" - -# who_ls is a Jupyter line magic to fetch currently defined vars -_VSCode_JupyterVars = _VSCODE_get_ipython().run_line_magic("who_ls", "") - -_VSCode_output = [] -for _VSCode_var in _VSCode_JupyterVars: - try: - _VSCode_type = type(eval(_VSCode_var)) - _VSCode_output.append( - { - "name": _VSCode_var, - "type": _VSCode_type.__name__, - "size": _VSCODE_getsizeof(_VSCode_var), - "supportsDataExplorer": _VSCode_type.__name__ - in _VSCode_supportsDataExplorer, - } - ) - del _VSCode_type - del _VSCode_var - except: - pass - -print(_VSCODE_json.dumps(_VSCode_output)) - -del _VSCODE_get_ipython -del _VSCode_output -del _VSCode_supportsDataExplorer -del _VSCode_JupyterVars -del _VSCODE_json -del _VSCODE_getsizeof diff --git a/pythonFiles/tests/ipython/getJupyterVariableValue.py b/pythonFiles/tests/ipython/getJupyterVariableValue.py deleted file mode 100644 index 6110d8653dee..000000000000 --- a/pythonFiles/tests/ipython/getJupyterVariableValue.py +++ /dev/null @@ -1,475 +0,0 @@ -import sys as VC_sys -import locale as VC_locale - -VC_IS_PY2 = VC_sys.version_info < (3,) - -# SafeRepr based on the pydevd implementation -# https://github.com/microsoft/ptvsd/blob/master/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_safe_repr.py -class VC_SafeRepr(object): - # Py3 compat - alias unicode to str, and xrange to range - try: - unicode # noqa - except NameError: - unicode = str - try: - xrange # noqa - except NameError: - xrange = range - - # Can be used to override the encoding from locale.getpreferredencoding() - locale_preferred_encoding = None - - # Can be used to override the encoding used for sys.stdout.encoding - sys_stdout_encoding = None - - # String types are truncated to maxstring_outer when at the outer- - # most level, and truncated to maxstring_inner characters inside - # collections. - maxstring_outer = 2 ** 16 - maxstring_inner = 30 - if not VC_IS_PY2: - string_types = (str, bytes) - set_info = (set, "{", "}", False) - frozenset_info = (frozenset, "frozenset({", "})", False) - int_types = (int,) - long_iter_types = (list, tuple, bytearray, range, dict, set, frozenset) - else: - string_types = (str, unicode) - set_info = (set, "set([", "])", False) - frozenset_info = (frozenset, "frozenset([", "])", False) - int_types = (int, long) # noqa - long_iter_types = ( - list, - tuple, - bytearray, - xrange, - dict, - set, - frozenset, - buffer, - ) # noqa - - # Collection types are recursively iterated for each limit in - # maxcollection. - maxcollection = (15, 10) - - # Specifies type, prefix string, suffix string, and whether to include a - # comma if there is only one element. (Using a sequence rather than a - # mapping because we use isinstance() to determine the matching type.) - collection_types = [ - (tuple, "(", ")", True), - (list, "[", "]", False), - frozenset_info, - set_info, - ] - try: - from collections import deque - - collection_types.append((deque, "deque([", "])", False)) - except Exception: - pass - - # type, prefix string, suffix string, item prefix string, - # item key/value separator, item suffix string - dict_types = [(dict, "{", "}", "", ": ", "")] - try: - from collections import OrderedDict - - dict_types.append((OrderedDict, "OrderedDict([", "])", "(", ", ", ")")) - except Exception: - pass - - # All other types are treated identically to strings, but using - # different limits. - maxother_outer = 2 ** 16 - maxother_inner = 30 - - convert_to_hex = False - raw_value = False - - def __call__(self, obj): - try: - if VC_IS_PY2: - return "".join( - (x.encode("utf-8") if isinstance(x, unicode) else x) - for x in self._repr(obj, 0) - ) - else: - return "".join(self._repr(obj, 0)) - except Exception as e: - try: - return "An exception was raised: " + str(e) - except Exception: - return "An exception was raised" - - def _repr(self, obj, level): - """Returns an iterable of the parts in the final repr string.""" - - try: - obj_repr = type(obj).__repr__ - except Exception: - obj_repr = None - - def has_obj_repr(t): - r = t.__repr__ - try: - return obj_repr == r - except Exception: - return obj_repr is r - - for t, prefix, suffix, comma in self.collection_types: - if isinstance(obj, t) and has_obj_repr(t): - return self._repr_iter(obj, level, prefix, suffix, comma) - - for ( - t, - prefix, - suffix, - item_prefix, - item_sep, - item_suffix, - ) in self.dict_types: # noqa - if isinstance(obj, t) and has_obj_repr(t): - return self._repr_dict( - obj, level, prefix, suffix, item_prefix, item_sep, item_suffix - ) - - for t in self.string_types: - if isinstance(obj, t) and has_obj_repr(t): - return self._repr_str(obj, level) - - if self._is_long_iter(obj): - return self._repr_long_iter(obj) - - return self._repr_other(obj, level) - - # Determines whether an iterable exceeds the limits set in - # maxlimits, and is therefore unsafe to repr(). - def _is_long_iter(self, obj, level=0): - try: - # Strings have their own limits (and do not nest). Because - # they don't have __iter__ in 2.x, this check goes before - # the next one. - if isinstance(obj, self.string_types): - return len(obj) > self.maxstring_inner - - # If it's not an iterable (and not a string), it's fine. - if not hasattr(obj, "__iter__"): - return False - - # If it's not an instance of these collection types then it - # is fine. Note: this is a fix for - # https://github.com/Microsoft/ptvsd/issues/406 - if not isinstance(obj, self.long_iter_types): - return False - - # Iterable is its own iterator - this is a one-off iterable - # like generator or enumerate(). We can't really count that, - # but repr() for these should not include any elements anyway, - # so we can treat it the same as non-iterables. - if obj is iter(obj): - return False - - # xrange reprs fine regardless of length. - if isinstance(obj, xrange): - return False - - # numpy and scipy collections (ndarray etc) have - # self-truncating repr, so they're always safe. - try: - module = type(obj).__module__.partition(".")[0] - if module in ("numpy", "scipy"): - return False - except Exception: - pass - - # Iterables that nest too deep are considered long. - if level >= len(self.maxcollection): - return True - - # It is too long if the length exceeds the limit, or any - # of its elements are long iterables. - if hasattr(obj, "__len__"): - try: - size = len(obj) - except Exception: - size = None - if size is not None and size > self.maxcollection[level]: - return True - return any( - (self._is_long_iter(item, level + 1) for item in obj) - ) # noqa - return any( - i > self.maxcollection[level] or self._is_long_iter(item, level + 1) - for i, item in enumerate(obj) - ) # noqa - - except Exception: - # If anything breaks, assume the worst case. - return True - - def _repr_iter(self, obj, level, prefix, suffix, comma_after_single_element=False): - yield prefix - - if level >= len(self.maxcollection): - yield "..." - else: - count = self.maxcollection[level] - yield_comma = False - for item in obj: - if yield_comma: - yield ", " - yield_comma = True - - count -= 1 - if count <= 0: - yield "..." - break - - for p in self._repr(item, 100 if item is obj else level + 1): - yield p - else: - if comma_after_single_element: - if count == self.maxcollection[level] - 1: - yield "," - yield suffix - - def _repr_long_iter(self, obj): - try: - length = hex(len(obj)) if self.convert_to_hex else len(obj) - obj_repr = "<%s, len() = %s>" % (type(obj).__name__, length) - except Exception: - try: - obj_repr = "<" + type(obj).__name__ + ">" - except Exception: - obj_repr = "" - yield obj_repr - - def _repr_dict( - self, obj, level, prefix, suffix, item_prefix, item_sep, item_suffix - ): - if not obj: - yield prefix + suffix - return - if level >= len(self.maxcollection): - yield prefix + "..." + suffix - return - - yield prefix - - count = self.maxcollection[level] - yield_comma = False - - try: - sorted_keys = sorted(obj) - except Exception: - sorted_keys = list(obj) - - for key in sorted_keys: - if yield_comma: - yield ", " - yield_comma = True - - count -= 1 - if count <= 0: - yield "..." - break - - yield item_prefix - for p in self._repr(key, level + 1): - yield p - - yield item_sep - - try: - item = obj[key] - except Exception: - yield "" - else: - for p in self._repr(item, 100 if item is obj else level + 1): - yield p - yield item_suffix - - yield suffix - - def _repr_str(self, obj, level): - return self._repr_obj(obj, level, self.maxstring_inner, self.maxstring_outer) - - def _repr_other(self, obj, level): - return self._repr_obj(obj, level, self.maxother_inner, self.maxother_outer) - - def _repr_obj(self, obj, level, limit_inner, limit_outer): - try: - if self.raw_value: - # For raw value retrieval, ignore all limits. - if isinstance(obj, bytes): - yield obj.decode("latin-1") - return - - try: - mv = memoryview(obj) - except Exception: - yield self._convert_to_unicode_or_bytes_repr(repr(obj)) - return - else: - # Map bytes to Unicode codepoints with same values. - yield mv.tobytes().decode("latin-1") - return - elif self.convert_to_hex and isinstance(obj, self.int_types): - obj_repr = hex(obj) - else: - obj_repr = repr(obj) - except Exception: - try: - obj_repr = object.__repr__(obj) - except Exception: - try: - obj_repr = ( - "" - ) # noqa - except Exception: - obj_repr = "" - - limit = limit_inner if level > 0 else limit_outer - - if limit >= len(obj_repr): - yield self._convert_to_unicode_or_bytes_repr(obj_repr) - return - - # Slightly imprecise calculations - we may end up with a string that is - # up to 3 characters longer than limit. If you need precise formatting, - # you are using the wrong class. - left_count, right_count = ( - max(1, int(2 * limit / 3)), - max(1, int(limit / 3)), - ) # noqa - - if VC_IS_PY2 and isinstance(obj_repr, bytes): - # If we can convert to unicode before slicing, that's better (but don't do - # it if it's not possible as we may be dealing with actual binary data). - - obj_repr = self._bytes_as_unicode_if_possible(obj_repr) - if isinstance(obj_repr, unicode): - # Deal with high-surrogate leftovers on Python 2. - try: - if left_count > 0 and unichr(0xD800) <= obj_repr[ - left_count - 1 - ] <= unichr(0xDBFF): - left_count -= 1 - except ValueError: - # On Jython unichr(0xD800) will throw an error: - # ValueError: unichr() arg is a lone surrogate in range (0xD800, 0xDFFF) (Jython UTF-16 encoding) - # Just ignore it in this case. - pass - - start = obj_repr[:left_count] - - # Note: yielding unicode is fine (it'll be properly converted to utf-8 if needed). - yield start - yield "..." - - # Deal with high-surrogate leftovers on Python 2. - try: - if right_count > 0 and unichr(0xD800) <= obj_repr[ - -right_count - 1 - ] <= unichr(0xDBFF): - right_count -= 1 - except ValueError: - # On Jython unichr(0xD800) will throw an error: - # ValueError: unichr() arg is a lone surrogate in range (0xD800, 0xDFFF) (Jython UTF-16 encoding) - # Just ignore it in this case. - pass - - yield obj_repr[-right_count:] - return - else: - # We can't decode it (binary string). Use repr() of bytes. - obj_repr = repr(obj_repr) - - yield obj_repr[:left_count] - yield "..." - yield obj_repr[-right_count:] - - def _convert_to_unicode_or_bytes_repr(self, obj_repr): - if VC_IS_PY2 and isinstance(obj_repr, bytes): - obj_repr = self._bytes_as_unicode_if_possible(obj_repr) - if isinstance(obj_repr, bytes): - # If we haven't been able to decode it this means it's some binary data - # we can't make sense of, so, we need its repr() -- otherwise json - # encoding may break later on. - obj_repr = repr(obj_repr) - return obj_repr - - def _bytes_as_unicode_if_possible(self, obj_repr): - # We try to decode with 3 possible encoding (sys.stdout.encoding, - # locale.getpreferredencoding() and 'utf-8). If no encoding can decode - # the input, we return the original bytes. - try_encodings = [] - encoding = self.sys_stdout_encoding or getattr(VC_sys.stdout, "encoding", "") - if encoding: - try_encodings.append(encoding.lower()) - - preferred_encoding = ( - self.locale_preferred_encoding or VC_locale.getpreferredencoding() - ) - if preferred_encoding: - preferred_encoding = preferred_encoding.lower() - if preferred_encoding not in try_encodings: - try_encodings.append(preferred_encoding) - - if "utf-8" not in try_encodings: - try_encodings.append("utf-8") - - for encoding in try_encodings: - try: - return obj_repr.decode(encoding) - except UnicodeDecodeError: - pass - - return obj_repr # Return the original version (in bytes) - - -# Query Jupyter server for the value of a variable -import json as _VSCODE_json - -_VSCODE_max_len = 200 -# In IJupyterVariables.getValue this '_VSCode_JupyterTestValue' will be replaced with the json stringified value of the target variable -# Indexes off of _VSCODE_targetVariable need to index types that are part of IJupyterVariable -_VSCODE_targetVariable = _VSCODE_json.loads("""_VSCode_JupyterTestValue""") - -_VSCODE_evalResult = eval(_VSCODE_targetVariable["name"]) - -# Find shape and count if available -if hasattr(_VSCODE_evalResult, "shape"): - try: - # Get a bit more restrictive with exactly what we want to count as a shape, since anything can define it - if isinstance(_VSCODE_evalResult.shape, tuple): - _VSCODE_shapeStr = str(_VSCODE_evalResult.shape) - if ( - len(_VSCODE_shapeStr) >= 3 - and _VSCODE_shapeStr[0] == "(" - and _VSCODE_shapeStr[-1] == ")" - and "," in _VSCODE_shapeStr - ): - _VSCODE_targetVariable["shape"] = _VSCODE_shapeStr - del _VSCODE_shapeStr - except TypeError: - pass - -if hasattr(_VSCODE_evalResult, "__len__"): - try: - _VSCODE_targetVariable["count"] = len(_VSCODE_evalResult) - except TypeError: - pass - -# Use SafeRepr to get our short string value -VC_sr = VC_SafeRepr() -_VSCODE_targetVariable["value"] = VC_sr(_VSCODE_evalResult) - -print(_VSCODE_json.dumps(_VSCODE_targetVariable)) - -del VC_locale -del VC_IS_PY2 -del VC_sys -del VC_SafeRepr -del VC_sr diff --git a/pythonFiles/tests/ipython/random.csv b/pythonFiles/tests/ipython/random.csv deleted file mode 100644 index dde35fda3850..000000000000 --- a/pythonFiles/tests/ipython/random.csv +++ /dev/null @@ -1,6001 +0,0 @@ -".Lh~N","~`y","]W|{92","|uHbIo","o}","+h2" -"-O;Xfw ","","KX^K)FHe:","3+4ncqL8=O","py]ZA;","_y>" -"}z#IiI'+c,","^) ""","(SLt4","JJ:2VnAQ/J","ZO>%nl>`EZ","" -"","ji#cufxu2",":7JAHYDdRx","klq?$","tItAtlu-","TxspzZ" -"x9>iX'C+F","","G`[I","p^Y=S)",">NLE@UX|=a","a=XsO" -"`Y""Wc2^tQ","HlZ","N9/@","J","A","Cv" -"","L f'%K","~UDs","mh/A:X","Yz9d:T{2Q|","ee" -"`3A* >","Nu6cv","%F13Kb6p","","8+{hhd","J#","W3_yiE","y)d8?","" -";,.;rnu\K","nfS)Fy=l","",",r~$","jOU[uQYt'","R" -"3$cA$","l4","t!nf","53snc8g","m&,:","OQg\ " -"m>D","74|LkY5eV","/LR","h","","B]" -"60z{e92;","^P","5s-:m","G2YGn*DpK","I","" -"4s2(","n?j1,f~~?d","?","3{j,tax>8r","","Ms_\Z" -",i9sLk3z","D';Tv","R","m1keY","|`DumH","k.2-Mu9P" -"!","Y$","O","r","xj _*%*","#" -"HU","Z_jT0?","!2X>5","@9tD~BA)va","^ Gn=|q4H","eX" -"-","H,m=WD","fE;","YCTw<","""sS/{ow","","D@=iE'j4" -"Tqi1","hTF?RF""g=","","9{","m]?81+hk","1ZHfSk" -"J""oMEj","""*MZ`q; ","{","C","R|}Ws|j)d","s" -"6 k\:$@gl","bo8)dK#Sj","3Z3#","","J*:F>",".$" -"4Fx0(~dQ#U","F('2","YO~phil","/NmyUrJ~E","","" -"Or,B""b@HO.","uV2tX%yAE8","fn HXw","","7R^b","Pq%" -"W8BTO3","Vz{xaSpxd(","7YTQU}D]q[","b]B|5x","?i(kD","" -"","P~O","?m&SG1o{","(",").{1PSm[E","{" -"a","?","k&8)","","FP31","!""P^$!" -"90g=","{XN","FFH;b","r^XHX>@R&1","","4!sz=W#K" -"","##LY$m13","-(","`b","#K","Dd" -"|jA","34 Q","dU-;:OOU\U","nKO","*I","}+IJv" -".>j&","d-Gz","nOe6W""q$Zc","MYyF~","","FzO&u" -"!@>3&","YCyw0y","","","I(,","!fhPR+!=8N","*^tw&-Rt","V","","Sc3S" -"u;","#no","U","|B1`","[^o;Lyp5","" -".Vb[JH9x-s","9?<""y","","","8@1K7Jh","-ySc" -"R9S@g;fJ","ae4b","Q01]VuYAIu","J]W(PXVu`","xUw_LZ!8CB","" -",",":6","CLUFQ[9!","'K9\l^","_(OjmvR?","lT1GR;k" -"""+u$2VaoTD","","","@\|#G","","8I","Nx@R&g&sw","*r-Ltk","","" -"KB/ uy0","YJ6.].gQ-s","Q","RE[3RGSE4H","Q6a_)5p?","","|~O>&Xua","#cD&u","n~nM`]" -"V","-47Q8,k","53S""EIi%`","lDlQVv","A""}c1c='x:",";{4?6\" -"z'","YP5e4(U","l^mTI6qb",")sDm^JM)qV","","lw/yD34~","|uM5","","O6z>P}",":w!CBD" -"GUoP=,Kqc",")0|O","[A99FJC","uM:Fg","O{&","aQ;G_{m" -"D?1m8|8","Ule","CkEK+","Sfi8wyM","c1I~t","2/&bE=QJ:""" -"7XpO#@","I0L(t[`59","E&grAL3,","7","","" -"&tLwX)e","<#R#","&fp","}z@s","Gn","1ln[S!GP" -"k>Kb&YeB","5Qw=$-oq","&^","J15BT'z>~","sBq6&TRf","3FOj0T" -".6r$K4","\6f,.E'","k<","","4l(d 5","J[>x$1D2" -"""fto","lR`(+","?","TJ","","" -"8Nk","H","","]X","m.d%BI#q","; ,Pq?`;,%" -"5^}7sQ`","7>:L","I","3L\-","wA7Nh@rww","9VXVw%)~" -".HS","","","{7?+C","zo17eF|v"," R" -"vH#+T","?_Dk.Y","Gvdh+)-PhU","A]gB^<^e","R%X)","Gna*.n" -"?d","S","J:vQmT_[`^","W_X","`""S<.S}'","IC " -")AFHI(1't","K>]R","Bx","iWTE2fc","1<","m=B;x4n$" -"Zyu","q5C% i""b","$N)""mCi/o","ULa nXr{nv","u9HQx5,_1Z","XbKX" -"OP[s ^wl)6","y",".}2t=:","E,z@aBPR","qC=P3","`p}A63u,3%" -"X[d","Q0tsJ#*","gMR-.6&6:@","H"">M""CfcR","0}BSkSW@/e","R`Wi&`Djr" -"e&gUxU*A","0"," :","","Q>B","E" -"s%>WO5$o3g","D","s{%",")V&x)`@;","5=","1Sw" -"","Ym@","_","M`(*_B>P- " -"K","","2O#D?","%RTu>7Otx","zbN|","I""/" -"-)",".JzD'","bc[y^d#Y,","Tm0ud\","1H@;","n|q< L3i9U" -"&cc-XYJ",">51","0BKHX[","",""":0JR/g","\'\`5l\O" -"","o""L#w?4qY",";X7Qt","ivrB]@","R","% %-6A23" -"E-I[","0TPXa/8);o","T4hBVto0F","Lr[|_:Si","%(/x""","tT" -"","vWq`y[!","fZ{b-)R(","Y^","","bJr$}.<$>r" -"F","","t+r]s,=T#","b]1?U","5Ldjnsq4:N","u,7}qpR" -"","zftO.","3-y","","Y]BLTq3?","-x%#b" -"<[s2YJ&}_","bn","$>Rt7tHW",">!*4Ao","","}}" -"c]-jDpO","!LsvN,/+6","W+w&=l","""@fd`ho","wk+i#'Q","q/:k}" -"IMaj/y","L","g*d_:\:","YT\0","O<[/q@_""#a","UDQzt#5" -"ZFE","xb","oe*1.uQ/.","Xi1fJ8*\I","DXVd!(6>","5sBi}E5" -"8)","Pz","\\ L|","G|e&9","2gd#5)x Z{","gU\Jq]RrA" -"d0A$fck]","`" -"8","Y9ds?,""","^u","GUha]","]MR%qJ",":gq""`lO","8B;N:c?" -"EE"," b:(W9N","|;P""&9","v@qz","}B40.e%Utn","M,ynqkYg" -"e!/]Rz]$","cDQ!NE7","8@=6","d","4#/O","" -"zn","nA^@g!Z","8nt$sxb","t","","m#x{0;V11" -"","{tT5","9QnNs@A","\0URMK","]","j<""gt" -"&0/UGKz'G""","K:j)_","QS>g!7","$`9uc,CJZ,","`8+w,","`" -";'V-7","$p<`=","j^I","r2 ","","jy{" -"","m(hNrbi","h","Z9$V83J","+5]b","I" -":","<|>b[X1jX","c","T","","}r^hq4g" -"(vwf","wQvCnhksM","`","8K,3S'lHv","4o","A!q% P""LX" -"","rf8'y7;","X!Ur","s","?iDO",">h(." -"ui""R&JBr","&'7.u","2Y,l-5","?HB","DL""$","sAh||/l=" -"lXaB7[","1hY","j!%6 ow","Tf","i^r [F","OV9%h5nY}" -"Bp}nh7Ic+M","YH{L","y","o!9","K!<|","@D5" -"OjI/r'r*S4","WI=pe$lRG","'*MB","l","AQapJF4bTE","oBx>" -"WkoM","~H8GI,1p3V","Wu=B7{0j,","","xg)","IZ8l'1+I" -"Q3lB!","Q'N","}xi!#DkY&%","FNv","5a|FsN ","T!98" -"6","h#5{)<+A","#i7|fSA""W_","\u3].o" -"wI*","Rqk|kYv","]d |#lE",")FWt}%O9C","os","i@)=y9" -"","&u[Jq7","MsE24i/b%" -"b","{)\e?09""D","D*?Ybms*","vk6NN?7fJ","~q2Pc|","/vW:v }{J5" -"|@'o"," XH4 haX"," Y^0e>}","A",".]WK""",")bLp%mt(" -"_x&yU!GiV","WTc'5nj","*3NkkGk$'","f?,/V~","Z8g mW4","hW" -"wwE@@","","7Di?""Dt(lB","BU~Gg-","pd:" -";NonN$t5","","4G~X3zC3 ","IHHBOJ.","P}",">r*>W5" -"?x2""L4","sv|)/mN","+""$=1:8","p@~6#C>mM","I0&1Aaq7M@","n" -"NWnM;J","LXjXFA f","iUa;""","|k","662X.1;t"," " -"SBL6tq~B","Is|v9","KjJ[",")mgQ.;iw]","ERR?z","WcWgb#$q6e" -"7~a'`","C?6`","Rr>q&YV6I/","?b6w)G0","'jpc$","c zI9eD " -"_^_4{O&gbY","T^5__","w-[Py`(Qxf","*",":'EKBh#S","ljM" -"","c#sJ","W^2","@2`","\\OuBL""^mo","2wm6WA|x" -"8pkGz","V,/WQq*J7Y","&]2?7Q","EF.T9\@","[k","9YI)(eh:;7" -"$wk~","VkK:0Kz","u0!R~2","xY7#","9#","`j}6&" -"9","~S.E)b" -"Fw4l2A V","o`""Py","{Izy$K","OYsbb8","3]ZDq","8hPS1Pz" -"~"," fQwMd').]","PR&T.;<.\U","""gUK74[","EAn<","JQlWX5","1QI","%AQlZmNPE","2|A?","W^XqWP" -"a#,W(`B@5C","",")","Dz","y \O%","&5'U|J.8",";)SH","q(7","EH&+jZ 1!","HTh" -"bzr]","^;V8$b>B","1:aY5Yyw/","yr+&","noUrd","pkg" -"S(Cp&`e]","",">-c|%.m","","C7Y'u","X" -"ny","dNYQ","FWYe;y!","i?4","1,bY?=","`EjF-M" -"8;;V%","%wY","x~^veI","t" -"kYYbvo8<","C\|",",4g","Ax{sqF-Cv","}^COxg 9","o""" -"UPjp%M","!#:r.8&""Xw","W!/h2","^%^f~","t","oP" -"T{6","","Om=~""9s","u/o","cZ@.F|B09'","LI%e%65gY\" -"5 I","r","'F&o""e","qn{s $lV","*}tz1","" -"M","E74,J","=z3U,)","W[.!,","e:Tr&5<.h""","y$Iwo" -".H""","Qq42V79RQ","^Fn-6A0c{","iZ{yG}nV^1","Kw:)[","0-YkX}/0" -"XZc","","o%Z","","q{,",")J*z=wr" -"cvOCJ&O","x%cJc@","O2bJ2fo","m","","ob]_Tj@*v" -"_!sCApL>_","","qWa6A#$""ww","3'","m4I","L;8( {ye" -" ",")~z0g","YlIA{:Ey","","","k" -"ax#""rfEO","","X(/Pj}","CC?[0","sJI","","fir_*o","8 ShApVE_g","4k6m" -"&>b","c=9","!","I","","$/dYTpip/$" -"'","FGj*S|Z","{N8","eonWJSsY","","U" -"EL|*\!V$1`","Og""","","","lo!:^",">H1&sgt8Z" -"r~:","u","'/Bn?[Qf","nltE-YYzDB","I'ODVU{xr","" -"[I(]cD","<,/","x","","2x/fH!8%","O4a~X" -"})","QoMIAa","%G@","46;pHf*","}3rU @V","+`eU&/gG" -"HD\5","!f","j6OfLC","jX/UU)k" -"","","2@","4bZ+L{","Z|H","""p7u" -"PD'!gTiPcA","","<","0N4x`","n%\E7ol","$duR1IFA2I" -"","d_B","|","aa7;ac","","UviR" -"O@ei*, ","u'Tr","NJo","B*E^/Xo?Y1","BFk""){%:","TS?>s=?= h" -"Lt","f","PYgD c","a4/",">2?+A~+S","IS""" -"xG')3","q","","0k>U)ZI","uHg)U""X+","}uU1?*" -"M!0;Kgz_G","+","1:g}+","b,TC;%rTQ","Z+S,","" -"i=lps","[","$^cP","n2",":0,Wkb","9[URQ8>&" -"@3","UXHc|C","~DLL\QV||[","uQ.8 Q)+q","""","A" -"x2ZrZ7kTQV","","!R<","G%\fT=","E","-qG0{" -"'&)T%oz","{kBu,","OF6vL}FsX","OT'5=[nqH","VdOw+[WU","]SZBjN" -"","rdpPo","Yh4[SDH",".^t","fJttaStU","@%5!;^h\" -"/uT_)[","""{","[up:^Qgi","6GF","5j%$6xE[g","&l" -"&>-Lf7]U","z>","$qPD^>O8","&IzR","e","}" -"}8X%","yDw;c#""b!","Y/}~G","6Oh[ZJIwQ4","km+ _8D~","dTCu58x","sSXv","pBuA>|X;_","eM!):?EMc",".KU>","Vtd`wZQ}" -"y$?{2Ti","}GZcl-","\kQ4","mk_l7O;#.X","KDd[Qd","Gg0pL*" -"sg\dV'=5z","!S""nEgUx~","","QcOn","6","#" -"N#fa","a1|+SwUd5","rC(nF`N0Z>","nnPaaE"," ","yEwZ6DM" -"V","^YU~b","","*bJ3,N","V={@V{","_>#Z0*" -"I-~QAavV\","K6;#0k","o","8(" -"gwut0","","WU^","4","/!][iG,z","""W7+uh" -"j+?WYE2","w]&o4Fr","bBV^SU}Hv","9'C","!Q_7<","" -"J*ok_L.*Gk","$Xl","","_ut","g h7,w-","<1" -"Y>)?WdJ","=","""n","Xn2m[x","Q]mFLF","Sh/Ns" -",/^a5S;","Vxs%'","QivnALbO","","+b8","Sk" -"2","2KNAr""91[<","VJbQ","sZOSuh(5&","","E1+D" -"W""","[V:67mEA","~]c","J5<+pN#!K","%,b})","Y;.>:'SI}e" -"R>flcq%","4e]fB^","^>s}0","8*hE"," a%","lVsOax0'-" -"!l{<+3a?3B","sH","y","~` \hy@+","_'eQ2/o=FS","E" -",","*Y","c%Uz+']<","M28o"," 2o[WuQ0DP","(oWt3($" -"3E","_S""CKJV","x&#@hpdvR","aQ+$(","S","#r%" -"","}!Q#fiHh","I","","RV<[9pS-c2","Ot,1X" -"!u","yn]v5>","'","","Wkf#",")spuPy" -"`","&NgU{7}","zA]&&{kE9o","h,&|5","jWNzw","`[2p""lG2""R" -"9JEw>3","ccc~#;"," ","80UvW9;/","","zebGg>#." -"2{","?wLn]","OXM","8+","LST%bsEJ","," -"V'","OzdoP^ /s=","%*","goIg|b","u","KKQ:va/E-K" -"\xSS0y|2","&","n~S>A",";in)T|","B""V~,z,i\","._w" -"A","""","[e_o","CJhj;~-","3_e2",".%*61w3>\A" -"","f6bOj/M]-","nlZFc7g.",": ""s q~","is/?d)T","c9+sD3bT" -"C0a'R|D","|+","gNvw","","CCZ?F1","N\;C," -"iQTa+b[X8s","""H""X.|#B+Q","AP26-cwO","G","","""EM^D*%%^" -"!ICp*Lw","7[WHV""XH{","[@","U","'1P","RN6lj","F:;'l","*hs%1","" -"",",~&gR.","3fIG","rHMo","&","#" -"JnsY","@?I)p","%37+bN","yS)]_3YAX","\31p!_","n}M(" -"|5W4","@9j-","PB^HQ","{-cCfQ]s","S","m[x64" -"[x","C[zL>vh","h-Ix;N","1-e9 %Nh|","mwdP=M,","X2:d>'0@Ku" -"Mc={-!j","ovo>;Tr","@vC2xP]3","""'Ev","a[x68|<)}<","DX\y)" -"b5U","MDI","aQ,}$v~@7#" -"=""","[[f#7=Nv","mQ","f~\Z^",")""$-)qwJg","I+^]u" -"Q>`tw7+^"," X","[A_qc0FRd""","h_","Swb6$","vRY" -"Pr4","nma)mc9Oq","- TdOChW+i","WQk$A$:N","_IhGX_RS","cO","Ho3@l6","6?","X=_R,8C","GHr",">Yk9{wPy" -"g.XDqYM","J')g@","Nm87","l=?> f","HWQ``Hd",";Oh1""*z" -"tldt ","opHA{-W+)","*PVq*Nnxnk","8",";W7,peh'","-w3kZHa","","+/0|>*.","YCD#$","DowrP" -"m","c4","xd","E6(:>","","_|" -";&d%BsH|i6",",SSk","oZ4" -"24f,,4","bQf/","r\;","zlvdPU","","Vkkp1eZ" -"w@v4m ","","D&","C34$","ZAgUJEP","wfSP*DFUB" -"|\.B[M,|=","y","uV#$""","jS4i","w-Y","~V" -""" LS","{","","al;p","&,ocis]","qj" -"1h,: Pm~^J","tN5zO*QW","<9lU_","uEbd?Ms&$","%bA3O$a","" -"","3P4B","Vh*7lq2","hQuYV;7","0Q","~""3~3yvt,z" -"Aq","{8%N","I*,ssZ(:>u",":68[{yN","","""uu]3" -"I#]O","Gh)NfB##q6",":p","rENjq","o","iBwt(" -"Uw}}wTD","6",",e@rNam","WO","x2tPl","cR9h" -"_o8go","S/","BWo_","z","","t<51fb-j{" -"Xnp#=","(GIR","","*WjHzw","|" -"`ikUg","",",Y","CX+\","U9\HE)1Gz","Wzs0" -"Kt","VO34)\\!*","t~zJ9uYT-X","9`9iN^"," Pr","!F_+Mhf" -"ij+W","jG","","","a@eHP","~D2:" -"","f[fd?","4V","]{cy<>44]R","tagc@me=","Yer" -"=","_ ;Tc","ji36s//v7;y","!Uy.RT$z","","PP~.C,vubY","k3%am","2Gs" -"E7#&8hMZ","-o","^G&","2NCWWjXL_E","","OFIQ.r" -"k@^b","q","wb)etgW2","bE",":F|>=q","K$" -"jg6#j","i","c","^|)2YPk","+<","-4A!5a" -"^:P3{^S5","Z~Q","^.cKSYy","","t*34V2>","t-""Rn]""V:" -"","M?=gzH[v]","[6uvyAC41","d~D","%xe_X\t","I" -"zQkYWrs","(#F |*x-","Sd","h79{!","NQ1LSo)6","=v`fl" -"bp5tK",")YaaJs8","w:rSY4SNecLp" -"vCJ,a","wg;S8js7vt","","0d<@-_O)","{MKV^N","^2" -"d.D`","'""","x.[&","*dcV","{z","6y+0a" -"K{:u","-{u3aN""","Eqr8%O:!X ","5>!p","ZX>0W.~lx","/p/rU" -"taZxgwfN8]","","]P:9M","M30-ptS~]","m4.7\V#'?","dvB?" -"3rq%*Yw","%f4v:4Iu","","5oJgGtPUIb","JI","=D!R*~H" -"gkVe;yG","R\oQ^67)3","2W0|b+ c","/W@0[;]mm`","2<}8I@z","uq]A0=v" -"YI}","}XVL!C",";i","wL#cIdG%","eod 6*",""";[qK" -"r-I6%#","kMM","b<<#4f57Gh","LQ$EPga","kkVPwlN0","Y>%wr D5?(","R[3e,","-j$<","]6aw" -"foY#cWjL","h6{BO4$","c+oVNk","x9UO0","{RIdlhJ","o|E" -"h=ZW!:8Kl","oqW","","R,(5H.piCE","0T","*fAdr" -"e","bQ","","[u;.","/9r","P ^H*X" -"n=ZO@A,S)",":3OyJ];G2","sS","{SZ9?nF","""-0#","p{n" -"8N'p","oe`;S%&MH",";}p","v]@eS,+B",". Sv+p$","BQ1m1HqV-" -"19]4(J","$:+"," 4Pby*H","Gi26IA2<7","AwIo1~z","$_x" -"{,'","g+{!s}g","meNrc+n~",")","WIu19/","FUi!" -"t,k\",")4aJEL<","e3","18","=","aiBKyxK" -"11C/eT-N2","v$","O0Z","u*E9$&#dR","J#{}","p FFaC&$OJ" -"-~J","L:K6 ","?","1","<","vi6G]s?D" -"0-","$[:","H","D&A","JcPwaG,","V" -"9,f","aC3","","mq}","4","{U|@/l2T#{" -"&N""jfZ`","1q","sjM","PrM20;(7.","oA3xzI\","O!H" -"$QX~VR","H/uKon","y-:V","Y\L;'&;q","DfLF5/FDPc","3>MA.8]a" -"Z>E9a.5~","$F?","T~rp{iU","","XWh)V3","H}" -"2","MK8?$x""w|.","","V$F6J.!SO","","8yt" -"_NNof4","QCw-Qh+",".J4xa","jVGk=(+9","G=|q","]1vi>$k+d " -"C9GS_","1>R/V0y.","","!","4pS","E>O6moM~ S" -"OX7-=<(","Zyow$o:,F","Y2Zl'Z","+9Q","xC x9]RbF","jp/RqMEwy+" -"!4BKn>}t","EN","MK","mKD","zTZq'3j " -"b","{v@","QDSA","{--/MV1","F~<#`{9^Q\","bY" -"DBuM4""jm2","e{ho8:","l8S","7","7I~-&S","D#M(n+u""" -"{n~%(z8m","T{o=Q","5","8!>(k1w#","!VaD]",">b~_(^" -"JB^(gmt","kq^,","A#yVx","i.o3ia)kRm","ze'rP","[k" -")#","-P' t~","c/6&","U}","","a{tY","ZoU*EKdP","/X)qa","O[;\RyO{" -"Y","*r4PX<}","PGrVzC+Q","'W;LxII:xo","s&?a2xX","(" -">,E.D|OK","DuqRfMYR/","|=c[rOgPI*","PHCU","E[hAIr#!PZ","" -"|[dE1`>u^","-cCdxXD+>X","","0U(Np","","hfDi}:tI?z" -"!''G","I","X9","a","`4d,?]","B%)("" Q" -"F\l]\","I8)$]<^~","-?v!K7#p","","!cq:k1","/`=" -"","DzvVS","8 @D^BR9Dr","%z","^'GG|oeza","@-" -"gT^9f ","","","Q}q- }%G>\","Eo1<_&+","tf" -"`{2","bbVEi","GcMv`P$]a","bU","v}|B$vu=TTe-H","Za","Vc+M)kP","","Gog!3c)qY","t5gq|Y2" -"'+W5","vs","c9P","/hn)o(B","0/" -"\+EoGy/w",":7QF$]|QF","euVo|G","HNtoa5U{S","g",".-UFU2" -"",":OX+","9B","N-R;C*J}H","/","]" -"/z","/QI.l@","","n","=","Y)tF""sb$" -"%ZL)LVy?","Xkp*,",";K","w","bha""","eMqxFJK" -")mE)Ldb","","{&H?UX#%ZF","T.3zEzOIX","""A2""Q |y","on" -"gt,^[9>As","Vt*","')T%","M","d@I`7n*13","&dBxYQ-" -"*"" uH","^",">u_I;)o","]_f3d.","t#bY7M](","\{Ge" -"["," ?""o~aBm","r2*TV^2","{mP^$:B","","3""" -"*S)","hUS0<*9","x6F8!Yh","f","lr%""dcG;","P`e?\kp`#" -"GqL[_G","X>[G:_i","S5SIm2","HZ","v;gZ=@","lp""V+')i)E" -"'\%","","M@c2","1|_;IfHt!t","Na[b lUC","cV" -"","","N <","n","lDbQ","fOT}A&sROX" -"j#=9J","kGB9l","Krq2omq9","RH+[b","{ MFOQuYOY","1?NGx\~)" -"vsl","[r","z2ZYUr)R5","jLO","=9v9","IJ'S" -"6[J4","=.2dB!","(~qz|F","8S22","3.^bP_2vzx","p" -"x`58Q2","XlP","ATYEQ",":eF_8Ml$RL","","5-?UMGT<\w" -",CB]|vx","rWOb{k>&p-","Bp0M$-kap","yhl5<5f:","E]a","]=/HY]@" -"tn~","`=_829iI{m","V","CI2,(","","y*W-=" -"YQjb{","m","|?U&k598","","OZ5~","uf]|2TREJv" -"\"," >\$^EY(8_","Rjm*""t@","}C","5%1Gah","O" -"(q>Vc5a","=/","Ye3drpl","f? = >>",",,7l""%v","e!%ln^.<'" -"=mnib0@a","sr~n<8i(aD","ky19#u/ApN","u","?gV,i2","'#-,[:" -"B","h(GdA","H&","d1y*j&k","'xT-2c","7JlgbHY" -"9p1k",">","%% iXeV1)","p'z$we`=Pp","Fq","I>Y1" -"ZsJZ","Gji","\","*#0 h~(^-q","=MLU,A8s","dm.m3" -"0.e\O","|nW","6'?L&<","EJa","JU1","s_#Qhx" -"","Eg9","!mOw=-!+ol","*1y,8\=","58 z[","IGE^Q>l?fI" -"Rv(p",".vd~96=I>s","3u%+E","#B[0@","c","bGq" -"SScmE","l Kh","l79JXC3=VZ","k2","$o""~","p" -"uh","ex@7k2p#m","bl","v","c"," $)T[V" -"f:ryalK","j","""|ARTeqi","","K.)4" -"N8","3u~","TD","q_I5L2J3RJ","ye","h8[" -"Ko""$","uu')*69X","3*Bh/P}","\[G%U'6","6%?","5UPL,1RLK`" -"KesVA?FW""","{lIto~*N","_=?g3","qes","8},","NT)Hre" -"S4Pd","}BZ#!&","}g'G*IAs","]=tw}",";1yXn9","90" -"4X+/""{+Q","|gxc6","4B];'[Yj8;"," \5","d]","" -"X\rKIB{V","]b3N","q","5vTkM}T7","lHM2Ax`~[","dhq" -"jjz%c;qcn","%hr\`~2 ","","]nLj","ndwjy{o","e_h" -"Y[$n?3"," ","!","","$KJLM",".zA" -"CX'8`Y,","k>w&T","E!C?Ut:Y ","Zre&","","" -"XgDa","soo-9u:0q","K",":`(Vh9RTCU","OcN[FV","`","@{T!""" -"^\s8:FN'","3f965+","B$nZ8`$;$","N-<","-?e","-Z;cPI" -"3lG1=W]NK","O!E0I","$t>r$/Jwo","E","","Bbp1" -"","J[4","J.W^CyoH",")cD","L","eV&[/" -"9@HrYJ`3Fz",">4"," /o(oz%'Y","lnT+","Vehm>,E*","" -"^qTZ_","%E@5So","~>","_sg)T_IRY","ppXj4","%4'" -"fG","b@D~u","P","0c","T!~%w&anOY","q9?uTURqZ" -"~%v`aU","P",")k","*""","4QXuI;\>","," -"SElfh$",",g5x73","r64`Y","""&Hn[,dk0<","R-c*wcq","""8^O5GKL" -"",".","2/;","=uQO8Q0E)~","?-T0-K'a0_","*rjf%5 n8D" -"kQAL2EM*yH","ABf^~84v7","","*/","&:]'.l","+(@","Nz*QRNB%","T" -"_y$##@","","1N!7#j","D7)DVq","sQr X7r[","L" -"\R*,^A(","yFa=6%z","0#[aK","Grw!S$lY}h","peLP","NO" -"*!w>F","o8EU@","I|rc>2",",U% ]A","0","JS/z " -"p 58","@Py@~HGC%","42B^=^="," &3sM","x#3J}S:k",".}67" -"","m`,v","G@4","s","|t.}""h|.","b$R" -"5%","N0U4","","9F;r1:""H%","","w7^&" -"l\{m'0","{","""""~/","`j34B|","y*CLY4E4","*ny" -"BHw}Fk","V4T9V","","94B","b9'yX""","@q\<_Q" -"b67Vf","1b$8","Z$Ul","pvp0O4x>","3FvrBg?j2i","=M" -"+ItwpkKY,","^ady6^nv","","-wsfp?","LvOcvq","Zw" -"`te",",B=","mK7_A][`w&","!8@;`#!n+","@3o46!S\","" -"Wc)E=FQt3","`lkmDlK","~GO]!%E2Ne","[icn0_w1","sYsucGc","0" -"","0","S2mFv1y","sm""Z :",""," 5$" -"","ktca'+_","esj","","52l=I]5","e,]I8Bn" -"P%=2T[d","/+","63$Ze","^1pKUdv`#","=r!","t>kjX=.|" -"~a|/T","O$S","dW^$pZ","/NJOxu3","m%","S""giH{<4/F" -"sV9w|60?$","'-k@mf^","QJ<","QjO']-D~","EasA","0{8l`h&#" -"3-/ch9Wx,$.","yN<.t&!.","d%IVo2Vzf>","lH'y#fTEa","8d^XPhWmNG" -"l8eVte","{gYR2hLz)","[WxTo","E","NO_8)d=I","b+@ZqL&asM","Et\" -"KW d","|M!MP+&Oas","\z0`","U","g.k","HSMo" -"E],@","7=Kx$""","pA3","CcM","l=a!="," kvcxW" -"`g","Ym6/V+&3z","I3""`1l*Uj","LrxBm:3i","","Rw;*X4CH" -"N","Ag","""F7i_z|&","","Dl1 {N*05i","?N" -"Ox0hNYyJ^;","","2U|-","<|kg=[SCxr","7q?Wj","u>J" -"wrkVHq{","UCs","D","V^[oTJ","C4*K3","yz{Q" -"M}`0+?1","1F-@r?","s","]","ZvOZYk" -"Hc%lPt+i",":#j","7-v20","I@\/{)^","QX",";Fw 8G>~" -"d-k_FKj,3V",";M8Gi""`F(I","m","Co","|.I{w","Ph>NyA" -"9@@?;","t:$x:^`","r?i","e","vK@a&G(","FG3\7oh" -"yxwt\5,o,r","&3s","M?c~oD","'jFO}OY","~t)V~.","(+w1j" -"=pRx|!E","wE>*zq;0Pk","","YAm&%VT&)","j|.i","" -"+Pi","*g","]7>%k;","=.*Ywgzs|","4<\B`$=4","vKY]" -"H'qizT3 u1","|l2#vYI{F","0","C.W@ ;8","=!&&;<","p,2{" -"FMxz{KT~","30}LX[.C@E","\5Q","","pAM=","r+r;eJ" -",","'N!*""","@","miy#0pL]<","mf{","#z0`s]" -"g r&","{",")l@9","P7,j%","0Jt","v" -"&^('>1q9x""","cSE=x4=5","N|MptOz*Z","","\;x/8m:","#T" -"0VJ[","","NE[jM ^X!","""^(Odv AY","","0" -"%7MW hq-","Nfl","?","m:7Mv~}G|","&.x&1","}*+qI'%" -"TLS ","8pCh0is","a","Vdg\ m","cP+n,X1k","/TD/lD7=(~" -",r 3a)o","mOV==","/6","~]eD","","=u""c" -"#-'mX","t/{Bl8","69_z)g\|X","","Ss;Avh}m#6","Zyl+?g" -"3","2:t}71?6j+",":[Q'`K","","))V","c" -"bxL/((`N'","A2","btd)xs" -"`Yo~7*KPX+","x5b!R@#","Z)*pc","VrckVlGQdy","","(g%'C1","n+.l0B6" -"W)#l? 2TgT","7`","~a;L6","I","q`v/|lbQR","$" -"A""Gx{s","dc:vtO;","lx`4,a","V-q w'%","M","|q]" -"*8","K8/|;"," ik<`N1M3,","/","","FJc3JZ m" -";!","M5+%]","+Kt(542;","!V|","pu","]Izuu}" -"+2W/#]R`","",")B^$","","2MK$<""=qX","^dS|" -"z*","","}R~eL)","-]sC?%"">Z","#R82z)vsh","mCRO`$a" -"s2","B&*Y%uUOOE","T|c!","6","*iP@.QXy+F`T(5E","aE=o=f;","<","dd ","NJ{ &axo",";P\cD" -"UEd`F^m","&-gNnM","$28lUuo","","Wl","]#" -"d8M","+",">","7","ijMrGEC6O","p" -"f@_","[","x","b>\f_|8G^","N;,nK","lK,f|Fz^,`" -"frpz","","jI|qqIi","E9N%x","WT","bC(sD" -"t","MJ1> x3","z@IF*y!zP","(T!gW,o","m1rNM","d""A;" -"3Qk.' ","$r_:","O=G!(","","!cFa|h.Q0o","'~u(\eY#" -"r!vO[4","\","0_&w`]mIb{","m","","r " -"V","DDU#eF_f% ","A&]a|2C/","*Sj3@$A0,",">1\41PE","Z(P3aR@" -"","u=Vc3ls","twjqlZX*","J_,H@2","?bgPa","aIj" -"Zgw""MIQQxG","P","?{pK^n]","Nr1 D#xJ","D","iVnJt" -":E&n","_""K4.","%x]","","T@Y\roh~N","LIL9i'8?" -"","~u","nA3 $V>0","!4 {","WLO)I","}$c=1$" -"f}Uc7g3LuX","+2-P","""x'^.","?","y!U!2-Kp8x","~F" -";1r""A>y9","","","=","P","" -"tjT","'","Gvl-"," q/!@N'0'",":XM","e:","jb?BuRT:" -"NR.%",")]5Lti(2~@","U=tm","WrArhRl""","y~","Eo]P" -"_ADu Z*F@","$r>X","mOJUz","g2","g6[@qWo5","C$" -"o(x]p","%f\","aQTSj","","Yh,u,X","5" -"JHSiP","WS@\","",";Y,{QZ`w","+![6/)*","",".X6 ","zx3p","l+6$g","YB7#~+4" -"W(XOUt","","ABY}~","JD-8o`g-XJ","l>ji d","2:Fb&Zl|" -"2h((k","P4]W,)","@zn$ux","P=","+f","#_" -"3`|e#/Rcv","Y",";8c{","zGY5ox",".?M~s11r","'r19)SNG" -"w|x$1#","l","{wT","*jYl-y|,O","$ @Lql",")um" -"Z[Q#",".jV^yJ","ZyyQm","Nr<+","(:1qzgtB(","f" -"f","UWn""Zf","n","7i[","gY=;Jx","" -"","MPH@{e,c*","Wi?~hQzOfX",",~Q/w['","t?x{<#N","CRcD" -"x+%Dvo:E{","8 qo","&+&ZDk$b%x",";\0> &^KZb","y/TR'1t+V","" -"q]\0|","iU>","",":#?A","L0;8&,[yhK","y[C7" -"(.",".et","a","\P@2Y-3{","","""wU\8a@" -"c[=5^}4","&I*P*","aLq5\&","N+/2V","xjE} o""","" -"M[U","gd","-4l_Q12wq","ZZT]L0","","d.kE)8B)" -"4uGc8E%m)E","gh-+X","}i","gZ9@?","Wwni","Xbq~XQ" -";:","O}ojl2i","1","^a~$","X^","$hA" -"GEJ","+`;>:<","VGU","@r#c-aRYm.","J>>","" -"fzi","",";hQZ_+b","F6(=` TY","H","M'AI$0D&" -"+","5!CJ0'!@","4GM}q%#fE[","","^J#mf7&igf","4T" -"f&={4VuYh#","&K%z","BRvl","<~UCW7h7L","-F:ve>eGd",">E" -"HIO","/~1cy9OMg","G/({s","}3%","","/|>]w%j 6" -"""#L6)","+]",":""^","","]B:ZZ","A[" -"uTPB$d$","j(R^","p1(*L?","Hu","","sU(HeC" -"TCD~","h^,q2|^x","!{u",".>^B&" -"b@A","./CXutA","UtN/'|","eg","k{OZ4G$6","`M""v" -"02#SV+,","kL^DY7`r","}g5L!c88`" -"m5myog1s3",";x","x","R!n8H""","6{S+_","Hc" -"h5f}n4~(`U","V","uCIpSU6 0","Zw7WX~uzbr"," Q*c","" -"iqH+rA=5j@","e-","+p:S:"";","Mj","s)L[INYY","U" -"M","<_=_pq","(","""~ge}/<","N","iu",":","Ai7 oxh","Gu" -">#K","4^Of:&-","l n","","""b_","tDp@B6" -")\&e^@yFb","c","- X*o","Iujl,;X","y ea`4","]FP" -"8;;tGHb","5GI(]8%","0B:pg,a3^D","E","~G~I{E" -"&|*YT^u&","UI__:|l(3q","z","euO","vf}'.J\MH","E0" -"^","&d?G{&",":\r","kD","EQE$","E4To~<" -"L","Kc"," ~","0s","mx|qUV","JuTeFhO" -"}N'\|u"," $fu gYq","5C7[","H+i","@3$ K:","tGW" -"#^","!){IZfj[","/$+;P]0l~","5Ga(,V",")a,","j?@Ih86qCz" -",ev%/3Xm:R","","y~Ev]]>","G","`7\S","" -"DbsFI.L/Rq","\&T,H^","n0+9hT,Qo","$,~Iac;i%",", ","rV`?gb4>" -"[)ZC","","a/_X$N","Z3xrK^3",";?<","8RX`jTF" -")'pp1nTL","\81kZO","qX8","#I%","qe|y:^=m1","w-ka#c7" -"]4TlU8kM","lR<(","C","0]O1E7*","","iay" -"G""v3v!1d","o,","\1we7dU","9kc#Mt_""","v&""q{ R t","t}ZAG" -"t","h/l}","_","Imwy%CG>RW","4SbC",",vE" -"t","@3>ml","T>}C ","pL","C]i(%S6e","8W1@e7","26A4vD@|b=","7l#\\X'gjm","ZB","/THj!^C","Kfye*" -"<=e","}*","t}kQ&Q0dg","|0b","0g*Ub!PT","G" -"n)FmG"," $)eUFe:","8$VD1'4X#M","d]yX&)","Rc0VIn","bTK;'R0q^O" -"-E#e\XH","`%4Uv\J9ix","8F|x!t-Ic","K+=!91k","9>","/N" -"RW","?'F@Cqwn%+","+.W","m~zxbMh+}E",">xL'PEk$","o8(Jn3#7hB","4y!@1Bp","D9m;","6-" -")=,","l =,#*#5OB","BNx","hr_:hKS","PS","C(AZ#o+," -"#HGXVPSQ","SQ","`","@LAh1X:","}q~(fo","P3RIUzI" -"fl6A[R","mj(","EJ/3Zpb-j","d","","rTr@^m" -"Cp\c*Vwsx","'","|h[;","G","\\U","Ex\X3Mx6u-" -"st c","","U!Qz6Ebn~","5pR4UjCjVl","i","/j^4N" -"@:","","",",w8)pEm>b;",";","gH*J!c" -"","XBs'!4","0&:kw","X,FD*","0PBc_9","a_" -"d>y&","THAl","","8e{",",3DK" -"Z7>&JO","XFr38","`","Oy1k#^",">iT3$!.","yMSO26>zA" -"kD;q.T","(ft$]",".t~'ecyAu","hd+","|)@,x""$8","X/b}1Z{" -"=@>j ","UnNh0I7(%-","p5F~m","-Na","WP@^","P(#Lp q" -"","(""nXpt:3e}","qF\","ypb;","X$","._r_1Q[G" -"K>Biv","~J~!m:CW%","y`ntR"," rY?_E_c","'u?,qp","x" -"7o(AA",">(;v:","z-z5","n }A","[79)/6.: k","" -"2!4CIt","2W@C","3-8[B","bQ~Ug28","/$Esspv","4?Daz#aje6" -"%[jTK","YeX,wa","9A~|v","D","!xR~X","h: ?smk/G_" -"/","OI>P~Cu@$","v:IQ\ZnkN*","J?Sq^6Y(","}hQ%","(N#" -"^f3M]~zqn:","jU*@%3",";z8`HsL*|","-qq","T2","`p5""" -"x*l-","0F9MDy!]o","JOynuf","9%","~!","5|" -"Dj@zV.","E}- |OB","2p^Z","B{fi:?","eL%>Pi]J'","9C""kwa#" -"LX}","PrLfFS","/}>a0E","("," AzT`6]W"," %e8.>4i" -"&Q.W3","""%5ohk.","'O","|eMU6y5h","?","T" -"A'"," P","'","(9-","","" -">?""_@uqs","<5IOk""7L@",",a@y","NbodJ","359fu`NN",".]g'\9" -"K","3OdL2","h""q_j","Qw","n5&=Er`D","OCU<^G:e","V?83jV","je|?","<",">=I*C.^Xj","Cmev" -"T","j4,}xxcV","cB","q?H","Q.HtJ","YX" -"`'.t8","N\","/Dh","j",":E*@Xj","Qs" -"b;fP""MoAZ|","u[hJ3T&gV","HU","4vT`[z","P2$*","cn%S~" -"?r2u~yr0","#55t","=[CUa#q","sG8kHNl""d}","","SJ9" -"TIUv*","vZh.EU\-","A","U","m","S@@" -"",";n","03Jp","h)","Zz:w72dcO","W2tdn" -"c[&","P%!","OYe#d","TpT)","U.+^`K","M4n~3vq S" -"h}POP'","]E","J4?3^","DGg7DE.'J","5{3fzx)%e ","lct;y9jLf" -"J3fCPT","CU~:jg>TUq","J","r1K&","%yx"";S$Pv","N6vZ1DN(" -"j9.F5","","\kiTSFFk","=Rx&g8","0s~Xi","/0peGo" -"{4(X1","<:{","\=F-!M2[","PHTy$Ot!:'","[DQ","L!f[$]]R" -"","]","gJ~imx(c","T.","'^&=9Z","_+Q#7(g:" -"M4'{","\N^","^","E6_(Ap*","q]{'n]","Ql\HKo" -",E+>j","","F{y*","","{6[XQ","h9" -"?/55z>e'x^","/","","SO`*jK","","*ORgj@QyG" -"!cY","uO8xt7 3T","ld6~9:J","RlQ4c","x","5l=m\R" -"63vLt","DFyH","=mm(pc|q","(","vX",";+W3S>;E" -"""C4-SSl F","=LL/+(","K/Eci","","","" -"WtI7","D;ou.un","R>D?#bym","=.AUe""GD","v'\&dfNfbK","!(""V)|)}" -"y;=n","es[T*o2","~kARTB","@)F""X",".!74L2Kf*G","4" -"xwtD","(.&$","$Z5BJP=rXD","ZTw,""9do","g","V" -"R&g5iIJg","Z","vGP","wG{Do","1Z","KRN$,b" -"+2z","9dX/j,\G","@=","y$T7O<{W","""%`jV|YI0","] ETvdAF" -"a>Qk","m@VB","^","","","U" -"H","""GE&","$Q","O|2-","","@C_l""ct`rB" -"BG3","ie","","{3h6'Q#","u" -"rf","","}QlF.w&","ni&a\","XY>vp","5J%51T9" -"|l","^U","A&ra,.`~","rQ","J6;rt",",{lo?bMh" -"WrYT?","q,K(gK0.","?","","WcEt","qL","`e|Elwp","GnlWNJm","`7\q%5[=A",",""}" -"^vV#","y;.\G&&\@","(URI@6a)jQ","m]~M?{is","rYeF|","]N4" -"B](![19;W*","w%J0@;",">OO~aO!mf","xPmNoX^[","jg8'JU","sYq*" -"itA[","@0lg|dK","LP*y*","}+HNr","?]nzT}","" -"tA?m'$\","rs5I5gp","6i*q","]",") %\L""Ce&$","F>i" -"*~Gi#","?sO","rj4=k[Ke","0G;yy","Ru","*9TU:$$.O" -"]","iS","-W","%#~#yBr","s","&U8r" -"","xRBII/","s$#","kSO","k8yhB?j^","!V&SSwp3" -"K>Qu","o|'PO?*q>b",")(","OcKra)^zy2","j/","yQ}/^08" -"03q4o#@","b0+K*yVO","RBD&zp","d*CN}Q","^ldUmLC%(U","@Sq|;O}iV" -"Klr","+sJlb|It","f","vnJ","",""" D[" -"ysRWT$B","e.)","@&4>1*""FLl","g8","$[d>va9","UbeM" -"","IzL3Ad$","^mGX5TK","rL,cp5:h ","Eh7)","""rDf'L" -"Q(+#c","Oct*:","","a,",";O"" bnX2","RE#" -"v|I5'yz]I{","bf3sNoy","^+N'*+""""[","Bq4@ XS""in",";j-t@Eekg","" -"9","","","J""","QCiU$s","5B/n" -"LpU.F$P`&","ej[p""","W?,0T?.c","}.L$a","#gUoHK;;","&=eHz+" -"Kt(|A","+","SY$ ","P7OfW)R","7|","whiJ?L=" -"w","+\N>_p2","BR^5rgWlJj","y9m+","K O#TO8","U" -"lEvw;WR!q@","[UCCz>,^;e","j!i&;4wcr","*V""8>u/n1","!]cK","dX=39:U6Ys" -"Fyf,o","1y%.`D32q","t|O\&","wb )|","yH{?]R","" -"]-9d","Jj+Rpp+ C;","1v,C{vlO&f","+SAr}""","@+]JtHx7R","4D'" -"~dKoFvL$","iECeIUXM!","q",">Pi+","l9nIl","8$x)(<1t" -"f_,CGQ3Xj","!","5c>N","n@3/","-Rvm3","RyLv" -")","g+{C8ep","=y","t8,//`oTw"," lS.'Z"," " -"G69JD&*",",e=","4dW:q\c?","FBPq","I","1p"";xgMO" -"p;5","","|@]","/0c4",";y2:",":_'AFa=" -"(","","","mJZW~#+VJ","z","P" -"dn}Xw7,:R","r dT","4x" -"R6Q.6r",">j[",",euQm)K2:","(","<","I'#" -"","k-{7,JW,{^","1","V%2#oE","Vh","ehN" -"kH}pCM","FAtz.d%%","m","VR","PU[;xxP6g","j6uUPb*I>" -"DfsKeW","e","eG`","#H","r""O","" -"9","Hzul4","#%Ypyq#p!","@aJ<^?r2N|","c51 ","5u'hyQ%DK?" -"%(WA*EMtVq","3-V",">eA}3lY`;","#A+ FaSrI","v#d","uy?=%?`?_K" -"J""","<}lA~","mW2NMb)lR","2Pa_?'","p[RitFhgG=","MH4m" -"?lnjj|","T1$","","FK","K\'[g","TN" -"g","","","","l=K,2J","o)\j" -"{U","E>w","7 'n$","^o e8&y","^Tx952","" -"w>S;","6kEOp8AOf","`h%Q2C","T6_7","h'%W","M~wi:fy" -"c'",":plRS3G=S","H:]Fm*`","NkOlv.","a8Io)","(~mi" -"r@M!aZ&-tWFh","uo>*L2`9f" -"A","","rI3-`YN","","Gh `>","an!S" -"oj>w\G|47n","c92C/nk","#","Wl2(yA","Y'","/ZS(32\" -"1QcaxxO","h#","L+-5PIN","iFyXP .&=","WN","E^Tl" -"!p~$[yne3Iu","",">tz!p&[*" -"mV4z","4+|A_QZ['",">,HPML","$}j([""V$X;","S3El","$" -"H?","/jz.{^<-","dm","O","gt]","hHrr?" -"tq""5je(\","IAuoD","g",",O>JKBp","SN","nS?" -"0i6B","x8^Mvu6yv","].","vjE_HR","1YG;zP~[","bR" -"i2","h#8zA","","(~W{CAn~[G","8E_|W4c0P'","" -"3$upm","""vAmbyb","msU9 3eI","hg@","2YHfF^Og$","m" -"[i","'UDP0Qw3","NHh!@M.b","rQ5","aWf)~6","bIjX""nh","`e" -"oj<#YqrR5","!Li}D:","deQtMk@?rI#","A.p","7k_h5M1V+" -"","","uVa)@Pa","3GB","-@dPF","%" -"eaI","<_f5","k6Q,!#&","{tH9e&*&bJ","TPV5OCM","8zS)'5" -"6F&dXI`Br7","7JM+7","mB77Uc_7","@74#","`P","M(\#>uZ" -"q17$i","","L^e]","75$","Q","l=m)KS" -"`{1","Z'! r","!R","WY~|l7",".wzMw","%YI=BpO-" -"r81S>","c={*","^0}","kT,r00+ggb@B_S","9_T\4IgTkB","SVj0Z" -">_Q-qN!{","kb}{FwP!I","?[B2\q","E;>IlZDY~","(SoaD","EX" -"!","FR}","H2jx","BQBnG632a","SO;k:""","~5VQu-O" -"E:#","BQ","Q","kI","D_","" -"mw0N?=tPv","]L","W6X@s_*Qg" -"`vX3Nwlq.f","[Y","pm$[;p","?8s.","1kkM^%!34","#5%bI;K!" -"!%M)fm/`z=",")x;NUFk","K2",")","",":3" -"Ndb","NbP","!k*==","8/18eI$","2b9KW}N0:","mhH%8P8" -"s70","","*x=4aA","wv9)+","_|M","9y`&" -"IJyF+;","ek","N","gs+",",","+E].an" -"\.::f.ha\m","* VMjbU ","nGQR?y,.6M","aEq/.?/_~","$qz,","nad""@gc3" -"(-","{nx4?QG","us","*!e`l;&(h.","0","<" -"_mlE.o2",")gX<|oNMpq","pl","",">%`%C7p5T","N^U^='4q" -"^p(R","K+9]i,","p0]EYeR$xl","QoP","db1'-T","48s/ioJ_>" -"S","2=t)T>zBd","","n|%(*~f7","","" -"ux'e","M;[oF5/$^R","'QU","THZ<","VrW[{fEX4V","l,dyipfMz" -"Bd.a","(z-W",";V","""s"," N","Q)B`5@5" -"R$oxUv1v,b","6","V$}W","%Hj""+2HYv|","cySY2U^HKB","x`>=0Z7" -"%","g0~3ZePY","D^q","y}E>U{(","<7a[zAraB","ha" -"^","W9","a","}0,A@","sV","$J" -"H+U!@!7ZM","y6c]I/Z|","eONy}" -"a0r`PJf,","","/0E,","SqW4ygj`","v)|0J-`F 2","" -"M^XO`5G","PErd3","'Q$\","?D","gzG","{1I0N)+.vM" -"A","~.'g","BgT.^fY_#","vow]:PZI9W",".c%T5","KMd~f2e]cT" -"/@-nD@","[/|+Q5tdjG","L","gh+`?g;`Xd","i< I34","[FA-9L&H" -"}7","[HND:;'5","}PumJIN:[-","=**","%o/O","75tbHL1,'""" -"K^!","A5DZQ","a/RLgy-)","_","j eUlA]*p:","" -"LD=hEhN1~W","d}6&S","g&8QM \","V#b=Kf6","J?","8b" -"t.",",3ZS.(l8m","ma-","O2E>V{acu^","YM@3+!K","KV{" -"@]2g?m" -"X ","Oe}36+",":ICO8:""kUe","]pgDU|Jyl." -"}R5%?lQBC","9Bu","[RU%O","V&l;&Ve","B-'","lR" -"%-9e","=.JKh","h/","33","~.*1aLx","b6=M" -"T5h?]s","e\2","*3rS@>o((","+\","/EgP6lI?T","]mM5E]v" -"'nmK)5Hls","MqP","!H{T","= 0","","{>7" -""," ","","","pw1","" -"I311m","IC8jc=","?KMP|","-77n","szOUXL/","kd0" -"8xL|_m\b","_{46""N)-N","=L*""+^~zm","k>A0","3=.Wz,","$>p~" -"u+S","N!D'E)","dkNm{","Q!","z:_q=90","b!" -"'+P\#f9{!k","\","8$8L{_iJQ","LU","z","6z-E!$""" -"m{Rs1E59zR","]#IZc1\","""8tAZOs_","[(","","0eWk^5" -"","0ZV","&a.{m]i,}\","'ppVg","gzn='~cj"," UZ x4T" -"nt pZiE,","uw. |'","0&86o*=QA","WZVisJ6b",":_z(^","YfJv&," -"(Gh)A{l""K","Tyqcqa`,up","0oAqe_","W(fBB" -"","WW%r2{i{X0","","\TN","T &*4:Lp","2H" -"Mgt}l:s[t","&cY%","&F{U","","`_&,o","u75~n","@CEPSu+" -"","-+YVyf1s","-","w:]31S.`","f6",")R" -"F","j1{","%WEq7lkVAx","DE\v5)W","`l",".Uu`_mfb" -"=","","!FbodK","3f$raWo","@xHx+%^","b^yh" -"Dx ayqT","-""`L=R-.SI","R\X{0","FD","4{i","]?MT%v" -">XKzRlz","|,6""=","YO>","^tIDT?d","tN'","];0uW2%" -"%.t ^m@D","F",".t;","","","@" -"=?g0I|Lcp~","Zne{R<","KUP4G~At","x})","""j","6Kn-#TQl" -"B=tY1[f","","HS?Gv2Nub","6^","B'kj","HvEL" -"I8`IH0Ld","E","O=I';",",ja(O""#","8","m4m" -"evO","","@IeR","|*.","l{N","CqA,n}gI&b" -"~MV1A6","@1WXON,|","vmx0 qY","&DD","~L","qK=lQS?v" -"Oqc'J","ZcYAFWVy","{Absei","lx1!nc","w""Y1Qa","O}%qn" -"H(c""pHi:","R><&","","~Ek:","*I[c","!twJ\7ME" -"*T","BMEf?'ZS>H","~A!","BL",">PV|\","" -"GQr)","" -"rVe","(-zfq),","FrU","p]|7)66[","@n*^e4","gex" -"4>a#9","d01H;s,Wh","T8<=e ","8BQo&o","}j>/<","3 i3; 4O~" -":vh{_Q(/L","@vKp4","uN2r)","P0,^]3K'3","x S","n^KQ" -"J4=ySK"," uOPOqA],","Z","=V.}yr#M.","48","f+-4S)G>" -"S;6C/mu","Sg@4g","WybQK","E4+9vR .W%","MZ","" -"e>Xr","GqV","a]-Ksd=;;g","{c')0N!","MCi","n|??" -"VB","y)#2Crk\Ip"," iKqtw",">QWer","}{r5EiC","RYitXl([" -"Q","xq1m","g",":d-","0s5q+#","pkfL6jP" -" ","K$","MI6_b5`","y-KT","`q","vL'=6""r8]z" -"","IN","vK>Y","]Tp3u","rg)v`u#","D MOYY-" -"d@|4/~Qr","t;'pf","V%rz!<","","[n","?=" -"w}O>Ks","c_","WS","gOB&>NhV","p[T6W3A+[-","Y6$m.e00a" -"+4SH=YX3""","i1","'}UM=J%\y9","Ssp%","5ct,?kY","I7O,C^#'q" -"4tl 8<","gi.","","isBc","yG~OR","a:" -"[i1q","!","ly@HXK","30@%vS""fB ","@GA","49'yrjXXpQ" -"!H2KM_vP","G(+","Ta","x%A,;",")P#|?","eQT~" -"""FO.eHhc\g","]x","Jk1vPAwd","","}"",VC","3YY-;" -"Q#:!mN.g7","~-u^Z","","T`;L","7(d","W`#y`" -">","F<","[/6uqy ","MK4*","Z+","U" -"/Y!duX","N[/","Id'k","w","%]0| Y(","W62ma`Sm)+" -"","xh4Y5:b+4d","1g/",",bBH&T","","x6=a6HhO~" -"","qMa","s@pn""'H,","@i<","@U%C","Z. VO]" -",Sb3Gl","t","44","x","~","Zw/P8P" -"N[7","K>nF54""F","|&/@hY","B[2","Zi)","kPy*(\tIEX" -"eH$lI","B","E}Nv,31V" -"u","c^3aLxEs","`2f","$&C0WtH9","9=fzolN*^z" -"zto9m*{","q/UC","$0d,1Z]*^","1x","b@6C","Eg6ld|k.w'" -"fbs$""dTdOr","%OcmCsma","Z","3_@da|z","O=","*x[-T8!'""" -"P ","VI9l8NDsX","!)Mx","m7$O+r^CXA","=3',#i]%","}" -"jZQ26","@}J(","!H_rpoe;",",Yh[","@1O@bc!&3~","Q5|g&+" -"MmaN ;on7,","Vo9","XO","SDyl(P6""D[","zN<,q;UGlt","1vu>QF&Bb#" -"&41sP","1L","N","]d","W.SVV[O&$","O6" -"'bdA","",":'d$DGmA","}","","]8Q9f" -"*/87(Q$N","]?","X","|?^#h"";[,","9^o:X@!<'","O7" -"v@rRy","]","\rhx,pKh9C"," f$UE","BT[1d`)a3","UZ" -"&\~7",".",")No8!{&zA","R>","fBt5rs8<","mX152/" -"&","ZEP""","GV0=on","/wu78_S?Zd","@>NT","pO4-$oRY." -" 9I<#5kPq","g,Ss\~Qg","NU","tHr[./:~","ZDRR1@-","P~~%U,iGb" -"'!","}","kJRwZKuLx2","","'%N=6v^9p"," lf""}u" -"e1[t7Y\B1","PM/2 8+a","%OWHm","foM3$:","Kc)",">Cuc_" -":PUV1nv?W5","*","~l@s_","n?shlB",";=N*P?4h*3","p3jLf39<47" -"9x","hHMPhPA","XW9{b","7w3","","44""N#h" -"59Mkps^F","{*aYUU","","","R!OIZmS7J/","7E3f&g=T" -"89E5}=@","","dDT","UUa","","UOa;`Pa+Xo" -"A{G%`","CYN7g>`^s","IjYb","tY8","","$^\TWH1" -"$9Q%T2G","","{B","()x=2","NY&5H{R@D","8cS""1/`P" -"I9GG=owO","Km^j*x ","<*V.a9","]>;_d]'rP?","&6","" -"(","4lAY[xLS","b9^62@" -"KSq*>`^","tamZjqfI""","4X!&qyR","IAw/)) ","f<","FbEL""" -"xK#${E|&;","v&7h","fr","","e1OJ;5OlD$","c+" -"A}i","FXqz","""|;!E$""/%)","(B","0Iw q+","F =+Oo" -"HWDg|","rj,+v@[","BLQh","\kiJQ1QF","Mw","L" -";\P-}G`6","%&zU~KfM^","d7Q$","o\Rux","4rBx(","2_+hr" -"TqnA3{T/Ch","V","hm","A`'a",":","D/QRkBg" -",t{E","KM"," _","H|fHS0B9","v^)?mI[@","5" -"\6|w/b}'B<","syl,","fz-*FZ#","i}Q4S","","[a" -"KwoiUan","&Csp>0%#Ma","+8A&9Hmg","(C0kVRd%","*2R""","5I3|r]" -"jd$pN",">0wc4","t.","","K","U#7" -"E~fj^N]0:","|[ClslGP","Ek`yZ;^v.n","4|TeYq}'j","QV|~<","bHU|!W" -"D.","@""J2CQ{v","/5l8Z","Rd?$+@","w","0y8|" -"","""-(9m5","""Ib~5AkC^D","-FG~","","M;O*bW" -"@PqS+*h","9{H","CAuT@LL""","1/z!","dg""xFuMP" -"N","[;3M<3/6j","x)s","7$)=^MT",",q","*D0F" -"Y","","X[""+{","X3","N/","~" -"q0xB","^_)f..p=~NV7","m6^c","n`oz5\|er","k["")","x]|Q!","ZB0t x" -"","Ie;$W7Ltq","b?+R m'F","R_@","a(aK@)v4","d^" -":a9LLz5o","Tu2 KmR","jeOLC|" -"Ab.n","A[M","O_B","J{p;","oKo>{","+N}2h}lf8" -"@g'z\","p1[rd","","lp=`""H","&Bey","yD&" -"d-","$6","1,l","m`Z`%Vy","G:JVW""","" -"d+""7Rb=4Wk","",":wc7W|HJWZ","n","o!Ll#k|0","k1" -"\E]|","|7M6Pph1jD","~q","/rhg","S`T^VjZ","]p{i2M""@!q" -"0|s","!.J{G","Q]VQ=QS","!3Ow",")>4","EiRMl" -"W\sSAu","P","hd%L~uL9r3","WBeO@W__","ET","p*7)?" -"dDAp1G7a","~Hf{wk","\>mMvO^M","uJ3","2 Rjl<>","JR" -"K7","R_"," ","d/d:b%k@!'","l","i%ymq|NJq" -"@>(Kd8ee","k6guOgk3","""]","Y ","YC\;=W","*upJ9sf}y3" -"I#q",""";+0X","","e","2J","3#M7`" -"+:e[mW""uD","C#Tw\G","ON2&1","VLK~.?z""","[4""Z","1" -"","~.,38","4sz#)QVo" -"4!,""y}).PV","y|o","2","Uzw[}","_b$oC=!","7Bcv-T" -"SZv/gj","y1Dt (","H}H}","","/D@]","sa*" -"*$,1T","(tUe1&","""{;1EG?","bUB[{9" -"8x""GrfC[:","7KBMvnn]<","$","+]35m","-_","ou" -"(*UT9}Lf","v;","/qBh=","8/pRg2P~%","\]1","Z" -"*Hni\@Q>;(","QD""?BA.","^h>","]t","E;Wkr=3=-h","" -"/i7dns[<","zA0M5#@e",".$","","A3Q+a","{Qt""n" -"a^","A5`G#>!]^8","","9","@Nc22${L","L*R" -"Q","ds?U/","c,","","/","" -"C6qs","MEW:;","","","+,L5,^63""^","Urb^;c" -"on","7!Kw","tee8rRNrJv","^;1:>2","a5"," HJOU" -"#8x_","20.tu","=db}Z_","@jbZ+AZ8","VE@b`uiT=&","J" -"9uz","E&Hqd2glvv","t4[*M","*Ot` [","dXmZtgE","""A{=" -"wIf[XC""8/","sG(U.l)cC","[tg","y","Lb8","yo}wO" -"X3>=RC""@5","(e?WL","_7^]2","#)dfqT""fu","{k[U`$3Jiw","s""gOd66qr" -";w9l9","""DA","gl3","jQ0{'\>","","OF" -"JcnXG\","_u'XbxK4(O","LzQ*","tGoHd%","G","@WO;S#TG=","ss6'6|.p","" -"=$","&I!","^D}PTpl","M;_H","x-PN","OtkNQ" -"\plR Lj8","dGW_","","","lv4a","XW2k" -"_^@=;L]-","k&","%_'V",",Nf.QD","jbaG$"," $)YbQf" -"m^h{B","8#Cy","U","mFb$$fc","1","dz(#3" -"(`5","JZ","e#""","]WN","","L_" -"=KJ1+","VH']{O u","Ttk*,toi","T{c","uO!@`","wJyKwtj" -"""we]c","KpQa","/kc2`G%`lE","Fi","p>1#!/T0*`","""G[" -"[FwV8vyqg","","w;b7!k","4FHmLwB""b","X=","we/",">U","-Bk/t","DuhJ8@ut?" -"ME","|k]d.6 ","bt},%-a_b","YtF6jDg_",",Ztt","[+Pwoc+6U" -" lcL","","0pJp7Q","N>0N)&Xe)g","5.zbIM>[" -"t""&42o","","U","q]RJ(}Z(Qt","'Ahx'","Q" -")","%F(U ","yT^vpM","|@=ah8T5?" -"1`Hy6nb,O","8","e[^}a^","c;mKT<","M-S+]","v/J\1_!" -"rL>ABq|_","+$y}j",":;5-=7","","rrJ'""'xR&F","@<8a" -"5oI.I?*^","Kx(X2UZL^k","nka/xTW~"," Zl8E^","","Q" -"*m4B","%K>3","|a~$0","h,&p","bC-kO6D","Lh=BNr" -"ef] I2K {","Q,V7","[&D||","XbBsPO","9B~A\y;kU","" -"u`R*aS","|eQ3aP0!&","RE(J}","","9ki|rM>d`^","hZA" -"*qXOdYcT","kNIZ]y(>","vkpcWicf'","2j9","d?A","2a81Q-k?~" -"q?","LGP","1P',","Uq_wAke\^",">gpP`o4Y2b","DHvoM6c" -"g.1A","={h]sHeb","g@wRl$","","!!B>T,Q","W2-LEI{" -"","""4VC#@G|r;","A","*rx","wz,ri","f7f" -"n6i","hT.P(gF","g)Z;.\x","?1","E8cg ","}/U" -"y@4O=VkPj","uuw{VS","AqGw(8nA<@","'T+dEO>Yt","foTr%[AicI","+vYcWQv5" -"ce$","","Ve.QW","9E@^","R$P","M{G{QJgv" -"u?$.lVL$M","~pLkO","od","","QXQw'W/Ny~","kJz^@" -"sr'Ny#_M","i/""JVmx\+","&v","2","rIc""p1E4","t1" -"~C-qD+)$=6","#r","*k^u[fuh,9","w2XE","$;""[","n-" -"s","|","","3n","{J7aU~3","Z" -"wOO","","m* Rva+","P","9Ui%S","^I/>c" -"LM","D","TBJWtB10","g*(2","Xs$*ju""H","}Z}Lh]" -"Gt,BulVC","SlY","","c~hjy4$""","DkGCX'/3.{","'' P/s" -"*H;","H%","^>q","hu[oWA$$","pO]#P<","RYe" -"yw A","Q?O8GD9,Z|","U","q;[2","I$mVse","xcT~" -"HMB","qu\L","","%B)g0","Yy5FQOQ>2","B9MY" -"whsJ\","%qi","UV?|f}*","F`hs~zqA","3dCR+Xm~|%","c.QU?J0" -"gvF%|.","3io","v(","Du","2o&KLe","C.V(#POu" -":gb!61a","","=o{BRQ""g","H","*W#d#%<]","oY9" -"p","D#tj|+~/Am","Bu)6suu""K#","","#JRg/sP:-","p+t" -"3m s/1oj+","#V ,aH,~L","j4tk[","","jPXV]8OB?","Z?i:" -"40[","KTvzMw-*g","^zDl9s;","a","","Cq" -"Fnh>|-u'","'","""z$]VqI$a{","_S;nX","ELltURP","<`}","""3d|Qev8C8","cC7k" -"K1F(jZ&)","","[UB7","8|","","IC|T" -";t}Kg5k",".^@F~.c","]Kr}-Zx","wco","c*","F" -"yyr]o6YcAC","dq","f","Eqdywx;","^","&F3q>Z?1/" -"YkaUl*",";","zlH%q","Q}-H7D|q""","t9=E","ud" -"1Po\)x6O","NilQ","O3*Txm/R","F/xp","35","=AgvpHH1","mK$d","\w/","q1" -" -:ofXf5 Z","j/X)","4>]x","JM#uP'UL","""<@cK28\r","*Qb P&QR" -"d","6-Y]Xi0","R9k'b""_","qq","L'R.NCB@a","b0n" -"i)\R9","K4^1","fW~","<{I8]xx>","~TG0p5Dn","y" -"B","!u","XxIC5","M`B]MomG4O","G\jxVH}|","?B,@fn." -"u*Z#&*Qc","<_i++PJPP%","","J-""yeaAz","RV",":zzoTjuSu" -"!)>JqUjQg","1<","j","/Ib5@whC%Z","/","f&a>Zq$xn" -"-oR+V$t2","ZwWXwAPy","\9NU","^E$s7","]obEe)T@+s","T(Hwo(" -"","5R+@",""" ;OsonX6","d9r?/?$E","$3/vxLt*v","@)." -":FV0","E]91l","<3k2Cm","[8n\k","I7TxGjT;[","iNr`KAN" -"$}2Amc#m(W","8[*;","F}:","z","~#k1K4^","6 ","SQLO",");" -"-Qj","a3p","PL~s0","AuEr6","`UcjuA","D","@@/;dD","lqoRUu","ub(]Fqbb""B",";9o-#+)_F" -"w_s_u9E ","88","f,9hAS","""$d6Fv","J""T","ma>Zb/M/6H" -"","f15_?L","*""1'}xa","Rh","L","" -"%2 *","j","~6","n5s|","+>W","","&P","nB1++lumCP" -"^AC=U","aO6~","XlSj_emnf","98[D~","","WQ*^h" -"Ct","djGwZ>","43","mh","O@]v>5""=0","o&l>e;zqef" -"9I+&HzW","UWF",")%eQp*iB","TagAMX{R","EZb","aVEDi(tR{b" -"@|24g{|__b","YqC+","JGy\)","Fcml","","R:q?9,-}" -"""$V'","/B:&9V","1aol7IV","","Z>+o\+","rJph~L e" -"=k0:8Au",":[:!GOXbu_","/u7+Q",">A9Y","i/.W","ex)MfbcL" -"m~r+","2>v","['(MV0","nT","ov8X_^","$vK" -"}nDF?%U$Mu","/Rh","""w","$$FXZ,","A;eI9hF`","mMT]lR" -"","9~v/q",">j","g.g<","P'B [K]V","'@w;Qap" -"u','f:?i","d1-#","cw""""ll~?","ZEdV","""3&","QwFZDt" -"oUGrr5AF","f","p<6]=<)/Y","f2iP","II/>6","mD\'x}E" -"Y1@XExlgX","ux","U79c{,;S\d",""" +!QH(","x+@[S""w<9","6Qu],=" -";C#","\CN[*","h7KuW]CZ","W","+l1Q","|,5;P(w" -"e","D*>{ Gz","q","K0'J","9Mh/N\X5&","$h62qDhC}" -"a\B-MUT7""","&","M]EO!a","QaI@'fs","Ex(e;Hdh","~j8\gD;r" -"Lxh&eC","","/h-!mx","","^3Pau" -"}]7?B","W_","J8>`*r_[.","@","(-N]F`.?:v","" -"Q%","wZ+Untty","|\~\","{z$8I","E'BNN3+4_","w""nV+j%" -"%bTi","aYeKT`","V","","/qriV[","pm72)n#v$N" -"hs(","0",">mUuk","u","C<","$J)!X'PMV" -"1HNGSv07S","})Y\3j\0","QXEKX""%","}6","]1i_ Jmk","fOAUJpG" -"Ngblkh'D","urQ""Pg","","md:X\-A$\","o}A4",":WIG&4" -"FuI6Vf!lgU","EKMu","CHfv",":{N","etRcEw","j" -"[;","Kwy7\;","KSC","0P{/\au","L5","VCwJ#;{S&","{\B7T:S4Q"," ,&.$G[x","$aU5:evb","d""39_Zo&p" -":_,||]b*","","f}]L","%I~","9-^Ggt","TBT}k3]]T" -"^U)""}","n.UUG5X","K9Kmda4)","{U;","jE4`euHi0:","?,myi04?" -"G~@l",")G#JDr5~","I}","#","aE2#up""~","gXM+wstSe" -"H% ","v'Y}","]6","4AgZi`64","0%e^B93>kQ","CO6" -"8K}","2vaPg-P(=",")7.L.>Q","7\9^","fAbU"," 7H\t=W" -"w(6s","7(&Mzxc7k","-kK3`P9-@","9)o","u-lRe`A%","%SfPv" -"NFS","R`6)`","","U1eR","qK#HJ","1G'Q.3]" -"n$","v:DI]","R(ZO!Sd","~)","","m" -"mMJ'|tLiv","S~WBIjy","-!DEg[\R","","0zB>","(X94" -">D^v2","XHhf#","M2""yDE","IakH`!$pw","x~3PK!q","CI4l*," -"e08rc+l(8","wD!9]h!8B","hc= ucF!","~9","}4+6","9a" -"","fr","4?b","R>s>8f\Ov","J","](`zt/,Qx[" -"r_]7{HA\","] #M>","#Z7~;oJ7 ","dQ'r>,)?A3","-pPD}MN_qD","" -"o)&jo","\","O0","[","2&","z" -"VRU_Mlm","*7WP<9","","O,/","s}z[GPm!","=J rfgZ","?G|aX18':","~i'C@" -"O_B~?F5","xTM7","9|f","Kl#""*wns","l]ixW&",">l" -"8uxz,#O","}","w$%O","ds""","Z8|k,","" -"x)","f_9l>2:i","Bx5%","Q[0rm7","r","MMlG??$92" -"X10q","H4t>D","jf/=","l","(L+FY>;","V<\kp9_W","z+","uW$Se7'A" -"DD4+","","e6|M|j","I","Y\j3*","n/V" -"m","P","5}[t","o$3XaB","(1p","Q" -"(l4","+5}If76","]:as","<{jR}[=","","Q;b&" -"&@0RkC2F","KpP#;7}0$","Qi%HZ%VG","d","U","/VC)&1z" -"c1<$OIm%*_","iN'UN!","{)oQmic!","p+^V.$","/rYs{<","Zjf!J]s" -">EI$^+","c@","sj[^","q\Z^v5:","x6","]3Fv+B%G\","!iexxRDer)" -"iz;C","*%]W;-","m9gf.","~EqHR7JI","63L+/7I`h","yq7"".n`>U" -"r4x@vF>x","","OXV3","sF`|wfBnK","","&T" -"e(X,(iYjD7","dGEtB","","s[=!^Ke0fs","ssK[)J?YnS","P!UL" -"","|62l|%?","6C","","","E5""5^xE8.y" -"r*l]6","=","'~omw^2H","y|W<","FJ~+9|",";>+`n^O" -"I","""SAq","( (w oOjfs","lua96Jg~u","L%K","^c" -"Q#f0GDJh_Q","fI,`>1:hbc","yDozSJ/c_","q:Gae{v;:","W)[7""?""Aj-","bJ@+N-AEG." -" &0Bz[a{ <","Tlm09","{ow,)>KiFu",".|z/","orZwLWz","sb]@" -"%#4dn""W6v","",")\ ie6Gq){","&","=oAo","0@Wu1lW=4U" -",}D:9","i>ty;^E","","&L%Otmb","~)Yb","~" -"mKi2","102d}Xj.i","/Z.hw!W","TU{)`","2'ptLd""","*:U ""Z@)`" -"","O?X/H>","*","","05JMV","]es)" -".#d]cs","G]i:-0s","","|","d`h3","?R;" -"{","",";,8Z","UMr3","GU>$C<#=","($" -"6?4^","W=","","YxMcaFi","","QU}RNU:" -"!Xit-N-E","3]I90","(pl&s","Rh","","$1 /|" -"WF0","YybY","o! p;Va0","zUIA","YdGvc|","\pzZF" -""," #","Ds7A","zfW","D","S]]>yZ" -"^zaep.&","[fVOXP","2t8","94#PGqW|r","%d-","'+'L|9T" -"G'+5dSY7","~Bn[ka=V_","[{"":{5","Sd:O=","","PVa" -"ok3J&","<2)R B","3nHmW2Wh","!4v","Xku","&8;%0" -"cLH:","","* m&T/jG`(","CH*","z.Cn3aR","gZ" -"FoUQev","'~!jP>/:=","KeJsGi4`","!@%mY+gI9R","M<^","b4Itg.DHd""" -"v&v``-AQ",">Tv","DfgIrN","+m,>x","nJ%/Q","2+r{-mz`k" -"Gq_,mf","}W|","[WyseU","il","9KG$Nh","n^p" -"FqNa-`P~]","4.z""2M","&UPS","1%`s*5I!","I","D" -"ZO+S|","lP?Oo","G1l","hHJW^)&5F5","!}%-D","zCXD|" -";@^^+;","","Rt!>+'`+v","pXaEZ+8z","Y",";`P0q^J1%" -"o8RLS3iDP","j=w","_,@y<-","Ce{7/88O<","V.#3V","N5\Pt" -")Q/3d(/IV ","j`G:J/","L!,.UynAL","%s ","?Tva%Z","a p" -"y%mogcs!t","&e|qk","WGE7q","","","{\s{k`" -"",")(","?8#e=Y","Usi-","d","o6tiyX" -"tOVe/f'Hh`","W""3?^5IZ","nGKdv4O]","2RW","h@Mk=qU+","R x3" -"pAaqAjus","i|:","x5Kj","}b9","PI","})V.4$" -"h)nUHN","N2c KreCQ","b{","p]T|^soV",">f(^~b*","aX{^8iYc" -"6He>J\zfYN",",-e","]9","1M;;J","7# }ll5a","#","czPm+","Ojt7","dg6d_Gz`K","AQB?y","P%/7M{(_" -"|{nW-c8T-","Lqb9B","^,<","V)&{_3Kp","WCv>rt","DUi!/Psch" -"'","","q%qyhzh'})","4o&","{z0CR","n=#Hjm\H_" -"","'fe","s-B.%$5?l!","&{h;QDK0",">Rj","p.1sET!" -"","n!u8RJ","WG&67~j","x","F","TNaMzW.V" -">","","NobK'eg,y","K|^)gpVP$","prNi3","E$J4o?m2" -"2xfMAc,<)","","6+%u","hoNrez","beSaoe","QJv(r#:O Z" -"3k@}+'i","]pm'|",".cBD","S",".9Ja.)j\un","pl9" -"'w=lq>5Y","O""","o:lgu=","!xO(",")(3*T","" -")","HAKr:]5L","","`wm","q","Bm0" -"fCI\V!Tg(t","yJGBu\mi]","}J 2*cEYd","=>TR","b_Es""","q" -"%cb","","iUqtTIy/","}","[2TGE","(Q" -"c","-H^f%i ","'3\}H#^0","eVHOQ/R^","`0","h" -"#_:i*","YW;","","y@+0klU","D1A@6","W&F^`(O)B" -"p","!t",".G","","-G9&/XyU&","5R0+-T" -"!h","Bl","E","G]wxk","AqC~K","y7eiO" -"XW7@","=+AF?O0/f","","XST","u^4_3","v!N7.=xS" -"&{a$xJYPN","S@Y[O{Rls","+v?09|`,pE","UcPW/=NkJ","GR#HB","o" -"","E3r","$K","P]",";glO","80R>" -"Z-R>","E0>Rdk","","""C8Ipn","Y/jY%(EoO","kTEr4cQir","t","egA3:~4f%","" -"^Uq","","",",gOJ%","ceKP|B" -"K4*#","","EI","yW|q;jg","(}uj","AL7iR8%~" -"6vqU`",")"," 6","2ke/`lW]|e","","Ri:>_NxZ" -"",",5ft","6S","8+$f","cm-FUpy","TN" -"RNU8^2","&ce{.K(1*!","KAe-XRL#Ru","y)$#]JjL","S``.`]","P-)XW","3$5(3<","AwbsP0;&Kx" -"O,mcSHZ@","6@*#","0)","""q:[\`<","QVn:","" -"I","evL","s2o","""ZmT:3O-~+","!|TZ","zP*J3D0hO" -"W9{)%L","y61a","","""n4^L`","3|","kBoX" -";g:{'8q",".","ksK~mJ&","J","tuG","32e][qO%1" -"^M2<","z|s;Da5B5","!Ya?l5/8+_","D4vjM:(""^a","d4t","7" -")_9o","NYe","lZp%Q8tYD",")CxxziJss","p~","oc","L`%H::9J" -"W","(@uS","<~RK=","4yg","qN{VNx=","8,g|hH&" -">DvSB1#hz","y","P","}","#m","4LBUACX" -"#ab","vH4+Kp","(efQH","1X[y*>N|#"," B_n+/*","whb" -"{S[e'`Q.E","l-BUV","~P6&\","kwx@f1'f-X","|li_"," F&^~PH""TY" -"3\SO$TY","a{","\aE6{",";KH","Yj:""G","1H>v/" -"o'T}=N3","","/8","[k.RX","g~","" -"","MF",">p]+B","(","5","zxov" -"VO","fu=&kgYh","CJJQP/3(8",")J ])`9%","f","","s,41i}td" -"","je>}6N9","s:lhwbm","I_w(","M;oC","vx-" -"J]","=N4q","@u0""b","","@^GzUnx","g0YH" -" ~k;hC","R[1}^GMs|","XvW3}F>KR","5>WY(9aBj","DRusg","RYR" -"{k#96oEr","s8Hu=O","","a7","o","""e<" -"","v","gw|#,&N;","nD.v:#A","rM/?3K.O=~","C6rgq" -"A"," Hsm","W!ozP76F&O","T","ZkNlCMF`","gsxD>dk" -"U 4-p","Qtt EWyf","k7'+^c$65","Nj{}j","Xe`I2","QqR}[t" -"","]BIeww)+F4","%$","#o{*","HEY&|w","/{;rp:)fD","","B","o" -"s&Yb|A==","","-&b","{f","oR1","~fymA6\C|<" -"svrbqg&B1","1c","lo","U8/1V!`v" -"J8v","4rWV9","76U9q{KV8P","vv) ae1:.",":QqOMsZ","^#4.;sg" -"","6","AFfoQNEO+","0{?WJ%","`0R/>]","" -"-","h",">","bYKG+T",";vmn0u1us","ls$x-`)" -"aBW1t","m`%l21ta","`.He.""Wk#'","","`p","x3%a4+?|" -"Mr?i8:U__t","aGA-!B&w","L[|Ui ","kKiJ`W!"," Furo","`g(WgAdq" -"01`dNfI","3`}C","}-):!3x",".#MchD","d`","}" -"Jh|,&4Ff","T~.""RDlk","R","*","","9ON^xZEi:A" -"RQno3","[N"," `}5Ek7","(>","Y552I",";AB" -"0=`","&H'4#2}A$","`| L","gF","Y{'E@:H3~M","OCndT't-" -"oe6f&",")7<-lzrZl","8{tT+v","ijM","FZ","n/" -"hyVrW"," ;(tj-q2","6#k!|B)X*","?H==`tp#7","WR0_y>={[","M" -"~N",";>i","l:#","w",")41#l","" -"UB-J^78Ra","""","","]CpRDGNi","t@$]vl%P{","zB6 #e"">U&" -"K:;@?","*","y]r.R<","\< -dU","],a2","F?pwg" -"&p","FSO;|WZ:","@","F1z","Tp{w","5~d~[c4i>g","1/z%p\qK}S","`.G6SG" -",L=RFMN0l","","k/g}x{%Q","u{","y""Jnd$","" -"]^iUl1(","GX~?:q|","","DKto","qyJo","E0BDjFqD-" -"qx","%F_`GWJ2d","FMf4|Bm5","","",": " -"VK>\f=~!","ps4","$UDLr%","","TRgk","i)n\s" -" \b;","7<=J/O~|`q","{","Nz^Ol.",",?","lYzM!A!1" -"A^Bg[7g)","s","aHJ","8JavxhkXHV","$1","%3G" -"4'5]","","(Q5{lpiB*M","C[~b|#t:d","#oa","G,`g?)uR8" -"","%o#&","5YL>'hw2","$-8:K'","6't,&sN[qh",";&lg?" -"%P'nh;&","@hB]=Xe","",",BKq*7","3kpkB","9^wKD[" -"hFj ']>{","""QW","HQ","Z>OM?41\S(","~qaLFh","rim+jN+" -"7{y[D","f]IYmPn#","V$g","1-6S9Pi^t","+","] 5Jrngv:P" -"'c45O{%","@","%#`@4;"," V5[1","0-d2","","","* " -"{","Y W#BO K:F","u","r","Cc oQ!","" -"*&\pHM","","z3qG3:e\X","4","l- ","$=e%|pmXm0" -"Z9*""]g",":","uLto","T0;N q,]U","puT)N4","L^7)" -"Fn}3f.LuPR","OoS6tU}6","AA& XSb","pVbgn;{ub","D","LPYGNNezf" -"uX|X[.","Dl,SdFOy","$9<})M","q","","[Ssf.Rbg" -"L>I","$X.","#$rA$7 #","utSV'RS","#G'daiU~4","xkg " -"dc<%(;I","ObU","""u\cB:\","","pzB+BKW","!@0x" -"","6)uY{","#'7GYK\","^RiXO/|?i+","CS,_#","wY,r(if1,(" -"hm-{","dp","'""E[=LYTk",",X%>K*",",","#TnK7^Bz" -"lFtbeR","%`)XZA]bQ2","HY",";^hM1S;oXC","","""e" -"DQ4CP","7""r(o.h","rf8r1","9oOobh","S/*.jpWQ>4","0` hF|""W:b" -"Y%q6",":x"," ]j5","\pQMmt;u","rTMG[pK","@!|o" -"t>I""7vmd0l","H{3Y8;X+m","","3u,,R#w"," Jme","" -"o","6LZff^3","psjx","2M>?rr",";%2^'BH #"," &e]" -"L=q<","GF;];'U@","","H","@(^!lnU7"," o=z)OOu" -":zHk9:.","cEHow,","KU@T#tZ`}","yz[)3","0","Th>'Nz{>6" -"9","+RS","(/t0601|Yr","","c/aI","C4)'9[s1ym" -"YN3]:B|P","7lVL&%","Cv|+-0sCm",",m>uClMAI","s F=","aQoeb9Sn>^" -"oI\G","]{t",",i\%ol.","3&","JtHBG1:wXO","qd" -"\2-by","","RJQroIL]$","dv.""- <^=","wNd=k:W","1" -"Ii","gkd","VF7uL","b*YK@","f","9;f6|w[0\" -"rqb-","D%","M9^fGFFx","pCsG6-vU%W","U+?gL","S,#4Y\Ba" -" ,&48]A<%*","ZSa -bdk","4AAk4kI","Q&E\","$x\].$",":*=BB%a)" -"Mrg59g|xP","/o8$,.;h_","{|ce|6-7I0","@<@x+byN","KCs-`*J","u_BwRG" -"w/","Qg","hf`",",dvS)","Z[PzR>@>*B","8e1JYN" -"!-]","(5<1uTFg","13*Ly*","V`toYS","7H)@l&!",""")","_B6p","*ndbzfk`,p" -"B#2H+ql","P~TQ^" -"yX ","w","= z","7L0k","","IpP" -"","H|","0",";%/|'g.,e","","o'o]@" -"Vws","tW5eMg P","bN)~cT^-oN","X","""5u,","9HO^<" -"Z.e_QycO","","j8","XV}hHbN&p|","=7}Z","w""!.H" -"2F'i7(]_/","X","zEme:* ?#:","kS&T`","e~9}YX#","4MtIdWXn" -"f:>1N2,4""","bQ!@","y%","qzw* E@=X","L","uK,9" -"G*f\?X\","J\+S=I00u","j","Jl#C","","" -"byTt)","pb!5KU","JWP&XoDbu","f1?Lw","u&Or","a9.MM+nkB" -"v{n","","7VivPC)r","En,","x","ts3nLc'd" -"ML6)","%H>.Yvk7s#","8","na","Kb@%","90B" -",MD","CWKn-SSJ|","F'V\","4=1","g/Lv(e=:b","S4C/,$zn$a" -"[[/*m(r","Qff`","","EQlprgP~;!","","cK(<>5~^bg" -"bdll=T","E%+Ro/\#""p","zU#q?Uh-jN","7r","@S@k/t","`#JX (:GR" -"/","MD;u%4","51i2K~jBD","UVCu`P","J|UDt(5" -"YnX*V_h|~","ql637%&M","=b,S1h","XVhx","LWN/rY","" -"eK+i0z?&3","j+}F","&EWGD_","D~e","FDj(.S\h]b","p3/" -"'K/8/","-pcH""z5#:M","M","c","zQ79","i59V" -"","|}","Rl""e","3+","rqA~S","GGDP {7" -"k5mL\=","!0Y>oy","+L+g#","B","g","" -"k","vJj","v8F|]8WwQ","%W","0W3","a+.3PAanf" -"muC~Tz}5l","","*FQ |/DZD","",":Z2t&N","F|S" -"CMK","aMLU*?^6:","l","_-z[kX","#eJ2""/oEb","i&J" -"PXa<%4s.x",",Lj,ms",";_QAG","\M","+xZ0D1W","" -"Iu","@_$0I","!~I","0475?Pr","","xfX(5fP" -"M","KW","9v","e","G^I0","%Bjba/PTl" -"","Zdj{GY^|3J","EQW2nF`","ioXX7F4(","Fj|)JyCN","5d" -"X<","SX;.v9","8","\=v}Z{i","s","y}m]j-D;L" -"7","C=.9c-V","/_b","rsI%","\9$","OUd'uz|-" -"[vMQV4kkg2","G14a,A@","X`k0;","",":u","x)=N" -"Y","OnMFVMzn\","wr","RT|tXR2","wQ37}","@" -"","n7","^{hYk 2","eOtY\c.1~","`Q>G'G_{_",";XG84" -"%D","dk_#r","dGvR,jI.","Op9+4","=r&2",";c}M(R<" -"M]Y","]","","c","-r"," " -",^]k"," M}","'J9O","p&977\26","h.Sa~7~","" -"VL'yY","Wqb","`","j6\w","YP","Xv`?3" -"v3+V0Ai#a","a-#","X","kHU\&*3F","q/*X8I$_""Q","" -"EH=1D&v3E","/MF","mc","kj\Se(k9k","_cS\*y@e","e !G~Z!Iry" -"fcFbijd9n","e{He`Xh","}aA","g=R$","Kl?-LGQjT","x6T/_`m","g4}OBOqkyg","f}ul$/G" -"","U","4:3","^6zo6?AS","e#A0:]rDP","$` w$S*zE8","pWxS9E(<$","iPO?5}u[" -"T","}yb","","p","?W(V!lmf6B","CF5I" -"h(!])-","k","o2*XV8","3=T`'3M3KL","|U%j&E:\&","" -"KcP>zQ2","m#74#6","m<4]RL","R&@","GukI4z\",")RQ" -";","{6iH",".gs6m:-N","+#i^hU","&t^7j#qS","|uY2QEew","m`$Nbzj`","p" -"e6>.""l","@n0k","^\z)z\g","B{+","","XvYO""" -"9","x?f(","Qq-sF","-0'U","1jVn* X","cFjR" -"rx.];o9ay","","<","""jUbs","C/1LG","MU" -"","|70zmRqt","Fu-C4=Q]","}}","%?E@L0 +,","vgD70ZoCMA" -"%pP~$}mb[B","aDNA_,(SYF","[smlZ;6s","-]hJ+","=z^m=0)gs]","m,(l&N""" -"9hiz","_HXn;}","2R7rS{/am8","A:f5Ah%","u833^tC<","y~ShTK(" -"kx.;?sUL$#","pc","F[r","tj7wK","","" -"_r%n",",[j#|","?8","ek""V{gIoq","P f","|l]:\-l%7" -"","Y""l8rV","wrn]30xDm","VuU","z",".{@CB-h;}" -"\W{j","zTj^qif""k(","}hB","k{.iLU[5Gc9","" -"'=E=+""","~2{j^3}}S","Hsmu`","s#t1","&R","" -"","#T4","XSM_1","","E4","!" -"<`,&","ULnQ/NZW(x","VsSgfCRD","U6L5","a\","&wEO4{_z" -" Jy<(1Pi","V5","';<"," @k+v9N+iw","Yo)e?oE","w%J~Zh" -"^cXyo",";n","","jDl>$7G0'","","u" -"l`D6","5x|+Ab{F7","d","U6K&","NNvkG|","IQe" -"gK )|",")BI4Bt5S/t","","*NGlRJhYW","'}f&","Xi~$Gqls^$" -"a]Y^71","r07hx","@","t5p30","]84p","b" -"","OQ$","HD","TR#qR?u","","`6&_Pz" -"}","b'Cz9w)c","0V","dg'3[Z]rM","G",";2;a" -")Q;","G&'rfX","7 3)E","/3]a|C%","c`}T""eB5e{","[6wA\ " -"!Ts-,k 1",".h*",";xa\AOB","","K/NX}a4S","^" -"0f\zlhVE0","d.#+h7K8r*" -"mdM:)#","V'\z\e3","ruH#0","R|","D{dmoH","?~5z" -",LBC|e","p","Spym-:{L","$eL","5 ","b7s?H0" -"2se""cYG_","={jA-""#)^","6","A$%d","qGEyiD","" -".N","""%-gc","!","Vdn{iRwo$x","[x;PX<)","3" -"E]","1IuA","s\z""#","""~AVs4p","T)A","! C/KT" -"*UX)","wR[%g\","W%JC""f={zW",":u,","4r","<(b`%","" -"~}3Cg","K;RD-" -"jB/?\?(","j%","]rH3*Xfc","[ktC@}","_oWt","" -"NjgH{""1","P f","Qs]/","|do=","qTaAF}0","3Hj(/'*" -"%1X}TH5(","{#KDZ","#?LpF","z\""\u!","ZpuzX*","PT}" -"zJ\","a","'","]","o","/ 0]v~" -"]W","fbB3k,1UE","~(""[w",",k6dS","561{z;E","!{*$vr]" -")C+1H""{","12R-PWsPW+","'","","U","^a&q>d}" -"`ee|l@",";P""&q@K_",")","d-","t`BG}W","qN" -"U","8N?-r`alX","4{b3j^BU","!0AKx'BWH`","feHF-%L","y$|pIK" -"y]@sV","{%AC%Pw9zl","fsm","HbN"," :U}","'P#L0+/bMM" -"]%","","T 6Ut","C!nm/? q(","s!E,>","" -"SLHvW",";8,j","7Yr6U","`EPEw","","z&9" -"%T&mrIT","pt",";A?1cr","5J}(,","&&","J+KG","uf/>BAD\" -"\wc~w[1DI<","R&fF","-.&jh","L","""","J;W" -"" -"Y=UwC%g0","!","ek$6","gOC!qh5:by","m|h","" -"&`0","Ga4AdHN","s-UQ}=c~W(","Qu=#}^","","g" -"9","","","3","*7n#sS","|'" -")c","/DHD$*","G","G-JXVX","yk7+pNhXKi","_zlM) G" -"rL5TJ","|<8$@""[h3#","F8MN4""","j,a0R&yX","[","`:)[qr9" -"X/#k","$=","|% mk#'","V[aF}","=-^" -"","_Pdpz","X[kn8p:","","(jI$B9o","=N]e" -"z$-^Bw","S#7JJ/K9","Uu:y@0aCZH","p;A)M?|~S\","o","?}`jx" -"}q","qF34HrEKOp",";{Y[;","a2M4-E","TKz$qX<@","D/." -"","l","","Kvg"," :","" -"vT","~{<}\zY","%kFh","","]w)","ehPVgF" -"q7U","Nbmx","id""&I8r..","Prv>&R8ch[","s9}!+h0","xX_vpyM" -"xD{G.9X","e9uP;69*","oniE\rs","BuVHm9!Z,f","w[d17[c","\uOusY" -"'SL)kgw@""R","q>","UG`ORA(","","","om Ez" -"%]1y","0/p!.l%","","(mQvxH""0uY","w~thjCIB",")_N>hEh7" -"|E2Xy","u","qYq","I;","<","5sw@nvo%\y" -"nY","RXUPmsxPs","","[]vkY 9p","A/A5\smVx4","" -"TV-jOx","_}","%nKV:c","v+9881","hUo!\x[o","l"";43!AD`" -"h-{MM:kkO","K?*n","92E9t?","a._^!","M q","" -"YoXUv","k+U0 #\/v","{:'8","l","K+A&:Qf","Uou}2BOAZ6" -"d)='u8",")","|",",?E[R-*}u","U","G4,IQ" -"_","m0(F""tT%","f`+","^q]`4n","0y`"," R" -"t==URE","{02:OE@Y}$","lxC`1","5maX","","Ptkh""eFfmn" -"","LN","","H-H(PPSv","l","7J^|vbV-""" -"Dn'^^f3hv","f7o'3","(RzY+#4","%!b""Lh\F-i","'u#'c*","x^Y%tj`" -"e,DQ;K",",!","Q m(","Q""h","6!5","x=0j1Z{" -"*X{&Nlsu","W.^M","+","-","","I}9%q,9net" -"","r)M8K","6[sYyY)vo","d/J(6","t8,@","/`kP(^!Xv" -"ohFtgV","n7v","","","Qg$}9" -"""^#;6","Uy`","X$","e","| bDM_G'}","@c|(" -"B,kz","pI]f/]CXL","uOCUDw","[:5F","v{?","W',]DwWL" -"c`%Uy~ ","U-","dYF%A/X","","g}qn","sLI@$(xU_" -"%Md*L",")W","'Q\)1>o `","BR^m0ddNTE","fk1;G>","`" -"","t?~HTB","&R%QQ1M j","%4H:z","4:UNI-&M8","" -"97","","#6/vD{;-_a$9Ed","I*b2" -"RFJ5","+-k:cb)4&T","5B<","njZ","xzpy""*%/","HA" -"","oMMtv","","VW{","9Kp8","" -"""w,;T\","Y","O%*c%J","","0""-;:{","S" -"k18","","J('r","","k}?Nr","-2M]>" -"W0?|N)|","x/\jF68&Hl","0t","B6K`ebq","=w","" -"s?{>(e","SK+Q'rgO?","`?f@TJ7',9","AD","Xy4-`[J","*OF18" -"fj+1'5","]T<>?YQ","q4V%","A_;,inKs","R;4-q^8ztX","Zv]" -"G@U","D","(dEH\g","m&","eGGS","" -"","#","",",T","]:!8N;","Q;&" -"l","","A.KrWVZi","3m %8S","vlbS","c3E;]" -"D`VPB","b={@p;","","qbyJ;{dK",".`/*C;|","<" -" P@yGH2","","hjmo~kHmo'","D$tqEM3e","","" -"237P ","",")~&1","!(","Tr","y@" -"/""^ME","","Jm9","An","m5","q|=E'" -"","aeOtD","^M*Ty#li","}.{wzRqI(c","p","iK7:" -"m","U2%Bz","1=mETG`x","<31oys^b","O`JXr",")" -"pM9c!7)","g","EES","^)'$=kz%Jj","=Mqr^","+Jf./[[" -"wA7","","~:","x7hpJXRh","MeFJ3-?/" -"T%_AR0","Z2{$?E>","Mf[Ag0ALr","Em[","D@4PY*RjaK","",":.6ZF1K" -"yo|Xqt'","]1P","6}Oi~]O","Y/=2}3$]8","","PG" -"S2#8[^T ","v","Vh","Y%Z?t","Y[gF\xsN","^=4;ke" -"]A/","","l-!_","Ko&","4OczA~cq","}2Xit" -"G&=mBM}","+Dnd10F[","[;8ln!OtqA","CcbEEU?","lsD6","I/tA" -"?","#!6<","","V\w","V","BSs}DSXA" -"_*~[","7F","$R",";$at;w","eqsR;.","" -"Cu\u","-i/g","p","DBLn",",-yhG1Z","$~@o/gR`" -"Z6U4VThU","","Y+c3","%>y","%?|n@&V""","" -"Y<8FAq(+","P","~$",":~K:f5","mu'G:<|p!i","yb&6(j" -"'gxV","nx%J`ZGh","TPK","Ppz;52","W)F3ojnUFz","(8^V" -"M","`SZ","9)3q_D.x$c","3nERar*s+q","","JxSe_" -"T","/jYQ@ZYGT","","Bjo;e5","@HPd#a~D^","G7Q.I" -">qD""2WdM","GA@3","yf+l","\+","Sf|<(2Us","ON$mn&"," 8wT^|,|6","`","u","","8~g" -"NS\","v#Ug+a","%q@UE}V,","mPgZ_V)6YG","TL2q","B*`y" -"e?WdS}d","yPItc(j",")4_X@u&","","f.D5_Zo t-","O,-E" -"{$2/n+","C|",",h",")}q","3=Fr","[.W" -"R9-Y9Pj4","w[ af2","0-H""Qi","9aM4#","jxieH",";/Q" -"riO","=$Ll0jJX:","M.)}|","=","@HLX","zh&J98" -"ppvrE","!RKB.2!z6<","g","on^(1:a","wl! ;c\","Ap^!cCR" -"uX!v:7",";_RIJh6$m","","JRfJ+M\,M'","WTVK","D|" -"sDJ#),","46","t5","dT|X})","i<0zl)","G" -"Isl~1]]FuK","H+ =c(","","",",b,Ay","]hv3eQ;OmU" -"S0C'$1:Y","^c2;","3h$nR|FW@E","T;wtypv",",DhU>","n~l" -"z7","kd\@","","H:0h-t=/","V{/wxTq","%~]13k","X>Zn" -"q. ","N|c>N?hm",">P_(pA.k,@","V","/5","t)mu" -"';~@D1","Vf","Vdg@]L5","","VcTj7K0=gK","zNj{]!d" -"U{Z&ze","S7Q|u5=D@","|9PkGMuNJ","","$","f1" -"lhU!","DX","3A@G~JmFyb","jg","Uv$m","i*8mSCY","Q3jX","<1Q.x!29%e" -"T}44:L)","bR",";(za|X>p","]lz><;o*Oo","2X(OM""","'?W#" -"!Ww",">DQqjyk","!QZ:}E","|)","-Cy8[[-a","AQ(7Jf""(" -"MOb","5l#V","p]3","4Ptqi","T","L" -"/dF ","[*;\47","","<_","tRo" -"kCbR#",":mRk9Y","\^",">?5!:}^Ul","/mEW;/D","oZI.OP{" -"R","}0nO+R",",?X*35","Dw24~&:QXL","5p'wNN","%/>D?\<6!8" -"^qx5?","Fj9#;Z","P","kgqTg@","Y/001LYg~T","237?D_kX*," -")4vqo@z!{3","Rg$","]==NB-'""h","z","~ |xXuwj{","z`1:#]pe" -"","","60$WnV","8T","+","" -"du","A#B8s",";K^;L","n3","LdQCmB","WY(,>EEd","Ob65:3,,O." -"","5" -"2ikxn>$KP",">vS\_dC","9","","_","kc+B':pbMd" -"Y!Hs{8]_-","wE4k","HHvHq","k",",Mdk","" -"0X!8dE","Xn+h*(>","uXtp","iv0TQ/d^","qDI,","`.Vle&Jde" -"5e;QCrWa|","{*","C3","",")","" -"S","3lecX2{!","","c","JU.j#?P\p","9x$ep |e+" -"l4fiS","","",",>^W","siE+#&","" -"Tenrh","?W","sE""J^yG","%{""\GE5","Lwr G/&\","7GZSL38bz" -"foF","~czq","?{,($,s5P","9+8","_\-b","*{bAr^eS" -"b^KtzXA]A","""0.l","5","r#i" -"bN""%*BZ@3","Wz","d3R" -"WE/,5WB4;x","Ld","+Hxy","<9""+","I/e","y=Ya0_$!^" -"Q","SZ,tWif*","","@JqM) ","#CwhIF","HudXtqg," -"","5p|jGX","/Gox+","\?K=PCXa2<","`qOp8\",",,0la/L" -"m?$(Uig","$gqu&n","zOagC{1X","|JA","","" -"c]z",">lL+ig","nF","","N^jYF",")?b}" -"x/Y","K[84zct`l","qC/p>JDl","w_fzR9{BX*","\#RG-j!","Kj" -"]bb:}","j","z}|: mh=i|",".s#N","3|B","cn(2`c(~" -"K?5R5Mw|<","_BO","IN$U x","#n]QP[{","*;M8FHmY7","?e4Nr/]" -"t*x","","Eu1x*$","k","g0},","""ywzOyLS]{","hDLUeAQ^","JdW=1BYC","Ds","tG" -"OWpbf","nM0$\X+(o","rGmj","uT[Y7dX","UP","*.:!P@Q" -"qh.","Jp","=,$TeF]@;D",";","nAxoY0","" -"yc~{ S++","#dhb7","J","284v","zI$=","TA`7" -"5m","-G","","q3w~$^C3","","RkY" -"[ '/hlo!","_","&Tr>O","{#X","O/6RYIP","r'p/b" -">P","?^MJR:XJ+","cWY?","OfOf!i]P:","ahVxyjB;","R" -"G7",",}%~u?Mev","s/wA","+w=#f","^L?VM]K/|","P" -"N","QyT{<'4t|p","","I|j<0'","&R#",":x?Es>H7Gg" -"@lNJ=6%","MBF,dX{K""[","Sw","b.Qn","mIPJ6@DN[k","=xBFyjQ.)" -">mlOo","99b1Sz7@C3","C8.~p","$RTV","/;+OT)zK6_","U+y&%H" -"McKt","C;X$s",",cN`","*5","oQR/EEm*Z@","CsLGK" -"zo(",")wa3O!O?+J","cpGoOi*9G^","9pF,#,Hz","gfh","^B" -"x","eJ","8>E.Rj","","k_","bg{","}YWF\\}","zx""f?&","qjsm5","","W)`[" -"","W","ZX[","A""Oo","Ch",">G" -"z$'{:S","Z$M-Ps$&F","` XG?,YBsv","I/Lx}TU(H?","MntpDE6gq","/_MiJ<`" -"+E,j.GD1","" -"a1!a","ae3n","JQ","M&","Da&&" -"j3pU73",")5x^GV}4H0","Ei:yo&","8``","5jScl^","1,q^9PP@g" -"#eeJtp[v_","6`icv.5pW","","!sMzd$%","G]Rod3<","$v\N%&xu@." -"o.?,K","H*T","5YkVF",">7^Y{","{)","Pro|iLF","4# ]c+fv","Q","s","}Zz5" -"Md{jbT_U5S","7L\P8W","DqXq'M`{ K","h6g","x;9N, ,L","G" -"","kXP:&rZ","Hwo","S'>5)q","F)+E[x","" -"g{`3W%bN;}","#uzo(ZAk","+\","9NL)","*q*Wz" -"e","1m","|Z 5\Uy",";#fY;Z","e=0%$","Xo><","","","+\#%icG" -"hHxS","lo","n,]","e_d:=v","","4~F-7.?<]3O","u+l*arKY3s","7W}i6","7vg\","8@Ww-","}6hLIoe?" -"","","%!=u%q;rq_","f_3","2v","" -"L","pi=%","5kA","c71","=""e$","d&w[gp&,&}" -"j","dR}F 6bi","3","","V8vAC\e_A","L""q(" -"/VZs""u'","N","rl'BIY1","J>zBk?\0+","","a$,6>2T5" -"8","XzW.b""M&","B{ |2w4?CO","IY%s'wH","&el-c","*dDoz]J" -"","/_iI[PQW4",">>{*ib^","n$","NT(osa","1","{]sJ_KV","6 j","" -"k;","th!TO","hNuK~ch\5l","YBWlQfK","e>iAO(HvCs",">" -"","VDps`ft2","s2Qn&dvMd","5w$TRBJ",":Jy-?g?I","O$BuZo^" -"}",",","#)l*P9h&","YJbl//d","q%{Pt9i:/","2law>(SU>" -"","~~Kt+nVe}V",")\",":","c$nRB|r","%}qf" -";x6","szLyj5a","","","Di,,gL#OF'","Lrb"," ?;","9d9.|6_pXO","s" -"S%","1/XcSaMX","I P0","#+9eQzCiCr","","jz9JT7)]Q" -"9b","G|&9Z5e(d+","d","C]d{~@su(","","T&_" -"RzDn,lBMB","XUzS?cHL","&j&M4","l|+C>" -"R",":1~hq","jg]o3","@","&&:*NP&{","-=B:(#","s,6>S]#","DlXI3Dq*U" -"$f""G","DMJWP","YI3ca'c-","~?,nj3R*","8=G'q""Y/u","kB" -"Aw|BH7vy.","","d5#):","zUNcA","=","xjE3@EQq@@" -"","n=j ","F}on@h","d#","+","hNK[=9E" -"U%5g-","","24","9","j%hhQC""d,","9l$yY3Nd}" -"l!XZ[yarA","jr8#Sl]","]Cu","g)]xx","lV4r^0Z^+","" -"","Ad)Zk3a","V`6R99","","","/|","^{" -"*'|z*HBd""","X3D$","&fUFS6","ztR9e_sRRu","%P42.","" -"[)hH2","[EZf<","WA4k@Yx<","","k7","V81M" -"Q","tXq","r","h~dB6","J78cdP |","?~o[x" -"Gt5er","\iCp+&aT|","1","=Qf>","0'HvX",";4YKF$!tA" -"YwMd`","?wx[5",": (WPPT6","",".w!R/0Au;f","" -"D\fl","P@-WeBl","yY","P\g*U0D","qyqWUEnqF%","%" -"s[L","","tkp,eaY^o+","]9*{","","aj","Y2MK","3LJ#L""L+$" -"rShLDbL)#m",")b","""%dH1$/&h","Hgc","`z:^#W","Q" -"y^d=Vq}\","o","%XIIDox4G","N#85#YtP","+K7","" -"m&\C)'wNXT","UO'\eQf","","ueW`","2QxV<33y ","*'k|*(9" -"","a","1zxr","D\+PL," -"n=cv$opPG&","","uU`!pHl","s5*`5","=M}","yvG\uy" -"Vpv9Ul>","G]7*ZK","",".?hm","A","@!" -"J^Wci.d""/","XHS","&","w+GO","^]S,Bj]","'39`" -"","[N","","qd~+Tr","Od~","J~R$S" -">BI]iFU","|","k!w^[P~I","","jj/1K@","7yTz_igXd#" -"><8DK;S","w#","\","avI~D","o]@%","Zo{P" -" G7&H8]=","x""trJt"";","_/ A2","hh)JU+d_-","","bru(" -"M/","hip.$","KjL4+!.c","SeB?Co?GP","","~^Y[K" -"yI@" -"e=4%","z.y","","O17 D^","m_j'\8","Hbu","" -"IA!PiGK","oO6PS>o","""le Z""FC" -"xS_","","U]M",")6WigXr?j","e#?E,Y0","Y,@:^/=r" -"%x;9","r`c`{NcXKu","}S[-1}VI?","~B.tqq)","YKNjCl2rup","" -"[ H","9SLb","{34IkB","Bj","KiP+&$C","P$!)uOB'w" -"","d!","uXz[}","8$=O",";D[@e","JqtW!y)" -"CNS""[!","H=*kR>zy>h","DIWhg",":%@?d*fhfZ","}rW51((","RlI&" -"N%DU","(8BaTyO","gy@o","","W(9","3=9:.V" -"9","!p5W9","X*_?","r","""r,x.pG","ouA!a)t'[" -"Wo^uq3","}_M","W*`45(A1",".Y,oi{T4'","d!G&","|R[-E" -".un.&[p","y","py2tVO_n","I","0b%/0u|a3%","" -"E2G*5u9t","I*Q*ae$q","m","","hQraj5$""","Rb}1`8P" -"`uM85JM","1L$``$","rbN" -"1_.f","AF;6nq3|","|6","yc","LOf7@} ","s:uf>\a:6R" -"/9v%]X}Vh","D","v0vcQ","-o","}MW""{j" -"","","R)WY?>&0\]","}Ask/",".83","Y}L={a/yL>" -"hTeeAb3i7",";","GXtGQ","mnlID","","-5w" -"CS`U","6.~RO!^jp~","z?(Q#5y8v9","i","q8","cFQ$(x-""" -"EC","OscQ1%%","I$=X","4~n0z","h~3","_{@F90" -"a_","J~@@hk","Wc&!^","U=<07L1","#y09@c","_G@$-Mcu" -"K",".HA6","1","(NQI","4mCZ5%)J","[)y" -"","l{dc","oq/","~ZC<;c","\Zj+eE","i!G" -"<(F?","\d2!t 5?","hg""","3}6oV6","8POkuS~\N%","sT" -"&B","G>':PrG3^:","","3'","9i_Z*A","r" -"`;zk<`","shu","Fn4!","([7~5*`Eh|DNs=0","m76;,","Keb!","rG1r","A$BA" -"-","-T","/It:W8O","V","n4~M}","fj+]" -"T44X#+`s>","-y|:m","p V4/v","B[c",":}U*i$1","LoD/cY3F|" -"gaqt",",|NSar4`","&","","yLECc\-","A&0x'&E" -"A{/2e, \?R","C?JX","_QI","TydVEl","$M2Gj%XTIH","k""" -"xT<@*hKs","X.+","/9YEP&q`{","X"":rRb","M","nbD" -"","{|8c#s&G{/","_2Y^v|$D","|z1ngG0q","|=9B/4]RL"," zE69" -"`","","[!@","/|t%;","^g?d","cBR)p@qELI" -"lkuV>","?Xi`~p4r@+","9,","(KmUq3p>","c~jagEu","$qC","}SJ|kW2","E&57LD_VK%","p@<","5Z_f","" -"""","[+Z#>tNb","m;+(lTK6AI","W2e","=E\","""H" -"",",hN9\","","IT$sM0","cwqF","HNXvEc@c" -"U","0h,E","","bl]S,","-9","_5S" -"4Pdl]=rUr","-Ze","@P9sC","*pgRYh","'","!$RkwHb-" -"#*YSiM1#k","c","jv?Q#?>","\_QC","Hrq","v2B|" -"pNW?(uH ","wS\6",",gx$t~M}","/v-$L","ShI`_:]w" -"","4[ \>IgZ=","$d*4dZ""","O<;vP","",">Ore\%\]" -"nu02(","$e*",">K7qZk","\v]e5""(W","**0,T","#7;AG7+5""<","8U']j3 Z","kI4e:","sTFDz^FC","ly>vQ" -"","eNj","n'oQUEw{Q","KIpcS q","ARedi","uh2b" -"Y>sGR","t5'+jf0AJ","JJ:!r2V]e","a)Y|.I$UP\","3S","S*" -"RFO}]Q%","]=fo_","pQ`E+) '","}+|9.LrL/","'h","D'{:" -"u","y!ZATW2m0&","?>%:WKjO~D","_","3","","dR",";e_zZ3(Wxw","" -"MH[]x#(P","W""]Y","*_pWGvjE| ",">1Dm?G,BBN","Z_~V)","W:Y|Y%(k" -"u.5Pq","\r#""A%B","o?X<","IuF","F","WN" -"8$L6Z","HSktb""5@n","f",""">hAPCAI2","","7APO9`" -";Ms8pO","*m+CDBS]","p","","rs\O|rD-","n9?blg?ru>" -"IO,","i9v|sah","-PkL","jyxo8i","aXfPa&"">k4","_l>j)N" -"m8>","eS2",":'Nxvm","F`$|y,uh","#lEQK0%","NDd5" -"o~,Wh1;iMB","JV%9x.@iI",""")VV@7*y","Ao`Kwe5c","/F@","{n!s,()<:a" -">e","{$B.",",Am&ttsU","@a|","8/" -")Q%","S+L;@@4","jr","mO%Cj$9""","6eSm5","@ytq" -"3Ei77[RC=","?","&R""d","j\en>","_M~3p]","[" -"t%({","3>idJ:9|NZ","","4$D<8{hb/","2h{","c$&@JhSY~n" -"DAS8|","Y7K1Px+C","`ak9","Wn&Pv^`r","Aaj~","q16{ir" -"kK*=rW7h" -"lY","`KUR","+4scACgs}O","C","8aBpHDR_","CtsJTZh~" -"x/[ouRM:*","(RA8d+","m(pE]V","b","(PiD_G","'K " -")mQK",";sV(","}N","9*Q$GcT2","SEY,6#y<0+","y!n2" -"|#"," k;' &","1BI>aDR","|q?&{(~$","exH","bq'",">ul""V","@`]mS{YL1H","9tOuZ" -"4N","","G,.","","S'k3(/q","A" -"37&Qa_W_","Nb&" -"/g)tnp_","R","E",".t`Q1t_j'-","Lp78","{)NXoK\[" -"Y","%v3","Z{DGm_Bg","Whih%F<","e^1E[Sf]","~A5Y(X;" -"1l""H/1*A4","2K)(ThDd,","24c","@M_n0W","","=m[O" -"","[SZBy,s","","u'0xNjw","/y:{+5iJ[","M {@OVzA" -"|@gr","^tL&","h4/k+","","E~y","!Jw{h" -"aX;y","Beu43#","rd2|1","i-g'vz1QX%","8|(b|","1:y:" -"tc'&HM(l|D","Lbg","@jB","DU*F0y","X)I.",">5yeR" -"ZBH%T1","""E8t[","bYj[v@","2","gVQ","*CU" -"xcO8,","/","Y[uI6N","9Im","x;&nx0FN8~","M;P" -"|\W",",7Bbo:,D",",'8>zD1","HUv4&>0iI","|q#PA*q3","x]t'{e" -"\","}yL3\(FC_","","SBh` s","VYd&Hw?","X/p:UB" -"\@-","`xd","nyY`D==","","wu","texOMu#V" -"X @K$d3",".0M|0","*\Y~S","E?';1e:","_l?)`",")4hts:5N" -"~\","4qTHu(x-","mmtcM","","5w:mu*oxc","ne;&Ym, " -"Hm""","",",LtXU}%j","7N'qB","A>H","?EaNJ=" -"","32=}pv%Q[s","K?'#o.!5>g","QQ","1m","}s{>DdC." -"",":Wv}3.>k","{}s?X]","p?f't)","s","}" -"QOi","ro# W8n7","|TTh|XNf$Q","/tQ}_!","z^,CF3O5","2X""lf-LWt" -"QE{","C~}JEq*L","","B","8()",",y~R" -"b!?o2:}t","!]gG>","e9.u","3D","A@f8<","" -"taTlE","pz2F/@h","'`]q'","4qYuS","s>M%K:)","'P-" -"V3oj","\[~HoLbl","e6v","9OKN,","'>,l+noQ","?z-&" -"","r5`5~j^d|",";~_!}Ev|Jt","AEP2f","4","j+\v7ANGt" -"5","nX^bBv|","}rzei`L\9m","","8k","qWCG(a2(W","" -"s`KG[",">/TVHz(&Kr","I2^}","R5s","w>C)j84+t","R(O-V b""TU" -"ZV:cFQj","]Y`CxQ ","ei","Tq>,]*%%","O_]6h<)m>","UqDH/bWG" -"-6","L|S<}%=5'","nTq1n>9","8lm","~J@wf&-JL9","&h" -"%Pn|G$=vw","x^oS",";w.^vUG#G>","i","F9","R|Z2NCw" -"R=Z*24#A9.","9$EI\}k]W","V_","","","6a|Y6P9<8" -"9A|]bLC-0i","!oO/!HE~{","","nO","H9P0LCP","q^W3 Q(L~{" -"FJE(","F","ww ","kgb./(X8;=","","YfDF%zWn2" -"p9i#Q","","]*mH}/&C","<","]fO}&7@w4","m(" -"dll*r%n'","RvvqQ_C","yH!z#A7Uo","3P>/,9s ek","'h(8pZ""","lK" -"s%FCq","&cNT","l[","Fk4{z","","Ap8D#" -"","<3IAZ""T}!X","C9d#","ckaS","'<042|APki","UVe$Z+We" -"","%","y/0N:Lr1","?|h&jX","d","]:O" -"(yce","u","'va]","w}5-#{o=","-","" -"%+,","1[","RaW~q92bO","T4","c73-IG~","kAz'a4" -"FHkeUm]","}Y","k""q]vm[","F i","U_evW4[9","<(_~f2E/7" -"^vv","JW Dp%K","B_u{cd.N","g*o~7?","""k6`rYtB","/D%5%~" -"4>","@","","56N","Xo+r`","k0=l" -"R{W&uA","H2","1ZY>L8.<;","LUFGto{","Kg{}=Y","''v!U.j" -"$-let\9n,","V6P","noR>07q2Z","hBpQJ","cD5?Im ","y1`" -"EF\n4td`]","Ly]y1mq","","O","\=WS;","Th""W4" -"2tsjOcJ9L","CXPQ","","F8)YFY",";C","d" -"z9P>1Q9|7","2","}8 l","d[0*cs","","PO<" -"[wV","QYL08T:;l","2$jCA^","(|*0","W%","pY.83=[Eh>" -","",Ps","k)%yr&]43","ZF4","m}\","a","x" -"Ks#'fTc","iK&\","wH2b","",".iu","sN8 b@" -"8F4","`|H?","","~A3""UR`","h?n)~)e","[ZNu" -"v","& vMrdG","`h!","$","W2)W","Xp!lN{" -"iJv","Ab","'+UdYJkQg","F>!","Gq:D","""vSsPS5" -"K","W@lwZ9u","XT>/Mqcek","y","RE","+'_=" -"5hA","X","v*iFb~g","lotbMi0u","","XG<-y.@kbX" -"E+_Mx","$o","o<","v6>`&J","!X7aE","[[mM5hrp" -"__9J'CSS7g","98E;c""","RV5","","","u" -"c0=","&k","k","/6","%~Cg+e5}","W)@|$0" -"Ds0wt;","8S_PZW(","y:;","^i(yU","U!Z-;nCZK","+%km" -"f';w","ubD\o","D[F#","gZ",";k'fx",":n>Xz" -"Q",">hD","bi6LbP@","`\@","/pqRB",")&w6/X,1Z" -"l5f?","vQX@62M|","Pv","][""l[!,U/","'g@s:ai]>D","+/}g#C4n+b" -"","oJ>2fo","wF>.[","WoN1,B","~|[$(L&<","# ]0" -",TJ""^Ej>","8Zr","bY?:J{""R","G~?","BsQ8","b" -"mY[?a","xKG&7""","0hg","O_xb","{nz[JBL- L","" -":","9{R};yB","K_","rpWT-""","8Iz?}" -"3p*FqmL;9","W?)D""H4H(","0$#Ln2)Xb'","(B","*s","*PF}rt[_V" -":wSD","q(4-{","x(&Dk*[^","g>v","YG","f!}e(" -":X_","nE-","EcI""O~a8c",",.=#-C:GQ","","n@o{" -"<#YrY","T","f","","9","mn?EIy" -"Wb1l""9;.*0","""f(","@K39""hkVR","mYW","qt","r" -"9","r$""","U+ Sui%",">z1Y)+k","*""Bvy"," O" -"`!","cPlLL","n?=%p","}bk>Wylnc","0D?L","%z-v$8q" -"T}y=8%qR)L","","nx","","","?h_8&NzF" -"_F}P,","0L","4jRkg&>","`@n","/616","sj7" -"mrXI",",5,\Uy}rY","rZL=","@k1&y6@5","","""@xh""F@" -"m_","]/Fgna9yV$","R)!","k'>Uc\","vW8[Z-","y?" -"NfXHZ+%[",")^$42V+?","kRZ}YY88&|SX","m]""8h","a" -"Z4s,","dA","W7:i;Yge","Prc","N.","" -"O","/o!2uT7#K","m5s~0","D{hZ(",".GG dGfo\m","T8n" -"bM","BVjk@?[J8","","q","=GU","2" -"c","8x'!","","D]~@UL*v","Zh","{-a" -"cE1 ME s","Qc[q ?1#J-","BCP","","X4[7",">^S" -"?","|}4BEnO52c","`nM]B7Y U","C[IvE""","yae2H?Z_&g",".+gjkGE]" -"popKD(}/fo","/4","TU.","&,L=DpA","*(","G7" -"c<4>","r~2(0[dgZr","A g(bG;","JQ\8~.E=&","&:'y{Pf@]","%:y" -"dB mDZ:","_{",",U|!@!c","","3HMwjEF","XE:4[IS" -"hlL","eQ[(^Qf","[ ","N","YTm\","" -"oVv-EAqS`=","=SVJwv2|","^F__;","P3Hc0S{G@","#=CP5","F]b0'u4" -"&$+o+T)","^#/5Y=","c2ruV 1^G","EAb4{Yi",">""uU+","T\8;lR\<" -"3FnR","{","" -";R:}$""e1","#","WY/A\m","q&[a=","lTJ6X$,y","c+oa,N$uX" -"","W=bSGc.sf","0n&w#J9;0","$@/-]e","MQ; ","7(B!" -"ta:Xyh1","noRfo","","L2te`A","U{enx~Y'`","Ou/(^""zO" -"%%|#no","kruMC;","R""4Km}qm2e","0N}3","g[@a(f^""?","a&v#QFjhu" -"EEGgch6YvZ","<=b*3S","","|f","\Qv","jDuf\gU,O" -"RwJC(","(U","8\==?P**","D&|?","Y","O""wP=+oDb" -"?S[Q/l(","wG4&v/s","2D'o{b^^be","aG9w%" -": [^A*Gd","T`k([`*SK9","""cTo5","aI[" -"RvA","H|J@J~qy","","{!m]+zs^GZ","fnO",",o>|(-=" -"pfci","k)./_i","@","z#a%$yw$","","H" -"+H:-}X","=zzwxQx0W%","t0B0[8","!ay""AQ~J*","A'","" -"u[ M61@~Tf","I*}aThg'#)","^n%@","","lB?)","PqPsHyi6" -"Px3lg","tR-B{.=alE","R&sG+SX","",":",":bFJ2Kn" -"K<\h2.6q","5\&==pj","!&%QuWUg%7","qxof?;O",">:=","~zx8BX5&}" -"s->~;Kr","jC~3E@7qfQ","","#=2Vlv,aiI","+FL","" -"","xy\~","","y>Hr4|ski","]$GA>L ",":v\e^dA_" -"","#uG;","XOI(","","!","oe%" -"C1R","w6ZJ.2","/&","iE})_MLSH#","g","AF""^ih>$S-" -"o)K3W:|`}","nT\/8gC^","{_","FSyv~SK","","a|" -";>","","AX%>k","{iq_f_^7\","fr;,$","(0+`-zP.{I" -"`$T3!cO,b","WPk@`Cm@GO","","d","=jx&_hXc[c",";" -"A?","e","0\H_QF","YG","7aiT#","x+PwO<" -"i","5d/X>3)#","1+'(bwSq","}o^V9kuS","fI4\(^Z","ON7C" -"uT&v|2","5","/","","y","r{=I.\" -"N[+v","`","","o=Rz|=\","1cWjDkh","Uljzk!" -"o8_`","nrJk}eqGfS","4fR""},!s?]","S{bGL*;0","","7*$" -"8JB7xr#Hy","06","_{F","tD43B75",".bLTb}i","o?T" -"ixXV+","`","","%$EV=S)-","+<2Rtr","tYq>Ptf" -"c\}3Ru07","n","+P.NZtRn","yn","D@Q|*39L","7P,XcH" -"mPOzk|<29","PdS/l:I","","([X;7@I]$","%QYy:%&Ih","|S" -"utBlI4O#M","c$NTF~-_{","V","4uW","(W@i","rN" -"JU(P.X","4!O>)","RQ+@ AT","jg8qpq|.TD","/u","=XBAnYIg" -"dz","p4*>{YG^","~","D<@_[","Si)?Y",".cL1we_(dD" -"9{(E"," 4","tQ['g8pu","t3_","yO'J|",":6=[k0*)}v" -"9?n{","jY_n:","7","2",".uWtU\","+/" -"jq:j"," sE","oJfe80i<","w4",">0KB","" -"O:",")}#f","Hrm" -"",":.","40!|00","/d#","lHH","k:#}(3" -"VF*b","NuFf("," $","#-Y#ipE","K/T2;YhA","-X9HasVKP" -"/","M08T!","qu]Hf&`","qjpc","d)sug","81ztm03!}" -");`$0o" -"chDz2's@p)","cZcf<","5jeuXv","+hrU,T=","fM&!r4","CMCYBL!m8" -"d]","h+9","o*[]0I","PR:@","_;f`hq_","r ON" -"Ol0d8Gf\(","64c&0IJ]or.W" -"[$+F<","D","fFv$5_n","^m6Z2","t^Gr-:Q7G2","Rt\V|" -"L:R-*","@ts|t|wnA","","`n","","]}w~Y2C#E" -"Q$","691i:","!!>j=#iv","K!]`|9H","dVB0uJ=WL","dXI" -"Xj `y>1=6","72","oJ=@If]N","-D5","LX","" -"aLQsfsMy)g","Lt7E\N5}","SVfn'C<9(d","","{\","h%bOzTB/q" -"Nf8lpr3; ","Z)/W^mihJ","_NamL@2m",">C",":s-Y","?" -")'N2=ZA~(*","86eAXvOnm","L-","6P^ex",";dg:rlWU","^ bn?x%" -"!^","&6zh( >$fK","R","","MV6Mh+'uQ","c >J-TZ." -"P","N","Dbew\d4","An>Z","","TbpiXl" -"e1Ht#}d'&","w[B[","4f","ogT","C","lC^" -"%y|NR:u","}s.bCb=i","iK7#","ok_jxu","+WGctl9","u1Z}" -"=S`U(DaG","M$","M1","e7m@MrM{","$tQ&C(p*","" -"atDF0m","%kYk'6fU<@","SD04Y'#+","g@P$ XA"," 9])Cl2@}e","T." -"V(XjAGfP+I","\GO4""T5,~h","","uo/","tdqu:=f`","?_(N" -"n","lg ",".[7","5:M8I6M.t","Ze#oSj\","ySs2$1VC" -"_R}z>",":","06zjeuTDBk","6VZ96A$","JYVnFi lOn","{/""+P8v5Un" -"{TbI,4'.\P","r!","|wf3?mR","5y5jo4QP","[I~1W2ZKc","SwIeq8" -"t5)Z>G22R,","S$3/?s){&","vMn","lJw+","17\$Iy","a1ZUK]gJ" -"cuh+","J/j\~A","k48~2mf"," >bd]rp~M","Hc"," cO" -"J","poQu+}SHi^","3IID","%4DX","xxoG:m`(","k" -"0`!+","Ux(n","'tAZ*","/&""oOY~","6OJc","nr[ M" -",][3X}O$","YUE\l:1R*:","J2@j","B>","$_k*&rnLo<","" -"iK/RK3ty","(d_o$UB","MN?[:/","nQl9","ox"".u&B0","CZ%Atz" -"",".SMK","8*IRHk/"," -RbUX3[n",",T3;wLh>~","{28FE|" -"?43h","","UK",">XR*(T","SFk\u-a`","p" -"-_","\%v>6Hy$CH","O2B)","Qa5","MT","^7" -"tw:,5E""X","#HmD#}xD","](&6@oI6=","an#@@jR","Y(9","""*0g" -"u?","qDAezvF","p_","zlp","In'ih5A%","cQdNj`D}`" -"V","7","OG","","Lghzp=)bYM",";-z" -"Y}ClD=ME","@AgHhx&kQ","+u","","x)","rD=@PJ+7\" -"sqg","or1l+E ","F>","","XB1ELt!}-","|uW[H" -"","","#oYfF&","y^R ","R4ai48|ee","M{5!Zei" -"zAv$.&X\s","vC;i","AV","n`'","$ht@TVJL(","P.KvkL" -"Lqa","'pjT8W`xP;","%F?7W""","nTW<%Uc","_","uv\@" -"","XA=Fh'1*Z","|/*8","yP8ibO","g@H>K","#" -"*R","`y]5MS3","","*MEFpvqF","1qecJ","F@" -";XU","(?KL$/#","w]LBt" -"*VUHX""1BZ",":,n-RHc4","#kSUF %`P","j4Rf>\`=!","z%^!x","9""H" -"iX8cgE)4G0","d$wp","Ll{JEV}z","*0K]gVSl","S&J","n,-Bg.sF'K","Y","a3^,_(" -"y7&","ine!a+ISD","tfC","]{z~+","WCGt!","" -"SO?y,E\Im","C~qxsE@9","m@/\uv","","WKa","^l,jUMe" -"XR1J9",")&D7uS'","+~`vt",".EH't ","SFup,QV","6rDpJ50+1" -"d09","`Bh{|e|]","","\iEe","T","k4,9^" -"HsTDq","X)L30jRRs","Ww\/$t;""!`","A""8*dn.x%","96+s`q ","'g3nb\l" -"WDW&>'r3;","7z52eH&>F","VSj","s=z=y,iI",")?!M","|wos,_<" -"Z6 c?,_JK","f.!O2TVA","k1IfG","lp","F])=3_V;","-""c" -"pzT>F}","/ee>","[o4l@u","Qox","@","}kjx" -"YK","gE(G""ZWK`","","5""+>,z","&Ocj*","[2HqzK,ojc" -"e4x5YN`@","PWk","vj","$8 ]b?~r/","\ l+7#%","" -"E8U7iAOa","b>N","^","9","""=","=Q#'" -"erD8N;d","","fSq","^GCp","S^D$I#rc6","fm-'r" -"j1","","Vtk?0p4&N","A[QpS5c","C+6mQ","G" -"9H","yUj5","-nd","/I@yQX?qg5","~SR","17;" -"t","B""","a","Zv:f#HPtYx","n","Cdv" -"}6""Slm$B","Yb?f?A","cU)&p","95XWTl*J","6m%i 5?.D","" -"NrJj","0|dA4%","Ht","+>eq","q4PP$","J)k" -"5)#40U}",")O?(","","_bp","gKsIP*l-",">a&U{" -"&)_mx5","p)*i""","'o#FV*y","","[Oy/:2a('","EXjV%=f?8" -"Gvtj","H&m_#67W","5","RH""F#qh3","","RPL" -"","w]'iz?$","7","bv3J","6/B7}7pJAd","SR5os;v)ll" -"A1@""'#)#'","","]Cc","gc+[","iv;t[Z","yQKx" -"`.)","1IV""L19S","Q`(Ssi=]M`","4EcI","1X7nc`Mt","{z'T" -"","b`","8w\UQ","mMX","&EbqkMQ","S","+A6`5" -"|6Dpk","nG/","h]gQ,Ub",".'","?U]HH1vy","%F<" -"A","#r","#+>>(r&w]","","","%g" -"VRtPS~e","}PYc&*^","Y5[SkAW","$?.I9\","lQ3y938VY","K}" -"zrjLc","Z|a#00TBxa","veLdNa 5","","U>M","" -"","t","U","tY!?y J?,","sUJ*Vi[06","Ey;" -"}w",")","B]%Kf",":kF","#'=l","P!","h6'K:","I?K" -"","W:M:7","TdaYc","$*Y","C0l","CU?BrtInc" -"/","d!rMw83I ","da|Urx7","Y~QSxE;","vKE'kfx","A=|:#i)eu/" -"S+HK","tgd7e9D^v{","","R","(dXJ]","WN-""" -"laWe/Rf1)I","e#}B-;,pD","/*A",">f|HY;Qa" -"Owm&fl?X","#B","-X(m#0O","","k?I","<*}+pSOo" -"%F?X","","SPx{$J","Gq7B5\e:","o%V4()F{-'","9|x)V|" -"PzR)}Ej^O","2","""vF4","M0.dz","l5b90uB","*ZryuJ" -"","T5Zt jS@O","5,","E7{KOm$5K","X(h-i%'}","[nk0+pSU'" -"^","Wh,Xn""(AD","cu~$sMt4]t",",Dh@/y","","" -"Nz+a}","}bar?5Q;sJ","T=C[/","J7""lx","","" -"KDJ75","8","yuJ","K,","^;EO~I\",",WZG]/oB8H" -"E;/P)0x{","D*i_F","6`j494Gg4c","jnL=PY9Q","#","7}4G;UIU7=" -"$U","E-F8xTE4","rGH:6Z./","5s>Lh9G","\8|dcK","E" -"vHb+Bt~!","wL<;","/e!J","_fUkr\ aWN","""XQgPj](}","tv" -"L(fKTM","3u$-v/ch&","HR+wc","QgLq$gVh","jI M7L","# &" -"r=M]n)!","A6HZ","fC","Jt7","kq#H$"";B","(Qo" -"e!s","AT\4R","^/gGe","Nc7Q_","7[_M","{ca5uPcSku" -"NS","","?vdq\6A","B","N4:","4Ji" -"""C6mWF9l","@a;kOW","y#c","MQbmn","]Sl/6qN^","" -"!z","z_is","i$aa=r" -"f~O#`95@8`","q","G-","5,R[N/'4xo","8^,a","fe|>B\" -"'}+pY_c","-F]>~-'",":-Y~S","","J.H>&Fi","Rq|V*" -">}","T~meCpyJ^","","Q","R.Qa[.z","k." -"+Nn+IK=S","e","o+{Uo","mp>","No'mgJ4-","" -"J","od]stFv-]","V`YEG>","","#i'",">m","","@@+""","+89" -"0MG#>0","j","S9x","<>","x0ZjM","xa3 " -"","sm^t\A","^0!/|\,cJ","","->","Ky\|" -"^\_Ewh","\e%TC","U|I/@'_l","o@s","AiJ{,",";ro$g&" -"^#`""Xd",",B#","Vsw8%h","""`1{f9bSc","t/y7!a@i","i""5>" -"+s","3JC","AGl*]XG","h","~Q8!FV9@","" -"miuBJ1R-.k","+""-~","OIQ$+Q=(","amu","","u-?" -"lq1}i","%KxCj","K)",":0a$Dr[","@VPNmm24/","" -",R5b.q!|-","5w","#:0(u60S*","Ye]=gb","1HzOtk","w~Hs" -"+#6V","vi#+x.t","z#J>","","g37`~R","/6a+Q" -"Xh(M","wbf`@T:/c",">: /,_","Rlt@","","!c}f>+T" -":C[","#""*x%\f:f","xa7/" -"","f{SEkSGh","(]","8O{","@wX'YTJt","V<\w~Aq" -"?P#{\=(K=","rAr%G","T","9]","","!:""ls=TMxw" -"JdD7w E%fE","fXgmQG""c","n[Xc+YIKD","^`6wj[~H","0","5S^" -"u[|4","Xw2[cskyX","","nWJrL7Zt","""E\(5l]Ph?","" -"Z^8","R",".CdMNd=F;","~x):x","""""DT|c6BpH","" -",W$Z)$qn",")","@FK","G,`?atq","FT{L","}a/%X" -"v,S","{","$uL{$[Ih,B","*G;PES>","NnX;Re","" -"X?}SY:6Xm","BN",".","","qco","!oS" -"7Cn:I","%Ub$","F4MFLH9)K$","","c@,O57KiV","mq\" -"Z)","w8J#gSg ","=l3$,@",";{Urg6","",":u" -"","Mr1Z","'~0","]kH)","]fD","#oZb" -"CJD3",":AI_vex5o",">y4","9,-WtDA&<{","+1\+~/sTc","" -"m4Q","^u`r>","!3]m]","","07TbdX6!H",";t: UFd" -"X","hrm","4J""VK8O:$","({","\(n<=9","T5Z>xd%9I" -"V1d'Jl+#Dn","(AXo]Pa{%s","",":K","_mLV","5U8b/Ozqw" -"d","VV`jA","n_>Zp6V'A","NI8","T;Dl#","Gtw?~" -")a;","@A6J[4`o24","","t!^^N","`c+Rr","p\d2G~L" -"eEG/&!","[OpE4)viNQ","yAw$""f0u","burv6O}","nzVS","ML" -"4S3b#3'j",";01","h.mSm","ZX~uV","MX(2pb&x","_mH4{A" -".eod} *1XH","","*gQ",";@w","8%Mj","wo4@@" -"Ch:AX","b^Y\{","H","Nq%!%A/sim","5#","ZDSE" -"P","CLe","UC5Z`G","","1tf","O8$fq^Y{j" -"&vHc","","jOR|_e[","mti\L>","Y","Iq#gbp0.%" -";E","]0fX","WPi`6wiVgb","='MgYi_I0?","5+/","O)`A>h)&","(""g&ER|1l5" -"*K]YeO","","M1","V","w>x","^.$N" -"?Q]","-","k6<'@N?","","prp%AN","%nU$!" -"-x6D!LgV4","","Iofa78Or","#5g4_lXn$I","PNMU|qU0","Z*os" -"'CLFy*""to","X+<","^kgjm1:.\","E?2_/t","V!?#","nZ*7" -"Zsi!PBf2u","ddU","j@^roIK","~wVcb\Y*","LV","o%`[nK" -"6I1","QF8TxP","v^Bl;o3","b","fWH3[","yw]5Nxe" -",*orfSKWLZ","","jX#k","h","ikwcv ","=4rhFs_xH" -"","tL>j>GJcC","T","uKX^_","a[sdV`Pz","Sm" -"V!S=W-c-4","t`b\ A9","","i9!-","96K\:""+o}{","+:" -"NAJ~","Az6t{m!CJ_","","d>G?X&sTZ","V",".hi&|""qa`" -"a.8#'","(","\'K]A38;O","J*|:_","|D","c" -"G$","","1","Ef","BoZa","[""J""" -"/0,H6]GY","","\F$-vz#K2r","0X@8A1fd","mj}'}a`ctN","u7BcM9:" -"nW|sA?qXC","h(?","!$\Eejf","H!P","9","|7nWBm`[T" -"r^FiB+mP=Z","!",":~J4T","b_uF4tV,(","vTl{=Us","_ -" -"","bth*fd9%","h1r\Ep;8s","TfIOs9yH",",","1xDIv~#G" -"!>Ln","|HlkL","4O~'`M0","","6nNcJxiJ","0" -"o9Q.!!?-","","\^","S","J_xJL%","" -"","^;%&<>8CmP","RdQ\Jj_!%f","]JP","tr{H7","" -"{8j/#N*73","","o|rUz","SDPE98A","sF","FMr" -"V7/Xf!a","3e","E3)|p}\y/","","pr","e;{\S1`=#" -"VsvFpD""u","=#>gL hgKc","5<>,\","H",")vV!m%u ir","#z_" -"oe32&Q^_F","}*","m[>t^2rNO","p;Wu9+sbh2","LIm l99L","O0|jH5" -";","7g","","L","2D~GAd","|6(H7>E%5" -"","9L","xSaL%H)::~","&m,",">Wf_@AllF","" -"p9=MfoJ","zQi#W?","OS(13\j=FB","","ha}u4H","" -"p","ja","l^^qH5k!gL","X~xYC","$?5N2a","gU" -""," y%f","2","%D57;+","#`*>","L>UzOrOn" -"A","((9vRc","PkUtfGU{4","W=","q`f#]vJgm>"," IIsn=" -"L`8""Q}-27%","S:pY+W@M","++:T_C:","A5AVY.!!;","=5""L","" -"]?","3f($cTk","t*4LP5","N","G).Z.=;N(","g" -"b3y)","'[0/","L&_,s>",")","%MO~&Bww","58t\r|X`=H" -"mjk*%&","qWB&j","&v0(JM,","!>*+","d^u&zEIgD","VN" -"*ee_.""","""","\qVrxV%","8>\[|N","$oJ7st","""/J1""k" -",","y7~ohX'F","""p","7|4qALx","^u\i","" -"ivBP]""uM","`8 7^yX","J`_","|?>VGDpGg","s7iMkU""k","RYN@zu~F","Ej, w;j$" -"`5c","","cRu>","{@%.A-""","{&8&J'","6P\cQaO" -"Hb@94rah1h","8~","Th[`7p$Z9G","YznAYYo:","(>>Ve<5","LFd\BlS_jK" -"FD^","","$n#","","/`qc","=<" -"pX1","6-_W","IzOA!H%","v?d*q$Nn","Nv}YrAyiaN","DK+=;w~P" -"/AW","""I{_","6N{7_+","PLsFAs1;","8","tmQ*SdJjEM" -"3AoPWCRh","{cwS","F>Z}y4^%A","?s)V43>&(","{dR,l^ah=","","b:w-" -"1@EhM,(\E","\" -"gN/~84","c3R_GmQ1","svrtdog""","2!}RI^1^lq","JH>J","" -"oJ0RJnr","{Cbo[w>K","EfuJ9\m~:","O_<=qD{'","p<4QQ","}" -"fkg","e\1,L","@|A8IY","Z"")9*","","T56:" -"",",e-\m""g&c","0Zo","FjM","rTPMA","^V-~E(s" -"spko;@X[F?","[s;Q","Xz","%U&[|","e?","Pcb(6;" -"TVF)I","|b:","V6","-","k0aWHpT","J>" -"4?K>ze","sU","Y8x] !",">qh","4W7QZfR9%4","rO?" -"Se6A/1","pwA3Yh","i","1`","W,f|FKz","3={[" -"2","P!SqX6","zm+;@Z","7RB","O%VT","^" -"Gp9XU","?","","","YKZ1@","V56lE`vo" -".""8$","-!","r6g$TZ0mP","KZ_a""^","B[]","#PR?9N#" -"Yqftd$~3> ","JHn","bB=","7","K|vhVdQ","n.7P9" -"s:;}/CAz:G","x/ixsR","6uwMr :Q","@#sK[;<#","Q#;ax1","" -"","","Uozc","s","","bf#Jya%" -"""kz4|ve","}!Lhy","j.jn2","ia","%r'D","c.$H\Sq\" -" Wb]","W7)","/=Lth`&","j;]fTs\N","kL;hCqH","Y1`!yg_LX\" -"f=",";zTu\,^","cyfRp:""","}X;P","$vK=3G08]R","" -"7","H!Z4.Nf","7$6T\B9","=>Dys",";/!X|j","%,S" -"e=u","N~dL$*OcW[","TGJ(FcC ","gt{Q(","pB^oq","","o:=v","C8C!P')&;" -"%s1=xGc}",").","'{cd;9j\","C*%,*&q%",".D","1I5hR;2" -"7/S=z!C","23","38""[KMT2","bwQAR.&","4ZF""]""D","/WnN7\!""a" -"iF``i","""AAOMk","""73","C&?mME|","","5)Y3_gx=" -"","1r~","S y","J6Pn","#kml","N^ZNj(t7" -";+7j-ob","9","y","NUyDn.r)","6B#9U","" -""" l",">P","]Cu""ZOccC","K>+","tc6/Gw(1","-fE1IJ" -"R","dY`74<:V1l","pj","PyD96M,","Au[","[DTO%c=" -"-_jo6[GJVA","dO","mJ&","$T G&","W","" -"0""mgGkS","X,[WN","Wx","-_L+F=R","3^","?*" -"Bp Usg""","|x","p?#^G5G","k","HUl""Gysx","7|E73" -"m$L","uG6_ eF_c","yp9cAvmNC>","j85=9P","=5,@u8",")" -"%:#7e^dBS","**a?X;","7","q","%n(gG-xh","jb" -")k'[*","BjFP+}@O:>","Q+W/\a","_}X6^nE","PG1B?","A6bYaZ" -"%","'5C]n","{","NJX$T*K4P|","YF`YB>","bz" -"h>>h?","IGDJfnfI{Q",")1|""c",":pVm","","f/" -"#`,k~F","|=n'","wEjZ7/0o","hh","1{=|ZvYES","OH2w" -"","./2^y""8r","mI_7SbX","srn:Wcl","","S" -":^bP","x@r)Hz4p","F)|N",",","IeIAn4B;","U=x!?u" -"Hi","PHM$VT" -"2Z`)","","=n,h5)","kPmF[KI""","*(#cx/ig","xR t" -"SJD&A?#?N","t","*","","^HMb/P.","M@t""d$Jt:c" -"`Yi","Y;RS%","","Ua WnnBCs$","","" -"","{t`$Y?","","XUa,dgi","*gV$","(!tG@N6" -"","lr{>Vb","~X","y@I","F59|v","5TUHH|'}i4" -"*I?k",".FF","r","WxZX%t}%","xMJ","7|H'QA" -"9Q","MLE","An","k7","q\dc-Od","""U0O_85","0oH2|T;","" -"P 3","rW&i","e","U",",3/'~D:_D","zaG;Pd2'+q" -"R","6","4\Q","(N","LB/MrcQ;a","=92" -"1\{Mfk{=E","","#/[yR","B$'""=","[k>","p L,dVkp" -"/J","9Ze;,S","43I","%U","BEG:i1","!E>v&-'ii:" -"VjF$e","Y79.j7-.n3","ro=k","!Ksx","Ko}N","jr3N?" -"8""xNF","YT0kE'}f","Y0Qc!T!5","d.ti",".LVo","(R" -"@?FHxP% D~","9{]Cw?'fVM","m;lI2ZG","2,I{D~C'&","wf""qPS~UV","O1f`]u;Z>" -"t?2c_AkD7","{tZ","K'}iy|p]y","Xk4O4}u","Xlu5N","r~Ad3\||}s" -"i","klzafkg:s","SGiS1p",",uPgGo",")W{0x","^m8" -"-F""}&u&::t","H@rJ:8VI","G=o.;2TIRy","z;<","gSt+7 bR","~F.+[" -"*);TRx","JAJ;An<","g","01H",""," 1J" -"~G^q{/Y","l_+7|G(;3",")&1#","f\","QZc,xok `","*R" -";>?ceN","z","ZJMjydz","v,zoi CBk","","@Y*Mf$" -":fGlmr","#(R-" -"T8zDm","k]",".",")5H*#us","3{","Xp`" -"l|p","","z4cst%""8E>","l! 8*,","=c%","" -"vS,eM:","","CUz]","","o","['" -"'%#","'@?m@aL",".sK","","7FC\D_p.0J","nT" -"r","<4&F","""xDnr@}","v.=8%6","dza@r:8yOU","n1ho`Umg" -"","z","xVL`o\QC2h","i~","gV>6tHl6@""","TpQznu5" -"/",";M","Uf]aO)","x(}UO4","","ec" -"2","VnCAM*J-(","uBSU""z\",">hr17&rW","aIObt","" -"|","`R","hBdRwI","=","0y-nAT8","'" -"y","`C1u Fu","U""","*","CHKw&","k" -"ad","u!","k%Kf","l]Ay!ga","","=3)TRTy" -"*{n4VO","""UD%R","{0fFFj","sI5Kj","6mj9}P","Uz=%=PpJ5H" -"R<=e","%taOp5h]Yu","ikx1(ixX","=J!","OtEl;\kgfe","#&v&v:\" -"b>OIF{ ","oT.","[Tm","}C]cL","o:k ","","s=IA^?[T","ZZ/613!rY","L'P`S[_I" -"#","I9","'aC" -"YwZ1~","u|","'$Z-QJ","Hi9ep)o/","t*x","'" -"7$.g#Kh=","80Vo*y[","cuE_sCfIh3","|,I'uK","^Ip^yU","d^jH-Nx7\" -"L&Q^M","yt6o]Ev","f*!]x","@b\6rQj%)","C=d","8GkJTqjBDq" -"P&","`~w_]","i?i","J-;ZeB~?G:","$~u{","sM" -"m","+2to","HE/""!","3","KMvNl&jAQ","Y\" -"EN","Nr}PW7t?+","@K8W","e","lf!WB","mNO" -"#'KQO","nf5'5NQOe","'}\3i#=*X=","<P","w>","k0qmxr" -"","l|g3_S""","cq)g&F","~B+/:'$/","r4?y{$","X" -"!B*hQv+","]L%\h=^ja]","ed'D0#","P*pN\dcC","R@","L[_adlPve/","Dbl9e+t" -"","]`d(0a","G8,","f-<","" -"^.PAa[Jp",")z-","$;","*`","8WnrFEP9","%X,Cs4" -"#%t","d<","M55K,.i","MQ/yp6v","V;9p","`U""I<]9" -"aT_{TI2L1","TL","Lw7A]","B\9cq\","6","]o{o" -"|IG-""","aPBM&u","LBt-jh(#7","oX","6^le'yJ",">!u0" -"oU2$p","nw)HbO","twDSt@.)H","_Sfv","","" -"P,t#+^","=\f~","-[","5RnMDZ","Z","&_GTSd#b" -">jb a","j-m%.)W","}>?ibm$Ej","DU(x=W","","'" -"n","@v$&TYmK","m%","Y{","LkG"," ","|x@#","UbI","G" -"`""","&","fdcRXEN._`","s>O^6/i+","OXg","" -"%A","}G0.i","yk}g~?>!g","UrIhIn,%","do{#$R L","fBC" -"2{Y-","Hl@eJ(}1y","XR!a","J=]_6"," P-d9ev","1?g" -"1R",",8*c","yD>_`Jx","%X9","fbuk=@f","gU.&{%" -":)""WmHE[)E",".uxm07Br&g","_;Kv8","Fuygx_j~`/","jl","E_zs=" -"lwD[3ue","aiGuqd+U""O","{^f|P~&3l7","_","M=H)%-pg","2{8D" -"}FKC;F>","^aBSL*n}","@S[Jwj1c","""VZe58b<","Hk20'I","" -"8J7Mp\5^[","=""q","ibI","Y""Opp0","Q@D|Lj","'.1L$([mMU" -"ad>%ZL","'MX!mvqH(I","!{4>@_","Q'd^","DF?L#*","+" -"","qC3~bhdm","1$w","oI" -"F`",";82`wbuNT","h","ioK\m","r","7@""L$s-." -"GjKg|ra{","\}y^NS|H2V","5R2%[^Y","JkK}k","}}","C#N" -"8#sK","B&Z:gP&F","]","zh#?d","=","`^GUM" -"FlZ","l","_~<","w","Rb'Ad","hX-" -"FkzRi","","T+`eWqcVV*",";[-y","S=&HK\'","-R)sxn`c5" -"Dh$Xh'E!","RJZmu[A|~B","u","s""}4Eg0gZj","]#L,0uR","" -"lbNx|","_Op=%6{s","sC'j_&lpY ","uDHyz","5CXx|C5*","H*0nKRfL" -"",",[>a-pvV<\","k9N--v)<\","]K0}o","4cKze;`N $","(" -"r(>Kj|j[j","-:_","D","([O","*rN","P" -"","o/L&Nkkn","'lt(&","","=FKWv>""T","Wwz" -"0TTj","","V1n6+8ju","5!eo","ll1`*A?","" -"~h\s_]","wt","AgDG","sMsY/TH","jsS",".dq-fJF-." -"X20I{PQy|(","","/l^~Lp34","O`]I","@uUg","\GqBJ" -"","H`>","Hmoa7[z","F8o?`",":","&Mf1>Q" -"z","|2! PXt_","[BJF" -"z3=n>\EO","OZb","J","$.2;_\bQ>y","x^","0`=^B" -"Sd"".dF9B","F]-n#^","","I?q","L","""8L\S+M^" -"BSzoV0)(z""","s","=9,B9","#q","m9","|Zm37ek8" -"/W","",",u'1m>`/P&","",":&ye?\Dt","ZT!u%\hNtn" -"{cx<^U","HDbQ'cO:l",";Lox~w1=Oh","D.qpb4","","9NY@n5" -"~nJ",".yL^O","7","""3` a~q_","_7" -"[","d","Z)Dm","HaW2R@v","}Mr",">}" -"","}I td7R","MVe=@9]*R","?","","3gNpp-" -"au7","$BMx&<","'&YZsIrF'","q]h","A[<","cE_J7" -"$y",".7M,B","u/","","=#'jtlT"," &Sb" -"m","&M?31<.dn","_","^",",@q#>","njFq7VV" -"","1","U=$FJz!","~0L\d","4{L55IaX=","j&JJ9(/:W" -"","pVBa&~3","\:9f3>e; ","B","F>","m5M_.XPAva" -")|unsjx%C2","}O$5D" -"n&{kK#$r","1)","","1""6Q2.","","Z-}'R" -"s","nUN@0pj;8","-B0g%FRB","Ku","s)_Y3@4\X?","eQC~" -"It2V* M","k]~,Bu1mc","HliCCg","4a9+$wt%oW","XOz`0eo!/h","HrME" -"3c6wL:","b@O+4k d}","(","{%0d^I;K0","VlCgF1x","U>7NUa" -"UT`t*>","}f5m@""","W:","YYEb*!KFns","-~,<,","pFwko 8[t" -"H/89c'","","Jn[V","Ae&3`("," 6[!","v" -"F5:%YAMMAW","0","KjMRKB","Z","",",e;" -"~K%o^dDpB0","\Q","_FD","b8WQm/=","","MZl3\LW,=" -":D*;","|IgY`",":*D[NGr=A","X~]oe14","","=~,0NX+6" -"5K=#|,uX","9LNs","[FG","cp\&D0&fk*","+&",">#M]T" -"='oA/","","xq!Q;8","qzNL","`duzWU","j" -";;~/.","U:'v||","","","l1a.2","0LW2P" -"Y","n`@!","J>H!2F","rI3>kN|Ac(","33L","z-R:-iQ80" -"y$k\","6wNF'D","6aW[:j<"":","OQ(e","U7[!L","ES>0]~r" -"p!=","0~X2^}&","{OGGdTA)~p","6 gY-","<*b4!cuh9","2" -"%z^e`M/.r?%I","K","^>$","3x$X","!Kz:0O." -"uK40ZtQyY"," t-lzS","","eF","d,`Z^1.$","Ln" -"","I-a&%.W,","wAo","nuS[|p"">#[","","[x#Bz" -"t[FV}8i","","V~7Me","9c+H`ALto","`H*$=Bgo","" -"}wOU\;>9qF","F#N|;","VN775""G","kH5","&-sO1/N ","^" -"T09","mLW8MMJb-)","NF$i^dy*","","^"",dv""|{/&",")JtR,(" -"5ab`[","),@{]kRm","HfaEwLe[","p","[U*M@J5","x" -"X13=T'&","!p:vnLZ+","Q)6p)ga","eD4IJE($ L","Z6o","" -"PX","_v>`Lg","","xB0^|]","YJ_-|","7koAWFYFO" -"t1@Bm","}mZ*Q}S>)Z","","6(r5","oOFDYO7WZ","nG%b\^aOR" -"'vCU""Y","|FRSC%5p",")c&,","tR2","MbJ_J9~fVf","R" -"(]ZMr}","4O<-EVWg(","","@r","","_Jv-!;$]+" -"","TMmwpgqiVt","{&ao%<","","q#ho6X9b","" -"b","","R|5W1q&","4\WP0{","5<&aQi{R","]+gM&m$o" -"IO","bh4'5O)SXr","""eL","^j8?|OO","zuM""",":Fsb" -"lg","{","x=" -"G=|","","s-b$#I",">\+","'9T\W->Q","Ef@" -"*d%;","=X[v[H>@","!","xa","/\","z" -"","C'Wq$]s","7bNW|5","~8wK","G""IS]HuAKf","bR&0" -"","v4j","u)MP)mUS","M","2%+[ ","1~" -"e","n(""$N-YDo","@~T","}q;*Y","EDJ$","P\N" -"!87kz_!,","rCJ3i","Y","9","gEl","}" -"bA/'","*p%yx","MbR^<","rCz%q*fi","~HH*","40AmOg" -"MOf4thzm5","E&'","TdTyX9","y","K9iC{bnV(","n}#=Ws" -"hO",">IZ3q}ziS","Ld+hkl)B'Z","psm)rYB@TC","OgqG`N","" -"lw9s","A,`~","'A?>fz","","]}?y%As","lJDuGI/" -"EdM""t","RZT}bLx{2","^&ek`[L","^]Drbd","wD}","t!\zOD?J%" -"","""%"," Hpo'@nY;P","1$/","nuHW","I9" -"",":\","B}9lv>Nb","{Gmx(L","/ug-a3Y","w*h Dl].A7" -"^l(w","EoO8^","xosm>5","<,kU0b","","?1`j" -"q&}/sKL0","tI9","+_`=","p","i\C^w]r\;" -")gmS)S"",D","KClkeXt","*S5pM6q","U}L`lkK6d>","m*Y(]O","t|&r)0T|" -"_l","KlpL","X","z6ca}f%@","_-2tQru",") " -"1V,L","%RQ&.tF)ku","`pG_qCul=i","GS_zVA","Zms~)/w","k","%","svQgA)","#","P998]Q>","5+#2az)" -"uV24q","Oz<.w_" -"v' q({d;","+njk}","nn|aa","4B","bHSZ%5m@Q6","V)Qs.Ucb" -"I[","U","r{=z""_PD","*Be7-2^68","}Dv_n<3ouV","" -"y","","K","","M5) |C4M",">1*YIeg4" -"{!h^{;","kFUNy","U.i#Hc","e/","","a<~" -" aB!","f&B%6&",",Lz]nPWGe","*;B9Wi[","","4" -"DKNdP@","3eqY","j","k&1","Toz}","""YO" -"$)'",",U","","M41)6>Q","[v^6_UP","P1y;#<" -"j]ItKmw4","uY*[2.","c,,","t;","PH.Dn?1w","yI(" -"#F}58/Qb","(`_%bj","7d,yx","73g n9b","tIwI~","57" -"`FZD);fk","km","a$cagNV8*~","u#>`","L28cl?","K}E85Y" -"k<~'Z9(nL","","N;5@","4Yx+_@u_zj","5N[","hx&'" -"vYr/45)","-{s(","vjh","^%A3!$mp","V+","GxT6>><" -"/w","z","3:8rKE\I","5!","","ZL~8F+ZE[" -"e!?jMw#.aw","|=e;*I~G~","}KA","6pa","","dK164]/w" -"5f","+|H+W","|@hr","A6%|R/","","HL&q)-" -"$(N8-Y","x:.!9VP(","qUp2@","5g_-W)rW-","","wb[w'qr$a" -"Cbuc","1GH6.7kR","bJ0\*","Ah6XW(o","J}S[8A","EJf0TLCW" -"7(m+/Z","v"," yvy}V3","LWU]",":","" -"rmS}]N","=~p-+]93~f","s","YAT","b","HiX&I" -"","A:d","jZ$","Vex","","iKUth@&" -"","rlSrlQ","","bz0q","frx""8","En" -"J",".@W@","~XP","s","WeKNE^rr","" -"XREU+4`joB","g[P~9[K0","E2`RqB=K","""][_W""~jq$","Rh;D","wO!!e(" -"F]v/",";+4(ovGD","eTB*rFq{","$&J`z%>","OVf`g3<","XGgeuV^j4" -"Ch(W","U{W{","NJWI}kC","<","-pKXGl","6LCYo" -"sz].tI","=;-yVdk5","","UNdl""[u","R*|hnn(|","_""&N>3G","Ajhy_^" -"\Spt","Y@a`U^","\5Me","oVqIa?dMB","H5]}","" -"Vci","%'\","$Ok[9VBm","tM]6OR)FH4",">g|J[bA_&","c" -"","s","_'{j)","%i1p~@f3n_" -"Rtl&xxi'&","2","dv8h`"," goL ","g>s3v","B]S4&\})" -"d;}","Ve3{v","#gp&D.2xn ","CQ","^d:+_btUp^","&)3D1T|rLr" -"-}~!%","|>oZ=O<0h","*%<>j52","","","H4(qHjctO" -")I","D[`XbyE[1h","$ZzJP","Uo","F,wfG0?e8","`.i|SdFdO" -"E/","./""UG+OR","pELt(S<,","ES5y[","h0966","\" -"I?","BQP;7~L","&",">`!#:Nz","","B }zNS" -"2-W/","5(y0[[kyO","","vKan^ge-d","97iHn@IS3{","BL=" -"*q,6","y(G,","yp^NFwy=Bfx]@" -"","frl:","|3","|d","^Lm}K","lUEw8>f$?F" -"","Kc","sI","4I8","tb6",":D" -"0\=7rQa","s","o64b>5o_T1","@xqS>","D|GyACR",")eXD**j$yB""%S","|~/",",@","","gLq@]R" -"4>W","$Wm","","b","rrl","/" -"!zp","T^C?","WH$@","S","3,pD7O3$","NWz$ka[3""5" -"`7,:U","""RS","0|&`2.#rX;","n+","Ku{8`j","uS&_" -"M,#-","","+V;ef{#}bN",":/=Z,a6uI","l_de#$","1YdPz,xkeW" -",M","$""\ 6A:T","","m,|","x","xVJ" -"3:haJG4[v","F","jH_gKcB","z[:V4[g#Y","W","n(" -"a#K","","","-mgzOKJwv2","4- #5<`yKr","Ol[UndAVZa" -"~","[/+AmvQdEA","f<#","R[/n","b&~,6]o+7:","|E,A[7ex" -"["," xV","jy&`U@a`h'","F=5T@fzb`4","v_W>c7wpl","T7%r'|S2]a" -"~{k","ZgX0mS","aV",">/Te]<'xsI"," -oeH","*C.WxeO9" -"l+n2CWN5S","F89[Yc"," ","","","^|d" -"PI WJ*","i","5c_Iq|","t","p2%xeR"";","_C5 ^." -")arZly","B[m","e,os","P`~NLO&","U{ 9H(>","po-^$" -"0VZ&=q@)","s@p:","\","","I}}(1!qb","b74r2Q1p" -"\\4@H>*S","RBDR","]/?","",".tUDA6-]!-","o(""AXMcBZ" -"lt","O""Ty&`A","-:","UjXg Q","q3VAM!G","`" -"D3_|\ShUhS","Ak5/1Fti2","t#5ns","i","ijg]2k"," @x>^zh+%" -"Bt3,Em'&8.","=p","I3\y-%d","m!l2>\","*%""""`_9h","&8" -"|dzx0","","kxY o","","w}a1vo","WP9ke1" -"i0{'h0MW:M","Y","H","6tE[;!,@","","`:,U" -"","O!A8","[Y,","lvl","=_&a7yd=","@" -"?[;V|(aeP","D(J","g\J#","x.~3v""CV","Du:$","NJ" -"s/&hsm","\S))$","","0hiCsodx2","%2c[K^^","H%d""~t[>~" -"DuW_&x-h","hjf","zXCvV","1m!C#","?I","K" -"J:}","u>","l|t%FS","u }J9k[eu5","7#zXM~B;In","{|" -"\>'D26OR","wv{w$2- ","vw-E","Y","""P","f[X" -"dykP","K5vi","C)}","KoV@}y(","jFk'","{#LhXAk/" -"(Pu%j","#","sQ\)^d8","w","aE&","+1K" -"Q@{[S","IBGW>hVcD+","(^-8l","A'u>|09E?",";#4=","e,4/" -"XJ^0%Ud2(p",":L>~","{-:b'B","5*'0IQ2(+","7l","0" -"EsRjSf,-4C",":*XhL@f_:","","7","AjK-d","O" -"vT]S","!*<5Qp","S/.sk`?*","+!" -"Y)?i8{a","uG@Q%$M","W`\=WVL'vH","qa""1\A.Orl","|@Cg?/Vk","W+ KG6[!^" -"d=[QB","Q",";9Hq?$9e(F","5#Z`A@zpCS","_3tO,&","]qd" -"j3U","Nu8","&}za97","H]AA","}x1q","'!" -"@d","","","Vf","O^GD_","q" -"p3","tELnilg#","tk","UeBQCQuMa","oV`a4(","V_RI(>P4Y" -"nEw_Tt","","6Kf","ZC>OE5","DV&rdHVK0,","?do" -"n>#i.Hv","8=","U4E)DJ:","&:*huDRSz]","4Z7[\H-94","xaPCz{H" -"g'w>","vF9Gg?m","oO","""(BV,z*","@a!cc/9\}","[9" -"k{=","DYrnMC","eu%I+_*","/DW_Y\y[yx","t","Cla;}TJK;",";IY4" -"~) &","","Cb+g>!0X","f8Fo2{","w_#","" -"","",">","wWV?>zBh^.","vp","f2",":Km}af$","?{_Y&" -"YBNV","JA!to4|k","^","3-Yk6#4","L)","oi=]" -"J7mlZo\_N","M","&[&^p5/0m",";6U-3S","rR\c]]v","F}C>:L" -".DDrM","6f7!","m{Mx{G","~K","BEhB9","ps" -"}4P,8","","fj4G","0$p{E","/YjE*","nQPn" -"^lY","H)T@","",")XH'","@q%j^(;nC","J_GEG>w'","-pDDH6SQq","g,","N0V4","","" -"I5)","hb","-wc_c~i#\C","wFGA","2rvz","((fntfviO-","(","]l" -"AQ;M%>4","G2-RVh","mwp4p","(3tGM1`\","iSHO?&3","Is" -"rQ","q&n7u4)<<","""%j)^|aJ","~h'(3N","","_=" -"dE7,}.","","qbVH","""RYl8V)]","kbX},","'buNq2" -"n0O+P8eg","aL(W@)w1""H","SF_UkkU'T","OK-QAJ'","Ngz/[","xH+" -"!E :/S@v","Joi~G'Bd;" -"","A8NEc9'","1\(","1GvT@{%","^!#3h""0ra",">Ot6}" -"%$@sxb!n0","mA;B$T'R","RXx" -"W;*?c!L","`2\_z/&","P","A.9t4","W]o.","_r&K" -" -TnzMUJ0.","wKi1","","khI(o","rJGy","oi6k`J" -"sW1d3","|zzD",",7","Qx\","2","" -"d",":QhAS^","ELT$WX","5X|;9t|S8","f_Gw.dGJ","P&@t0e" -"dPFzD-PlTTS2","CL?|U;HSgl" -"]EJ_R1H{eP","-C]|l>q","P","3mR|(","00N{-{4V","I," -"nN ","R&:","czgSf","8","~([","yD}z?T" -"/='",">{^}" -"{.b1 /{Ta","?z5V*l&EFa","L07|;`a,p[","qXG-j.+i","{IC!^EZ","" -"xdMO",">",",5=","L",")y3> l","[#E:xdS" -"4kWy(","q,}\","_1i2!=","v^1*;@u{""","V","vjG/rZ}M" -"1:ogQAJ","","%(","m","0WL&?V","$~" -"^$\","a","fls'o","_",""," %Gxy-TQ" -"r#&H","q","j{9I0i(\D","1gfjbCLR.5","/Wx","#""hc1XM" -"~:&Nv1)K1","a}Rq","3SJ$Q.C62t","Dd{RyK7Q","LVK?*8%(BX","opH","EiM/N@&K.N","G!`[j`","","_tB[Q\","^cCECFz" -"j[]hAoY:g","e06Ox","/][{AV9\k","WAz{",".","Xoa" -"906","cY\VzA","R_D@","Wy","!<8tMxLv$r","UL" -"","z_y","",":}","N|gi","Y\Yb$T" -"rSK).^[K","~(8Y^W%d","pk$O{t","m#|;","","/imFpGE","%Evj" -",PU","TI" -"\Y*CkdMO","u,ZT<$",">h""","V4T{ik((W","b|""TE[",":QN4b5" -"=TE|[IE","g","o@","'#Iwh@|","*iI$","6ZG6es" -"w","",":X'tPQ$U-k","Y&>vD","3","M^33" -"6?@32:u","kzfVjL(x3","E0","de.|Z@gXM","5H","Bs&" -"A$Mx>ml^n","@F7%;","7]cs0/\yk","1,","'|E4Aa:IE","so4kQ|pAT" -"\v>;T'I}+""","","m","n","7!X=o*,[s","b.^X" -"7?}8","","]PrI.","E","HfD`l","+GfQ" -"]0Nq","R[cI >S","JlCzjV",".","}>.Q[pu48%","X'PkPf" -"k>)s$M(b"," &UOTtZ)>","OWS","]","&e8","fMqlS" -".>*6|","1_" -"FNzKCZ","Q6|jGO`","h2\d'BvR","CeW","","2>YZhc#P" -"2","DQ""=7w","]L5&","","~","^Z4|}1Al2." -"T+}Anz","vYY|Y@fw","~5x8","P","2l-","3$.S$SE" -"","~y/","glOL(%XJ56kk8","""wQm6x.D;:o","c^O","HdHyB!@74A","=Xxb;K""","o]eg ,c};" -"z*","jd(Y2~" -"J","|=o.))kn","^+?l8-H","Yve*vf88.","ss.+C~&~U","#wrXq'~p" -"V4} hj6#@x","B*oW","@j","=/WqePy,-","6\T)","5*Ta'" -"gRvpk|7","VOuT","Z","","}/KUa@5","M74<.kAe" -"e:B`o %M~","aNIe{)`|\","deZd","U(An","X8Rcy","V0w%xY}V8" -"^yZ49{:","4K","PeXY/<)$","j[F+pB9","^","&'%xo" -"c/8b","n&@_xw>","I/TN","],r:","@t:ze?~","A!qYD-;" -"QU","!}eU7h","5Y","=7","","" -"O$oG","bBZ","","uN *Hk","3","X)" -"","YC/\APy`","1Cu6?!bNj,","O=2 R !,_#","yvQNQ","hpv2G" -"Iw<","tt?V6b","5?d>","&)b0q%h~7",",r}kR}y","" -"]@\m","p","==IYuOq","<2fMt'hNoc","mu!Idz@","c)0^KEAcd#" -"","q}@35VuN","Ud""]1y=m0e","r7-iV",")gWB=}","" -"e=GzB","R3Hr*o","'.)0/","","nVZ","tU'6{{/" -"1+*kkNF2","TfA","60Q'T~X+[","W)?Tl"," Vr""'/s;^9","9#" -")9qv9H","","hZ XU':S#","x","Zy","nvB+" -"\","xFZ+3X","L-?","aFXd_R[v","fuL@NE;","$p" -"w/`|^e","w)","","N W","fE2q:~k","" -"HD7qI]R:","&coY""","(71","",";F","U" -"``w","E","Zyc],","[gG@,9niw","d8eB>5,t]","3k*gC\Q&09" -"<.2","","""^aQI[",".","Z","+L%=""" -">p","hvAdZ$@2z","9EzRQ",".m _*S=l{","x","" -"cOJ;l*v","f","G_Y.oUz","j","","V" -"~SLIP/6=D)","%d&6/P","d""P","@d","","sOK26@w<" -"*_9Wrj/FGJ",">MBsQ~",">-?Sqt'yQ5","-Z","r8W@\B~C","","Y6tN'VMjw","l&nB" -"scult","","%^T@)","3-<","u:x","cltd" -"","KTFNw","+A","2w","Z&*' e(2e","?" -"+ <%K\","9d0g^,'+","S'-xdB","R0sGmEy11",">","?Y98agC" -"j#|{} ",")","&","HAh""!zA","-cWn#a","Dq" -"","rt>b","R",">~6di",")o{dJ3","zNS" -"mn.%U""xwI","=0&1nde[e","l-g$)oy","8Bu`Bm","KrA!Qtx","eB$3" -")o5","@UC9yU","EqtWT","2Ta!{","v@Pdj+[S&R","5}c)\dzy" -"Etza9|","7ASvQy*;","5Tkm","'}mnxT" -"kY[uj!","",";n>Wxi'}","|9SA","5Q","6emu/~" -">h%i""X","t1b","bz yE","}vkn","@p","""Jht" -"","fGt:A","B","*","\]E\yEe3","k" -"'|?oF:4<-X","gLKCefb""+[","","\lk l","","p" -"g!3u""7","t)RVmT","Iu","?sQ]","`,sYZvf=","g&cCz" -">""{P}W%0","*%h""","'#KZdi7}","-","%","" -"7$fn}5","vHyS,L*-X",";","v[","0cWihVf}/L","g5E;A*j3" -"3uk","8""tb*:","_9KK!","Df^{+hB(","5Z,","G7o5=(H" -":","-e","IJ""UVGy8>",",'a","gBsja]ur:","ciZ" -"@%/d2","GHJQ8","!QQjaYXM:","3[GqC","""QUP/_+j[","d" -"N<""mV+wF","","aviq","cp.7",",","N,|" -"","ZzN*X","4OYi&ci%6A","lX1XP","X/JVo[,","v|H`" -"e2(HK","","6M","D","VN:f;|$u|P","kD+)8rF-Yp" -"$X_]0;,","Q|v","=nYd","BSiPgqC","","8x" -"FuJ[_","Cz","#IdY","u`C.or=9*","=@-2'","e" -"?","GdS?h|\Cjt","$/uH","g0","","F" -"G","@_N[`?29","`l?@@yV\","bn_N","L4","i" -"rcqK(","!Cak\x~Y","}","Yv!CK@","=@c(b=","J;" -"b","*gXQ~","","M=","L{^","YKK,7$L" -"g","X{6@&mP","I?#O,m,","mn!O","i2X_,a,j","_02tx?.v" -" a($K0=wsH","|a.lr ","I,`S[","8""C","&","$YZ~,it)31" -"72""RWvO\\","","&]AQ+s_lMX","6u","j6}$+$U)","" -"s7E>gPd@",";","Nvm%IV9ZT1","3h2eD2yv","Fn5","|UmuEQWXs" -"T/]$,>Te","kOzl3/U8]M","Qwfc\3=!,<","X&.","G","Jv+mpb" -"7","j:C1!).","(]~Mlt,g","#2JA:~w","{","5Toqu||t" -"*.@wA&z}18","`Sp2w=A","f{%9|%*!?","qq$Eh:","BkSFS5L","6" -"i.<7#vE&",">Z}n#w~","yO","QR6)0=S","T","T\D6","" -"+","RX6V,*%f1}","v~lcqf5,}j","k+B7+CRF","","_ci" -"D","I3mFk#","'","V;6f","/D",":s(:" -"cKi/Bi-P","U","1p","4sc/*Lu>s<","@!zM2","ZQM-3" -"KFq+*40b&^","hc&D%j","y^?('~/815","","v%BmJ_","s","P2Jg12LT-{" -"*,","1:=C+b>",">~","G>>f'*N|g"," 0P&4","ghtQ1" -"Y","Qu*vMl!r","@)G_$9eY",">C.a. })K'","93jw%MX","1j&kjXN((" -"d:","q","$nuLrp","Ex(%|-'(E","9bPt","lsg" -"R[Lq$QbQ","","p~","=&B","bf[r","fcPUZ" -"&/!AP'of2[","","gK%S`qxEi","a""HQ#tREJm","V9?",")" -"}jpsIFQFV","6,hLe","9QZK","$(Q}","swAH[!","nEgdsX9Uo" -"DI","33","fKG}8","","^c1","9-" -"N?m8!GSJ9","Y?@@17=F","4","Xwdk-ch","!;{","3BI" -"9lfj","tj#%@Y]t","<","#L","mPk ","2EvG" -"Zsg6}5","3\","W$[$D~|h2","03C*3Y]um","/"," eg.*" -"8wN_e\~Ee",";{ad","Fe'~6ZKS_,","y)C`lnmZ\",",fF?","oW\X-" -"j","o@Y","@","LP>%>=PS","!'b-{_Im7B","{o#3d5x0" -"6H!a0,{ I5","J[|Wj""","*7%\As","l:{}$@","L^!jub>U","1]nw]" -"70(Tco","\pHZ^q6w","","FXwjG","BB{`I","|" -"56%Ia0sCe","6mXvd;","I(hP`p]","/2$","|v@`X","AwZ" -"2jyg&","l;pE5r","\#;v","","","]_4," -"c%v3a&","Svyd","I&\?","o,/Dj1v3","BOjBe]"," Yfi6" -"hb*","*jC3bV","9>>JhZ""zz","# M \","w""^\r","9~.pW 7|e" -"d","I5e]H+",",Mt/6eW","r0o1v","( "," m_\""A" -",,^;%{d",">>","oN","flXdqu'2","n>UK","^1MlQ{vm" -"#a=;De","FOpy)G8R+","|*|W6H-","@?mTf{8?","e2g[E`","4qje|","z","2OwQ)","dp" -"61&<%Ig)",")","1","tu#","","AH&TN" -"y&","c)LM??\","e.]N","yv`opl",":",".wT" -"Fc^2x7","8pA","rudMziKX","rlMu@X{{","~6.vH&~-",";*2~U\P" -"[hQED6","eC?kD?;,~p","Z'c","d[",";.","0(L/" -"'l.u{","Q~S","kuXmX","yi10T]E","M p","jMTMC\uV" -"/","NiN","I.)7*","Er ","","" -"","VhWg\","tl","t6{H+v)cQ4","a","6" -"""W `e<3-=","yOgYfMU","J;8","rL16\vp3a","&gwR(","/V&" -"e\","B=o]=","!CxGh8@","V","uY","^l",";FPO;C!","Y" -"","_Z@S+(f=","O_j|tRLA&}",">&zf","J!St~q","+hdhl^" -"(\CK9","M{<:vy)Ku","n$W5""O]",")a60OGrN","{}FJzjxl","LF|\" -":c","G'","S","D-Rb[_zG","MH~SL|)t","""#[O" -"Yv(","1oFv%eYQ","`#p?q""QuU","cwAXB","aZHY","wyg" -"b}j\+","}oiBQ?]","\","","","HEexFaI7" -"zyP0+pa","Fc"".ZR.ZJ$","3Ahbe`q","Bsg>}W'","8EmimWCt6b","Ei]j+" -"g~){ z","","#'S'","{+S","\FRO:M","E" -"5(""","","SJibvzbm!^","`FO","HS3","B" -"sPI 4*","Y9BInacCg","1!","{z","!'P","(P$oiJ" -"+,","r","*","&P q6","q@4B7!Qvk4","t>`]TaEeF" -"GL?E ]a","INJ>#$","Q_d>WN`a,A","VKIHGE\""^e","N-Vo53h?","AJH<" -"|\ RkL","3H","",".KwI","WD","8LR)4V#ed[" -"/a","R3","8rwy","G(x|;~","b^","[" -":k}>`!R0h",">oj3UxK@7A","9'usR.Tgt","jUMUJW.l","UMEa@","i~:" -"N?!^","3dYd4","_?px""{)",")ovLc`{","gQP$g,h?Kz","7Ekg#|%f" -"8","=#J^-","q)SN","D_*S""U","\=nID:","z" -"3}N(`","*=k","9;\oILK]6r","/+7^-",";B=$","@!lXYjlZF" -"r=Ml5","?.8zt","","eOG.=\","","V113" -"g)IfEngk/lf2","{" -"-[f^","8a","f[Hs:'f$[o","@Z_vg|/","o?j","f~f" -"zv","","q^^1X",">3Wa;" -"m$ \x","a2","9t","G","Z?","f60{W.3" -"+""k/=","2","s]Vp[,PAK1","_>xP","td51 'w","@Rs""fMt\" -"","","x@zdtwY#T]","Ngz{>Gu\","sIX?","Qrc?[}F" -"]z4a+","QTH","J0t\I]m","9","Q!R","_2>cVmG1" -"Pe4}A","t::r+U80k","%n|Gp_^qb","pE$Ks6l!~&","tE","Lt7-Wi" -"DV%W{4_f","%`gF!&x]p_","N}\ 0B }>","dMm)ZkwFq","S)","@a4kn-+" -"'#a","W:}G","zsa:>6u^x~","","]D","Ll`v" -">~jFgP|=c","bVK","eu-F23@",")]!^4","&?TJJl","4a*qB`S" -"c_","v3qX","IEx;E","I,po.>","hG{7}'","Pp&l!`"," nJdM","_:-","%1e'1xr""","W~^","eZ]NcSX\","b25?" -"}^C=ANXiDP","[Kh,&#W8","S'EB7zQH2 ","(_R6","{zI4BO","" -"","X","viLPqKpUq@","","%EMD=kZsl6","y%L8Fks4pT" -"+s","/.","","VAF~l","mgie9Mf;;,",")hH" -"D[]y","{|","Pkw[4I8{8","K53;yZ","/","inMJ,n&#R`" -"yQ9""","`m,8QU""N%v","&c}$x}c","o$;z\S","{>fka","@v06}6~" -"","]kBBjz&X$",",F j{Mnw","9i1*8","ebiSz{/z","|?0o""1W" -"uyP8","<'.i[m","QT:k0","","q$hlrSC","" -"+H@1dlA",">qkRH9rZw","@","h1f","hx+T","QVw=Q?bLDW" -";'Gf<","p?","sbd.""","q>]!T4i:","FGcX!-b","JdVe~6^" -"b+^d0","t6ND","5/T'L7","#&","/CIT","d4M" -"O","D8;iE]N","t51ZNd;l","f+~k;d>@Lo","|qf@sFq-f/","0,!63oA" -"eFwd;a","DBRf","BQcFIUm","MoJU'","O*Mn","rcd gq" -"QV/v=+_4N","o7rj?<","LFK/ !j1","*2<0h","4\P0q" -":rG_2""BjJp","4Skm;Lu","6jzpq","","H","[wK(v?Ncd{" -"!","","ElMY}i","aznu&1","0t#P|I","p4Fj" -"R&-","bU>xS(1`K7","Cm\[dJBHc","^hBulws #p","lI","+p&o74x3" -"/","oX9[Ap$","@","B>tob","b]7mzF+|","6." -"","Y6-<`e","EVd%.W,v'a","F ","CnNAUInAG","o(znE" -"","hiS=(#V","c""[(mdOR}","qY",":Y","n%$OS(huUG" -">2V" -"@iv","V2rq[","P^P","Q=TSzT|.","+ztzwmvN","B4JL4" -"Zh,","Ga(j=#RT","BzC)%f\>","AtSH@","s""&\78bHdJ","z=[" -"{H""ij1","lR\H17|p(D","IwEKh|QvD","vUsY","","Y" -":V_J>1^","!{AW","UEFA_","J1","0","D0" -":no","4WMuG","D4xbJR9","+~.sQ","nR@?","6&Al3b" -"S+u","","LY!66lV.","]&d","`hJZ`2" -"lG","J>>[rKNpR2S""","-'R(","1tQ <&<","z+ZzxTY" -"toN<\'0x\n","k7V","Tr""#l,sy","]f,ENi;","vWKY","" -"g((n","","2,(","","Q\8;+7#Mq","1{b)v" -"I4+","","[( se",".D3%QouV;%","4>W!A",">Uw`E,xJv" -"DwR4m?",")L,u5""","6G$3%$HiG","'|yFywU","DjT","[m1_YNjJ" -"1&U@ow!A","x9TI)M6\J","!x&","Jv)?#z/0","mHkE43(#M]","j>'Y" -"l=","BG!","\L'h+PQO","&[6N7>","","M" -"_;KP -Q","D~)z4A4o_w","kl&","/","B^sY",":" -"[',|w)","Jd","/{r0","""|cJH76/[",">Qja^O;","jfX" -"@;k2`;H","3N_(D}E[","G,3]","=","<","eev~>" -"e","~","q]$Ho~","GfXA$V.ukt","'TA{","/KZt)ess" -"0,mZ.}<","","!t>u={Wi""C","#""[!{",".Z{HZ",">rTnP[{" -"D];g8""z&H~","/.#_","1@L1","","R6L8W'","pcFXOGm'/" -".$%vc{","~yxd~G4*1","y","D","kBgBv""?A]'","" -"bF3""!4%O","p:,ZPA*",">'pL<~","m.LuDB","Oy{,`6M~Ng","W=L=H|ZNH<" -"qWQ3~@4=U","","URuEI)v~Ly","&RV","G.ZZ_XA>o5","-bp" -"$]LZP","C","[!/n","","`?[MQ","l{%B?]" -"","ef8M","QYq","@]mUz~","S![","o>x" -"bBr0fIQ","$`!c","^d1Y-I<","h>ywp""","&%","%" -"W4+f.","mNTCn","S*2qWW","7S","?8kjD84@CO",")T:0" -"HCS&TN4=}","-.cTz8c0!",";c4WW'","","i#{X^-?$m","m?D}JJR" -"^3CAzaBv","W","g|IV=`",",_I","%+%q;b","u ?" -" !YW`Smd","","O?","","{WeO:Dl","G.@0om2D" -"ON_" -"xb!,k<","]@>DX~CAv","u'H>z}XrJ","JEyy6JpO)","*RF[%6q9B_","eAOZ*>+Z" -"Cx_-o","q>J2`u","!Gc>AC","GR","b<+J';BWy*","P" -">Uy:'(0)it","o#]oCMH","$dqJKQ)JjW","B@}iKE","B","kL4]*s5" -"w_k""QA<","N,g$eqH","bJ","Ua\2g","p*","wt-s+p" -"t","O@#1WP!","d-Pq","8%=Vf","ik","F_}E1&a]","nBfw=U" -"^_$j!w","","cYkkG","s|wS3Q","x&Dks&","2A" -"?GmVudUd9","N}.+2","%nRDx-+","|Ag7","aQ<*W1d","!`}k}Vk" -"\0_$9{N","WBV^Mr","x-","","+",")nXV$YDH" -" >/H=BN","TH6{q<*","I5$p,H","Hs0kRJ4","Yf<%+","$`w0J" -",C3q{2h""X","?PH76*>\p0","v=N"," Dc;=f\52'4m","cbN--%" -"u5l\","k[ 6=","xh""qA]J","VX7","E2*0tg","~" -";Vvb($%L)","lI 2D","m]kY~+XT/9","m)5","","AKLMgK.m," -"vBMCW","V[","n*P,af","(>","~XBuxh}","j" -"XT","F}UEA","j#~BK","g7C","@l","d/OcT-+M;" -"P|pB_n~}0","L","O","zY]",";""-B","e>.x" -"$hg@FzVuec","H;,Kv","Hw.XKk2OrZ",":+3z8$!","V*4:FkV*","p}hiB9.&[R" -"","Q!#!6P/","Sb`<","|x}]o","TxB%},jrj","fwGncH" -"By3(O:,lZ","@-","$8=""I5FF","s(","|_QfdY?`}","\saa/eFMT" -"UR2`U","I",";VL<+H","5VK",".V-q6"":","CIh""E!," -"fYfm)!","","dY-|jg`Fl""","","Doq","+" -"3_A","lNi\CV","*#0+*TBm2=Tl} +","9=SI^m" -"EiRk6oQBQo","qj?tVv$a","iX","uftdmnN","v.6","PPn*4.EK{" -")f5S","{","B-oa-:?","w3=!AN;?-@","","N<" -"","""l","VlKy","IZ+ ,5N","9'o.a%H","*.'u1""jC" -"0MT3Rz_","()kd","","GF","Cj""ntU8","$UL~xYW1" -"WkwEEC","","""ASSq","","D^[Do","AoSoUnQt","^rn{c]l*","y|2uS","78" -"P0h}^8tl","@0aZgTN","@fl<","Wm*","9pYy(;","@Lc5s$`1gD" -"iQa45[.c{X","0l3nBo_|Vr","TaYJ","z+V","K","l]C[oEaGbF" -"","mvQrj'dC1o","j",":r\6{o~PQ|","","?W=" -"86fo[","{)[z",".O@","","`fw?D8hUAep","47TW#).","+q|-,|","h3uP" -"^E39i~","M$\I5],!N",".eD:4","@5K","~","KTe3l" -"OHk","/n'*rblt)m","[0.'bT","=zjy%t_Nap","kd","J:%)aC" -"rqU}85x>","It^Y=]","Ul-x4b;!kK",":WufT)[97","&Z F&v[TZf","!FT`8Pv,T" -"","wZ]","z2{k)",";**k3F,7(",":_T0~S""",";V&&;34%R" -"C{d5lt~","u>Z","","\","mB0N.","tIW`k=u U" -";biUHorBT","45k1i<-eD","{VrMw","!qM","Vhr!","Y78_Y" -"7Y9{GsBEw>","q=V","9M[EiZfNy","C","s}Wwyq:{h","lg" -"1Fi]","tx;Xhip","{sR","1Jq","","8CBr?<-" -"v.X","b","","","^Lry9",";K@X" -"0\E`e6ID~\","[","7","cv%M","nAQf{W?q","mm>v),TM8" -"","{1_5hB(DQG","?7$ Xm","z6VTG}]*Gr","vzy","G(6bCkD!g2" -". 6tY","","P2","+4&)2sJRD","G##","&{" -"]*TBo","c","L","g1+J:V","","@WZXyy*3e" -"__c*","T{U%VJ8K","p2!i;r","","","Te7u`K" -"8g","rhp","<03V\k1@",".^UAWt7","vva\CqT'C{",">rdq" -"HvGi","k!P-`%m3","Eg'x","2","xp-W9O]h8","$zKf0Ak(" -"RR-rW","sN2","y|$4)b","-b x7+HDJ_","9QTQ&0u","VM" -"jR@8o+.!eH","$gZfMB>$8A","","*C@:\cM","l/{@}^F?v j.r:","h","""","Zso+" -"jczC*@6S","uXYJLEF","*bS*a+wf",")_-B","*}EY-8","-~g/8" -"$",",k","","[0","$t","[g}\kb&" -"","","Kk%qFAaA{q","B}fQ6p$`","p37","n;!jik" -"w 788?Xp","4KHhNNG{","k/4ndZ='!c","BHG5n\\c","'&vs@V3-" -"","}Qwvm^td@",";o;?Jc"," fFSsL","O$${:;q","`8M[O" -"yHsHt_Z","N%aO$","(SsgbVS","5pP>L","Z<_)%j>*}","xSP" -"hQJ(nz-","pvEO" -"h""H4i","Vx#.4eFSP","P%b","g29/ohJ""~","1""W>c>7,Ff" -";JP;lF=","","sk","2pgI","","" -"PNJCZk`B$b","o_rES_{G'","","c; 5f8X@]\","5501hCLwp{","k-|~QTz" -"5|-'3H","(MBjQ","^L!]ELN!","FtK#er:,w","n~d6_w10","~Pv" -"{^h|}DK90\","\S0dCXF@","~Q6yqV4","0~^eD","p.(?f","" -"O`{32q/6>","gr@D","","[^: ~0+d","b6E","5n3dZ" -")","_F",";PXBX~JYz","","VSgfwg);F","]5(>W@ON" -"i'","_zl|Ly","c#0um=K","AYJz","BG",")rd(,k)MYv" -"7","-[eJkQ","g5DV2","6Q7>","5MWw9Am.","3" -"y 3@hOj{","O~V-",":","%ll#","wn]I;1","bE'1gAdTqv" -"M","J0zmzLj&","X-}8(d%q","&/kHn)lC!","","(" -" ^4JD`-","egb"",]Y","@u,G`b","1)|T1rnI""","M7w}J`-)","WJM6" -",UNTcL1J","M>","1","PxdP?/Lm","6(I+D)7Jur","s>?Y;s" -"\W1%TE","ky[f5PQ","j","!UpdZ","}H^m","I" -"%""","s_y;Z","""6vk8","=v07[5","3=gkHmvH",";2FXcoB=" -"Rkg`vQ","g9wm@_" -"3\|1Tn\","/0X","\a4+\vz","","y}","" -"\a5G67'""",")yr",".J*:vr1*ME","b","AwS&Jo","if~2d" -"`3O","L*f""`","9&8z","6A\","FFJ$[$!reS","I2aP f" -"x^Z>*[_ .","2vg*ZX,","U5z]f","\,BLCRZP","FZ@""","u?s7w5W@B" -"amMNm9WUb","]GXr","Sp.","7;h_i! 4","QW.","N1" -"","aj,$Um\5","Xma","=j","|KQ?tg%8","]4:Fn:}fL" -"(6(i+-Q/,j","XW>","s0y/","O9W'C1zw","fc`,n","B","jk#jL9","lM%Na<","!","fbhYVV" -"Av","7,]T$","",".8o6g","-\8","|xsPPd%e" -"$#p8>D&c4G","xd)%mt71L","""Q","JGoXI9z","J" -"Q Q","OY/ ","~!;5Dm","r zv","]yxPpvQ=1","BYY=U" -"w~vY","(m|@Q8",";8im94=.","g","7k'","@" -"Q>2=V","#","~g","(g6at" -"",">dTi\_95r","*sIaw|g*^","SK","*","rv" -"8S4-5D).3R","cnm","]","!O","nH=""+_'+","" -" &G","""P~[Z"," !Z","CyW0 dkajc","D2I#HdCMkN","~G;4" -" 8.s~6V(0y(" -"Pb","Nh5fY*vmU","F2'","<1UjC.""Og","","=" -"5u$","_ywJ5B","G.8","gS mW:7J","","`ihU-\2}" -""," D`n","Bm","P#(@2","M6^f{=}@PE","[`j3F " -"z"," ","iBrsxGF`","fT_5''>zNJ","Pgv=?DYXDG","eiU}dhlk" -"","rxD","Dq!?)oZ1rj"," g_7Y2J","","Zvq7Ca+*" -"cHDEOx/Jw","4S9!vN","#i](lCG]","El7O""$","OLeNUQ;'O6","mvbL1" -"bR8Tc",">c:!m@AT","","~6#:G'G","T'","RaV:E{6r","Le?u{tcR","XjuH1@" -"<$L{r!",";","GST~*T'a'","","}3}`FQ","I","\" -"=Mb","^EKB["," a4","K2p)_@","_9^0","y6`nJb%lI-" -"GmKxKI>B","2y >!lm","tFF","","1","d/Kyg" -":Lh=","(/i6nm|","%L5>","XiFs""","iWj_0D]>A}"," " -"-XBS+","$ooq'A","wQ% .h5","]",")>8l#m/","9" -"j3","z","","cZBb9}","@N9","Z`@%!" -"{","o\_*]L=*%v","xkK'","A",")FfU_SJW{v","VgtO" -":Ppg","LDT","S`jrrI*","UsvG-","","N(jPK]" -"=qxY","iY","tD,4u","BLm4","nXky\B=>","Y" -"","S'v!jtVN~a","","]_\!)F","o4we",":Q" -"|z#","m E~R3^","_pQd(_QXxD","&Bg?y\q_n","1z$H","k" -"IVtq2euva","CzAvYu:/~","%H~X%" -"ZE6Gx","Zf:","@e$","p","]WO4J",".7k-U0Qsia" -"h$$4}d9)*I","(LYf7","lrw,o","B}","tg3Y~MR","F" -"\SMp./?","XN","qt","M","hy","OYl" -"x'OB","n>/WnKEjRa^","Ej=$RJy" -"FOqxm 3a;","0RAOKOC:","{","pL8g7*","[{","Xq" -"-hL","DZT","qn0{rQ:Z^","","$T_yha&Q","eTI@sO>&s" -"gku;x5wO>o","t","`","ilv-urL$f","" -"8RbT7x7!U","x4[7@C*s","yw^ yT","&BA 3@O","u_gEPsg{","p" -"~~75UkG!>","gf>H","^,id","nwua[ JEL}","/\3jp+2])" -"(iD?","MYP_@bd6""","%!(I+1H","","\&cI","SnzW" -"C+C,FzV","4K`-&p`;~","pIXwz3@>8$","1.","aX+Y","HbA.OA,[Ec" -"kfp#yjnNs~",";eT|","vsjh3%p~",">RI|'&0J=","u","_" -"1\ImZd@$v.","UB","[)#0euLp`","<+/ep","&","/Y!6" -".=>;D>NT",":I_LgJ0xL","|z\_b/8s","HPe,","UIohz$hnd","* z" -"0Ukdlv/Pw","3{","","","","uXk^WOG" -"rw`Z;Bb`ev","B|c3V","M4M","pAe_C:","U9au#*Bp>","[4Fm_]t," -";","h@ZV?","","#./5""K~]","",";8Jtxz" -"BBC4@jng`","u8lD G>]48","OZf>6oj","" -"-Z(}8{",";_)n9h#/lo","","gIvZg","X_On#p4","^","Fhg",".","aJz7p" -"Ke","M#jJVuB1Fb","eB>S","F1]","[/","=tCCB$k'W:" -",,4_","fC=00a","ReK+mxC<",";6Eh",")B","RL" -"Ql4:EHi+4","wG]b!`$","~.Bq","t0:$f\E1","","d:M""" -"J","","L)X{#2","%","Dr0Q=","Fw0@o%1z}" -"DcEB*4\H","'H","4;tb[a","G","(","""4 c:<5p" -"Jp@~_eb","Deg","uXv","","k","6JL-","DU)N?4IGQ" -"AS`q","V+DgaCsEuc","#mMs","","iJkBL",".Q|t O" -">SE","w","[m?","Lkx+uqS""+Z","jEl","*UXJkG]c" -"Xx3","h!9UUKGH,F","","_Isxv)^DD","Z","" -"&UY","[*S","g:`7Dnuwj@","26BfG8{","o%>2eT%h|?","/ue_a9" -"fg\@g,ey","j]%w","dLZ9IVtKbz","Wa/","&o7?!QK%","uDf" -"^wl0(3"," &f>`lwIL ","0>lt9Hg""q","al","","47!k" -"v0","Ksf(m$","\",")","","QU7mR?" -"$5PbDf,i","","?gQb)","V'4)","%0|)fJ^Yx","@+l?0`B}k" -"L=L7","WR#@,\e_","","b","%_.{_$","d" -"6-?Pl.","^P.om""XB^","CVk","AuOs(U h$","^S]9n.3+","[""+" -"I%D/\S*","x#f1%","Qo","FCQjLg=F9","r_iYR?]]N","" -"23Lw3m","qvOa8Ykh","7d{)","","WaHk$","{8L3r]$hy6" -"0h","$1^*q",",!N","Vo~@qe","$-%V)O%","pKuKG%5z.B","%hK:.|" -"[JFR}[mtR'","5$=>oG<","5#Pnk*","x8F\","X%/P\<7dyK","vT?50" -"ulb[Di#mi","a?rd9 i","","j90QD","0j","7c6EH" -"?WE)t4/","Qp,rvIi","H@ybC@V]#","!=",";Jzy)tX","Va" -"t2vrM]",">7f","[B ","gdhbhk","fs=g>`XJ~B","F!`GS","`x[t.t$XY","W;@R~oA7","7eP","StcV/w2C" -"{ksWf8X","L1?","w|Nq","`cGF3Nb\p9","QJ|","K#c$" -"tk]F","dZv'Te9","dkDelv","H3ewbm=}?","","):)" -"AU'i-^v7Qo","2F]2<0{""fa","dO88E7JC","?ep8h""X,","\P76P*2IZS","6." -"k","EzR;3:YRj","gCg","T1k-Nz1","fA-zc","t" -".WJ1+Ro=De","g8e(+","_eJoCv%0la","))""`<)'m+","n_1(","|" -"pgmjK(&;}4","","8?>Kd","sY","","EX(~VIHb&" -"E/4Y6","#%iu]","q!""P'H","o;9","%Kr>6&asm~","q" -"%@S#B{2E<","","?8$0o,8!s"," %o}j J}K","yE+j-","" -"f%(","<`4","g","}c","u>ik)-Q","\?mu" -"\","H","$","WuZg`:d`*A","Y|Q","Q|(P" -"l","N/","YK[&>^`ZC?","rK%MO""0'M","|ZcN|1WS","#\67" -"ds#1M@|","IJzR6F","-t)a","","Etq^","g!" -"J1wK)9G ","H&,?","&S(dJ'{","}z#DC","*T","" -"z Mx&p.|K|","@)#E}}","aC7T","X <,Pm,","8^uTz|o","R" -"(/dW9","","6","N5D`9y","","" -"","]Lc;$$1X]T","C}?mt![t1","NWp\j.","}m\","j-=IYK9/" -"xc#OKD2O2","lyXWx@x",".T\+"," :","0u","'$4" -"jAlzegNuX","+`lqS$!NOv-","^gi1A3","c^GD^:I%G" -"+f87zuY~","\v""","&","S$","JLN#","oP[NpwiQQ" -",=:lri","q0","&mVB&1","f`^c+","IN*+ ]mgJ","]Q3mBc/Ih" -"lUFwZQFYL","|j=T67","R%/Sy","gM@x","^1b, VTi$","" -"y~,","","yfAn]OTpH","7Yz1c?5CS","","=33te]w" -"M(",")~qN","/y-n2yM","","hIcIY~UX.r","LcX3RQz" -"0","xVJkO/'_(?",".[ZT+tUYx","q|""B\O","'V~9Wx1\",".e&&" -"","g8[q}B","","lKhQ$f!p","t)_sK(W","y" -"i:","aFhK95","n]u043a=H","[R0U/h;","f""","`Sc" -"","HA","P>)R","Gr","s","bU4xh""ee5" -"fn8","&suh","|$#o?s0X)","?","!E[V","Jq7""" -"T","gzvDhD","fcjimbwEc","<%0@S-fm#9","8I*}WSQa$","ExQN:7@" -"J","TYJ","","","-%f6 (z!7S","O{EaN" -"so","tH","QvQ4\%f135" -">So;u+9","=#)>(P","h\{TciLlA",">-","4r`Z`_F","-U@E>W~" -"I|","^*MI","x!""v`","=C|""A","TPBJK","^$p_" -".K9j l","i>","KLOR!{iwU","","p","oJ6M38?Xz<" -"1o","AC~E~){=$6","C+%%%RWE.","L A4EQuA","","fb#""'" -"\Uz!","V!.>(&8u>","(1t(*","Egt#B","pd/","dX" -"D&IP7.i"," jGOZ :my","\","","!G2.8`","tSf@" -".","@WP\" -""," ","^ds","C","NM""&","" -"f\>H*l^","3Z_i","*HZDhE","=O2GCEzPS%","5j#4S" -"}'84w","M","i7;/p","T\xbvh(Zvy","m:}j3m","0t:U" -"aFT6","e@N=>[","e","yw{d%zVO}","e@n","MosC" -"Z3>H{%k]","5$j",",Su#fb6_U","eIZX","KH\Q: G)","q4|1UA","J$/Rt$4Q1r","YR~6t;nh.","#)bCw","","l%Ou_Nay" -"Kd*\y,^EL]","0#m&ulCk##","C)`^\y1"," `n""Zr","","Gv&A:/#b*5" -">#KI","cF(0T9""<","$`E^p6$","<","#?","m&oxl*X" -"Q,","RvXUe_|*oP","[(MpJY>","lAa G,K","V)r+","T" -".mIQF.w4","qkP&tVq","L&","HT1JD, ","L2(Gh","rAM32T[s>x" -">","","vZ/[P\6","""n<_,B","","G" -"=","","vKiynaC}","","'go#eVf.u;","!(5O" -"","","b","H~+7Bt6v","\,BXVP+","iT^wB" -"j","!K79","f4DGj(e(",")2 V4WV","wxU#O,>0CeQ)","|Cr'of*","EYGd","z*UoM" -"]]\","E70<","*Vk-)]rsa^","","y?/qYr0","W_y$V" -"5V~X)","g9","Bo;#)","(#&^=ZA","_+d.=LT&,","/" -".y1Gtj","K5A%","\,V>wQj-_c","{x6d","_o","O&" -"D",",[<5","6?","d","+ocf:ay`","*1!3Jb","-","-AMVOE","aY;","`k'%k3S" -".Eeqz;&1","!","",""";ll*","b=YeL*","&" -"[sd?O","^/SIk+","","1L;'XJIDJ"," &v=:1@-Pr","/e61'+9&w" -"dc<(HA;}3>","{_M" -"^~r[FD+","1K","2OvR","@9","?({)i","2?ApV]:h]#" -"t","n]k+","vQv6","p","7+*P|\'","yzL" -"PqnhWiHv|","Dekm","I=","7",";fDW +","w*'T" -"r.A*Ox?Z","`c","3","5:wi\CCCx","J)","! k|~O\s&" -"R!Fy/%l","L_]g9","+","","","lfc5%6%" -"Lxl,n","^t","BBgoMVi!","\@*omRr_","G?","" -"V`?","7PW","i=",">b+D","LX" -"-ZB","{c}8j","oR-*\ylE+",")~rK4}b=","NAD=fT0","VK]" -"G","<","rHso{>","","SaiZc","@" -".T","9p5hU#YP?","w}}R?'e","#}","1.~Qz:","njV" -"\+@}qd'K","1/VGUy'97c","c5a`mH}n","$V_i&","/Ko","x/S}" -"""hfNX>3","n$","p2q%r","f","","" -"""}`&(","?-P}p:N""76","","BpdrX8","2slLCRs`*","VV4HK>|{B)" -"f7","azN\JZH)","t})1)Q8-aW","Ac&","_k@/4[W]q","s:4vUc" -"4_T","^Y0R%","GWETK",")3","*,9PHsEw","3vcB" -"9","MPFmx5A","-$@""`&bY +","?""","L%","((HX" -"9Moc",".dJ","W","yi~k",",9","{nQ" -"jQz:1<@mF)","4E\0i/v","","Kb<","W~ XK/","*\0" -">'XO+#h","-~","qPBJJCN",":W ","z)J","\032y`Bt" -"OB(8MquC","6W{@~^U","<;=Z3-.j","6","reV","" -"53i#0|&[","=ke6u","@i&xsw>","eF;d","eV8h2+|*","FSXt" -"1534*}","7OwHQ0" -"Z}J$y54\*","","7hg(gHYI","{","?D-)",":1n]E" -"c","pnP","'*nz","Xa}D)q9L","7DL","i" -"2$#p`5H!dx","","9F2|","3u","Ccik<\'","Zn&)|l1y)" -"B.~1q} H","MaT8qa'+","N%N)","$cW3""","*Jdd","1-@!nX<-" -"2DPCA':.Ih",")","'U*N>\g(","u_d6^eT","V","" -"xm12.kj","""H'lk","c/L",")|@0","LP.8P#|>","=R4P|" -"","+s","e,T7|HX|tZ",".I"")(:e","",")u-","p+kd:(ES" -"iPx","W",",","059F=a;","Z,h\c)xCS1","" -">TZd","i$b",")_Xm[","e1+'aE:3","d1m%}","?-swZR" -"A>b,RB\C" -"na","","","3k} &-","g{?Otjt","C]'" -"""er!ts","xx","^p","+*c-?H" -"HS]9'"," 8ayV}6""","@'^w#!/","A",">I"" ","%" -"`Rz(n ","./v|","YZP'R]'","hId^$i","Ir","qQ)(" -"']k+}(2VT","a-","f&)e.,","ukO'",",r`ilIjhv7","LR" -")}","d1ts%)ya_","H","Kc","#9Kn#+","h[*=""g;z~y" -"j","r","Ifa]i}Ih","e","","x][0-H]" -"@~H))=r","x]4&1pgD","}.","%{a89N{","~{#+<``","wq" -"1J","M_1}&w>s","{^e,usXNy@","N.ItrH","Sk2%r","_m22me27" -"G%xDjw4{","","%3jx","4~S|","b[gX=XRHOj","(m" -"sB","/E~Pl&A","!","K",";h!aqS+?[F","" -"d_W[=\T","Zo","H`S<","E",".6DT45n0?*","{:f \M" -"!e&`)n\?",":","ZGGCo?&|A","ubd:","","^te}P`1)fu" -"#1m","Y?*%.s$)!i","G","O""Y'b]p" -"aTe~o","f;a@_Ku4","Y[L#A","i6|e","lXM","W$J\:z6" -"36KG#","rX7\","","zM7","",")|]*nHuHDn" -"b)]","@<","I(tNADDwi`","2@sN","&4","Po0" -"nogD'S","","( r!j","33u*(<","?% XWDC[","u""X2h" -"""9AF","kk9m","","","Hosf9>RQ","" -"fo9s","","Ehm#m","A0,{w~n","=|","W[#Xv;^" -"DhDAd0W","","YU+A|/E","zfYD63","AVw(A}S;-U","`(8=" -"","b$r%>","`uG rv","j0.UQM+t","R5c:D.9>>!","#i!7~" -"JJ","X\9^%9J'","~NjY?p+B:9","j","b","-" -"~>K c""O%<","pUW","p/k~}I@Wq","8ztcxiO","s6&;^","#shZ" -"DB6","m","","p_`lCn{","wUNd%3.A","*.NtX-%" -"","s","?CE.","LS+","r'SQ\)","D\kG%P{wr" -"K5&v~""`pS>","8](}+b","""_","",";G","Kbxn.$d"," @0","ehml<","oT7" -"_","5gbAv')$-(","U:21uG-","YC^c","","b!O,^" -"ioUP-;gl","h2`*","nXZ","t|fWgds",".B*","vj." -"]abR:T",";[ae^B'UH","X|""b0q","&t","*","AuF0" -"Rjau8ul&","f","P8","-IU^Y)ZQ]D","5V$_UF^vo0","up" -"","?D[kd%C\K","f","xR","=","o@@6" -"c:Q-6R","m.K8w I","OgX","_Jf~-","F8KGHN","t)L" -"}C??2","r965oHDNz1"," $~","t","qhdH<<","z)gC\" -"FM&]""25","<9(N[#E<}","","'}w.!_U&","[H2Vl$gI""w","F" -"`J+^l\gy","?QiPoL6E","$jj.PL","'a(N}M4","`Q%O8","$UC@Qz" -"ww0]]","MuAX+N>",".i8m-","&=%^z/","6C/kTP.Y","" -"24""","","c%+Rs93Fu8","J:iNcX","BQ*RN" -">8|Jt$","L}","WF/xC/kLH-","55;Vr","yE=0'" -"","","MQs?h","","wO(D,;;","kCs" -"MH","d G%","U","Qg)YB+>>k8","F|2sxVmI9x","I83" -"qwG-ZboHlY","LWjmr+""","(N*","7k6 91","wNP}o@a","eX2f" -"XU[+JRFPX","","O","","8Bg","MUF\'L('" -"J*6L38s<[8","JfI`Mf~T","zH","+","~RGoZ","z_Q" -"~5 e","Uo","V5fAF0-","","@a/q","2" -"x,M*>CiM*+","J~r7*D g","W.Ux","","","_UFyZ"",[:" -"^W8F]#:c",">6","gZHx,+_a/]","Gv","sr","}" -"9'B*:Y","DWDfkr*v1y","E@","P@","","w5*$mR@" -"{""-\([G},","F;gM=0:","[)","+9t","Bs""8(2~","uQgt" -"d#","YZe!vB;?""=","q","L9h\bgDE#r","/J","fFc:AYA3_" -";U,:z)@","","l>^R_S0]",". Rp=","[.R","|[r+Uq{OC" -"N54x","w:7NbeRkO","M?}Ry591","#|@z","'IyWG","" -"h=02i<","7W#es~","","Uh","K\e",":D1" -"7dRh$","","m","VB.$e.D""""","v_",":;T9`7" -"]y){""[t","}.%)/m}","|TD=pZLz","H'|9l","[v_Ew$=","i(HlZOM" -"","=J","@am",".ZIZoUYI7","W@>6OL","~76uy6" -"","`0&3$n*T",".}","oXB*b9FH","R^6"," E[R's" -";^kkE5aup""","jLIj8","N`53;3","_d(/?;/n","h&y<","t" -"kNk;Ke5","tw\&zmY>,","{x)+K#x{jI","","3$b,d#^)","" -"Gp=1(TJ","f","(0u|#u7t't","","*:{<","" -"<","|^R|LYA","*pb" -"#Y","NlW1`","ezT^Jnm","oX@m!:","6","_? =lT" -"",">Rf*u?[","`kzNE.",",KD&,{[jH","V<~.","id84AE " -"lbn","[~11ZBK","8[:yUd","9cSn","","" -"^qgqcc","%:OK$c""","Zm6]SE uuR","","<,P_K?gb,r","AY(lB6" -"RSpH","\|U!:r","{lv)","qxVzjwG(r","X9Kr%g'","" -"d<:","fE4",".Tsv/J>lvN","","#2l~_S.","J9oH" -"OiI0$Ps4",",","Z^7oe","X}B{B~","<8ho.uw#","l !~" -"1V'","","9DDU","GgnZ]T","zECUL[Q","hobJh!h\r7" -"P)L","Y_@eaV|","P:y`#","z","#',vQj!s","kZ@""*eig" -"%%{^~RaO","","R&??QE","=","dT 19e","" -"(j","s6vZr","G","j?CuNK>L<","rHaC2&\#","pZPttv[" -"<%Pplf","s~KZm","-/C""","Fz1k1Y60","K0$","QmEkIPG" -"","IW?",";Ub1ZVWo0","F/1NP#fFC","9`zW>Lc.w","n8""Qq" -"4:?","","j","D+","kyPd#oHVdt","" -"","#","S}=PCb","","oj{C;d1h:","?QV}Fyu%9\" -"wD","",")tjb/+I+","^""O","","XFHQ" -"vK","\","G/",";`/.NU","{z@e""4A","VtA4spU" -"c7,DK05",""," :fpef","LD#/F#R(_D","aVn?_k ","t +eq4Tg8" -"v|8x","s0G|G","8C","<\Q","R0t","%R""C[" -"T_1<","""$E18g3","E?<","6e_x,","|e{9!fL%""","z" -"B","mM\",";oz-\E6d",")","w:8?]N","TNoQ?HOx`" -"prWQ2edO(","@P_=\","",".K@`!hgm$O","Pwn","#]#6" -"","a,@?#=s5\","5eO",")z","'B[9","}'N","ZipE" -"!'.f",">f>\_'Tg","W?EW","h","(p_u:}t","6vYcvmT4P@" -"0","","X","pkZh ","fuem|2w&","|LXXsM}-iG" -"","OR","N0,%vYTx2","7lr9E=c#^}","?i8Et#^<","h2" -"99","qo'*YR","{U3:f","F","x]EJX4","R""Yjuk8U" -",`*[o]UA","G5,tTLZM","","(tQ3>hRT:","d}0M","2WhW[\L" -"5","v4ew","%u{3","fF#ay","u$[","$Nv4S(Ip16" -"oR6WW'y?_","+czdXh)`Fn","CbWLwO","k;.zHc2U#","1)MP3P:yN5","t/+)" -"V53~m""Jc","h3Ld[k","o3OUr0u a1","Z/kzq","B""","&$Q{ .#6FD" -"O",".0y?YQv^n","Of>\Y7~B",")*h72"," ","E3" -"a,V}""x","h(XYXKwEP ","P","I/yDSK61C","Wf,Q84n","" -"ICCY,l4#]","1=^bd*","t44[pF","O0:\n","","7e@1<;^_d,:d'o","+o?""uUt:23" -"x3X<6","shK:t","H&9K","Inud[","9B","t" -"z5","qb%fZ6z[W,","|s","","X","RZH\$P3/G" -"%@0GqM","|?}:mh","MlVt","Zq%U","+i'6","+1mM?J" -"A$&",";B:h%","S6","7J$wz","Z","U}" -"k","M{%U-,,","Fh7_K8Wpk",")hQ9u23t","qX3#0eoq~","h?_EptSeiv" -";Kz3" -"h~xRIUu>Hl","g,","x7I,(-""rs","&.","C3|K}2t6","w[@sG$I)%n" -"",""";[N66I","tJ]T9","RbaC{","_55_z@sA#M","1-UnR3c" -":re""28A_-}","A","~-)S","0","JZQg$\E.","$SWi" -"=3NxWzs","{qJ0[,C" -"7Xm|#","","Md#%,EXZJ","&Y!(AH(","{Etq(|","o_" -")%/yN","K8","|ph9V","J4*","","" -"0!","n","Fh","<-4d}Ao8","t[dx,","_y" -"%d_S!WX-z","""I9&}0;B","-7bHs3i6QG","q/K}[iI4U","1Pqpu",">A;8UR" -"8","m.4J","","!","??FqZ.2H_" -"N]PSgH","|Qf \#M4#","m,c","',v`AY","c","'kqc14EUH|" -"|'","GJ5/)a!N","H","XDjj(x","/C[\jl]^","JKgp,UPF" -"kxxi_A",":f","|hb{u","","-rh3~w)@G","=G" -"a[8","T1","1bqV41","[>=bA","02(" -"qQ`?",":T4M'*","l[j[w]#Df","","kw~E?e]Af)","ew~o x" -"6Abb","Kt<^N`,"," P~","ZrQW*","aoX~NI""","}" -"*tws","~SUh.","OKpR&T<:","M+wo}|1/","","(VN}j" -"`_.Z","5}KX;""ch""","`mKKtX#;","K}K|xiHk?","trVy^tAM^W","","Gc-(" -"[b9^97%-|","lb9<:J*|","J","e","","\yxp3dTh~" -"gel4=","y$","+gqzP, i" -"~TrZ MZ[w","{bQaO@l$}","c ","e",";'IB08L<0","dm83LY3" -"tXv","8t90ODWV","(@4?U+}","h","rE","i>c CwpPM" -"BCe]Kplg","Ge~`3;","S0&kZkPw4U","Id@6lyw ND","`8,Q(9vEb","Z\}_3[|-_{" -"OPC-ov","R","2.c%1f9","%}c}srAJc3","(u""2gWUIc-","0MU/v" -"g,y""mG I","8*3Y*/_","{6F~","@","9R7P%","NUOc" -"U""l<+O%*","tX6(^9HC2","&Kb[","](41~9]cW","v'.sX8O","" -"kl","mn","N9","%*?H@0w","p-C+ci:h:V","" -"WUb","","K{8Xs,","}/7C=","ybRC`","+<#~>$52l","U!2","" -"=tRxwd","","H%*tXfM+[","w\=^7",":w","Min[*8" -"RwFFL0@","NGzjX","6=ml'#O.fU","*] A0hm","1(","*eQXu" -"zl5^$,","__(faA`tT+","`e<\u7","Odd*TmpU8","M","_*""[5aZ6E" -"y+FQiy""","|C/w~d+","","U","(Kb\u","db,@" -"s","9","","R'ksKw8V","wJ:2B7","X@Kzh{" -"2","t[0Jg+io","rlMzm","pslFfg0","9P\J'","~d^""2" -"#0","`","p_MvZzd-","Si","1UhGDN64@F","T%nizDfy" -"xg","PK3:N","rla2(r","Sp ","BY""","FY!~/" -":Hu","P","gYmIGX%","+ehAD","","MpW?q_j" -"&O/!,e","c[tn","aY/5&5Is{","H>h","yrI`#","H6K7" -"3K$W\:%!","&n]|:0^/","aj.","C?5!2(","QO","o6+\G" -";Moc",">=-iF=K","`eA[U[9L","","5S:9:r""","fT}J*06""h}" -"j","*","WP6y^/Jn>*","OS[l8","]<","{Z\Y8d" -"QS","n0tVf","HVa~e","Pun=8#j2<","E-T^=p7","" -"w","^","Z","u.","4J;4+vW!S","9" -"aG","s>sT!SU`","s7i43","@8QR50^CYTX@u","rg" -"$bGY[)5OQ","kHG\B*i",",U&f[GU\Eu","rn3j+8aCR","w\=t#4L;","" -"7^So","`KBSSu8","A krj:k0FB","{","B{g~4&Q","~8Wo4","}I","}","@SgM" -"!5 P@ p^-b","#tKl9","4&","KYYE","\K)PsgY!","q},pFW$E" -"@AB#Y","/GFo","O7t9j^&XDE","mP;C.=W","-p96WVdwRp","oIo'A~#" -"ZGJs","$H?p.R","Fl=*XWGpIP","g|+Q4%x","PSaj","QeMky:3R+" -"e|Qv9rs?D,","|+c","mL*5ZgW&P","K2Q?I^G}","!<","b" -"#;1@G:rM2","","|h+uwN","})bLT","P 8yqc@9","''" -"^A%KXE~nz}","MSop7F8","LKNkYF","]SpTHYF~I","","q/e7;Yw{O" -"&C>""",":!",",e","C;*g~","a","7dJKU)dE&" -"AJ^MCPZ#A","^z","'D","yeEc%yq[g",";1i","y=O-|0Wu" -">V4W","","o%48wC","0_, +~s","",";^GI&","\E~+;|<","??V4%gasz","9z]","u] g3O" -"bcG","JrQzi)}x1","AFf2h","RUxC2","pRa/i1w6]H","" -"$}%?y%",")HpC","N$BR)","h~2NG)8a","+s?-;","f`" -"","4@T=~cC","","fI","c","+]T)vbg" -"B13F@","9C","+52","V:AN cgc~","5'pt]9cXs?","M:R!4V'VT\" -"G..XUS","LWdJS,7,$O","-: #F3o*","tS{i","gqE@o`",":;&,|L! " -"}O aB""","pS`'","0oR5","+-5Mi\8|<""","oA_^y^I[","u|z""" -"gWv","x0>U","","+","x","7}mBmY5Cji" -"Y%*9 YsrJ","kk/$]M2k+n",".LA","0Bi03U""{","aI`6tc0z,=","w&","RPC}:2DM","VZM:V;2aWG" -"~|(Q.5","prY","=>S`]3:oC","Ea1","T@lp2R""Z","C]Se@" -"","j?0 .","qXk?Q^>*"," XNP8","","B!~$jG9Y*t" -"gnO+y3[","QwWawS2H83","7UZ*B;g(2","S_oylo","gjF","le",",5CG9/Pv","M;Clqe" -":#\","bX~q;'l/w","IA)t|"," Jk|ZR%64I","},REL7","a!`naR5N'","cuP`>'""","9mi_=!Zx&","v8BzApTzu""","ZcKt","2{'I""(" -"pE\$l",">{m","V3*","","8R3X.!-gK~","p?ojc" -"5,dlP$","ov","Ta","I-Pz","^Fn""dZ|Zr4","F4SkH87" -"[DF9T{","0w\i/u","Spi8z\B-U+","59{a",")*x_TByJ@","stHZ" -"","45##TM8qs_","J8T:86eR",">hO","'s|",":TJr","rE#x," -"9t*KB","$","","F]~~_","sK=Bjn>" -"|","}","+{~~4GOnrD","Fv4""}",";u?nOmB\6&","" -"","x","Irr_.Cl!","d3 5N^","Z(=","I" -"s3",",>","GTxocT'","","&&a8NV","3#9_{" -"oK","AatU","#",",#24","8","l-+k`M!" -"o,^k;PdU\","C%)","IE]}jm","\&fho""M:m","ackx","K.b'@" -"",".\y5)^E","hoXh","&hR)lR?d#i","{0bG=:OO/","Et" -"o*EP=""y:-","(/5VbK","P?4%+v","p#s","%z[- xOxLs",".;""XyQBC0" -"Vw>C9x>>","Ga","Eb","DhO]","apAP","/uWkaFu" -"D3a1U6oE>","!\sKdBoA2","VR=","h]b:O6","$","S]&K" -"oW""K5?C9","","_","i3oor8-","Xe5acgv","kB6AP4Sv" -"u/D66*Q","s",">x6","L6qtiZ|77","q","u>^y,|_Cs:" -"C3Y","Ff/wII<){","\!A$5~yp","j^d","/>&:dIuNlM",";]8^nMN" -"","P","U~i2\E*w(","hJb 9i","Ds""D","" -"j#{!wo#",".jQ+","yU_ny\3L'T","jBk\","<","BY}:@4" -"","Macm;=hVl<","13rB%Y1","$`","","WKHRPT;fA","EGE_#A","""" -"S2","dMuJ","t t*r[K9","rSDc?&K=u9","DP*","" -"81","","","V_","""p>>6ZXG","A,`^E]K-""&" -"M04JE@o","CTET","zCk)_q","FMpG/\X^","J.YO","N|`" -"XTSkM","HHDT`;","","%1{k]}?","""b=","/ID.v" -"R","","Vm","","qbKULxV" -";qNbq|eqw3","0\o,""eVe[g","vKn\VPp:","=)j|D!z* ","9DmUUl)zf","$^!?" -"<[/" -"|Jb u","!","d^w.","A;g","!R)Xk;","lcF@OX%" -"hB!7","\f0}","tQ;O2a=""","!:}'@o*P?-","Lxq2i","!{","6" -"","3b=3;9c","KYSFO!@>B]",";2V$Zwye","^L>Qn7D","'FE" -"ui""|\","2w","m-5E;K`sdT","I,.s/I","|""nvp","(Y68%" -"8*1ufFM","9{AN ","1LML","","/e|Kj~K2 ","f]hU^" -"X?'|h?D}R{","1;^kx?","","-\","d\wh","`A" -"p""0FPO","6","Y#04%P=4","QZ!r","xZ~E2","b@" -"",":(A5]r~.","1","A>{","tku%%6l","hVOh?4z`r" -"529E=~","P","w}EEc","q7!aVh|2&","","","d.L%" -"y-I'","}DM","x",")d25=Ct=a","(}r(aDiOhg","Lph9K`~0" -".rJD%}","n%)-;76Y","at","u@H'WcG55[","T=DJj4LF9","-J(,U-xV" -"IGn.","H7Z_","4q[&{P""UKm","5=eKS",".{]","1P\Y" -"`E9D","",":","aaP%p@","63'Cc,","8PS" -"]*0VA-SBRD","""a-~i.{FwW","}T(7hu]Z","K_J)qL'","Ca*o""mQM","kPd" -"EZD","Y~",",\BJ .J4","1,q)c","","iY""y6w*#" -"XRj)}YN%","Rq9/","L7SG;6S+T","1)5'{}FPC",";&>?X"""," \" -"5S{*1bF","Of@x=mv}","N|l,9@k-qV","p&OTv=xi\","J*","dhEgs" -"7uLg>s|","5Q4x","r","%z{O","","IG%" -"^","tsI""R}6_J","&-49G[A","_k$(u@\","hzsDI","+yV6P" -"]Y","\iwci:8","H1eJ'YdP0","(uy!*WP@","\'e","""axFFr)7.&" -"0N#02UAe~f","6","d","~ zPu%$L","Rw!nc@~c"," 8pHCf" -"ej-6","N7","2s3tE,","76`ir_8nUu","{pO/rH","" -"|4\@/@n_q","cov","09.*y","o{KG,f;XfG","C{L+Gj*h}","Pw>" -"aZ>gxL;A","PMP94xo","ho7%&#XGW?","I2l","_~@" -"[#THPA@8","j[""","#+}?27v","4`M}1|G",",","?KX" -"8)v\!B~""","4@L",""":gWn"",S","!zzW5q>h$[","","ifUqg" -"!ea","d^'t8=$YLB","$~s","","a$d*1"," 3fM2pq2F\" -"|","awbAJp6%/m","XF","y","*1AXL%VaO","*]@8=R2" -"f'/x*Tz:","BGF8A","`","Ebqk","A;p","o2c)" -"m2y^=:l5","P","]vW","p!","","=?:R~\" -"#;JE4\jOfy","d:E~6<-T^=M40(","`j\","6/L,)}C!","=+r","T;&","!+Q%<" -"Z","!5","fhX6v}3QN",":jm$ff}Ja","'","9[Zf" -"J?e}-","7J1","","\^a",";nIn","" -"4_","FI|im()}","Qn","l))s]<_","&#","Ne$" -"9X./Tl+.*{","&","ori","m)","f:JZ","G6Y&" -"`x)","r=p;""oUw*J","`g","t?|RH","Ef~]HZ:","B=?J7-07" -"/D5}inwij","PGJo mM@t","AU""b6E>-rN","VBGY ivC","BG,r?uVT","p!jn","5" -"X@%qxj4","!?S^bftN",";o7M. t","%1",">Gyd","o" -"V#CL5H","","S7]XF:" -"q}Qx3|ei","+8s)a1\","Z","M(#>nrx>Kl","62IX>$","|Uw+","%enx3fhbT","39zf:" -"0pieC","1C","#e","sFmUE","9","0S|ilar" -"`9K6","OBG\Ir&Fx","#","T,","L~n`T^_","<{k,qdw" -"veC-TrqVK1","C_bZj{BYMe","Q$zE","",". ","" -"Qp;R%","}.B>%#","2Z(,8B0Xj[","\v)","_","k?Ntippq?" -"!","6n""-~>","t66>k",")tv9){i(i9"," f","\AH6oG{" -"5|8","`",":&f9","GeM{R92""e",";wU-","!}" -"=HP7BHD","Zp","oM3K{B&4tz","FhX4.kPgYz","D==bFB+%G%","|W&>Iqsv/" -"_R>R2c=~S","7G/#","NF'q%te","hJL$NG","TV","vi;" -"n","]:(UzIms","3H}tx","82","H","9O`*E" -"He~%vP""","a#","vFFg((a>p","L_b7_7x7M`","`^ #i_[Y","*ts{T%mu" -"K^J1l","4",":P>rI","}6~_AhaBc","f","Atu" -"z+",".2XW^","","]H%x","|Ue","vqw=l" -"vrc^u6T","/","","{""]K=+%w","q$tn*q'","F[#;ex\" -"OY#\h","","$","jkVo","1(","trR;O$<" -"-""C)p!wW+","NRh.HtfUnh","(ni3@","pA5un|[l?9","z]6{Yn","XSAcP UG" -"TU=5@","?AKY-*T","Xq","-","tLAqYc^uwt","""g'z" -"/`'A{","Z(Q","-vxMG)>h}","]K]*L",";/zZV^ZL9k","""0" -"h","","y^mI=R~(0t","9)m","*0zUM","h""" -"","CDyvOC&@Zw","qZHzId(M","$[Vl 6","'h\}Fx5y","B" -"X9AI~>Y","","!:","8aFO*923","m,p","g[" -"}q","","bW0A}a","","(t1q?~+"," h^j!X}VW" -"8!Q1.~ s","Py","c&Lv&\e*","ms_d2-bi3A",")#HGbA-?","" -"9C+gr#uS ","g","G![<","NMCy","D","'9PHH?" -"#.VPb","7=2B37e?4>","nPT7`","llpW","9Aj9Ooe","sJ-nNB" -"b2=S@IxIkm","+BA%Zs","W4%I55M{8'","\'D4,o>F ","=midD","9(J<" -"#bHT49QG?","","ds","BFLy\W+/x#","n","M" -"w_&Vo,","dg3'jV","4I}Y1:k;43","","Y,9FX/8j",">8+t#" -"BXQ6-pUq/?","VY","3MoQp5?","D4\'","|tX2S{\wg%",":A5kWJ8z" -"l","=Wj)t?(H","jw&L6?","(","fUlr6""9]","y'Z2,We-u-" -"U_bx*y7p,",":$]Qs9Q}fH","j|39y{>M.n","AMN6]","^","" -"){uDx:v","T","]","uxCJ","][m4ys*1;","UQ[@" -"^Vp}u}","v>ewvCQ","^mv`P@O","_Q)(I]","]w'e`","?YnBb?a" -"","Hr""x`99Q3c","9'{""WkY\EA","nSh/aN","J1z1,]","4@u" -"_","&","r&","D/rAv(","hM","u0" -"(ra?E","H4bU$8O","9q.w0[T;r{","\R","","XW!8o*k" -"]","}jHz","M-8.SSwe","i_T8m{^","oc&+^zX-@","`4"">U|I%Id" -"D3oIaNg","OV|WXNOJi","VXM1guw","''%}8oT","Q;K@c}HA","E|B1" -"%F[?","","","fOz2","`WQz+x","" -"bYi;G""';i","N=1MX}C>","qxIF{T","+!XJpV'UI","+","e|dR(*E" -"M","""zkip8g","","(OwBj?z#","^Mtv","X" -"x","XFs/K{?6","4Dw&rj|,k9","","G","yZL7" -"","","4nHhv","}++","","OW-mE2" -"q[L^hs'uT=","SR:tN'","Efc\cz","","","" -"yk8ona`{","bUiD1`n2F","B,W[GK6I","x""t9lqZn:F","Sgr~>S-p""","G~@L;" -"ap/""*WB?A]","Y","V(+C1y)","yZS}wh","nRc)","=B?*" -"0w)u2h5","g","9","","@q]uL'","Ifkh" -"ny&#vmzAXQ","0-]2i","oM","oG3I0","n}y","V[Uk" -"B","1(^AJ","d_'","_","s","" -"","6","MZ^b opH[","K}=q","?jzkO=%","@r1_kqb&" -"b7EZF","6SA","qP6]J@(J","P,ti","(Ur"," N_Bv\" -"EZ","g17W","N","#>x'","""\Mld1","FR[zD?W" -">2g.","u","Jo,|#","nd9?u","","" -",9",">Yk/*8CZ","2~T[","NZUF","M$$VVMt","*:U+C" -"XJ","T","Wjk'nBQI26","p","G.""L3>sZ","." -"]3TOkQ","4Sa%..3:[",".fW27ORchA","",";:W~`","1U9W#]W" -"uL&P","Q[7AJg[,%","^/Nd%$u","3vReU;YD","KQK6{-l2^","#i)-H!/n7" -"qrCR/-85?G","g#t="")r2)","","6a1","D$RB%R5\C","?ldH7" -"TE_jpj","sQ\\9:","""I3","\-0Y+","Q\p^","'Rd9wwIk" -"=H~=fV> *G","Et","gf!?`?-=y","tH.m<9^PD4","oB","8>h{1" -"KQ6","","_V >3J6xnk","!^@<9","J{j^","S.S9wm%?#" -"=OD:u","""B^th^`Y&","","^pC_7zY","3zI","^{1j=hG)\C" -"PyD*cT|aeq","0""Ql~","Nt-td@e","n`VXB","s)N[P[dGz","$fvP{" -"~YfBP+D(A","","#]})*A_","kZ""'",",NbG6z0N6","T%+P" -"BbC,h1","&9V","1)","X&tCA","UOL]k""","/0mU" -"U7%=[,M{tf","9OWO*d v","s_?=Q2","","N9fBp","g!N8!Rsoj%" -"+j$","`cm9N","\5.Dl","XFLRQfT.xU","x>","{*)" -"I{N!9","","Ad",".S9Ve!s*e","?pceUt/","tm\6AJ|~" -"R;A:[&>H+,","yzpYi","BZ0U}n!","v-EH","9""e","`\Rt" -"*1V9w",".""C","lm)%C&/|L","yo#tqP+8","LM","" -"","&*Y`","}v&","GeGiWR""z;A","f.","Vy,`u" -"@`fx","]\AP\zF=<(",":Oo%c;gvx","&.","FGkz"".",",f&MXV" -"$5","~5_`2","QKN|%fa7,","","wH","" -"","","+~Q""5t)S.",",g-D ","{",",as+};" -"Es.rh~;.^f","IW]!\Y$~b[","6=x","L1H)]O","aiF#}b","S`_DWAb:U" -"P `D.}l","0","J ","_UR ,","|E-Q3;:s","M\{g|dMjxy" -"","Z|7"," -GH,;0;A","~5'","*KHU{CE5~","AJ.[&1" -"ij*>L#qvt","Ma+5;Lh","","WV$43fpF}t","Ur4H=H","&t" -"3","+""}29D3-","T?XG","r_r*f\:","qOJa{","gm" -"_0M+xc","cp}[","1" -"i","eqCYG","","?","+18E|8W",")W2+R%" -"","6'WT=O[^;/","eF","G(v*xst","pPxN","KCVs3p&saz" -"","MEon","TUgSEKQl","],+-U","6qa^lp!q4x","H;#0{#ujV`" -"lW-","2rhQT","A","qY'8","^","#=}G\6rqI5" -"\sWh","dS","38 H#W","J|UT~" -"O89kTE-","&""E%){","/","KlEuc","eh S_V{jjB","" -"{W.\gK0a","V+u^2$_","@a?","|}","v@7",")~Do,`pi" -"7j'","q15qc1v","\uu","Xc,[#y2C","}FS/|x$","Q+1m.o>7" -"","RCZv]Pr#8","e/nf2"".wy","REeXl%6]","\e<1``YL+","%,/o" -"|UomE~i.","^a","H","GX:t!@WE","QhM>",";" -"=2","9KGu2v},Y","w","$E?","","" -"J/{=tmV%C","#,1-g","}Mx!>b(","JA33G","x\/h!""V","O!6Nzg" -"",">QH0d(a@AK","|=ZIX,*cd","K;[{f.fBBK","^;&p","q5;fYR" -"1k=k","'n'Q50","","","E[AZ:1m?z","|e" -"'L-NSp","~T\@!ELw#)","ad!","Y?$Y","{tcP80","" -"J&F)'""=>*","*~F]YW","q:?G.","","}RX8B""","","DuLj x'~N","","","","E[&66'|" -"2mU*]","9}E,R|P","","pT}TL?ISEt","Ak","? t" -"djk","","IC<<% ","$w!v,SO$","UY9fL|p","F>rAp;v" -"[ >","wl'd{hw",",(]","rmX","j5nl~[t=Np","6q z4:/Q;C" -"6",">XIn-fFF^K","]F{``P","",".,","\e@" -"9n!X","I}","kg|Gj9td","@@(5","~Jf","F^(>;" -"aHR7","Ix7*BleyZ:","M$@]""G'","y1n>7","","\.YP{>" -"ps5Aog_","}","J0$C.M+9yF","z.I96","y*UB","" -"jERgb","","","7b?","","`*v|V-r" -"8t]","4YOUNjYp",":J+/@rP&","","n:xFp","EOjCoPLtS" -"$( ","d9t>{#mFp*","k","{ILy]","[O@=","8Rjf`" -"F","NDNR",".","`CC","","eD" -"(~z}ka&C%","E","wIy","+eT|)0","LtY)W",",sz""w2L" -"r","j AD","WDs""*U~<","h","..s>X]k U","0" -"gO","Hxq""ny","","(%$","c-PS","f7.|XZ:" -"~pYj&)","(bmAm","","Q}BN*)",">E>N@v","9*" -"Ecbpng/","/V:","E/","HLD!''$6="," T/AMh>7","G/=>m86N%" -")gU3*!T<=f","Ug'MUU4","X;""C","lK@","x^~m","IAY" -"G3tzi;?-","-l","-tw","6CAIQKv " -"YjDe","EP*","Rg@VYU|","&","e1=`iJux0*","h" -"W","nQG3wv","","W=","iTh^9 ""#","PJ!\1-/fhl" -"_dimgIOPz","","ruAH^u","5|<>Rt{L","kk","" -"6d"," Xd$r\","_vc;"," A[","","F23Q^Gb`ea" -"","W","_,~G=|7SGB","aUHw","$#,|F","MR[5\4" -"MAnL`","esO8B/r}U","zki+%('aT","K2=","9(E&eG)YV","L9-$#Vslx" -"D","$(","NniaK""E3/l_T","(","EyM;p%T=c","xI" -"*V1`@2oxN","h;X{","YeyVi,",",","T","vG" -"m=`Pl","uj""L`MbeE","KI","netv!9ZB8%","X5+","6\O%9l3250" -"sZMn[@urg?",":TND'","5IC","LYrYg","dn","H""Rod","""a#|","" -"$p,M9((3<","H?16",",F","Uou\`i{/'","7SBk+@","v YR" -"sKEW]|z,","`4B","m>","hO^le-Sp|","!Rxc>f~`","V" -"MRy^niI#^W","","","vA=]Knpq5","7Ax[}",",R" -">^k!YZ+z.","N+-eI","WE~""|-1zjy","/`K/17","{:=""l","" -"ZaF?4uSE\4",":M","rHx&8","&MzX`N"," >aG^L\@t*","|'r@,0" -"#m","""X?B_","X|,%%?","@`z","Qa%PUf","27A" -"0sNK_W$","[{WW#i","","bU"," e0fa\==p","C0$A`@NR5" -"OQGkN[","[gV;73[","","&0.c"," 41z","%)I7m""(kOP" -"sW?0VX*tl","$@t\zH",", 6AxCK-$","]9h_b >","-?ie"," A$sk}c" -"0(iHD","zmv%","-&","oZ][s9","cu3!`P4x4","n, " -"#0EP.mtx","S@]s;","6Nj","b","Q8H|;","" -"-/;\*#","}>","XwPhAK1[6a","0Yn","23","Qx!|lS6" -"2O&T3DWG","#PKZrJG","D W","aQHEw/4/"," b","klBBNEA","8k6",")L<^V","!b;|akP" -"ZPF9Vy-:","SQ%)4Mp","Srdq2/n._" -"<,=((V!","te","V|P$[$/","JgIDE","iXu>=-","1CVf1mrA" -"Dj98#RG _[","n@?HK6dz""@","rHLT","Lk","{[=xY","","D(C,uB","` S{[ti4" -"]0dR&wYZ","utp","y6","","+,p;TJP+1","[!{" -"`}E}!}Q","ej9(5OQe","=T'j:( $v",")sH","9ueJeL","" -"8n8eTE3","#4","\rlM~\","?f=>7g","","\" -"mD`$&]N|f#","k","_xpJ|",">w'u2","rodIU?h","Vp E#M" -"EwK","T","L;v","dUgvgm7","pPYA7krakU",".>}D" -",Q","D5u7~$z,","x+@K;","Sj-!8""U","","'!0*h5QX1." -"}k",".P","td?@Q._","VM""",""""," fpv-" -"Wwh3","m-AS@`,","TxS",")l1","'SSW0cdB@ZZ9","b{UsQjg2$","5\E-=Zf","","FNj8X","#d" -"l-",".'bEHrm","a0td","4=b\O_","~ArL[","V^""Eh#h" -"vn~","'""(PH","","[NuNt3y+","18uv:D","w\=pX""" -"$~,^vwlN&E","t","","TY8%j+L32","lt)NY} ","A/xc'vYrz" -"Y","qRdO","=-IsfX;I","E5+n","b/e-\.DtsC7" -" wj","","scL$","&7+)CQ:","","qNn" -"6ki?`(K:T","S';Ni","P4","B%j,.kDiI","l","fl4&v^" -"dMdiZ4=\","K1'ZZnY`","WsH0","","{#S\`Jb>","$" -"kV((5#","xV*Dc>","TeZ}EC","9!e","i)\X","G" -"YfCZ[CA?","71jR~$h'X","K[+-,C","SvfR","Iaj2","" -"UM>W","B'!""<","eF}/F","qCE0'","R/w6M","]`SK~N]`7b" -"o(:F}o","y+&W$","w ,&y","@=.e=qC","*3/*%(=R","Rj}C748o|!" -"#8ij|OlO","Qb","ldMsGXz\c","{PwuC9ORE","FW||r^|","g" -"[A(","%!3D3qN","eZO","n4Fw.:","G~wdG","" -"<","J}@>Tn","%}b-<","#","X']" -"<(&\\1G","\P.R$.~(","1G.","srP","2o_T-.-","" -":H.5","","","CxnO)2L","VX x0","u~f\*" -"~!f:so",".k[#1t*K","_H.>*^ !V","p)>#h",".0i","2=??m""" -"XZ|","8|dQzJY?",")4bHG?w%","","~TMQv!kTB`","1%$Y@W\%" -"tt$oh=3n","v}","s!]rr[<-3","T38VpO5^","]Pj(%{o'","Dyw@#Wwj" -"]>uE[a","9iF{9AEo","^ogx5","mt","V>","Po+PMC:G" -"HN`{X.","J",",_","d| ",".~","+=X" -"zUW@7","sxVm","1|="," %","g?Hjdb-nQ","f-P" -"=m","]t",",CSO)&1","ACm","e","]{" -"{4%j);c"," ?_7","R","&^g","c{(","-Vju3qlM@," -"zx","dE46kz/","~V2L`[~cH","","iG\",":" -"","J!2Gq","q\0Psz(:?m","H3=kH]=K","A"," " -"FT^g5>","M=Z%YJ!{~","v+","%LNy+#9","4|(S","1/HRtxuU|" -"{o|2oR9k","WAs=}Y","aqWf","C$s","b","$""tQz3sw`r" -"wZ","G$k>'{?q$","","f._!*","D0`S5*Kt","" -"","x#;Sir^TPo","'20#","A @V","FnJxC8@k([","=@h_> 3p" -"74,kgf","","YL~|)K5","PJ11_y",".","Yrv" -"wz:2cC_","MI4Ef","#1Uo","57cax(","73","8" -"8]tv~tau!","~uxDQ","$Duy","ej=Zs/","3KK+","5M" -"I{lsbr","%a>MvkAn|w","L*r!]","kSBkz","\)i=","L" -"5","LUq|_]#Q","8[;V","U","x]Zy\OK","[p'%yX)^q" -"-HuYPT.J[l","1C`smvf","X4PYL","n;N/N","d+l","T_+" -"ZW}ot]","\1^&/_t""" -" ","/Bwvj","<[=8VqI",">+P","Klz/u1ps+~","-xl,V" -"p","X7jx=LGW+",",","_Ua4CYT-","yW7m","w)uU$I{[4R" -"z","Aa","FXX`)%E","","a","b" -"=Kr#bxmmq*","V/w:","","5o oQ$'1","D|]6[","gb<" -"=IRc9A!=w[","0lRRcu6jyZ","#""","","","r}" -"","&K*DQvP`","","]@@1Y","zNvqD{B-.;","rRt1yu6p;'" -"m","jFJ`$Jg","Dci_7]{UF","@; kbb)" -"= 6(gd]zK","%:""rOA","]UNvls):^","bw?<7r W!J" -"tu(R1h0v-","/lc$V;:n]"," uDm$<","s}","*W;+2 [","hT6d.LvZa" -"","~-_2g?","{","m0o","~8","=msEu" -"1< <$","Lg$}N","aj","9:.=mK""_+K","YDJc5csE","V" -"l#","g^p@fb/","/ZMbc0k","xdHXp","G","wAx^t!y#f" -"M)","6x","DfqF","sX(WBL]ch","\W#=iq",">RN'%oxs&," -"=","Bp'e!","^&.Epk","4'n2X9","awF?kV","q|XH.1Wr" -"DzPOe.9N","}wkuS5E","yHGn_<@",">r","/j[","M%" -"","<.t-N+f|>","oU3{}","z","W","7nR4^" -"tlq","r8w","/Vz(6uv","jEkLh","0fM","rkGk9lclrc" -"$u)FlIAqI","]O@c","lSNG.kl4:","U@b-=j2","~" -"0b\,v'Q","z3J-E:h","=9]%E","RWKPcqV","+",",6P[qFFZ$K" -"u3x=dPE","pE"":!k","l`Em3#bX","Tbms}'gTT&",";Vu5b-H","a^UG-t" -"Qd{84Ax","(`Tj;","7","slGG6<>xe","-","O" -"X|M}","LH","7x","",">[ZI2oW","4Uvwa[x2" -"l",""">>'9""4z",">C\Rzdf","594[^oC","W",">" -"*4D4W","Nt~","(_SW","k2Z!F#C>_?","VLDE['9z@}","].G""tu" -"WqZ!&o","j+9{QIU","cKCEHb>3|","d:M\|dLl)B","h{X:g","[WQ{+wf}" -"","+","~Wu$!Ub","N","" -"Q87","v",";x4m","J h,W","kNW","oLlQ" -"tn","#kXw","dM[ ~j;","`*^Ixk","S>LD","0bgS,p\^`" -"bc0VMYJ","a)2A","oSZ2.O","_*Aua{","&i#P5um","u~ F\3?W63" -".hQfq@[","nhkC.q","""dN","op","(""U-MI|F","~!~t-5n{=S" -"y%?","bwxm-=#","T5","`ShN4 pv`e","u","vxQ/]$7^m","#","avu7D$","","|u" -"@Czp'","?""&]s7","q","n!`","vvJV}i0L3m","4!T!" -":IcC","7=;>yv#I","F4TS9","[l","RNj93iz","+np1D" -"3","","]XMt]l","`j=}D","SX:9w8","6t" -"Rx=TAx={8F]","cI","c@H","" -"l@wVdWV=~s","8","&7WIizx","z&n:yA","3hwiuXI*","/S0|=:4GG" -"Zf,5RiHf\;","neFp?",",","B","-qK!6s]","6""wTc*N?" -"|f9","","I`N" -"m","","","WS^3x","gl""""|59","O?a" -"()","\DgIL","7","gwPU|knaA;","H.z3x","RYfn","KQM","p{lpx8","ucbs3""sDU!" -"[","UG`_X","\H}tv6""%","Esp9W_j[k","fD(7\7{b<","m" -"$&XQNcSQ","'MT!bV","DeT7Z&f:V.","/Fap`","Lh{7,?;","M0KIoc:;" -"K6","*?1Q}","",";hf$sE","@V@=JVWo","Opk" -"nVIN","y"," /X","xMWy?80]2G","w","|" -"gf[","|swiB$G%","T" -"l04j)%","&","#""","lA+JV3:e>","SMV: 6","X +pBY" -"|+%ar","Rh;","{","6kzX*",":^*y)4}z","D8m7!-Ef""m" -"PNC","R6A","ZV","87-Z","LKi/wY","rdDn3_KXs" -"do&`C]foe","]0N-jN","trQ'@<]4M","d'2","=a.fbg;S" -"","","p","9sg'uSs","zUAOCiW+5E","95H" -"\bciML","","`i.-#!A0X","|BF@`+jU:U","","+9r/$S ~$N" -"ek+s?bWt","'}","}IEOS.F","","#9WN^rT","" -"9'mAm","G9Y ","XY>t(i6@s|","mXQ4g'`l?M","","yVcse2" -"","","Q`Jn.? Xe","a~53Ff","q$","Z'i" -"","N.BV2m"," 1m*Be#+","w*Cz/%I""'I","+C^Pgd","%m$nS" -"",";","F=[.EpKv","6t`,","@","" -"A","","J@K(","7ap'$mI","{$OTs/Fs0!","g.)Dd" -"6J_","FeHvZ8g","$ZI#Orb","Qp|iRxGDo3","ZVu","G" -"uv)l}""S:","W3kwn3","","3|","WD'3|TVhew","35)v0+cEz" -"+]vFKwQ}J","o\b[(Adk2%","1 @!ug'","n|:zBN(?x","n?u-","r<^IP,2" -"b* hj!pp]7","&Ck","j/O%w","4@3PW#","/*2","Y" -"PhueOrt","ce""[eY","E","","aSlch=b" -"_PRLP:","e~-51",":rM9,OcE5 ","[q23'","09j#","x+^&!kIqSt" -"g! tMLhqk","'fJ[Y9d","xQZ;<","(o;,t(0",".%'$jL","M]h*UY3\d" -";bkMy)","","sAm2",")gFe","u5=bn9a/","kB[_.5n" -"%iZ","L$","cx4gQr","""H'$lNUhY?","HR""""9K YR","q({8?aJ" -"73c"" oBx;;","G~D:vh","WMyJUiqqv","Ld^5K}","W[]58RF","^)A" -"","","`F","y*q.1eV#`","p(","}87" -"B}","qVAxg*","]h@Mxx","#e>4","[h/n>","JQi|mXd3KK" -"]K9"")L|9Kp","f$c_.R","fR""6zAyR","%3qX4","hC'vL","@5tzcN" -"","8u\'FZA5d","WZ'=3","g","+e[;","" -"4Ys!z#9PL",")K+rNIgG""@","e-j5","2qrlai_K","~JP@","'Ig" -"F#_WY?&","4x~X!<~`3'","o,1UP}SaQ","n`y","z8-nh/]J","~tQx~3v:&," -"~~&j=UzU","V'^AT","R#cw0","wra","8@Z","84?l" -"f4xf","5","&10J9*.Z,","u","9$+koqH'3w","*-" -"5M7xn","/j","","<}0%-]","+","rg" -"","8LOd","3T0J_X,$","Jmbh?'F","|m/","eidJC/" -"$H!l{","nlw5.=,",",d.@7^","#M*DR@Uv K" -"}E}*&u>!>l","]>","^Y4p","CTA89eM,D","*6QO","TkZO5Wlu" -"js'r""",":h(","dxt","LD`?`qv""s%","OJ4i","L^qos:}MS@" -"=$s+[bM",")r|7#Va{k7","J","]","7e:{V@%9 ","QaAbt&LL" -"gr}-qu","r$a<",".","","~pv#70T`^","*mf-" -"y)""Ggkm\@t",",2_A","","@d_3Nh","LE ","&_DA" -"MOD~CS:0","8-","@CtqXf2","Q","","WJ>O" -">9+[+m","{ytHorhJ","egj) 6h'tT","|('",",qzG/","=t-" -"X&P","Mx'D_",""," 1Wy4Et","VG6\?\","zux55" -""";gWk","","2","iL,zy",",&","h" -"a","16~*""+58j","c1LEVYi>!","","","=" -":;Nrfu/Vgq","0?A","sQVBo ","Kx","bP8tj*1Ikp","g" -"-W","d","BL7R+\G","","rs","iL;'yJ;" -"$S","i8)E09","V+<*","b","%s+","`Z$P" -"4$kZ|08rBa","P;2W","t3yh73pa","","W/","" -"Gukr5","PPa7|Pi","FJ","","-e>8n,_","iwUlE" -"",",ypj","S;ac","-0j","Jte-2n'd" -"45S@Ts","3p","Cz","jEpI7}znj","*[#Y$","7z)D_+A<" -"f:]*s:[LS)","Z*W24[",";j]G","1_?W$+o_xe","0h(_[","Np?'>","" -"|/m0<_`","h64#f{Y=Sx","lE7=J","Ps#i3Fc$","t\\'\","xuM:" -"fP",",uH*mK<","st25Pb,F\","WoMmepO2>","?+>F&hu0","p" -"{","Q","]4","R3YnS","i(l","" -"+o7&p>yzn","rg","=@b","","W:y*$ahX","0|*Wk-Kl.8" -"""Wc.(/","*Q]!Zy","G+inbj-:p","*Ix&s~sdRp","<)","_8Xk[,r\#)" -"F>P","M_WtMOwU","7S\>`|xB","H Mt&v","""~kk(","OJIxt-" -"6q=b7","I_@4chB(fD","cGr>h","'D ","Ej%PyZ7CtN","X2*/h0MD`D" -">+_[65C#c$","%TB9","ul>z6","?51i","GZb/U","9/]F8o<]]" -"Qf","(R""8","","k\j","%1.Yr$s","Jdf[" -"E","7Q","HpXW","""g","1B","" -"w*$","GXtzoTd","wk&C:zzzL","~K<","/3V","tWyp""5x" -"sFh,3Q$","I","Eq8Ed@)J","O:)uVYJB","9%b23(@{v","|S'g" -"gVbR","Nu}l","Iwx>~","$E(ChS`l","|2J/\#%","5d81" -"","Lw","3o].b0 ","lR=Mr","g3C^","1^^l|&V8" -"9~~fT*","s0"";sZd","W0Z=","%(d`mVK","","hIke-" -"gzx\bI'v}f","S}>L]27","6","dRAY7","","VW#46kzhC" -"IeDL#aL","]?L&>MG:x","1L`{;`r","","hd1Gm}'U","" -"{","``st1&","it0$^'*","V^","\@7a","EXA''" -"D","z|+(^!mY","b","Uv0`m","=5p@IlyjiA","DT'O","\H>|fj" -"?hp6IP",":F?m/ka<1#","6dE","S","M|u9G{*H$T",".a%.6 LPHS" -"&.-zF%@z#]","e4B=`6[!QC",">","en","jx.","uXaNdy{" -"""#@","!JE","wuI.!Gz%","","Q_PfsezC$`","Yq^T5Upwu_" -"@c+3{I7e",">qx","A@pPoF","","U.wYH6","!" -"W>&&Z`O+]^","E`0Vw\","{2*7K>j","x17:oOWA.","EQ&","+u3$U1l" -"qcmz",":v9I","E","3CgNM!Zyuu","J3CnPxlZ1\","3zAoCIzOP~" -"Q.t ","J&9n^+6%","&!:2Reo9","C","n )D=C)7g","F?)`HLn" -"2Cr3G&.?","W7K ""gLu*","/","0%G%bIpe","jV","" -"A-8E","-W;vrHa","%",";","uOHC^6!D","5}+Bo]a!xl" -"<;""","j9v,X?ggD&","Wi^P""M=j:;x|xG","E)h(gfy3:(","V]","K: e9uf=","}#'>Bna","" -"#","r%1]4-/ d","Vo","fjt$4{.","","*Z" -"AI52g","|PLgl\'6""","jIpD","p/GZ~^gb","wHB,vy","\5" -"Es7S_|","!$IlYw""","ShaDcoA67","F5oo\?","c^cVR","'0Mg<4" -"ge@PP","mYHd[",")gT=pKN","J\vE+.D","v?","%Gigv5:N" -"","Ky","r ","i","R'e","v,LJ2|" -"","f,[_@C:lN[","","Q5","*84es9L1@X","sK`" -"&","0(&^\V","Bf\'o8Tde%","r","Zz)L=3YA2","s?_ds=," -"LBq?[","A","?}9Qv",">!BI","z+!}Ha4A","Spg1[B" -",@T ;M","","?3+","nQMla^H","{","sp|" -"Vl6SPJ","SqB6mN&""1","","qaJ'bl","","" -"yv@usc","","?zAaKO5","g(:(FQ)VT","","Q" -"Pr+=+","h","","Rr)C][" -"%c%o,","X,Z>C_%""3","Kj6hh>C?)H","6n:i+G_^e8","","aBEH" -"",".e]Kn'","1ZrSG}#rZB","L69","SR{(","C&ju>,}K\x" -"I-2x""Rrc&"," b&SLKl","\y/+Y.e","3X3","[g","$7j1;du@" -"5_2""FEMGJ"," lS`fl1","4bEh'v^&9F","hzw&u`2%n","#","v|8VT" -"v_E5DH","&!pR4D$lL[","[;:n7j","",".as$S)yF3B","jL~" -"7+#U9evL.",">M{","zgYU(K","FM;M","Z{?,6E9&",">^6hf`P" -"GT/","9dGdsq",":/s+xB>","E@","`","wd" -"C/~nk","_b","F+wu\HU","=cSkL/wU-","^zE","o`2,BG8","OPV+{x;Q","6)<{bwo" -"0","#","K",":,fb){bD","N'p""0RP&=Q","IOc.+N" -"$KWlF+rHT","h[[q91~Q","","dbI<","","P_XIMZ""4L;","&k4[&rC","/nb3" -"D}#QOPR","F=","fZj>^)]\9T","HV!hK","","]Fn" -"@7`&","3/]|@A#aLk","HhR>JY4-","r/8yT3","h?Et1m","cTH7k""_5" -"M$i.O'f","g@M","{Zzd{g?","Gth","","w+~(Zp" -"hbLQD$7\-","B}h0]j +v",":.:#C",";>/7","m98u!M","i*h!V`4`Z0" -"d%^","1/","Yv""Z;-","","{","QCJyMZ^X?" -"X","iz`3{tm*cE","qF",".[","HcG46","." -"991I;pz#'{","","&R%>c","BZ-|tO","tmt?","p" -"Y9o/43","","6GCu}","iwp~f0","=^y+","qK9}a" -".yq#%CcH","8(cQf","TbxTg?+","Jho/nr!9pX","a","t2z>" -"3m","L?cHNvo81P","s'vc Q&)o","","p","zZ" -"a$","{",".z oVLjsq","7","Hg"," qo" -"2?7","X",">|s>%%A","","{~","BE}|E^yt5C" -"IZ|@_)%28u",";","V cCIZU)","","#?E>/}","6YK" -".","x^hsN*2hV","c&ot","9F%[ m$c","P.h?LE","9744" -"hG:ey4lW:*","O i","OowDOrq4","-vRlPVi'B",",","4 wqI" -"eG","uj.{b4","","D]-*3P>uw","GFNY","""" -"9""|HX6JQ","D%","* ","^v(E[Lp","=uTx+0","{r(Tp`" -"H*p!#E]","|!u#]""6'n","{,|(I","ObQ0Iwsz","*2_gQ7B7('","G" -"6I[TcL","ai:MvfPt","*eat2L","nv"," k}5","_^{\?b_.s" -"Ohe[,0[3e ","|PKyw%s^","[s]JWg"">?k","92q. ",">r$3=BQ","+%%L1N" -"}Q5L1 ","JA~yY""","55o=;qh8~*","c51/)E3snj","}XVo?R(V","x" -"hiTVAZD]",")]"," 4km[","2","l?/^bH~b","d|'J2" -"","","p@","OAQ$","/3/!Nf","\4-" -"NOef7RR6",":Pv?U'E:$","U","5eWeM","+t2B}","l^N\GsW" -"","q+c%?","mmg!","?l6GoO","tJzl!" -"k%CwZK!6R)","","['","E1h#]~fx,","0s!_so9,X>","'ZDf$" -"A&0TSD/","","nq4rPnfq","+>G","fe5>H44","jL&\c""6" -"-YV\x|YH","","","I`lz|P",";XA","" -"","6rnX#KJZ","^J>.J{","nT5","qE(4y","#u/u!&>JJ?""",".[C5b"," ##f)J","WS9e/g@","+a)" -"Ih2jwR[?","/:a","4y&!w~","XE;;^#8","N$\]0x*","" -"m","XFD","4QZ","Ny,HT|~P","x""","" -"+iL2","rHxM>^\","IB;L~Qh","","/~D$","ie/pC<~5r^" -"Kwv","vRU","","NBTr","ae=_P22-p5","@*Zo'ar" -"1","~6G""+N","B'ImQomu f","d8R","s","frrai?hD1" -"[:_@","~JC->,KA""","ar*'>",",E#%}u","+d\go|99""","E*r*+/:|5" -"n""j]1)bP","k+y0iZd9q0","5L/m3@e","i8"")(\","K~","%" -"D@9","eG","&3y8","","8(w%","me4$yFW" -"Pzx6heCkcx","gW6t{A4|",")Ul)","26","[e[d3I","i27kE;a" -"t_JN,","cS/2@'DO","22.AdL@R?a","+","%5e2OMLu","h(;96sI" -"p&o{{wgA","","","","X3Jc]tZ1","HhS","!{M-MGO<","h|`<","xrk4[~AeO" -"9-nSy","[i","tp-","]1@Dlg","kU","!j\ho" -"Z{ ':6","MU?ysE","[","Ie0?w ","d`","\R" -"N,&$gmPK","$St\V5T","cml>7k","","F}","('50I_" -"-#3&9K\reC",".2'","\F9#>c0>","QvMG","""=O","bkmKhVqr7" -"0",":#3","!K+j rf","q09E =Ea","%#e^}dyD","2,(M*s" -"e!:^8*[Y^","","/kl[(i:'vj","L()=(UDb+<","qDNH","tQ_7lG_" -"6yBv","","#r","L&1P*#","lT`DMe,9s(","q""QBkRSW2" -";RTJ","xn","}xAZH0Bf-","lZCl+s]!j","=Ex=;7I(|r","0ZbhNU,8=a" -"","uW-3ptELpa","W%TCuF*;","/`~@-C5?""","%?Naa;","nyEf;L7y!" -";f;`t*=Y6","gHn","C?F`};","QRZ1","'DJ][gy","L:=+`_1c]" -"(2G","{,B#0B-qz","zeF7","?^+.hwCe","z#Sa)","*mD =k." -"'L>=","+" -"2H!",":x{dqv","+",";SNYqc","2%","G&^\[" -"%iC9^lY>","~:WA/eT",">","RBi]B,\3|","sZ","#Y:%%a^a" -"JRlT7+1","A`gP","Blsw""Ew:;","CP>.6rQ","LO_","Yk1" -"-t=JvO","!]sr","""","01dx=pc","B""L_","OW" -"%","w1!S0","Z;c;","2i@","5:rX4Z(_j","^bJg" -"O","r{Al/PQ)","Vys AP7M","ri+QE",",","M$k" -"ZouZ3u",".eM>-[|{5","amsJ/U{Chc",".","g","nWS[P" -"*k5=","*f","/Q:`5XIKy","45","h2q" -"","B","","_a1V1","jil*(z[?{","clJeSFGmq<" -"VIMR}!$","","\BEc|J8dx","y*w=a>","f0AMfL","oi]" -"e'X'Nn","ok","N","wD[4","","+12E" -"IcrO","E-3+4I8.\","L$","","Y"," lXL(9" -"Fwf^","jPf",":%(ln%jY","","I{m#","VY.$AV`Sw]" -"W,%H2>","a^UvVH","WY","qqaI^F!Vmn","Q%H~jh4HE","d#" -"J4IvLSF","aG9E""","va","cA","le9%)9","Bwy" -"~dT3A","x$$",")o90{V_6RD","6Joh!wqezL",":1mYh","DYbPZJ" -"tB","MM?fQ=,","Gm&T1","7LYo[1c-","X8Fa>Q|","!=:" -"8b|.iQ 1W",":fQqqc","(srw\Of1","hw>twY?~","*=&","A*" -"|a","C'yLi~}C/P","[,q*4","1$B","~RLxmnvB1C","GMyO95J" -"'","S:4wN","JGp","XOgy,)gEK","(-MJ","r]3PF" -"_^{a>`72","r:lb\ZAYct","3KC$n@A","Jvn","dlA=","Wd)9" -"Jq>x~z#x","N#$-`1f","r",":","{y.","o(\" -"KmK`uJ1bG^","bqj","_aBc!gGk","E5''" -"A<>B","","~$?","qR","jY""","9CkRu hn-n" -"5""VZfg8^^G","CoC","W2.([i&\I","Z{*","}?>;2b0","|g-[","|jT:-J6Xq(""/5&H","","<" -"Y7<}Wu[","O{9c","VB\3!3","BA`R6+N","n}W+_a51+)",".Ii_&2&HO&" -"r","/bu{i/J1zZ","WiK","i9gJ;","sw}?_b~kIU","dL" -"Eg","(q=",".:-","y","i0EgI,","&FlD2" -"v6q8DS7Z","gxeT","f","Y","G!K2V4r#M+","%Q+,_0Cr\D" -"$)*2","<^","z,W4NUHmI#",",9","}^WCH","&LVLV$","vOH(n\>J*&","%'F3,l","m^`8""","AJ","3T" -"*","5Dtb_Y7V","P2+$TT","yYX09a6","k","" -"dB""5","WCsv{tz","T","","","-Cvo\" -">pM/","^H","yjbU-Y","OX","F[(~u7*P","sr," -"fY""","0yF}^yKpO","","",".BKyLg","@MPkL0" -"X","mN","8uq-{","f7}R"":oB","Me",":p" -"","sdxSg","$,_i@U4R","YJ)","J","n6=aK" -"1LmLbC3 p","/(/6r","o(?:kOC","Jj&g2","/ne >S$>EL",",FPYM|" -"","C","9[P#$inlUd","HU}","r3x","sYKa" -"kM|SP<{I]N","F""a=e0A=","X@-s","lB%","~ZN-c","|HR" -"","Ip%P'*8GO@","Qio,hHM ~","l5w*""(:K2&","","qw\P6H:" -"2x2l(eJc#T","{I8G8#L","ycDF","Ro`","9Jca","%FNZS" -"^hNiedR}0","O.w","w'Z3$+@<","n4[I#",".'1/RUvxo","ESm!JU" -"W","","Cn","Es&D","1>|qceZS","c" -"GNCOr/n4",")HhO?yRJX","w","/|3(|qM","|}E`f","" -"fl?!XY-Qye","_5Vi","[,d","u3#6","h6","dC)" -"Glvf!+","O:[K8","C&","$p^;<#::sQ","""X!{","" -"UN2N>5","rVo'{8&u=&",";9I`hl6D(","=4AS3F","])U+","orfTuN7u" -"","J@","Y=","=Ql","@5v;9L :u","`>kyY" -"xum7","","D&#,AP","{mBX$gi","V0Ov","Q" -";,IxZ" -"'*RN~A","L?I!4rw|","@'AeCfvj;Q","Pv","Ha","vnV~?:HV" -"tcl|?eN","","",";*>%","f&i","-U-e4<*hR5" -":iPd","hk","i65t(n!>0","{","n(eP5?l+[","Ch5" -"3>[N26no#r","jOoQ\6*30l","V]0Vk#","%>?","7H=f5_d","'4" -"K[","m{Vo@_esn","M{=4U&$","8","vJe$UbU]","""ts" -"f",">wm=}q%3cp","","i`'""","]Y!E!_lU`","Tnv?TV" -"i/E'","lTkgm/uQ","o","X@{#/","3>A1","/(GDFW" -"!j%5D;","-ON","mcs0(khy","A}ADG58vbA","0|Gy,S\+","3aH%" -"sy1zB8W2L6","~0~0fxi%h","xooZs{@M>","CvLa62","I1M2@f)L","C" -"vNl@!l,","M`q}(W","5p!t%H+X<","+G=$,>sG5","","6" -"@jrzq","hB=","","U&n+Ek7P`(","zj","yZm;" -"","0]","fXQm&p$md","|'""v","\E520s" -"?$6,>","nn1<6OQ","d?5^QPx","@|lQ`5m","PK^(bnW^","PU-'" -"!;""F-","}17PjCBK","VXpm""G-",">9","S,Y.+u,","];=)Tg" -"HUvnz5f","-","","aQ","Vw+>","+mZh" -"H+29W""","W","RF$9Bf$z","&","HhOu,/" -"-L]R_!1^lR","c=:t_","M{%Xk7{f","","","t.hSS`*a" -"W7BF%SamV&","\g42","9on4&:",";6?e{=m* ","r","NChdJ\&P&2" -"c2MLf_","Unr","r>RMGg;*","'wkg0","a]O={K.~5Q","D^I~","`" -"Uy!mkR!}2","Q","gX}\-I[yk5","iZ'ri","{`+KNPP","~[*:J" -"q","+7[2{2*;\R","uzHg\","4_/Ka6_","Yu$","(?n","*]1B","G7=vw`^" -"D;uQX_yoA","#2FwW`To)K","~3KaS[","KH67Rk","{iYo1ge","73/c&5tLxr" -"$oth##sNO","@q",">B5v","`X ","f6","Zk+" -"G:","R,$","8","_\s3*","$/","/d/;|" -"C","o#@;`;g","i)7BJ","Yjh7a","<9ul@i","^[fMq","EF}E(:oD" -"&y","N.u!H","+RN","u","_7\G","pcA8Yl^eZ" -"","P","3P>!","o","~5OWah","|!","Od:pvdD","-U7z7\" -"SbDX8ZJW","S>h3:C_.`","')SLc6!>v"," cg$lg)ob<","","DNwfnm$6-)" -"","Z/&la,}(W","]","DEZHzFk}=}","","&K\" -"+T","=|0","{","jw[dHnc$_","7goKy%X'","@W" -"k H2]6","2""Nc8 SM","","","$X( ","i1""Y," -"EA>g2CW,","1C","}kcAGI<+$","r","*","" -";2M]cbb1","eMs|gMag~"," ,J>PEP","","","QjR5" -"6i\s+","YXbo>+gP^(","=S(]EJFOg","-Qi","RP5{JgjnC"," 5D" -"uc","t-Nn:TOk9l","Got\.Y,Zv",";S$x,u=Wq'","~`w()Q&","x2v" -"a","^ZX<%nFNw8","IY1DK8","{","@hK","u6]5+/>`/9","'" -"V`\RI{8","po","RV4","~gMG7Ez3u-","!8e?gU\ZQ","_8KYZTm;$" -"","FX}hk6{mJ","K=xs","o","OzZau","A5,QMsO" -"/~","B1","&@0k^","d^Hm!%;ORT","^;43gD,_7=","]" -"{&t~V<,t","oOF*0gc3T%",".>","g_{]?","M!@N4hVA\i","p(3=MW(" -"8C""w{[",")$x\Jv)Ao7","cf/F?-ld","Q%>X:","@","SuF:<42" -"Y%x\q","$","/Ag{ H","#vZv|4","+","HNO5Z y$'" -"P-L,JzC\O","-}i'jpM0D","","[z","([","JXP/ {UoF" -"@vF_+Zq2","iaGO$Hz","}d)","","6ydu&","b" -"m.ze\;4_","][OZ","\VF0$e","ME>W4","","7;" -"a~X>i>;E ","=..TSN5IY","9`","o2=GZbZTQ","i0-","_b|QsOSu" -"HjInmRx&","!7LgEXKQ[",".8^bq~pRA","m)t*a}","","rC:{" -"","5+NAC_*h","","X6","z6D]]!","3o6ThM7N" -"CA+PH|CQE","#D","eH`oD0fS|8","","I^6u","wd_n""" -"1X1d<","]/^sZB]=w",",","Wz","fHSf1.twFh","afkl2/" -"]\C3&""6"," ","]","PF","H""@B","9" -"r00KOp|","-","R16F","","X~IGj F3","$c" -"bi>","d7Hv","[:.","TI$##k(mR}",".B|YdVR4;*","JM[=]J" -"Ex z+Eh7","Ja","Uw02 -","]","`]w0bi","" -"qb!1","Ft?B=#","-M","0KoI5[sQ,""","1S$T@%sz)g" -"={","","","e6;|","E""a02(pe","","1v3D}'J" -"/b0","s=Eb?5$1","Jr","t')TV+","B{/G","uQVkmvl" -"Rj","fwyByySt;","@I,S","f_i/\GFb","<3Is_%Y4","#45{aKn-" -"}","LD?8I[31}M","U,anzV&Ir","e43","T~sa:","!gIa=T{5* " -"FZza7*Vp&)","Lr","cPQo.od%80","p8","R","xI`Pj" -"gv","/@z0?z-'IS","1bYF,","v4N]0-uaV:","_R&4","t7'AUt#E" -"q""T","","V=!SO","~YGK)WWC","sm.(.Na","SBH!@5" -"wC2b=g","['mrq;+cO","xV{h","","akQtP5k","T}:cr" -":/J","!",":/Hq""9","","6=Z_kL","M>X3F?" -"'^n/U","%&uJd#SK","6G`xh8MFC","","M%+w|M{~","d>8T" -"Uk","Y#y)})5X","a_","ZMJ(.l-]","/K*","M.G!:","7}}%@I",">_0][,","DT","#\&" -"-)G.Qy","UI,","3b]o0h*<[","q9\","yYr+","-Q|VW" -"WV","62ldDw:","Tg>","|","Ijn4B","rH6EgcQ" -"5\2T/9<","U6wKfM@8","M:@wF-","./|`ZF^^'I","""57yS{3G","B" -"n_jV","?i}C\*>E9 ","JS:;yR" -"M","E@5;_","U","96QR6#!9!-","uCV1e(Y6No","+VQ8)^""" -"E',5Ez","qzbShb",",;0d~M","vv,fb:E",".tAz98n",">S" -"","-BiHqx","6$J.","((v","pih.*F","}IRC""" -"Mr~v","xU`","","k$XN*J","^\w","w." -"","j>J~kiIo$L","gm","NIB","1&UB:9aw-s","F.xAr1nE" -"rz5'y'","4/>AH","~a","g*GAfy","9XLfL!Ern","vW6SQ#lMMW" -"|uu`V1*","In'F7yn","lkaqoN2:Q0","","`","/U""Y7mB9+e" -"R5","x?Z,FT~*H","K~I","80;bt9:5$H","W",")." -">.Lpxd","rWr4`J*&","n,\By$-8+",":J",">\GI","h/zpf%-" -"hwg<1\/S/","d4<","d{D*","M","77fki~8Bi","" -"IY8I]=8p+Z","Ub=.'","$^1a]]R","LI6!","h","'T/&hi" -"qjF?cG};:","\38>Hc","e^+-`jJ","}","K,VXVZ~B1N","oES8" -"#","","P6\MW|~V3\","BokP#","E*'Ej3GO*8","rJ" -"W}su>","rcQcJt{1}N","c=0z@m(y)F","moR^X","0\XD","Rr8$,;6P)" -"::)V6Q|c|B","{v","tU","tH.f4|%cAO",",dA)w!uL*","-fY","_*[~W7T","f" -"QL","",")>X/Y","wgXEuL\","~T","![8uQ" -"","","M$s{#<'~","|d][","LJk","Osf" -"!oH","uS","Z7","QSz/","yXF{X@%","~" -" {","B1N{6swX?","T'}@2q4y","I","k","G!KgP;Y" -"W8D-","L+Fr","~[C,U8K_f`","+wO2","?Yy7","" -"r#,\%Co4@","#[Y0jL?",",D","o^"," !(paQF","" -"","3:d@).{","bmURnX0'i_","-p/*(@"," ","Vo" -"","P6Xv/7 ","&","","ohO38@""","=J):z" -"RX","","!7M?NQ","Bzi"," s;2","Dt~Kf%}" -"","OQV:t","LN=RBNVy H","NOh","3","" -"`uO","\-t)wk/hPR","~uQ+{cEI","rS_+&$a","kMJ7fC!","=G","{kp)Aw","joB","d^/e0YSrl" -"IMv$O","Atc5kK","0nTfQWz","KG41X^" -"yxWd$20qt(","gVgwi","?f","Pa~""","=" -"P&w!JO6","zx`D"," a1B&","h:_(Ie;" -"jk/#.d","&iDBe 8","O8TL8O@","WuBmy;hH[","","HIK4+!I" -"Y[;?j?z[4k","z3","","Ffxe-","4]6d/2Bd","03rugQ" -"PUL9FtNL","yaYi=5","#m","Xi","&0S4i)T9`b","Kp9h@" -"7i]"">++x}]","`%Jm*u1","`{C&6]","#oQ","UXA :F","rQC61<" -"`","e_t,T","","FMjz","K};tdY(yNa","" -"K0tM=E!>#","Q.y","2s;R51p[DV"," 8i","f","!" -"H{""Fw)^j!6","th","|","{lA^QA=US",".IZr=xd+","Tn" -"|>K-[k9pE","Z]n0","K,3s{B","@l","{j3f$r>S","#2ti6" -"l","j.HeasNPH","x&""<","iR2FT4Q>^","jnHi""u(J5=",";X" -"2_,ch`","oW'","DIGCYT{","{w"";' b*","","U<+}?2EG" -"A@lAq","00KZ]<","v[c&[","aT.h"," n!?6",":6" -"7+)wQHw#b","K%ALf1ug&","h7","5","rAJWl-E","I>""]KkD" -"L3}o,!l-A6","~tE.","2W?D","zWahMD","hD3MX{h","<$Z" -"[l8","|h?fPS?H","kGMqLEX","""pOx0","I/'o<8_","\\","^q5y","","X" -"6dnMzlxE~;","3h","&S","RV?wX_","lH)Un$v>","A0q;; Rg:" -"x46Me","KRJ)E6YV","S6","NxF3Xe`","5Kx@+g'","4{rXn7d;" -"jKKh]Nbd","mwvt$)nL_#","]V$n","z7lNQH2","MF""9;",",`k*5o" -"YOq","GCj^va","u@G7Qn","0\7","|","" -"h]","ntAfy7zi","0_y*@Oe~","B9KE","7;","B@\wW" -"M","n^","5%e$m'$)ZU","","Mt?]e,","N|Z1Q" -"{VEwFi).r","bQcI3","m.","pS(<","r.R;yx,5a","[xjE" -"kd(","UF:X(]P","vf(dV0-","UT#","y!Ut{q#","^c}e|3;H" -"$'A+","`","H@D#}2AP^q","\-n","Lc`mb<","|-D2c5V" -"",";^-'&MU","9{o}^(41]","<`","96s.?>L1%5","+?i" -"o~^","g\Mz<)(","Y","Vi_?]4!MFi",",","" -"Knc#xK'G","I_\f)<","xJ!","x*",".w~s$o,","""3P#","/>9b1@Mz","]G|/","e4UsHl$","J^YR2/s1@W","Gls\#$hDF" -"bh0zBn","","G#Nvg","}9%e=a\","","x""T1/q" -"]/0Lp","]5L_ [*","u To^","","{Q@","kyP6.uHY1" -"QUXjd","""1BRv*-5",":b6>",")fDs","~)._.","VSyuG`#1","`4:e!Hr#(i","T%KP",");`%R","t" -"[x>G'","~Y*LU","R\mx;}","?Khr#?yh]","pE+8\k.&#I","." -"!xee","XjR\ w","]Fg(2>D\7","9f[UR","|v","vYdyU7j" -"<;(zk","md@","]K1aK(S","mm","""$3|])p","T>4X.N}e" -"s,]","}i;vBr+","+pr-" -"\Gf" -"T-b","13lg~d@T",">]HMXT","J*bQi.Gp","+","" -"[= ?x]","f9d_.;","~u/WuJ","=B7","8(G';V <\","jJ1X","j.","%sS","St+&}b;qZ" -"XA*","","28@TX`?n","jbJg0Xa#:z","p9%60","zpD" -"","z","vbB1e","?q","F","MjTNN" -"TD;E9uXF","","XI6Qh'\$2b","]jBMoDA","R?Ezs","Qc" -"'",":gj","i","N8$7","}^"," Cd%@R<" -"&SOX7e9u9N","Tv2yH-|B","/[\*B","FH7%T]r|<","'Yl","" -"o{0","C'","*","cv","v}","" -"NuGy+x1!N","","","b*","zB,]5La","" -"",".","","y[<^oc","","`#k#w[" -"Qjb}22","B^D6o*_Loh","fVyvi)@","","~G","3]N_" -"Z~|K|f6|Oa","`WE>(PJuu","B@wzpD;","x","=",";tgXMUNF<" -">|(","J]GI""5{0.3","\wQ[w`D","9079X3NUW","z$|",":lM)w" -"@/c%","93ZJ.+=\mk","5ui_N%/","]","$@o",";!FlH4$" -"xcgXX","20#","PSUlab","Vkf","fW8","0A#>""nd" -"De!^",",}qqc,RB/k","c4\","sn#",";","=bgp" -"1Ryoy","*oUI+","Cd","50{0pm,n6","kt[z","" -"yt**","fy7%","Ie320&NhS0","3DP","Ccg{",">E1" -"-.Z!:^Y6v","M5/B","%~*`Z","C8CyOD","&q]","~" -"'pgL5/0gpI",",m[ r""gGx","f7C/N""","","*3#*;xD)!","B\" -"LKi","x9BW^","\O-9"," !D/zsCKd","|INL>4","o}:y" -"MA~kMkwsRl","p","u}","EJ](7|i!","s'2","Ds1-M`Uj""X" -"qn)dj.","""er","","HAR&x*1","zUzu!tI","QYwB&>-0)" -"y$","ZK_f~&fq(b","+.","%]ha?X","D:a7ypd","GU" -"","","$/U","","4P}f]Pd","`dB2>3XAoP" -"|Xu","Sp|?]l","",":I","c4b<0","!a7-'r,o$" -")","fUNO'@:P-t","@{@)(fl|","\HJ;|m.","3_Px_","Y]WHtd`a" -"@[+k?*#QT","?","Z]zwW: ","+)1F","pc?3Sigt","" -"lk1@:","cd\'",",dEl?","H4~|","YGq~g","" -"LSt3OV","v]1Uu*{","G(y-TmBL>","dtk","LNq","" -"Bi3Id9R{","3l""?\","","C@|_RD/w21","$=BDA8" -"`[ Y.0","z;{","+n",":,z","]lo;zSh","X`!HCUXJ/H" -"vR83Ib","{g","qm->~!","`nBroYF#K","|5",";PF\rfd" -"","}}","R<`,Ri",",","=T"," P-)" -"","b[b89C_I","K^!0","hP","{r8>N","D::C~n" -"hdyQK","""07:jB{",",20G?d","\OEvWr","+u","3L`%dOs$#3" -"qDJ[=Xp",")<+e]u","!_mwrd#^R""","[FvtG","i:","C~" -"EDmN","","@[i|:2",":M'yof 'n","(","T" -". 50`Ia~Qe","@!&<\Q","=","2M|$.7_w","]# fQ""T","ZVf" -"]t","+1& D.u",")!:aE'u~}0","4HA","d#qoS","O}v]3;v" -"C","^3.","BRU1ac.","c#pHg#n!;","lQ","OF^BbQ+nJ" -"OxC%[]V?<","#p8ur9g","f%t.+viSba","u;)K:.1~","a","<2~83g^" -"de:$I","y","+|Y^r8!]c","","9Xv?t{1PN}(","0C","]0T","5}D|v\\","=|K","+5*n","!ElE^e","4.""" -"sT4-'1t","H'l","^y!~O","=Ou5l,h","?","'1r'9p

d","-Qbvc(n:Q","c\gspW^","@ZB3","I[lL6M?xA1","m:6)bH""uJ" -"H*f5VYX","+2Jf6DL0","|Jj","^K.Ui\0>",":Rc","Atc9:hJZy" -"_8)_g9pe","POa)WT","""lL+b_eK","Cgg","Xtb","R" -"<>q-@""w;","\x,%T5]kS" -"M[{:ip(O]V","U# P.AC","44)vfbfO","3","D","1H" -"MG","92Q dmDH","8K4Jn","","B?L","b4rk6X~5GK" -"R!cYx-","S39N|","VKcj3VE","{St8=9w3`w","7Ex^B"," F/XxD.O" -"+S","%","P\k}IW","[ZF(9a]bH","hf","u" -"TnA%F){`j","e2;","-CT|6([;","w","vR8^","w\O~|" -"I.L","","3[`","Bpb","HOI-H","I%" -"Ye4fq]LeS","n^","Gl+>eX+","n5c,w^z q","M+Wg4","MK1y7/s" -"","5","Z l","\R$dt66uB6","","7vtS" -";p","L","r","4:|i[\lk-1","0[?9","L-!c:.Z" -"qL2B","+","pMa6-","-7T?","XrSN$B","~gH:4u~`" -"V/q","Ne)2lTC",".r","~","bo[=","c xcta#zU" -"*s","w'Kd,(6%K","","dTY4","Ud/E[m7d","" -"WF1~Z9ey","q","2W\o\2","<=8R8{f9oo","",";AOU<#Z:~`" -"UAd","J",".b6","8DN^]3dZ=F","rmfNjZ1{","1!iS!.~M" -"1U","phL^S(k2","","R","b(z B","J5C+zG_" -"M6I<]KmFP","#U}l3Ej"," ",",7V]h|lMYE","nk'@+","Vsh2" -"1an/.:d\Aw","w)I1","*s-VWon","o5N","{r","M)n" -"N","@ta","ld(SB?/|",",","].R","MY=WFw.0!T" -"_2_ee","AjD-]U",".}9","""N3_-","Uc)z4Q","" -"`Nh","'YZ_K%","~3v>JbjK*",",aHA2T","vlT~","M01cPO:d" -"%""=/","A,KeHm","-5qe$oa","","&'Q~I","5z5P }'" -"Qq])","","`8","LJ","h","u`" -"@o#NVZ9O","w-","IsM[yR-JC8","mz\XUEe ","","z+EjF""+Rm","h","<-|R9Ni@:","","Dr{/ZUP","t_ m\<}" -"6q~","&5L3","Sp%1t>mw~","","`>/c-","R*Q#cCo#" -"0Y@j8","r26#|uxSC","#o\p]Y","!$A>,]yI","E'?-",")%=Q${7(Z" -"iI","_9IG93yU","Yli=A@gi~","|%17","""B,y",";RoMaqx" -"@wh","W'2N(O0K","m=4FW?K)m/","9W$T","^q-i""","7:r #a" -"[X7aL!X{4",".$","_;/_","|IGc=8A","r#k?},Z|D","v03x4)c" -"h,)","g:y^","1~k","j$","!-wcbhC","!mD'p" -"i]f2,ndU.","UO?yjn0*r9","c8`/1D K","szhv)V.->A","","XSoe>" -"M/%gr",";KW"")5","Oh`x","*ib*","s","Xoz-G(JPa" -"P%*C8Po","{5Z","v","6Vm1mcw'pu","D>F3&8[y","09e*cu" -"9kK@4^wz_","G","> ","HuJ4-","kY]5vhxy","*H?TuXW","PNA6-C7^Q","Ts:J","F" -"G\Sk","","I:","^JfS>YGRD","H","j" -"","#E""","xf^K>eG+Z?","7*&ou","gx>s","c^!" -"D6^j"," oQ","S{",";w;","4KC<(","" -"Lt","&gPpli6}|(","Pr?kT","kNcB3e+Ck","qPnByk[r","]j;v0PutuX" -"X$k","=)T)4!q]Q","'T~rv)","","J'E[um","IPt\'~" -"8{+<4s9","NL\j)l4j","u""69pBva>c","o0o#%:Ad","w","~4","!F" -"z>R`gOw","kD>IZ(](-","B","A","%@8@","oSAp,xJE" -"SwqY ","ts%0H","h","!>","k6W(}X","v" -"yD,Ypjzv","mBs6","v","#f","4T @@H\W6%" -"fZ*1nUi",")xt","nF","D6U:qu[*","R=cLp$r.P]","!cxpw" -"*w_njO","fvK_2F[%","isa|%$X[.","(viUh","Sh; 2W","Y[*^.n" -"","\#I\M:.4a","1np?j","{","","p,2y,Q" -"(_lTsz~=TC","veCy%8zjpZ","Qf""Vld&8}","f$`gMUQFc","&]","I;`#_s" -":BcyN","GjB6dZ{Rd_","M9Uh","XPa","s':G""sw","HT4K^xOLM" -"""G/Q""`","`R3Ibn""","PjJS","!",")","4" -"$_,~","dgvVy","yy}e*","~6`oywRos","wP:)4uW@KV","I","R}NI3m","'Yt;V_0C" -"0vd~wmZ7","","","t_SI;r$","B{G","{E*&^^j<9'" -"F*oK","(To.aG","4""%=g3","Ig46,}ZUN","W0zJL","D,7[&Jy)." -"b7S",">cp{^ ","x}+J","","Y/DwZAg","*" -"","[es\x;1~","3Fi4U!tl","","c{*+S",">" -"uH","?U+$[[Bp`g","'","N5S;'","","""" -"+a$Ms?8ED","]ve~@P+""I","7Yz","_","qI)m u" -"I\?","","<%Wu'","yviC:vV","","n84" -"[fX!\","","L|i7B?(","YO@kbC3","Bn/""","nF#*","","","T","^M%WpBd","C{MA" -"k0^","bgO<*","U%{K:bA?","D","9?l`Y}h","YoP&U}0W" -"uQ",",pI['i^","@,msdH5","/&`pE'","GO@3WP","" -"p,D5dY>F","#l)4:z>Q","4Rlx|(","BV1/5g[w V" -"*|a1DL","{{k&bY@|ho","Mn3-d#"," l%-J^","j","GV/" -"Sl","dka:`a","HS+Px","pF`'?'C\8P","7_6(Hdq-_S","%He1}qQ)" -"!)F","xHu=r","5yCOXn3V","Xb1+Y;>>)I","JU#i","w4-1H" -"cA","-Z",":y6J`t?0J8","Eb","C?f!","qTb^?c)>I>" -"(5};","i%","-TfO?~3x9","G.(~tDOl5","[qc4","J`E^c<%" -"%bj0A",">4F","r%","6^","%7oXb@Ba<","JD?t""Cj_v" -"Q","","d","5{D",",","/e2H1N","=aw?c%","}zx1 ,Z","PY2" -"yNmB/UQq","rf^""\","qya8x","PKVs","x+^ O_G6","cL" -"","ZyRxpw~","PCajf{$D5","","G&","Wqi_" -"`""ik","Dnem6JvoL(","iVBg H","","aJh""|y9","8*","$\68\%zv","S{=(a!R","e" -"cy","","UTl",">;>x+\z]"," %^","k" -"4mt","P{;2),_","^,""","PCo[","Shop","K9Q`z" -"y:X","z:","gvJA$OfYw","9c~GX5OggZ","$0","t-@k>ry" -"","C","H{"".c9","0Wq?9","4n#|","piP^," -"s-oj($O","~HX","c.BSEk=H","mUy-4]{/","",")""BaQ#" -"z","","$7?3j","k~ 6;(""l6b","Im","v" -"7""U\tJf$42","[j","[#x1~u]4#M","j:","z}kk94O",";G1-","Js","mXK<--FDb9","" -")M;#*S","i","&9;@SIX{;","{b)}]X","51","/-bN.MN'" -"""xCY","","'61Mg3y\=","$/j%Q)/","TW&(h\i@GP" -"Du!","""'Oy 'Z","b:%8f8=XlE","T{0""HSm!3","`*jxI=","" -"9","d$$","o}8t#C\3P","mYxb","%Dc9*JE1","Z`""[B,U`","6AzNGtXWt",")2I!G","++'Mb@tux","~H2E.G(\","}Whl"";$-?2" -"2\","cQ","Z27g","ICj]n1" -"IrZ uQ6K.v","x0'#:s_a","vI" -"sP+kU\","[","g""7_y","v\f$x^","!aKiL,H","Zs{Rj" -"snNQ0T",":{rD","&!","k\`TUNSTtW","4f}(7L]G","" -",H273~a","l3vF^S","]ORT(Qz","NmY","4","Q" -"Ty|R#r","X","hJ#f}Lu|","ME","'","" -"=aMV~#~<","N,","-'Csk,J","bWK$tB.%i" -"i4k[NdH`4","U=]v38GLuQ","\Wu9r.iSH>","}cLq","{ '#","whfKl" -"~O/{",":FL""7;","?:\#vur2ZX","R","Zw8zZit","ZE2tfb>" -"=#/Y*S","QrvY@wv_","0M2){","fOAK@@8","B,(K&=8","Y" -"/r aTHPl","?","xv%zL^c<",":4f7e","Ij?hV_?","0z,-z!^>" -"!1mLC&","@9)@{y:","4QF","m?*^F","!G8","8]p^]J/","D'.v|j" -"*o:*y )v@3","#k","2?j/O_m","A:Z6>`%","E.diTT&,","_rohH?","r.'_-Ysi" -"EMJ","24VWJ","I\""6}LQU","*t8e^fNzhN","Kr#w","e7\<\%@`" -"\7GnX}","","7Pd""k(0'^","]","''N""FP)Ck","-F-7dL*z{s" -"uTVk,<t-;/V","k\",".","w7#c.W" -"""+@),:V'Rn","$","*","p&0UB",">C","^2q2i,N.!" -"F|p","l3?ZvpN","DDPW","" -"NP B?[h",",$6Kew","?>>01","","#cn%1p7c","p:" -"i!","Qp+:h}xiL",".]%5""nnP","DZR6|>","-AC6`C6V{7","YZ4Tf" -"I""}eD;{x1A","ICnW&]UU","w/+c/!","L""p'","C","(k" -"G","6n]u@^x","fF","8Z. 7\\Fc","4^YIC6v1"," i:IYq" -"D9k"">J-","'K`]]V$&","","-A'{51I&.f","T!","QmWc" -"go'4Y_","lgije;}C","I+aC","0U^46","X","gwID8[s" -"4","(?'t{4",";Av@^","Ln]","1_&_r=:,","?kKf'=5}?N" -"2W}LdI0G-","W8Ugf","wfi{R","u[mW((Ln@","Zn!~4CT","M/dUo\S)G4" -"%","","|~","w%Q&","c2F/#3M_TQ","0" -"P^","SBM&'OuL","M|6\a:+$","","}","(l1Z75N'" -"3?kD/c_@""B","/Ln:ZH,d",")4ykVY[+z","ZcP","i","{D*L/" -"IdN,Lh\gNA","W<9xisj","Yt<~KVR' >",">*","qL4ELo","" -"M+VcI%+@","u<(2[","" -"V_UoQmTC2","dSLJG|","4BPdqd.Q","iR1t6","fPdd=","oh=7&jn" -";pbA","w_xz=zr","%(rE]","=Xb*O","H%","'8WbZnI>J" -"","/w|L4lA66","","Hr1AQM\","0v""3","@\" -"sR","PMS{*JD^Da","r4f!|S","m$j5:","8,f2e-wo","7D{,mx!" -">h60}P@U_","q1i1","E","2a_","a} ","0V" -"",",eT&F","C",")S8o`^R","kkeF^N^","(s4Q_:]" -"9TlG.| [pe","pv","j1gu",">>","C5v'","gNHt1J" -"fm8 r8]Wv#","dG1wZz00?k","N+m@","a#I","6","07fKI[t" -"",".s","Y",";Iz{","","wzVT*" -"4kD`9^bH","BB639","U","aE^!zs!dPX",":5B#+b","[OK}k=k" -"[6","3r7twC.","\Zg!^""*","GbT<%iC%4","","4" -"T|","0 e?9#rJ","m[+Ud<#[","fJ!n0sQ]","=3?`z0","P""~" -"","\)","r`#-qf:4","+D\5c)uLEp","","" -"""X~i._5'<","=G~e+(L","","!d5Htw","<$w_71","b" -"Zb%ia",";,GBWibWi","""@","`#rmH% g","'faQgw:s,zVCoB" -"M9&A&4x:pY","Es{})","5"," l{hN)" -"o0GC[","","Gu","cq5ud/8>*M","s%SR","#s " -"Xo","-vuY","dFF/J" -"Cire0-RGM*","])'Q2}0K\s","$)8\","","N6Wc0","/u1N/" -"QE|(","rID/$UC{a","INp|dv@pS{","hyoK&%pq","yee>*G%yR,","bBWJw" -" +","e@*:=>`B","woR4","7Q2@NPJi^","_G3","" -"6z""","]~@9","-K+j&+yY6","MeOr=" -"}e:7J","j\WVx_':Rt","xW{","$AJeiS","Z!Q/S","+=m]" -"\",";v|C6`e85""","io<1?-+","!:'N-","A,$1]2","pV","vum=ZU" -"e',.E&EM[B","57`=@kxh","):3:","u0zMA'He)","=]cAT","V`NW","Z&l&~w","ZjiEt","","","jc7" -"*=jgc+fL","","n_l=Y","^PDTwcv~x","u&)$Zp qYU","<>" -";E}","G]CXC","2 e7)$=","""","U9><}j","P","gr","M@5","|." -",Vuxw>S'j","$m[@8y",")4u3[","Ga`x>K/+>","}o","~Ot" -"^U-+TT","FHD","Z7fl","H$","OCj","t$XV" -"5z6P-","","","JS","[7<{T","/L\y=1`Eyf" -"v]K\4fOj","w'oI$G""LM ","uum]Q>&nM","","~A0G)WH","il" -"l","4","mn?","ot#c","VzP}sy65uo","$',u?;Iw" -"8[$mIq*","6{Z","WV[RiMU","Q]2G!","YjZ.W^EQSg","#B,6" -"","DS","W_=O#","^VP&:Ed]'","AB9e-<[","t" -"tt",";","6OXm[","5!d5nS",".:P","60(48","","L2nv96","s-","WM""x:%","xx@","","w@","F I-" -"{Cxh","","5P~ilp1","}\cKj`","hR}?Ae6J","z!" -"*K","|WFI6","YBXwY.bne%","]k9*D@U[5","Y\G","" -"{","am*HEtW","","xXht","g<>;rp|A","2\29#9-o2J" -"c9w$%2S","","H" -"76Bi4:iULa","qX4""J","/TJb","; ","","R" -"","Q\hZ%3CU","Reo^)yTz[\","P1","]wxJP/","" -"!M","KV)","g3O:SL VX","hl+y,aai;","c`","" -"-qR(>""","V;F+!","1","%@vN;%","""p3pUC","4!^}vE" -"c","{(.(`*rjt" -"}I0E:","","cP~&?#","uIXH2i!","H","Bn1]X!" -",lU;8d[","xbSL","y","Q f1XNt ","r*8Dha}","?*[Hg~Uu{" -"tV","Q nm","xm|.","*LI91.F@%","" -"^V3w>9d/E","@X","k'~Pa+","\Wf}","d""NRf*ex","Q2@o,:" -"WSFyMp","r_NG]","^^Z2<&)H)6","a>}""`JE","W ~iTZ","&wqU" -"p,","2jqA","^uhS&~YI_j","Pl ",";#%s9.k","r*" -"^6ny:","","(X-","E]_b7F","9%","" -"ZxI=TB>","wpok","AC# ","*yr","g;+)","" -"z[@;go`=A","+g2","f","W|M""5I","]\m1/aSY6","Wi}b" -" c+^@r","ru6z-c*9;",".Tc29]","~vj36qux%","L_NQ","R-.S" -"xMe:iI8n(","=z}(H~","oUFP<","q~6jat","Fw\a","~&Z$!-&[o" -"o~x0qMT^","Z 5A|",";","YAo[#_'","kJ9-DCg","E=DvK)1;" -"a0aM","%","GJTr4q","gg,SZ^","","i1xA7" -"zdC-M~y;L","jE%&Y","IM#dixX{fB","","sTY1y'!&","v8 GhpP" -"c+*)0","","j`)rF@Attf"," _" -"u1GDnO","0c78Lb","sE","v1+'F-oV`","Kt","+mz,h!$h9N" -"WBp%ZHHxP","n\:j,Q\yb",".7!dK","I*_Q7","'S'~C4(|Z" -"3Q0zA[Qcc[","YmxHr@E0","Ks"," iz[Y{q_H","0","l).1~" -"}{|qK","3","","&y sNI^T]]","F@]U]Bw`A(","-" -"j","1","Hg+`hi","HoV","S""2@+~[c7","" -"sfH","Eh]Rb","uC@}xC5B+_","w@","","8nn" -"'c71","","c","~':E-","D*qwlE\)X","Q&a" -",0","OR","P~loB","QM",")w 0 ""","`n" -"Ef&Y_Pp","^",">]6p""ZSY","m_lE'","g","FnJ" -"KR","wG SN._Nxx","DH4k^&","A:~C","E","uXnx:N8E=" -"M","","IqEa)X6G","{-7^HZ","u$F","G2" -"yBy5","B_u_X8","QWT Nij","rhg","Vv+:""","" -":;w","2v/H","z""`Ie/+9","|Uo*","Po,td",";DT~iBGu," -"`s!(p]1RXw","2iC/#a","ww=K%[aOH","N%Sd","aeyD","K." -"]obQQjN","9;edTJO","^es,C""","n=LqmBbY>i","tKn7","1u`p" -"%","Bh!q.z^@vK","kjVk0GJ","a:5-","","&dQ" -".","[U?pf","S4^\$Z v1","]/hM#`*","=","Q`Td4RP\(" -"d9d_","-ly;SO","0y:bX=Bhb3","RAgf",":8XqzEJ!","oSUDCyH)c" -"Cd<8",">","}8de/[C","MVdTrzCbbW","","kSKS=D1:o}" -"hvw.F","+SBS","\`ZQg","FX,O`EoVf4","4tg^!i","a" -"@Me","w:(Rx","mvlFQv++Dz","","'","p" -"""","94'6Xo ","+'",")*Zj'","g","ZcUj/[S6(" -"9;h2R{tS""!","! P","y","T2k.i*^&s","vM","{s&" -"'KnsN>DO42","coPF","D9ydC>V","E1q ","Vl!x}","q+z}urY6Oh" -"a9>o02>","X%","d34MR","P",":G","h|hod" -"YZz:y","elx2$'d","q","we;K","k*^Flq","" -"W~]","p7X","CagX_M_l",")_@","","u.&" -"","4$C>4_D)","wDh","h%25t2+","RbpE","/pcf""" -"!",""",y","Iox/yx","65","JEflds7","5+" -"M^.rY","Auvh<{_Mq","[`M_/ET0?%","FE""{SR[","Fz2A","z7-/" -"'u","]A","r~&""JH@p~","\UD.","A\}l","Q7%\Mr" -"3%","x[","","h3s","","N@ZFRUsX" -"']|","ERo`>Zs-.","]^wq`iuk","E","CFFBG$","Ek" -"_'","?y4^""Q","B_-","{s","SG","a" -"GCf}0","+E","'x][Y","g$^h3 86n~","t {KfF","","wg,U[7h","=ebag\GxPj","" -"?\i>gbAOe","p/\&$z","INQhg4)=@!","P~:;bf","-","Bn","N4=}:\L3","TSJy{","(L3NVw","hDM'L" -"GP5`LDVx4","h","Vf.","U^|ggLhK=H","py^du{=8","C$w4XM|kg" -")Xo>x07","%~V{","YT!m8g","Syisp","fb@ KU","Z6" -"YAB:ZA_3+","~TPD","Lsd6QH","58~&r","","X^" -"S;{","0I]iC?M","n+U_e","K","P?Nm#K#;Y:",",lUi" -"","KpP","Pt l","}I{bot/ts","=h M","d#dH5eTqf" -"?4c46%$y","#C$Ib5H-","","*~eM'f9","","G+4Ut""9G" -"dL7|",">;","Hj{fQK^a","=}?","zX&<@N" -"t","NGgwnD!","(sKfr","33hqVpX","](]`R","GH_|}x`""O" -"zzzC$T^EO","FLYu","&0Xy~`\Vs","",",;>d0ag66B","d?#" -"~","OwFYcF*%","P[hQK3z","SZ1[9xjnu","v2x{"," 4/de<" -"~gi5\ua ","D`1C)","s_[|[Z[jM-","sAzDoB3","'JZKhhKU","qi" -"/r;?fl""]","wkCxHw/@bp",".","]f_&**_D","Z!`eD","BxmNgUKQ5" -"UBQV","fM0]?","ea+fC)B$","}","\v,]c{]<(W","0\pw/8uy" -"w}duH","y4msg","(D=OtUZc&t","aDYIZ","k""<8S(v","9pOU|","Mi","**""dU]" -"_cdC.`/=","R%na","wW","Z","}UC},.S","L4*L" -":e%"," w>i","a|%7j","=%j}(19g;U","vJen1j`75","qso" -"7X:HuS","hw|Kd","z","","3hz$L0S$T","nayBvnNzl" -"UU",":{5U*TS$V","@D)0I","x$G","^mum/","p" -"?V]J!G4c","t","~]O","wk|\ t)M","Nm7","Y&m" -"OR+TUs","`","l2u:a%?c","'d4f","' w|","{" -"7NS6r[@","m:59(k","4pT[m9i0Y>","","6A<","M!X>*{cQ" -"","uv[<","~!uMx=m;K:","KRK2|","j5","`X," -"","9pY7D-C","/","CsPr5M_$eH","G6XJAu","$0qg'S" -"rzz","$8W[ejr$q","","b\t6>~{~%","z0L","=" -"(""C.,yOR*k","E>T|",")6nK2E>","","Ma)vW%","O4" -":Z#bMS'K4","B1R`+8%,","[zNJlg?","b}","t<.c","=e}6A'ZJM\" -"zeYk'5","-E%??fT","~3NrD","","0`SzOR_","&nF||\P/!*" -"j4X6t qZD","$&","VH,bX/iqx%","Lb#?nJOL","O@r""","~pV;d]=!" -"","/6S}WJ:","qw\D7&}|p(i,9",")Gt6|s","36lpT@","iuQc" -" WpI)^Yas","FeXfJ*","lzljk.t","fD?Cq","g",",(H1Vh](" -"q""","","<'az","M[mi","*%Tjk:fY\","iyDF \L" -"7T*uj","!ih","rqXO","\h3i*i&","lx/cL,Z?H","}Wwn@Ug@y[" -"dDobT>97","it","SUGaTg?4g","[~Rn`Vqq","_?oI`G6@P","fh[" -"i:9oaO:;@L","XFt{%Ll\","?!7|L8BG","l","ZKJf","","O","17MvwUY","efGn","pBx{R-}Lbm" -"$1","t7Ra`","fGX","kVD4n","!C4Kg>5Izk" -"r8=NCPVr","t4@7e,(DRF","wg3a}","bUXZM(Vi","1-S.M","Yz@" -" X%v5j9",";*z","" -"ck7\C","","W--","3Rkn.XS","IOx7>f","#HRJFU(N=e" -"jU""[fymZ$!","","As~1RPB","*SZ#M3","Ea9-#7 m:f",":]fiXKd" -"$`Y8bM","DEnVz=&`","nG;_EWiji`","((jhe` uGB","2[d",">#hx3cxOS" -"C*YAih^ViO","hOF","$Sagt","qC(1!B%","C>>Q*UJq0","@" -"Kz)HE","*Pcj4Us9sx","VXI]f"," I!Otau\x","l9YKUpq?","" -"","CR8`","rb>",">Q\|D7n^;","cv~","fP(&" -"VhMQ","h5E","","\H1r}yR","V*[}N4T","I" -"{8p}*]NO","HZ;","':cu-<*d","KG>gw","/(VFB4d","o$*9>/rQc3" -"aWY","Nf:N&R0pR`","+(1*f4","-uyDp/*Cf","lMF#","B)KTS.ZV\b" -"S5Qh[8ox(]","s","5iG*?U;","uV_3,","YAzid]Cmg",")" -"r6$K+","'J","fR$R","K50uWL~","9~Ps","JKV^m>.9","CA?A]F({",")hw'$gVN","" -"T9'.#Q8LO","u","_65","|6>l*","0_""""","0" -"yEh^`5C-","Vm""D","qO","d/mS+e","r[z" -"v9hk/Qfb","1J","%HSp","","DlO-e;;",".*?48=:Au," -"F=(tb","w(B17O<","kId-""6l","","52","+gbm,z,aX4" -"'=","r&fL2UD","5,","q+.","U0Z6MxY[","}" -"CJ)O-","#E]vp(E-","#y","nN'nfp","?e&6MCC/","U7k","zt>)!","9" -";s","hgL9","","Dj{n","x?|PL.b","yac" -".6dc","Yq^im","|","1J{","","" -"Yja""M6","`UO1w7{pk`","b^h]=1N","2p*cdX68","u]|aq;ol","g^ UX1o-" -" 9V5}q3","Gf","","c","i","*mBe" -"k5Vz",">","d\?!*+5B","-","q\5j4","7 ET'" -"\&""""","\5JE@h`r*]","","!Vf"," ~","wF$+`536^WQ75a","fBF-JG(5U","XeQK!C""d6","D","7JCH" -"tgv((?2U&","x","W8ddA""_}/g","&h*s<","Z-t ","8$" -".VJR=B1","xG=`]s","yH$j","@@[W","R[@P3","( =0]Cc3@" -"O$r=/s|","vt:Fv","","V{i@_1","l.W+-E_","b!" -"5( DQc*","'}AV\",",#i{):#","Q<&fvv@ @","QQCLac%v-","&_pD" -")Uek)9j","I%GZ","r:qC@","QG5S5@","FB(hOn,","#yT." -"","UUN:f9<","","v^7v[IQ=","7","" -"4n","B 9T+;6Op-","U%Ug.:W," -"4%MMt97]3","tf","C-)","3={;CYcQ:C","b#ZrVuwu=","r`/OZ" -"|LCn","LD+S_@C\",";","&qY*","sR-","(i"".`2j" -";I","K;%","a0OMM0","J$A0","R\c+!#]","p@Jaia^8w" -":-fzi","jeDk","^D8`","{g#U*$,C","5Y/3jU2l2","i!w LKb? g" -"g)^","v0\A","dA7n A","DI","@-^4;Ng`1","" -"Q]WZ7ePI5g","oWTo6V|","B>zL!dKZ","9","&'_@SR","KOr" -"AE#:0A^;m/","f","D9","W[l","Ulp}}ysx","e*dsWDA`~" -"0:kJL","2=_JO","i","z[QRG^Z","zzu+W","u?'];_[?,M" -"Qx@z0-}m","wKFe_%","","2Y?S[QE61?j","wbvhFqO","3w_sgCNb;l","LR" -"\~5v","`z","","kEB}","1?]?=CX32","S" -"sj.m","~`22qb}""]","^F","`COA","@fh,UR(zU","5lx6pvQ$M" -"Eth>1lx-","d[/Qd>0","Ust-""z8","w?","[)*vGi","[x`.|," -"v","dC","%""","x1i2K.p'=","E0^&^-","4Xv?9" -"QP{0+*",".'~|$[O-rH","d]Uk0{Y5*]",",F5AhPgNw","s4G:9Z^(5","?8R3V" -"}!&rF","Qd>(EU:K73","I)=","-_EO=>","q4*;","!1" -"48`\","+?D""W","r/Wrjc~3@y","T","*{:t]e","[Q~tiplY" -"jaNna<+@?","< ""!%u","","agI1IM","","D(n8(7" -"","k","Fr\{*J'Z37","qW=[x","QN)","J1" -"S9fMPCZ07?","r40rg3A'","[d2p","1","CJ""i",";3;4(EAn" -"4,=2?;A$_","","kdA",".fl5","Y","c{z}AO#Q" -"S,`)>?r","bg;dqM&^9","TDJ^}OH6!_","&& A","utD.",":Z~GUJZo~" -"[:UMq6%","","Wm6X VpV","YjqrGsJ^","G(@8_","E" -"X","Q?:","Oo:q$S}","I/`OG*7","","e" -"P?G}/zAPqd","pi2$J","stk>'Ec+gZ","RLzg2*","(#t6RJ#v","/NEi`!3*" -"}",";O","","xqEvY,Otf","4VeTY|","Y89/" -",'","\d>y9^o-C","&DTe4","N6kMA@8<5!","T","Sz" -":4lcZ+&T","^v","X6pZOs","!/z",",",":xst" -"<).Z>>$","R","xzY\>","B7","_","o[3:ZZo?" -"","&>=","g?$aElH+","B9[","O!1}+","R;0WgK.","%o/Q9y(" -"Z{F0x|=lT","V>UJ#7r#v","Dh""j*^","`","&Vk))5I2]","'3:t(&" -"/Jb","`xa","1eI89S","","v;mdlg)mh@","" -"","J/'\+","","kN","<","@WF2v_=%rc" -"","|Wm,ob/","TuoB)K|(@E","ZP","|f=xY)/#U","Al(D","+",":iX" -"*n","ug pb[dHu","[aMO*7Zwx2",":Q@U!w","p",")JPzK" -"+vf""8%[T","Gxv}?","/YsX","PD\e\DZ,","","&,`z" -"7G[gJK.1)n","t+YIExD>@","U0>$NA]$","yZ*L9-2r","|t.L9U}q","" -"-,hT>LR9q","|0y","&","=Us7q^E","E""nt=c","XtT~" -"^",",_J:XO9","u-Xl$(+%","tU)W-O","j$,$a%(\i{","G|O" -"Mu","/j`.5H^>","","H`nk!_Hug&","+$U]-}M","MtU$mX^lP" -"(4gk^AxHGK","etSi$+","TG","hs|4I*Iv[)","","|&78" -"","{sjW\O""","V_","W]AlB) C","t>z`]a7H1","=" -"","kT","",";","Rz>_mPR","H]uI@s6fU$" -"j!3tLX}C","\N|(","TJ,nx","y^C","z","6()?O\dBj" -"}1J%XD26","Zm","a","q29b","pTH3","mt]tD.b\XE" -"C`","%$L%.xb\","MT",",","%rz","+SaFh@PJ1" -"n!*5q","","EDMS&""","","T","" -"+","sW`EfAJ#6D","","c&","!RG3mS","}a0qwjc}" -"","4","joB","8BHb]Cn",">`" -"`Op6(CdKN}","NCH!P0k57","a|!O6*mi","U""%yC","""0G~","C}CedO" -"\zz;0","ZJv","-~<}re","","sa4mu","!0" -".=O6.P<","?O$ss","U","65D>X$\{t","F>V","|VNqC" -"l","U","3%lv=e","xNa!ns","[rux","'mX~o" -"!Fmx4V","a.ik","1q","l6HzDDl*)","Y0vi42]`","&Egq" -"Y","r\N","FF","|2[;","ez0H)B","^Fj*:_" -"pI=","",">[9vK","a","","@,_IP=NR\" -"A/x<#","J","O","h,R""zSi@y3","QD:sQdEU,",".W.\V4L[`4" -"M3^F","0""CD8-xd","j!W0","XX;","j-u8OrgGw" -"lt2cth^R","","8>***B4o@'","_4","Ew? ,","3j}qe`" -"~","BL ","f","o""r J","G^f/,",";)^7" -"q-;:D@GkwP","tw8O","|8&","sDgR","w{eHl=Sl","$}" -"hlJFk"," ","\ uI","8" -"J#;Q","\N/h{","u(eznIXEoE","?%g ","myydNO","cd#L !" -"PrJ)T","v","dh' $>","U \-@~m","nG","/yk" -"$L(1;",",-3n}J","a_-d,j","3","]","!mY%jv2" -"&'Ry",".4","",")i]rP-","4","$""C#-QF\E" -"L&AIt","VD]\B{f/","`is95zj","}F8FY","C"," " -"*(]e(1P*O","3VT","P","}l:>s" -"x!?LmJ:sV","^Ce@DW4","5E(3FFNc","tUd)Om","RICDpT2!S","&Th6m)W)","?veTq","DQ;""_}i=b1","'g>" -",miP3t#_","aonhrAdul)","""Mra.","&c","","DZ" -"J)PLS}XL","jf/&)\qNZ","Vh","r=U]GS^","p<[hHDj","6(@>.O0Y" -"k35`p%+~F","Eh(J","7hek_","=l]$?`_","O4Ue~","Ob98qPm0Hk" -"h","~:l;}v","(","Q>!W@","&4wfpNAO?",":hwj6Gb{IZ" -"36jjAw","`[w","(,wN^x;j3B","+#vX3wFS","","d$3/?" -"xiB","T#rzlr}","BSB",")","o_2m2+Bm","~r<4eA""5$'" -"H ,uT(.","f","qdyPW(jE","xXn","","|RI|" -",'K","6.o2eFt","","=?7Qfe6","`zO?","+(=x5dRi;" -"/iON","A$","^X%l4Pj$","E$f","2","aV" -"2-#ki<8(.","/h{4}7joC","gu!2x1","8(Lf4nv","<","8_R%H" -"d}{f)k]dy*","<_)flkg-]1","2/","MIE","=R[C^M","(VG+" -"L2I\> ","Pb\I{c""Lf{","s?z(,PK+_","Dg~,lDuX","Tx-T","fG","bf""H","[Z8I","Y[","+D3^" -".6f","y""-(o","Z#++~<",">9V>`nPz","-nxW^Mzf@o" -"*,_E*0","<7""j&[O 8","#k@)>,o-","Os|","][3",");n" -"","%p?D#5;","]r@r""IDmD","gg=Y","f05fQ&.}#","it(M+o@_B_" -"hgkeED","uHh_ra({",",(","/","$J#.>7","$K~3" -"","=-GsX","kTlfow","&k|!","lbUA","-'We" -"{j##","?2^-2`Z9(d","13|x\B(Lc*","m9*B#","~7hy","l!q5" -"TBnW,\Fe","uaOtsq","^ID7rR%","2;+wvT}c/","6&&h","59CVp:" -"=f|~+H?t","5vPij","~;e","z8UWCu$\T+","G","E>H0" -"1Af","`O&J7","",")_n;iJjz","g=n^hSUF","mk0;3(VU(P" -"UXiAM=A","","@#","xtdXe-aOgf","_Kf._Uiev","FmUtmgr;" -"TePh.P","7AV_jW","","",";8gh<+e2","" -",nO","ZPrOobHD",";bvU(F!qXr","0W;t$Lb","@","D>4K." -"","5","(L","UC7q6""""4b%","C","T54be4|NQ-" -"PVYwa","1H""d;+M8O","","","40GHJg3%","O" -"x5GywI3t","*%","eUJ 9mY","tyO","6lKipQi","_Z>PDO|" -"X*j@","","E","hs","~X_Gz^m",")" -"Ia}P ","{g","-","hzz8[vQ","jB\X>#(+""","hDy" -"Dl_4yHk^+","JPSQ8(cqSq","H!5{sSA]g!","Y,:f*2","cD{NE&Bx'I","" -"","RjIaNtq}^3","Y","","RyAvo","4=0ORc&>4," -"B5","","*(ZW","","J0Yd`","{`zwlvi" -"qEy",".'?MP","""})m""_","""4jy2)ac",">bR,ZDUW\","tP:.z/g" -":F","m8Ema_","",";B\JGV<",";O Nr0?Q1""","4:Z}" -"=|r.\rLwL","0YN|3|gLr6","Xt>K-'L","OC,tw_<3~:","",";dAJ""^QO:c" -"{","]_r;","|",":jqsuf","k7~;V","O" -"od.6I","rK(#","C","$&Z","""R4n 3?vmC","z~R-B.Z" -"ODBbMnT","{",">)LQ","#c,K^7>#","BS9]","eC""" -">","Lf!`","@iL","iVb@EPA","","eu" -"2\*u]JVd3","x9w=xu","_`[7#8XRQL","Yp","~y6P","@W5v`\jRy" -"vh2","I!","A.RYB(",":u~*oxp@2","P!q","[.(F'!R" -"N?;4bJIYx","j6TBnK","","D6CY5","","D" -"| 9${?0","ZnTB2oM","}kq","e9UEGq74A","~","UP^|7" -"*#/)","oW$F,wL$ ","","z^C\N:","","lIX" -")!lW53=$C","!EJL8==","5(","W(TR2o","zsh%","Q!lb. mCQ)" -"\ZV5{gFy3","k#xTOtdqCH","Q2Q.\iu)","NF%x(","M","o)U" -"zeA,5G+","#Vq=K)V","!" -"{>^@j","iTEL|","Jy-_SU/'wH","@lh_","]+l36).","csK0M" -"wQL_","L","Kpef:","Q<","15ho( r","c" -"^(pD","\V)%","uV# H&%)}","H]""#_GU","Z3^A","1""" -"][SBqb2","sm","z","fj]/9","","@J>J?syq" -"$>pc@U","Pc\",";+m6b66ydo","+?jE`.\Q,2","{iQ ","" -"63HSx","'_+eJi","yH",",vWtmMy","Y","xgPWJ:" -"7l39<","","#:SBey#j0:","n1r;{)","B_0C","" -"d","a{eaS _W7","[pZC}4xc","mx^&c","6Xg?#e","" -"Km","x","Pkc*287","60=J","$ciiq>0","aOP","DyL{VAko^2","nCx;^4" -"""AL/","Kvu`","?Ri=~\","Uwo)>>GUx]","T","PUlR=uXl" -",-Z}","NS%s7YSmx","M\~G!8","Fa6!RE!%>","]","AC" -"ITrnuX$z=","Pd ","TN{P]Qx{\R","%""FET","cC)muo","b""7W]?#K" -":XOT","J""mLD\S6&R","]F(XDy%V","V","[72S_fq","SUBd:2#;#e" -"z1e4l4","&:","(Nn","Hfu&s*","g{[kt$5r ","}E" -"","`KT6Md","JA~=","E^[rtkE&","q@(|","&" -"4p:C7+U","(","|P\;TB","/R4I=]i!&","<","[Ioaxib+s[" -" l@i$?","]KeEV{^G","c/O",")xU qf'","JkR","O&\" -".;F9","-0^rp","`n& ","0@`7+!&xS","._fu-y]$N;","i/" -"7:!%75w(","g&W","0n}}h","GecP&'P","y/.WA,","GMh>f[Ub" -"'|sUuHc@","|)JV07o","}>+;p++m3o","1z:],\","[PL","" -"xS6&\2F","vZ0jhcZdOM","$","","C?$@6 lx","cELb,/QZD0" -"6O'^2X6>LY",">kDfL{T|Jq","rq3wv&m#|","GU ","GeD0$","H^" -"$p'v=","","77#","vI+TaW","kU","vn" -"Z3kq+","r(N","ovb:xf","Vk",":5&':7iD",",c}" -"'","@v""","pC9[X#5bLZ","cannP=c9h","6'L0)#KgA1","uFTUYz" -"7y\","Fk'","e~0ucbP_8","}1!ULDjnev",";)J(L","lz" -"eZa6Bj","NY>>E|s=:n","08","G"," [^i)$'","p9wy" -"j'FE"""""" J","","+Y$YLVW","UpmY58Z",",|ot8" -"SY","*:i6","K{RMh8d63'","40SGVof","{~7","|]LrN\)Qf7" -"[>E","s","cG","D6D0nwmqc","""j","QV-H+=!5p" -"T/4SaW","""O9w,|d=","8:5VZYfX.)","8;)H","","""BGhSH70" -"EgC23","&HG","tOT","oohuHfm","","f3Np*4V" -">-wot","pXL}l","%jI!-","q","43SyeR)","&>+V0" -"z","JSqgq","G)7d]o2r%","B ilJ.$","=HVoj""Uuh","ilIlT}^","y!R!}" -"7M+AnR","","V'r","n<(FlA","uG;d","4z," -"CJF|q%}j","<","CxTe)+!!K","rVVSu","-1 ""","t8)A$,G|R&" -".l","L`","ZHr,(","f@DS:",",W))Tu [}","XptY3~qqvx" -"=","sY","tHQ1;~F$",";'|usG<{","/./+","H;PD0x{" -"$T8[+?Hu0","F'RnV#NHX","","+d{I5}N(i","ol?@","s0lmSM","w","54VRh","W?c_" -"~h!,Sh56D","Bl thgiQM","{t","_k[nc ","A9.LZnVV","FIk$lY" -"vmZclCsBW","e=C;6","8{/x0Kzl4",",6fi","v'`ib;v^]","JZ|kZ(o:2" -"HkMT>:p_","c*WOX$i9","?","ZTjO5","J704GZ","Hgh!7q" -">","rk C5&Vp","0BK\]{n","px!PiJT#E","q),`J","0@f8[e=#" -"!$d","y+.<&l","3!]CAr&jU4","","CZ3v'/6;`","N" -"kovwCdyB7S","P","t|6&hRY","@Aw};","^:Izb=vz/n","s1{_" -"]Y!ZV&r","Sebf","x_<","Jk0(","SLK","j" -"NbR]CZ","G}q}}i","y","T","vj","eO" -":Oh,{`W""i","BeRUryiixF","U\]d,YB",";8@j(]zX`" -"?M8=[x1:","z85dvP,SzL","2w2E+mH7S^","W","""\$5qvEF~:","'""2-wPFw0T" -"CG","<2&;!sbo&s","g","D=d(t4O","qb^&CgN8b-","($ea[u" -"","","j&","","#G&+s","FK)","2Wogt","e""(![yggn ","?Po\r[7","s[{f%|W","" -"jpe@V[(;0","{,","~Tjexb","*u|jJd22(/","6v>w",":","","]fzYg" -"yk?t","O)*'%fQPq","nXWAcQzL",",++~/VV8",",*he>{","7znYjvFD(" -"H?","0wH4C%z1be","}\p50","P-xf]^]-/)","XP?[q","ki%@DJC(vW" -""".","HE X)s","{.G8p&","<^yu_9#=g","]b%%-9","QJ2MWO<=F(" -"","|jvCB+f45","'/""cSFn(=""","6HD2","=o=","F`HNwtt" -";%VJ:V",")kd+esu~mf","|N]d4sT[i ","bu-l",";kV_K$c478","98`" -"8Y#]7K;B","#DRFsv9","a","a^TA]L>Le","Y,*Msah:W5","xkKLzd \n" -"@gx}z7%y","6rcx'WpG`=","0/)Fe<","#+A8W","SM<*pl(""","xDg " -"2I?","WU@","+.","3 t","{#_","hP+8D1$L" -"T1Z%)LG","Be","f+jpbEpM$X",":LS","-'N$B1","r$;>h?\" -"","@s","O9:B","'K1{{","oj+19Y;Uc","" -"E}~!z","_!2~K","h\#x^v%","LpxO","Y$","VByN" -"%qty&3IdoX","q","{5xS1+@yC/",")%pPx","r$fHG%Sa","" -"Xf13,Mjp91","(C7-p5%sw","@D[JL","MAmWJ","","[u3?^k4" -"Jv","`F?U","92]","","B:","<+sO|rg" -"\IO9lcAD","IZ*VuA}qxb","2\#zO","F)%d","3D~HCj","bs']M" -"6d6V","""","z%Gc%T?|","^XUF{d","wj}Rrg\Vl|","""/fQE+0J" -"V-()QED""L","1y","r","UDV","3Q>0GM/","","&pR{qC$","=q","Qfu``/","#yA" -"W^R&4","+","_HHxJRL","~+Mn'|gs","9[SQh]","wNzqV}" -"*je*sKQT","R%X^S#C'q","""","-f}^2\6","B!+u%gd""yB","m" -":<","T>hT",")P6Y3","C>","c_w$MySQF]","N" -"?\)H",",!I4G}$e8","rf4|=q'E/Y","\@v=V","cAi=a","3pJa*[d-" -"Ocn@Z$z<+","?k","","wD4MH1xc#","_byG$% t","!C6E=``@*" -"w6$B","p","iP-","]cj","hQ!i<)","erz=2C%,." -"Nw7^`;#3b,","JG/$N","!aAOC","\","~mZn8fb<;l","}UK% 8QL" -"^y!ZvbA:B","Z4mZ4riw","X","fzgiH^~o*6","E","J.&7" -"@6H5",")","d","70CM","y",";xQ@?" -"'","akxgy>NC","UJS","fTSh|`S","""%""isoHd","" -"pT1@0=x","p,i","KBH","w;KA->iE_G","ZYm(","+k=4D}6+H" -"P4Au+$V7Y","o;%>E8","{5",">","\","" -";`""H,v ","","0iN)","qWS7\Hj3R","U:\p}]","0*Su@" -"YBik""tKo","Wv_S4UZ","q?-#[Rf/vR","G","+(","N$ea1" -"Q","^R\Wx","{","`;$$R;}uX","n@nz?6u","b],'zR)P" -"haBm:",",1$","YY","","E71%z","" -"@","Revc","4sScb","fy4m","D=i?10X}P","|rtBrZgcoH" -"8yE4fWPl(","#eq5`.Tj6}","[/)","","","7F*2Q-" -"((K'Rb","hb,c","@i7KT)qJ.!","&WT1$W+","o","=NtVGs/z" -"P?I#!{k","p$","","?Ark)YUq6c","&NTxt","" -"V","W*M","QhXxQ>ZM)V",",Ss^","mwUN","V" -"L%pOM","s&%""e","|.0","","W;kC`a;^""]","v0x2s" -"Q","PHd;","y","p?)ofu BHd","+8","H6q~M" -"f^","9/","[","","9>3MClis","q"")@Xk$^c%" -"PJpB_F","T","","z;8\=:5LY","!)M4N_KS|b","o" -"","-rMJC","A_7U}#H","""cG!NW+jy","x+{A","UiW=|*" -"M","3p","|js&~(W""o","m^o3","K:oWx'}7FF","vKpA?\WR<" -".Z'Toi3.","Kw3M5'","D{)`J|p%","y> <",")Tzjh?/5;""","hkHe2dg/" -"F","","YQ#bjKU","|oyIi.Pn","X%lZJ%G","4p`$gA" -"0<3BcF>","mj#?'cr","?eg","?c\gBxpIL","C^V","4h+/}>2" -"|Gyedrt","&U8j-39",",o{OR|#K&","e*+{TfoLt","7#EHo&yK","-l),i@5@sy" -"( Ru-","8YA_.{ lbA","B","?1","Y?q18G]:","F" -"`"," {@yUR`","N%sfb]._}","2+H","a?(","~488\v,~" -"e%}/?__","1m|&Zwk\H","m;y0J","A?&_bN)","*{YY4g mOv","" -"+o+1~PR^rP","9s:","9[","{","","vSm,]mcX" -"v:Uc","","zmyt","D?hl.Q)_ ","M3`U&^u)","'|b0""I" -"$!Y","`l[rU","P*SX0*7ews","E4tEEV","r4Mc$i?Y","x`-43E" -"XFz","ICV{D","eFh{;F.:","H}L(F8","EFq","UE:7WM7E^" -"","","/",">mK|c?""","fFRcp *!","O" -"","uP4b","","+","WYE+","@*|s}GBT|" -"-1|U","; 0i3FvNS","","U",">Z?`X" -");UmU,5L)U","X0LaKcd","o#"," #f5m]7l",";Vk[u","YD~H3]4 f^" -"@<^43=q|","9J3_Px-_),","(B4s","H'E&","Azb3n","nkLm%@cG","%yh]S\#nbQ" -"yl8%1 Z<","%3+}","|ElVJ","qYMWxz","-O2E","z" -"/~Z}]!Y[","^s>/.","]r*","6G>","W4-","P9%%W28CrK" -"f}hbE",".{w(H.sVl","<","+yR;f$","Z_R","" -"@""^=KMw+&aV=","q>SH7K","X=","YrPU@Qz}" -"R='#.C","%V7VRk","m](y.J",";","],#","/" -"j^;6V","~aY""ZoE","-4~pU""q9 z","Hfy't40","%k$!cb","` t f" -"T,Oyx)dBN","E,","iOWQ`","qUSyf","Gic)PI\","4" -"","b7'-v","mrR""[}68,z","z","1~LW","MEx0E=" -"$7","=","OPyk[tg","T|Jv>","","|}MiE8*\b0" -"xg?ey","r&%L9`&O","E6V]U","Uo^lp_","Jd#","JHC" -"75r","Dm8""","PKDe(jR@","Q|3]{I6,~G","g","x_Z*}" -"+.+",".Q"," H 4&i"," r9x;]?","L$FE#|b`%d","Km%5*Y,=Z{" -"t),H>6^mo","T]v|=?W)","Gq","U6m"",","Iv M","STWnPUuF" -"S_28GTA$/","cy_m(3?.","%Esg;RS^F?7" -"*Lm*BZ;1","","{}:4[j.","","T","w2e:|" -"bn>vI","1wRW=$#hBO","l","aWbEFt.","geWriwB",">" -"[,2""N3N1F","2np3C","F>HPSX","P$NF3,N*\","`","%." -"j*@Vckc","q:a","""5","Au!lI)","","" -""," 2*7g,dY","","zcz5O","Jd4",";Q~U7" -"'r`G","0:-l~C%",";{6bGzD","fzc""w#+2hB","5 .ec",">L*.c+9" -"bLrT9|.","&","BuGS3",";W-;+","","Y8Sg<5AF""" -"t[){H{C","7P)M"," 8J","]G7","uxnCx72","#" -"9Jpg;,S40U","7~,]","pB9I","""F","O","KX<" -"S7D+""Ct?,m","K3y/tj","","hBn'@6;Q","^*,@fi",">I)JxVCGjS" -"",";IC]JIRG","#@","K","!`wp","C#!Lq`ouC" -"\D&{2<7","A)vo{?u","",">","][F","|" -"o,h","VZ,L[K/a?","""%","q'RP_={?","}L]?","/f#W","Sgg`" -"xjzKX","","FgVv","UP7","D'","" -"&","HJot_~M-","Iz","","hl*?wrgh"";","r.j8*" -"cw\34","y%sO=Xx","!'1b!YH","KR","k?daX23=#","R/G" -"&Gd_P","6Uotb;w-","{f,ub","qY0#|=g","Y+@RF3~","yF" -"cyj? /&fo","(o!i","M}W1O","o!sNA[HtT","E/<%Z !Z;","gMGl#t" -"_FWHy","0=|0""<6jb*"," {l7jc,h","","RE<0N5lhD","#msM7=" -"M","h1>y","Rae]'L6"," WxK$",";","WCJu" -"Jn<","Mx/~Gb]cE,_]F" -"$(GZ'.a@|","P*","F[R@~","QC""nTGV[|","Gqx","" -"B$j4S.QyR "," @ub/","Q;-{Rg&","=","mp}cqJ" -"K",".;p-:cf],B",",d.ahp?<","Ax2'E.d2bW","","""N" -"","tmw#$!W4","X wR|2","J","M`py!r9-","u*>" -"Uah","","^vGgUW","_a=","q","?8" -"Kk!","3","-dAP]|WlTd","2daZ 4$M","Sm:%J|J","|4.Y0]FG*=" -"T(zz47nK","rrW!","+m6&","$>O","*b?8/","3Z&0vdO" -"","""1#Wzhd|Q","znf","Ix^_U","B""!<","]" -"YD","j+P6yT","}qELaS","Poi,1DYx5y","9","JAFlfy39F" -"""_A1","!Gz8FKW|Q","gK3xAlVP","h{[J]g","Mp^XsE={",",~""@{O=a9" -"|5}%ou{h","","rEt0m$+","3H}","/agu""#I(","ZA8" -"rTg","C|JOy""K","PAiUKV(>sm","9d%*oE't","k]xpx1C","Mn" -"FIFq\ +","t","","PF?4J","KiV)9rDEWa","&!!a" -"HZF`-Bl","*3T)/2>5ZT","k","4_=2","D*&","T$Reie3o" -"[?N1^","v_qS#k@5F ","","qQ=JSLC","g8J340B:","VpJ" -"#","8!o#?9fD;W8[&%","W5dd*H|","w!+N#2'Kv","","1z" -"A","?U-:+9$Ad","fgG(5$xfYB","be{|1\v+8N","bjh]GeV","U" -"t_q ho","IAN>9OHx ","Nz#i;","","","q3&Ee&4KU" -"k","e","1M@K)D","<%]x:","g@ucMqR","cEU" -"h2{z1","?4/","","","","a<(" -"","l2e6","CT","S7C4osiM!",">h'cl.t/X","yM70tqVeJ" -"5W~n0\","A:&{Ct","Q|~","!j?Q2-","","oZzcS|BOk*" -"","@m9W: 1PY+","d'","T6Z@XI8#","*C","{WY0" -"c|oq^LLWk","c","v~:H","TUjFM2W`#S","+J>E","fRd44" -"","-k_+,H-P+<","j,nLk@","wU16tnz","IB/(#b","],}l<","A(4{hV\S","EU1fDRJDK?","th2M{@VM" -"dd7'7F","u","ez[F\=/","-ND",".%{f|@r@J","}",".r4/b[2+" -"$","|Hl})","mAi^/0%,","","M","42Q1zn2Fj" -"","~kIZCuW|Y","@a;&\W""sVM","""js;V","Ty""-","udU';ci" -"","C@l-Xr=",";4o","FAciX1_.x{","","X" -"Xo=}hYQA","OdLO5~D","GOou_fr","%h|"," iaH)6MCG","Pm?|EKwe=" -"reI:O","rO0","geUs",")","$1'{C)sTH","dF^m,s48/" -"\`A","iCt&`","SQ%x4H>","S","eNJB","F$v=" -"Y4@#","Zx","b;Lzw52FR2","WR","w^21u"" ","c" -"vza hr","h*"""," vr","bu^3R:>tx","'","[" -"","nz(Mu","1Y?<","WY","^","+R.'D7~-V|" -"[","M_0V?}g","4&yc?/.V","v+yw""2N}j=","C","q^G*d<#cOt" -"","[G?YXdCQ8","y","gS2","","GK'ws#@b%","gz_L:!P\R","yjF","K+j5dt","""u*c&z","H" -">ccvc =Y=",",lfu","ob[@GMfb\N","DJYb*-`""","GZ`j?Um~E~","-j/Wl?!}U^" -"TP8ng:k|c","","#Dy[-M[0S","@b'O1rt","fs'","wm;YJd" -"g""vzo=7~j:","#C'.JT","af""<6"")","Uk8u","P00^t:%kI " -"fYg%",";'EvJ)""",",g1~Bn""","HOgS(*iG",",N4][1","`>Qg7'hb" -"j:C^/D&","L}" -"y","3KHns2","yphC]>","g2^","iz","c49k$t>%&j" -"_B5~%W4",">mYNa|)LQ&","|h`O3F","$c" -",B..bVR","PBp%ghU","i","6-n;|w","(?fll%2","v>\w7ZO}>S" -"+}8","+A^","=nEbX;T=kl","/)r.",")7R" -"c(TUPDMq9","S$Y","!{H","ar&s""}X","]n9^ynwJ","KA^XuPC" -"G=@{ |4tp","x","","Q%g'bU*>ko","","8/;_x!&d" -"Eye%U sT","D!vqs,",",/-dIm['","b$6ns","!O4&B","w2-FQ5" -"","vb","Ag","Iz","xAq","Sg/0" -"Ci","Gr_x","rZ.bLnI_n`","IF`&&","<\8x","(Dgo""","%RK>]iM|","G&N" -"g&","MuXViHSk",";sqY..}Qa","]x6","GQfWk/|m",">+{6FTcJ" -"","8eQ9T+","/#c-U[..","6s{","#_'E","56E" -"tJ{)","IazQ","st","}J`& ","pBX6d","7_yYf\kW}" -"r[d|?PuJx","","HHK1 >X","uC",">WJf}[86m","1#" -"FBzo 4PP50D","x","a5=Q|Gp","VMc1PB","0pWSRoYZx" -"#Oy=/-T","EN>S3I^","g`","hKRa]","tX","8" -"Lu&","SI","|>cxM(","B","&HK#%-$a1","Ep" -")V=@P+ ","bNbg","W"," R%w","U-;2Y","" -"kp.~#P{","10C .ckv|v","/rh_7j","vC/","Be6]Ah0s8n","8_s@]","K6EN","JIi,""bWH;g","IpmjQ " -"q3""hoZ-","u{(=+GD!","","","WWs","6t[" -"aL*GReab","g","]xy(zaP","f","=5+kzD","|l8&" -"{}Z`o_","2nro$","mZ Nx},it\",")^~O2Xg<","","=v?" -"]A#4=YjX~","","a7L","Vi6KCa","n-MKd{%^J2","vSCEKmv" -"Qr>","dK",")v`V\","|PyobH;`D","t4","$6","" -"\L}Os","PubF`","7\b?'p","","n19&@B","Q!vM5" -"<&Dl7![","z1*0","/C0;tyxCW","mx~",":lK38^$","w)F" -"=ST:y};;6m","kIja&","$h""nqTG?QX","Mv" -")2M8g\j&","9 ","M.SAW4:""","DX*%","LjMZ]-","V2Y_" -"X|LW.","","ADMXR%rM","+*","}3*Uv ","&" -"`<","r~%h:O(","v--@~@Z_","AWrujA%9ksC","@/",".^p(!" -"7n","[",">bsF","0&i4rnFUn)","\z\$-","5" -"s=zO_X:V","1]X","+2-=","","|$","F({0B9AzL)" -"[l^P","Qsnx:6'","W,R8qFs4X:","XE","B5Hv%u","z#>" -"DgGoEDI|}","#2eDN>0","a","I([""Z" -"O","E",">O","yia.Z","!W","Wst" -"rieJAB)j","Jh>X0'W","0%[Tbb1wU","yu\r!bR&oJ","mL{:Y","" -"g)+9v","\V7G","","i65","u1z""","mwMA0mduA" -"""] Mmz0x","W)Z7Z]I.","Y>Gy?s~","B^VT/[","!1Po*","GRTX)#s8""" -"Qg","D","f","F:;ZMn","","^z" -"m!N+>,G>","rBM)f","S/WddM","*7x~bK?~",":rGV#_=OLS","8*uQ,c" -"NV]byBNum=","]j?#zg&Q""","",",>WC&q","a&ggxHA","6","S","S","vB`v\$X\" -" $I>Qn(","F$vCj}W^""","f,m$(`","Tk&1","g","" -"F""r_833","M","","@:W$gx","rZdtt)y","S1 " -"}I+H?^U","@hvC3b!|L,","O","","nu z9>%#bt}","UnXI/vn-" -"@\6 ","z,fUI78CJ","uqM,A+|[~","~86o","Ttg\v","'W&'.!S|" -"(]+1","DWn>pl0y","5>pp{","pE:","t""n4ovOCp","q9^Nt" -"i","7XNxL.","Y@E2~}Ua?T","NvkqS;","b+XAuT~'tg","{IH" -"","2A)","t?-N)j","uOVY","&-EZa","`" -"E52JNq=h<","iPtlx","Y","","faS","p+Y""o" -"{$]","=S>&n","Q&V","IAYvv","2!_Nzwu8","]fB," -"i2=",">UW","","O{1@""V@nqa","CK","T" -"U%","T","F","-93t\^>","0","Vd" -",m_G&8""\99","-H`NpPZM","!8TxU^","`CKOAeq","T[lX&""4","" -";=t",":l<2DTlM","e7tof`Qgz","pm-xU:m","uffMI!%R","6.R" -"f:^k|)$","8j;c SN""y#","::[^#t%flO",">y[","lYlT2Lh","x\|jiZC=kb" -"$OEjV!'","L","iJbq(/t*P","cn>p<|O\","%c","_&IRa0L" -"""","uq3"," \_u","'+.z+'EcHU","+vjE8","" -"@1Q(^","q!HDi","5 J)22","Z","Hr_;WiMS","6q" -"w$(iH|P","]Q","q86n/","aemHb","S%J3,>3","j\>9/kZw" -";F","*4","[a)2;4","m;*R","B3(pp%","|s6C]" -"N0xX","gK$48$i","" -"LC4Mu","","~MP,U,-g6","0x ({8G^ ","","tL5zhiA|Q" -"drlO","$F\3 95","\","w^n( ^","","uA BKj" -"",".^p+]_D'C>","(r/=:*","(&*#7a.",""," L 2" -"b01`BI&dTL",")$Qd","","YKZw","rs","V""q?W","r]\AR%9eQ","ar2 (X" -"""Q*?lEd","Mr7aZ",">y76","iK","is","wQ" -"_$MJZ","6""%\-","Ea]","$","","" -"\p","vMKAJE(]","Qv"," ;.+w5$","_I~","<.DUo/H7" -">^{","afP:>acaF+","&)Cy","*nyEfBQj;:","EC?=KR}","+7" -"1&vw9Ao","A\C""2!xul(","1|{mLsD","9cSg)y194","WKTG'_!c.",";x!?YBad+" -"","","sN+","","w5w","8J2w6cg*" -"E","6,WQ:}G-H","","1C","5","1" -"cMk","WCj.J{:","f=&4","m^3^","RU","" -"b-~5b^P-O","p%4XTKcK|","od","b5",":",";L!q" -"g\T&","X$vd[#",",PM""H""","a","S Ja]E","$dJ]8" -"<'e#%x","n ","""nfqy","2U","O","yY" -"]S","m","rwU{1YoBts","hf7~qC+,wl","=&P?","<$g" -"K6s#","s}Yz;","aw","S0Gl","Y""IO^C","g[Dj" -"1UuW?- ","lO\","!W)kOeH2OK","","QV!oGl I%","" -"MH$ 2","",">DlLK","[U<-0","","g" -"KZ","","CL&e1nnJG`","e","8&[8\X>a","jJS" -"Z",".3eN\","eW' tBt","+u}:=X","UxJUIs~W^-","TN" -"!mMfsPH","HFNPePB","","Xs^#VQp","K .>g,N","> *_d-" -"{)]y""","(]Z4%","?HL{,#3[NT","","|)-Gm$DVV","" -"O","","vF2@q""","i0","i-=/`k7","yH)-$h0B^" -"F%mKA","O#Cx:]t","J~{,V&","Y^>""V*","v/QKA""","VU7FI0" -"Bt<>","3NNIq","Xb2""\ju7nm","gnKl]","jk3(`l","'\>t vwR" -"ZY","}[J))N%R","","","4Wmf","RK" -"aH&Zd!KIDR",",?ZMK?&v79","A","h1v","K%9~""Arg8=","S&","Lo|8","yeP$IgQ-u"," 2_2}fw","S'CH" -">f{Y","Nk","5q[EGW[","M[n{V\~","_-'b","" -"u/3S\~","m3j7:.JjF0","PH3(yO4@ k","^7?M+m\-z,","K",";<|gC$=Qo" -"\mjYcUz","?uhpF1N=ao","","{F`cP","hzx$EV1h","" -"G%"," OQf{?UJ","%U>>w[[","+|W)E:xR#/","0","QRp" -"d9J;.(VV","G{MC5","^z","}E|p[","H!hX0/",".')Kn#OU#u" -"ym","CnD=D","w;E2:t","g728X.XV'","Od4c:","[uq" -".eDWg","","yw","","H%u","+f!>V&N3 T" -"pA5""(P","t+","","L\36R","*lOLX8[>IY","#&""B[K" -"3\$Ho","f","F!sU!5JE!","baZjc","DVFA","yH&cnw{hJ$" -"""= d(Uu","6.8","QtHv","wltHY}kP","=?F"," /AuX","u$jdzb","sl~BeJgoZy" -"|O>nuG","={.Q>DKh","]o5","x-I\J","]fv F","61Bh7S-" -"<","ZU}""6\",":gR","Y""Fr-tK","Y90yK","w}" -"""EZ[,^dq","u$K]glZ]fX","o""3 b","7,DfS8""EH)",":[XN","rk5~" -"","^:","8.}","&C{,B3)","{ Q^66YOjx","N7|hz+J" -"')Gi(QfwRV","s8|tR&H","C2L>r~'Q","","""g!8WWDydX","_8)v3#QZc," -"UI%*Z+EaC n/T*0","5Qj6q&",",I!9Yf)-","O7""h$","cc};2[W/H ","qi." -"#f&Xc!8_","L/+Sv","-|N-OTo3j",";G]R","9zhHI$[(TA","?cr2"":" -"+(OUct<;TV",".U","imZSB","6bZ3n p{",")w3|,AJ7a","pXq" -"tZZ=2tjg","s:KP]","0\I","hCT9Y","}1k"," OHsW" -"","","ftw40/,","OIT3z2yp&K","","lDAFzM6z" -"","x:1FVv","(-i}","6SZ5`FfS","*$yk","ST%Y" -"B[","6`u{","c","mRb5" -"<}CxG","wdS1l" -"v!K&ctH","9-tk","L8DKsx1E3*","YnI&&(!3!D","dfNpbnp","$Z+TlYK" -"\Jw@%Z","8>Rv?Vamp","&45","0vJlPq","@|-6 '","i:" -",B4E","28","8Ese|pGFa","s","/'up,-",">#In," -"Dcr)4'KN","","5`kb$","'uMK;X",",H.","_K>4}eq-" -"<\r= ,","/MT=6","4Zf5Y","q","X>hcA?n","xL}u%PV" -"C","T2OE","Q'ptg={w$}","dolr5\","hhG}l0O5]","M" -"","P8$","6)Vfr<","1sF)}?=.mf","`7!Zb","" -"0)","h?<)X","|9xwC d","uxU","%CXVw$U","rTFHWR" -".4N(h:8","","ykN5F5bg%}",".@M%t","KP,""ZiX","k?e\b{E:" -"z}zN7v.iJh","+L:BY*'","c","YN=PxD]e^","x[i'NTtO(","8:*zHd" -"xw","o2m,U=","Y","G[Z<>ln","h)d>","/cre+6}" -"xd","OV4.,#3d","KMbT7Z","Jv ","mW|g-/c","fJ\" -"{<|(!P","""uP]z;io",">39","JC","JL*7i%1","qy%n*!" -"Efqv+19gb",",^","(""c(","]9ZhpF+""V}","&lf=cA`~o9","pxnqY!^" -"P","n?","/~QJg`$$6","7XA","j!Al","^7mg" -"y0/Q,pS","/G|FD&=eH","LZQ4oH\{F","i","K!+mU6","u!YAg4" -"=|-f<","=cpf","F[bW&=x?","G1s7w+","0[e7Lqk@N-","Q\p{vW6""O/" -"pG}4J}##","/nU56phs","0%wr%^4,S","""","cJcRi.Sj)","R," -"G","","","(2_l|doqUR","F0FF",",F^" -"[#*'","/rd","^- +HT\!&i","Av?t B","z:jL|{","/" -"bC'W","r98","Fy]","}\1+rzKI_W","y","9T{vO" -"-fk0~4","CV=a2sl+$$","w$~I\T","{vRXai","u","WT" -"5","0hY","jhk}#Ef\","m^tv2*[(","7*","}XR$AM}Io" -"RPk","","yAaG","t1","hNzKKop","{)RG" -"","9c","}EDN^+","%l","2","099=:" -"T!A| }RR","$G^u","a]J:Z9S","A2md{@7b","%>K*afb6O","""'W ~Rtz" -"!j7@sH'S","]Z","`tCQR}","!X'}(2","$`)J^]","A4a)" -"s","eXI!,AOn","=!e:D","96","-4&","B#wJOP" -"S2 ","ae!rLIGc3","_.<","@lkfd9:R","mFw#)s}O",",_","N24NK","^9])","=Fl" -"mA","c-EU","W|v","^dP)","{P59","{n=OSV~" -"h&0zs6","@/,a9]sv","N~","J\R)7B","^A|U","2f~l4w" -"?Z;cu.","1?#^eU","W^otY","A","+JAX","LA;","t" -"pexG4{","m([kDbr#","H)",">S}n/Yp;","h5gRE","gZr3ll8N" -"lEr","5_1fz@AUi","MC","=Wy5u%","~","{aeJEa;0 " -"r","gl-q{~","Ar5","#}1Ks!5'K","w$FC","r5!" -"f","i","<-\[@X4l&","tJ#=","j~SZU86,B,","Cd>9s\~`WG" -"qV[Nvy}RGz","AahvU-I,A6","s\I`;GQ5b","G/mPIa1}","._m","E8aZ!lZvB" -"YZw-> ","{}UK","kzw_9g" -"vuY\7!K!Z5","qE\?","Qi;","Gw6NLB. ;","","b[kw:M*" -"b%","G","xqjT+B>7M","%IR^pRgW4f","J.@V;p{","vhcJ+V" -"h""","K(R=SZJ","^PdEa%^R","@S#oT","T?+""","$C#;o]94;" -"9","n","=fl","P3j#(m","EsdN","60""oa" -"#w*l","CR","GPo""","J""{nZ C;","nKY:P8",";t'2" -"8OU=`","n)9rtw {>","YchZ$dap","","tnnM[8","P" -"Gc""","","","J'Ws","n0DRQ4","FY=Z+" -"_c.bgs","L,m4lr-","t""!zm+z?","","""","RGQ||" -"",",Q@e","k^N@5Q(1z`","","p<9C[",");{<" -"D-gAzD1UEp","b","e.Q6>W","@","UPZ`MTb} ","" -"%A T0[XV""","BK/@?$2;","A.we","mq/uaN~","?=h5L""dLOw",";.n]mr1" -"K","z/HBrSj","j/f","-tp5","dk6]l;tn","vEf2q" -"`8F|_1l07z","!T?","a34HX!4A",")?Lj3Q%,O" -"XbA~","!(0VBdS\a","y2+1","","se","x]" -"j8|","zpuMz","Zx!.^","pE,Q","+","O1}g&" -"-Rj3z&","22","7fgr'b","P*YUXoGzOL","Id","7@6('" -"","~Mn!","zU|U","","mK*rw0","r\Thj" -"'ES^=V",";%~E$fUc","","6/J?","7p8V+'D""tQ","WN?)|9;_sK" -"X?fx""","","","|","[FCyM8","lt1" -"Q:6_NG8"," M","5vO1~","aeL$>K;","({0Y|gn,N{","#[=F0@" -"ws'erE4myY","0spk|3QK","_pIarmCx","nv0""H$wt0T","3v#P","4XJn","a7" -"*+EH4D","8Mkt1D","","y@PpS\D","_>$GB&","5$Tc5" -";","D","|","u}O","NQ0j}4$$","E@]`z9'(" -"Xm^S5rm;%<","PRd","","Fr~-8","cK2P%-> ,v","","1","/Uz" -"(82+{8",">#6f","~%p?+","XGib 7QuID" -"9o:4n[7(+T","fWk~}<|3",">","b","G/E?F","UNpx4Z8" -"I^]W,M5|P","C7","TbKD:N YD","R|'\%","`Ub!1","{*f" -"H",">=","Bu|wuQ?","HegP",".A*&4x<;&f","FLuhcOB" -"e!)","JB$Q`X0F","M","8bg{","_ imFTj~~T","HW:" -"M*x","% +x:p/O","","K[^P&Ux'","O=Oq","k|Y" -"","Cp.a5!","0gg__6G/By","L#(CMmio","",">E""K" -"#`7=j","J","","","L>$py/","R)q3CTl" -"i.","D=","%_>On#]B","" -"","1jfHH","gI/[Y""","#b!k","B|^4#DNcP",":" -"","_wv[3","nC~TVh>G2","YCy=uRO3/","0%++b[v","3dY=AF" -"","bd","D}*vg2CH53","-~","-7i}_U[&","f[O^oGx" -"!/m `(Q4#%","^,ghnAK","ry=md_r<_x","6!+zjh@h",")/","/(" -"(2$S8","=''0]","","","u}+G=f5`","[@-" -"2Xg" -"","+N`#JAwY&","h&","Dz~~8N]7H3","(NCI>jmu","aHy!P9/%Ba" -"&=#","Yq'W","'M6bl","nuUh>K",",M","2K_eT8YS%" -"1""[,8H","jT-)","J9ix(RDM,","}","ky~,'m Jn" -"$","","VVjnJ","$z]j","Bo|Mtn(wGo",".m" -"p","9JK4",")Uo|34ojc","","I\wE{","b\|H1w[iR" -"N.r}Z!","YsgQ","G3cUX","Nby5_",")d0","9([?z" -"UIJO~cs7","[ZHr","l')h/G~","83M","n","f*+YXo","XRXcm\(^+|" -"LS4hCw8","}`uu{ ","O>r v#!",".8","e","RXxFv" -"","4F^nM}n*","'gehCKSk2J","\JY]","q","s53" -"V~F.j,j6t","nq","{@\##Ol","","ij2","'k^t(b" -"0","J/6[Jf%j","?*wS!|_44","","CH,Z","]Y*" -"s~%/!UT","v","6..v1","H,","7zuN""?8","Kmk1D[i","?@j@p~-8Je","p90y9 Iy" -"","Sk6|vQ&lmF","8H(G'&5Ts"," !t","\/Y","MAJGBxi" -"|DkfGX8\","4-o=R*B'L","mp8G4kY","7O^S","i$5MQ=yt","Ab&U","g`etY{;H*)","","Xv","q","6ky6" -"=tEtq&","","DfR`2W","9NE","yF'P*P ","c" -"=B","O[7)3bo","<=7T3eC@&Z","","i","o.B4" -"?#[h/8\a","9f","qaG7p6x]F&","'gSreOQwR","/E }r9","" -"","","F+k4j]F",")O*d","","k7s VlaK)h" -"=d{jlC","B","tJ'^8nb","_""e%6,lU","0:","=" -"^v?bc","~<","c","~$u6I)lB","n","aF," -"s",":WMDU&[","&@""~+G","N","by","g0$LD;?""S+" -"M]",")Zz({Rw","eK*53J","S8vOP=Er","@EkS|8","" -"1x9b","No@","3a","","YQ?]XA#",">zc" -"?p~F]","JG]E}C","Kf '!P`4","(","sI""p=W?)I","5<9T" -"IEj+\]|r9","","+ uB","sxb","}.c","+BJ`!C29Nl" -"6zkl4=z","{\b9Nt[","R\=8S$","=bCc1","/","" -" ","i4m'$T KH\",",-[V1gyE",",3vPcP0","u","ju.sNB*Y" -"j)E/7>]l/n","}R/<8","[","[m90:G","","Mpfb" -"aW,7$7XC","q-","6Cx!yp","j","+kA!7g","l,2(S7/ev4" \ No newline at end of file diff --git a/pythonFiles/tests/ipython/scripts.py b/pythonFiles/tests/ipython/scripts.py deleted file mode 100644 index 4d6bd8803b9c..000000000000 --- a/pythonFiles/tests/ipython/scripts.py +++ /dev/null @@ -1,119 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -import re -import os -import json -import sys - - -def check_for_ipython(): - if int(sys.version[0]) >= 3: - try: - from IPython import get_ipython - - return not get_ipython() == None - except ImportError: - pass - return False - - -def execute_script(file, replace_dict=dict([])): - from IPython import get_ipython - - regex = ( - re.compile("|".join(replace_dict.keys())) - if len(replace_dict.keys()) > 0 - else None - ) - - # Open the file. Read all lines into a string - contents = "" - with open(file, "r") as fp: - for line in fp: - # Replace the key value pairs - contents += ( - line - if regex == None - else regex.sub(lambda m: replace_dict[m.group()], line) - ) - - # Execute this script as a cell - result = get_ipython().run_cell(contents) - return result.success - - -def execute_code(code): - # Execute this script as a cell - result = get_ipython().run_cell(code) - return result - - -def get_variables(capsys): - path = os.path.dirname(os.path.abspath(__file__)) - file = os.path.abspath(os.path.join(path, "./getJupyterVariableList.py")) - if execute_script(file): - read_out = capsys.readouterr() - return json.loads(read_out.out) - else: - raise Exception("Getting variables failed.") - - -def find_variable_json(varList, varName): - for sub in varList: - if sub["name"] == varName: - return sub - - -def get_variable_value(variables, name, capsys): - varJson = find_variable_json(variables, name) - path = os.path.dirname(os.path.abspath(__file__)) - file = os.path.abspath(os.path.join(path, "./getJupyterVariableValue.py")) - keys = dict([("_VSCode_JupyterTestValue", json.dumps(varJson))]) - if execute_script(file, keys): - read_out = capsys.readouterr() - return json.loads(read_out.out)["value"] - else: - raise Exception("Getting variable value failed.") - - -def get_data_frame_info(variables, name, capsys): - varJson = find_variable_json(variables, name) - path = os.path.dirname(os.path.abspath(__file__)) - syspath = os.path.abspath( - os.path.join(path, "../../vscode_datascience_helpers/dataframes") - ) - syscode = 'import sys\nsys.path.append("{0}")'.format(syspath.replace("\\", "\\\\")) - importcode = "import vscodeGetDataFrameInfo\nprint(vscodeGetDataFrameInfo._VSCODE_getDataFrameInfo({0}))".format( - name - ) - result = execute_code(syscode) - if not result.success: - result.raise_error() - result = execute_code(importcode) - if result.success: - read_out = capsys.readouterr() - info = json.loads(read_out.out[0:-1]) - varJson.update(info) - return varJson - else: - result.raise_error() - - -def get_data_frame_rows(varJson, start, end, capsys): - path = os.path.dirname(os.path.abspath(__file__)) - syspath = os.path.abspath( - os.path.join(path, "../../vscode_datascience_helpers/dataframes") - ) - syscode = 'import sys\nsys.path.append("{0}")'.format(syspath.replace("\\", "\\\\")) - importcode = "import vscodeGetDataFrameRows\nprint(vscodeGetDataFrameRows._VSCODE_getDataFrameRows({0}, {1}, {2}))".format( - varJson["name"], start, end - ) - result = execute_code(syscode) - if not result.success: - result.raise_error() - result = execute_code(importcode) - if result.success: - read_out = capsys.readouterr() - return json.loads(read_out.out[0:-1]) - else: - result.raise_error() diff --git a/pythonFiles/tests/ipython/test_variables.py b/pythonFiles/tests/ipython/test_variables.py deleted file mode 100644 index 1267f3ed880f..000000000000 --- a/pythonFiles/tests/ipython/test_variables.py +++ /dev/null @@ -1,164 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -import pytest -import os -from .scripts import ( - get_variable_value, - get_variables, - get_data_frame_info, - get_data_frame_rows, - check_for_ipython, -) - -haveIPython = check_for_ipython() - - -@pytest.mark.skipif( - not haveIPython, reason="Can't run variable tests without IPython console" -) -def test_variable_list(capsys): - from IPython import get_ipython - - # Execute a single cell before we get the variables. - get_ipython().run_cell("x = 3\r\ny = 4\r\nz=5") - vars = get_variables(capsys) - have_x = False - have_y = False - have_z = False - for sub in vars: - have_x |= sub["name"] == "x" - have_y |= sub["name"] == "y" - have_z |= sub["name"] == "z" - assert have_x - assert have_y - assert have_z - - -@pytest.mark.skipif( - not haveIPython, reason="Can't run variable tests without IPython console" -) -def test_variable_value(capsys): - from IPython import get_ipython - - # Execute a single cell before we get the variables. This is the variable we'll look for. - get_ipython().run_cell("x = 3") - vars = get_variables(capsys) - varx_value = get_variable_value(vars, "x", capsys) - assert varx_value - assert varx_value == "3" - - -@pytest.mark.skipif( - not haveIPython, reason="Can't run variable tests without IPython console" -) -def test_dataframe_info(capsys): - from IPython import get_ipython - - # Setup some different types - get_ipython().run_cell( - """ -import pandas as pd -import numpy as np -ls = list([10, 20, 30, 40]) -df = pd.DataFrame(ls) -se = pd.Series(ls) -np1 = np.array(ls) -np2 = np.array([[1, 2, 3], [4, 5, 6]]) -dict1 = {'Name': 'Zara', 'Age': 7, 'Class': 'First'} -obj = {} -col = pd.Series(data=np.random.random_sample((7,))*100) -dfInit = {} -idx = pd.date_range('2007-01-01', periods=7, freq='M') -for i in range(30): - dfInit[i] = col -dfInit['idx'] = idx -df2 = pd.DataFrame(dfInit).set_index('idx') -df3 = df2.iloc[:, [0,1]] -se2 = df2.loc[df2.index[0], :] -""" - ) - vars = get_variables(capsys) - df = get_variable_value(vars, "df", capsys) - se = get_variable_value(vars, "se", capsys) - np = get_variable_value(vars, "np1", capsys) - np2 = get_variable_value(vars, "np2", capsys) - ls = get_variable_value(vars, "ls", capsys) - obj = get_variable_value(vars, "obj", capsys) - df3 = get_variable_value(vars, "df3", capsys) - se2 = get_variable_value(vars, "se2", capsys) - dict1 = get_variable_value(vars, "dict1", capsys) - assert df - assert se - assert np - assert ls - assert obj - assert df3 - assert se2 - assert dict1 - verify_dataframe_info(vars, "df", "index", capsys, True) - verify_dataframe_info(vars, "se", "index", capsys, True) - verify_dataframe_info(vars, "np1", "index", capsys, True) - verify_dataframe_info(vars, "ls", "index", capsys, True) - verify_dataframe_info(vars, "np2", "index", capsys, True) - verify_dataframe_info(vars, "obj", "index", capsys, False) - verify_dataframe_info(vars, "df3", "idx", capsys, True) - verify_dataframe_info(vars, "se2", "index", capsys, True) - verify_dataframe_info(vars, "df2", "idx", capsys, True) - verify_dataframe_info(vars, "dict1", "index", capsys, True) - - -def verify_dataframe_info(vars, name, indexColumn, capsys, hasInfo): - info = get_data_frame_info(vars, name, capsys) - assert info - assert "columns" in info - assert len(info["columns"]) > 0 if hasInfo else True - assert "rowCount" in info - if hasInfo: - assert info["rowCount"] > 0 - assert info["indexColumn"] - assert info["indexColumn"] == indexColumn - - -@pytest.mark.skipif( - not haveIPython, reason="Can't run variable tests without IPython console" -) -def test_dataframe_rows(capsys): - from IPython import get_ipython - - # Setup some different types - path = os.path.dirname(os.path.abspath(__file__)) - file = os.path.abspath(os.path.join(path, "random.csv")) - file = file.replace("\\", "\\\\") - dfstr = "import pandas as pd\r\ndf = pd.read_csv('{}')".format(file) - get_ipython().run_cell(dfstr) - vars = get_variables(capsys) - df = get_variable_value(vars, "df", capsys) - assert df - info = get_data_frame_info(vars, "df", capsys) - assert "rowCount" in info - assert info["rowCount"] == 6000 - rows = get_data_frame_rows(info, 100, 200, capsys) - assert rows - assert rows["data"][0]["+h2"] == "Fy3 W[pMT[" - get_ipython().run_cell( - """ -import pandas as pd -import numpy as np -ls = list([10, 20, 30, 40]) -df = pd.DataFrame(ls) -se = pd.Series(ls) -np1 = np.array(ls) -np2 = np.array([[1, 2, 3], [4, 5, 6]]) -obj = {} -""" - ) - vars = get_variables(capsys) - np2 = get_variable_value(vars, "np2", capsys) - assert np2 - info = get_data_frame_info(vars, "np2", capsys) - assert "rowCount" in info - assert info["rowCount"] == 2 - rows = get_data_frame_rows(info, 0, 2, capsys) - assert rows - assert rows["data"][0] diff --git a/pythonFiles/tests/run_all.py b/pythonFiles/tests/run_all.py deleted file mode 100644 index ce5a62649962..000000000000 --- a/pythonFiles/tests/run_all.py +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -# Replace the "." entry. -import os.path -import sys - -sys.path[0] = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - -from tests.__main__ import main, parse_args - - -if __name__ == "__main__": - mainkwargs, pytestargs = parse_args() - ec = main(pytestargs, **mainkwargs) - sys.exit(ec) diff --git a/pythonFiles/tests/test_normalize_for_interpreter.py b/pythonFiles/tests/test_normalize_for_interpreter.py deleted file mode 100644 index 37a2dce5cb7d..000000000000 --- a/pythonFiles/tests/test_normalize_for_interpreter.py +++ /dev/null @@ -1,122 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -import pytest -import sys -import textwrap - -import normalizeForInterpreter - - -class TestNormalizationScript(object): - """Basic unit tests for the normalization script.""" - - @pytest.mark.skipif( - sys.version_info.major == 2, - reason="normalizeForInterpreter not working for 2.7, see GH #4805", - ) - def test_basicNormalization(self, capsys): - src = 'print("this is a test")' - normalizeForInterpreter.normalize_lines(src) - captured = capsys.readouterr() - assert captured.out == src - - @pytest.mark.skipif( - sys.version_info.major == 2, - reason="normalizeForInterpreter not working for 2.7, see GH #4805", - ) - def test_moreThanOneLine(self, capsys): - src = textwrap.dedent( - """\ - # Some rando comment - - def show_something(): - print("Something") - """ - ) - normalizeForInterpreter.normalize_lines(src) - captured = capsys.readouterr() - assert captured.out == src - - @pytest.mark.skipif( - sys.version_info.major == 2, - reason="normalizeForInterpreter not working for 2.7, see GH #4805", - ) - def test_withHangingIndent(self, capsys): - src = textwrap.dedent( - """\ - x = 22 - y = 30 - z = -10 - result = x + y + z - - if result == 42: - print("The answer to life, the universe, and everything") - """ - ) - normalizeForInterpreter.normalize_lines(src) - captured = capsys.readouterr() - assert captured.out == src - - @pytest.mark.skipif( - sys.version_info.major == 2, - reason="normalizeForInterpreter not working for 2.7, see GH #4805", - ) - def test_clearOutExtraneousNewlines(self, capsys): - src = textwrap.dedent( - """\ - value_x = 22 - - value_y = 30 - - value_z = -10 - - print(value_x + value_y + value_z) - - """ - ) - expectedResult = textwrap.dedent( - """\ - value_x = 22 - value_y = 30 - value_z = -10 - print(value_x + value_y + value_z) - - """ - ) - normalizeForInterpreter.normalize_lines(src) - result = capsys.readouterr() - assert result.out == expectedResult - - @pytest.mark.skipif( - sys.version_info.major == 2, - reason="normalizeForInterpreter not working for 2.7, see GH #4805", - ) - def test_clearOutExtraLinesAndWhitespace(self, capsys): - src = textwrap.dedent( - """\ - if True: - x = 22 - - y = 30 - - z = -10 - - print(x + y + z) - - """ - ) - expectedResult = textwrap.dedent( - """\ - if True: - x = 22 - y = 30 - z = -10 - - print(x + y + z) - - """ - ) - normalizeForInterpreter.normalize_lines(src) - result = capsys.readouterr() - assert result.out == expectedResult diff --git a/pythonFiles/tests/testing_tools/adapter/.data/NormCase/tests/A/b/C/test_Spam.py b/pythonFiles/tests/testing_tools/adapter/.data/NormCase/tests/A/b/C/test_Spam.py deleted file mode 100644 index 3501b9e118e5..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/.data/NormCase/tests/A/b/C/test_Spam.py +++ /dev/null @@ -1,3 +0,0 @@ - -def test_okay(): - assert True diff --git a/pythonFiles/tests/testing_tools/adapter/.data/complex/README.md b/pythonFiles/tests/testing_tools/adapter/.data/complex/README.md deleted file mode 100644 index e30e96142d02..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/.data/complex/README.md +++ /dev/null @@ -1,156 +0,0 @@ -## Directory Structure - -``` -pythonFiles/tests/testing_tools/adapter/.data/ - tests/ # test root - test_doctest.txt - test_pytest.py - test_unittest.py - test_mixed.py - spam.py # note: no "test_" prefix, but contains tests - test_foo.py - test_42.py - test_42-43.py # note the hyphen - testspam.py - v/ - __init__.py - spam.py - test_eggs.py - test_ham.py - test_spam.py - w/ - # no __init__.py - test_spam.py - test_spam_ex.py - x/y/z/ # each with a __init__.py - test_ham.py - a/ - __init__.py - test_spam.py - b/ - __init__.py - test_spam.py -``` - -## Tests (and Suites) - -basic: - -- `./test_foo.py::test_simple` -- `./test_pytest.py::test_simple` -- `./test_pytest.py::TestSpam::test_simple` -- `./test_pytest.py::TestSpam::TestHam::TestEggs::test_simple` -- `./test_pytest.py::TestEggs::test_simple` -- `./test_pytest.py::TestParam::test_simple` -- `./test_mixed.py::test_top_level` -- `./test_mixed.py::MyTests::test_simple` -- `./test_mixed.py::TestMySuite::test_simple` -- `./test_unittest.py::MyTests::test_simple` -- `./test_unittest.py::OtherTests::test_simple` -- `./x/y/z/test_ham.py::test_simple` -- `./x/y/z/a/test_spam.py::test_simple` -- `./x/y/z/b/test_spam.py::test_simple` - -failures: - -- `./test_pytest.py::test_failure` -- `./test_pytest.py::test_runtime_failed` -- `./test_pytest.py::test_raises` - -skipped: - -- `./test_mixed.py::test_skipped` -- `./test_mixed.py::MyTests::test_skipped` -- `./test_pytest.py::test_runtime_skipped` -- `./test_pytest.py::test_skipped` -- `./test_pytest.py::test_maybe_skipped` -- `./test_pytest.py::SpamTests::test_skipped` -- `./test_pytest.py::test_param_13_markers[???]` -- `./test_pytest.py::test_param_13_skipped[*]` -- `./test_unittest.py::MyTests::test_skipped` -- (`./test_unittest.py::MyTests::test_maybe_skipped`) -- (`./test_unittest.py::MyTests::test_maybe_not_skipped`) - -in namespace package: - -- `./w/test_spam.py::test_simple` -- `./w/test_spam_ex.py::test_simple` - -filename oddities: - -- `./test_42.py::test_simple` -- `./test_42-43.py::test_simple` -- (`./testspam.py::test_simple` not discovered by default) -- (`./spam.py::test_simple` not discovered) - -imports discovered: - -- `./v/test_eggs.py::test_simple` -- `./v/test_eggs.py::TestSimple::test_simple` -- `./v/test_ham.py::test_simple` -- `./v/test_ham.py::test_not_hard` -- `./v/test_spam.py::test_simple` -- `./v/test_spam.py::test_simpler` - -subtests: - -- `./test_pytest.py::test_dynamic_*` -- `./test_pytest.py::test_param_01[]` -- `./test_pytest.py::test_param_11[1]` -- `./test_pytest.py::test_param_13[*]` -- `./test_pytest.py::test_param_13_markers[*]` -- `./test_pytest.py::test_param_13_repeat[*]` -- `./test_pytest.py::test_param_13_skipped[*]` -- `./test_pytest.py::test_param_23_13[*]` -- `./test_pytest.py::test_param_23_raises[*]` -- `./test_pytest.py::test_param_33[*]` -- `./test_pytest.py::test_param_33_ids[*]` -- `./test_pytest.py::TestParam::test_param_13[*]` -- `./test_pytest.py::TestParamAll::test_param_13[*]` -- `./test_pytest.py::TestParamAll::test_spam_13[*]` -- `./test_pytest.py::test_fixture_param[*]` -- `./test_pytest.py::test_param_fixture[*]` -- `./test_pytest_param.py::test_param_13[*]` -- `./test_pytest_param.py::TestParamAll::test_param_13[*]` -- `./test_pytest_param.py::TestParamAll::test_spam_13[*]` -- (`./test_unittest.py::MyTests::test_with_subtests`) -- (`./test_unittest.py::MyTests::test_with_nested_subtests`) -- (`./test_unittest.py::MyTests::test_dynamic_*`) - -For more options for pytests's parametrize(), see -https://docs.pytest.org/en/latest/example/parametrize.html#paramexamples. - -using fixtures: - -- `./test_pytest.py::test_fixture` -- `./test_pytest.py::test_fixture_param[*]` -- `./test_pytest.py::test_param_fixture[*]` -- `./test_pytest.py::test_param_mark_fixture[*]` - -other markers: - -- `./test_pytest.py::test_known_failure` -- `./test_pytest.py::test_param_markers[2]` -- `./test_pytest.py::test_warned` -- `./test_pytest.py::test_custom_marker` -- `./test_pytest.py::test_multiple_markers` -- (`./test_unittest.py::MyTests::test_known_failure`) - -others not discovered: - -- (`./test_pytest.py::TestSpam::TestHam::TestEggs::TestNoop1`) -- (`./test_pytest.py::TestSpam::TestNoop2`) -- (`./test_pytest.py::TestNoop3`) -- (`./test_pytest.py::MyTests::test_simple`) -- (`./test_unittest.py::MyTests::TestSub1`) -- (`./test_unittest.py::MyTests::TestSub2`) -- (`./test_unittest.py::NoTests`) - -doctests: - -- `./test_doctest.txt::test_doctest.txt` -- (`./test_doctest.py::test_doctest.py`) -- (`../mod.py::mod`) -- (`../mod.py::mod.square`) -- (`../mod.py::mod.Spam`) -- (`../mod.py::mod.spam.eggs`) diff --git a/pythonFiles/tests/testing_tools/adapter/.data/complex/mod.py b/pythonFiles/tests/testing_tools/adapter/.data/complex/mod.py deleted file mode 100644 index b8c495503895..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/.data/complex/mod.py +++ /dev/null @@ -1,51 +0,0 @@ -""" - -Examples: - ->>> square(1) -1 ->>> square(2) -4 ->>> square(3) -9 ->>> spam = Spam() ->>> spam.eggs() -42 -""" - - -def square(x): - """ - - Examples: - - >>> square(1) - 1 - >>> square(2) - 4 - >>> square(3) - 9 - """ - return x * x - - -class Spam(object): - """ - - Examples: - - >>> spam = Spam() - >>> spam.eggs() - 42 - """ - - def eggs(self): - """ - - Examples: - - >>> spam = Spam() - >>> spam.eggs() - 42 - """ - return 42 diff --git a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/spam.py b/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/spam.py deleted file mode 100644 index 4c4134d75584..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/spam.py +++ /dev/null @@ -1,3 +0,0 @@ - -def test_simple(): - assert True diff --git a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_42-43.py b/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_42-43.py deleted file mode 100644 index 4c4134d75584..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_42-43.py +++ /dev/null @@ -1,3 +0,0 @@ - -def test_simple(): - assert True diff --git a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_42.py b/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_42.py deleted file mode 100644 index 4c4134d75584..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_42.py +++ /dev/null @@ -1,3 +0,0 @@ - -def test_simple(): - assert True diff --git a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_doctest.py b/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_doctest.py deleted file mode 100644 index 27cccbdb77cc..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_doctest.py +++ /dev/null @@ -1,6 +0,0 @@ -""" -Doctests: - ->>> 1 == 1 -True -""" diff --git a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_doctest.txt b/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_doctest.txt deleted file mode 100644 index 4b51fde5667e..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_doctest.txt +++ /dev/null @@ -1,15 +0,0 @@ - -assignment & lookup: - ->>> x = 3 ->>> x -3 - -deletion: - ->>> del x ->>> x -Traceback (most recent call last): - ... -NameError: name 'x' is not defined - diff --git a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_foo.py b/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_foo.py deleted file mode 100644 index e752106f503a..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_foo.py +++ /dev/null @@ -1,4 +0,0 @@ - - -def test_simple(): - assert True diff --git a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_mixed.py b/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_mixed.py deleted file mode 100644 index e9c675647f13..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_mixed.py +++ /dev/null @@ -1,27 +0,0 @@ -import pytest -import unittest - - -def test_top_level(): - assert True - - -@pytest.mark.skip -def test_skipped(): - assert False - - -class TestMySuite(object): - - def test_simple(self): - assert True - - -class MyTests(unittest.TestCase): - - def test_simple(self): - assert True - - @pytest.mark.skip - def test_skipped(self): - assert False diff --git a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_pytest.py b/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_pytest.py deleted file mode 100644 index 39d3ece9c0ba..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_pytest.py +++ /dev/null @@ -1,227 +0,0 @@ -# ... - -import pytest - - -def test_simple(): - assert True - - -def test_failure(): - assert False - - -def test_runtime_skipped(): - pytest.skip('???') - - -def test_runtime_failed(): - pytest.fail('???') - - -def test_raises(): - raise Exception - - -@pytest.mark.skip -def test_skipped(): - assert False - - -@pytest.mark.skipif(True) -def test_maybe_skipped(): - assert False - - -@pytest.mark.xfail -def test_known_failure(): - assert False - - -@pytest.mark.filterwarnings -def test_warned(): - assert False - - -@pytest.mark.spam -def test_custom_marker(): - assert False - - -@pytest.mark.filterwarnings -@pytest.mark.skip -@pytest.mark.xfail -@pytest.mark.skipif(True) -@pytest.mark.skip -@pytest.mark.spam -def test_multiple_markers(): - assert False - - -for i in range(3): - def func(): - assert True - globals()['test_dynamic_{}'.format(i + 1)] = func -del func - - -class TestSpam(object): - - def test_simple(): - assert True - - @pytest.mark.skip - def test_skipped(self): - assert False - - class TestHam(object): - - class TestEggs(object): - - def test_simple(): - assert True - - class TestNoop1(object): - pass - - class TestNoop2(object): - pass - - -class TestEggs(object): - - def test_simple(): - assert True - - -# legend for parameterized test names: -# "test_param_XY[_XY]*" -# X - # params -# Y - # cases -# [_XY]* - extra decorators - -@pytest.mark.parametrize('', [()]) -def test_param_01(): - assert True - - -@pytest.mark.parametrize('x', [(1,)]) -def test_param_11(x): - assert x == 1 - - -@pytest.mark.parametrize('x', [(1,), (1.0,), (1+0j,)]) -def test_param_13(x): - assert x == 1 - - -@pytest.mark.parametrize('x', [(1,), (1,), (1,)]) -def test_param_13_repeat(x): - assert x == 1 - - -@pytest.mark.parametrize('x,y,z', [(1, 1, 1), (3, 4, 5), (0, 0, 0)]) -def test_param_33(x, y, z): - assert x*x + y*y == z*z - - -@pytest.mark.parametrize('x,y,z', [(1, 1, 1), (3, 4, 5), (0, 0, 0)], - ids=['v1', 'v2', 'v3']) -def test_param_33_ids(x, y, z): - assert x*x + y*y == z*z - - -@pytest.mark.parametrize('z', [(1,), (5,), (0,)]) -@pytest.mark.parametrize('x,y', [(1, 1), (3, 4), (0, 0)]) -def test_param_23_13(x, y, z): - assert x*x + y*y == z*z - - -@pytest.mark.parametrize('x', [ - (1,), - pytest.param(1.0, marks=[pytest.mark.skip, pytest.mark.spam], id='???'), - pytest.param(2, marks=[pytest.mark.xfail]), - ]) -def test_param_13_markers(x): - assert x == 1 - - -@pytest.mark.skip -@pytest.mark.parametrize('x', [(1,), (1.0,), (1+0j,)]) -def test_param_13_skipped(x): - assert x == 1 - - -@pytest.mark.parametrize('x,catch', [(1, None), (1.0, None), (2, pytest.raises(Exception))]) -def test_param_23_raises(x, catch): - if x != 1: - with catch: - raise Exception - - -class TestParam(object): - - def test_simple(): - assert True - - @pytest.mark.parametrize('x', [(1,), (1.0,), (1+0j,)]) - def test_param_13(self, x): - assert x == 1 - - -@pytest.mark.parametrize('x', [(1,), (1.0,), (1+0j,)]) -class TestParamAll(object): - - def test_param_13(self, x): - assert x == 1 - - def test_spam_13(self, x): - assert x == 1 - - -@pytest.fixture -def spamfix(request): - yield 'spam' - - -@pytest.fixture(params=['spam', 'eggs']) -def paramfix(request): - return request.param - - -def test_fixture(spamfix): - assert spamfix == 'spam' - - -@pytest.mark.usefixtures('spamfix') -def test_mark_fixture(): - assert True - - -@pytest.mark.parametrize('x', [(1,), (1.0,), (1+0j,)]) -def test_param_fixture(spamfix, x): - assert spamfix == 'spam' - assert x == 1 - - -@pytest.mark.parametrize('x', [ - (1,), - (1.0,), - pytest.param(1+0j, marks=[pytest.mark.usefixtures('spamfix')]), - ]) -def test_param_mark_fixture(x): - assert x == 1 - - -def test_fixture_param(paramfix): - assert paramfix == 'spam' - - -class TestNoop3(object): - pass - - -class MyTests(object): # does not match default name pattern - - def test_simple(): - assert True diff --git a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_pytest_param.py b/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_pytest_param.py deleted file mode 100644 index bd22d89f42bd..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_pytest_param.py +++ /dev/null @@ -1,18 +0,0 @@ -import pytest - - -# module-level parameterization -pytestmark = pytest.mark.parametrize('x', [(1,), (1.0,), (1+0j,)]) - - -def test_param_13(x): - assert x == 1 - - -class TestParamAll(object): - - def test_param_13(self, x): - assert x == 1 - - def test_spam_13(self, x): - assert x == 1 diff --git a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_unittest.py b/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_unittest.py deleted file mode 100644 index dd3e82535739..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/test_unittest.py +++ /dev/null @@ -1,66 +0,0 @@ -import unittest - - -class MyTests(unittest.TestCase): - - def test_simple(self): - self.assertTrue(True) - - @unittest.skip('???') - def test_skipped(self): - self.assertTrue(False) - - @unittest.skipIf(True, '???') - def test_maybe_skipped(self): - self.assertTrue(False) - - @unittest.skipUnless(False, '???') - def test_maybe_not_skipped(self): - self.assertTrue(False) - - def test_skipped_inside(self): - raise unittest.SkipTest('???') - - class TestSub1(object): - - def test_simple(self): - self.assertTrue(True) - - class TestSub2(unittest.TestCase): - - def test_simple(self): - self.assertTrue(True) - - def test_failure(self): - raise Exception - - @unittest.expectedFailure - def test_known_failure(self): - raise Exception - - def test_with_subtests(self): - for i in range(3): - with self.subtest(i): # This is invalid under Py2. - self.assertTrue(True) - - def test_with_nested_subtests(self): - for i in range(3): - with self.subtest(i): # This is invalid under Py2. - for j in range(3): - with self.subtest(i): # This is invalid under Py2. - self.assertTrue(True) - - for i in range(3): - def test_dynamic_(self, i=i): - self.assertEqual(True) - test_dynamic_.__name__ += str(i) - - -class OtherTests(unittest.TestCase): - - def test_simple(self): - self.assertTrue(True) - - -class NoTests(unittest.TestCase): - pass diff --git a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/testspam.py b/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/testspam.py deleted file mode 100644 index 7ec91c783e2c..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/testspam.py +++ /dev/null @@ -1,9 +0,0 @@ -''' -... -... -... -''' - - -def test_simple(): - assert True diff --git a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/v/spam.py b/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/v/spam.py deleted file mode 100644 index 18c92c09306e..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/v/spam.py +++ /dev/null @@ -1,9 +0,0 @@ - -def test_simple(self): - assert True - - -class TestSimple(object): - - def test_simple(self): - assert True diff --git a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/v/test_eggs.py b/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/v/test_eggs.py deleted file mode 100644 index f3e7d9517631..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/v/test_eggs.py +++ /dev/null @@ -1 +0,0 @@ -from .spam import * diff --git a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/v/test_ham.py b/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/v/test_ham.py deleted file mode 100644 index 6b6a01f87ec5..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/v/test_ham.py +++ /dev/null @@ -1,2 +0,0 @@ -from .spam import test_simple -from .spam import test_simple as test_not_hard diff --git a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/v/test_spam.py b/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/v/test_spam.py deleted file mode 100644 index 18cf56f90533..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/v/test_spam.py +++ /dev/null @@ -1,5 +0,0 @@ -from .spam import test_simple - - -def test_simpler(self): - assert True diff --git a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/w/test_spam.py b/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/w/test_spam.py deleted file mode 100644 index 6a0b60d1d5bd..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/w/test_spam.py +++ /dev/null @@ -1,5 +0,0 @@ - - - -def test_simple(): - assert True diff --git a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/w/test_spam_ex.py b/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/w/test_spam_ex.py deleted file mode 100644 index 6a0b60d1d5bd..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/w/test_spam_ex.py +++ /dev/null @@ -1,5 +0,0 @@ - - - -def test_simple(): - assert True diff --git a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/x/y/z/a/test_spam.py b/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/x/y/z/a/test_spam.py deleted file mode 100644 index bdb7e4fec3a5..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/x/y/z/a/test_spam.py +++ /dev/null @@ -1,12 +0,0 @@ -""" -... -""" - - -# ... - -ANSWER = 42 - - -def test_simple(): - assert True diff --git a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/x/y/z/b/test_spam.py b/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/x/y/z/b/test_spam.py deleted file mode 100644 index 4923c556c29a..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/x/y/z/b/test_spam.py +++ /dev/null @@ -1,8 +0,0 @@ - - -# ?!? -CHORUS = 'spamspamspamspamspam...' - - -def test_simple(): - assert True diff --git a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/x/y/z/test_ham.py b/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/x/y/z/test_ham.py deleted file mode 100644 index 4c4134d75584..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/.data/complex/tests/x/y/z/test_ham.py +++ /dev/null @@ -1,3 +0,0 @@ - -def test_simple(): - assert True diff --git a/pythonFiles/tests/testing_tools/adapter/.data/simple/tests/test_spam.py b/pythonFiles/tests/testing_tools/adapter/.data/simple/tests/test_spam.py deleted file mode 100644 index 4c4134d75584..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/.data/simple/tests/test_spam.py +++ /dev/null @@ -1,3 +0,0 @@ - -def test_simple(): - assert True diff --git a/pythonFiles/tests/testing_tools/adapter/.data/syntax-error/tests/test_spam.py b/pythonFiles/tests/testing_tools/adapter/.data/syntax-error/tests/test_spam.py deleted file mode 100644 index 54d6400a3465..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/.data/syntax-error/tests/test_spam.py +++ /dev/null @@ -1,7 +0,0 @@ - -def test_simple(): - assert True - - -# A syntax error: -: diff --git a/pythonFiles/tests/testing_tools/adapter/pytest/test_cli.py b/pythonFiles/tests/testing_tools/adapter/pytest/test_cli.py deleted file mode 100644 index 6f590a31fa56..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/pytest/test_cli.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -import unittest - -from ....util import Stub, StubProxy -from testing_tools.adapter.errors import UnsupportedCommandError -from testing_tools.adapter.pytest._cli import add_subparser - - -class StubSubparsers(StubProxy): - def __init__(self, stub=None, name="subparsers"): - super(StubSubparsers, self).__init__(stub, name) - - def add_parser(self, name): - self.add_call("add_parser", None, {"name": name}) - return self.return_add_parser - - -class StubArgParser(StubProxy): - def __init__(self, stub=None): - super(StubArgParser, self).__init__(stub, "argparser") - - def add_argument(self, *args, **kwargs): - self.add_call("add_argument", args, kwargs) - - -class AddCLISubparserTests(unittest.TestCase): - def test_discover(self): - stub = Stub() - subparsers = StubSubparsers(stub) - parser = StubArgParser(stub) - subparsers.return_add_parser = parser - - add_subparser("discover", "pytest", subparsers) - - self.assertEqual( - stub.calls, - [ - ("subparsers.add_parser", None, {"name": "pytest"}), - ], - ) - - def test_unsupported_command(self): - subparsers = StubSubparsers(name=None) - subparsers.return_add_parser = None - - with self.assertRaises(UnsupportedCommandError): - add_subparser("run", "pytest", subparsers) - with self.assertRaises(UnsupportedCommandError): - add_subparser("debug", "pytest", subparsers) - with self.assertRaises(UnsupportedCommandError): - add_subparser("???", "pytest", subparsers) - self.assertEqual( - subparsers.calls, - [ - ("add_parser", None, {"name": "pytest"}), - ("add_parser", None, {"name": "pytest"}), - ("add_parser", None, {"name": "pytest"}), - ], - ) diff --git a/pythonFiles/tests/testing_tools/adapter/pytest/test_discovery.py b/pythonFiles/tests/testing_tools/adapter/pytest/test_discovery.py deleted file mode 100644 index 98b18567253e..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/pytest/test_discovery.py +++ /dev/null @@ -1,1540 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -from __future__ import print_function, unicode_literals - -try: - from io import StringIO -except ImportError: - from StringIO import StringIO # type: ignore (for Pylance) -import os -import sys -import tempfile -import unittest -import warnings - -import pytest -import _pytest.doctest - -from .... import util -from testing_tools.adapter import util as adapter_util -from testing_tools.adapter.pytest import _pytest_item as pytest_item -from testing_tools.adapter import info -from testing_tools.adapter.pytest import _discovery - -# In Python 3.8 __len__ is called twice, which impacts some of the test assertions we do below. -PYTHON_38_OR_LATER = sys.version_info[0] >= 3 and sys.version_info[1] >= 8 - - -class StubPyTest(util.StubProxy): - def __init__(self, stub=None): - super(StubPyTest, self).__init__(stub, "pytest") - self.return_main = 0 - - def main(self, args, plugins): - self.add_call("main", None, {"args": args, "plugins": plugins}) - return self.return_main - - -class StubPlugin(util.StubProxy): - - _started = True - - def __init__(self, stub=None, tests=None): - super(StubPlugin, self).__init__(stub, "plugin") - if tests is None: - tests = StubDiscoveredTests(self.stub) - self._tests = tests - - def __getattr__(self, name): - if not name.startswith("pytest_"): - raise AttributeError(name) - - def func(*args, **kwargs): - self.add_call(name, args or None, kwargs or None) - - return func - - -class StubDiscoveredTests(util.StubProxy): - - NOT_FOUND = object() - - def __init__(self, stub=None): - super(StubDiscoveredTests, self).__init__(stub, "discovered") - self.return_items = [] - self.return_parents = [] - - def __len__(self): - self.add_call("__len__", None, None) - return len(self.return_items) - - def __getitem__(self, index): - self.add_call("__getitem__", (index,), None) - return self.return_items[index] - - @property - def parents(self): - self.add_call("parents", None, None) - return self.return_parents - - def reset(self): - self.add_call("reset", None, None) - - def add_test(self, test, parents): - self.add_call("add_test", None, {"test": test, "parents": parents}) - - -class FakeFunc(object): - def __init__(self, name): - self.__name__ = name - - -class FakeMarker(object): - def __init__(self, name): - self.name = name - - -class StubPytestItem(util.StubProxy): - - _debugging = False - _hasfunc = True - - def __init__(self, stub=None, **attrs): - super(StubPytestItem, self).__init__(stub, "pytest.Item") - if attrs.get("function") is None: - attrs.pop("function", None) - self._hasfunc = False - - attrs.setdefault("user_properties", []) - - self.__dict__.update(attrs) - - if "own_markers" not in attrs: - self.own_markers = () - - def __repr__(self): - return object.__repr__(self) - - def __getattr__(self, name): - if not self._debugging: - self.add_call(name + " (attr)", None, None) - if name == "function": - if not self._hasfunc: - raise AttributeError(name) - - def func(*args, **kwargs): - self.add_call(name, args or None, kwargs or None) - - return func - - -class StubSubtypedItem(StubPytestItem): - def __init__(self, *args, **kwargs): - super(StubSubtypedItem, self).__init__(*args, **kwargs) - if "nodeid" in self.__dict__: - self._nodeid = self.__dict__.pop("nodeid") - - @property - def location(self): - return self.__dict__.get("location") - - -class StubFunctionItem(StubSubtypedItem, pytest.Function): - @property - def function(self): - return self.__dict__.get("function") - - -def create_stub_function_item(*args, **kwargs): - # StubFunctionItem should not be calling __init__(), but instead from_parent(). - # Unfortunately the detangling is massive due to the complexity of the test - # harness, so we are punting in hopes that we rewrite test discovery before - # pytest removes this functionality. - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - return StubFunctionItem(*args, **kwargs) - - -class StubDoctestItem(StubSubtypedItem, _pytest.doctest.DoctestItem): - pass - - -def create_stub_doctest_item(*args, **kwargs): - # StubDoctestItem should not be calling __init__(), but instead from_parent(). - # Unfortunately the detangling is massive due to the complexity of the test - # harness, so we are punting in hopes that we rewrite test discovery before - # pytest removes this functionality. - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - return StubDoctestItem(*args, **kwargs) - - -class StubPytestSession(util.StubProxy): - def __init__(self, stub=None): - super(StubPytestSession, self).__init__(stub, "pytest.Session") - - def __getattr__(self, name): - self.add_call(name + " (attr)", None, None) - - def func(*args, **kwargs): - self.add_call(name, args or None, kwargs or None) - - return func - - -class StubPytestConfig(util.StubProxy): - def __init__(self, stub=None): - super(StubPytestConfig, self).__init__(stub, "pytest.Config") - - def __getattr__(self, name): - self.add_call(name + " (attr)", None, None) - - def func(*args, **kwargs): - self.add_call(name, args or None, kwargs or None) - - return func - - -def generate_parse_item(pathsep): - if pathsep == "\\": - - def normcase(path): - path = path.lower() - return path.replace("/", "\\") - - else: - raise NotImplementedError - ########## - def _fix_fileid(*args): - return adapter_util.fix_fileid( - *args, - **dict( - _normcase=normcase, - _pathsep=pathsep, - ) - ) - - def _normalize_test_id(*args): - return pytest_item._normalize_test_id( - *args, - **dict( - _fix_fileid=_fix_fileid, - _pathsep=pathsep, - ) - ) - - def _iter_nodes(*args): - return pytest_item._iter_nodes( - *args, - **dict( - _normalize_test_id=_normalize_test_id, - _normcase=normcase, - _pathsep=pathsep, - ) - ) - - def _parse_node_id(*args): - return pytest_item._parse_node_id( - *args, - **dict( - _iter_nodes=_iter_nodes, - ) - ) - - ########## - def _split_fspath(*args): - return pytest_item._split_fspath( - *args, - **dict( - _normcase=normcase, - ) - ) - - ########## - def _matches_relfile(*args): - return pytest_item._matches_relfile( - *args, - **dict( - _normcase=normcase, - _pathsep=pathsep, - ) - ) - - def _is_legacy_wrapper(*args): - return pytest_item._is_legacy_wrapper( - *args, - **dict( - _pathsep=pathsep, - ) - ) - - def _get_location(*args): - return pytest_item._get_location( - *args, - **dict( - _matches_relfile=_matches_relfile, - _is_legacy_wrapper=_is_legacy_wrapper, - _pathsep=pathsep, - ) - ) - - ########## - def _parse_item(item): - return pytest_item.parse_item( - item, - **dict( - _parse_node_id=_parse_node_id, - _split_fspath=_split_fspath, - _get_location=_get_location, - ) - ) - - return _parse_item - - -################################## -# tests - - -def fake_pytest_main(stub, use_fd, pytest_stdout): - def ret(args, plugins): - stub.add_call("pytest.main", None, {"args": args, "plugins": plugins}) - if use_fd: - os.write(sys.stdout.fileno(), pytest_stdout.encode()) - else: - print(pytest_stdout, end="") - return 0 - - return ret - - -class DiscoverTests(unittest.TestCase): - - DEFAULT_ARGS = [ - "--collect-only", - ] - - def test_basic(self): - stub = util.Stub() - stubpytest = StubPyTest(stub) - plugin = StubPlugin(stub) - expected = [] - plugin.discovered = expected - calls = [ - ("pytest.main", None, {"args": self.DEFAULT_ARGS, "plugins": [plugin]}), - ("discovered.parents", None, None), - ("discovered.__len__", None, None), - ("discovered.__getitem__", (0,), None), - ] - - # In Python 3.8 __len__ is called twice. - if PYTHON_38_OR_LATER: - calls.insert(3, ("discovered.__len__", None, None)) - - parents, tests = _discovery.discover( - [], _pytest_main=stubpytest.main, _plugin=plugin - ) - - self.assertEqual(parents, []) - self.assertEqual(tests, expected) - self.assertEqual(stub.calls, calls) - - def test_failure(self): - stub = util.Stub() - pytest = StubPyTest(stub) - pytest.return_main = 2 - plugin = StubPlugin(stub) - - with self.assertRaises(Exception): - _discovery.discover([], _pytest_main=pytest.main, _plugin=plugin) - - self.assertEqual( - stub.calls, - [ - ("pytest.main", None, {"args": self.DEFAULT_ARGS, "plugins": [plugin]}), - ], - ) - - def test_no_tests_found(self): - stub = util.Stub() - pytest = StubPyTest(stub) - pytest.return_main = 5 - plugin = StubPlugin(stub) - expected = [] - plugin.discovered = expected - calls = [ - ("pytest.main", None, {"args": self.DEFAULT_ARGS, "plugins": [plugin]}), - ("discovered.parents", None, None), - ("discovered.__len__", None, None), - ("discovered.__getitem__", (0,), None), - ] - - # In Python 3.8 __len__ is called twice. - if PYTHON_38_OR_LATER: - calls.insert(3, ("discovered.__len__", None, None)) - - parents, tests = _discovery.discover( - [], _pytest_main=pytest.main, _plugin=plugin - ) - - self.assertEqual(parents, []) - self.assertEqual(tests, expected) - self.assertEqual(stub.calls, calls) - - def test_stdio_hidden_file(self): - stub = util.Stub() - - plugin = StubPlugin(stub) - plugin.discovered = [] - calls = [ - ("pytest.main", None, {"args": self.DEFAULT_ARGS, "plugins": [plugin]}), - ("discovered.parents", None, None), - ("discovered.__len__", None, None), - ("discovered.__getitem__", (0,), None), - ] - pytest_stdout = "spamspamspamspamspamspamspammityspam" - - # In Python 3.8 __len__ is called twice. - if PYTHON_38_OR_LATER: - calls.insert(3, ("discovered.__len__", None, None)) - - # to simulate stdio behavior in methods like os.dup, - # use actual files (rather than StringIO) - with tempfile.TemporaryFile("r+") as mock: - sys.stdout = mock - try: - _discovery.discover( - [], - hidestdio=True, - _pytest_main=fake_pytest_main(stub, False, pytest_stdout), - _plugin=plugin, - ) - finally: - sys.stdout = sys.__stdout__ - - mock.seek(0) - captured = mock.read() - - self.assertEqual(captured, "") - self.assertEqual(stub.calls, calls) - - def test_stdio_hidden_fd(self): - # simulate cases where stdout comes from the lower layer than sys.stdout - # via file descriptors (e.g., from cython) - stub = util.Stub() - plugin = StubPlugin(stub) - pytest_stdout = "spamspamspamspamspamspamspammityspam" - - # Replace with contextlib.redirect_stdout() once Python 2.7 support is dropped. - sys.stdout = StringIO() - try: - _discovery.discover( - [], - hidestdio=True, - _pytest_main=fake_pytest_main(stub, True, pytest_stdout), - _plugin=plugin, - ) - captured = sys.stdout.read() - self.assertEqual(captured, "") - finally: - sys.stdout = sys.__stdout__ - - def test_stdio_not_hidden_file(self): - stub = util.Stub() - - plugin = StubPlugin(stub) - plugin.discovered = [] - calls = [ - ("pytest.main", None, {"args": self.DEFAULT_ARGS, "plugins": [plugin]}), - ("discovered.parents", None, None), - ("discovered.__len__", None, None), - ("discovered.__getitem__", (0,), None), - ] - pytest_stdout = "spamspamspamspamspamspamspammityspam" - - # In Python 3.8 __len__ is called twice. - if PYTHON_38_OR_LATER: - calls.insert(3, ("discovered.__len__", None, None)) - - buf = StringIO() - - sys.stdout = buf - try: - _discovery.discover( - [], - hidestdio=False, - _pytest_main=fake_pytest_main(stub, False, pytest_stdout), - _plugin=plugin, - ) - finally: - sys.stdout = sys.__stdout__ - captured = buf.getvalue() - - self.assertEqual(captured, pytest_stdout) - self.assertEqual(stub.calls, calls) - - def test_stdio_not_hidden_fd(self): - # simulate cases where stdout comes from the lower layer than sys.stdout - # via file descriptors (e.g., from cython) - stub = util.Stub() - plugin = StubPlugin(stub) - pytest_stdout = "spamspamspamspamspamspamspammityspam" - stub.calls = [] - with tempfile.TemporaryFile("r+") as mock: - sys.stdout = mock - try: - _discovery.discover( - [], - hidestdio=False, - _pytest_main=fake_pytest_main(stub, True, pytest_stdout), - _plugin=plugin, - ) - finally: - mock.seek(0) - captured = sys.stdout.read() - sys.stdout = sys.__stdout__ - self.assertEqual(captured, pytest_stdout) - - -class CollectorTests(unittest.TestCase): - def test_modifyitems(self): - stub = util.Stub() - discovered = StubDiscoveredTests(stub) - session = StubPytestSession(stub) - config = StubPytestConfig(stub) - collector = _discovery.TestCollector(tests=discovered) - - testroot = adapter_util.fix_path("/a/b/c") - relfile1 = adapter_util.fix_path("./test_spam.py") - relfile2 = adapter_util.fix_path("x/y/z/test_eggs.py") - - collector.pytest_collection_modifyitems( - session, - config, - [ - create_stub_function_item( - stub, - nodeid="test_spam.py::SpamTests::test_one", - name="test_one", - location=("test_spam.py", 12, "SpamTests.test_one"), - fspath=adapter_util.PATH_JOIN(testroot, "test_spam.py"), - function=FakeFunc("test_one"), - ), - create_stub_function_item( - stub, - nodeid="test_spam.py::SpamTests::test_other", - name="test_other", - location=("test_spam.py", 19, "SpamTests.test_other"), - fspath=adapter_util.PATH_JOIN(testroot, "test_spam.py"), - function=FakeFunc("test_other"), - ), - create_stub_function_item( - stub, - nodeid="test_spam.py::test_all", - name="test_all", - location=("test_spam.py", 144, "test_all"), - fspath=adapter_util.PATH_JOIN(testroot, "test_spam.py"), - function=FakeFunc("test_all"), - ), - create_stub_function_item( - stub, - nodeid="test_spam.py::test_each[10-10]", - name="test_each[10-10]", - location=("test_spam.py", 273, "test_each[10-10]"), - fspath=adapter_util.PATH_JOIN(testroot, "test_spam.py"), - function=FakeFunc("test_each"), - ), - create_stub_function_item( - stub, - nodeid=relfile2 + "::All::BasicTests::test_first", - name="test_first", - location=(relfile2, 31, "All.BasicTests.test_first"), - fspath=adapter_util.PATH_JOIN(testroot, relfile2), - function=FakeFunc("test_first"), - ), - create_stub_function_item( - stub, - nodeid=relfile2 + "::All::BasicTests::test_each[1+2-3]", - name="test_each[1+2-3]", - location=(relfile2, 62, "All.BasicTests.test_each[1+2-3]"), - fspath=adapter_util.PATH_JOIN(testroot, relfile2), - function=FakeFunc("test_each"), - own_markers=[ - FakeMarker(v) - for v in [ - # supported - "skip", - "skipif", - "xfail", - # duplicate - "skip", - # ignored (pytest-supported) - "parameterize", - "usefixtures", - "filterwarnings", - # ignored (custom) - "timeout", - ] - ], - ), - ], - ) - - self.maxDiff = None - self.assertEqual( - stub.calls, - [ - ("discovered.reset", None, None), - ( - "discovered.add_test", - None, - dict( - parents=[ - ("./test_spam.py::SpamTests", "SpamTests", "suite"), - ("./test_spam.py", "test_spam.py", "file"), - (".", testroot, "folder"), - ], - test=info.SingleTestInfo( - id="./test_spam.py::SpamTests::test_one", - name="test_one", - path=info.SingleTestPath( - root=testroot, - relfile=relfile1, - func="SpamTests.test_one", - sub=None, - ), - source="{}:{}".format(relfile1, 13), - markers=None, - parentid="./test_spam.py::SpamTests", - ), - ), - ), - ( - "discovered.add_test", - None, - dict( - parents=[ - ("./test_spam.py::SpamTests", "SpamTests", "suite"), - ("./test_spam.py", "test_spam.py", "file"), - (".", testroot, "folder"), - ], - test=info.SingleTestInfo( - id="./test_spam.py::SpamTests::test_other", - name="test_other", - path=info.SingleTestPath( - root=testroot, - relfile=relfile1, - func="SpamTests.test_other", - sub=None, - ), - source="{}:{}".format(relfile1, 20), - markers=None, - parentid="./test_spam.py::SpamTests", - ), - ), - ), - ( - "discovered.add_test", - None, - dict( - parents=[ - ("./test_spam.py", "test_spam.py", "file"), - (".", testroot, "folder"), - ], - test=info.SingleTestInfo( - id="./test_spam.py::test_all", - name="test_all", - path=info.SingleTestPath( - root=testroot, - relfile=relfile1, - func="test_all", - sub=None, - ), - source="{}:{}".format(relfile1, 145), - markers=None, - parentid="./test_spam.py", - ), - ), - ), - ( - "discovered.add_test", - None, - dict( - parents=[ - ("./test_spam.py::test_each", "test_each", "function"), - ("./test_spam.py", "test_spam.py", "file"), - (".", testroot, "folder"), - ], - test=info.SingleTestInfo( - id="./test_spam.py::test_each[10-10]", - name="test_each[10-10]", - path=info.SingleTestPath( - root=testroot, - relfile=relfile1, - func="test_each", - sub=["[10-10]"], - ), - source="{}:{}".format(relfile1, 274), - markers=None, - parentid="./test_spam.py::test_each", - ), - ), - ), - ( - "discovered.add_test", - None, - dict( - parents=[ - ( - "./x/y/z/test_eggs.py::All::BasicTests", - "BasicTests", - "suite", - ), - ("./x/y/z/test_eggs.py::All", "All", "suite"), - ("./x/y/z/test_eggs.py", "test_eggs.py", "file"), - ("./x/y/z", "z", "folder"), - ("./x/y", "y", "folder"), - ("./x", "x", "folder"), - (".", testroot, "folder"), - ], - test=info.SingleTestInfo( - id="./x/y/z/test_eggs.py::All::BasicTests::test_first", - name="test_first", - path=info.SingleTestPath( - root=testroot, - relfile=adapter_util.fix_relpath(relfile2), - func="All.BasicTests.test_first", - sub=None, - ), - source="{}:{}".format( - adapter_util.fix_relpath(relfile2), 32 - ), - markers=None, - parentid="./x/y/z/test_eggs.py::All::BasicTests", - ), - ), - ), - ( - "discovered.add_test", - None, - dict( - parents=[ - ( - "./x/y/z/test_eggs.py::All::BasicTests::test_each", - "test_each", - "function", - ), - ( - "./x/y/z/test_eggs.py::All::BasicTests", - "BasicTests", - "suite", - ), - ("./x/y/z/test_eggs.py::All", "All", "suite"), - ("./x/y/z/test_eggs.py", "test_eggs.py", "file"), - ("./x/y/z", "z", "folder"), - ("./x/y", "y", "folder"), - ("./x", "x", "folder"), - (".", testroot, "folder"), - ], - test=info.SingleTestInfo( - id="./x/y/z/test_eggs.py::All::BasicTests::test_each[1+2-3]", - name="test_each[1+2-3]", - path=info.SingleTestPath( - root=testroot, - relfile=adapter_util.fix_relpath(relfile2), - func="All.BasicTests.test_each", - sub=["[1+2-3]"], - ), - source="{}:{}".format( - adapter_util.fix_relpath(relfile2), 63 - ), - markers=["expected-failure", "skip", "skip-if"], - parentid="./x/y/z/test_eggs.py::All::BasicTests::test_each", - ), - ), - ), - ], - ) - - def test_finish(self): - stub = util.Stub() - discovered = StubDiscoveredTests(stub) - session = StubPytestSession(stub) - testroot = adapter_util.fix_path("/a/b/c") - relfile = adapter_util.fix_path("x/y/z/test_eggs.py") - session.items = [ - create_stub_function_item( - stub, - nodeid=relfile + "::SpamTests::test_spam", - name="test_spam", - location=(relfile, 12, "SpamTests.test_spam"), - fspath=adapter_util.PATH_JOIN(testroot, relfile), - function=FakeFunc("test_spam"), - ), - ] - collector = _discovery.TestCollector(tests=discovered) - - collector.pytest_collection_finish(session) - - self.maxDiff = None - self.assertEqual( - stub.calls, - [ - ("discovered.reset", None, None), - ( - "discovered.add_test", - None, - dict( - parents=[ - ("./x/y/z/test_eggs.py::SpamTests", "SpamTests", "suite"), - ("./x/y/z/test_eggs.py", "test_eggs.py", "file"), - ("./x/y/z", "z", "folder"), - ("./x/y", "y", "folder"), - ("./x", "x", "folder"), - (".", testroot, "folder"), - ], - test=info.SingleTestInfo( - id="./x/y/z/test_eggs.py::SpamTests::test_spam", - name="test_spam", - path=info.SingleTestPath( - root=testroot, - relfile=adapter_util.fix_relpath(relfile), - func="SpamTests.test_spam", - sub=None, - ), - source="{}:{}".format( - adapter_util.fix_relpath(relfile), 13 - ), - markers=None, - parentid="./x/y/z/test_eggs.py::SpamTests", - ), - ), - ), - ], - ) - - def test_doctest(self): - stub = util.Stub() - discovered = StubDiscoveredTests(stub) - session = StubPytestSession(stub) - testroot = adapter_util.fix_path("/a/b/c") - doctestfile = adapter_util.fix_path("x/test_doctest.txt") - relfile = adapter_util.fix_path("x/y/z/test_eggs.py") - session.items = [ - create_stub_doctest_item( - stub, - nodeid=doctestfile + "::test_doctest.txt", - name="test_doctest.txt", - location=(doctestfile, 0, "[doctest] test_doctest.txt"), - fspath=adapter_util.PATH_JOIN(testroot, doctestfile), - ), - # With --doctest-modules - create_stub_doctest_item( - stub, - nodeid=relfile + "::test_eggs", - name="test_eggs", - location=(relfile, 0, "[doctest] test_eggs"), - fspath=adapter_util.PATH_JOIN(testroot, relfile), - ), - create_stub_doctest_item( - stub, - nodeid=relfile + "::test_eggs.TestSpam", - name="test_eggs.TestSpam", - location=(relfile, 12, "[doctest] test_eggs.TestSpam"), - fspath=adapter_util.PATH_JOIN(testroot, relfile), - ), - create_stub_doctest_item( - stub, - nodeid=relfile + "::test_eggs.TestSpam.TestEggs", - name="test_eggs.TestSpam.TestEggs", - location=(relfile, 27, "[doctest] test_eggs.TestSpam.TestEggs"), - fspath=adapter_util.PATH_JOIN(testroot, relfile), - ), - ] - collector = _discovery.TestCollector(tests=discovered) - - collector.pytest_collection_finish(session) - - self.maxDiff = None - self.assertEqual( - stub.calls, - [ - ("discovered.reset", None, None), - ( - "discovered.add_test", - None, - dict( - parents=[ - ("./x/test_doctest.txt", "test_doctest.txt", "file"), - ("./x", "x", "folder"), - (".", testroot, "folder"), - ], - test=info.SingleTestInfo( - id="./x/test_doctest.txt::test_doctest.txt", - name="test_doctest.txt", - path=info.SingleTestPath( - root=testroot, - relfile=adapter_util.fix_relpath(doctestfile), - func=None, - ), - source="{}:{}".format( - adapter_util.fix_relpath(doctestfile), 1 - ), - markers=[], - parentid="./x/test_doctest.txt", - ), - ), - ), - ( - "discovered.add_test", - None, - dict( - parents=[ - ("./x/y/z/test_eggs.py", "test_eggs.py", "file"), - ("./x/y/z", "z", "folder"), - ("./x/y", "y", "folder"), - ("./x", "x", "folder"), - (".", testroot, "folder"), - ], - test=info.SingleTestInfo( - id="./x/y/z/test_eggs.py::test_eggs", - name="test_eggs", - path=info.SingleTestPath( - root=testroot, - relfile=adapter_util.fix_relpath(relfile), - func=None, - ), - source="{}:{}".format(adapter_util.fix_relpath(relfile), 1), - markers=[], - parentid="./x/y/z/test_eggs.py", - ), - ), - ), - ( - "discovered.add_test", - None, - dict( - parents=[ - ("./x/y/z/test_eggs.py", "test_eggs.py", "file"), - ("./x/y/z", "z", "folder"), - ("./x/y", "y", "folder"), - ("./x", "x", "folder"), - (".", testroot, "folder"), - ], - test=info.SingleTestInfo( - id="./x/y/z/test_eggs.py::test_eggs.TestSpam", - name="test_eggs.TestSpam", - path=info.SingleTestPath( - root=testroot, - relfile=adapter_util.fix_relpath(relfile), - func=None, - ), - source="{}:{}".format( - adapter_util.fix_relpath(relfile), 13 - ), - markers=[], - parentid="./x/y/z/test_eggs.py", - ), - ), - ), - ( - "discovered.add_test", - None, - dict( - parents=[ - ("./x/y/z/test_eggs.py", "test_eggs.py", "file"), - ("./x/y/z", "z", "folder"), - ("./x/y", "y", "folder"), - ("./x", "x", "folder"), - (".", testroot, "folder"), - ], - test=info.SingleTestInfo( - id="./x/y/z/test_eggs.py::test_eggs.TestSpam.TestEggs", - name="test_eggs.TestSpam.TestEggs", - path=info.SingleTestPath( - root=testroot, - relfile=adapter_util.fix_relpath(relfile), - func=None, - ), - source="{}:{}".format( - adapter_util.fix_relpath(relfile), 28 - ), - markers=[], - parentid="./x/y/z/test_eggs.py", - ), - ), - ), - ], - ) - - def test_nested_brackets(self): - stub = util.Stub() - discovered = StubDiscoveredTests(stub) - session = StubPytestSession(stub) - testroot = adapter_util.fix_path("/a/b/c") - relfile = adapter_util.fix_path("x/y/z/test_eggs.py") - session.items = [ - create_stub_function_item( - stub, - nodeid=relfile + "::SpamTests::test_spam[a-[b]-c]", - name="test_spam[a-[b]-c]", - location=(relfile, 12, "SpamTests.test_spam[a-[b]-c]"), - fspath=adapter_util.PATH_JOIN(testroot, relfile), - function=FakeFunc("test_spam"), - ), - ] - collector = _discovery.TestCollector(tests=discovered) - - collector.pytest_collection_finish(session) - - self.maxDiff = None - self.assertEqual( - stub.calls, - [ - ("discovered.reset", None, None), - ( - "discovered.add_test", - None, - dict( - parents=[ - ( - "./x/y/z/test_eggs.py::SpamTests::test_spam", - "test_spam", - "function", - ), - ("./x/y/z/test_eggs.py::SpamTests", "SpamTests", "suite"), - ("./x/y/z/test_eggs.py", "test_eggs.py", "file"), - ("./x/y/z", "z", "folder"), - ("./x/y", "y", "folder"), - ("./x", "x", "folder"), - (".", testroot, "folder"), - ], - test=info.SingleTestInfo( - id="./x/y/z/test_eggs.py::SpamTests::test_spam[a-[b]-c]", - name="test_spam[a-[b]-c]", - path=info.SingleTestPath( - root=testroot, - relfile=adapter_util.fix_relpath(relfile), - func="SpamTests.test_spam", - sub=["[a-[b]-c]"], - ), - source="{}:{}".format( - adapter_util.fix_relpath(relfile), 13 - ), - markers=None, - parentid="./x/y/z/test_eggs.py::SpamTests::test_spam", - ), - ), - ), - ], - ) - - def test_nested_suite(self): - stub = util.Stub() - discovered = StubDiscoveredTests(stub) - session = StubPytestSession(stub) - testroot = adapter_util.fix_path("/a/b/c") - relfile = adapter_util.fix_path("x/y/z/test_eggs.py") - session.items = [ - create_stub_function_item( - stub, - nodeid=relfile + "::SpamTests::Ham::Eggs::test_spam", - name="test_spam", - location=(relfile, 12, "SpamTests.Ham.Eggs.test_spam"), - fspath=adapter_util.PATH_JOIN(testroot, relfile), - function=FakeFunc("test_spam"), - ), - ] - collector = _discovery.TestCollector(tests=discovered) - - collector.pytest_collection_finish(session) - - self.maxDiff = None - self.assertEqual( - stub.calls, - [ - ("discovered.reset", None, None), - ( - "discovered.add_test", - None, - dict( - parents=[ - ( - "./x/y/z/test_eggs.py::SpamTests::Ham::Eggs", - "Eggs", - "suite", - ), - ("./x/y/z/test_eggs.py::SpamTests::Ham", "Ham", "suite"), - ("./x/y/z/test_eggs.py::SpamTests", "SpamTests", "suite"), - ("./x/y/z/test_eggs.py", "test_eggs.py", "file"), - ("./x/y/z", "z", "folder"), - ("./x/y", "y", "folder"), - ("./x", "x", "folder"), - (".", testroot, "folder"), - ], - test=info.SingleTestInfo( - id="./x/y/z/test_eggs.py::SpamTests::Ham::Eggs::test_spam", - name="test_spam", - path=info.SingleTestPath( - root=testroot, - relfile=adapter_util.fix_relpath(relfile), - func="SpamTests.Ham.Eggs.test_spam", - sub=None, - ), - source="{}:{}".format( - adapter_util.fix_relpath(relfile), 13 - ), - markers=None, - parentid="./x/y/z/test_eggs.py::SpamTests::Ham::Eggs", - ), - ), - ), - ], - ) - - def test_windows(self): - stub = util.Stub() - discovered = StubDiscoveredTests(stub) - session = StubPytestSession(stub) - testroot = r"C:\A\B\C" - altroot = testroot.replace("\\", "/") - relfile = r"X\Y\Z\test_Eggs.py" - session.items = [ - # typical: - create_stub_function_item( - stub, - # pytest always uses "/" as the path separator in node IDs: - nodeid="X/Y/Z/test_Eggs.py::SpamTests::test_spam", - name="test_spam", - # normal path separator (contrast with nodeid): - location=(relfile, 12, "SpamTests.test_spam"), - # path separator matches location: - fspath=testroot + "\\" + relfile, - function=FakeFunc("test_spam"), - ), - ] - tests = [ - # permutations of path separators - (r"X/test_a.py", "\\", "\\"), # typical - (r"X/test_b.py", "\\", "/"), - (r"X/test_c.py", "/", "\\"), - (r"X/test_d.py", "/", "/"), - (r"X\test_e.py", "\\", "\\"), - (r"X\test_f.py", "\\", "/"), - (r"X\test_g.py", "/", "\\"), - (r"X\test_h.py", "/", "/"), - ] - for fileid, locfile, fspath in tests: - if locfile == "/": - locfile = fileid.replace("\\", "/") - elif locfile == "\\": - locfile = fileid.replace("/", "\\") - if fspath == "/": - fspath = (testroot + "/" + fileid).replace("\\", "/") - elif fspath == "\\": - fspath = (testroot + "/" + fileid).replace("/", "\\") - session.items.append( - create_stub_function_item( - stub, - nodeid=fileid + "::test_spam", - name="test_spam", - location=(locfile, 12, "test_spam"), - fspath=fspath, - function=FakeFunc("test_spam"), - ) - ) - collector = _discovery.TestCollector(tests=discovered) - if os.name != "nt": - collector.parse_item = generate_parse_item("\\") - - collector.pytest_collection_finish(session) - - self.maxDiff = None - self.assertEqual( - stub.calls, - [ - ("discovered.reset", None, None), - ( - "discovered.add_test", - None, - dict( - parents=[ - (r"./X/Y/Z/test_Eggs.py::SpamTests", "SpamTests", "suite"), - (r"./X/Y/Z/test_Eggs.py", "test_Eggs.py", "file"), - (r"./X/Y/Z", "Z", "folder"), - (r"./X/Y", "Y", "folder"), - (r"./X", "X", "folder"), - (".", testroot, "folder"), - ], - test=info.SingleTestInfo( - id=r"./X/Y/Z/test_Eggs.py::SpamTests::test_spam", - name="test_spam", - path=info.SingleTestPath( - root=testroot, # not normalized - relfile=r".\X\Y\Z\test_Eggs.py", # not normalized - func="SpamTests.test_spam", - sub=None, - ), - source=r".\X\Y\Z\test_Eggs.py:13", # not normalized - markers=None, - parentid=r"./X/Y/Z/test_Eggs.py::SpamTests", - ), - ), - ), - # permutations - # (*all* the IDs use "/") - # (source path separator should match relfile, not location) - # /, \, \ - ( - "discovered.add_test", - None, - dict( - parents=[ - (r"./X/test_a.py", "test_a.py", "file"), - (r"./X", "X", "folder"), - (".", testroot, "folder"), - ], - test=info.SingleTestInfo( - id=r"./X/test_a.py::test_spam", - name="test_spam", - path=info.SingleTestPath( - root=testroot, - relfile=r".\X\test_a.py", - func="test_spam", - sub=None, - ), - source=r".\X\test_a.py:13", - markers=None, - parentid=r"./X/test_a.py", - ), - ), - ), - # /, \, / - ( - "discovered.add_test", - None, - dict( - parents=[ - (r"./X/test_b.py", "test_b.py", "file"), - (r"./X", "X", "folder"), - (".", altroot, "folder"), - ], - test=info.SingleTestInfo( - id=r"./X/test_b.py::test_spam", - name="test_spam", - path=info.SingleTestPath( - root=altroot, - relfile=r"./X/test_b.py", - func="test_spam", - sub=None, - ), - source=r"./X/test_b.py:13", - markers=None, - parentid=r"./X/test_b.py", - ), - ), - ), - # /, /, \ - ( - "discovered.add_test", - None, - dict( - parents=[ - (r"./X/test_c.py", "test_c.py", "file"), - (r"./X", "X", "folder"), - (".", testroot, "folder"), - ], - test=info.SingleTestInfo( - id=r"./X/test_c.py::test_spam", - name="test_spam", - path=info.SingleTestPath( - root=testroot, - relfile=r".\X\test_c.py", - func="test_spam", - sub=None, - ), - source=r".\X\test_c.py:13", - markers=None, - parentid=r"./X/test_c.py", - ), - ), - ), - # /, /, / - ( - "discovered.add_test", - None, - dict( - parents=[ - (r"./X/test_d.py", "test_d.py", "file"), - (r"./X", "X", "folder"), - (".", altroot, "folder"), - ], - test=info.SingleTestInfo( - id=r"./X/test_d.py::test_spam", - name="test_spam", - path=info.SingleTestPath( - root=altroot, - relfile=r"./X/test_d.py", - func="test_spam", - sub=None, - ), - source=r"./X/test_d.py:13", - markers=None, - parentid=r"./X/test_d.py", - ), - ), - ), - # \, \, \ - ( - "discovered.add_test", - None, - dict( - parents=[ - (r"./X/test_e.py", "test_e.py", "file"), - (r"./X", "X", "folder"), - (".", testroot, "folder"), - ], - test=info.SingleTestInfo( - id=r"./X/test_e.py::test_spam", - name="test_spam", - path=info.SingleTestPath( - root=testroot, - relfile=r".\X\test_e.py", - func="test_spam", - sub=None, - ), - source=r".\X\test_e.py:13", - markers=None, - parentid=r"./X/test_e.py", - ), - ), - ), - # \, \, / - ( - "discovered.add_test", - None, - dict( - parents=[ - (r"./X/test_f.py", "test_f.py", "file"), - (r"./X", "X", "folder"), - (".", altroot, "folder"), - ], - test=info.SingleTestInfo( - id=r"./X/test_f.py::test_spam", - name="test_spam", - path=info.SingleTestPath( - root=altroot, - relfile=r"./X/test_f.py", - func="test_spam", - sub=None, - ), - source=r"./X/test_f.py:13", - markers=None, - parentid=r"./X/test_f.py", - ), - ), - ), - # \, /, \ - ( - "discovered.add_test", - None, - dict( - parents=[ - (r"./X/test_g.py", "test_g.py", "file"), - (r"./X", "X", "folder"), - (".", testroot, "folder"), - ], - test=info.SingleTestInfo( - id=r"./X/test_g.py::test_spam", - name="test_spam", - path=info.SingleTestPath( - root=testroot, - relfile=r".\X\test_g.py", - func="test_spam", - sub=None, - ), - source=r".\X\test_g.py:13", - markers=None, - parentid=r"./X/test_g.py", - ), - ), - ), - # \, /, / - ( - "discovered.add_test", - None, - dict( - parents=[ - (r"./X/test_h.py", "test_h.py", "file"), - (r"./X", "X", "folder"), - (".", altroot, "folder"), - ], - test=info.SingleTestInfo( - id=r"./X/test_h.py::test_spam", - name="test_spam", - path=info.SingleTestPath( - root=altroot, - relfile=r"./X/test_h.py", - func="test_spam", - sub=None, - ), - source=r"./X/test_h.py:13", - markers=None, - parentid=r"./X/test_h.py", - ), - ), - ), - ], - ) - - def test_mysterious_parens(self): - stub = util.Stub() - discovered = StubDiscoveredTests(stub) - session = StubPytestSession(stub) - testroot = adapter_util.fix_path("/a/b/c") - relfile = adapter_util.fix_path("x/y/z/test_eggs.py") - session.items = [ - create_stub_function_item( - stub, - nodeid=relfile + "::SpamTests::()::()::test_spam", - name="test_spam", - location=(relfile, 12, "SpamTests.test_spam"), - fspath=adapter_util.PATH_JOIN(testroot, relfile), - function=FakeFunc("test_spam"), - ), - ] - collector = _discovery.TestCollector(tests=discovered) - - collector.pytest_collection_finish(session) - - self.maxDiff = None - self.assertEqual( - stub.calls, - [ - ("discovered.reset", None, None), - ( - "discovered.add_test", - None, - dict( - parents=[ - ("./x/y/z/test_eggs.py::SpamTests", "SpamTests", "suite"), - ("./x/y/z/test_eggs.py", "test_eggs.py", "file"), - ("./x/y/z", "z", "folder"), - ("./x/y", "y", "folder"), - ("./x", "x", "folder"), - (".", testroot, "folder"), - ], - test=info.SingleTestInfo( - id="./x/y/z/test_eggs.py::SpamTests::test_spam", - name="test_spam", - path=info.SingleTestPath( - root=testroot, - relfile=adapter_util.fix_relpath(relfile), - func="SpamTests.test_spam", - sub=[], - ), - source="{}:{}".format( - adapter_util.fix_relpath(relfile), 13 - ), - markers=None, - parentid="./x/y/z/test_eggs.py::SpamTests", - ), - ), - ), - ], - ) - - def test_imported_test(self): - # pytest will even discover tests that were imported from - # another module! - stub = util.Stub() - discovered = StubDiscoveredTests(stub) - session = StubPytestSession(stub) - testroot = adapter_util.fix_path("/a/b/c") - relfile = adapter_util.fix_path("x/y/z/test_eggs.py") - srcfile = adapter_util.fix_path("x/y/z/_extern.py") - session.items = [ - create_stub_function_item( - stub, - nodeid=relfile + "::SpamTests::test_spam", - name="test_spam", - location=(srcfile, 12, "SpamTests.test_spam"), - fspath=adapter_util.PATH_JOIN(testroot, relfile), - function=FakeFunc("test_spam"), - ), - create_stub_function_item( - stub, - nodeid=relfile + "::test_ham", - name="test_ham", - location=(srcfile, 3, "test_ham"), - fspath=adapter_util.PATH_JOIN(testroot, relfile), - function=FakeFunc("test_spam"), - ), - ] - collector = _discovery.TestCollector(tests=discovered) - - collector.pytest_collection_finish(session) - - self.maxDiff = None - self.assertEqual( - stub.calls, - [ - ("discovered.reset", None, None), - ( - "discovered.add_test", - None, - dict( - parents=[ - ("./x/y/z/test_eggs.py::SpamTests", "SpamTests", "suite"), - ("./x/y/z/test_eggs.py", "test_eggs.py", "file"), - ("./x/y/z", "z", "folder"), - ("./x/y", "y", "folder"), - ("./x", "x", "folder"), - (".", testroot, "folder"), - ], - test=info.SingleTestInfo( - id="./x/y/z/test_eggs.py::SpamTests::test_spam", - name="test_spam", - path=info.SingleTestPath( - root=testroot, - relfile=adapter_util.fix_relpath(relfile), - func="SpamTests.test_spam", - sub=None, - ), - source="{}:{}".format( - adapter_util.fix_relpath(srcfile), 13 - ), - markers=None, - parentid="./x/y/z/test_eggs.py::SpamTests", - ), - ), - ), - ( - "discovered.add_test", - None, - dict( - parents=[ - ("./x/y/z/test_eggs.py", "test_eggs.py", "file"), - ("./x/y/z", "z", "folder"), - ("./x/y", "y", "folder"), - ("./x", "x", "folder"), - (".", testroot, "folder"), - ], - test=info.SingleTestInfo( - id="./x/y/z/test_eggs.py::test_ham", - name="test_ham", - path=info.SingleTestPath( - root=testroot, - relfile=adapter_util.fix_relpath(relfile), - func="test_ham", - sub=None, - ), - source="{}:{}".format(adapter_util.fix_relpath(srcfile), 4), - markers=None, - parentid="./x/y/z/test_eggs.py", - ), - ), - ), - ], - ) diff --git a/pythonFiles/tests/testing_tools/adapter/test___main__.py b/pythonFiles/tests/testing_tools/adapter/test___main__.py deleted file mode 100644 index 53500a2f4afe..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/test___main__.py +++ /dev/null @@ -1,210 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -import unittest - -from ...util import Stub, StubProxy -from testing_tools.adapter.__main__ import ( - parse_args, - main, - UnsupportedToolError, - UnsupportedCommandError, -) - - -class StubTool(StubProxy): - def __init__(self, name, stub=None): - super(StubTool, self).__init__(stub, name) - self.return_discover = None - - def discover(self, args, **kwargs): - self.add_call("discover", (args,), kwargs) - if self.return_discover is None: - raise NotImplementedError - return self.return_discover - - -class StubReporter(StubProxy): - def __init__(self, stub=None): - super(StubReporter, self).__init__(stub, "reporter") - - def report(self, tests, parents, **kwargs): - self.add_call("report", (tests, parents), kwargs or None) - - -################################## -# tests - - -class ParseGeneralTests(unittest.TestCase): - def test_unsupported_command(self): - with self.assertRaises(SystemExit): - parse_args(["run", "pytest"]) - with self.assertRaises(SystemExit): - parse_args(["debug", "pytest"]) - with self.assertRaises(SystemExit): - parse_args(["???", "pytest"]) - - -class ParseDiscoverTests(unittest.TestCase): - def test_pytest_default(self): - tool, cmd, args, toolargs = parse_args( - [ - "discover", - "pytest", - ] - ) - - self.assertEqual(tool, "pytest") - self.assertEqual(cmd, "discover") - self.assertEqual(args, {"pretty": False, "hidestdio": True, "simple": False}) - self.assertEqual(toolargs, []) - - def test_pytest_full(self): - tool, cmd, args, toolargs = parse_args( - [ - "discover", - "pytest", - # no adapter-specific options yet - "--", - "--strict", - "--ignore", - "spam,ham,eggs", - "--pastebin=xyz", - "--no-cov", - "-d", - ] - ) - - self.assertEqual(tool, "pytest") - self.assertEqual(cmd, "discover") - self.assertEqual(args, {"pretty": False, "hidestdio": True, "simple": False}) - self.assertEqual( - toolargs, - [ - "--strict", - "--ignore", - "spam,ham,eggs", - "--pastebin=xyz", - "--no-cov", - "-d", - ], - ) - - def test_pytest_opts(self): - tool, cmd, args, toolargs = parse_args( - [ - "discover", - "pytest", - "--simple", - "--no-hide-stdio", - "--pretty", - ] - ) - - self.assertEqual(tool, "pytest") - self.assertEqual(cmd, "discover") - self.assertEqual(args, {"pretty": True, "hidestdio": False, "simple": True}) - self.assertEqual(toolargs, []) - - def test_unsupported_tool(self): - with self.assertRaises(SystemExit): - parse_args(["discover", "unittest"]) - with self.assertRaises(SystemExit): - parse_args(["discover", "nose"]) - with self.assertRaises(SystemExit): - parse_args(["discover", "???"]) - - -class MainTests(unittest.TestCase): - - # TODO: We could use an integration test for pytest.discover(). - - def test_discover(self): - stub = Stub() - tool = StubTool("spamspamspam", stub) - tests, parents = object(), object() - tool.return_discover = (parents, tests) - reporter = StubReporter(stub) - main( - tool.name, - "discover", - {"spam": "eggs"}, - [], - _tools={ - tool.name: { - "discover": tool.discover, - } - }, - _reporters={ - "discover": reporter.report, - }, - ) - - self.assertEqual( - tool.calls, - [ - ("spamspamspam.discover", ([],), {"spam": "eggs"}), - ("reporter.report", (tests, parents), {"spam": "eggs"}), - ], - ) - - def test_unsupported_tool(self): - with self.assertRaises(UnsupportedToolError): - main( - "unittest", - "discover", - {"spam": "eggs"}, - [], - _tools={"pytest": None}, - _reporters=None, - ) - with self.assertRaises(UnsupportedToolError): - main( - "nose", - "discover", - {"spam": "eggs"}, - [], - _tools={"pytest": None}, - _reporters=None, - ) - with self.assertRaises(UnsupportedToolError): - main( - "???", - "discover", - {"spam": "eggs"}, - [], - _tools={"pytest": None}, - _reporters=None, - ) - - def test_unsupported_command(self): - tool = StubTool("pytest") - with self.assertRaises(UnsupportedCommandError): - main( - "pytest", - "run", - {"spam": "eggs"}, - [], - _tools={"pytest": {"discover": tool.discover}}, - _reporters=None, - ) - with self.assertRaises(UnsupportedCommandError): - main( - "pytest", - "debug", - {"spam": "eggs"}, - [], - _tools={"pytest": {"discover": tool.discover}}, - _reporters=None, - ) - with self.assertRaises(UnsupportedCommandError): - main( - "pytest", - "???", - {"spam": "eggs"}, - [], - _tools={"pytest": {"discover": tool.discover}}, - _reporters=None, - ) - self.assertEqual(tool.calls, []) diff --git a/pythonFiles/tests/testing_tools/adapter/test_discovery.py b/pythonFiles/tests/testing_tools/adapter/test_discovery.py deleted file mode 100644 index ec3d198b0108..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/test_discovery.py +++ /dev/null @@ -1,675 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -from __future__ import absolute_import, print_function - -import unittest - -from testing_tools.adapter.util import fix_path, fix_relpath -from testing_tools.adapter.info import SingleTestInfo, SingleTestPath, ParentInfo -from testing_tools.adapter.discovery import fix_nodeid, DiscoveredTests - - -def _fix_nodeid(nodeid): - - nodeid = nodeid.replace("\\", "/") - if not nodeid.startswith("./"): - nodeid = "./" + nodeid - return nodeid - - -class DiscoveredTestsTests(unittest.TestCase): - def test_list(self): - testroot = fix_path("/a/b/c") - relfile = fix_path("./test_spam.py") - tests = [ - SingleTestInfo( - # missing "./": - id="test_spam.py::test_each[10-10]", - name="test_each[10-10]", - path=SingleTestPath( - root=testroot, - relfile=relfile, - func="test_each", - sub=["[10-10]"], - ), - source="{}:{}".format(relfile, 10), - markers=None, - # missing "./": - parentid="test_spam.py::test_each", - ), - SingleTestInfo( - id="test_spam.py::All::BasicTests::test_first", - name="test_first", - path=SingleTestPath( - root=testroot, - relfile=relfile, - func="All.BasicTests.test_first", - sub=None, - ), - source="{}:{}".format(relfile, 62), - markers=None, - parentid="test_spam.py::All::BasicTests", - ), - ] - allparents = [ - [ - (fix_path("./test_spam.py::test_each"), "test_each", "function"), - (fix_path("./test_spam.py"), "test_spam.py", "file"), - (".", testroot, "folder"), - ], - [ - (fix_path("./test_spam.py::All::BasicTests"), "BasicTests", "suite"), - (fix_path("./test_spam.py::All"), "All", "suite"), - (fix_path("./test_spam.py"), "test_spam.py", "file"), - (".", testroot, "folder"), - ], - ] - expected = [ - test._replace(id=_fix_nodeid(test.id), parentid=_fix_nodeid(test.parentid)) - for test in tests - ] - discovered = DiscoveredTests() - for test, parents in zip(tests, allparents): - discovered.add_test(test, parents) - size = len(discovered) - items = [discovered[0], discovered[1]] - snapshot = list(discovered) - - self.maxDiff = None - self.assertEqual(size, 2) - self.assertEqual(items, expected) - self.assertEqual(snapshot, expected) - - def test_reset(self): - testroot = fix_path("/a/b/c") - discovered = DiscoveredTests() - discovered.add_test( - SingleTestInfo( - id="./test_spam.py::test_each", - name="test_each", - path=SingleTestPath( - root=testroot, - relfile="test_spam.py", - func="test_each", - ), - source="test_spam.py:11", - markers=[], - parentid="./test_spam.py", - ), - [ - ("./test_spam.py", "test_spam.py", "file"), - (".", testroot, "folder"), - ], - ) - - before = len(discovered), len(discovered.parents) - discovered.reset() - after = len(discovered), len(discovered.parents) - - self.assertEqual(before, (1, 2)) - self.assertEqual(after, (0, 0)) - - def test_parents(self): - testroot = fix_path("/a/b/c") - relfile = fix_path("x/y/z/test_spam.py") - tests = [ - SingleTestInfo( - # missing "./", using pathsep: - id=relfile + "::test_each[10-10]", - name="test_each[10-10]", - path=SingleTestPath( - root=testroot, - relfile=fix_relpath(relfile), - func="test_each", - sub=["[10-10]"], - ), - source="{}:{}".format(relfile, 10), - markers=None, - # missing "./", using pathsep: - parentid=relfile + "::test_each", - ), - SingleTestInfo( - # missing "./", using pathsep: - id=relfile + "::All::BasicTests::test_first", - name="test_first", - path=SingleTestPath( - root=testroot, - relfile=fix_relpath(relfile), - func="All.BasicTests.test_first", - sub=None, - ), - source="{}:{}".format(relfile, 61), - markers=None, - # missing "./", using pathsep: - parentid=relfile + "::All::BasicTests", - ), - ] - allparents = [ - # missing "./", using pathsep: - [ - (relfile + "::test_each", "test_each", "function"), - (relfile, relfile, "file"), - (".", testroot, "folder"), - ], - # missing "./", using pathsep: - [ - (relfile + "::All::BasicTests", "BasicTests", "suite"), - (relfile + "::All", "All", "suite"), - (relfile, "test_spam.py", "file"), - (fix_path("x/y/z"), "z", "folder"), - (fix_path("x/y"), "y", "folder"), - (fix_path("./x"), "x", "folder"), - (".", testroot, "folder"), - ], - ] - discovered = DiscoveredTests() - for test, parents in zip(tests, allparents): - discovered.add_test(test, parents) - - parents = discovered.parents - - self.maxDiff = None - self.assertEqual( - parents, - [ - ParentInfo( - id=".", - kind="folder", - name=testroot, - ), - ParentInfo( - id="./x", - kind="folder", - name="x", - root=testroot, - relpath=fix_path("./x"), - parentid=".", - ), - ParentInfo( - id="./x/y", - kind="folder", - name="y", - root=testroot, - relpath=fix_path("./x/y"), - parentid="./x", - ), - ParentInfo( - id="./x/y/z", - kind="folder", - name="z", - root=testroot, - relpath=fix_path("./x/y/z"), - parentid="./x/y", - ), - ParentInfo( - id="./x/y/z/test_spam.py", - kind="file", - name="test_spam.py", - root=testroot, - relpath=fix_relpath(relfile), - parentid="./x/y/z", - ), - ParentInfo( - id="./x/y/z/test_spam.py::All", - kind="suite", - name="All", - root=testroot, - parentid="./x/y/z/test_spam.py", - ), - ParentInfo( - id="./x/y/z/test_spam.py::All::BasicTests", - kind="suite", - name="BasicTests", - root=testroot, - parentid="./x/y/z/test_spam.py::All", - ), - ParentInfo( - id="./x/y/z/test_spam.py::test_each", - kind="function", - name="test_each", - root=testroot, - parentid="./x/y/z/test_spam.py", - ), - ], - ) - - def test_add_test_simple(self): - testroot = fix_path("/a/b/c") - relfile = "test_spam.py" - test = SingleTestInfo( - # missing "./": - id=relfile + "::test_spam", - name="test_spam", - path=SingleTestPath( - root=testroot, - # missing "./": - relfile=relfile, - func="test_spam", - ), - # missing "./": - source="{}:{}".format(relfile, 11), - markers=[], - # missing "./": - parentid=relfile, - ) - expected = test._replace( - id=_fix_nodeid(test.id), parentid=_fix_nodeid(test.parentid) - ) - discovered = DiscoveredTests() - - before = list(discovered), discovered.parents - discovered.add_test( - test, - [ - (relfile, relfile, "file"), - (".", testroot, "folder"), - ], - ) - after = list(discovered), discovered.parents - - self.maxDiff = None - self.assertEqual(before, ([], [])) - self.assertEqual( - after, - ( - [expected], - [ - ParentInfo( - id=".", - kind="folder", - name=testroot, - ), - ParentInfo( - id="./test_spam.py", - kind="file", - name=relfile, - root=testroot, - relpath=relfile, - parentid=".", - ), - ], - ), - ) - - def test_multiroot(self): - # the first root - testroot1 = fix_path("/a/b/c") - relfile1 = "test_spam.py" - alltests = [ - SingleTestInfo( - # missing "./": - id=relfile1 + "::test_spam", - name="test_spam", - path=SingleTestPath( - root=testroot1, - relfile=fix_relpath(relfile1), - func="test_spam", - ), - source="{}:{}".format(relfile1, 10), - markers=[], - # missing "./": - parentid=relfile1, - ), - ] - allparents = [ - # missing "./": - [ - (relfile1, "test_spam.py", "file"), - (".", testroot1, "folder"), - ], - ] - # the second root - testroot2 = fix_path("/x/y/z") - relfile2 = fix_path("w/test_eggs.py") - alltests.extend( - [ - SingleTestInfo( - id=relfile2 + "::BasicTests::test_first", - name="test_first", - path=SingleTestPath( - root=testroot2, - relfile=fix_relpath(relfile2), - func="BasicTests.test_first", - ), - source="{}:{}".format(relfile2, 61), - markers=[], - parentid=relfile2 + "::BasicTests", - ), - ] - ) - allparents.extend( - [ - # missing "./", using pathsep: - [ - (relfile2 + "::BasicTests", "BasicTests", "suite"), - (relfile2, "test_eggs.py", "file"), - (fix_path("./w"), "w", "folder"), - (".", testroot2, "folder"), - ], - ] - ) - - discovered = DiscoveredTests() - for test, parents in zip(alltests, allparents): - discovered.add_test(test, parents) - tests = list(discovered) - parents = discovered.parents - - self.maxDiff = None - self.assertEqual( - tests, - [ - # the first root - SingleTestInfo( - id="./test_spam.py::test_spam", - name="test_spam", - path=SingleTestPath( - root=testroot1, - relfile=fix_relpath(relfile1), - func="test_spam", - ), - source="{}:{}".format(relfile1, 10), - markers=[], - parentid="./test_spam.py", - ), - # the secondroot - SingleTestInfo( - id="./w/test_eggs.py::BasicTests::test_first", - name="test_first", - path=SingleTestPath( - root=testroot2, - relfile=fix_relpath(relfile2), - func="BasicTests.test_first", - ), - source="{}:{}".format(relfile2, 61), - markers=[], - parentid="./w/test_eggs.py::BasicTests", - ), - ], - ) - self.assertEqual( - parents, - [ - # the first root - ParentInfo( - id=".", - kind="folder", - name=testroot1, - ), - ParentInfo( - id="./test_spam.py", - kind="file", - name="test_spam.py", - root=testroot1, - relpath=fix_relpath(relfile1), - parentid=".", - ), - # the secondroot - ParentInfo( - id=".", - kind="folder", - name=testroot2, - ), - ParentInfo( - id="./w", - kind="folder", - name="w", - root=testroot2, - relpath=fix_path("./w"), - parentid=".", - ), - ParentInfo( - id="./w/test_eggs.py", - kind="file", - name="test_eggs.py", - root=testroot2, - relpath=fix_relpath(relfile2), - parentid="./w", - ), - ParentInfo( - id="./w/test_eggs.py::BasicTests", - kind="suite", - name="BasicTests", - root=testroot2, - parentid="./w/test_eggs.py", - ), - ], - ) - - def test_doctest(self): - testroot = fix_path("/a/b/c") - doctestfile = fix_path("./x/test_doctest.txt") - relfile = fix_path("./x/y/z/test_eggs.py") - alltests = [ - SingleTestInfo( - id=doctestfile + "::test_doctest.txt", - name="test_doctest.txt", - path=SingleTestPath( - root=testroot, - relfile=doctestfile, - func=None, - ), - source="{}:{}".format(doctestfile, 0), - markers=[], - parentid=doctestfile, - ), - # With --doctest-modules - SingleTestInfo( - id=relfile + "::test_eggs", - name="test_eggs", - path=SingleTestPath( - root=testroot, - relfile=relfile, - func=None, - ), - source="{}:{}".format(relfile, 0), - markers=[], - parentid=relfile, - ), - SingleTestInfo( - id=relfile + "::test_eggs.TestSpam", - name="test_eggs.TestSpam", - path=SingleTestPath( - root=testroot, - relfile=relfile, - func=None, - ), - source="{}:{}".format(relfile, 12), - markers=[], - parentid=relfile, - ), - SingleTestInfo( - id=relfile + "::test_eggs.TestSpam.TestEggs", - name="test_eggs.TestSpam.TestEggs", - path=SingleTestPath( - root=testroot, - relfile=relfile, - func=None, - ), - source="{}:{}".format(relfile, 27), - markers=[], - parentid=relfile, - ), - ] - allparents = [ - [ - (doctestfile, "test_doctest.txt", "file"), - (fix_path("./x"), "x", "folder"), - (".", testroot, "folder"), - ], - [ - (relfile, "test_eggs.py", "file"), - (fix_path("./x/y/z"), "z", "folder"), - (fix_path("./x/y"), "y", "folder"), - (fix_path("./x"), "x", "folder"), - (".", testroot, "folder"), - ], - [ - (relfile, "test_eggs.py", "file"), - (fix_path("./x/y/z"), "z", "folder"), - (fix_path("./x/y"), "y", "folder"), - (fix_path("./x"), "x", "folder"), - (".", testroot, "folder"), - ], - [ - (relfile, "test_eggs.py", "file"), - (fix_path("./x/y/z"), "z", "folder"), - (fix_path("./x/y"), "y", "folder"), - (fix_path("./x"), "x", "folder"), - (".", testroot, "folder"), - ], - ] - expected = [ - test._replace(id=_fix_nodeid(test.id), parentid=_fix_nodeid(test.parentid)) - for test in alltests - ] - - discovered = DiscoveredTests() - - for test, parents in zip(alltests, allparents): - discovered.add_test(test, parents) - tests = list(discovered) - parents = discovered.parents - - self.maxDiff = None - self.assertEqual(tests, expected) - self.assertEqual( - parents, - [ - ParentInfo( - id=".", - kind="folder", - name=testroot, - ), - ParentInfo( - id="./x", - kind="folder", - name="x", - root=testroot, - relpath=fix_path("./x"), - parentid=".", - ), - ParentInfo( - id="./x/test_doctest.txt", - kind="file", - name="test_doctest.txt", - root=testroot, - relpath=fix_path(doctestfile), - parentid="./x", - ), - ParentInfo( - id="./x/y", - kind="folder", - name="y", - root=testroot, - relpath=fix_path("./x/y"), - parentid="./x", - ), - ParentInfo( - id="./x/y/z", - kind="folder", - name="z", - root=testroot, - relpath=fix_path("./x/y/z"), - parentid="./x/y", - ), - ParentInfo( - id="./x/y/z/test_eggs.py", - kind="file", - name="test_eggs.py", - root=testroot, - relpath=fix_relpath(relfile), - parentid="./x/y/z", - ), - ], - ) - - def test_nested_suite_simple(self): - testroot = fix_path("/a/b/c") - relfile = fix_path("./test_eggs.py") - alltests = [ - SingleTestInfo( - id=relfile + "::TestOuter::TestInner::test_spam", - name="test_spam", - path=SingleTestPath( - root=testroot, - relfile=relfile, - func="TestOuter.TestInner.test_spam", - ), - source="{}:{}".format(relfile, 10), - markers=None, - parentid=relfile + "::TestOuter::TestInner", - ), - SingleTestInfo( - id=relfile + "::TestOuter::TestInner::test_eggs", - name="test_eggs", - path=SingleTestPath( - root=testroot, - relfile=relfile, - func="TestOuter.TestInner.test_eggs", - ), - source="{}:{}".format(relfile, 21), - markers=None, - parentid=relfile + "::TestOuter::TestInner", - ), - ] - allparents = [ - [ - (relfile + "::TestOuter::TestInner", "TestInner", "suite"), - (relfile + "::TestOuter", "TestOuter", "suite"), - (relfile, "test_eggs.py", "file"), - (".", testroot, "folder"), - ], - [ - (relfile + "::TestOuter::TestInner", "TestInner", "suite"), - (relfile + "::TestOuter", "TestOuter", "suite"), - (relfile, "test_eggs.py", "file"), - (".", testroot, "folder"), - ], - ] - expected = [ - test._replace(id=_fix_nodeid(test.id), parentid=_fix_nodeid(test.parentid)) - for test in alltests - ] - - discovered = DiscoveredTests() - for test, parents in zip(alltests, allparents): - discovered.add_test(test, parents) - tests = list(discovered) - parents = discovered.parents - - self.maxDiff = None - self.assertEqual(tests, expected) - self.assertEqual( - parents, - [ - ParentInfo( - id=".", - kind="folder", - name=testroot, - ), - ParentInfo( - id="./test_eggs.py", - kind="file", - name="test_eggs.py", - root=testroot, - relpath=fix_relpath(relfile), - parentid=".", - ), - ParentInfo( - id="./test_eggs.py::TestOuter", - kind="suite", - name="TestOuter", - root=testroot, - parentid="./test_eggs.py", - ), - ParentInfo( - id="./test_eggs.py::TestOuter::TestInner", - kind="suite", - name="TestInner", - root=testroot, - parentid="./test_eggs.py::TestOuter", - ), - ], - ) diff --git a/pythonFiles/tests/testing_tools/adapter/test_functional.py b/pythonFiles/tests/testing_tools/adapter/test_functional.py deleted file mode 100644 index 153ad5508d9b..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/test_functional.py +++ /dev/null @@ -1,1535 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -from __future__ import absolute_import, unicode_literals - -import json -import os -import os.path -import subprocess -import sys -import unittest - -from ...__main__ import TESTING_TOOLS_ROOT -from testing_tools.adapter.util import fix_path, PATH_SEP - -# Pytest 3.7 and later uses pathlib/pathlib2 for path resolution. -try: - from pathlib import Path -except ImportError: - from pathlib2 import Path # type: ignore (for Pylance) - - -CWD = os.getcwd() -DATA_DIR = os.path.join(os.path.dirname(__file__), ".data") -SCRIPT = os.path.join(TESTING_TOOLS_ROOT, "run_adapter.py") - - -def resolve_testroot(name): - projroot = os.path.join(DATA_DIR, name) - testroot = os.path.join(projroot, "tests") - return str(Path(projroot).resolve()), str(Path(testroot).resolve()) - - -def run_adapter(cmd, tool, *cliargs): - try: - return _run_adapter(cmd, tool, *cliargs) - except subprocess.CalledProcessError as exc: - print(exc.output) - - -def _run_adapter(cmd, tool, *cliargs, **kwargs): - hidestdio = kwargs.pop("hidestdio", True) - assert not kwargs or tuple(kwargs) == ("stderr",) - kwds = kwargs - argv = [sys.executable, SCRIPT, cmd, tool, "--"] + list(cliargs) - if not hidestdio: - argv.insert(4, "--no-hide-stdio") - kwds["stderr"] = subprocess.STDOUT - argv.append("--cache-clear") - print( - "running {!r}".format(" ".join(arg.rpartition(CWD + "/")[-1] for arg in argv)) - ) - output = subprocess.check_output(argv, universal_newlines=True, **kwds) - return output - - -def fix_test_order(tests): - if sys.version_info >= (3, 6): - return tests - fixed = [] - curfile = None - group = [] - for test in tests: - if (curfile or "???") not in test["id"]: - fixed.extend(sorted(group, key=lambda t: t["id"])) - group = [] - curfile = test["id"].partition(".py::")[0] + ".py" - group.append(test) - fixed.extend(sorted(group, key=lambda t: t["id"])) - return fixed - - -def fix_source(tests, testid, srcfile, lineno): - for test in tests: - if test["id"] == testid: - break - else: - raise KeyError("test {!r} not found".format(testid)) - if not srcfile: - srcfile = test["source"].rpartition(":")[0] - 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. - - -class PytestTests(unittest.TestCase): - def setUp(self): - if PATH_SEP is not os.path.sep: - raise unittest.SkipTest("functional tests require unmodified env") - super(PytestTests, self).setUp() - - def complex(self, testroot): - results = COMPLEX.copy() - results["root"] = testroot - return [results] - - def test_discover_simple(self): - projroot, testroot = resolve_testroot("simple") - - out = run_adapter("discover", "pytest", "--rootdir", projroot, testroot) - result = json.loads(out) - - self.maxDiff = None - self.assertEqual( - result, - [ - { - "root": projroot, - "rootid": ".", - "parents": [ - { - "id": "./tests", - "kind": "folder", - "name": "tests", - "relpath": fix_path("./tests"), - "parentid": ".", - }, - { - "id": "./tests/test_spam.py", - "kind": "file", - "name": "test_spam.py", - "relpath": fix_path("./tests/test_spam.py"), - "parentid": "./tests", - }, - ], - "tests": [ - { - "id": "./tests/test_spam.py::test_simple", - "name": "test_simple", - "source": fix_path("./tests/test_spam.py:2"), - "markers": [], - "parentid": "./tests/test_spam.py", - }, - ], - } - ], - ) - - def test_discover_complex_default(self): - projroot, testroot = resolve_testroot("complex") - expected = self.complex(projroot) - expected[0]["tests"] = fix_test_order(expected[0]["tests"]) - if sys.version_info < (3,): - decorated = [ - "./tests/test_unittest.py::MyTests::test_skipped", - "./tests/test_unittest.py::MyTests::test_maybe_skipped", - "./tests/test_unittest.py::MyTests::test_maybe_not_skipped", - ] - for testid in decorated: - fix_source(expected[0]["tests"], testid, None, 0) - - out = run_adapter("discover", "pytest", "--rootdir", projroot, testroot) - result = json.loads(out) - result[0]["tests"] = fix_test_order(result[0]["tests"]) - - self.maxDiff = None - self.assertEqual(sorted_object(result), sorted_object(expected)) - - def test_discover_complex_doctest(self): - projroot, _ = resolve_testroot("complex") - expected = self.complex(projroot) - # add in doctests from test suite - expected[0]["parents"].insert( - 3, - { - "id": "./tests/test_doctest.py", - "kind": "file", - "name": "test_doctest.py", - "relpath": fix_path("./tests/test_doctest.py"), - "parentid": "./tests", - }, - ) - expected[0]["tests"].insert( - 2, - { - "id": "./tests/test_doctest.py::tests.test_doctest", - "name": "tests.test_doctest", - "source": fix_path("./tests/test_doctest.py:1"), - "markers": [], - "parentid": "./tests/test_doctest.py", - }, - ) - # add in doctests from non-test module - expected[0]["parents"].insert( - 0, - { - "id": "./mod.py", - "kind": "file", - "name": "mod.py", - "relpath": fix_path("./mod.py"), - "parentid": ".", - }, - ) - expected[0]["tests"] = [ - { - "id": "./mod.py::mod", - "name": "mod", - "source": fix_path("./mod.py:1"), - "markers": [], - "parentid": "./mod.py", - }, - { - "id": "./mod.py::mod.Spam", - "name": "mod.Spam", - "source": fix_path("./mod.py:33"), - "markers": [], - "parentid": "./mod.py", - }, - { - "id": "./mod.py::mod.Spam.eggs", - "name": "mod.Spam.eggs", - "source": fix_path("./mod.py:43"), - "markers": [], - "parentid": "./mod.py", - }, - { - "id": "./mod.py::mod.square", - "name": "mod.square", - "source": fix_path("./mod.py:18"), - "markers": [], - "parentid": "./mod.py", - }, - ] + expected[0]["tests"] - expected[0]["tests"] = fix_test_order(expected[0]["tests"]) - if sys.version_info < (3,): - decorated = [ - "./tests/test_unittest.py::MyTests::test_skipped", - "./tests/test_unittest.py::MyTests::test_maybe_skipped", - "./tests/test_unittest.py::MyTests::test_maybe_not_skipped", - ] - for testid in decorated: - fix_source(expected[0]["tests"], testid, None, 0) - - out = run_adapter( - "discover", "pytest", "--rootdir", projroot, "--doctest-modules", projroot - ) - result = json.loads(out) - result[0]["tests"] = fix_test_order(result[0]["tests"]) - - self.maxDiff = None - self.assertEqual(sorted_object(result), sorted_object(expected)) - - def test_discover_not_found(self): - projroot, testroot = resolve_testroot("notests") - - out = run_adapter("discover", "pytest", "--rootdir", projroot, testroot) - result = json.loads(out) - - self.maxDiff = None - self.assertEqual(result, []) - # TODO: Expect the following instead? - # self.assertEqual(result, [{ - # 'root': projroot, - # 'rootid': '.', - # 'parents': [], - # 'tests': [], - # }]) - - @unittest.skip("broken in CI") - def test_discover_bad_args(self): - projroot, testroot = resolve_testroot("simple") - - with self.assertRaises(subprocess.CalledProcessError) as cm: - _run_adapter( - "discover", - "pytest", - "--spam", - "--rootdir", - projroot, - testroot, - stderr=subprocess.STDOUT, - ) - self.assertIn("(exit code 4)", cm.exception.output) - - def test_discover_syntax_error(self): - projroot, testroot = resolve_testroot("syntax-error") - - with self.assertRaises(subprocess.CalledProcessError) as cm: - _run_adapter( - "discover", - "pytest", - "--rootdir", - projroot, - testroot, - stderr=subprocess.STDOUT, - ) - self.assertIn("(exit code 2)", cm.exception.output) - - def test_discover_normcase(self): - projroot, testroot = resolve_testroot("NormCase") - - out = run_adapter("discover", "pytest", "--rootdir", projroot, testroot) - result = json.loads(out) - - self.maxDiff = None - self.assertTrue(projroot.endswith("NormCase")) - self.assertEqual( - result, - [ - { - "root": projroot, - "rootid": ".", - "parents": [ - { - "id": "./tests", - "kind": "folder", - "name": "tests", - "relpath": fix_path("./tests"), - "parentid": ".", - }, - { - "id": "./tests/A", - "kind": "folder", - "name": "A", - "relpath": fix_path("./tests/A"), - "parentid": "./tests", - }, - { - "id": "./tests/A/b", - "kind": "folder", - "name": "b", - "relpath": fix_path("./tests/A/b"), - "parentid": "./tests/A", - }, - { - "id": "./tests/A/b/C", - "kind": "folder", - "name": "C", - "relpath": fix_path("./tests/A/b/C"), - "parentid": "./tests/A/b", - }, - { - "id": "./tests/A/b/C/test_Spam.py", - "kind": "file", - "name": "test_Spam.py", - "relpath": fix_path("./tests/A/b/C/test_Spam.py"), - "parentid": "./tests/A/b/C", - }, - ], - "tests": [ - { - "id": "./tests/A/b/C/test_Spam.py::test_okay", - "name": "test_okay", - "source": fix_path("./tests/A/b/C/test_Spam.py:2"), - "markers": [], - "parentid": "./tests/A/b/C/test_Spam.py", - }, - ], - } - ], - ) - - -COMPLEX = { - "root": None, - "rootid": ".", - "parents": [ - # - { - "id": "./tests", - "kind": "folder", - "name": "tests", - "relpath": fix_path("./tests"), - "parentid": ".", - }, - # +++ - { - "id": "./tests/test_42-43.py", - "kind": "file", - "name": "test_42-43.py", - "relpath": fix_path("./tests/test_42-43.py"), - "parentid": "./tests", - }, - # +++ - { - "id": "./tests/test_42.py", - "kind": "file", - "name": "test_42.py", - "relpath": fix_path("./tests/test_42.py"), - "parentid": "./tests", - }, - # +++ - { - "id": "./tests/test_doctest.txt", - "kind": "file", - "name": "test_doctest.txt", - "relpath": fix_path("./tests/test_doctest.txt"), - "parentid": "./tests", - }, - # +++ - { - "id": "./tests/test_foo.py", - "kind": "file", - "name": "test_foo.py", - "relpath": fix_path("./tests/test_foo.py"), - "parentid": "./tests", - }, - # +++ - { - "id": "./tests/test_mixed.py", - "kind": "file", - "name": "test_mixed.py", - "relpath": fix_path("./tests/test_mixed.py"), - "parentid": "./tests", - }, - { - "id": "./tests/test_mixed.py::MyTests", - "kind": "suite", - "name": "MyTests", - "parentid": "./tests/test_mixed.py", - }, - { - "id": "./tests/test_mixed.py::TestMySuite", - "kind": "suite", - "name": "TestMySuite", - "parentid": "./tests/test_mixed.py", - }, - # +++ - { - "id": "./tests/test_pytest.py", - "kind": "file", - "name": "test_pytest.py", - "relpath": fix_path("./tests/test_pytest.py"), - "parentid": "./tests", - }, - { - "id": "./tests/test_pytest.py::TestEggs", - "kind": "suite", - "name": "TestEggs", - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::TestParam", - "kind": "suite", - "name": "TestParam", - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::TestParam::test_param_13", - "kind": "function", - "name": "test_param_13", - "parentid": "./tests/test_pytest.py::TestParam", - }, - { - "id": "./tests/test_pytest.py::TestParamAll", - "kind": "suite", - "name": "TestParamAll", - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::TestParamAll::test_param_13", - "kind": "function", - "name": "test_param_13", - "parentid": "./tests/test_pytest.py::TestParamAll", - }, - { - "id": "./tests/test_pytest.py::TestParamAll::test_spam_13", - "kind": "function", - "name": "test_spam_13", - "parentid": "./tests/test_pytest.py::TestParamAll", - }, - { - "id": "./tests/test_pytest.py::TestSpam", - "kind": "suite", - "name": "TestSpam", - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::TestSpam::TestHam", - "kind": "suite", - "name": "TestHam", - "parentid": "./tests/test_pytest.py::TestSpam", - }, - { - "id": "./tests/test_pytest.py::TestSpam::TestHam::TestEggs", - "kind": "suite", - "name": "TestEggs", - "parentid": "./tests/test_pytest.py::TestSpam::TestHam", - }, - { - "id": "./tests/test_pytest.py::test_fixture_param", - "kind": "function", - "name": "test_fixture_param", - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_param_01", - "kind": "function", - "name": "test_param_01", - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_param_11", - "kind": "function", - "name": "test_param_11", - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_param_13", - "kind": "function", - "name": "test_param_13", - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_param_13_markers", - "kind": "function", - "name": "test_param_13_markers", - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_param_13_repeat", - "kind": "function", - "name": "test_param_13_repeat", - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_param_13_skipped", - "kind": "function", - "name": "test_param_13_skipped", - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_param_23_13", - "kind": "function", - "name": "test_param_23_13", - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_param_23_raises", - "kind": "function", - "name": "test_param_23_raises", - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_param_33", - "kind": "function", - "name": "test_param_33", - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_param_33_ids", - "kind": "function", - "name": "test_param_33_ids", - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_param_fixture", - "kind": "function", - "name": "test_param_fixture", - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_param_mark_fixture", - "kind": "function", - "name": "test_param_mark_fixture", - "parentid": "./tests/test_pytest.py", - }, - # +++ - { - "id": "./tests/test_pytest_param.py", - "kind": "file", - "name": "test_pytest_param.py", - "relpath": fix_path("./tests/test_pytest_param.py"), - "parentid": "./tests", - }, - { - "id": "./tests/test_pytest_param.py::TestParamAll", - "kind": "suite", - "name": "TestParamAll", - "parentid": "./tests/test_pytest_param.py", - }, - { - "id": "./tests/test_pytest_param.py::TestParamAll::test_param_13", - "kind": "function", - "name": "test_param_13", - "parentid": "./tests/test_pytest_param.py::TestParamAll", - }, - { - "id": "./tests/test_pytest_param.py::TestParamAll::test_spam_13", - "kind": "function", - "name": "test_spam_13", - "parentid": "./tests/test_pytest_param.py::TestParamAll", - }, - { - "id": "./tests/test_pytest_param.py::test_param_13", - "kind": "function", - "name": "test_param_13", - "parentid": "./tests/test_pytest_param.py", - }, - # +++ - { - "id": "./tests/test_unittest.py", - "kind": "file", - "name": "test_unittest.py", - "relpath": fix_path("./tests/test_unittest.py"), - "parentid": "./tests", - }, - { - "id": "./tests/test_unittest.py::MyTests", - "kind": "suite", - "name": "MyTests", - "parentid": "./tests/test_unittest.py", - }, - { - "id": "./tests/test_unittest.py::OtherTests", - "kind": "suite", - "name": "OtherTests", - "parentid": "./tests/test_unittest.py", - }, - ## - { - "id": "./tests/v", - "kind": "folder", - "name": "v", - "relpath": fix_path("./tests/v"), - "parentid": "./tests", - }, - ## +++ - { - "id": "./tests/v/test_eggs.py", - "kind": "file", - "name": "test_eggs.py", - "relpath": fix_path("./tests/v/test_eggs.py"), - "parentid": "./tests/v", - }, - { - "id": "./tests/v/test_eggs.py::TestSimple", - "kind": "suite", - "name": "TestSimple", - "parentid": "./tests/v/test_eggs.py", - }, - ## +++ - { - "id": "./tests/v/test_ham.py", - "kind": "file", - "name": "test_ham.py", - "relpath": fix_path("./tests/v/test_ham.py"), - "parentid": "./tests/v", - }, - ## +++ - { - "id": "./tests/v/test_spam.py", - "kind": "file", - "name": "test_spam.py", - "relpath": fix_path("./tests/v/test_spam.py"), - "parentid": "./tests/v", - }, - ## - { - "id": "./tests/w", - "kind": "folder", - "name": "w", - "relpath": fix_path("./tests/w"), - "parentid": "./tests", - }, - ## +++ - { - "id": "./tests/w/test_spam.py", - "kind": "file", - "name": "test_spam.py", - "relpath": fix_path("./tests/w/test_spam.py"), - "parentid": "./tests/w", - }, - ## +++ - { - "id": "./tests/w/test_spam_ex.py", - "kind": "file", - "name": "test_spam_ex.py", - "relpath": fix_path("./tests/w/test_spam_ex.py"), - "parentid": "./tests/w", - }, - ## - { - "id": "./tests/x", - "kind": "folder", - "name": "x", - "relpath": fix_path("./tests/x"), - "parentid": "./tests", - }, - ### - { - "id": "./tests/x/y", - "kind": "folder", - "name": "y", - "relpath": fix_path("./tests/x/y"), - "parentid": "./tests/x", - }, - #### - { - "id": "./tests/x/y/z", - "kind": "folder", - "name": "z", - "relpath": fix_path("./tests/x/y/z"), - "parentid": "./tests/x/y", - }, - ##### - { - "id": "./tests/x/y/z/a", - "kind": "folder", - "name": "a", - "relpath": fix_path("./tests/x/y/z/a"), - "parentid": "./tests/x/y/z", - }, - ##### +++ - { - "id": "./tests/x/y/z/a/test_spam.py", - "kind": "file", - "name": "test_spam.py", - "relpath": fix_path("./tests/x/y/z/a/test_spam.py"), - "parentid": "./tests/x/y/z/a", - }, - ##### - { - "id": "./tests/x/y/z/b", - "kind": "folder", - "name": "b", - "relpath": fix_path("./tests/x/y/z/b"), - "parentid": "./tests/x/y/z", - }, - ##### +++ - { - "id": "./tests/x/y/z/b/test_spam.py", - "kind": "file", - "name": "test_spam.py", - "relpath": fix_path("./tests/x/y/z/b/test_spam.py"), - "parentid": "./tests/x/y/z/b", - }, - #### +++ - { - "id": "./tests/x/y/z/test_ham.py", - "kind": "file", - "name": "test_ham.py", - "relpath": fix_path("./tests/x/y/z/test_ham.py"), - "parentid": "./tests/x/y/z", - }, - ], - "tests": [ - ########## - { - "id": "./tests/test_42-43.py::test_simple", - "name": "test_simple", - "source": fix_path("./tests/test_42-43.py:2"), - "markers": [], - "parentid": "./tests/test_42-43.py", - }, - ##### - { - "id": "./tests/test_42.py::test_simple", - "name": "test_simple", - "source": fix_path("./tests/test_42.py:2"), - "markers": [], - "parentid": "./tests/test_42.py", - }, - ##### - { - "id": "./tests/test_doctest.txt::test_doctest.txt", - "name": "test_doctest.txt", - "source": fix_path("./tests/test_doctest.txt:1"), - "markers": [], - "parentid": "./tests/test_doctest.txt", - }, - ##### - { - "id": "./tests/test_foo.py::test_simple", - "name": "test_simple", - "source": fix_path("./tests/test_foo.py:3"), - "markers": [], - "parentid": "./tests/test_foo.py", - }, - ##### - { - "id": "./tests/test_mixed.py::test_top_level", - "name": "test_top_level", - "source": fix_path("./tests/test_mixed.py:5"), - "markers": [], - "parentid": "./tests/test_mixed.py", - }, - { - "id": "./tests/test_mixed.py::test_skipped", - "name": "test_skipped", - "source": fix_path("./tests/test_mixed.py:9"), - "markers": ["skip"], - "parentid": "./tests/test_mixed.py", - }, - { - "id": "./tests/test_mixed.py::TestMySuite::test_simple", - "name": "test_simple", - "source": fix_path("./tests/test_mixed.py:16"), - "markers": [], - "parentid": "./tests/test_mixed.py::TestMySuite", - }, - { - "id": "./tests/test_mixed.py::MyTests::test_simple", - "name": "test_simple", - "source": fix_path("./tests/test_mixed.py:22"), - "markers": [], - "parentid": "./tests/test_mixed.py::MyTests", - }, - { - "id": "./tests/test_mixed.py::MyTests::test_skipped", - "name": "test_skipped", - "source": fix_path("./tests/test_mixed.py:25"), - "markers": ["skip"], - "parentid": "./tests/test_mixed.py::MyTests", - }, - ##### - { - "id": "./tests/test_pytest.py::test_simple", - "name": "test_simple", - "source": fix_path("./tests/test_pytest.py:6"), - "markers": [], - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_failure", - "name": "test_failure", - "source": fix_path("./tests/test_pytest.py:10"), - "markers": [], - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_runtime_skipped", - "name": "test_runtime_skipped", - "source": fix_path("./tests/test_pytest.py:14"), - "markers": [], - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_runtime_failed", - "name": "test_runtime_failed", - "source": fix_path("./tests/test_pytest.py:18"), - "markers": [], - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_raises", - "name": "test_raises", - "source": fix_path("./tests/test_pytest.py:22"), - "markers": [], - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_skipped", - "name": "test_skipped", - "source": fix_path("./tests/test_pytest.py:26"), - "markers": ["skip"], - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_maybe_skipped", - "name": "test_maybe_skipped", - "source": fix_path("./tests/test_pytest.py:31"), - "markers": ["skip-if"], - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_known_failure", - "name": "test_known_failure", - "source": fix_path("./tests/test_pytest.py:36"), - "markers": ["expected-failure"], - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_warned", - "name": "test_warned", - "source": fix_path("./tests/test_pytest.py:41"), - "markers": [], - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_custom_marker", - "name": "test_custom_marker", - "source": fix_path("./tests/test_pytest.py:46"), - "markers": [], - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_multiple_markers", - "name": "test_multiple_markers", - "source": fix_path("./tests/test_pytest.py:51"), - "markers": ["expected-failure", "skip", "skip-if"], - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_dynamic_1", - "name": "test_dynamic_1", - "source": fix_path("./tests/test_pytest.py:62"), - "markers": [], - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_dynamic_2", - "name": "test_dynamic_2", - "source": fix_path("./tests/test_pytest.py:62"), - "markers": [], - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_dynamic_3", - "name": "test_dynamic_3", - "source": fix_path("./tests/test_pytest.py:62"), - "markers": [], - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::TestSpam::test_simple", - "name": "test_simple", - "source": fix_path("./tests/test_pytest.py:70"), - "markers": [], - "parentid": "./tests/test_pytest.py::TestSpam", - }, - { - "id": "./tests/test_pytest.py::TestSpam::test_skipped", - "name": "test_skipped", - "source": fix_path("./tests/test_pytest.py:73"), - "markers": ["skip"], - "parentid": "./tests/test_pytest.py::TestSpam", - }, - { - "id": "./tests/test_pytest.py::TestSpam::TestHam::TestEggs::test_simple", - "name": "test_simple", - "source": fix_path("./tests/test_pytest.py:81"), - "markers": [], - "parentid": "./tests/test_pytest.py::TestSpam::TestHam::TestEggs", - }, - { - "id": "./tests/test_pytest.py::TestEggs::test_simple", - "name": "test_simple", - "source": fix_path("./tests/test_pytest.py:93"), - "markers": [], - "parentid": "./tests/test_pytest.py::TestEggs", - }, - { - "id": "./tests/test_pytest.py::test_param_01[]", - "name": "test_param_01[]", - "source": fix_path("./tests/test_pytest.py:103"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_01", - }, - { - "id": "./tests/test_pytest.py::test_param_11[x0]", - "name": "test_param_11[x0]", - "source": fix_path("./tests/test_pytest.py:108"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_11", - }, - { - "id": "./tests/test_pytest.py::test_param_13[x0]", - "name": "test_param_13[x0]", - "source": fix_path("./tests/test_pytest.py:113"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_13", - }, - { - "id": "./tests/test_pytest.py::test_param_13[x1]", - "name": "test_param_13[x1]", - "source": fix_path("./tests/test_pytest.py:113"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_13", - }, - { - "id": "./tests/test_pytest.py::test_param_13[x2]", - "name": "test_param_13[x2]", - "source": fix_path("./tests/test_pytest.py:113"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_13", - }, - { - "id": "./tests/test_pytest.py::test_param_13_repeat[x0]", - "name": "test_param_13_repeat[x0]", - "source": fix_path("./tests/test_pytest.py:118"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_13_repeat", - }, - { - "id": "./tests/test_pytest.py::test_param_13_repeat[x1]", - "name": "test_param_13_repeat[x1]", - "source": fix_path("./tests/test_pytest.py:118"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_13_repeat", - }, - { - "id": "./tests/test_pytest.py::test_param_13_repeat[x2]", - "name": "test_param_13_repeat[x2]", - "source": fix_path("./tests/test_pytest.py:118"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_13_repeat", - }, - { - "id": "./tests/test_pytest.py::test_param_33[1-1-1]", - "name": "test_param_33[1-1-1]", - "source": fix_path("./tests/test_pytest.py:123"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_33", - }, - { - "id": "./tests/test_pytest.py::test_param_33[3-4-5]", - "name": "test_param_33[3-4-5]", - "source": fix_path("./tests/test_pytest.py:123"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_33", - }, - { - "id": "./tests/test_pytest.py::test_param_33[0-0-0]", - "name": "test_param_33[0-0-0]", - "source": fix_path("./tests/test_pytest.py:123"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_33", - }, - { - "id": "./tests/test_pytest.py::test_param_33_ids[v1]", - "name": "test_param_33_ids[v1]", - "source": fix_path("./tests/test_pytest.py:128"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_33_ids", - }, - { - "id": "./tests/test_pytest.py::test_param_33_ids[v2]", - "name": "test_param_33_ids[v2]", - "source": fix_path("./tests/test_pytest.py:128"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_33_ids", - }, - { - "id": "./tests/test_pytest.py::test_param_33_ids[v3]", - "name": "test_param_33_ids[v3]", - "source": fix_path("./tests/test_pytest.py:128"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_33_ids", - }, - { - "id": "./tests/test_pytest.py::test_param_23_13[1-1-z0]", - "name": "test_param_23_13[1-1-z0]", - "source": fix_path("./tests/test_pytest.py:134"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_23_13", - }, - { - "id": "./tests/test_pytest.py::test_param_23_13[1-1-z1]", - "name": "test_param_23_13[1-1-z1]", - "source": fix_path("./tests/test_pytest.py:134"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_23_13", - }, - { - "id": "./tests/test_pytest.py::test_param_23_13[1-1-z2]", - "name": "test_param_23_13[1-1-z2]", - "source": fix_path("./tests/test_pytest.py:134"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_23_13", - }, - { - "id": "./tests/test_pytest.py::test_param_23_13[3-4-z0]", - "name": "test_param_23_13[3-4-z0]", - "source": fix_path("./tests/test_pytest.py:134"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_23_13", - }, - { - "id": "./tests/test_pytest.py::test_param_23_13[3-4-z1]", - "name": "test_param_23_13[3-4-z1]", - "source": fix_path("./tests/test_pytest.py:134"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_23_13", - }, - { - "id": "./tests/test_pytest.py::test_param_23_13[3-4-z2]", - "name": "test_param_23_13[3-4-z2]", - "source": fix_path("./tests/test_pytest.py:134"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_23_13", - }, - { - "id": "./tests/test_pytest.py::test_param_23_13[0-0-z0]", - "name": "test_param_23_13[0-0-z0]", - "source": fix_path("./tests/test_pytest.py:134"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_23_13", - }, - { - "id": "./tests/test_pytest.py::test_param_23_13[0-0-z1]", - "name": "test_param_23_13[0-0-z1]", - "source": fix_path("./tests/test_pytest.py:134"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_23_13", - }, - { - "id": "./tests/test_pytest.py::test_param_23_13[0-0-z2]", - "name": "test_param_23_13[0-0-z2]", - "source": fix_path("./tests/test_pytest.py:134"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_23_13", - }, - { - "id": "./tests/test_pytest.py::test_param_13_markers[x0]", - "name": "test_param_13_markers[x0]", - "source": fix_path("./tests/test_pytest.py:140"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_13_markers", - }, - { - "id": "./tests/test_pytest.py::test_param_13_markers[???]", - "name": "test_param_13_markers[???]", - "source": fix_path("./tests/test_pytest.py:140"), - "markers": ["skip"], - "parentid": "./tests/test_pytest.py::test_param_13_markers", - }, - { - "id": "./tests/test_pytest.py::test_param_13_markers[2]", - "name": "test_param_13_markers[2]", - "source": fix_path("./tests/test_pytest.py:140"), - "markers": ["expected-failure"], - "parentid": "./tests/test_pytest.py::test_param_13_markers", - }, - { - "id": "./tests/test_pytest.py::test_param_13_skipped[x0]", - "name": "test_param_13_skipped[x0]", - "source": fix_path("./tests/test_pytest.py:149"), - "markers": ["skip"], - "parentid": "./tests/test_pytest.py::test_param_13_skipped", - }, - { - "id": "./tests/test_pytest.py::test_param_13_skipped[x1]", - "name": "test_param_13_skipped[x1]", - "source": fix_path("./tests/test_pytest.py:149"), - "markers": ["skip"], - "parentid": "./tests/test_pytest.py::test_param_13_skipped", - }, - { - "id": "./tests/test_pytest.py::test_param_13_skipped[x2]", - "name": "test_param_13_skipped[x2]", - "source": fix_path("./tests/test_pytest.py:149"), - "markers": ["skip"], - "parentid": "./tests/test_pytest.py::test_param_13_skipped", - }, - { - "id": "./tests/test_pytest.py::test_param_23_raises[1-None]", - "name": "test_param_23_raises[1-None]", - "source": fix_path("./tests/test_pytest.py:155"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_23_raises", - }, - { - "id": "./tests/test_pytest.py::test_param_23_raises[1.0-None]", - "name": "test_param_23_raises[1.0-None]", - "source": fix_path("./tests/test_pytest.py:155"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_23_raises", - }, - { - "id": "./tests/test_pytest.py::test_param_23_raises[2-catch2]", - "name": "test_param_23_raises[2-catch2]", - "source": fix_path("./tests/test_pytest.py:155"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_23_raises", - }, - { - "id": "./tests/test_pytest.py::TestParam::test_simple", - "name": "test_simple", - "source": fix_path("./tests/test_pytest.py:164"), - "markers": [], - "parentid": "./tests/test_pytest.py::TestParam", - }, - { - "id": "./tests/test_pytest.py::TestParam::test_param_13[x0]", - "name": "test_param_13[x0]", - "source": fix_path("./tests/test_pytest.py:167"), - "markers": [], - "parentid": "./tests/test_pytest.py::TestParam::test_param_13", - }, - { - "id": "./tests/test_pytest.py::TestParam::test_param_13[x1]", - "name": "test_param_13[x1]", - "source": fix_path("./tests/test_pytest.py:167"), - "markers": [], - "parentid": "./tests/test_pytest.py::TestParam::test_param_13", - }, - { - "id": "./tests/test_pytest.py::TestParam::test_param_13[x2]", - "name": "test_param_13[x2]", - "source": fix_path("./tests/test_pytest.py:167"), - "markers": [], - "parentid": "./tests/test_pytest.py::TestParam::test_param_13", - }, - { - "id": "./tests/test_pytest.py::TestParamAll::test_param_13[x0]", - "name": "test_param_13[x0]", - "source": fix_path("./tests/test_pytest.py:175"), - "markers": [], - "parentid": "./tests/test_pytest.py::TestParamAll::test_param_13", - }, - { - "id": "./tests/test_pytest.py::TestParamAll::test_param_13[x1]", - "name": "test_param_13[x1]", - "source": fix_path("./tests/test_pytest.py:175"), - "markers": [], - "parentid": "./tests/test_pytest.py::TestParamAll::test_param_13", - }, - { - "id": "./tests/test_pytest.py::TestParamAll::test_param_13[x2]", - "name": "test_param_13[x2]", - "source": fix_path("./tests/test_pytest.py:175"), - "markers": [], - "parentid": "./tests/test_pytest.py::TestParamAll::test_param_13", - }, - { - "id": "./tests/test_pytest.py::TestParamAll::test_spam_13[x0]", - "name": "test_spam_13[x0]", - "source": fix_path("./tests/test_pytest.py:178"), - "markers": [], - "parentid": "./tests/test_pytest.py::TestParamAll::test_spam_13", - }, - { - "id": "./tests/test_pytest.py::TestParamAll::test_spam_13[x1]", - "name": "test_spam_13[x1]", - "source": fix_path("./tests/test_pytest.py:178"), - "markers": [], - "parentid": "./tests/test_pytest.py::TestParamAll::test_spam_13", - }, - { - "id": "./tests/test_pytest.py::TestParamAll::test_spam_13[x2]", - "name": "test_spam_13[x2]", - "source": fix_path("./tests/test_pytest.py:178"), - "markers": [], - "parentid": "./tests/test_pytest.py::TestParamAll::test_spam_13", - }, - { - "id": "./tests/test_pytest.py::test_fixture", - "name": "test_fixture", - "source": fix_path("./tests/test_pytest.py:192"), - "markers": [], - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_mark_fixture", - "name": "test_mark_fixture", - "source": fix_path("./tests/test_pytest.py:196"), - "markers": [], - "parentid": "./tests/test_pytest.py", - }, - { - "id": "./tests/test_pytest.py::test_param_fixture[x0]", - "name": "test_param_fixture[x0]", - "source": fix_path("./tests/test_pytest.py:201"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_fixture", - }, - { - "id": "./tests/test_pytest.py::test_param_fixture[x1]", - "name": "test_param_fixture[x1]", - "source": fix_path("./tests/test_pytest.py:201"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_fixture", - }, - { - "id": "./tests/test_pytest.py::test_param_fixture[x2]", - "name": "test_param_fixture[x2]", - "source": fix_path("./tests/test_pytest.py:201"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_fixture", - }, - { - "id": "./tests/test_pytest.py::test_param_mark_fixture[x0]", - "name": "test_param_mark_fixture[x0]", - "source": fix_path("./tests/test_pytest.py:207"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_mark_fixture", - }, - { - "id": "./tests/test_pytest.py::test_param_mark_fixture[x1]", - "name": "test_param_mark_fixture[x1]", - "source": fix_path("./tests/test_pytest.py:207"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_mark_fixture", - }, - { - "id": "./tests/test_pytest.py::test_param_mark_fixture[x2]", - "name": "test_param_mark_fixture[x2]", - "source": fix_path("./tests/test_pytest.py:207"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_param_mark_fixture", - }, - { - "id": "./tests/test_pytest.py::test_fixture_param[spam]", - "name": "test_fixture_param[spam]", - "source": fix_path("./tests/test_pytest.py:216"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_fixture_param", - }, - { - "id": "./tests/test_pytest.py::test_fixture_param[eggs]", - "name": "test_fixture_param[eggs]", - "source": fix_path("./tests/test_pytest.py:216"), - "markers": [], - "parentid": "./tests/test_pytest.py::test_fixture_param", - }, - ###### - { - "id": "./tests/test_pytest_param.py::test_param_13[x0]", - "name": "test_param_13[x0]", - "source": fix_path("./tests/test_pytest_param.py:8"), - "markers": [], - "parentid": "./tests/test_pytest_param.py::test_param_13", - }, - { - "id": "./tests/test_pytest_param.py::test_param_13[x1]", - "name": "test_param_13[x1]", - "source": fix_path("./tests/test_pytest_param.py:8"), - "markers": [], - "parentid": "./tests/test_pytest_param.py::test_param_13", - }, - { - "id": "./tests/test_pytest_param.py::test_param_13[x2]", - "name": "test_param_13[x2]", - "source": fix_path("./tests/test_pytest_param.py:8"), - "markers": [], - "parentid": "./tests/test_pytest_param.py::test_param_13", - }, - { - "id": "./tests/test_pytest_param.py::TestParamAll::test_param_13[x0]", - "name": "test_param_13[x0]", - "source": fix_path("./tests/test_pytest_param.py:14"), - "markers": [], - "parentid": "./tests/test_pytest_param.py::TestParamAll::test_param_13", - }, - { - "id": "./tests/test_pytest_param.py::TestParamAll::test_param_13[x1]", - "name": "test_param_13[x1]", - "source": fix_path("./tests/test_pytest_param.py:14"), - "markers": [], - "parentid": "./tests/test_pytest_param.py::TestParamAll::test_param_13", - }, - { - "id": "./tests/test_pytest_param.py::TestParamAll::test_param_13[x2]", - "name": "test_param_13[x2]", - "source": fix_path("./tests/test_pytest_param.py:14"), - "markers": [], - "parentid": "./tests/test_pytest_param.py::TestParamAll::test_param_13", - }, - { - "id": "./tests/test_pytest_param.py::TestParamAll::test_spam_13[x0]", - "name": "test_spam_13[x0]", - "source": fix_path("./tests/test_pytest_param.py:17"), - "markers": [], - "parentid": "./tests/test_pytest_param.py::TestParamAll::test_spam_13", - }, - { - "id": "./tests/test_pytest_param.py::TestParamAll::test_spam_13[x1]", - "name": "test_spam_13[x1]", - "source": fix_path("./tests/test_pytest_param.py:17"), - "markers": [], - "parentid": "./tests/test_pytest_param.py::TestParamAll::test_spam_13", - }, - { - "id": "./tests/test_pytest_param.py::TestParamAll::test_spam_13[x2]", - "name": "test_spam_13[x2]", - "source": fix_path("./tests/test_pytest_param.py:17"), - "markers": [], - "parentid": "./tests/test_pytest_param.py::TestParamAll::test_spam_13", - }, - ###### - { - "id": "./tests/test_unittest.py::MyTests::test_dynamic_", - "name": "test_dynamic_", - "source": fix_path("./tests/test_unittest.py:54"), - "markers": [], - "parentid": "./tests/test_unittest.py::MyTests", - }, - { - "id": "./tests/test_unittest.py::MyTests::test_failure", - "name": "test_failure", - "source": fix_path("./tests/test_unittest.py:34"), - "markers": [], - "parentid": "./tests/test_unittest.py::MyTests", - }, - { - "id": "./tests/test_unittest.py::MyTests::test_known_failure", - "name": "test_known_failure", - "source": fix_path("./tests/test_unittest.py:37"), - "markers": [], - "parentid": "./tests/test_unittest.py::MyTests", - }, - { - "id": "./tests/test_unittest.py::MyTests::test_maybe_not_skipped", - "name": "test_maybe_not_skipped", - "source": fix_path("./tests/test_unittest.py:17"), - "markers": [], - "parentid": "./tests/test_unittest.py::MyTests", - }, - { - "id": "./tests/test_unittest.py::MyTests::test_maybe_skipped", - "name": "test_maybe_skipped", - "source": fix_path("./tests/test_unittest.py:13"), - "markers": [], - "parentid": "./tests/test_unittest.py::MyTests", - }, - { - "id": "./tests/test_unittest.py::MyTests::test_simple", - "name": "test_simple", - "source": fix_path("./tests/test_unittest.py:6"), - "markers": [], - "parentid": "./tests/test_unittest.py::MyTests", - }, - { - "id": "./tests/test_unittest.py::MyTests::test_skipped", - "name": "test_skipped", - "source": fix_path("./tests/test_unittest.py:9"), - "markers": [], - "parentid": "./tests/test_unittest.py::MyTests", - }, - { - "id": "./tests/test_unittest.py::MyTests::test_skipped_inside", - "name": "test_skipped_inside", - "source": fix_path("./tests/test_unittest.py:21"), - "markers": [], - "parentid": "./tests/test_unittest.py::MyTests", - }, - { - "id": "./tests/test_unittest.py::MyTests::test_with_nested_subtests", - "name": "test_with_nested_subtests", - "source": fix_path("./tests/test_unittest.py:46"), - "markers": [], - "parentid": "./tests/test_unittest.py::MyTests", - }, - { - "id": "./tests/test_unittest.py::MyTests::test_with_subtests", - "name": "test_with_subtests", - "source": fix_path("./tests/test_unittest.py:41"), - "markers": [], - "parentid": "./tests/test_unittest.py::MyTests", - }, - { - "id": "./tests/test_unittest.py::OtherTests::test_simple", - "name": "test_simple", - "source": fix_path("./tests/test_unittest.py:61"), - "markers": [], - "parentid": "./tests/test_unittest.py::OtherTests", - }, - ########### - { - "id": "./tests/v/test_eggs.py::test_simple", - "name": "test_simple", - "source": fix_path("./tests/v/spam.py:2"), - "markers": [], - "parentid": "./tests/v/test_eggs.py", - }, - { - "id": "./tests/v/test_eggs.py::TestSimple::test_simple", - "name": "test_simple", - "source": fix_path("./tests/v/spam.py:8"), - "markers": [], - "parentid": "./tests/v/test_eggs.py::TestSimple", - }, - ###### - { - "id": "./tests/v/test_ham.py::test_simple", - "name": "test_simple", - "source": fix_path("./tests/v/spam.py:2"), - "markers": [], - "parentid": "./tests/v/test_ham.py", - }, - { - "id": "./tests/v/test_ham.py::test_not_hard", - "name": "test_not_hard", - "source": fix_path("./tests/v/spam.py:2"), - "markers": [], - "parentid": "./tests/v/test_ham.py", - }, - ###### - { - "id": "./tests/v/test_spam.py::test_simple", - "name": "test_simple", - "source": fix_path("./tests/v/spam.py:2"), - "markers": [], - "parentid": "./tests/v/test_spam.py", - }, - { - "id": "./tests/v/test_spam.py::test_simpler", - "name": "test_simpler", - "source": fix_path("./tests/v/test_spam.py:4"), - "markers": [], - "parentid": "./tests/v/test_spam.py", - }, - ########### - { - "id": "./tests/w/test_spam.py::test_simple", - "name": "test_simple", - "source": fix_path("./tests/w/test_spam.py:4"), - "markers": [], - "parentid": "./tests/w/test_spam.py", - }, - { - "id": "./tests/w/test_spam_ex.py::test_simple", - "name": "test_simple", - "source": fix_path("./tests/w/test_spam_ex.py:4"), - "markers": [], - "parentid": "./tests/w/test_spam_ex.py", - }, - ########### - { - "id": "./tests/x/y/z/test_ham.py::test_simple", - "name": "test_simple", - "source": fix_path("./tests/x/y/z/test_ham.py:2"), - "markers": [], - "parentid": "./tests/x/y/z/test_ham.py", - }, - ###### - { - "id": "./tests/x/y/z/a/test_spam.py::test_simple", - "name": "test_simple", - "source": fix_path("./tests/x/y/z/a/test_spam.py:11"), - "markers": [], - "parentid": "./tests/x/y/z/a/test_spam.py", - }, - { - "id": "./tests/x/y/z/b/test_spam.py::test_simple", - "name": "test_simple", - "source": fix_path("./tests/x/y/z/b/test_spam.py:7"), - "markers": [], - "parentid": "./tests/x/y/z/b/test_spam.py", - }, - ], -} diff --git a/pythonFiles/tests/testing_tools/adapter/test_report.py b/pythonFiles/tests/testing_tools/adapter/test_report.py deleted file mode 100644 index bb68c8a65e79..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/test_report.py +++ /dev/null @@ -1,1179 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -import json -import unittest - -from ...util import StubProxy -from testing_tools.adapter.util import fix_path, fix_relpath -from testing_tools.adapter.info import SingleTestInfo, SingleTestPath, ParentInfo -from testing_tools.adapter.report import report_discovered - - -class StubSender(StubProxy): - def send(self, outstr): - self.add_call("send", (json.loads(outstr),), None) - - -################################## -# tests - - -class ReportDiscoveredTests(unittest.TestCase): - def test_basic(self): - stub = StubSender() - testroot = fix_path("/a/b/c") - relfile = "test_spam.py" - relpath = fix_relpath(relfile) - tests = [ - SingleTestInfo( - id="test#1", - name="test_spam", - path=SingleTestPath( - root=testroot, - relfile=relfile, - func="test_spam", - ), - source="{}:{}".format(relfile, 10), - markers=[], - parentid="file#1", - ), - ] - parents = [ - ParentInfo( - id="", - kind="folder", - name=testroot, - ), - ParentInfo( - id="file#1", - kind="file", - name=relfile, - root=testroot, - relpath=relpath, - parentid="", - ), - ] - expected = [ - { - "rootid": "", - "root": testroot, - "parents": [ - { - "id": "file#1", - "kind": "file", - "name": relfile, - "relpath": relpath, - "parentid": "", - }, - ], - "tests": [ - { - "id": "test#1", - "name": "test_spam", - "source": "{}:{}".format(relfile, 10), - "markers": [], - "parentid": "file#1", - } - ], - } - ] - - report_discovered(tests, parents, _send=stub.send) - - self.maxDiff = None - self.assertEqual( - stub.calls, - [ - ("send", (expected,), None), - ], - ) - - def test_multiroot(self): - stub = StubSender() - # the first root - testroot1 = fix_path("/a/b/c") - relfileid1 = "./test_spam.py" - relpath1 = fix_path(relfileid1) - relfile1 = relpath1[2:] - tests = [ - SingleTestInfo( - id=relfileid1 + "::test_spam", - name="test_spam", - path=SingleTestPath( - root=testroot1, - relfile=relfile1, - func="test_spam", - ), - source="{}:{}".format(relfile1, 10), - markers=[], - parentid=relfileid1, - ), - ] - parents = [ - ParentInfo( - id=".", - kind="folder", - name=testroot1, - ), - ParentInfo( - id=relfileid1, - kind="file", - name="test_spam.py", - root=testroot1, - relpath=relpath1, - parentid=".", - ), - ] - expected = [ - { - "rootid": ".", - "root": testroot1, - "parents": [ - { - "id": relfileid1, - "kind": "file", - "name": "test_spam.py", - "relpath": relpath1, - "parentid": ".", - }, - ], - "tests": [ - { - "id": relfileid1 + "::test_spam", - "name": "test_spam", - "source": "{}:{}".format(relfile1, 10), - "markers": [], - "parentid": relfileid1, - } - ], - }, - ] - # the second root - testroot2 = fix_path("/x/y/z") - relfileid2 = "./w/test_eggs.py" - relpath2 = fix_path(relfileid2) - relfile2 = relpath2[2:] - tests.extend( - [ - SingleTestInfo( - id=relfileid2 + "::BasicTests::test_first", - name="test_first", - path=SingleTestPath( - root=testroot2, - relfile=relfile2, - func="BasicTests.test_first", - ), - source="{}:{}".format(relfile2, 61), - markers=[], - parentid=relfileid2 + "::BasicTests", - ), - ] - ) - parents.extend( - [ - ParentInfo( - id=".", - kind="folder", - name=testroot2, - ), - ParentInfo( - id="./w", - kind="folder", - name="w", - root=testroot2, - relpath=fix_path("./w"), - parentid=".", - ), - ParentInfo( - id=relfileid2, - kind="file", - name="test_eggs.py", - root=testroot2, - relpath=relpath2, - parentid="./w", - ), - ParentInfo( - id=relfileid2 + "::BasicTests", - kind="suite", - name="BasicTests", - root=testroot2, - parentid=relfileid2, - ), - ] - ) - expected.extend( - [ - { - "rootid": ".", - "root": testroot2, - "parents": [ - { - "id": "./w", - "kind": "folder", - "name": "w", - "relpath": fix_path("./w"), - "parentid": ".", - }, - { - "id": relfileid2, - "kind": "file", - "name": "test_eggs.py", - "relpath": relpath2, - "parentid": "./w", - }, - { - "id": relfileid2 + "::BasicTests", - "kind": "suite", - "name": "BasicTests", - "parentid": relfileid2, - }, - ], - "tests": [ - { - "id": relfileid2 + "::BasicTests::test_first", - "name": "test_first", - "source": "{}:{}".format(relfile2, 61), - "markers": [], - "parentid": relfileid2 + "::BasicTests", - } - ], - }, - ] - ) - - report_discovered(tests, parents, _send=stub.send) - - self.maxDiff = None - self.assertEqual( - stub.calls, - [ - ("send", (expected,), None), - ], - ) - - def test_complex(self): - """ - /a/b/c/ - test_ham.py - MySuite - test_x1 - test_x2 - /a/b/e/f/g/ - w/ - test_ham.py - test_ham1 - HamTests - test_uh_oh - test_whoa - MoreHam - test_yay - sub1 - sub2 - sub3 - test_eggs.py - SpamTests - test_okay - x/ - y/ - a/ - test_spam.py - SpamTests - test_okay - b/ - test_spam.py - SpamTests - test_okay - test_spam.py - SpamTests - test_okay - """ - stub = StubSender() - testroot = fix_path("/a/b/c") - relfileid1 = "./test_ham.py" - relfileid2 = "./test_spam.py" - relfileid3 = "./w/test_ham.py" - relfileid4 = "./w/test_eggs.py" - relfileid5 = "./x/y/a/test_spam.py" - relfileid6 = "./x/y/b/test_spam.py" - tests = [ - SingleTestInfo( - id=relfileid1 + "::MySuite::test_x1", - name="test_x1", - path=SingleTestPath( - root=testroot, - relfile=fix_path(relfileid1), - func="MySuite.test_x1", - ), - source="{}:{}".format(fix_path(relfileid1), 10), - markers=None, - parentid=relfileid1 + "::MySuite", - ), - SingleTestInfo( - id=relfileid1 + "::MySuite::test_x2", - name="test_x2", - path=SingleTestPath( - root=testroot, - relfile=fix_path(relfileid1), - func="MySuite.test_x2", - ), - source="{}:{}".format(fix_path(relfileid1), 21), - markers=None, - parentid=relfileid1 + "::MySuite", - ), - SingleTestInfo( - id=relfileid2 + "::SpamTests::test_okay", - name="test_okay", - path=SingleTestPath( - root=testroot, - relfile=fix_path(relfileid2), - func="SpamTests.test_okay", - ), - source="{}:{}".format(fix_path(relfileid2), 17), - markers=None, - parentid=relfileid2 + "::SpamTests", - ), - SingleTestInfo( - id=relfileid3 + "::test_ham1", - name="test_ham1", - path=SingleTestPath( - root=testroot, - relfile=fix_path(relfileid3), - func="test_ham1", - ), - source="{}:{}".format(fix_path(relfileid3), 8), - markers=None, - parentid=relfileid3, - ), - SingleTestInfo( - id=relfileid3 + "::HamTests::test_uh_oh", - name="test_uh_oh", - path=SingleTestPath( - root=testroot, - relfile=fix_path(relfileid3), - func="HamTests.test_uh_oh", - ), - source="{}:{}".format(fix_path(relfileid3), 19), - markers=["expected-failure"], - parentid=relfileid3 + "::HamTests", - ), - SingleTestInfo( - id=relfileid3 + "::HamTests::test_whoa", - name="test_whoa", - path=SingleTestPath( - root=testroot, - relfile=fix_path(relfileid3), - func="HamTests.test_whoa", - ), - source="{}:{}".format(fix_path(relfileid3), 35), - markers=None, - parentid=relfileid3 + "::HamTests", - ), - SingleTestInfo( - id=relfileid3 + "::MoreHam::test_yay[1-2]", - name="test_yay[1-2]", - path=SingleTestPath( - root=testroot, - relfile=fix_path(relfileid3), - func="MoreHam.test_yay", - sub=["[1-2]"], - ), - source="{}:{}".format(fix_path(relfileid3), 57), - markers=None, - parentid=relfileid3 + "::MoreHam::test_yay", - ), - SingleTestInfo( - id=relfileid3 + "::MoreHam::test_yay[1-2][3-4]", - name="test_yay[1-2][3-4]", - path=SingleTestPath( - root=testroot, - relfile=fix_path(relfileid3), - func="MoreHam.test_yay", - sub=["[1-2]", "[3=4]"], - ), - source="{}:{}".format(fix_path(relfileid3), 72), - markers=None, - parentid=relfileid3 + "::MoreHam::test_yay[1-2]", - ), - SingleTestInfo( - id=relfileid4 + "::SpamTests::test_okay", - name="test_okay", - path=SingleTestPath( - root=testroot, - relfile=fix_path(relfileid4), - func="SpamTests.test_okay", - ), - source="{}:{}".format(fix_path(relfileid4), 15), - markers=None, - parentid=relfileid4 + "::SpamTests", - ), - SingleTestInfo( - id=relfileid5 + "::SpamTests::test_okay", - name="test_okay", - path=SingleTestPath( - root=testroot, - relfile=fix_path(relfileid5), - func="SpamTests.test_okay", - ), - source="{}:{}".format(fix_path(relfileid5), 12), - markers=None, - parentid=relfileid5 + "::SpamTests", - ), - SingleTestInfo( - id=relfileid6 + "::SpamTests::test_okay", - name="test_okay", - path=SingleTestPath( - root=testroot, - relfile=fix_path(relfileid6), - func="SpamTests.test_okay", - ), - source="{}:{}".format(fix_path(relfileid6), 27), - markers=None, - parentid=relfileid6 + "::SpamTests", - ), - ] - parents = [ - ParentInfo( - id=".", - kind="folder", - name=testroot, - ), - ParentInfo( - id=relfileid1, - kind="file", - name="test_ham.py", - root=testroot, - relpath=fix_path(relfileid1), - parentid=".", - ), - ParentInfo( - id=relfileid1 + "::MySuite", - kind="suite", - name="MySuite", - root=testroot, - parentid=relfileid1, - ), - ParentInfo( - id=relfileid2, - kind="file", - name="test_spam.py", - root=testroot, - relpath=fix_path(relfileid2), - parentid=".", - ), - ParentInfo( - id=relfileid2 + "::SpamTests", - kind="suite", - name="SpamTests", - root=testroot, - parentid=relfileid2, - ), - ParentInfo( - id="./w", - kind="folder", - name="w", - root=testroot, - relpath=fix_path("./w"), - parentid=".", - ), - ParentInfo( - id=relfileid3, - kind="file", - name="test_ham.py", - root=testroot, - relpath=fix_path(relfileid3), - parentid="./w", - ), - ParentInfo( - id=relfileid3 + "::HamTests", - kind="suite", - name="HamTests", - root=testroot, - parentid=relfileid3, - ), - ParentInfo( - id=relfileid3 + "::MoreHam", - kind="suite", - name="MoreHam", - root=testroot, - parentid=relfileid3, - ), - ParentInfo( - id=relfileid3 + "::MoreHam::test_yay", - kind="function", - name="test_yay", - root=testroot, - parentid=relfileid3 + "::MoreHam", - ), - ParentInfo( - id=relfileid3 + "::MoreHam::test_yay[1-2]", - kind="subtest", - name="test_yay[1-2]", - root=testroot, - parentid=relfileid3 + "::MoreHam::test_yay", - ), - ParentInfo( - id=relfileid4, - kind="file", - name="test_eggs.py", - root=testroot, - relpath=fix_path(relfileid4), - parentid="./w", - ), - ParentInfo( - id=relfileid4 + "::SpamTests", - kind="suite", - name="SpamTests", - root=testroot, - parentid=relfileid4, - ), - ParentInfo( - id="./x", - kind="folder", - name="x", - root=testroot, - relpath=fix_path("./x"), - parentid=".", - ), - ParentInfo( - id="./x/y", - kind="folder", - name="y", - root=testroot, - relpath=fix_path("./x/y"), - parentid="./x", - ), - ParentInfo( - id="./x/y/a", - kind="folder", - name="a", - root=testroot, - relpath=fix_path("./x/y/a"), - parentid="./x/y", - ), - ParentInfo( - id=relfileid5, - kind="file", - name="test_spam.py", - root=testroot, - relpath=fix_path(relfileid5), - parentid="./x/y/a", - ), - ParentInfo( - id=relfileid5 + "::SpamTests", - kind="suite", - name="SpamTests", - root=testroot, - parentid=relfileid5, - ), - ParentInfo( - id="./x/y/b", - kind="folder", - name="b", - root=testroot, - relpath=fix_path("./x/y/b"), - parentid="./x/y", - ), - ParentInfo( - id=relfileid6, - kind="file", - name="test_spam.py", - root=testroot, - relpath=fix_path(relfileid6), - parentid="./x/y/b", - ), - ParentInfo( - id=relfileid6 + "::SpamTests", - kind="suite", - name="SpamTests", - root=testroot, - parentid=relfileid6, - ), - ] - expected = [ - { - "rootid": ".", - "root": testroot, - "parents": [ - { - "id": relfileid1, - "kind": "file", - "name": "test_ham.py", - "relpath": fix_path(relfileid1), - "parentid": ".", - }, - { - "id": relfileid1 + "::MySuite", - "kind": "suite", - "name": "MySuite", - "parentid": relfileid1, - }, - { - "id": relfileid2, - "kind": "file", - "name": "test_spam.py", - "relpath": fix_path(relfileid2), - "parentid": ".", - }, - { - "id": relfileid2 + "::SpamTests", - "kind": "suite", - "name": "SpamTests", - "parentid": relfileid2, - }, - { - "id": "./w", - "kind": "folder", - "name": "w", - "relpath": fix_path("./w"), - "parentid": ".", - }, - { - "id": relfileid3, - "kind": "file", - "name": "test_ham.py", - "relpath": fix_path(relfileid3), - "parentid": "./w", - }, - { - "id": relfileid3 + "::HamTests", - "kind": "suite", - "name": "HamTests", - "parentid": relfileid3, - }, - { - "id": relfileid3 + "::MoreHam", - "kind": "suite", - "name": "MoreHam", - "parentid": relfileid3, - }, - { - "id": relfileid3 + "::MoreHam::test_yay", - "kind": "function", - "name": "test_yay", - "parentid": relfileid3 + "::MoreHam", - }, - { - "id": relfileid3 + "::MoreHam::test_yay[1-2]", - "kind": "subtest", - "name": "test_yay[1-2]", - "parentid": relfileid3 + "::MoreHam::test_yay", - }, - { - "id": relfileid4, - "kind": "file", - "name": "test_eggs.py", - "relpath": fix_path(relfileid4), - "parentid": "./w", - }, - { - "id": relfileid4 + "::SpamTests", - "kind": "suite", - "name": "SpamTests", - "parentid": relfileid4, - }, - { - "id": "./x", - "kind": "folder", - "name": "x", - "relpath": fix_path("./x"), - "parentid": ".", - }, - { - "id": "./x/y", - "kind": "folder", - "name": "y", - "relpath": fix_path("./x/y"), - "parentid": "./x", - }, - { - "id": "./x/y/a", - "kind": "folder", - "name": "a", - "relpath": fix_path("./x/y/a"), - "parentid": "./x/y", - }, - { - "id": relfileid5, - "kind": "file", - "name": "test_spam.py", - "relpath": fix_path(relfileid5), - "parentid": "./x/y/a", - }, - { - "id": relfileid5 + "::SpamTests", - "kind": "suite", - "name": "SpamTests", - "parentid": relfileid5, - }, - { - "id": "./x/y/b", - "kind": "folder", - "name": "b", - "relpath": fix_path("./x/y/b"), - "parentid": "./x/y", - }, - { - "id": relfileid6, - "kind": "file", - "name": "test_spam.py", - "relpath": fix_path(relfileid6), - "parentid": "./x/y/b", - }, - { - "id": relfileid6 + "::SpamTests", - "kind": "suite", - "name": "SpamTests", - "parentid": relfileid6, - }, - ], - "tests": [ - { - "id": relfileid1 + "::MySuite::test_x1", - "name": "test_x1", - "source": "{}:{}".format(fix_path(relfileid1), 10), - "markers": [], - "parentid": relfileid1 + "::MySuite", - }, - { - "id": relfileid1 + "::MySuite::test_x2", - "name": "test_x2", - "source": "{}:{}".format(fix_path(relfileid1), 21), - "markers": [], - "parentid": relfileid1 + "::MySuite", - }, - { - "id": relfileid2 + "::SpamTests::test_okay", - "name": "test_okay", - "source": "{}:{}".format(fix_path(relfileid2), 17), - "markers": [], - "parentid": relfileid2 + "::SpamTests", - }, - { - "id": relfileid3 + "::test_ham1", - "name": "test_ham1", - "source": "{}:{}".format(fix_path(relfileid3), 8), - "markers": [], - "parentid": relfileid3, - }, - { - "id": relfileid3 + "::HamTests::test_uh_oh", - "name": "test_uh_oh", - "source": "{}:{}".format(fix_path(relfileid3), 19), - "markers": ["expected-failure"], - "parentid": relfileid3 + "::HamTests", - }, - { - "id": relfileid3 + "::HamTests::test_whoa", - "name": "test_whoa", - "source": "{}:{}".format(fix_path(relfileid3), 35), - "markers": [], - "parentid": relfileid3 + "::HamTests", - }, - { - "id": relfileid3 + "::MoreHam::test_yay[1-2]", - "name": "test_yay[1-2]", - "source": "{}:{}".format(fix_path(relfileid3), 57), - "markers": [], - "parentid": relfileid3 + "::MoreHam::test_yay", - }, - { - "id": relfileid3 + "::MoreHam::test_yay[1-2][3-4]", - "name": "test_yay[1-2][3-4]", - "source": "{}:{}".format(fix_path(relfileid3), 72), - "markers": [], - "parentid": relfileid3 + "::MoreHam::test_yay[1-2]", - }, - { - "id": relfileid4 + "::SpamTests::test_okay", - "name": "test_okay", - "source": "{}:{}".format(fix_path(relfileid4), 15), - "markers": [], - "parentid": relfileid4 + "::SpamTests", - }, - { - "id": relfileid5 + "::SpamTests::test_okay", - "name": "test_okay", - "source": "{}:{}".format(fix_path(relfileid5), 12), - "markers": [], - "parentid": relfileid5 + "::SpamTests", - }, - { - "id": relfileid6 + "::SpamTests::test_okay", - "name": "test_okay", - "source": "{}:{}".format(fix_path(relfileid6), 27), - "markers": [], - "parentid": relfileid6 + "::SpamTests", - }, - ], - } - ] - - report_discovered(tests, parents, _send=stub.send) - - self.maxDiff = None - self.assertEqual( - stub.calls, - [ - ("send", (expected,), None), - ], - ) - - def test_simple_basic(self): - stub = StubSender() - testroot = fix_path("/a/b/c") - relfile = fix_path("x/y/z/test_spam.py") - tests = [ - SingleTestInfo( - id="test#1", - name="test_spam_1", - path=SingleTestPath( - root=testroot, - relfile=relfile, - func="MySuite.test_spam_1", - sub=None, - ), - source="{}:{}".format(relfile, 10), - markers=None, - parentid="suite#1", - ), - ] - parents = None - expected = [ - { - "id": "test#1", - "name": "test_spam_1", - "testroot": testroot, - "relfile": relfile, - "lineno": 10, - "testfunc": "MySuite.test_spam_1", - "subtest": None, - "markers": [], - } - ] - - report_discovered(tests, parents, simple=True, _send=stub.send) - - self.maxDiff = None - self.assertEqual( - stub.calls, - [ - ("send", (expected,), None), - ], - ) - - def test_simple_complex(self): - """ - /a/b/c/ - test_ham.py - MySuite - test_x1 - test_x2 - /a/b/e/f/g/ - w/ - test_ham.py - test_ham1 - HamTests - test_uh_oh - test_whoa - MoreHam - test_yay - sub1 - sub2 - sub3 - test_eggs.py - SpamTests - test_okay - x/ - y/ - a/ - test_spam.py - SpamTests - test_okay - b/ - test_spam.py - SpamTests - test_okay - test_spam.py - SpamTests - test_okay - """ - stub = StubSender() - testroot1 = fix_path("/a/b/c") - relfile1 = fix_path("./test_ham.py") - testroot2 = fix_path("/a/b/e/f/g") - relfile2 = fix_path("./test_spam.py") - relfile3 = fix_path("w/test_ham.py") - relfile4 = fix_path("w/test_eggs.py") - relfile5 = fix_path("x/y/a/test_spam.py") - relfile6 = fix_path("x/y/b/test_spam.py") - tests = [ - # under first root folder - SingleTestInfo( - id="test#1", - name="test_x1", - path=SingleTestPath( - root=testroot1, - relfile=relfile1, - func="MySuite.test_x1", - sub=None, - ), - source="{}:{}".format(relfile1, 10), - markers=None, - parentid="suite#1", - ), - SingleTestInfo( - id="test#2", - name="test_x2", - path=SingleTestPath( - root=testroot1, - relfile=relfile1, - func="MySuite.test_x2", - sub=None, - ), - source="{}:{}".format(relfile1, 21), - markers=None, - parentid="suite#1", - ), - # under second root folder - SingleTestInfo( - id="test#3", - name="test_okay", - path=SingleTestPath( - root=testroot2, - relfile=relfile2, - func="SpamTests.test_okay", - sub=None, - ), - source="{}:{}".format(relfile2, 17), - markers=None, - parentid="suite#2", - ), - SingleTestInfo( - id="test#4", - name="test_ham1", - path=SingleTestPath( - root=testroot2, - relfile=relfile3, - func="test_ham1", - sub=None, - ), - source="{}:{}".format(relfile3, 8), - markers=None, - parentid="file#3", - ), - SingleTestInfo( - id="test#5", - name="test_uh_oh", - path=SingleTestPath( - root=testroot2, - relfile=relfile3, - func="HamTests.test_uh_oh", - sub=None, - ), - source="{}:{}".format(relfile3, 19), - markers=["expected-failure"], - parentid="suite#3", - ), - SingleTestInfo( - id="test#6", - name="test_whoa", - path=SingleTestPath( - root=testroot2, - relfile=relfile3, - func="HamTests.test_whoa", - sub=None, - ), - source="{}:{}".format(relfile3, 35), - markers=None, - parentid="suite#3", - ), - SingleTestInfo( - id="test#7", - name="test_yay (sub1)", - path=SingleTestPath( - root=testroot2, - relfile=relfile3, - func="MoreHam.test_yay", - sub=["sub1"], - ), - source="{}:{}".format(relfile3, 57), - markers=None, - parentid="suite#4", - ), - SingleTestInfo( - id="test#8", - name="test_yay (sub2) (sub3)", - path=SingleTestPath( - root=testroot2, - relfile=relfile3, - func="MoreHam.test_yay", - sub=["sub2", "sub3"], - ), - source="{}:{}".format(relfile3, 72), - markers=None, - parentid="suite#3", - ), - SingleTestInfo( - id="test#9", - name="test_okay", - path=SingleTestPath( - root=testroot2, - relfile=relfile4, - func="SpamTests.test_okay", - sub=None, - ), - source="{}:{}".format(relfile4, 15), - markers=None, - parentid="suite#5", - ), - SingleTestInfo( - id="test#10", - name="test_okay", - path=SingleTestPath( - root=testroot2, - relfile=relfile5, - func="SpamTests.test_okay", - sub=None, - ), - source="{}:{}".format(relfile5, 12), - markers=None, - parentid="suite#6", - ), - SingleTestInfo( - id="test#11", - name="test_okay", - path=SingleTestPath( - root=testroot2, - relfile=relfile6, - func="SpamTests.test_okay", - sub=None, - ), - source="{}:{}".format(relfile6, 27), - markers=None, - parentid="suite#7", - ), - ] - expected = [ - { - "id": "test#1", - "name": "test_x1", - "testroot": testroot1, - "relfile": relfile1, - "lineno": 10, - "testfunc": "MySuite.test_x1", - "subtest": None, - "markers": [], - }, - { - "id": "test#2", - "name": "test_x2", - "testroot": testroot1, - "relfile": relfile1, - "lineno": 21, - "testfunc": "MySuite.test_x2", - "subtest": None, - "markers": [], - }, - { - "id": "test#3", - "name": "test_okay", - "testroot": testroot2, - "relfile": relfile2, - "lineno": 17, - "testfunc": "SpamTests.test_okay", - "subtest": None, - "markers": [], - }, - { - "id": "test#4", - "name": "test_ham1", - "testroot": testroot2, - "relfile": relfile3, - "lineno": 8, - "testfunc": "test_ham1", - "subtest": None, - "markers": [], - }, - { - "id": "test#5", - "name": "test_uh_oh", - "testroot": testroot2, - "relfile": relfile3, - "lineno": 19, - "testfunc": "HamTests.test_uh_oh", - "subtest": None, - "markers": ["expected-failure"], - }, - { - "id": "test#6", - "name": "test_whoa", - "testroot": testroot2, - "relfile": relfile3, - "lineno": 35, - "testfunc": "HamTests.test_whoa", - "subtest": None, - "markers": [], - }, - { - "id": "test#7", - "name": "test_yay (sub1)", - "testroot": testroot2, - "relfile": relfile3, - "lineno": 57, - "testfunc": "MoreHam.test_yay", - "subtest": ["sub1"], - "markers": [], - }, - { - "id": "test#8", - "name": "test_yay (sub2) (sub3)", - "testroot": testroot2, - "relfile": relfile3, - "lineno": 72, - "testfunc": "MoreHam.test_yay", - "subtest": ["sub2", "sub3"], - "markers": [], - }, - { - "id": "test#9", - "name": "test_okay", - "testroot": testroot2, - "relfile": relfile4, - "lineno": 15, - "testfunc": "SpamTests.test_okay", - "subtest": None, - "markers": [], - }, - { - "id": "test#10", - "name": "test_okay", - "testroot": testroot2, - "relfile": relfile5, - "lineno": 12, - "testfunc": "SpamTests.test_okay", - "subtest": None, - "markers": [], - }, - { - "id": "test#11", - "name": "test_okay", - "testroot": testroot2, - "relfile": relfile6, - "lineno": 27, - "testfunc": "SpamTests.test_okay", - "subtest": None, - "markers": [], - }, - ] - parents = None - - report_discovered(tests, parents, simple=True, _send=stub.send) - - self.maxDiff = None - self.assertEqual( - stub.calls, - [ - ("send", (expected,), None), - ], - ) diff --git a/pythonFiles/tests/testing_tools/adapter/test_util.py b/pythonFiles/tests/testing_tools/adapter/test_util.py deleted file mode 100644 index 822ba2ed1b22..000000000000 --- a/pythonFiles/tests/testing_tools/adapter/test_util.py +++ /dev/null @@ -1,330 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -from __future__ import absolute_import, print_function - -import ntpath -import os -import os.path -import posixpath -import shlex -import sys -import unittest - -import pytest - -# Pytest 3.7 and later uses pathlib/pathlib2 for path resolution. -try: - from pathlib import Path -except ImportError: - from pathlib2 import Path # type: ignore (for Pylance) - -from testing_tools.adapter.util import ( - fix_path, - fix_relpath, - fix_fileid, - shlex_unsplit, -) - - -@unittest.skipIf(sys.version_info < (3,), "Python 2 does not have subTest") -class FilePathTests(unittest.TestCase): - def test_isolated_imports(self): - import testing_tools.adapter - from testing_tools.adapter import util - from . import test_functional - - ignored = { - str(Path(os.path.abspath(__file__)).resolve()), - str(Path(os.path.abspath(util.__file__)).resolve()), - str(Path(os.path.abspath(test_functional.__file__)).resolve()), - } - adapter = os.path.abspath(os.path.dirname(testing_tools.adapter.__file__)) - tests = os.path.join( - os.path.abspath(os.path.dirname(os.path.dirname(testing_tools.__file__))), - "tests", - "testing_tools", - "adapter", - ) - found = [] - for root in [adapter, tests]: - for dirname, _, files in os.walk(root): - if ".data" in dirname: - continue - for basename in files: - if not basename.endswith(".py"): - continue - filename = os.path.join(dirname, basename) - if filename in ignored: - continue - with open(filename) as srcfile: - for line in srcfile: - if line.strip() == "import os.path": - found.append(filename) - break - - if found: - self.fail( - os.linesep.join( - [ - "", - "Please only use path-related API from testing_tools.adapter.util.", - 'Found use of "os.path" in the following files:', - ] - + [" " + file for file in found] - ) - ) - - def test_fix_path(self): - tests = [ - ("./spam.py", r".\spam.py"), - ("./some-dir", r".\some-dir"), - ("./some-dir/", ".\\some-dir\\"), - ("./some-dir/eggs", r".\some-dir\eggs"), - ("./some-dir/eggs/spam.py", r".\some-dir\eggs\spam.py"), - ("X/y/Z/a.B.c.PY", r"X\y\Z\a.B.c.PY"), - ("/", "\\"), - ("/spam", r"\spam"), - ("C:/spam", r"C:\spam"), - ] - for path, expected in tests: - pathsep = ntpath.sep - with self.subTest(r"fixed for \: {!r}".format(path)): - fixed = fix_path(path, _pathsep=pathsep) - self.assertEqual(fixed, expected) - - pathsep = posixpath.sep - with self.subTest("unchanged for /: {!r}".format(path)): - unchanged = fix_path(path, _pathsep=pathsep) - self.assertEqual(unchanged, path) - - # no path -> "." - for path in ["", None]: - for pathsep in [ntpath.sep, posixpath.sep]: - with self.subTest(r"fixed for {}: {!r}".format(pathsep, path)): - fixed = fix_path(path, _pathsep=pathsep) - self.assertEqual(fixed, ".") - - # no-op paths - paths = [path for _, path in tests] - paths.extend( - [ - ".", - "..", - "some-dir", - "spam.py", - ] - ) - for path in paths: - for pathsep in [ntpath.sep, posixpath.sep]: - with self.subTest(r"unchanged for {}: {!r}".format(pathsep, path)): - unchanged = fix_path(path, _pathsep=pathsep) - self.assertEqual(unchanged, path) - - def test_fix_relpath(self): - tests = [ - ("spam.py", posixpath, "./spam.py"), - ("eggs/spam.py", posixpath, "./eggs/spam.py"), - ("eggs/spam/", posixpath, "./eggs/spam/"), - (r"\spam.py", posixpath, r"./\spam.py"), - ("spam.py", ntpath, r".\spam.py"), - (r"eggs\spam.py", ntpath, r".\eggs\spam.py"), - ("eggs\\spam\\", ntpath, ".\\eggs\\spam\\"), - ("/spam.py", ntpath, r"\spam.py"), # Note the fixed "/". - # absolute - ("/", posixpath, "/"), - ("/spam.py", posixpath, "/spam.py"), - ("\\", ntpath, "\\"), - (r"\spam.py", ntpath, r"\spam.py"), - (r"C:\spam.py", ntpath, r"C:\spam.py"), - # no-op - ("./spam.py", posixpath, "./spam.py"), - (r".\spam.py", ntpath, r".\spam.py"), - ] - # no-op - for path in [".", ".."]: - tests.extend( - [ - (path, posixpath, path), - (path, ntpath, path), - ] - ) - for path, _os_path, expected in tests: - with self.subTest((path, _os_path.sep)): - fixed = fix_relpath( - path, - _fix_path=(lambda p: fix_path(p, _pathsep=_os_path.sep)), - _path_isabs=_os_path.isabs, - _pathsep=_os_path.sep, - ) - self.assertEqual(fixed, expected) - - def test_fix_fileid(self): - common = [ - ("spam.py", "./spam.py"), - ("eggs/spam.py", "./eggs/spam.py"), - ("eggs/spam/", "./eggs/spam/"), - # absolute (no-op) - ("/", "/"), - ("//", "//"), - ("/spam.py", "/spam.py"), - # no-op - (None, None), - ("", ""), - (".", "."), - ("./spam.py", "./spam.py"), - ] - tests = [(p, posixpath, e) for p, e in common] - tests.extend( - (p, posixpath, e) - for p, e in [ - (r"\spam.py", r"./\spam.py"), - ] - ) - tests.extend((p, ntpath, e) for p, e in common) - tests.extend( - (p, ntpath, e) - for p, e in [ - (r"eggs\spam.py", "./eggs/spam.py"), - ("eggs\\spam\\", "./eggs/spam/"), - (r".\spam.py", r"./spam.py"), - # absolute - (r"\spam.py", "/spam.py"), - (r"C:\spam.py", "C:/spam.py"), - ("\\", "/"), - ("\\\\", "//"), - ("C:\\\\", "C://"), - ("C:/", "C:/"), - ("C://", "C://"), - ("C:/spam.py", "C:/spam.py"), - ] - ) - for fileid, _os_path, expected in tests: - pathsep = _os_path.sep - with self.subTest(r"for {}: {!r}".format(pathsep, fileid)): - fixed = fix_fileid( - fileid, - _path_isabs=_os_path.isabs, - _normcase=_os_path.normcase, - _pathsep=pathsep, - ) - self.assertEqual(fixed, expected) - - # with rootdir - common = [ - ("spam.py", "/eggs", "./spam.py"), - ("spam.py", r"\eggs", "./spam.py"), - # absolute - ("/spam.py", "/", "./spam.py"), - ("/eggs/spam.py", "/eggs", "./spam.py"), - ("/eggs/spam.py", "/eggs/", "./spam.py"), - # no-op - ("/spam.py", "/eggs", "/spam.py"), - ("/spam.py", "/eggs/", "/spam.py"), - # root-only (no-op) - ("/", "/", "/"), - ("/", "/spam", "/"), - ("//", "/", "//"), - ("//", "//", "//"), - ("//", "//spam", "//"), - ] - tests = [(p, r, posixpath, e) for p, r, e in common] - tests = [(p, r, ntpath, e) for p, r, e in common] - tests.extend( - (p, r, ntpath, e) - for p, r, e in [ - ("spam.py", r"\eggs", "./spam.py"), - # absolute - (r"\spam.py", "\\", r"./spam.py"), - (r"C:\spam.py", "C:\\", r"./spam.py"), - (r"\eggs\spam.py", r"\eggs", r"./spam.py"), - (r"\eggs\spam.py", "\\eggs\\", r"./spam.py"), - # normcase - (r"C:\spam.py", "c:\\", r"./spam.py"), - (r"\Eggs\Spam.py", "\\eggs", r"./Spam.py"), - (r"\eggs\spam.py", "\\Eggs", r"./spam.py"), - (r"\eggs\Spam.py", "\\Eggs", r"./Spam.py"), - # no-op - (r"\spam.py", r"\eggs", r"/spam.py"), - (r"C:\spam.py", r"C:\eggs", r"C:/spam.py"), - # TODO: Should these be supported. - (r"C:\spam.py", "\\", r"C:/spam.py"), - (r"\spam.py", "C:\\", r"/spam.py"), - # root-only - ("\\", "\\", "/"), - ("\\\\", "\\", "//"), - ("C:\\", "C:\\eggs", "C:/"), - ("C:\\", "C:\\", "C:/"), - (r"C:\spam.py", "D:\\", r"C:/spam.py"), - ] - ) - for fileid, rootdir, _os_path, expected in tests: - pathsep = _os_path.sep - with self.subTest( - r"for {} (with rootdir {!r}): {!r}".format(pathsep, rootdir, fileid) - ): - fixed = fix_fileid( - fileid, - rootdir, - _path_isabs=_os_path.isabs, - _normcase=_os_path.normcase, - _pathsep=pathsep, - ) - self.assertEqual(fixed, expected) - - -class ShlexUnsplitTests(unittest.TestCase): - def test_no_args(self): - argv = [] - joined = shlex_unsplit(argv) - - self.assertEqual(joined, "") - self.assertEqual(shlex.split(joined), argv) - - def test_one_arg(self): - argv = ["spam"] - joined = shlex_unsplit(argv) - - self.assertEqual(joined, "spam") - self.assertEqual(shlex.split(joined), argv) - - def test_multiple_args(self): - argv = [ - "-x", - "X", - "-xyz", - "spam", - "eggs", - ] - joined = shlex_unsplit(argv) - - self.assertEqual(joined, "-x X -xyz spam eggs") - self.assertEqual(shlex.split(joined), argv) - - def test_whitespace(self): - argv = [ - "-x", - "X Y Z", - "spam spam\tspam", - "eggs", - ] - joined = shlex_unsplit(argv) - - self.assertEqual(joined, "-x 'X Y Z' 'spam spam\tspam' eggs") - self.assertEqual(shlex.split(joined), argv) - - def test_quotation_marks(self): - argv = [ - "-x", - "''", - 'spam"spam"spam', - "ham'ham'ham", - "eggs", - ] - joined = shlex_unsplit(argv) - - self.assertEqual( - joined, - "-x ''\"'\"''\"'\"'' 'spam\"spam\"spam' 'ham'\"'\"'ham'\"'\"'ham' eggs", - ) - self.assertEqual(shlex.split(joined), argv) diff --git a/pythonFiles/tests/util.py b/pythonFiles/tests/util.py deleted file mode 100644 index 45c3536145cf..000000000000 --- a/pythonFiles/tests/util.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - - -class Stub(object): - def __init__(self): - self.calls = [] - - def add_call(self, name, args=None, kwargs=None): - self.calls.append((name, args, kwargs)) - - -class StubProxy(object): - def __init__(self, stub=None, name=None): - self.name = name - self.stub = stub if stub is not None else Stub() - - @property - def calls(self): - return self.stub.calls - - def add_call(self, funcname, *args, **kwargs): - callname = funcname - if self.name: - callname = "{}.{}".format(self.name, funcname) - return self.stub.add_call(callname, *args, **kwargs) diff --git a/pythonFiles/visualstudio_py_testlauncher.py b/pythonFiles/visualstudio_py_testlauncher.py deleted file mode 100644 index 7731b63b7e65..000000000000 --- a/pythonFiles/visualstudio_py_testlauncher.py +++ /dev/null @@ -1,393 +0,0 @@ -# Python Tools for Visual Studio -# Copyright(c) Microsoft Corporation -# All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the License); you may not use -# this file except in compliance with the License. You may obtain a copy of the -# License at http://www.apache.org/licenses/LICENSE-2.0 -# -# THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS -# OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY -# IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, -# MERCHANTABLITY OR NON-INFRINGEMENT. -# -# See the Apache Version 2.0 License for specific language governing -# permissions and limitations under the License. - -__author__ = "Microsoft Corporation " -__version__ = "3.0.0.0" - -import os -import sys -import json -import unittest -import socket -import traceback -from types import CodeType, FunctionType -import signal - -try: - import thread -except: - import _thread as thread - - -class _TestOutput(object): - """file like object which redirects output to the repl window.""" - - errors = "strict" - - def __init__(self, old_out, is_stdout): - self.is_stdout = is_stdout - self.old_out = old_out - if sys.version >= "3." and hasattr(old_out, "buffer"): - self.buffer = _TestOutputBuffer(old_out.buffer, is_stdout) - - def flush(self): - if self.old_out: - self.old_out.flush() - - def writelines(self, lines): - for line in lines: - self.write(line) - - @property - def encoding(self): - return "utf8" - - def write(self, value): - _channel.send_event("stdout" if self.is_stdout else "stderr", content=value) - if self.old_out: - self.old_out.write(value) - # flush immediately, else things go wonky and out of order - self.flush() - - def isatty(self): - return True - - def next(self): - pass - - @property - def name(self): - if self.is_stdout: - return "" - else: - return "" - - def __getattr__(self, name): - return getattr(self.old_out, name) - - -class _TestOutputBuffer(object): - def __init__(self, old_buffer, is_stdout): - self.buffer = old_buffer - self.is_stdout = is_stdout - - def write(self, data): - _channel.send_event("stdout" if self.is_stdout else "stderr", content=data) - self.buffer.write(data) - - def flush(self): - self.buffer.flush() - - def truncate(self, pos=None): - return self.buffer.truncate(pos) - - def tell(self): - return self.buffer.tell() - - def seek(self, pos, whence=0): - return self.buffer.seek(pos, whence) - - -class _IpcChannel(object): - def __init__(self, socket, callback): - self.socket = socket - self.seq = 0 - self.callback = callback - self.lock = thread.allocate_lock() - self._closed = False - # start the testing reader thread loop - self.test_thread_id = thread.start_new_thread(self.readSocket, ()) - - def close(self): - self._closed = True - - def readSocket(self): - try: - data = self.socket.recv(1024) - self.callback() - except OSError: - if not self._closed: - raise - - def receive(self): - pass - - def send_event(self, name, **args): - with self.lock: - body = {"type": "event", "seq": self.seq, "event": name, "body": args} - self.seq += 1 - content = json.dumps(body).encode("utf8") - headers = ("Content-Length: %d\n\n" % (len(content),)).encode("utf8") - self.socket.send(headers) - self.socket.send(content) - - -_channel = None - - -class VsTestResult(unittest.TextTestResult): - def startTest(self, test): - super(VsTestResult, self).startTest(test) - if _channel is not None: - _channel.send_event(name="start", test=test.id()) - - def addError(self, test, err): - super(VsTestResult, self).addError(test, err) - self.sendResult(test, "error", err) - - def addFailure(self, test, err): - super(VsTestResult, self).addFailure(test, err) - self.sendResult(test, "failed", err) - - def addSuccess(self, test): - super(VsTestResult, self).addSuccess(test) - self.sendResult(test, "passed") - - def addSkip(self, test, reason): - super(VsTestResult, self).addSkip(test, reason) - self.sendResult(test, "skipped") - - def addExpectedFailure(self, test, err): - super(VsTestResult, self).addExpectedFailure(test, err) - self.sendResult(test, "failed", err) - - def addUnexpectedSuccess(self, test): - super(VsTestResult, self).addUnexpectedSuccess(test) - self.sendResult(test, "passed") - - def sendResult(self, test, outcome, trace=None): - if _channel is not None: - tb = None - message = None - if trace is not None: - traceback.print_exc() - formatted = traceback.format_exception(*trace) - # Remove the 'Traceback (most recent call last)' - formatted = formatted[1:] - tb = "".join(formatted) - message = str(trace[1]) - _channel.send_event( - name="result", - outcome=outcome, - traceback=tb, - message=message, - test=test.id(), - ) - - -def stopTests(): - try: - os.kill(os.getpid(), signal.SIGUSR1) - except: - try: - os.kill(os.getpid(), signal.SIGTERM) - except: - pass - - -class ExitCommand(Exception): - pass - - -def signal_handler(signal, frame): - raise ExitCommand() - - -def main(): - import os - import sys - import unittest - from optparse import OptionParser - - global _channel - - parser = OptionParser( - prog="visualstudio_py_testlauncher", - usage="Usage: %prog [

+ +Output for Python in the Output panel (ViewOutput, change the drop-down the upper-right of the Output panel to Python) + + +

+ +``` +XXX +``` + +

+
diff --git a/resources/report_issue_user_data_template.md b/resources/report_issue_user_data_template.md new file mode 100644 index 000000000000..037b844511d3 --- /dev/null +++ b/resources/report_issue_user_data_template.md @@ -0,0 +1,21 @@ +- Python version (& distribution if applicable, e.g. Anaconda): {0} +- Type of virtual environment used (e.g. conda, venv, virtualenv, etc.): {1} +- Value of the `python.languageServer` setting: {2} + +
+User Settings +

+ +``` +{3}{4} +``` +

+
+ +
+Installed Extensions + +|Extension Name|Extension Id|Version| +|---|---|---| +{5} +
diff --git a/resources/report_issue_user_settings.json b/resources/report_issue_user_settings.json new file mode 100644 index 000000000000..7e034651c46d --- /dev/null +++ b/resources/report_issue_user_settings.json @@ -0,0 +1,99 @@ +{ + "initialize": false, + "pythonPath": "placeholder", + "onDidChange": false, + "defaultInterpreterPath": "placeholder", + "defaultLS": false, + "envFile": "placeholder", + "venvPath": "placeholder", + "venvFolders": "placeholder", + "activeStateToolPath": "placeholder", + "condaPath": "placeholder", + "pipenvPath": "placeholder", + "poetryPath": "placeholder", + "pixiToolPath": "placeholder", + "devOptions": false, + "globalModuleInstallation": false, + "languageServer": true, + "languageServerIsDefault": false, + "logging": true, + "useIsolation": false, + "changed": false, + "_pythonPath": false, + "_defaultInterpreterPath": false, + "workspace": false, + "workspaceRoot": false, + "linting": { + "enabled": true, + "cwd": "placeholder", + "flake8Args": "placeholder", + "flake8CategorySeverity": false, + "flake8Enabled": true, + "flake8Path": "placeholder", + "ignorePatterns": false, + "lintOnSave": true, + "maxNumberOfProblems": false, + "banditArgs": "placeholder", + "banditEnabled": true, + "banditPath": "placeholder", + "mypyArgs": "placeholder", + "mypyCategorySeverity": false, + "mypyEnabled": true, + "mypyPath": "placeholder", + "pycodestyleArgs": "placeholder", + "pycodestyleCategorySeverity": false, + "pycodestyleEnabled": true, + "pycodestylePath": "placeholder", + "prospectorArgs": "placeholder", + "prospectorEnabled": true, + "prospectorPath": "placeholder", + "pydocstyleArgs": "placeholder", + "pydocstyleEnabled": true, + "pydocstylePath": "placeholder", + "pylamaArgs": "placeholder", + "pylamaEnabled": true, + "pylamaPath": "placeholder", + "pylintArgs": "placeholder", + "pylintCategorySeverity": false, + "pylintEnabled": false, + "pylintPath": "placeholder" + }, + "analysis": { + "completeFunctionParens": true, + "autoImportCompletions": true, + "autoSearchPaths": "placeholder", + "stubPath": "placeholder", + "diagnosticMode": true, + "extraPaths": "placeholder", + "useLibraryCodeForTypes": true, + "typeCheckingMode": true, + "memory": true, + "symbolsHierarchyDepthLimit": false + }, + "testing": { + "cwd": "placeholder", + "debugPort": true, + "promptToConfigure": true, + "pytestArgs": "placeholder", + "pytestEnabled": true, + "pytestPath": "placeholder", + "unittestArgs": "placeholder", + "unittestEnabled": true, + "autoTestDiscoverOnSaveEnabled": true, + "autoTestDiscoverOnSavePattern": "placeholder" + }, + "terminal": { + "activateEnvironment": true, + "executeInFileDir": "placeholder", + "launchArgs": "placeholder", + "activateEnvInCurrentTerminal": false + }, + "tensorBoard": { + "logDirectory": "placeholder" + }, + "experiments": { + "enabled": true, + "optInto": true, + "optOutFrom": true + } +} diff --git a/resources/walkthrough/create-environment.svg b/resources/walkthrough/create-environment.svg new file mode 100644 index 000000000000..bb48e1b16711 --- /dev/null +++ b/resources/walkthrough/create-environment.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/walkthrough/create-notebook.svg b/resources/walkthrough/create-notebook.svg new file mode 100644 index 000000000000..05dadc0cc6de --- /dev/null +++ b/resources/walkthrough/create-notebook.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/walkthrough/data-science.svg b/resources/walkthrough/data-science.svg new file mode 100644 index 000000000000..506bed2161b1 --- /dev/null +++ b/resources/walkthrough/data-science.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/walkthrough/environments-info.md b/resources/walkthrough/environments-info.md new file mode 100644 index 000000000000..7bdc61a96e2e --- /dev/null +++ b/resources/walkthrough/environments-info.md @@ -0,0 +1,10 @@ +## Python Environments + +Create Environment Dropdown + +Python virtual environments are considered a best practice in Python development. A virtual environment includes a Python interpreter and any packages you have installed into it, such as numpy or Flask. + +After you create a virtual environment using the **Python: Create Environment** command, you can install packages into the environment. +For example, type `python -m pip install numpy` in an activated terminal to install `numpy` into the environment. + +🔍 Check out our [docs](https://aka.ms/pythonenvs) to learn more. diff --git a/resources/walkthrough/install-python-linux.md b/resources/walkthrough/install-python-linux.md new file mode 100644 index 000000000000..78a12870799f --- /dev/null +++ b/resources/walkthrough/install-python-linux.md @@ -0,0 +1,22 @@ +# Install Python on Linux + +To install the latest version of Python on [Debian-based Linux distributions](https://www.debian.org/), you can create a new terminal (Ctrl + Shift + `) and run the following commands: + + +``` +sudo apt-get update +sudo apt-get install python3 python3-venv python3-pip +``` + +For [Fedora-based Linux distributions](https://getfedora.org/), you can run the following: + +``` +sudo dnf install python3 +``` + +To verify if Python was successfully installed, run the following command in the terminal: + + +``` +python3 --version +``` diff --git a/resources/walkthrough/install-python-macos.md b/resources/walkthrough/install-python-macos.md new file mode 100644 index 000000000000..470d682d4eb2 --- /dev/null +++ b/resources/walkthrough/install-python-macos.md @@ -0,0 +1,15 @@ +# Install Python on macOS + +If you have [Homebrew](https://brew.sh/) installed, you can install Python by running the following command in the terminal (Ctrl + Shift + `): + +``` +brew install python3 +``` + +If you don't have Homebrew, you can download a Python installer for macOS from [python.org](https://www.python.org/downloads/mac-osx/). + +To verify if Python was successfully installed, run the following command in the terminal: + +``` +python3 --version +``` diff --git a/resources/walkthrough/install-python-windows-8.md b/resources/walkthrough/install-python-windows-8.md new file mode 100644 index 000000000000..f25f2f7d024d --- /dev/null +++ b/resources/walkthrough/install-python-windows-8.md @@ -0,0 +1,15 @@ +## Install Python on Windows + +If you don't have Python installed on your Windows machine, you can install it [from python.org](https://www.python.org/downloads). + +To verify it's installed, create a new terminal (Ctrl + Shift + `) and try running the following command: + +``` +python --version +``` + +You should see something similar to the following: +``` +Python 3.9.5 +``` +For additional information about using Python on Windows, see [Using Python on Windows at Python.org](https://docs.python.org/3.10/using/windows.html). diff --git a/resources/walkthrough/interactive-window.svg b/resources/walkthrough/interactive-window.svg new file mode 100644 index 000000000000..83446ed8e66a --- /dev/null +++ b/resources/walkthrough/interactive-window.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/walkthrough/learnmore.svg b/resources/walkthrough/learnmore.svg new file mode 100644 index 000000000000..c5fd67e75471 --- /dev/null +++ b/resources/walkthrough/learnmore.svg @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/walkthrough/open-folder.svg b/resources/walkthrough/open-folder.svg new file mode 100644 index 000000000000..1615718a83dd --- /dev/null +++ b/resources/walkthrough/open-folder.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/walkthrough/play-button-dark.png b/resources/walkthrough/play-button-dark.png new file mode 100644 index 000000000000..113ad62b87c2 Binary files /dev/null and b/resources/walkthrough/play-button-dark.png differ diff --git a/resources/walkthrough/python-interpreter.svg b/resources/walkthrough/python-interpreter.svg new file mode 100644 index 000000000000..0f6e262321ec --- /dev/null +++ b/resources/walkthrough/python-interpreter.svg @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/walkthrough/rundebug2.svg b/resources/walkthrough/rundebug2.svg new file mode 100644 index 000000000000..6d1fe753cc4f --- /dev/null +++ b/resources/walkthrough/rundebug2.svg @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schemas/conda-environment.json b/schemas/conda-environment.json index 458676942a44..fb1e821778c3 100644 --- a/schemas/conda-environment.json +++ b/schemas/conda-environment.json @@ -1,6 +1,6 @@ { "title": "conda environment file", - "description": "Support for conda's enviroment.yml files (e.g. `conda env export > environment.yml`)", + "description": "Support for conda's environment.yml files (e.g. `conda env export > environment.yml`)", "id": "https://raw.githubusercontent.com/Microsoft/vscode-python/main/schemas/conda-environment.json", "$schema": "http://json-schema.org/draft-04/schema#", "definitions": { diff --git a/schemas/condarc.json b/schemas/condarc.json index 396236238c1a..a881315d3137 100644 --- a/schemas/condarc.json +++ b/schemas/condarc.json @@ -59,7 +59,14 @@ } }, "ssl_verify": { - "type": "boolean" + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "string" + } + ] }, "offline": { "type": "boolean" diff --git a/scripts/cleanup-eslintignore.js b/scripts/cleanup-eslintignore.js new file mode 100644 index 000000000000..848f5a9c4910 --- /dev/null +++ b/scripts/cleanup-eslintignore.js @@ -0,0 +1,44 @@ +const fs = require('fs'); +const path = require('path'); + +const baseDir = process.cwd(); +const eslintignorePath = path.join(baseDir, '.eslintignore'); + +fs.readFile(eslintignorePath, 'utf8', (err, data) => { + if (err) { + console.error('Error reading .eslintignore file:', err); + return; + } + + const lines = data.split('\n'); + const files = lines.map((line) => line.trim()).filter((line) => line && !line.startsWith('#')); + const nonExistentFiles = []; + + files.forEach((file) => { + const filePath = path.join(baseDir, file); + if (!fs.existsSync(filePath) && file !== 'pythonExtensionApi/out/') { + nonExistentFiles.push(file); + } + }); + + if (nonExistentFiles.length > 0) { + console.log('The following files listed in .eslintignore do not exist:'); + nonExistentFiles.forEach((file) => console.log(file)); + + const updatedLines = lines.filter((line) => { + const trimmedLine = line.trim(); + return !nonExistentFiles.includes(trimmedLine) || trimmedLine === 'pythonExtensionApi/out/'; + }); + const updatedData = `${updatedLines.join('\n')}\n`; + + fs.writeFile(eslintignorePath, updatedData, 'utf8', (err) => { + if (err) { + console.error('Error writing to .eslintignore file:', err); + return; + } + console.log('Non-existent files have been removed from .eslintignore.'); + }); + } else { + console.log('All files listed in .eslintignore exist.'); + } +}); diff --git a/scripts/issue_velocity_summary_script.py b/scripts/issue_velocity_summary_script.py new file mode 100644 index 000000000000..94929d1798a9 --- /dev/null +++ b/scripts/issue_velocity_summary_script.py @@ -0,0 +1,110 @@ +""" +This script fetches open issues from the microsoft/vscode-python repository, +calculates the thumbs-up per day for each issue, and generates a markdown +summary of the issues sorted by highest thumbs-up per day. Issues with zero +thumbs-up are excluded from the summary. +""" + +import requests +import os +from datetime import datetime, timezone + + +GITHUB_API_URL = "https://api.github.com" +REPO = "microsoft/vscode-python" +TOKEN = os.getenv("GITHUB_TOKEN") + + +def fetch_issues(): + """ + Fetches all open issues from the specified GitHub repository. + + Returns: + list: A list of dictionaries representing the issues. + """ + headers = {"Authorization": f"token {TOKEN}"} + issues = [] + page = 1 + while True: + query = ( + f"{GITHUB_API_URL}/repos/{REPO}/issues?state=open&per_page=25&page={page}" + ) + response = requests.get(query, headers=headers) + if response.status_code == 403: + raise Exception( + "Access forbidden: Check your GitHub token and permissions." + ) + response.raise_for_status() + page_issues = response.json() + if not page_issues: + break + issues.extend(page_issues) + page += 1 + return issues + + +def calculate_thumbs_up_per_day(issue): + """ + Calculates the thumbs-up per day for a given issue. + + Args: + issue (dict): A dictionary representing the issue. + + Returns: + float: The thumbs-up per day for the issue. + """ + created_at = datetime.strptime(issue["created_at"], "%Y-%m-%dT%H:%M:%SZ").replace( + tzinfo=timezone.utc + ) + now = datetime.now(timezone.utc) + days_open = (now - created_at).days or 1 + thumbs_up = issue["reactions"].get("+1", 0) + return thumbs_up / days_open + + +def generate_markdown_summary(issues): + """ + Generates a markdown summary of the issues. + + Args: + issues (list): A list of dictionaries representing the issues. + + Returns: + str: A markdown-formatted string summarizing the issues. + """ + summary = "| URL | Title | 👍 | Days Open | 👍/day |\n| --- | ----- | --- | --------- | ------ |\n" + issues_with_thumbs_up = [] + for issue in issues: + created_at = datetime.strptime( + issue["created_at"], "%Y-%m-%dT%H:%M:%SZ" + ).replace(tzinfo=timezone.utc) + now = datetime.now(timezone.utc) + days_open = (now - created_at).days or 1 + thumbs_up = issue["reactions"].get("+1", 0) + if thumbs_up > 0: + thumbs_up_per_day = thumbs_up / days_open + issues_with_thumbs_up.append( + (issue, thumbs_up, days_open, thumbs_up_per_day) + ) + + # Sort issues by thumbs_up_per_day in descending order + issues_with_thumbs_up.sort(key=lambda x: x[3], reverse=True) + + for issue, thumbs_up, days_open, thumbs_up_per_day in issues_with_thumbs_up: + summary += f"| {issue['html_url']} | {issue['title']} | {thumbs_up} | {days_open} | {thumbs_up_per_day:.2f} |\n" + + return summary + + +def main(): + """ + Main function to fetch issues, generate the markdown summary, and write it to a file. + """ + issues = fetch_issues() + summary = generate_markdown_summary(issues) + with open("endorsement_velocity_summary.md", "w") as f: + f.write(summary) + + +if __name__ == "__main__": + main() diff --git a/scripts/onCreateCommand.sh b/scripts/onCreateCommand.sh new file mode 100644 index 000000000000..3d473d1ee172 --- /dev/null +++ b/scripts/onCreateCommand.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +# Install pyenv and Python versions here to avoid using shim. +curl https://pyenv.run | bash +echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc +echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc +# echo 'eval "$(pyenv init -)"' >> ~/.bashrc + +export PYENV_ROOT="$HOME/.pyenv" +command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH" +# eval "$(pyenv init -)" Comment this out and DO NOT use shim. +source ~/.bashrc + +# Install Python via pyenv . +pyenv install 3.8.18 3.9:latest 3.10:latest 3.11:latest + +# Set default Python version to 3.8 . +pyenv global 3.8.18 + +npm ci + +# Create Virutal environment. +pyenv exec python -m venv .venv + +# Activate Virtual environment. +source /workspaces/vscode-python/.venv/bin/activate + +# Install required Python libraries. +/workspaces/vscode-python/.venv/bin/python -m pip install nox +nox --session install_python_libs + +/workspaces/vscode-python/.venv/bin/python -m pip install -r build/test-requirements.txt +/workspaces/vscode-python/.venv/bin/python -m pip install -r build/functional-test-requirements.txt + +# Below will crash codespace +# npm run compile diff --git a/snippets/python.json b/snippets/python.json deleted file mode 100644 index 59dde0bc62db..000000000000 --- a/snippets/python.json +++ /dev/null @@ -1,175 +0,0 @@ -{ - "if": { - "prefix": "if", - "body": ["if ${1:expression}:", "\t${2:pass}"], - "description": "Code snippet for an if statement" - }, - "if/else": { - "prefix": "if/else", - "body": ["if ${1:condition}:", "\t${2:pass}", "else:", "\t${3:pass}"], - "description": "Code snippet for an if statement with else" - }, - "elif": { - "prefix": "elif", - "body": ["elif ${1:expression}:", "\t${2:pass}"], - "description": "Code snippet for an elif" - }, - "else": { - "prefix": "else", - "body": ["else:", "\t${1:pass}"], - "description": "Code snippet for an else" - }, - "while": { - "prefix": "while", - "body": ["while ${1:expression}:", "\t${2:pass}"], - "description": "Code snippet for a while loop" - }, - "while/else": { - "prefix": "while/else", - "body": ["while ${1:expression}:", "\t${2:pass}", "else:", "\t${3:pass}"], - "description": "Code snippet for a while loop with else" - }, - "for": { - "prefix": "for", - "body": ["for ${1:target_list} in ${2:expression_list}:", "\t${3:pass}"], - "description": "Code snippet for a for loop" - }, - "for/else": { - "prefix": "for/else", - "body": ["for ${1:target_list} in ${2:expression_list}:", "\t${3:pass}", "else:", "\t${4:pass}"], - "description": "Code snippet for a for loop with else" - }, - "try/except": { - "prefix": "try/except", - "body": ["try:", "\t${1:pass}", "except ${2:expression} as ${3:identifier}:", "\t${4:pass}"], - "description": "Code snippet for a try/except statement" - }, - "try/finally": { - "prefix": "try/finally", - "body": ["try:", "\t${1:pass}", "finally:", "\t${2:pass}"], - "description": "Code snippet for a try/finally statement" - }, - "try/except/else": { - "prefix": "try/except/else", - "body": [ - "try:", - "\t${1:pass}", - "except ${2:expression} as ${3:identifier}:", - "\t${4:pass}", - "else:", - "\t${5:pass}" - ], - "description": "Code snippet for a try/except/else statement" - }, - "try/except/finally": { - "prefix": "try/except/finally", - "body": [ - "try:", - "\t${1:pass}", - "except ${2:expression} as ${3:identifier}:", - "\t${4:pass}", - "finally:", - "\t${5:pass}" - ], - "description": "Code snippet for a try/except/finally statement" - }, - "try/except/else/finally": { - "prefix": "try/except/else/finally", - "body": [ - "try:", - "\t${1:pass}", - "except ${2:expression} as ${3:identifier}:", - "\t${4:pass}", - "else:", - "\t${5:pass}", - "finally:", - "\t${6:pass}" - ], - "description": "Code snippet for a try/except/else/finally statement" - }, - "with": { - "prefix": "with", - "body": ["with ${1:expression} as ${2:target}:", "\t${3:pass}"], - "description": "Code snippet for a with statement" - }, - "def": { - "prefix": "def", - "body": ["def ${1:funcname}(${2:parameter_list}):", "\t\"\"\"", "\t${3:docstring}", "\t\"\"\"","\t${4:pass}"], - "description": "Code snippet for a function definition" - }, - "def(class method)": { - "prefix": "def(class method)", - "body": ["def ${1:funcname}(self, ${2:parameter_list}):", "\t\"\"\"", "\t${3:docstring}", "\t\"\"\"", "\t${4:pass}"], - "description": "Code snippet for a class method" - }, - "def(static class method)": { - "prefix": "def(static class method)", - "body": ["@staticmethod", "def ${1:funcname}(${2:parameter_list}):", "\t\"\"\"", "\t${3:docstring}", "\t\"\"\"", "\t${4:pass}"], - "description": "Code snippet for a static class method" - }, - "def(abstract class method)": { - "prefix": "def(abstract class method)", - "body": ["def ${1:funcname}(self, ${2:parameter_list}):", "\t\"\"\"", "\t${3:docstring}", "\t\"\"\"", "\traise NotImplementedError"], - "description": "Code snippet for an abstract class method" - }, - "class": { - "prefix": "class", - "body": ["class ${1:classname}(${2:object}):", "\t\"\"\"", "\t${3:docstring}", "\t\"\"\"", "\t${4:pass}"], - "description": "Code snippet for a class definition" - }, - "lambda": { - "prefix": "lambda", - "body": ["lambda ${1:parameter_list}: ${2:expression}"], - "description": "Code snippet for a lambda statement" - }, - "if(main)": { - "prefix": "__main__", - "body": ["if __name__ == \"__main__\":", " ${1:pass}"], - "description": "Code snippet for a `if __name__ == \"__main__\": ...` block" - }, - "async/def": { - "prefix": "async/def", - "body": ["async def ${1:funcname}(${2:parameter_list}):", "\t${3:pass}"], - "description": "Code snippet for an async statement" - }, - "async/for": { - "prefix": "async/for", - "body": ["async for ${1:target} in ${2:iter}:", "\t${3:block}"], - "description": "Code snippet for an async for statement" - }, - "async/for/else": { - "prefix": "async/for/else", - "body": ["async for ${1:target} in ${2:iter}:", "\t${3:block}", "else:", "\t${4:block}"], - "description": "Code snippet for an async for statement with else" - }, - "async/with": { - "prefix": "async/with", - "body": ["async with ${1:expr} as ${2:var}:", "\t${3:block}"], - "description": "Code snippet for an async with statement" - }, - "ipdb": { - "prefix": "ipdb", - "body": "import ipdb; ipdb.set_trace()", - "description": "Code snippet for ipdb debug" - }, - "pdb": { - "prefix": "pdb", - "body": "import pdb; pdb.set_trace()", - "description": "Code snippet for pdb debug" - }, - "pudb": { - "prefix": "pudb", - "body": "import pudb; pudb.set_trace()", - "description": "Code snippet for pudb debug" - }, - "add/new/cell": { - "prefix": "add/new/cell", - "body": "# %%", - "description": "Code snippet to add a new cell" - }, - "mark/markdown": { - "prefix": "mark/markdown", - "body": "# %% [markdown]", - "description": "Code snippet to add a new markdown cell" - } -} diff --git a/sprint-planning.github-issues b/sprint-planning.github-issues old mode 100755 new mode 100644 index 73c40eae0253..1fbd09a790e8 --- a/sprint-planning.github-issues +++ b/sprint-planning.github-issues @@ -2,97 +2,71 @@ { "kind": 1, "language": "markdown", - "value": "# Query constants", - "editable": true + "value": "# Query constants" }, { "kind": 2, "language": "github-issues", - "value": "$pvsc=repo:microsoft/vscode-python\n$not_DS=-label:\"data science\"\n$open=is:open", - "editable": true + "value": "$pvsc=repo:microsoft/vscode-python\n$open=is:open\n$upvotes=sort:reactions-+1-desc" }, { "kind": 1, "language": "markdown", - "value": "# Priority issues 🚨", - "editable": true + "value": "# Priority issues 🚨" }, { "kind": 1, "language": "markdown", - "value": "## P0", - "editable": true + "value": "## Important/P1" }, { "kind": 2, "language": "github-issues", - "value": "$pvsc $not_DS $open label:\"P0\"", - "editable": true + "value": "$pvsc $open label:\"important\"" }, { "kind": 1, "language": "markdown", - "value": "## P1", - "editable": true + "value": "# Regressions 🔙" }, { "kind": 2, "language": "github-issues", - "value": "$pvsc $not_DS $open label:\"P1\"", - "editable": true + "value": "$pvsc $open label:\"regression\"" }, { "kind": 1, "language": "markdown", - "value": "# Regressions 🔙", - "editable": true + "value": "# Partner asks" }, { "kind": 2, "language": "github-issues", - "value": "$pvsc $not_DS $open label:\"reason-regression\"", - "editable": true + "value": "$pvsc $open label:\"partner ask\"" }, { "kind": 1, "language": "markdown", - "value": "# Partner asks", - "editable": true - }, - { - "kind": 2, - "language": "github-issues", - "value": "$pvsc $not_DS $open label:\"partner ask\"", - "editable": true - }, - { - "kind": 1, - "language": "markdown", - "value": "# Upvotes 👍", - "editable": true + "value": "# Upvotes 👍" }, { "kind": 1, "language": "markdown", - "value": "## Enhancements 💪", - "editable": true + "value": "## Enhancements 💪" }, { "kind": 2, "language": "github-issues", - "value": "$pvsc $not_DS $open sort:reactions-+1-desc label:\"type-enhancement\" ", - "editable": true + "value": "$pvsc $open $upvotes label:\"feature-request\" " }, { "kind": 1, "language": "markdown", - "value": "## Bugs 🐜", - "editable": true + "value": "## Bugs 🐜" }, { "kind": 2, "language": "github-issues", - "value": "$pvsc $not_DS $open sort:reactions-+1-desc label:\"type-bug\"", - "editable": true + "value": "$pvsc $open $upvotes label:\"bug\"" } -] \ No newline at end of file +] diff --git a/src/client/activation/aaTesting.ts b/src/client/activation/aaTesting.ts deleted file mode 100644 index 1a424e5f2606..000000000000 --- a/src/client/activation/aaTesting.ts +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { ValidateABTesting } from '../common/experiments/groups'; -import { IExperimentsManager } from '../common/types'; -import { IExtensionSingleActivationService } from './types'; - -@injectable() -export class AATesting implements IExtensionSingleActivationService { - constructor(@inject(IExperimentsManager) private experiments: IExperimentsManager) {} - - public async activate(): Promise { - this.experiments.sendTelemetryIfInExperiment(ValidateABTesting.experiment); - this.experiments.sendTelemetryIfInExperiment(ValidateABTesting.control); - } -} diff --git a/src/client/activation/activationManager.ts b/src/client/activation/activationManager.ts index bf53a1d18103..9e97c5c48857 100644 --- a/src/client/activation/activationManager.ts +++ b/src/client/activation/activationManager.ts @@ -7,40 +7,59 @@ import { inject, injectable, multiInject } from 'inversify'; import { TextDocument } from 'vscode'; import { IApplicationDiagnostics } from '../application/types'; import { IActiveResourceService, IDocumentManager, IWorkspaceService } from '../common/application/types'; -import { DEFAULT_INTERPRETER_SETTING, PYTHON_LANGUAGE } from '../common/constants'; -import { DeprecatePythonPath } from '../common/experiments/groups'; -import { traceDecorators } from '../common/logger'; +import { PYTHON_LANGUAGE } from '../common/constants'; import { IFileSystem } from '../common/platform/types'; -import { IDisposable, IExperimentsManager, IInterpreterPathService, Resource } from '../common/types'; -import { createDeferred, Deferred } from '../common/utils/async'; -import { IInterpreterAutoSelectionService, IInterpreterSecurityService } from '../interpreter/autoSelection/types'; -import { IInterpreterService } from '../interpreter/contracts'; +import { IDisposable, IInterpreterPathService, Resource } from '../common/types'; +import { Deferred } from '../common/utils/async'; +import { StopWatch } from '../common/utils/stopWatch'; +import { IInterpreterAutoSelectionService } from '../interpreter/autoSelection/types'; +import { traceDecoratorError } from '../logging'; import { sendActivationTelemetry } from '../telemetry/envFileTelemetry'; import { IExtensionActivationManager, IExtensionActivationService, IExtensionSingleActivationService } from './types'; @injectable() export class ExtensionActivationManager implements IExtensionActivationManager { public readonly activatedWorkspaces = new Set(); + protected readonly isInterpreterSetForWorkspacePromises = new Map>(); + private readonly disposables: IDisposable[] = []; + private docOpenedHandler?: IDisposable; + constructor( - @multiInject(IExtensionActivationService) private readonly activationServices: IExtensionActivationService[], + @multiInject(IExtensionActivationService) private activationServices: IExtensionActivationService[], @multiInject(IExtensionSingleActivationService) - private readonly singleActivationServices: IExtensionSingleActivationService[], + private singleActivationServices: IExtensionSingleActivationService[], @inject(IDocumentManager) private readonly documentManager: IDocumentManager, - @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, @inject(IInterpreterAutoSelectionService) private readonly autoSelection: IInterpreterAutoSelectionService, @inject(IApplicationDiagnostics) private readonly appDiagnostics: IApplicationDiagnostics, @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, @inject(IFileSystem) private readonly fileSystem: IFileSystem, @inject(IActiveResourceService) private readonly activeResourceService: IActiveResourceService, - @inject(IExperimentsManager) private readonly experiments: IExperimentsManager, @inject(IInterpreterPathService) private readonly interpreterPathService: IInterpreterPathService, - @inject(IInterpreterSecurityService) private readonly interpreterSecurityService: IInterpreterSecurityService ) {} - public dispose() { + private filterServices() { + if (!this.workspaceService.isTrusted) { + this.activationServices = this.activationServices.filter( + (service) => service.supportedWorkspaceTypes.untrustedWorkspace, + ); + this.singleActivationServices = this.singleActivationServices.filter( + (service) => service.supportedWorkspaceTypes.untrustedWorkspace, + ); + } + if (this.workspaceService.isVirtualWorkspace) { + this.activationServices = this.activationServices.filter( + (service) => service.supportedWorkspaceTypes.virtualWorkspace, + ); + this.singleActivationServices = this.singleActivationServices.filter( + (service) => service.supportedWorkspaceTypes.virtualWorkspace, + ); + } + } + + public dispose(): void { while (this.disposables.length > 0) { const disposable = this.disposables.shift()!; disposable.dispose(); @@ -50,91 +69,65 @@ export class ExtensionActivationManager implements IExtensionActivationManager { this.docOpenedHandler = undefined; } } - public async activate(): Promise { + + public async activate(startupStopWatch: StopWatch): Promise { + this.filterServices(); await this.initialize(); + // Activate all activation services together. + await Promise.all([ - Promise.all(this.singleActivationServices.map((item) => item.activate())), - this.activateWorkspace(this.activeResourceService.getActiveResource()) + ...this.singleActivationServices.map((item) => item.activate()), + this.activateWorkspace(this.activeResourceService.getActiveResource(), startupStopWatch), ]); - await this.autoSelection.autoSelectInterpreter(undefined); } - @traceDecorators.error('Failed to activate a workspace') - public async activateWorkspace(resource: Resource) { + + @traceDecoratorError('Failed to activate a workspace') + public async activateWorkspace(resource: Resource, startupStopWatch?: StopWatch): Promise { + const folder = this.workspaceService.getWorkspaceFolder(resource); + resource = folder ? folder.uri : undefined; const key = this.getWorkspaceKey(resource); if (this.activatedWorkspaces.has(key)) { return; } this.activatedWorkspaces.add(key); - if (this.experiments.inExperiment(DeprecatePythonPath.experiment)) { + if (this.workspaceService.isTrusted) { + // Do not interact with interpreters in a untrusted workspace. + await this.autoSelection.autoSelectInterpreter(resource); await this.interpreterPathService.copyOldInterpreterStorageValuesToNew(resource); } - this.experiments.sendTelemetryIfInExperiment(DeprecatePythonPath.control); - - // Get latest interpreter list in the background. - this.interpreterService.getInterpreters(resource).ignoreErrors(); - await sendActivationTelemetry(this.fileSystem, this.workspaceService, resource); - - await this.autoSelection.autoSelectInterpreter(resource); - await this.evaluateAutoSelectedInterpreterSafety(resource); - await Promise.all(this.activationServices.map((item) => item.activate(resource))); + await Promise.all(this.activationServices.map((item) => item.activate(resource, startupStopWatch))); await this.appDiagnostics.performPreStartupHealthCheck(resource); } - public async initialize() { + + public async initialize(): Promise { this.addHandlers(); this.addRemoveDocOpenedHandlers(); } - public onDocOpened(doc: TextDocument) { + + public onDocOpened(doc: TextDocument): void { if (doc.languageId !== PYTHON_LANGUAGE) { return; } const key = this.getWorkspaceKey(doc.uri); + const hasWorkspaceFolders = (this.workspaceService.workspaceFolders?.length || 0) > 0; // If we have opened a doc that does not belong to workspace, then do nothing. - if (key === '' && this.workspaceService.hasWorkspaceFolders) { + if (key === '' && hasWorkspaceFolders) { return; } if (this.activatedWorkspaces.has(key)) { return; } - const folder = this.workspaceService.getWorkspaceFolder(doc.uri); - this.activateWorkspace(folder ? folder.uri : undefined).ignoreErrors(); + this.activateWorkspace(doc.uri).ignoreErrors(); } - public async evaluateAutoSelectedInterpreterSafety(resource: Resource) { - if (this.experiments.inExperiment(DeprecatePythonPath.experiment)) { - const workspaceKey = this.getWorkspaceKey(resource); - const interpreterSettingValue = this.interpreterPathService.get(resource); - if (interpreterSettingValue.length === 0 || interpreterSettingValue === DEFAULT_INTERPRETER_SETTING) { - // Setting is not set, extension will use the autoselected value. Make sure it's safe. - const interpreter = this.autoSelection.getAutoSelectedInterpreter(resource); - if (interpreter) { - const isInterpreterSetForWorkspace = createDeferred(); - this.isInterpreterSetForWorkspacePromises.set(workspaceKey, isInterpreterSetForWorkspace); - await Promise.race([ - isInterpreterSetForWorkspace.promise, - this.interpreterSecurityService.evaluateAndRecordInterpreterSafety(interpreter, resource) - ]); - } - } else { - // Resolve any concurrent calls waiting on the promise - if (this.isInterpreterSetForWorkspacePromises.has(workspaceKey)) { - this.isInterpreterSetForWorkspacePromises.get(workspaceKey)!.resolve(); - this.isInterpreterSetForWorkspacePromises.delete(workspaceKey); - } - } - } - this.experiments.sendTelemetryIfInExperiment(DeprecatePythonPath.control); - } - - protected addHandlers() { + protected addHandlers(): void { this.disposables.push(this.workspaceService.onDidChangeWorkspaceFolders(this.onWorkspaceFoldersChanged, this)); - this.disposables.push( - this.interpreterPathService.onDidChange((i) => this.evaluateAutoSelectedInterpreterSafety(i.uri)) - ); } - protected addRemoveDocOpenedHandlers() { + + protected addRemoveDocOpenedHandlers(): void { if (this.hasMultipleWorkspaces()) { if (!this.docOpenedHandler) { this.docOpenedHandler = this.documentManager.onDidOpenTextDocument(this.onDocOpened, this); @@ -146,10 +139,11 @@ export class ExtensionActivationManager implements IExtensionActivationManager { this.docOpenedHandler = undefined; } } - protected onWorkspaceFoldersChanged() { - //If an activated workspace folder was removed, delete its key + + protected onWorkspaceFoldersChanged(): void { + // If an activated workspace folder was removed, delete its key const workspaceKeys = this.workspaceService.workspaceFolders!.map((workspaceFolder) => - this.getWorkspaceKey(workspaceFolder.uri) + this.getWorkspaceKey(workspaceFolder.uri), ); const activatedWkspcKeys = Array.from(this.activatedWorkspaces.keys()); const activatedWkspcFoldersRemoved = activatedWkspcKeys.filter((item) => workspaceKeys.indexOf(item) < 0); @@ -160,10 +154,12 @@ export class ExtensionActivationManager implements IExtensionActivationManager { } this.addRemoveDocOpenedHandlers(); } - protected hasMultipleWorkspaces() { - return this.workspaceService.hasWorkspaceFolders && this.workspaceService.workspaceFolders!.length > 1; + + protected hasMultipleWorkspaces(): boolean { + return (this.workspaceService.workspaceFolders?.length || 0) > 1; } - protected getWorkspaceKey(resource: Resource) { + + protected getWorkspaceKey(resource: Resource): string { return this.workspaceService.getWorkspaceFolderIdentifier(resource, ''); } } diff --git a/src/client/activation/activationService.ts b/src/client/activation/activationService.ts deleted file mode 100644 index e076b9625869..000000000000 --- a/src/client/activation/activationService.ts +++ /dev/null @@ -1,316 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import '../common/extensions'; - -import { inject, injectable } from 'inversify'; -import { ConfigurationChangeEvent, Disposable, OutputChannel, Uri } from 'vscode'; - -import { LSNotSupportedDiagnosticServiceId } from '../application/diagnostics/checks/lsNotSupported'; -import { IDiagnosticsService } from '../application/diagnostics/types'; -import { - IApplicationEnvironment, - IApplicationShell, - ICommandManager, - IWorkspaceService -} from '../common/application/types'; -import { STANDARD_OUTPUT_CHANNEL } from '../common/constants'; -import { traceError } from '../common/logger'; -import { - IConfigurationService, - IDisposableRegistry, - IExtensions, - IOutputChannel, - IPersistentStateFactory, - IPythonSettings, - Resource -} from '../common/types'; -import { swallowExceptions } from '../common/utils/decorators'; -import { LanguageService } from '../common/utils/localize'; -import { noop } from '../common/utils/misc'; -import { IInterpreterService } from '../interpreter/contracts'; -import { IServiceContainer } from '../ioc/types'; -import { PythonEnvironment } from '../pythonEnvironments/info'; -import { sendTelemetryEvent } from '../telemetry'; -import { EventName } from '../telemetry/constants'; -import { Commands } from './commands'; -import { LanguageServerChangeHandler } from './common/languageServerChangeHandler'; -import { RefCountedLanguageServer } from './refCountedLanguageServer'; -import { - IExtensionActivationService, - ILanguageServerActivator, - ILanguageServerCache, - LanguageServerType -} from './types'; - -const languageServerSetting: keyof IPythonSettings = 'languageServer'; -const workspacePathNameForGlobalWorkspaces = ''; - -interface IActivatedServer { - key: string; - server: ILanguageServerActivator; - jedi: boolean; -} - -@injectable() -export class LanguageServerExtensionActivationService - implements IExtensionActivationService, ILanguageServerCache, Disposable { - private cache = new Map>(); - private activatedServer?: IActivatedServer; - private readonly workspaceService: IWorkspaceService; - private readonly output: OutputChannel; - private readonly interpreterService: IInterpreterService; - private readonly languageServerChangeHandler: LanguageServerChangeHandler; - private resource!: Resource; - - constructor( - @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(IPersistentStateFactory) private stateFactory: IPersistentStateFactory - ) { - this.workspaceService = this.serviceContainer.get(IWorkspaceService); - this.interpreterService = this.serviceContainer.get(IInterpreterService); - this.output = this.serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); - - const commandManager = this.serviceContainer.get(ICommandManager); - const disposables = serviceContainer.get(IDisposableRegistry); - disposables.push(this); - disposables.push(this.workspaceService.onDidChangeConfiguration(this.onDidChangeConfiguration.bind(this))); - disposables.push(this.workspaceService.onDidChangeWorkspaceFolders(this.onWorkspaceFoldersChanged, this)); - disposables.push(this.interpreterService.onDidChangeInterpreter(this.onDidChangeInterpreter.bind(this))); - disposables.push( - commandManager.registerCommand(Commands.ClearAnalyisCache, this.onClearAnalysisCaches.bind(this)) - ); - - this.languageServerChangeHandler = new LanguageServerChangeHandler( - this.getCurrentLanguageServerType(), - this.serviceContainer.get(IExtensions), - this.serviceContainer.get(IApplicationShell), - this.serviceContainer.get(IApplicationEnvironment), - this.serviceContainer.get(ICommandManager) - ); - disposables.push(this.languageServerChangeHandler); - } - - public async activate(resource: Resource): Promise { - // Get a new server and dispose of the old one (might be the same one) - this.resource = resource; - const interpreter = await this.interpreterService.getActiveInterpreter(resource); - const key = await this.getKey(resource, interpreter); - - // If we have an old server with a different key, then deactivate it as the - // creation of the new server may fail if this server is still connected - if (this.activatedServer && this.activatedServer.key !== key) { - this.activatedServer.server.deactivate(); - } - - // Get the new item - const result = await this.get(resource, interpreter); - - // Now we dispose. This ensures the object stays alive if it's the same object because - // we dispose after we increment the ref count. - if (this.activatedServer) { - this.activatedServer.server.dispose(); - } - - // Save our active server. - this.activatedServer = { key, server: result, jedi: result.type === LanguageServerType.Jedi }; - - // Force this server to reconnect (if disconnected) as it should be the active - // language server for all of VS code. - this.activatedServer.server.activate(); - } - - public async get(resource: Resource, interpreter?: PythonEnvironment): Promise { - // See if we already have it or not - const key = await this.getKey(resource, interpreter); - let result: Promise | undefined = this.cache.get(key); - if (!result) { - // Create a special ref counted result so we don't dispose of the - // server too soon. - result = this.createRefCountedServer(resource, interpreter, key); - this.cache.set(key, result); - } else { - // Increment ref count if already exists. - result = result.then((r) => { - r.increment(); - return r; - }); - } - return result; - } - - public dispose() { - if (this.activatedServer) { - this.activatedServer.server.dispose(); - } - } - - @swallowExceptions('Send telemetry for language server current selection') - public async sendTelemetryForChosenLanguageServer(languageServer: LanguageServerType): Promise { - const state = this.stateFactory.createGlobalPersistentState( - 'SWITCH_LS', - undefined - ); - if (typeof state.value !== 'string') { - await state.updateValue(languageServer); - } - if (state.value !== languageServer) { - await state.updateValue(languageServer); - sendTelemetryEvent(EventName.PYTHON_LANGUAGE_SERVER_CURRENT_SELECTION, undefined, { - switchTo: languageServer - }); - } else { - sendTelemetryEvent(EventName.PYTHON_LANGUAGE_SERVER_CURRENT_SELECTION, undefined, { - lsStartup: languageServer - }); - } - } - - /** - * Checks if user does not have any `languageServer` setting set. - * @param resource - * @returns `true` if user is using default configuration, `false` if user has `languageServer` setting added. - */ - public isJediUsingDefaultConfiguration(resource: Resource): boolean { - const settings = this.workspaceService - .getConfiguration('python', resource) - .inspect('languageServer'); - if (!settings) { - traceError('WorkspaceConfiguration.inspect returns `undefined` for setting `python.languageServer`'); - return false; - } - return ( - settings.globalValue === undefined && - settings.workspaceValue === undefined && - settings.workspaceFolderValue === undefined - ); - } - - protected async onWorkspaceFoldersChanged() { - //If an activated workspace folder was removed, dispose its activator - const workspaceKeys = await Promise.all( - this.workspaceService.workspaceFolders!.map((workspaceFolder) => this.getKey(workspaceFolder.uri)) - ); - const activatedWkspcKeys = Array.from(this.cache.keys()); - const activatedWkspcFoldersRemoved = activatedWkspcKeys.filter((item) => workspaceKeys.indexOf(item) < 0); - if (activatedWkspcFoldersRemoved.length > 0) { - for (const folder of activatedWkspcFoldersRemoved) { - const server = await this.cache.get(folder); - server?.dispose(); // This should remove it from the cache if this is the last instance. - } - } - } - - private async onDidChangeInterpreter() { - // Reactivate the resource. It should destroy the old one if it's different. - return this.activate(this.resource); - } - - private getCurrentLanguageServerType(): LanguageServerType { - const configurationService = this.serviceContainer.get(IConfigurationService); - return configurationService.getSettings(this.resource).languageServer; - } - - private async createRefCountedServer( - resource: Resource, - interpreter: PythonEnvironment | undefined, - key: string - ): Promise { - let serverType = this.getCurrentLanguageServerType(); - if (serverType === LanguageServerType.Microsoft) { - const lsNotSupportedDiagnosticService = this.serviceContainer.get( - IDiagnosticsService, - LSNotSupportedDiagnosticServiceId - ); - const diagnostic = await lsNotSupportedDiagnosticService.diagnose(undefined); - lsNotSupportedDiagnosticService.handle(diagnostic).ignoreErrors(); - if (diagnostic.length) { - sendTelemetryEvent(EventName.PYTHON_LANGUAGE_SERVER_PLATFORM_SUPPORTED, undefined, { - supported: false - }); - serverType = LanguageServerType.Jedi; - } - } - - this.sendTelemetryForChosenLanguageServer(serverType).ignoreErrors(); - - await this.logStartup(serverType); - let server = this.serviceContainer.get(ILanguageServerActivator, serverType); - try { - await server.start(resource, interpreter); - } catch (ex) { - if (serverType === LanguageServerType.Jedi) { - throw ex; - } - this.output.appendLine(LanguageService.lsFailedToStart()); - serverType = LanguageServerType.Jedi; - server = this.serviceContainer.get(ILanguageServerActivator, serverType); - await server.start(resource, interpreter); - } - - // Wrap the returned server in something that ref counts it. - return new RefCountedLanguageServer(server, serverType, () => { - // When we finally remove the last ref count, remove from the cache - this.cache.delete(key); - - // Dispose of the actual server. - server.dispose(); - }); - } - - private async logStartup(serverType: LanguageServerType): Promise { - let outputLine; - switch (serverType) { - case LanguageServerType.Jedi: - outputLine = LanguageService.startingJedi(); - break; - case LanguageServerType.Microsoft: - outputLine = LanguageService.startingMicrosoft(); - break; - case LanguageServerType.Node: - outputLine = LanguageService.startingPylance(); - break; - case LanguageServerType.None: - outputLine = LanguageService.startingNone(); - break; - default: - throw new Error('Unknown langauge server type in activator.'); - } - this.output.appendLine(outputLine); - } - - private async onDidChangeConfiguration(event: ConfigurationChangeEvent): Promise { - const workspacesUris: (Uri | undefined)[] = this.workspaceService.hasWorkspaceFolders - ? this.workspaceService.workspaceFolders!.map((workspace) => workspace.uri) - : [undefined]; - if ( - workspacesUris.findIndex((uri) => event.affectsConfiguration(`python.${languageServerSetting}`, uri)) === -1 - ) { - return; - } - const lsType = this.getCurrentLanguageServerType(); - if (this.activatedServer?.key !== lsType) { - await this.languageServerChangeHandler.handleLanguageServerChange(lsType); - } - } - - private async getKey(resource: Resource, interpreter?: PythonEnvironment): Promise { - const configurationService = this.serviceContainer.get(IConfigurationService); - const serverType = configurationService.getSettings(this.resource).languageServer; - if (serverType === LanguageServerType.Node) { - return 'shared-ls'; - } - - const resourcePortion = this.workspaceService.getWorkspaceFolderIdentifier( - resource, - workspacePathNameForGlobalWorkspaces - ); - interpreter = interpreter ? interpreter : await this.interpreterService.getActiveInterpreter(resource); - const interperterPortion = interpreter ? `${interpreter.path}-${interpreter.envName}` : ''; - return `${resourcePortion}-${interperterPortion}`; - } - - private async onClearAnalysisCaches() { - const values = await Promise.all([...this.cache.values()]); - values.forEach((v) => (v.clearAnalysisCache ? v.clearAnalysisCache() : noop())); - } -} diff --git a/src/client/activation/commands.ts b/src/client/activation/commands.ts index 6a6628e6d4a3..158d9662ec46 100644 --- a/src/client/activation/commands.ts +++ b/src/client/activation/commands.ts @@ -3,6 +3,5 @@ 'use strict'; export namespace Commands { - export const ClearAnalyisCache = 'python.analysis.clearCache'; export const RestartLS = 'python.analysis.restartLanguageServer'; } diff --git a/src/client/activation/common/activatorBase.ts b/src/client/activation/common/activatorBase.ts deleted file mode 100644 index 7a3c972ef960..000000000000 --- a/src/client/activation/common/activatorBase.ts +++ /dev/null @@ -1,325 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { - CancellationToken, - CodeLens, - CompletionContext, - CompletionItem, - CompletionList, - DocumentSymbol, - Hover, - Location, - LocationLink, - Position, - ProviderResult, - ReferenceContext, - SignatureHelp, - SignatureHelpContext, - SymbolInformation, - TextDocument, - WorkspaceEdit -} from 'vscode'; -import * as vscodeLanguageClient from 'vscode-languageclient/node'; - -import { injectable } from 'inversify'; -import { IWorkspaceService } from '../../common/application/types'; -import { traceDecorators } from '../../common/logger'; -import { IFileSystem } from '../../common/platform/types'; -import { IConfigurationService, Resource } from '../../common/types'; -import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { ILanguageServerActivator, ILanguageServerManager } from '../types'; - -/** - * Starts the language server managers per workspaces (currently one for first workspace). - * - * @export - * @class LanguageServerActivatorBase - * @implements {ILanguageServerActivator} - */ -@injectable() -export abstract class LanguageServerActivatorBase implements ILanguageServerActivator { - protected resource?: Resource; - constructor( - protected readonly manager: ILanguageServerManager, - private readonly workspace: IWorkspaceService, - protected readonly fs: IFileSystem, - protected readonly configurationService: IConfigurationService - ) {} - - @traceDecorators.error('Failed to activate language server') - public async start(resource: Resource, interpreter?: PythonEnvironment): Promise { - if (!resource) { - resource = this.workspace.hasWorkspaceFolders ? this.workspace.workspaceFolders![0].uri : undefined; - } - this.resource = resource; - await this.ensureLanguageServerIsAvailable(resource); - await this.manager.start(resource, interpreter); - } - - public dispose(): void { - this.manager.dispose(); - } - - public abstract async ensureLanguageServerIsAvailable(resource: Resource): Promise; - - public activate(): void { - this.manager.connect(); - } - - public deactivate(): void { - this.manager.disconnect(); - } - - public get connection() { - const languageClient = this.getLanguageClient(); - if (languageClient) { - // Return an object that looks like a connection - return { - sendNotification: languageClient.sendNotification.bind(languageClient), - sendRequest: languageClient.sendRequest.bind(languageClient), - sendProgress: languageClient.sendProgress.bind(languageClient), - onRequest: languageClient.onRequest.bind(languageClient), - onNotification: languageClient.onNotification.bind(languageClient), - onProgress: languageClient.onProgress.bind(languageClient) - }; - } - } - - public get capabilities() { - const languageClient = this.getLanguageClient(); - if (languageClient) { - return languageClient.initializeResult?.capabilities; - } - } - - public provideRenameEdits( - document: TextDocument, - position: Position, - newName: string, - token: CancellationToken - ): ProviderResult { - return this.handleProvideRenameEdits(document, position, newName, token); - } - - public provideDefinition( - document: TextDocument, - position: Position, - token: CancellationToken - ): ProviderResult { - return this.handleProvideDefinition(document, position, token); - } - - public provideHover(document: TextDocument, position: Position, token: CancellationToken): ProviderResult { - return this.handleProvideHover(document, position, token); - } - - public provideReferences( - document: TextDocument, - position: Position, - context: ReferenceContext, - token: CancellationToken - ): ProviderResult { - return this.handleProvideReferences(document, position, context, token); - } - - public provideCompletionItems( - document: TextDocument, - position: Position, - token: CancellationToken, - context: CompletionContext - ): ProviderResult { - return this.handleProvideCompletionItems(document, position, token, context); - } - - public provideCodeLenses(document: TextDocument, token: CancellationToken): ProviderResult { - return this.handleProvideCodeLenses(document, token); - } - - public provideDocumentSymbols( - document: TextDocument, - token: CancellationToken - ): ProviderResult { - return this.handleProvideDocumentSymbols(document, token); - } - - public provideSignatureHelp( - document: TextDocument, - position: Position, - token: CancellationToken, - context: SignatureHelpContext - ): ProviderResult { - return this.handleProvideSignatureHelp(document, position, token, context); - } - - protected getLanguageClient(): vscodeLanguageClient.LanguageClient | undefined { - const proxy = this.manager.languageProxy; - if (proxy) { - return proxy.languageClient; - } - } - - private async handleProvideRenameEdits( - document: TextDocument, - position: Position, - newName: string, - token: CancellationToken - ): Promise { - const languageClient = this.getLanguageClient(); - if (languageClient) { - const args: vscodeLanguageClient.RenameParams = { - textDocument: languageClient.code2ProtocolConverter.asTextDocumentIdentifier(document), - position: languageClient.code2ProtocolConverter.asPosition(position), - newName - }; - const result = await languageClient.sendRequest(vscodeLanguageClient.RenameRequest.type, args, token); - if (result) { - return languageClient.protocol2CodeConverter.asWorkspaceEdit(result); - } - } - } - - private async handleProvideDefinition( - document: TextDocument, - position: Position, - token: CancellationToken - ): Promise { - const languageClient = this.getLanguageClient(); - if (languageClient) { - const args: vscodeLanguageClient.TextDocumentPositionParams = { - textDocument: languageClient.code2ProtocolConverter.asTextDocumentIdentifier(document), - position: languageClient.code2ProtocolConverter.asPosition(position) - }; - const result = await languageClient.sendRequest(vscodeLanguageClient.DefinitionRequest.type, args, token); - if (result) { - return languageClient.protocol2CodeConverter.asDefinitionResult(result); - } - } - } - - private async handleProvideHover( - document: TextDocument, - position: Position, - token: CancellationToken - ): Promise { - const languageClient = this.getLanguageClient(); - if (languageClient) { - const args: vscodeLanguageClient.TextDocumentPositionParams = { - textDocument: languageClient.code2ProtocolConverter.asTextDocumentIdentifier(document), - position: languageClient.code2ProtocolConverter.asPosition(position) - }; - const result = await languageClient.sendRequest(vscodeLanguageClient.HoverRequest.type, args, token); - if (result) { - return languageClient.protocol2CodeConverter.asHover(result); - } - } - } - - private async handleProvideReferences( - document: TextDocument, - position: Position, - context: ReferenceContext, - token: CancellationToken - ): Promise { - const languageClient = this.getLanguageClient(); - if (languageClient) { - const args: vscodeLanguageClient.ReferenceParams = { - textDocument: languageClient.code2ProtocolConverter.asTextDocumentIdentifier(document), - position: languageClient.code2ProtocolConverter.asPosition(position), - context - }; - const result = await languageClient.sendRequest(vscodeLanguageClient.ReferencesRequest.type, args, token); - if (result) { - // Remove undefined part. - return result.map((l) => { - const r = languageClient!.protocol2CodeConverter.asLocation(l); - return r!; - }); - } - } - } - - private async handleProvideCodeLenses( - document: TextDocument, - token: CancellationToken - ): Promise { - const languageClient = this.getLanguageClient(); - if (languageClient) { - const args: vscodeLanguageClient.CodeLensParams = { - textDocument: languageClient.code2ProtocolConverter.asTextDocumentIdentifier(document) - }; - const result = await languageClient.sendRequest(vscodeLanguageClient.CodeLensRequest.type, args, token); - if (result) { - return languageClient.protocol2CodeConverter.asCodeLenses(result); - } - } - } - - private async handleProvideCompletionItems( - document: TextDocument, - position: Position, - token: CancellationToken, - context: CompletionContext - ): Promise { - const languageClient = this.getLanguageClient(); - if (languageClient) { - const args = languageClient.code2ProtocolConverter.asCompletionParams(document, position, context); - const result = await languageClient.sendRequest(vscodeLanguageClient.CompletionRequest.type, args, token); - if (result) { - return languageClient.protocol2CodeConverter.asCompletionResult(result); - } - } - } - - private async handleProvideDocumentSymbols( - document: TextDocument, - token: CancellationToken - ): Promise { - const languageClient = this.getLanguageClient(); - if (languageClient) { - const args: vscodeLanguageClient.DocumentSymbolParams = { - textDocument: languageClient.code2ProtocolConverter.asTextDocumentIdentifier(document) - }; - const result = await languageClient.sendRequest( - vscodeLanguageClient.DocumentSymbolRequest.type, - args, - token - ); - if (result && result.length) { - // tslint:disable-next-line: no-any - if ((result[0] as any).range) { - // Document symbols - const docSymbols = result as vscodeLanguageClient.DocumentSymbol[]; - return languageClient.protocol2CodeConverter.asDocumentSymbols(docSymbols); - } else { - // Document symbols - const symbols = result as vscodeLanguageClient.SymbolInformation[]; - return languageClient.protocol2CodeConverter.asSymbolInformations(symbols); - } - } - } - } - - private async handleProvideSignatureHelp( - document: TextDocument, - position: Position, - token: CancellationToken, - _context: SignatureHelpContext - ): Promise { - const languageClient = this.getLanguageClient(); - if (languageClient) { - const args: vscodeLanguageClient.TextDocumentPositionParams = { - textDocument: languageClient.code2ProtocolConverter.asTextDocumentIdentifier(document), - position: languageClient.code2ProtocolConverter.asPosition(position) - }; - const result = await languageClient.sendRequest( - vscodeLanguageClient.SignatureHelpRequest.type, - args, - token - ); - if (result) { - return languageClient.protocol2CodeConverter.asSignatureHelp(result); - } - } - } -} diff --git a/src/client/activation/common/analysisOptions.ts b/src/client/activation/common/analysisOptions.ts index a5f462f2daf8..75d0aabef9d2 100644 --- a/src/client/activation/common/analysisOptions.ts +++ b/src/client/activation/common/analysisOptions.ts @@ -1,46 +1,39 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { injectable } from 'inversify'; import { Disposable, Event, EventEmitter, WorkspaceFolder } from 'vscode'; import { DocumentFilter, LanguageClientOptions, RevealOutputChannelOn } from 'vscode-languageclient/node'; +import { IWorkspaceService } from '../../common/application/types'; import { PYTHON, PYTHON_LANGUAGE } from '../../common/constants'; -import { traceDecorators } from '../../common/logger'; -import { IOutputChannel, Resource } from '../../common/types'; +import { ILogOutputChannel, Resource } from '../../common/types'; import { debounceSync } from '../../common/utils/decorators'; import { IEnvironmentVariablesProvider } from '../../common/variables/types'; +import { traceDecoratorError } from '../../logging'; import { PythonEnvironment } from '../../pythonEnvironments/info'; import { ILanguageServerAnalysisOptions, ILanguageServerOutputChannel } from '../types'; -@injectable() export abstract class LanguageServerAnalysisOptionsBase implements ILanguageServerAnalysisOptions { - protected disposables: Disposable[] = []; protected readonly didChange = new EventEmitter(); - private envPythonPath: string = ''; - private readonly output: IOutputChannel; + private readonly output: ILogOutputChannel; protected constructor( - private readonly envVarsProvider: IEnvironmentVariablesProvider, - lsOutputChannel: ILanguageServerOutputChannel + lsOutputChannel: ILanguageServerOutputChannel, + protected readonly workspace: IWorkspaceService, ) { this.output = lsOutputChannel.channel; } - public async initialize(_resource: Resource, _interpreter: PythonEnvironment | undefined) { - const disposable = this.envVarsProvider.onDidEnvironmentVariablesChange(this.onEnvVarChange, this); - this.disposables.push(disposable); - } + public async initialize(_resource: Resource, _interpreter: PythonEnvironment | undefined) {} public get onDidChange(): Event { return this.didChange.event; } public dispose(): void { - this.disposables.forEach((d) => d.dispose()); this.didChange.dispose(); } - @traceDecorators.error('Failed to get analysis options') + @traceDecoratorError('Failed to get analysis options') public async getAnalysisOptions(): Promise { const workspaceFolder = this.getWorkspaceFolder(); const documentSelector = this.getDocumentFilters(workspaceFolder); @@ -49,11 +42,11 @@ export abstract class LanguageServerAnalysisOptionsBase implements ILanguageServ documentSelector, workspaceFolder, synchronize: { - configurationSection: PYTHON_LANGUAGE + configurationSection: this.getConfigSectionsToSynchronize(), }, outputChannel: this.output, revealOutputChannelOn: RevealOutputChannelOn.Never, - initializationOptions: await this.getInitializationOptions() + initializationOptions: await this.getInitializationOptions(), }; } @@ -62,13 +55,39 @@ export abstract class LanguageServerAnalysisOptionsBase implements ILanguageServ } protected getDocumentFilters(_workspaceFolder?: WorkspaceFolder): DocumentFilter[] { - return PYTHON; + return this.workspace.isVirtualWorkspace ? [{ language: PYTHON_LANGUAGE }] : PYTHON; + } + + protected getConfigSectionsToSynchronize(): string[] { + return [PYTHON_LANGUAGE]; } - // tslint:disable-next-line: no-any protected async getInitializationOptions(): Promise { return undefined; } +} + +export abstract class LanguageServerAnalysisOptionsWithEnv extends LanguageServerAnalysisOptionsBase { + protected disposables: Disposable[] = []; + private envPythonPath: string = ''; + + protected constructor( + private readonly envVarsProvider: IEnvironmentVariablesProvider, + lsOutputChannel: ILanguageServerOutputChannel, + workspace: IWorkspaceService, + ) { + super(lsOutputChannel, workspace); + } + + public async initialize(_resource: Resource, _interpreter: PythonEnvironment | undefined) { + const disposable = this.envVarsProvider.onDidEnvironmentVariablesChange(this.onEnvVarChange, this); + this.disposables.push(disposable); + } + + public dispose(): void { + super.dispose(); + this.disposables.forEach((d) => d.dispose()); + } protected async getEnvPythonPath(): Promise { const vars = await this.envVarsProvider.getEnvironmentVariables(); diff --git a/src/client/activation/common/cancellationUtils.ts b/src/client/activation/common/cancellationUtils.ts index 18cf55705765..d14307174107 100644 --- a/src/client/activation/common/cancellationUtils.ts +++ b/src/client/activation/common/cancellationUtils.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-classes-per-file */ /* * cancellationUtils.ts * Copyright (c) Microsoft Corporation. @@ -15,7 +16,7 @@ import { CancellationSenderStrategy, CancellationStrategy, Disposable, - MessageConnection + MessageConnection, } from 'vscode-languageclient/node'; type CancellationId = string | number; @@ -32,7 +33,7 @@ function tryRun(callback: () => void) { try { callback(); } catch (e) { - // tslint:disable-next-line: no-empty + // No body. } } @@ -42,7 +43,7 @@ class FileCancellationSenderStrategy implements CancellationSenderStrategy { tryRun(() => fs.mkdirSync(folder, { recursive: true })); } - public sendCancellation(_: MessageConnection, id: CancellationId): void { + public async sendCancellation(_: MessageConnection, id: CancellationId) { const file = getCancellationFilePath(this.folderName, id); tryRun(() => fs.writeFileSync(file, '', { flag: 'w' })); } @@ -80,6 +81,7 @@ export class FileBasedCancellationStrategy implements CancellationStrategy, Disp this._sender = new FileCancellationSenderStrategy(folderName); } + // eslint-disable-next-line class-methods-use-this get receiver(): CancellationReceiverStrategy { return CancellationReceiverStrategy.Message; } diff --git a/src/client/activation/common/defaultlanguageServer.ts b/src/client/activation/common/defaultlanguageServer.ts new file mode 100644 index 000000000000..dc40a2c0ed5b --- /dev/null +++ b/src/client/activation/common/defaultlanguageServer.ts @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { injectable } from 'inversify'; +import { PYLANCE_EXTENSION_ID } from '../../common/constants'; +import { IDefaultLanguageServer, IExtensions, DefaultLSType } from '../../common/types'; +import { IServiceManager } from '../../ioc/types'; +import { LanguageServerType } from '../types'; + +@injectable() +class DefaultLanguageServer implements IDefaultLanguageServer { + public readonly defaultLSType: DefaultLSType; + + constructor(defaultServer: DefaultLSType) { + this.defaultLSType = defaultServer; + } +} + +export async function setDefaultLanguageServer( + extensions: IExtensions, + serviceManager: IServiceManager, +): Promise { + const lsType = await getDefaultLanguageServer(extensions); + serviceManager.addSingletonInstance( + IDefaultLanguageServer, + new DefaultLanguageServer(lsType), + ); +} + +async function getDefaultLanguageServer(extensions: IExtensions): Promise { + if (extensions.getExtension(PYLANCE_EXTENSION_ID)) { + return LanguageServerType.Node; + } + + return LanguageServerType.Jedi; +} diff --git a/src/client/activation/common/downloadChannelRules.ts b/src/client/activation/common/downloadChannelRules.ts deleted file mode 100644 index ebffee922377..000000000000 --- a/src/client/activation/common/downloadChannelRules.ts +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IPersistentStateFactory } from '../../common/types'; -import { IServiceContainer } from '../../ioc/types'; -import { FolderVersionPair, IDownloadChannelRule } from '../types'; - -const lastCheckedForLSDateTimeCacheKey = 'LS.LAST.CHECK.TIME'; -const frequencyForBetalLSDownloadCheck = 1000 * 60 * 60 * 24; // One day. - -@injectable() -export class DownloadDailyChannelRule implements IDownloadChannelRule { - public async shouldLookForNewLanguageServer(_currentFolder?: FolderVersionPair): Promise { - return true; - } -} -@injectable() -export class DownloadStableChannelRule implements IDownloadChannelRule { - public async shouldLookForNewLanguageServer(currentFolder?: FolderVersionPair): Promise { - return currentFolder ? false : true; - } -} -@injectable() -export class DownloadBetaChannelRule implements IDownloadChannelRule { - constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) {} - public async shouldLookForNewLanguageServer(currentFolder?: FolderVersionPair): Promise { - // For beta, we do this only once a day. - const stateFactory = this.serviceContainer.get(IPersistentStateFactory); - const globalState = stateFactory.createGlobalPersistentState( - lastCheckedForLSDateTimeCacheKey, - true, - frequencyForBetalLSDownloadCheck - ); - - // If we have checked it in the last 24 hours, then ensure we don't do it again. - if (globalState.value) { - await globalState.updateValue(false); - return true; - } - - return !currentFolder || globalState.value; - } -} diff --git a/src/client/activation/common/downloader.ts b/src/client/activation/common/downloader.ts deleted file mode 100644 index b4e690e6b919..000000000000 --- a/src/client/activation/common/downloader.ts +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { ProgressLocation, window } from 'vscode'; -import { IApplicationShell, IWorkspaceService } from '../../common/application/types'; -import '../../common/extensions'; -import { traceError } from '../../common/logger'; -import { IFileSystem } from '../../common/platform/types'; -import { IFileDownloader, IOutputChannel, Resource } from '../../common/types'; -import { createDeferred } from '../../common/utils/async'; -import { Common, LanguageService } from '../../common/utils/localize'; -import { StopWatch } from '../../common/utils/stopWatch'; -import { IServiceContainer } from '../../ioc/types'; -import { sendTelemetryEvent } from '../../telemetry'; -import { EventName } from '../../telemetry/constants'; -import { - ILanguageServerDownloader, - ILanguageServerFolderService, - ILanguageServerOutputChannel, - IPlatformData -} from '../types'; - -// tslint:disable:no-require-imports no-any - -const downloadFileExtension = '.nupkg'; - -@injectable() -export class LanguageServerDownloader implements ILanguageServerDownloader { - private output: IOutputChannel; - constructor( - @inject(ILanguageServerOutputChannel) private readonly lsOutputChannel: ILanguageServerOutputChannel, - @inject(IFileDownloader) private readonly fileDownloader: IFileDownloader, - @inject(ILanguageServerFolderService) private readonly lsFolderService: ILanguageServerFolderService, - @inject(IApplicationShell) private readonly appShell: IApplicationShell, - @inject(IFileSystem) private readonly fs: IFileSystem, - @inject(IWorkspaceService) private readonly workspace: IWorkspaceService, - @inject(IServiceContainer) private readonly services: IServiceContainer - ) { - this.output = this.lsOutputChannel.channel; - } - - public async getDownloadInfo(resource: Resource) { - const info = await this.lsFolderService.getLatestLanguageServerVersion(resource).then((item) => item!); - - let uri = info.uri; - if (uri.startsWith('https:')) { - const cfg = this.workspace.getConfiguration('http', resource); - if (!cfg.get('proxyStrictSSL', true)) { - // tslint:disable-next-line:no-http-string - uri = uri.replace(/^https:/, 'http:'); - } - } - const lsNameTrimmed = info.package.split('.')[0]; - return [uri, info.version.raw, lsNameTrimmed]; - } - - public async downloadLanguageServer(destinationFolder: string, resource: Resource): Promise { - if (await this.lsFolderService.skipDownload()) { - // Sanity check; this case should not be hit if skipDownload is true elsewhere. - traceError('Attempted to download with skipDownload true.'); - return; - } - - const [downloadUri, lsVersion, lsName] = await this.getDownloadInfo(resource); - const timer: StopWatch = new StopWatch(); - let success: boolean = true; - let localTempFilePath = ''; - - try { - localTempFilePath = await this.downloadFile( - downloadUri, - 'Downloading Microsoft Python Language Server... ' - ); - } catch (err) { - this.output.appendLine(LanguageService.downloadFailedOutputMessage()); - this.output.appendLine(err); - success = false; - this.showMessageAndOptionallyShowOutput(LanguageService.lsFailedToDownload()).ignoreErrors(); - sendTelemetryEvent( - EventName.PYTHON_LANGUAGE_SERVER_ERROR, - undefined, - { error: 'Failed to download (platform)' }, - err - ); - throw new Error(err); - } finally { - const usedSSL = downloadUri.startsWith('https:'); - sendTelemetryEvent(EventName.PYTHON_LANGUAGE_SERVER_DOWNLOADED, timer.elapsedTime, { - success, - lsVersion, - usedSSL, - lsName - }); - } - - timer.reset(); - try { - await this.unpackArchive(destinationFolder, localTempFilePath); - } catch (err) { - this.output.appendLine(LanguageService.extractionFailedOutputMessage()); - this.output.appendLine(err); - success = false; - this.showMessageAndOptionallyShowOutput(LanguageService.lsFailedToExtract()).ignoreErrors(); - sendTelemetryEvent( - EventName.PYTHON_LANGUAGE_SERVER_ERROR, - undefined, - { error: 'Failed to extract (platform)' }, - err - ); - throw new Error(err); - } finally { - sendTelemetryEvent(EventName.PYTHON_LANGUAGE_SERVER_EXTRACTED, timer.elapsedTime, { - success, - lsVersion, - lsName - }); - await this.fs.deleteFile(localTempFilePath); - } - } - - public async showMessageAndOptionallyShowOutput(message: string) { - const selection = await this.appShell.showErrorMessage(message, Common.openOutputPanel()); - if (selection !== Common.openOutputPanel()) { - return; - } - this.output.show(true); - } - - public async downloadFile(uri: string, title: string): Promise { - const downloadOptions = { - extension: downloadFileExtension, - outputChannel: this.output, - progressMessagePrefix: title - }; - return this.fileDownloader.downloadFile(uri, downloadOptions).then((file) => { - this.output.appendLine(LanguageService.extractionCompletedOutputMessage()); - return file; - }); - } - - protected async unpackArchive(destinationFolder: string, tempFilePath: string): Promise { - this.output.append('Unpacking archive... '); - - const deferred = createDeferred(); - - const title = 'Extracting files... '; - await window.withProgress( - { - location: ProgressLocation.Window - }, - (progress) => { - // tslint:disable-next-line:no-require-imports no-var-requires - const StreamZip = require('node-stream-zip'); - const zip = new StreamZip({ - file: tempFilePath, - storeEntries: true - }); - - let totalFiles = 0; - let extractedFiles = 0; - zip.on('ready', async () => { - totalFiles = zip.entriesCount; - if (!(await this.fs.directoryExists(destinationFolder))) { - await this.fs.createDirectory(destinationFolder); - } - zip.extract(null, destinationFolder, (err: any) => { - if (err) { - deferred.reject(err); - } else { - deferred.resolve(); - } - zip.close(); - }); - }) - .on('extract', () => { - extractedFiles += 1; - progress.report({ message: `${title}${Math.round((100 * extractedFiles) / totalFiles)}%` }); - }) - .on('error', (e: any) => { - deferred.reject(e); - }); - return deferred.promise; - } - ); - - // Set file to executable (nothing happens in Windows, as chmod has no definition there) - if (this.services) { - try { - const platformData = this.services.get(IPlatformData); - const executablePath = path.join(destinationFolder, platformData.engineExecutableName); - await this.fs.chmod(executablePath, '0764'); // -rwxrw-r-- - // tslint:disable-next-line: no-empty - } catch {} - } - - this.output.appendLine(LanguageService.extractionDoneOutputMessage()); - } -} diff --git a/src/client/activation/common/languageServerChangeHandler.ts b/src/client/activation/common/languageServerChangeHandler.ts index c154ba83b1fb..83ff204ed6e7 100644 --- a/src/client/activation/common/languageServerChangeHandler.ts +++ b/src/client/activation/common/languageServerChangeHandler.ts @@ -1,28 +1,42 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { Disposable } from 'vscode'; -import { IApplicationEnvironment, IApplicationShell, ICommandManager } from '../../common/application/types'; +import { ConfigurationTarget, Disposable } from 'vscode'; +import { IApplicationShell, ICommandManager, IWorkspaceService } from '../../common/application/types'; import { PYLANCE_EXTENSION_ID } from '../../common/constants'; -import { IExtensions } from '../../common/types'; +import { IConfigurationService, IExtensions } from '../../common/types'; import { createDeferred } from '../../common/utils/async'; -import { Common, LanguageService, Pylance } from '../../common/utils/localize'; -import { getPylanceExtensionUri } from '../../languageServices/proposeLanguageServerBanner'; +import { Pylance } from '../../common/utils/localize'; import { LanguageServerType } from '../types'; export async function promptForPylanceInstall( appShell: IApplicationShell, - appEnv: IApplicationEnvironment + commandManager: ICommandManager, + workspace: IWorkspaceService, + configService: IConfigurationService, ): Promise { - // If not installed, point user to Pylance at the store. const response = await appShell.showWarningMessage( - Pylance.installPylanceMessage(), - Common.bannerLabelYes(), - Common.bannerLabelNo() + Pylance.pylanceRevertToJediPrompt, + Pylance.pylanceInstallPylance, + Pylance.pylanceRevertToJedi, + Pylance.remindMeLater, ); - if (response === Common.bannerLabelYes()) { - appShell.openUrl(getPylanceExtensionUri(appEnv)); + if (response === Pylance.pylanceInstallPylance) { + commandManager.executeCommand('extension.open', PYLANCE_EXTENSION_ID); + } else if (response === Pylance.pylanceRevertToJedi) { + const inspection = workspace.getConfiguration('python').inspect('languageServer'); + + let target: ConfigurationTarget | undefined; + if (inspection?.workspaceValue) { + target = ConfigurationTarget.Workspace; + } else if (inspection?.globalValue) { + target = ConfigurationTarget.Global; + } + + if (target) { + await configService.updateSetting('languageServer', LanguageServerType.Jedi, undefined, target); + } } } @@ -30,21 +44,24 @@ export async function promptForPylanceInstall( export class LanguageServerChangeHandler implements Disposable { // For tests that need to track Pylance install completion. private readonly pylanceInstallCompletedDeferred = createDeferred(); + private readonly disposables: Disposable[] = []; + private pylanceInstalled = false; constructor( private currentLsType: LanguageServerType | undefined, private readonly extensions: IExtensions, private readonly appShell: IApplicationShell, - private readonly appEnv: IApplicationEnvironment, - private readonly commands: ICommandManager + private readonly commands: ICommandManager, + private readonly workspace: IWorkspaceService, + private readonly configService: IConfigurationService, ) { this.pylanceInstalled = this.isPylanceInstalled(); this.disposables.push( extensions.onDidChange(async () => { await this.extensionsChangeHandler(); - }) + }), ); } @@ -60,7 +77,7 @@ export class LanguageServerChangeHandler implements Disposable { } public async handleLanguageServerChange(lsType: LanguageServerType | undefined): Promise { - if (this.currentLsType === lsType) { + if (this.currentLsType === lsType || lsType === LanguageServerType.Microsoft) { return; } // VS Code has to be reloaded when language server type changes. In case of Pylance @@ -69,42 +86,23 @@ export class LanguageServerChangeHandler implements Disposable { // may get one reload prompt now and then another when Pylance is finally installed. // Instead, check the installation and suppress prompt if Pylance is not there. // Extensions change event handler will then show its own prompt. - let response: string | undefined; if (lsType === LanguageServerType.Node && !this.isPylanceInstalled()) { // If not installed, point user to Pylance at the store. - await promptForPylanceInstall(this.appShell, this.appEnv); + await promptForPylanceInstall(this.appShell, this.commands, this.workspace, this.configService); // At this point Pylance is not yet installed. Skip reload prompt // since we are going to show it when Pylance becomes available. - } else { - response = await this.appShell.showInformationMessage( - LanguageService.reloadAfterLanguageServerChange(), - Common.reload() - ); - if (response === Common.reload()) { - this.commands.executeCommand('workbench.action.reloadWindow'); - } } + this.currentLsType = lsType; } private async extensionsChangeHandler(): Promise { // Track Pylance extension installation state and prompt to reload when it becomes available. const oldInstallState = this.pylanceInstalled; + this.pylanceInstalled = this.isPylanceInstalled(); if (oldInstallState === this.pylanceInstalled) { this.pylanceInstallCompletedDeferred.resolve(); - return; - } - - const response = await this.appShell.showWarningMessage( - Pylance.pylanceInstalledReloadPromptMessage(), - Common.bannerLabelYes(), - Common.bannerLabelNo() - ); - - this.pylanceInstallCompletedDeferred.resolve(); - if (response === Common.bannerLabelYes()) { - this.commands.executeCommand('workbench.action.reloadWindow'); } } diff --git a/src/client/activation/common/languageServerFolderService.ts b/src/client/activation/common/languageServerFolderService.ts deleted file mode 100644 index ae501d8b709e..000000000000 --- a/src/client/activation/common/languageServerFolderService.ts +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable, unmanaged } from 'inversify'; -import * as path from 'path'; -import * as semver from 'semver'; -import { EXTENSION_ROOT_DIR } from '../../common/constants'; -import { traceDecorators } from '../../common/logger'; -import { NugetPackage } from '../../common/nuget/types'; -import { IFileSystem } from '../../common/platform/types'; -import { IConfigurationService, Resource } from '../../common/types'; -import { IServiceContainer } from '../../ioc/types'; -import { - FolderVersionPair, - IDownloadChannelRule, - ILanguageServerFolderService, - ILanguageServerPackageService -} from '../types'; - -@injectable() -export abstract class LanguageServerFolderService implements ILanguageServerFolderService { - constructor( - @inject(IServiceContainer) protected readonly serviceContainer: IServiceContainer, - @unmanaged() protected readonly languageServerFolder: string - ) {} - - public async skipDownload(): Promise { - return false; - } - - @traceDecorators.verbose('Get language server folder name') - public async getLanguageServerFolderName(resource: Resource): Promise { - const currentFolder = await this.getCurrentLanguageServerDirectory(); - let serverVersion: NugetPackage | undefined; - - const shouldLookForNewVersion = await this.shouldLookForNewLanguageServer(currentFolder); - if (currentFolder && !shouldLookForNewVersion) { - return path.basename(currentFolder.path); - } - - try { - serverVersion = await this.getLatestLanguageServerVersion(resource); - } catch { - serverVersion = undefined; - } - - if (currentFolder && (!serverVersion || serverVersion.version.compare(currentFolder.version) <= 0)) { - return path.basename(currentFolder.path); - } - - return `${this.languageServerFolder}.${serverVersion!.version.raw}`; - } - - @traceDecorators.verbose('Get latest version of language server') - public getLatestLanguageServerVersion(resource: Resource): Promise { - const minVersion = this.getMinimalLanguageServerVersion(); - const lsPackageService = this.serviceContainer.get( - ILanguageServerPackageService - ); - return lsPackageService.getLatestNugetPackageVersion(resource, minVersion); - } - - public async shouldLookForNewLanguageServer(currentFolder?: FolderVersionPair): Promise { - const configService = this.serviceContainer.get(IConfigurationService); - const autoUpdateLanguageServer = configService.getSettings().autoUpdateLanguageServer; - const downloadLanguageServer = configService.getSettings().downloadLanguageServer; - if (currentFolder && (!autoUpdateLanguageServer || !downloadLanguageServer)) { - return false; - } - const downloadChannel = this.getDownloadChannel(); - const rule = this.serviceContainer.get(IDownloadChannelRule, downloadChannel); - return rule.shouldLookForNewLanguageServer(currentFolder); - } - - public async getCurrentLanguageServerDirectory(): Promise { - const configService = this.serviceContainer.get(IConfigurationService); - if (!configService.getSettings().downloadLanguageServer) { - return { path: this.languageServerFolder, version: new semver.SemVer('0.0.0') }; - } - const dirs = await this.getExistingLanguageServerDirectories(); - if (dirs.length === 0) { - return; - } - dirs.sort((a, b) => a.version.compare(b.version)); - return dirs[dirs.length - 1]; - } - - public async getExistingLanguageServerDirectories(): Promise { - const fs = this.serviceContainer.get(IFileSystem); - const subDirs = await fs.getSubDirectories(EXTENSION_ROOT_DIR); - return subDirs - .filter((dir) => path.basename(dir).startsWith(this.languageServerFolder)) - .map((dir) => { - return { path: dir, version: this.getFolderVersion(path.basename(dir)) }; - }); - } - - public getFolderVersion(dirName: string): semver.SemVer { - const suffix = dirName.substring(this.languageServerFolder.length + 1); - return suffix.length === 0 - ? new semver.SemVer('0.0.0') - : semver.parse(suffix, true) || new semver.SemVer('0.0.0'); - } - - protected abstract getMinimalLanguageServerVersion(): string; - - private getDownloadChannel() { - const lsPackageService = this.serviceContainer.get( - ILanguageServerPackageService - ); - return lsPackageService.getLanguageServerDownloadChannel(); - } -} diff --git a/src/client/activation/common/languageServerPackageService.ts b/src/client/activation/common/languageServerPackageService.ts deleted file mode 100644 index feed13c03b53..000000000000 --- a/src/client/activation/common/languageServerPackageService.ts +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { injectable } from 'inversify'; -import { parse, SemVer } from 'semver'; -import { IApplicationEnvironment } from '../../common/application/types'; -import { PVSC_EXTENSION_ID } from '../../common/constants'; -import { traceDecorators, traceVerbose } from '../../common/logger'; -import { INugetRepository, INugetService, NugetPackage } from '../../common/nuget/types'; -import { IPlatformService } from '../../common/platform/types'; -import { IConfigurationService, IExtensions, LanguageServerDownloadChannels, Resource } from '../../common/types'; -import { IServiceContainer } from '../../ioc/types'; -import { ILanguageServerPackageService } from '../types'; -import { azureCDNBlobStorageAccount, LanguageServerDownloadChannel } from './packageRepository'; - -export const maxMajorVersion = 0; - -@injectable() -export abstract class LanguageServerPackageService implements ILanguageServerPackageService { - public maxMajorVersion: number = maxMajorVersion; - constructor( - protected readonly serviceContainer: IServiceContainer, - protected readonly appEnv: IApplicationEnvironment, - protected readonly platform: IPlatformService - ) {} - - public abstract getNugetPackageName(): string; - - @traceDecorators.verbose('Get latest language server nuget package version') - public async getLatestNugetPackageVersion(resource: Resource, minVersion?: string): Promise { - const downloadChannel = this.getLanguageServerDownloadChannel(); - const nugetRepo = this.serviceContainer.get(INugetRepository, downloadChannel); - const packageName = this.getNugetPackageName(); - traceVerbose(`Listing packages for ${downloadChannel} for ${packageName}`); - const packages = await nugetRepo.getPackages(packageName, resource); - - return this.getValidPackage(packages, minVersion); - } - - public getLanguageServerDownloadChannel(): LanguageServerDownloadChannels { - const configService = this.serviceContainer.get(IConfigurationService); - const settings = configService.getSettings(); - if (settings.analysis.downloadChannel) { - return settings.analysis.downloadChannel; - } - - if (settings.insidersChannel === 'daily' || settings.insidersChannel === 'weekly') { - return 'beta'; - } - const isAlphaVersion = this.isAlphaVersionOfExtension(); - return isAlphaVersion ? 'beta' : 'stable'; - } - - public isAlphaVersionOfExtension() { - const extensions = this.serviceContainer.get(IExtensions); - const extension = extensions.getExtension(PVSC_EXTENSION_ID)!; - const version = parse(extension.packageJSON.version)!; - return version.prerelease.length > 0 && version.prerelease[0] === 'alpha'; - } - - protected getValidPackage(packages: NugetPackage[], minimumVersion?: string): NugetPackage { - const nugetService = this.serviceContainer.get(INugetService); - const validPackages = packages - .filter((item) => item.version.major === this.maxMajorVersion) - .filter((item) => nugetService.isReleaseVersion(item.version)) - .sort((a, b) => a.version.compare(b.version)); - - const pkg = validPackages[validPackages.length - 1]; - minimumVersion = minimumVersion || '0.0.0'; - if (pkg.version.compare(minimumVersion) >= 0) { - return validPackages[validPackages.length - 1]; - } - - // This is a fall back, if the wrong version is returned, e.g. version is cached downstream in some proxy server or similar. - // This way, we always ensure we have the minimum version that's compatible. - return { - version: new SemVer(minimumVersion), - package: LanguageServerDownloadChannel.stable, - uri: `${azureCDNBlobStorageAccount}/${ - LanguageServerDownloadChannel.stable - }/${this.getNugetPackageName()}.${minimumVersion}.nupkg` - }; - } -} diff --git a/src/client/activation/common/loadLanguageServerExtension.ts b/src/client/activation/common/loadLanguageServerExtension.ts new file mode 100644 index 000000000000..87fa5d9e6213 --- /dev/null +++ b/src/client/activation/common/loadLanguageServerExtension.ts @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable } from 'inversify'; +import { ICommandManager } from '../../common/application/types'; +import { IDisposableRegistry } from '../../common/types'; +import { IExtensionSingleActivationService } from '../types'; + +// This command is currently used by IntelliCode. This was used to +// trigger MPLS. Since we no longer have MPLS we are going to set +// this command to no-op temporarily until this is removed from +// IntelliCode + +@injectable() +export class LoadLanguageServerExtension implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: true, virtualWorkspace: true }; + + constructor( + @inject(ICommandManager) private readonly commandManager: ICommandManager, + @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry, + ) {} + + public activate(): Promise { + const disposable = this.commandManager.registerCommand('python._loadLanguageServerExtension', () => { + /** no-op */ + }); + this.disposables.push(disposable); + return Promise.resolve(); + } +} diff --git a/src/client/activation/common/outputChannel.ts b/src/client/activation/common/outputChannel.ts new file mode 100644 index 000000000000..60a99687793e --- /dev/null +++ b/src/client/activation/common/outputChannel.ts @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { IApplicationShell, ICommandManager } from '../../common/application/types'; +import '../../common/extensions'; +import { IDisposableRegistry, ILogOutputChannel } from '../../common/types'; +import { OutputChannelNames } from '../../common/utils/localize'; +import { ILanguageServerOutputChannel } from '../types'; + +@injectable() +export class LanguageServerOutputChannel implements ILanguageServerOutputChannel { + public output: ILogOutputChannel | undefined; + + private registered = false; + + constructor( + @inject(IApplicationShell) private readonly appShell: IApplicationShell, + @inject(ICommandManager) private readonly commandManager: ICommandManager, + @inject(IDisposableRegistry) private readonly disposable: IDisposableRegistry, + ) {} + + public get channel(): ILogOutputChannel { + if (!this.output) { + this.output = this.appShell.createOutputChannel(OutputChannelNames.languageServer); + this.disposable.push(this.output); + this.registerCommand().ignoreErrors(); + } + return this.output; + } + + private async registerCommand() { + if (this.registered) { + return; + } + this.registered = true; + // This controls the visibility of the command used to display the LS Output panel. + // We don't want to display it when Jedi is used instead of LS. + await this.commandManager.executeCommand('setContext', 'python.hasLanguageServerOutputChannel', true); + this.disposable.push( + this.commandManager.registerCommand('python.viewLanguageServerOutput', () => this.output?.show(true)), + ); + this.disposable.push({ + dispose: () => { + this.registered = false; + }, + }); + } +} diff --git a/src/client/activation/common/packageRepository.ts b/src/client/activation/common/packageRepository.ts deleted file mode 100644 index ba073d7b1808..000000000000 --- a/src/client/activation/common/packageRepository.ts +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { injectable } from 'inversify'; -import { AzureBlobStoreNugetRepository } from '../../common/nuget/azureBlobStoreNugetRepository'; -import { IServiceContainer } from '../../ioc/types'; - -export const azureBlobStorageAccount = 'https://pvsc.blob.core.windows.net'; -export const azureCDNBlobStorageAccount = 'https://pvsc.azureedge.net'; - -export enum LanguageServerDownloadChannel { - stable = 'stable', - beta = 'beta', - daily = 'daily' -} - -@injectable() -export abstract class StableLanguageServerPackageRepository extends AzureBlobStoreNugetRepository { - constructor(serviceContainer: IServiceContainer, packageName: string) { - super( - serviceContainer, - azureBlobStorageAccount, - `${packageName}-${LanguageServerDownloadChannel.stable}`, - azureCDNBlobStorageAccount - ); - } -} - -@injectable() -export abstract class BetaLanguageServerPackageRepository extends AzureBlobStoreNugetRepository { - constructor(serviceContainer: IServiceContainer, packageName: string) { - super( - serviceContainer, - azureBlobStorageAccount, - `${packageName}-${LanguageServerDownloadChannel.beta}`, - azureCDNBlobStorageAccount - ); - } -} - -@injectable() -export abstract class DailyLanguageServerPackageRepository extends AzureBlobStoreNugetRepository { - constructor(serviceContainer: IServiceContainer, packageName: string) { - super( - serviceContainer, - azureBlobStorageAccount, - `${packageName}-${LanguageServerDownloadChannel.daily}`, - azureCDNBlobStorageAccount - ); - } -} diff --git a/src/client/activation/extensionSurvey.ts b/src/client/activation/extensionSurvey.ts index 45750a4c9b4d..d32ba7180c0f 100644 --- a/src/client/activation/extensionSurvey.ts +++ b/src/client/activation/extensionSurvey.ts @@ -3,15 +3,16 @@ 'use strict'; -import { inject, injectable, optional } from 'inversify'; +import { inject, injectable } from 'inversify'; import * as querystring from 'querystring'; -import { IApplicationEnvironment, IApplicationShell } from '../common/application/types'; +import { env, UIKind } from 'vscode'; +import { IApplicationEnvironment, IApplicationShell, IWorkspaceService } from '../common/application/types'; import { ShowExtensionSurveyPrompt } from '../common/experiments/groups'; import '../common/extensions'; -import { traceDecorators } from '../common/logger'; import { IPlatformService } from '../common/platform/types'; -import { IBrowserService, IExperimentsManager, IPersistentStateFactory, IRandom } from '../common/types'; +import { IBrowserService, IExperimentService, IPersistentStateFactory, IRandom } from '../common/types'; import { Common, ExtensionSurveyBanner } from '../common/utils/localize'; +import { traceDecoratorError } from '../logging'; import { sendTelemetryEvent } from '../telemetry'; import { EventName } from '../telemetry/constants'; import { IExtensionSingleActivationService } from './types'; @@ -19,7 +20,7 @@ import { IExtensionSingleActivationService } from './types'; // persistent state names, exported to make use of in testing export enum extensionSurveyStateKeys { doNotShowAgain = 'doNotShowExtensionSurveyAgain', - disableSurveyForTime = 'doNotShowExtensionSurveyUntilTime' + disableSurveyForTime = 'doNotShowExtensionSurveyUntilTime', } const timeToDisableSurveyFor = 1000 * 60 * 60 * 24 * 7 * 12; // 12 weeks @@ -27,21 +28,22 @@ const WAIT_TIME_TO_SHOW_SURVEY = 1000 * 60 * 60 * 3; // 3 hours @injectable() export class ExtensionSurveyPrompt implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; constructor( @inject(IApplicationShell) private appShell: IApplicationShell, @inject(IBrowserService) private browserService: IBrowserService, @inject(IPersistentStateFactory) private persistentState: IPersistentStateFactory, @inject(IRandom) private random: IRandom, - @inject(IExperimentsManager) private experiments: IExperimentsManager, + @inject(IExperimentService) private experiments: IExperimentService, @inject(IApplicationEnvironment) private appEnvironment: IApplicationEnvironment, @inject(IPlatformService) private platformService: IPlatformService, - @optional() private sampleSizePerOneHundredUsers: number = 10, - @optional() private waitTimeToShowSurvey: number = WAIT_TIME_TO_SHOW_SURVEY + @inject(IWorkspaceService) private readonly workspace: IWorkspaceService, + private sampleSizePerOneHundredUsers: number = 10, + private waitTimeToShowSurvey: number = WAIT_TIME_TO_SHOW_SURVEY, ) {} public async activate(): Promise { - if (!this.experiments.inExperiment(ShowExtensionSurveyPrompt.enabled)) { - this.experiments.sendTelemetryIfInExperiment(ShowExtensionSurveyPrompt.control); + if (!(await this.experiments.inExperiment(ShowExtensionSurveyPrompt.experiment))) { return; } const show = this.shouldShowBanner(); @@ -51,11 +53,26 @@ export class ExtensionSurveyPrompt implements IExtensionSingleActivationService setTimeout(() => this.showSurvey().ignoreErrors(), this.waitTimeToShowSurvey); } - @traceDecorators.error('Failed to check whether to display prompt for extension survey') + @traceDecoratorError('Failed to check whether to display prompt for extension survey') public shouldShowBanner(): boolean { + if (env.uiKind === UIKind?.Web) { + return false; + } + + let feedbackEnabled = true; + + const telemetryConfig = this.workspace.getConfiguration('telemetry'); + if (telemetryConfig) { + feedbackEnabled = telemetryConfig.get('feedback.enabled', true); + } + + if (!feedbackEnabled) { + return false; + } + const doNotShowSurveyAgain = this.persistentState.createGlobalPersistentState( extensionSurveyStateKeys.doNotShowAgain, - false + false, ); if (doNotShowSurveyAgain.value) { return false; @@ -63,7 +80,7 @@ export class ExtensionSurveyPrompt implements IExtensionSingleActivationService const isSurveyDisabledForTimeState = this.persistentState.createGlobalPersistentState( extensionSurveyStateKeys.disableSurveyForTime, false, - timeToDisableSurveyFor + timeToDisableSurveyFor, ); if (isSurveyDisabledForTimeState.value) { return false; @@ -76,36 +93,32 @@ export class ExtensionSurveyPrompt implements IExtensionSingleActivationService return true; } - @traceDecorators.error('Failed to display prompt for extension survey') + @traceDecoratorError('Failed to display prompt for extension survey') public async showSurvey() { - const prompts = [ - ExtensionSurveyBanner.bannerLabelYes(), - ExtensionSurveyBanner.maybeLater(), - Common.doNotShowAgain() - ]; - const telemetrySelections: ['Yes', 'Maybe later', 'Do not show again'] = [ + const prompts = [ExtensionSurveyBanner.bannerLabelYes, ExtensionSurveyBanner.maybeLater, Common.doNotShowAgain]; + const telemetrySelections: ['Yes', 'Maybe later', "Don't show again"] = [ 'Yes', 'Maybe later', - 'Do not show again' + "Don't show again", ]; - const selection = await this.appShell.showInformationMessage(ExtensionSurveyBanner.bannerMessage(), ...prompts); + const selection = await this.appShell.showInformationMessage(ExtensionSurveyBanner.bannerMessage, ...prompts); sendTelemetryEvent(EventName.EXTENSION_SURVEY_PROMPT, undefined, { - selection: selection ? telemetrySelections[prompts.indexOf(selection)] : undefined + selection: selection ? telemetrySelections[prompts.indexOf(selection)] : undefined, }); if (!selection) { return; } - if (selection === ExtensionSurveyBanner.bannerLabelYes()) { + if (selection === ExtensionSurveyBanner.bannerLabelYes) { this.launchSurvey(); // Disable survey for a few weeks await this.persistentState .createGlobalPersistentState( extensionSurveyStateKeys.disableSurveyForTime, false, - timeToDisableSurveyFor + timeToDisableSurveyFor, ) .updateValue(true); - } else if (selection === Common.doNotShowAgain()) { + } else if (selection === Common.doNotShowAgain) { // Never show the survey again await this.persistentState .createGlobalPersistentState(extensionSurveyStateKeys.doNotShowAgain, false) @@ -118,7 +131,7 @@ export class ExtensionSurveyPrompt implements IExtensionSingleActivationService o: encodeURIComponent(this.platformService.osType), // platform v: encodeURIComponent(this.appEnvironment.vscodeVersion), e: encodeURIComponent(this.appEnvironment.packageJson.version), // extension version - m: encodeURIComponent(this.appEnvironment.sessionId) + m: encodeURIComponent(this.appEnvironment.sessionId), }); const url = `https://aka.ms/AA5rjx5?${query}`; this.browserService.launch(url); diff --git a/src/client/activation/jedi.ts b/src/client/activation/jedi.ts deleted file mode 100644 index 7b964d5e7974..000000000000 --- a/src/client/activation/jedi.ts +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import { inject, injectable } from 'inversify'; -import { - CancellationToken, - CodeLens, - commands, - CompletionContext, - CompletionItem, - CompletionList, - DocumentFilter, - DocumentSymbol, - Event, - Hover, - languages, - Location, - LocationLink, - Position, - ProviderResult, - ReferenceContext, - SignatureHelp, - SignatureHelpContext, - SymbolInformation, - TextDocument, - WorkspaceEdit -} from 'vscode'; - -import { PYTHON } from '../common/constants'; -import { traceError } from '../common/logger'; -import { IConfigurationService, IDisposable, IExtensionContext, Resource } from '../common/types'; -import { IShebangCodeLensProvider } from '../interpreter/contracts'; -import { IServiceContainer, IServiceManager } from '../ioc/types'; -import { JediFactory } from '../languageServices/jediProxyFactory'; -import { PythonCompletionItemProvider } from '../providers/completionProvider'; -import { PythonDefinitionProvider } from '../providers/definitionProvider'; -import { PythonHoverProvider } from '../providers/hoverProvider'; -import { PythonObjectDefinitionProvider } from '../providers/objectDefinitionProvider'; -import { PythonReferenceProvider } from '../providers/referenceProvider'; -import { PythonRenameProvider } from '../providers/renameProvider'; -import { PythonSignatureProvider } from '../providers/signatureProvider'; -import { JediSymbolProvider } from '../providers/symbolProvider'; -import { PythonEnvironment } from '../pythonEnvironments/info'; -import { ITestManagementService } from '../testing/types'; -import { BlockFormatProviders } from '../typeFormatters/blockFormatProvider'; -import { OnTypeFormattingDispatcher } from '../typeFormatters/dispatcher'; -import { OnEnterFormatter } from '../typeFormatters/onEnterFormatter'; -import { WorkspaceSymbols } from '../workspaceSymbols/main'; -import { ILanguageServerActivator } from './types'; - -@injectable() -export class JediExtensionActivator implements ILanguageServerActivator { - private static workspaceSymbols: WorkspaceSymbols | undefined; - private readonly context: IExtensionContext; - private jediFactory?: JediFactory; - private readonly documentSelector: DocumentFilter[]; - private renameProvider: PythonRenameProvider | undefined; - private hoverProvider: PythonHoverProvider | undefined; - private definitionProvider: PythonDefinitionProvider | undefined; - private referenceProvider: PythonReferenceProvider | undefined; - private completionProvider: PythonCompletionItemProvider | undefined; - private codeLensProvider: IShebangCodeLensProvider | undefined; - private symbolProvider: JediSymbolProvider | undefined; - private signatureProvider: PythonSignatureProvider | undefined; - private registrations: IDisposable[] = []; - private objectDefinitionProvider: PythonObjectDefinitionProvider | undefined; - - constructor(@inject(IServiceManager) private serviceManager: IServiceManager) { - this.context = this.serviceManager.get(IExtensionContext); - this.documentSelector = PYTHON; - } - - public async start(_resource: Resource, interpreter: PythonEnvironment | undefined): Promise { - if (this.jediFactory) { - throw new Error('Jedi already started'); - } - const context = this.context; - const jediFactory = (this.jediFactory = new JediFactory(interpreter, this.serviceManager)); - context.subscriptions.push(jediFactory); - const serviceContainer = this.serviceManager.get(IServiceContainer); - - this.renameProvider = new PythonRenameProvider(this.serviceManager); - this.definitionProvider = new PythonDefinitionProvider(jediFactory); - this.hoverProvider = new PythonHoverProvider(jediFactory); - this.referenceProvider = new PythonReferenceProvider(jediFactory); - this.completionProvider = new PythonCompletionItemProvider(jediFactory, this.serviceManager); - this.codeLensProvider = this.serviceManager.get(IShebangCodeLensProvider); - this.objectDefinitionProvider = new PythonObjectDefinitionProvider(jediFactory); - this.symbolProvider = new JediSymbolProvider(serviceContainer, jediFactory); - this.signatureProvider = new PythonSignatureProvider(jediFactory); - - if (!JediExtensionActivator.workspaceSymbols) { - // Workspace symbols is static because it doesn't rely on the jediFactory. - JediExtensionActivator.workspaceSymbols = new WorkspaceSymbols(serviceContainer); - context.subscriptions.push(JediExtensionActivator.workspaceSymbols); - } - - const testManagementService = this.serviceManager.get(ITestManagementService); - testManagementService - .activate(this.symbolProvider) - .catch((ex) => traceError('Failed to activate Unit Tests', ex)); - } - - public deactivate() { - this.registrations.forEach((r) => r.dispose()); - this.registrations = []; - } - - public activate() { - if ( - this.registrations.length === 0 && - this.renameProvider && - this.definitionProvider && - this.hoverProvider && - this.referenceProvider && - this.completionProvider && - this.codeLensProvider && - this.symbolProvider && - this.signatureProvider - ) { - // Make sure commands are in the registration list that gets disposed when the language server is disconnected from the - // IDE. - this.registrations.push( - commands.registerCommand('python.goToPythonObject', () => - this.objectDefinitionProvider!.goToObjectDefinition() - ) - ); - this.registrations.push(languages.registerRenameProvider(this.documentSelector, this.renameProvider)); - this.registrations.push( - languages.registerDefinitionProvider(this.documentSelector, this.definitionProvider) - ); - this.registrations.push(languages.registerHoverProvider(this.documentSelector, this.hoverProvider)); - this.registrations.push(languages.registerReferenceProvider(this.documentSelector, this.referenceProvider)); - this.registrations.push( - languages.registerCompletionItemProvider(this.documentSelector, this.completionProvider, '.') - ); - this.registrations.push(languages.registerCodeLensProvider(this.documentSelector, this.codeLensProvider)); - const onTypeDispatcher = new OnTypeFormattingDispatcher({ - '\n': new OnEnterFormatter(), - ':': new BlockFormatProviders() - }); - const onTypeTriggers = onTypeDispatcher.getTriggerCharacters(); - if (onTypeTriggers) { - this.registrations.push( - languages.registerOnTypeFormattingEditProvider( - PYTHON, - onTypeDispatcher, - onTypeTriggers.first, - ...onTypeTriggers.more - ) - ); - } - this.registrations.push( - languages.registerDocumentSymbolProvider(this.documentSelector, this.symbolProvider) - ); - const pythonSettings = this.serviceManager.get(IConfigurationService).getSettings(); - if (pythonSettings.devOptions.indexOf('DISABLE_SIGNATURE') === -1) { - this.registrations.push( - languages.registerSignatureHelpProvider(this.documentSelector, this.signatureProvider, '(', ',') - ); - } - } - } - - public provideRenameEdits( - document: TextDocument, - position: Position, - newName: string, - token: CancellationToken - ): ProviderResult { - if (this.renameProvider) { - return this.renameProvider.provideRenameEdits(document, position, newName, token); - } - } - public provideDefinition( - document: TextDocument, - position: Position, - token: CancellationToken - ): ProviderResult { - if (this.definitionProvider) { - return this.definitionProvider.provideDefinition(document, position, token); - } - } - public provideHover(document: TextDocument, position: Position, token: CancellationToken): ProviderResult { - if (this.hoverProvider) { - return this.hoverProvider.provideHover(document, position, token); - } - } - public provideReferences( - document: TextDocument, - position: Position, - context: ReferenceContext, - token: CancellationToken - ): ProviderResult { - if (this.referenceProvider) { - return this.referenceProvider.provideReferences(document, position, context, token); - } - } - public provideCompletionItems( - document: TextDocument, - position: Position, - token: CancellationToken, - _context: CompletionContext - ): ProviderResult { - if (this.completionProvider) { - return this.completionProvider.provideCompletionItems(document, position, token); - } - } - - public resolveCompletionItem(item: CompletionItem, token: CancellationToken): ProviderResult { - if (this.completionProvider) { - return this.completionProvider.resolveCompletionItem(item, token); - } - } - - public get onDidChangeCodeLenses(): Event | undefined { - return this.codeLensProvider ? this.codeLensProvider.onDidChangeCodeLenses : undefined; - } - public provideCodeLenses(document: TextDocument, token: CancellationToken): ProviderResult { - if (this.codeLensProvider) { - return this.codeLensProvider.provideCodeLenses(document, token); - } - } - public provideDocumentSymbols( - document: TextDocument, - token: CancellationToken - ): ProviderResult { - if (this.symbolProvider) { - return this.symbolProvider.provideDocumentSymbols(document, token); - } - } - public provideSignatureHelp( - document: TextDocument, - position: Position, - token: CancellationToken, - _context: SignatureHelpContext - ): ProviderResult { - if (this.signatureProvider) { - return this.signatureProvider.provideSignatureHelp(document, position, token); - } - } - - public dispose(): void { - this.registrations.forEach((r) => r.dispose()); - if (this.jediFactory) { - this.jediFactory.dispose(); - } - } -} diff --git a/src/client/activation/jedi/activator.ts b/src/client/activation/jedi/activator.ts deleted file mode 100644 index f0ea4f2d2313..000000000000 --- a/src/client/activation/jedi/activator.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -// tslint:disable-next-line: import-name -import { IWorkspaceService } from '../../common/application/types'; -import { IFileSystem } from '../../common/platform/types'; -import { IConfigurationService, Resource } from '../../common/types'; -import { LanguageServerActivatorBase } from '../common/activatorBase'; -import { ILanguageServerManager } from '../types'; - -/** - * Starts jedi language server manager. - * - * @export - * @class JediLanguageServerActivator - * @implements {ILanguageServerActivator} - * @extends {LanguageServerActivatorBase} - */ -@injectable() -export class JediLanguageServerActivator extends LanguageServerActivatorBase { - constructor( - @inject(ILanguageServerManager) manager: ILanguageServerManager, - @inject(IWorkspaceService) workspace: IWorkspaceService, - @inject(IFileSystem) fs: IFileSystem, - @inject(IConfigurationService) configurationService: IConfigurationService - ) { - super(manager, workspace, fs, configurationService); - } - - public async ensureLanguageServerIsAvailable(_resource: Resource): Promise { - // Nothing to do here. Jedi language server is shipped with the extension - } -} diff --git a/src/client/activation/jedi/analysisOptions.ts b/src/client/activation/jedi/analysisOptions.ts index 8e2298c9781a..007008dc9b13 100644 --- a/src/client/activation/jedi/analysisOptions.ts +++ b/src/client/activation/jedi/analysisOptions.ts @@ -1,17 +1,93 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { inject, injectable } from 'inversify'; + +import * as path from 'path'; +import { WorkspaceFolder } from 'vscode'; +import { IWorkspaceService } from '../../common/application/types'; +import { IConfigurationService, Resource } from '../../common/types'; import { IEnvironmentVariablesProvider } from '../../common/variables/types'; -import { LanguageServerAnalysisOptionsBase } from '../common/analysisOptions'; +import { PythonEnvironment } from '../../pythonEnvironments/info'; +import { LanguageServerAnalysisOptionsWithEnv } from '../common/analysisOptions'; import { ILanguageServerOutputChannel } from '../types'; -@injectable() -export class JediLanguageServerAnalysisOptions extends LanguageServerAnalysisOptionsBase { +/* eslint-disable @typescript-eslint/explicit-module-boundary-types, class-methods-use-this */ + +export class JediLanguageServerAnalysisOptions extends LanguageServerAnalysisOptionsWithEnv { + private resource: Resource | undefined; + + private interpreter: PythonEnvironment | undefined; + constructor( - @inject(IEnvironmentVariablesProvider) envVarsProvider: IEnvironmentVariablesProvider, - @inject(ILanguageServerOutputChannel) lsOutputChannel: ILanguageServerOutputChannel + envVarsProvider: IEnvironmentVariablesProvider, + lsOutputChannel: ILanguageServerOutputChannel, + private readonly configurationService: IConfigurationService, + workspace: IWorkspaceService, ) { - super(envVarsProvider, lsOutputChannel); + super(envVarsProvider, lsOutputChannel, workspace); + this.resource = undefined; + } + + public async initialize(resource: Resource, interpreter: PythonEnvironment | undefined) { + this.resource = resource; + this.interpreter = interpreter; + return super.initialize(resource, interpreter); + } + + protected getWorkspaceFolder(): WorkspaceFolder | undefined { + return this.workspace.getWorkspaceFolder(this.resource); + } + + protected async getInitializationOptions() { + const pythonSettings = this.configurationService.getSettings(this.resource); + const workspacePath = this.getWorkspaceFolder()?.uri.fsPath; + const extraPaths = pythonSettings.autoComplete + ? pythonSettings.autoComplete.extraPaths.map((extraPath) => { + if (path.isAbsolute(extraPath)) { + return extraPath; + } + return workspacePath ? path.join(workspacePath, extraPath) : ''; + }) + : []; + + if (workspacePath) { + extraPaths.unshift(workspacePath); + } + + const distinctExtraPaths = extraPaths + .filter((value) => value.length > 0) + .filter((value, index, self) => self.indexOf(value) === index); + + return { + markupKindPreferred: 'markdown', + completion: { + resolveEagerly: false, + disableSnippets: true, + }, + diagnostics: { + enable: true, + didOpen: true, + didSave: true, + didChange: true, + }, + hover: { + disable: { + keyword: { + all: true, + }, + }, + }, + workspace: { + extraPaths: distinctExtraPaths, + environmentPath: this.interpreter?.path, + symbols: { + // 0 means remove limit on number of workspace symbols returned + maxSymbols: 0, + }, + }, + semantic_tokens: { + enable: true, + }, + }; } } diff --git a/src/client/activation/jedi/languageClientFactory.ts b/src/client/activation/jedi/languageClientFactory.ts index aaa8c8b000c9..70bd65da8d0d 100644 --- a/src/client/activation/jedi/languageClientFactory.ts +++ b/src/client/activation/jedi/languageClientFactory.ts @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { inject, injectable } from 'inversify'; import * as path from 'path'; import { LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient/node'; @@ -11,34 +10,24 @@ import { IInterpreterService } from '../../interpreter/contracts'; import { PythonEnvironment } from '../../pythonEnvironments/info'; import { ILanguageClientFactory } from '../types'; -// tslint:disable:no-require-imports no-require-imports no-var-requires max-classes-per-file -const languageClientName = 'Python Tools'; +const languageClientName = 'Python Jedi'; -@injectable() export class JediLanguageClientFactory implements ILanguageClientFactory { - constructor(@inject(IInterpreterService) private interpreterService: IInterpreterService) {} + constructor(private interpreterService: IInterpreterService) {} public async createLanguageClient( resource: Resource, _interpreter: PythonEnvironment | undefined, - clientOptions: LanguageClientOptions + clientOptions: LanguageClientOptions, ): Promise { // Just run the language server using a module - const jediServerModulePath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'runJediLanguageServer.py'); + const lsScriptPath = path.join(EXTENSION_ROOT_DIR, 'python_files', 'run-jedi-language-server.py'); const interpreter = await this.interpreterService.getActiveInterpreter(resource); - const pythonPath = interpreter ? interpreter.path : 'python'; - const args = [jediServerModulePath]; const serverOptions: ServerOptions = { - command: pythonPath, - args + command: interpreter ? interpreter.path : 'python', + args: [lsScriptPath], }; - const vscodeLanguageClient = require('vscode-languageclient/node') as typeof import('vscode-languageclient/node'); // NOSONAR - return new vscodeLanguageClient.LanguageClient( - PYTHON_LANGUAGE, - languageClientName, - serverOptions, - clientOptions - ); + return new LanguageClient(PYTHON_LANGUAGE, languageClientName, serverOptions, clientOptions); } } diff --git a/src/client/activation/jedi/languageClientMiddleware.ts b/src/client/activation/jedi/languageClientMiddleware.ts new file mode 100644 index 000000000000..c8bb99629946 --- /dev/null +++ b/src/client/activation/jedi/languageClientMiddleware.ts @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { IServiceContainer } from '../../ioc/types'; +import { LanguageClientMiddleware } from '../languageClientMiddleware'; +import { LanguageServerType } from '../types'; + +export class JediLanguageClientMiddleware extends LanguageClientMiddleware { + public constructor(serviceContainer: IServiceContainer, serverVersion?: string) { + super(serviceContainer, LanguageServerType.Jedi, serverVersion); + } +} diff --git a/src/client/activation/jedi/languageServerProxy.ts b/src/client/activation/jedi/languageServerProxy.ts index 85f34fc6f4c6..d7ffe8328b9e 100644 --- a/src/client/activation/jedi/languageServerProxy.ts +++ b/src/client/activation/jedi/languageServerProxy.ts @@ -1,166 +1,112 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + import '../../common/extensions'; +import { Disposable, LanguageClient, LanguageClientOptions } from 'vscode-languageclient/node'; -import { inject, injectable } from 'inversify'; -import { - DidChangeConfigurationNotification, - Disposable, - LanguageClient, - LanguageClientOptions -} from 'vscode-languageclient/node'; - -import { DeprecatePythonPath } from '../../common/experiments/groups'; -import { traceDecorators, traceError } from '../../common/logger'; -import { IConfigurationService, IExperimentsManager, IInterpreterPathService, Resource } from '../../common/types'; -import { createDeferred, Deferred, sleep } from '../../common/utils/async'; -import { swallowExceptions } from '../../common/utils/decorators'; -import { noop } from '../../common/utils/misc'; -import { LanguageServerSymbolProvider } from '../../providers/symbolProvider'; +import { ChildProcess } from 'child_process'; +import { Resource } from '../../common/types'; import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; +import { captureTelemetry } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; -import { ITestManagementService } from '../../testing/types'; -import { FileBasedCancellationStrategy } from '../common/cancellationUtils'; -import { LanguageClientMiddleware } from '../languageClientMiddleware'; +import { JediLanguageClientMiddleware } from './languageClientMiddleware'; import { ProgressReporting } from '../progress'; import { ILanguageClientFactory, ILanguageServerProxy } from '../types'; +import { killPid } from '../../common/process/rawProcessApis'; +import { traceDecoratorError, traceDecoratorVerbose, traceError } from '../../logging'; -@injectable() export class JediLanguageServerProxy implements ILanguageServerProxy { - public languageClient: LanguageClient | undefined; - private startupCompleted: Deferred; - private cancellationStrategy: FileBasedCancellationStrategy | undefined; + private languageClient: LanguageClient | undefined; + private readonly disposables: Disposable[] = []; - private disposed: boolean = false; + private lsVersion: string | undefined; - constructor( - @inject(ILanguageClientFactory) private readonly factory: ILanguageClientFactory, - @inject(ITestManagementService) private readonly testManager: ITestManagementService, - @inject(IConfigurationService) private readonly configurationService: IConfigurationService, - @inject(IExperimentsManager) private readonly experiments: IExperimentsManager, - @inject(IInterpreterPathService) private readonly interpreterPathService: IInterpreterPathService - ) { - this.startupCompleted = createDeferred(); - } + constructor(private readonly factory: ILanguageClientFactory) {} private static versionTelemetryProps(instance: JediLanguageServerProxy) { return { - lsVersion: instance.lsVersion + lsVersion: instance.lsVersion, }; } - @traceDecorators.verbose('Stopping language server') - public dispose() { - if (this.languageClient) { - // Do not await on this. - this.languageClient.stop().then(noop, (ex) => traceError('Stopping language client failed', ex)); - this.languageClient = undefined; - } - if (this.cancellationStrategy) { - this.cancellationStrategy.dispose(); - this.cancellationStrategy = undefined; - } - while (this.disposables.length > 0) { - const d = this.disposables.shift()!; - d.dispose(); - } - if (this.startupCompleted.completed) { - this.startupCompleted.reject(new Error('Disposed language server')); - this.startupCompleted = createDeferred(); - } - this.disposed = true; + @traceDecoratorVerbose('Disposing language server') + public dispose(): void { + this.stop().ignoreErrors(); } - @traceDecorators.error('Failed to start language server') + @traceDecoratorError('Failed to start language server') @captureTelemetry( - EventName.LANGUAGE_SERVER_ENABLED, + EventName.JEDI_LANGUAGE_SERVER_ENABLED, undefined, true, undefined, - JediLanguageServerProxy.versionTelemetryProps + JediLanguageServerProxy.versionTelemetryProps, ) public async start( resource: Resource, interpreter: PythonEnvironment | undefined, - options: LanguageClientOptions + options: LanguageClientOptions, ): Promise { - if (!this.languageClient) { - this.lsVersion = - (options.middleware ? (options.middleware).serverVersion : undefined) ?? - '0.19.3'; - - this.cancellationStrategy = new FileBasedCancellationStrategy(); - options.connectionOptions = { cancellationStrategy: this.cancellationStrategy }; - - this.languageClient = await this.factory.createLanguageClient(resource, interpreter, options); - this.disposables.push(this.languageClient.start()); - await this.serverReady(); - if (this.disposed) { - // Check if it got disposed in the interim. - return; - } - const progressReporting = new ProgressReporting(this.languageClient); - this.disposables.push(progressReporting); - - if (this.experiments.inExperiment(DeprecatePythonPath.experiment)) { - this.disposables.push( - this.interpreterPathService.onDidChange(() => { - // Manually send didChangeConfiguration in order to get the server to requery - // the workspace configurations (to then pick up pythonPath set in the middleware). - // This is needed as interpreter changes via the interpreter path service happen - // outside of VS Code's settings (which would mean VS Code sends the config updates itself). - this.languageClient?.sendNotification(DidChangeConfigurationNotification.type, { - settings: null - }); - }) - ); - } + this.lsVersion = + (options.middleware ? (options.middleware).serverVersion : undefined) ?? + '0.19.3'; + + try { + const client = await this.factory.createLanguageClient(resource, interpreter, options); + this.registerHandlers(client); + await client.start(); + this.languageClient = client; + } catch (ex) { + traceError('Failed to start language server:', ex); + throw new Error('Launching Jedi language server using python failed, see output.'); + } + } + + @traceDecoratorVerbose('Stopping language server') + public async stop(): Promise { + while (this.disposables.length > 0) { + const d = this.disposables.shift()!; + d.dispose(); + } + + if (this.languageClient) { + const client = this.languageClient; + this.languageClient = undefined; - const settings = this.configurationService.getSettings(resource); - if (settings.downloadLanguageServer) { - this.languageClient.onTelemetry((telemetryEvent) => { - const eventName = telemetryEvent.EventName || EventName.LANGUAGE_SERVER_TELEMETRY; - const formattedProperties = { - ...telemetryEvent.Properties, - // Replace all slashes in the method name so it doesn't get scrubbed by vscode-extension-telemetry. - method: telemetryEvent.Properties.method?.replace(/\//g, '.') - }; - sendTelemetryEvent(eventName, telemetryEvent.Measurements, formattedProperties); - }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const pid: number | undefined = ((client as any)._serverProcess as ChildProcess)?.pid; + const killServer = () => { + if (pid) { + killPid(pid); + } + }; + + try { + await client.stop(); + await client.dispose(); + killServer(); + } catch (ex) { + traceError('Stopping language client failed', ex); + killServer(); } - await this.registerTestServices(); - } else { - await this.startupCompleted.promise; } } - // tslint:disable-next-line: no-empty - public loadExtension(_args?: {}) {} + // eslint-disable-next-line class-methods-use-this + public loadExtension(): void { + // No body. + } @captureTelemetry( - EventName.LANGUAGE_SERVER_READY, + EventName.JEDI_LANGUAGE_SERVER_READY, undefined, true, undefined, - JediLanguageServerProxy.versionTelemetryProps + JediLanguageServerProxy.versionTelemetryProps, ) - protected async serverReady(): Promise { - while (this.languageClient && !this.languageClient.initializeResult) { - await sleep(100); - } - if (this.languageClient) { - await this.languageClient.onReady(); - } - this.startupCompleted.resolve(); - } - - @swallowExceptions('Activating Unit Tests Manager for Jedi language server') - protected async registerTestServices() { - if (!this.languageClient) { - throw new Error('languageClient not initialized'); - } - await this.testManager.activate(new LanguageServerSymbolProvider(this.languageClient)); + private registerHandlers(client: LanguageClient) { + const progressReporting = new ProgressReporting(client); + this.disposables.push(progressReporting); } } diff --git a/src/client/activation/jedi/manager.ts b/src/client/activation/jedi/manager.ts index 9ae8ac8692dc..bafdcc735a12 100644 --- a/src/client/activation/jedi/manager.ts +++ b/src/client/activation/jedi/manager.ts @@ -1,14 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + import * as fs from 'fs-extra'; import * as path from 'path'; import '../../common/extensions'; -import { inject, injectable, named } from 'inversify'; - import { ICommandManager } from '../../common/application/types'; -import { traceDecorators } from '../../common/logger'; -import { IConfigurationService, IDisposable, IExperimentsManager, Resource } from '../../common/types'; +import { IDisposable, Resource } from '../../common/types'; import { debounceSync } from '../../common/utils/decorators'; import { EXTENSION_ROOT_DIR } from '../../constants'; import { IServiceContainer } from '../../ioc/types'; @@ -16,89 +14,91 @@ import { PythonEnvironment } from '../../pythonEnvironments/info'; import { captureTelemetry } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; import { Commands } from '../commands'; -import { LanguageClientMiddleware } from '../languageClientMiddleware'; -import { - ILanguageServerAnalysisOptions, - ILanguageServerManager, - ILanguageServerProxy, - LanguageServerType -} from '../types'; - -@injectable() +import { JediLanguageClientMiddleware } from './languageClientMiddleware'; +import { ILanguageServerAnalysisOptions, ILanguageServerManager, ILanguageServerProxy } from '../types'; +import { traceDecoratorError, traceDecoratorVerbose, traceVerbose } from '../../logging'; + export class JediLanguageServerManager implements ILanguageServerManager { - private languageServerProxy?: ILanguageServerProxy; private resource!: Resource; + private interpreter: PythonEnvironment | undefined; - private middleware: LanguageClientMiddleware | undefined; + + private middleware: JediLanguageClientMiddleware | undefined; + private disposables: IDisposable[] = []; - private connected: boolean = false; + + private static commandDispose: IDisposable; + + private connected = false; + private lsVersion: string | undefined; constructor( - @inject(IServiceContainer) private readonly serviceContainer: IServiceContainer, - @inject(ILanguageServerAnalysisOptions) - @named(LanguageServerType.Jedi) + private readonly serviceContainer: IServiceContainer, private readonly analysisOptions: ILanguageServerAnalysisOptions, - @inject(IExperimentsManager) private readonly experimentsManager: IExperimentsManager, - @inject(IConfigurationService) private readonly configService: IConfigurationService, - @inject(ICommandManager) commandManager: ICommandManager + private readonly languageServerProxy: ILanguageServerProxy, + commandManager: ICommandManager, ) { - this.disposables.push( - commandManager.registerCommand(Commands.RestartLS, () => { - this.restartLanguageServer().ignoreErrors(); - }) - ); + if (JediLanguageServerManager.commandDispose) { + JediLanguageServerManager.commandDispose.dispose(); + } + JediLanguageServerManager.commandDispose = commandManager.registerCommand(Commands.RestartLS, () => { + this.restartLanguageServer().ignoreErrors(); + }); } private static versionTelemetryProps(instance: JediLanguageServerManager) { return { - lsVersion: instance.lsVersion + lsVersion: instance.lsVersion, }; } - public dispose() { - if (this.languageProxy) { - this.languageProxy.dispose(); - } + public dispose(): void { + this.stopLanguageServer().ignoreErrors(); + JediLanguageServerManager.commandDispose.dispose(); this.disposables.forEach((d) => d.dispose()); } - public get languageProxy() { - return this.languageServerProxy; - } - - @traceDecorators.error('Failed to start language server') + @traceDecoratorError('Failed to start language server') public async start(resource: Resource, interpreter: PythonEnvironment | undefined): Promise { - if (this.languageProxy) { - throw new Error('Language server already started'); - } this.resource = resource; this.interpreter = interpreter; this.analysisOptions.onDidChange(this.restartLanguageServerDebounced, this, this.disposables); - // Version is actually hardcoded in our requirements.txt. - const requirementsTxt = await fs.readFile(path.join(EXTENSION_ROOT_DIR, 'requirements.txt'), 'utf-8'); - - // Search using a regex in the text - const match = /jedi-language-server==([0-9\.]*)/.exec(requirementsTxt); - if (match && match.length > 1) { - this.lsVersion = match[1]; - } else { - this.lsVersion = '0.19.3'; + try { + // Version is actually hardcoded in our requirements.txt. + const requirementsTxt = await fs.readFile( + path.join(EXTENSION_ROOT_DIR, 'python_files', 'jedilsp_requirements', 'requirements.txt'), + 'utf-8', + ); + + // Search using a regex in the text + const match = /jedi-language-server==([0-9\.]*)/.exec(requirementsTxt); + if (match && match.length === 2) { + [, this.lsVersion] = match; + } + } catch (ex) { + // Getting version here is best effort and does not affect how LS works and + // failing to get version should not stop LS from working. + traceVerbose('Failed to get jedi-language-server version: ', ex); } await this.analysisOptions.initialize(resource, interpreter); await this.startLanguageServer(); } - public connect() { - this.connected = true; - this.middleware?.connect(); + public connect(): void { + if (!this.connected) { + this.connected = true; + this.middleware?.connect(); + } } - public disconnect() { - this.connected = false; - this.middleware?.disconnect(); + public disconnect(): void { + if (this.connected) { + this.connected = false; + this.middleware?.disconnect(); + } } @debounceSync(1000) @@ -106,33 +106,25 @@ export class JediLanguageServerManager implements ILanguageServerManager { this.restartLanguageServer().ignoreErrors(); } - @traceDecorators.error('Failed to restart language server') - @traceDecorators.verbose('Restarting language server') + @traceDecoratorError('Failed to restart language server') + @traceDecoratorVerbose('Restarting language server') protected async restartLanguageServer(): Promise { - if (this.languageProxy) { - this.languageProxy.dispose(); - } + await this.stopLanguageServer(); await this.startLanguageServer(); } @captureTelemetry( - EventName.LANGUAGE_SERVER_STARTUP, + EventName.JEDI_LANGUAGE_SERVER_STARTUP, undefined, true, undefined, - JediLanguageServerManager.versionTelemetryProps + JediLanguageServerManager.versionTelemetryProps, ) - @traceDecorators.verbose('Starting language server') + @traceDecoratorVerbose('Starting language server') protected async startLanguageServer(): Promise { - this.languageServerProxy = this.serviceContainer.get(ILanguageServerProxy); - const options = await this.analysisOptions.getAnalysisOptions(); - options.middleware = this.middleware = new LanguageClientMiddleware( - this.experimentsManager, - this.configService, - LanguageServerType.Jedi, - this.lsVersion - ); + this.middleware = new JediLanguageClientMiddleware(this.serviceContainer, this.lsVersion); + options.middleware = this.middleware; // Make sure the middleware is connected if we restart and we we're already connected. if (this.connected) { @@ -142,4 +134,11 @@ export class JediLanguageServerManager implements ILanguageServerManager { // Then use this middleware to start a new language client. await this.languageServerProxy.start(this.resource, this.interpreter, options); } + + @traceDecoratorVerbose('Stopping language server') + protected async stopLanguageServer(): Promise { + if (this.languageServerProxy) { + await this.languageServerProxy.stop(); + } + } } diff --git a/src/client/activation/jedi/multiplexingActivator.ts b/src/client/activation/jedi/multiplexingActivator.ts deleted file mode 100644 index 70f5d664fc62..000000000000 --- a/src/client/activation/jedi/multiplexingActivator.ts +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import { inject, injectable } from 'inversify'; -import { - CancellationToken, - CompletionContext, - Event, - EventEmitter, - Position, - ReferenceContext, - SignatureHelpContext, - TextDocument -} from 'vscode'; -// tslint:disable-next-line: import-name -import { IWorkspaceService } from '../../common/application/types'; -import { JediLSP } from '../../common/experiments/groups'; -import { IFileSystem } from '../../common/platform/types'; -import { IConfigurationService, IExperimentService, Resource } from '../../common/types'; -import { IServiceManager } from '../../ioc/types'; -import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { JediExtensionActivator } from '../jedi'; -import { ILanguageServerActivator, ILanguageServerManager } from '../types'; -import { JediLanguageServerActivator } from './activator'; - -/** - * Starts jedi language server manager. - * - * @export - * @class JediLanguageServerActivator - * @implements {ILanguageServerActivator} - * @extends {LanguageServerActivatorBase} - */ -@injectable() -export class MultiplexingJediLanguageServerActivator implements ILanguageServerActivator { - private realLanguageServerPromise: Promise; - private realLanguageServer: ILanguageServerActivator | undefined; - private onDidChangeCodeLensesEmitter = new EventEmitter(); - - constructor( - @inject(IServiceManager) private readonly manager: IServiceManager, - @inject(IExperimentService) experimentService: IExperimentService - ) { - // Check experiment service to see if using new Jedi LSP protocol - this.realLanguageServerPromise = experimentService.inExperiment(JediLSP.experiment).then((inExperiment) => { - this.realLanguageServer = !inExperiment - ? // Pick how to launch jedi based on if in the experiment or not. - new JediExtensionActivator(this.manager) - : new JediLanguageServerActivator( - this.manager.get(ILanguageServerManager), - this.manager.get(IWorkspaceService), - this.manager.get(IFileSystem), - this.manager.get(IConfigurationService) - ); - return this.realLanguageServer; - }); - } - public async start(resource: Resource, interpreter: PythonEnvironment | undefined): Promise { - const realServer = await this.realLanguageServerPromise; - return realServer.start(resource, interpreter); - } - public activate(): void { - if (this.realLanguageServer) { - this.realLanguageServer.activate(); - } - } - public deactivate(): void { - if (this.realLanguageServer) { - this.realLanguageServer.deactivate(); - } - } - public get onDidChangeCodeLenses(): Event { - return this.onDidChangeCodeLensesEmitter.event; - } - - public get connection() { - if (this.realLanguageServer) { - return this.realLanguageServer.connection; - } - } - - public get capabilities() { - if (this.realLanguageServer) { - return this.realLanguageServer.capabilities; - } - } - - public async provideRenameEdits( - document: TextDocument, - position: Position, - newName: string, - token: CancellationToken - ) { - const server = await this.realLanguageServerPromise; - return server.provideRenameEdits(document, position, newName, token); - } - public async provideDefinition(document: TextDocument, position: Position, token: CancellationToken) { - const server = await this.realLanguageServerPromise; - return server.provideDefinition(document, position, token); - } - public async provideHover(document: TextDocument, position: Position, token: CancellationToken) { - const server = await this.realLanguageServerPromise; - return server.provideHover(document, position, token); - } - public async provideReferences( - document: TextDocument, - position: Position, - context: ReferenceContext, - token: CancellationToken - ) { - const server = await this.realLanguageServerPromise; - return server.provideReferences(document, position, context, token); - } - public async provideCompletionItems( - document: TextDocument, - position: Position, - token: CancellationToken, - context: CompletionContext - ) { - const server = await this.realLanguageServerPromise; - return server.provideCompletionItems(document, position, token, context); - } - public async provideCodeLenses(document: TextDocument, token: CancellationToken) { - const server = await this.realLanguageServerPromise; - return server.provideCodeLenses(document, token); - } - public async provideDocumentSymbols(document: TextDocument, token: CancellationToken) { - const server = await this.realLanguageServerPromise; - return server.provideDocumentSymbols(document, token); - } - public async provideSignatureHelp( - document: TextDocument, - position: Position, - token: CancellationToken, - context: SignatureHelpContext - ) { - const server = await this.realLanguageServerPromise; - return server.provideSignatureHelp(document, position, token, context); - } - public dispose(): void { - if (this.realLanguageServer) { - this.realLanguageServer.dispose(); - this.realLanguageServer = undefined; - } - } -} diff --git a/src/client/activation/languageClientMiddleware.ts b/src/client/activation/languageClientMiddleware.ts index 2232af85e039..d3d1e0c3c171 100644 --- a/src/client/activation/languageClientMiddleware.ts +++ b/src/client/activation/languageClientMiddleware.ts @@ -1,476 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import * as path from 'path'; -import { - CancellationToken, - CodeAction, - CodeActionContext, - CodeLens, - Command, - CompletionContext, - CompletionItem, - Declaration as VDeclaration, - Definition, - DefinitionLink, - Diagnostic, - DocumentHighlight, - DocumentLink, - DocumentSymbol, - FormattingOptions, - Location, - Position, - Position as VPosition, - ProviderResult, - Range, - SignatureHelp, - SignatureHelpContext, - SymbolInformation, - TextDocument, - TextEdit, - Uri, - WorkspaceEdit -} from 'vscode'; -import { - ConfigurationParams, - ConfigurationRequest, - HandleDiagnosticsSignature, - HandlerResult, - Middleware, - PrepareRenameSignature, - ProvideCodeActionsSignature, - ProvideCodeLensesSignature, - ProvideCompletionItemsSignature, - ProvideDefinitionSignature, - ProvideDocumentFormattingEditsSignature, - ProvideDocumentHighlightsSignature, - ProvideDocumentLinksSignature, - ProvideDocumentRangeFormattingEditsSignature, - ProvideDocumentSymbolsSignature, - ProvideHoverSignature, - ProvideOnTypeFormattingEditsSignature, - ProvideReferencesSignature, - ProvideRenameEditsSignature, - ProvideSignatureHelpSignature, - ProvideWorkspaceSymbolsSignature, - ResolveCodeLensSignature, - ResolveCompletionItemSignature, - ResolveDocumentLinkSignature, - ResponseError -} from 'vscode-languageclient/node'; -import { ProvideDeclarationSignature } from 'vscode-languageclient/lib/common/declaration'; -import { HiddenFilePrefix } from '../common/constants'; -import { CollectLSRequestTiming, CollectNodeLSRequestTiming } from '../common/experiments/groups'; -import { IConfigurationService, IExperimentsManager } from '../common/types'; -import { StopWatch } from '../common/utils/stopWatch'; +import { IServiceContainer } from '../ioc/types'; import { sendTelemetryEvent } from '../telemetry'; -import { EventName } from '../telemetry/constants'; -import { LanguageServerType } from './types'; - -// Only send 100 events per hour. -const globalDebounce = 1000 * 60 * 60; -const globalLimit = 100; - -// For calls that are more likely to happen during a session (hover, completion, document symbols). -const debounceFrequentCall = 1000 * 60 * 5; - -// For calls that are less likely to happen during a session (go-to-def, workspace symbols). -const debounceRareCall = 1000 * 60; - -export class LanguageClientMiddleware implements Middleware { - // These are public so that the captureTelemetryForLSPMethod decorator can access them. - public readonly eventName: EventName | undefined; - public readonly lastCaptured = new Map(); - public nextWindow: number = 0; - public eventCount: number = 0; - - public workspace = { - // tslint:disable:no-any - configuration: ( - params: ConfigurationParams, - token: CancellationToken, - next: ConfigurationRequest.HandlerSignature - ): HandlerResult => { - // Hand-collapse "Thenable | Thenable | Thenable" into just "Thenable" to make TS happy. - const result: any[] | ResponseError | Thenable> = next(params, token); - - // For backwards compatibility, set python.pythonPath to the configured - // value as though it were in the user's settings.json file. - const addPythonPath = (settings: any[] | ResponseError) => { - if (settings instanceof ResponseError) { - return settings; - } - - params.items.forEach((item, i) => { - if (item.section === 'python') { - const uri = item.scopeUri ? Uri.parse(item.scopeUri) : undefined; - settings[i].pythonPath = this.configService.getSettings(uri).pythonPath; - } - }); - - return settings; - }; - - if (isThenable(result)) { - return result.then(addPythonPath); - } - - return addPythonPath(result); - } - // tslint:enable:no-any - }; - - private connected = false; // Default to not forwarding to VS code. - - public constructor( - experimentsManager: IExperimentsManager, - private readonly configService: IConfigurationService, - serverType: LanguageServerType, - public readonly serverVersion?: string - ) { - this.handleDiagnostics = this.handleDiagnostics.bind(this); // VS Code calls function without context. - - let group: { experiment: string; control: string } | undefined; - - if (serverType === LanguageServerType.Microsoft) { - this.eventName = EventName.PYTHON_LANGUAGE_SERVER_REQUEST; - group = CollectLSRequestTiming; - } else if (serverType === LanguageServerType.Node) { - this.eventName = EventName.LANGUAGE_SERVER_REQUEST; - group = CollectNodeLSRequestTiming; - } else { - return; - } - - if (!experimentsManager.inExperiment(group.experiment)) { - this.eventName = undefined; - experimentsManager.sendTelemetryIfInExperiment(group.control); - } - } - - public connect() { - this.connected = true; - } - - public disconnect() { - this.connected = false; - } - - @captureTelemetryForLSPMethod('textDocument/completion', debounceFrequentCall) - public provideCompletionItem( - document: TextDocument, - position: Position, - context: CompletionContext, - token: CancellationToken, - next: ProvideCompletionItemsSignature - ) { - if (this.connected) { - return next(document, position, context, token); - } - } - - @captureTelemetryForLSPMethod('textDocument/hover', debounceFrequentCall) - public provideHover( - document: TextDocument, - position: Position, - token: CancellationToken, - next: ProvideHoverSignature - ) { - if (this.connected) { - return next(document, position, token); - } - } - - public handleDiagnostics(uri: Uri, diagnostics: Diagnostic[], next: HandleDiagnosticsSignature) { - if (this.connected) { - // Skip sending if this is a special file. - const filePath = uri.fsPath; - const baseName = filePath ? path.basename(filePath) : undefined; - if (!baseName || !baseName.startsWith(HiddenFilePrefix)) { - next(uri, diagnostics); - } - } - } - - @captureTelemetryForLSPMethod('completionItem/resolve', debounceFrequentCall) - public resolveCompletionItem( - item: CompletionItem, - token: CancellationToken, - next: ResolveCompletionItemSignature - ): ProviderResult { - if (this.connected) { - return next(item, token); - } - } - - @captureTelemetryForLSPMethod('textDocument/signatureHelp', debounceFrequentCall) - public provideSignatureHelp( - document: TextDocument, - position: Position, - context: SignatureHelpContext, - token: CancellationToken, - next: ProvideSignatureHelpSignature - ): ProviderResult { - if (this.connected) { - return next(document, position, context, token); - } - } - - @captureTelemetryForLSPMethod('textDocument/definition', debounceRareCall) - public provideDefinition( - document: TextDocument, - position: Position, - token: CancellationToken, - next: ProvideDefinitionSignature - ): ProviderResult { - if (this.connected) { - return next(document, position, token); - } - } - - @captureTelemetryForLSPMethod('textDocument/references', debounceRareCall) - public provideReferences( - document: TextDocument, - position: Position, - options: { - includeDeclaration: boolean; - }, - token: CancellationToken, - next: ProvideReferencesSignature - ): ProviderResult { - if (this.connected) { - return next(document, position, options, token); - } - } - - public provideDocumentHighlights( - document: TextDocument, - position: Position, - token: CancellationToken, - next: ProvideDocumentHighlightsSignature - ): ProviderResult { - if (this.connected) { - return next(document, position, token); - } - } - - @captureTelemetryForLSPMethod('textDocument/documentSymbol', debounceFrequentCall) - public provideDocumentSymbols( - document: TextDocument, - token: CancellationToken, - next: ProvideDocumentSymbolsSignature - ): ProviderResult { - if (this.connected) { - return next(document, token); - } - } - - @captureTelemetryForLSPMethod('workspace/symbol', debounceRareCall) - public provideWorkspaceSymbols( - query: string, - token: CancellationToken, - next: ProvideWorkspaceSymbolsSignature - ): ProviderResult { - if (this.connected) { - return next(query, token); - } - } - - @captureTelemetryForLSPMethod('textDocument/codeAction', debounceFrequentCall) - public provideCodeActions( - document: TextDocument, - range: Range, - context: CodeActionContext, - token: CancellationToken, - next: ProvideCodeActionsSignature - ): ProviderResult<(Command | CodeAction)[]> { - if (this.connected) { - return next(document, range, context, token); - } - } - - @captureTelemetryForLSPMethod('textDocument/codeLens', debounceFrequentCall) - public provideCodeLenses( - document: TextDocument, - token: CancellationToken, - next: ProvideCodeLensesSignature - ): ProviderResult { - if (this.connected) { - return next(document, token); - } - } - - @captureTelemetryForLSPMethod('codeLens/resolve', debounceFrequentCall) - public resolveCodeLens( - codeLens: CodeLens, - token: CancellationToken, - next: ResolveCodeLensSignature - ): ProviderResult { - if (this.connected) { - return next(codeLens, token); - } - } - - public provideDocumentFormattingEdits( - document: TextDocument, - options: FormattingOptions, - token: CancellationToken, - next: ProvideDocumentFormattingEditsSignature - ): ProviderResult { - if (this.connected) { - return next(document, options, token); - } - } - - public provideDocumentRangeFormattingEdits( - document: TextDocument, - range: Range, - options: FormattingOptions, - token: CancellationToken, - next: ProvideDocumentRangeFormattingEditsSignature - ): ProviderResult { - if (this.connected) { - return next(document, range, options, token); - } - } - - public provideOnTypeFormattingEdits( - document: TextDocument, - position: Position, - ch: string, - options: FormattingOptions, - token: CancellationToken, - next: ProvideOnTypeFormattingEditsSignature - ): ProviderResult { - if (this.connected) { - return next(document, position, ch, options, token); - } - } - - @captureTelemetryForLSPMethod('textDocument/rename', debounceRareCall) - public provideRenameEdits( - document: TextDocument, - position: Position, - newName: string, - token: CancellationToken, - next: ProvideRenameEditsSignature - ): ProviderResult { - if (this.connected) { - return next(document, position, newName, token); - } - } - - @captureTelemetryForLSPMethod('textDocument/prepareRename', debounceRareCall) - public prepareRename( - document: TextDocument, - position: Position, - token: CancellationToken, - next: PrepareRenameSignature - ): ProviderResult< - | Range - | { - range: Range; - placeholder: string; - } - > { - if (this.connected) { - return next(document, position, token); - } - } - public provideDocumentLinks( - document: TextDocument, - token: CancellationToken, - next: ProvideDocumentLinksSignature - ): ProviderResult { - if (this.connected) { - return next(document, token); - } - } - - public resolveDocumentLink( - link: DocumentLink, - token: CancellationToken, - next: ResolveDocumentLinkSignature - ): ProviderResult { - if (this.connected) { - return next(link, token); - } - } +import { LanguageClientMiddlewareBase } from './languageClientMiddlewareBase'; +import { LanguageServerType } from './types'; - @captureTelemetryForLSPMethod('textDocument/declaration', debounceRareCall) - public provideDeclaration( - document: TextDocument, - position: VPosition, - token: CancellationToken, - next: ProvideDeclarationSignature - ): ProviderResult { - if (this.connected) { - return next(document, position, token); - } +export class LanguageClientMiddleware extends LanguageClientMiddlewareBase { + public constructor(serviceContainer: IServiceContainer, serverType: LanguageServerType, serverVersion?: string) { + super(serviceContainer, serverType, sendTelemetryEvent, serverVersion); } } - -function captureTelemetryForLSPMethod(method: string, debounceMilliseconds: number) { - // tslint:disable-next-line:no-function-expression no-any - return function (_target: Object, _propertyKey: string, descriptor: TypedPropertyDescriptor) { - const originalMethod = descriptor.value; - - // tslint:disable-next-line:no-any - descriptor.value = function (this: LanguageClientMiddleware, ...args: any[]) { - const eventName = this.eventName; - if (!eventName) { - return originalMethod.apply(this, args); - } - - const now = Date.now(); - - if (now > this.nextWindow) { - // Past the end of the last window, reset. - this.nextWindow = now + globalDebounce; - this.eventCount = 0; - } else if (this.eventCount >= globalLimit) { - // Sent too many events in this window, don't send. - return originalMethod.apply(this, args); - } - - const lastCapture = this.lastCaptured.get(method); - if (lastCapture && now - lastCapture < debounceMilliseconds) { - return originalMethod.apply(this, args); - } - - this.lastCaptured.set(method, now); - this.eventCount += 1; - - // Replace all slashes in the method name so it doesn't get scrubbed by vscode-extension-telemetry. - const formattedMethod = method.replace(/\//g, '.'); - - const properties = { - lsVersion: this.serverVersion || 'unknown', - method: formattedMethod - }; - - const stopWatch = new StopWatch(); - // tslint:disable-next-line:no-unsafe-any - const result = originalMethod.apply(this, args); - - // tslint:disable-next-line:no-unsafe-any - if (result && isThenable(result)) { - result.then(() => { - sendTelemetryEvent(eventName, stopWatch.elapsedTime, properties); - }); - } else { - sendTelemetryEvent(eventName, stopWatch.elapsedTime, properties); - } - - return result; - }; - - return descriptor; - }; -} - -// tslint:disable-next-line: no-any -function isThenable(v: any): v is Thenable { - return typeof v?.then === 'function'; -} diff --git a/src/client/activation/languageClientMiddlewareBase.ts b/src/client/activation/languageClientMiddlewareBase.ts new file mode 100644 index 000000000000..f1e102a4081d --- /dev/null +++ b/src/client/activation/languageClientMiddlewareBase.ts @@ -0,0 +1,596 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +import * as path from 'path'; +import { CancellationToken, Diagnostic, Disposable, Uri } from 'vscode'; +import { + ConfigurationParams, + ConfigurationRequest, + HandleDiagnosticsSignature, + LSPObject, + Middleware, + ResponseError, +} from 'vscode-languageclient'; +import { ConfigurationItem } from 'vscode-languageserver-protocol'; + +import { HiddenFilePrefix } from '../common/constants'; +import { createDeferred, isThenable } from '../common/utils/async'; +import { StopWatch } from '../common/utils/stopWatch'; +import { IEnvironmentVariablesProvider } from '../common/variables/types'; +import { IInterpreterService } from '../interpreter/contracts'; +import { IServiceContainer } from '../ioc/types'; +import { EventName } from '../telemetry/constants'; +import { LanguageServerType } from './types'; + +// Only send 100 events per hour. +const globalDebounce = 1000 * 60 * 60; +const globalLimit = 100; + +// For calls that are more likely to happen during a session (hover, completion, document symbols). +const debounceFrequentCall = 1000 * 60 * 5; + +// For calls that are less likely to happen during a session (go-to-def, workspace symbols). +const debounceRareCall = 1000 * 60; + +type Awaited = T extends PromiseLike ? U : T; +type MiddleWareMethods = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [P in keyof Middleware]-?: NonNullable extends (...args: any) => any ? Middleware[P] : never; +}; + +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable prefer-rest-params */ +/* eslint-disable consistent-return */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +interface SendTelemetryEventFunc { + (eventName: EventName, measuresOrDurationMs?: Record | number, properties?: any, ex?: Error): void; +} + +export class LanguageClientMiddlewareBase implements Middleware { + private readonly eventName: EventName | undefined; + + private readonly lastCaptured = new Map(); + + private nextWindow = 0; + + private eventCount = 0; + + public workspace = { + configuration: async ( + params: ConfigurationParams, + token: CancellationToken, + next: ConfigurationRequest.HandlerSignature, + ) => { + if (!this.serviceContainer) { + return next(params, token); + } + + const interpreterService = this.serviceContainer.get(IInterpreterService); + const envService = this.serviceContainer.get(IEnvironmentVariablesProvider); + + let settings = next(params, token); + if (isThenable(settings)) { + settings = await settings; + } + if (settings instanceof ResponseError) { + return settings; + } + + for (const [i, item] of params.items.entries()) { + if (item.section === 'python') { + const uri = item.scopeUri ? Uri.parse(item.scopeUri) : undefined; + // For backwards compatibility, set python.pythonPath to the configured + // value as though it were in the user's settings.json file. + // As this is for backwards compatibility, `ConfigService.pythonPath` + // can be considered as active interpreter path. + const settingDict: LSPObject & { pythonPath: string; _envPYTHONPATH: string } = settings[ + i + ] as LSPObject & { pythonPath: string; _envPYTHONPATH: string }; + settingDict.pythonPath = (await interpreterService.getActiveInterpreter(uri))?.path ?? 'python'; + + const env = await envService.getEnvironmentVariables(uri); + const envPYTHONPATH = env.PYTHONPATH; + if (envPYTHONPATH) { + settingDict._envPYTHONPATH = envPYTHONPATH; + } + } + + this.configurationHook(item, settings[i] as LSPObject); + } + + return settings; + }, + }; + + // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-empty-function + protected configurationHook(_item: ConfigurationItem, _settings: LSPObject): void {} + + private get connected(): Promise { + return this.connectedPromise.promise; + } + + protected notebookAddon: (Middleware & Disposable) | undefined; + + private connectedPromise = createDeferred(); + + public constructor( + readonly serviceContainer: IServiceContainer | undefined, + serverType: LanguageServerType, + public readonly sendTelemetryEventFunc: SendTelemetryEventFunc, + public readonly serverVersion?: string, + ) { + this.handleDiagnostics = this.handleDiagnostics.bind(this); // VS Code calls function without context. + this.didOpen = this.didOpen.bind(this); + this.didSave = this.didSave.bind(this); + this.didChange = this.didChange.bind(this); + this.didClose = this.didClose.bind(this); + this.willSave = this.willSave.bind(this); + this.willSaveWaitUntil = this.willSaveWaitUntil.bind(this); + + if (serverType === LanguageServerType.Node) { + this.eventName = EventName.LANGUAGE_SERVER_REQUEST; + } else if (serverType === LanguageServerType.Jedi) { + this.eventName = EventName.JEDI_LANGUAGE_SERVER_REQUEST; + } + } + + public connect() { + this.connectedPromise.resolve(true); + } + + public disconnect() { + this.connectedPromise = createDeferred(); + this.connectedPromise.resolve(false); + } + + public didChange() { + return this.callNext('didChange', arguments); + } + + public didOpen() { + // Special case, open and close happen before we connect. + return this.callNext('didOpen', arguments); + } + + public didClose() { + // Special case, open and close happen before we connect. + return this.callNext('didClose', arguments); + } + + public didSave() { + return this.callNext('didSave', arguments); + } + + public willSave() { + return this.callNext('willSave', arguments); + } + + public willSaveWaitUntil() { + return this.callNext('willSaveWaitUntil', arguments); + } + + public async didOpenNotebook() { + return this.callNotebooksNext('didOpen', arguments); + } + + public async didSaveNotebook() { + return this.callNotebooksNext('didSave', arguments); + } + + public async didChangeNotebook() { + return this.callNotebooksNext('didChange', arguments); + } + + public async didCloseNotebook() { + return this.callNotebooksNext('didClose', arguments); + } + + notebooks = { + didOpen: this.didOpenNotebook.bind(this), + didSave: this.didSaveNotebook.bind(this), + didChange: this.didChangeNotebook.bind(this), + didClose: this.didCloseNotebook.bind(this), + }; + + public async provideCompletionItem() { + if (await this.connected) { + return this.callNextAndSendTelemetry( + 'textDocument/completion', + debounceFrequentCall, + 'provideCompletionItem', + arguments, + (_, result) => { + if (!result) { + return { resultLength: 0 }; + } + const resultLength = Array.isArray(result) ? result.length : result.items.length; + return { resultLength }; + }, + ); + } + } + + public async provideHover() { + if (await this.connected) { + return this.callNextAndSendTelemetry('textDocument/hover', debounceFrequentCall, 'provideHover', arguments); + } + } + + public async handleDiagnostics(uri: Uri, _diagnostics: Diagnostic[], _next: HandleDiagnosticsSignature) { + if (await this.connected) { + // Skip sending if this is a special file. + const filePath = uri.fsPath; + const baseName = filePath ? path.basename(filePath) : undefined; + if (!baseName || !baseName.startsWith(HiddenFilePrefix)) { + return this.callNext('handleDiagnostics', arguments); + } + } + } + + public async resolveCompletionItem() { + if (await this.connected) { + return this.callNextAndSendTelemetry( + 'completionItem/resolve', + debounceFrequentCall, + 'resolveCompletionItem', + arguments, + ); + } + } + + public async provideSignatureHelp() { + if (await this.connected) { + return this.callNextAndSendTelemetry( + 'textDocument/signatureHelp', + debounceFrequentCall, + 'provideSignatureHelp', + arguments, + ); + } + } + + public async provideDefinition() { + if (await this.connected) { + return this.callNextAndSendTelemetry( + 'textDocument/definition', + debounceRareCall, + 'provideDefinition', + arguments, + ); + } + } + + public async provideReferences() { + if (await this.connected) { + return this.callNextAndSendTelemetry( + 'textDocument/references', + debounceRareCall, + 'provideReferences', + arguments, + ); + } + } + + public async provideDocumentHighlights() { + if (await this.connected) { + return this.callNext('provideDocumentHighlights', arguments); + } + } + + public async provideDocumentSymbols() { + if (await this.connected) { + return this.callNextAndSendTelemetry( + 'textDocument/documentSymbol', + debounceFrequentCall, + 'provideDocumentSymbols', + arguments, + ); + } + } + + public async provideWorkspaceSymbols() { + if (await this.connected) { + return this.callNextAndSendTelemetry( + 'workspace/symbol', + debounceRareCall, + 'provideWorkspaceSymbols', + arguments, + ); + } + } + + public async provideCodeActions() { + if (await this.connected) { + return this.callNextAndSendTelemetry( + 'textDocument/codeAction', + debounceFrequentCall, + 'provideCodeActions', + arguments, + ); + } + } + + public async provideCodeLenses() { + if (await this.connected) { + return this.callNextAndSendTelemetry( + 'textDocument/codeLens', + debounceFrequentCall, + 'provideCodeLenses', + arguments, + ); + } + } + + public async resolveCodeLens() { + if (await this.connected) { + return this.callNextAndSendTelemetry( + 'codeLens/resolve', + debounceFrequentCall, + 'resolveCodeLens', + arguments, + ); + } + } + + public async provideDocumentFormattingEdits() { + if (await this.connected) { + return this.callNext('provideDocumentFormattingEdits', arguments); + } + } + + public async provideDocumentRangeFormattingEdits() { + if (await this.connected) { + return this.callNext('provideDocumentRangeFormattingEdits', arguments); + } + } + + public async provideOnTypeFormattingEdits() { + if (await this.connected) { + return this.callNextAndSendTelemetry( + 'textDocument/onTypeFormatting', + debounceFrequentCall, + 'provideOnTypeFormattingEdits', + arguments, + ); + } + } + + public async provideRenameEdits() { + if (await this.connected) { + return this.callNextAndSendTelemetry( + 'textDocument/rename', + debounceRareCall, + 'provideRenameEdits', + arguments, + ); + } + } + + public async prepareRename() { + if (await this.connected) { + return this.callNextAndSendTelemetry( + 'textDocument/prepareRename', + debounceRareCall, + 'prepareRename', + arguments, + ); + } + } + + public async provideDocumentLinks() { + if (await this.connected) { + return this.callNext('provideDocumentLinks', arguments); + } + } + + public async resolveDocumentLink() { + if (await this.connected) { + return this.callNext('resolveDocumentLink', arguments); + } + } + + public async provideDeclaration() { + if (await this.connected) { + return this.callNextAndSendTelemetry( + 'textDocument/declaration', + debounceRareCall, + 'provideDeclaration', + arguments, + ); + } + } + + public async provideTypeDefinition() { + if (await this.connected) { + return this.callNextAndSendTelemetry( + 'textDocument/typeDefinition', + debounceRareCall, + 'provideTypeDefinition', + arguments, + ); + } + } + + public async provideImplementation() { + if (await this.connected) { + return this.callNext('provideImplementation', arguments); + } + } + + public async provideDocumentColors() { + if (await this.connected) { + return this.callNext('provideDocumentColors', arguments); + } + } + + public async provideColorPresentations() { + if (await this.connected) { + return this.callNext('provideColorPresentations', arguments); + } + } + + public async provideFoldingRanges() { + if (await this.connected) { + return this.callNextAndSendTelemetry( + 'textDocument/foldingRange', + debounceFrequentCall, + 'provideFoldingRanges', + arguments, + ); + } + } + + public async provideSelectionRanges() { + if (await this.connected) { + return this.callNextAndSendTelemetry( + 'textDocument/selectionRange', + debounceRareCall, + 'provideSelectionRanges', + arguments, + ); + } + } + + public async prepareCallHierarchy() { + if (await this.connected) { + return this.callNext('prepareCallHierarchy', arguments); + } + } + + public async provideCallHierarchyIncomingCalls() { + if (await this.connected) { + return this.callNext('provideCallHierarchyIncomingCalls', arguments); + } + } + + public async provideCallHierarchyOutgoingCalls() { + if (await this.connected) { + return this.callNext('provideCallHierarchyOutgoingCalls', arguments); + } + } + + public async provideDocumentSemanticTokens() { + if (await this.connected) { + return this.callNext('provideDocumentSemanticTokens', arguments); + } + } + + public async provideDocumentSemanticTokensEdits() { + if (await this.connected) { + return this.callNext('provideDocumentSemanticTokensEdits', arguments); + } + } + + public async provideDocumentRangeSemanticTokens() { + if (await this.connected) { + return this.callNext('provideDocumentRangeSemanticTokens', arguments); + } + } + + public async provideLinkedEditingRange() { + if (await this.connected) { + return this.callNext('provideLinkedEditingRange', arguments); + } + } + + private callNext(funcName: keyof Middleware, args: IArguments) { + // This function uses the last argument to call the 'next' item. If we're allowing notebook + // middleware, it calls into the notebook middleware first. + if (this.notebookAddon && (this.notebookAddon as any)[funcName]) { + // It would be nice to use args.callee, but not supported in strict mode + return (this.notebookAddon as any)[funcName](...args); + } + + return args[args.length - 1](...args); + } + + private callNotebooksNext(funcName: 'didOpen' | 'didSave' | 'didChange' | 'didClose', args: IArguments) { + // This function uses the last argument to call the 'next' item. If we're allowing notebook + // middleware, it calls into the notebook middleware first. + if (this.notebookAddon?.notebooks && (this.notebookAddon.notebooks as any)[funcName]) { + // It would be nice to use args.callee, but not supported in strict mode + return (this.notebookAddon.notebooks as any)[funcName](...args); + } + + return args[args.length - 1](...args); + } + + private callNextAndSendTelemetry( + lspMethod: string, + debounceMilliseconds: number, + funcName: T, + args: IArguments, + lazyMeasures?: (this_: any, result: Awaited>) => Record, + ): ReturnType { + const now = Date.now(); + const stopWatch = new StopWatch(); + let calledNext = false; + // Change the 'last' argument (which is our next) in order to track if + // telemetry should be sent or not. + const changedArgs = [...args]; + + // Track whether or not the middleware called the 'next' function (which means it actually sent a request) + changedArgs[changedArgs.length - 1] = (...nextArgs: any) => { + // If the 'next' function is called, then legit request was made. + calledNext = true; + + // Then call the original 'next' + return args[args.length - 1](...nextArgs); + }; + + // Check if we need to reset the event count (if we're past the globalDebounce time) + if (now > this.nextWindow) { + // Past the end of the last window, reset. + this.nextWindow = now + globalDebounce; + this.eventCount = 0; + } + const lastCapture = this.lastCaptured.get(lspMethod); + + const sendTelemetry = (result: Awaited>) => { + // Skip doing anything if not allowed + // We should have: + // - called the next function in the middleware (this means a request was actually sent) + // - eventcount is not over the global limit + // - elapsed time since we sent this event is greater than debounce time + if ( + this.eventName && + calledNext && + this.eventCount < globalLimit && + (!lastCapture || now - lastCapture > debounceMilliseconds) + ) { + // We're sending, so update event count and last captured time + this.lastCaptured.set(lspMethod, now); + this.eventCount += 1; + + // Replace all slashes in the method name so it doesn't get scrubbed by @vscode/extension-telemetry. + const formattedMethod = lspMethod.replace(/\//g, '.'); + + const properties = { + lsVersion: this.serverVersion || 'unknown', + method: formattedMethod, + }; + + let measures: number | Record = stopWatch.elapsedTime; + if (lazyMeasures) { + measures = { + duration: measures, + ...lazyMeasures(this, result), + }; + } + + this.sendTelemetryEventFunc(this.eventName, measures, properties); + } + return result; + }; + + // Try to call the 'next' function in the middleware chain + const result: ReturnType = this.callNext(funcName, changedArgs as any); + + // Then wait for the result before sending telemetry + if (isThenable(result)) { + return result.then(sendTelemetry); + } + return sendTelemetry(result as any) as ReturnType; + } +} diff --git a/src/client/activation/languageServer/activator.ts b/src/client/activation/languageServer/activator.ts deleted file mode 100644 index 69d810540817..000000000000 --- a/src/client/activation/languageServer/activator.ts +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable, named } from 'inversify'; -import * as path from 'path'; - -import { IWorkspaceService } from '../../common/application/types'; -import { EXTENSION_ROOT_DIR, isTestExecution } from '../../common/constants'; -import { IFileSystem } from '../../common/platform/types'; -import { BANNER_NAME_PROPOSE_LS, IConfigurationService, IPythonExtensionBanner, Resource } from '../../common/types'; -import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { LanguageServerActivatorBase } from '../common/activatorBase'; -import { ILanguageServerDownloader, ILanguageServerFolderService, ILanguageServerManager } from '../types'; - -/** - * Starts the language server managers per workspaces (currently one for first workspace). - * - * @export - * @class DotNetLanguageServerActivator - * @implements {ILanguageServerActivator} - * @extends {LanguageServerActivatorBase} - */ -@injectable() -export class DotNetLanguageServerActivator extends LanguageServerActivatorBase { - constructor( - @inject(ILanguageServerManager) manager: ILanguageServerManager, - @inject(IWorkspaceService) workspace: IWorkspaceService, - @inject(IFileSystem) fs: IFileSystem, - @inject(ILanguageServerDownloader) private readonly lsDownloader: ILanguageServerDownloader, - @inject(ILanguageServerFolderService) - private readonly languageServerFolderService: ILanguageServerFolderService, - @inject(IConfigurationService) configurationService: IConfigurationService, - @inject(IPythonExtensionBanner) - @named(BANNER_NAME_PROPOSE_LS) - private proposePylancePopup: IPythonExtensionBanner - ) { - super(manager, workspace, fs, configurationService); - } - - public async start(resource: Resource, interpreter?: PythonEnvironment): Promise { - if (!isTestExecution()) { - this.proposePylancePopup.showBanner().ignoreErrors(); - } - return super.start(resource, interpreter); - } - - public async ensureLanguageServerIsAvailable(resource: Resource): Promise { - const languageServerFolderPath = await this.ensureLanguageServerFileIsAvailable(resource, 'mscorlib.dll'); - if (languageServerFolderPath) { - await this.prepareLanguageServerForNoICU(languageServerFolderPath); - } - } - - public async prepareLanguageServerForNoICU(languageServerFolderPath: string): Promise { - const targetJsonFile = path.join( - languageServerFolderPath, - 'Microsoft.Python.LanguageServer.runtimeconfig.json' - ); - // tslint:disable-next-line:no-any - let content: any = {}; - if (await this.fs.fileExists(targetJsonFile)) { - try { - content = JSON.parse(await this.fs.readFile(targetJsonFile)); - if ( - content.runtimeOptions && - content.runtimeOptions.configProperties && - content.runtimeOptions.configProperties['System.Globalization.Invariant'] === true - ) { - return; - } - } catch { - // Do nothing. - } - } - content.runtimeOptions = content.runtimeOptions || {}; - content.runtimeOptions.configProperties = content.runtimeOptions.configProperties || {}; - content.runtimeOptions.configProperties['System.Globalization.Invariant'] = true; - await this.fs.writeFile(targetJsonFile, JSON.stringify(content)); - } - - private async ensureLanguageServerFileIsAvailable( - resource: Resource, - fileName: string - ): Promise { - const settings = this.configurationService.getSettings(resource); - if (settings.downloadLanguageServer === false) { - // Development mode - return; - } - const languageServerFolder = await this.languageServerFolderService.getLanguageServerFolderName(resource); - if (languageServerFolder) { - const languageServerFolderPath = path.join(EXTENSION_ROOT_DIR, languageServerFolder); - const mscorlib = path.join(languageServerFolderPath, fileName); - if (!(await this.fs.fileExists(mscorlib))) { - await this.lsDownloader.downloadLanguageServer(languageServerFolderPath, resource); - } - return languageServerFolderPath; - } - } -} diff --git a/src/client/activation/languageServer/analysisOptions.ts b/src/client/activation/languageServer/analysisOptions.ts deleted file mode 100644 index 21e263d08a42..000000000000 --- a/src/client/activation/languageServer/analysisOptions.ts +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { ConfigurationChangeEvent, WorkspaceFolder } from 'vscode'; -import { DocumentFilter } from 'vscode-languageclient/node'; - -import { IWorkspaceService } from '../../common/application/types'; -import { traceDecorators, traceError } from '../../common/logger'; -import { IConfigurationService, IExtensionContext, IPathUtils, Resource } from '../../common/types'; -import { debounceSync } from '../../common/utils/decorators'; -import { IEnvironmentVariablesProvider } from '../../common/variables/types'; -import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { LanguageServerAnalysisOptionsBase } from '../common/analysisOptions'; -import { ILanguageServerFolderService, ILanguageServerOutputChannel } from '../types'; - -@injectable() -export class DotNetLanguageServerAnalysisOptions extends LanguageServerAnalysisOptionsBase { - private resource: Resource; - private interpreter: PythonEnvironment | undefined; - private languageServerFolder: string = ''; - private excludedFiles: string[] = []; - private typeshedPaths: string[] = []; - - constructor( - @inject(IExtensionContext) private readonly context: IExtensionContext, - @inject(IEnvironmentVariablesProvider) envVarsProvider: IEnvironmentVariablesProvider, - @inject(IConfigurationService) private readonly configuration: IConfigurationService, - @inject(IWorkspaceService) private readonly workspace: IWorkspaceService, - @inject(ILanguageServerOutputChannel) lsOutputChannel: ILanguageServerOutputChannel, - @inject(IPathUtils) private readonly pathUtils: IPathUtils, - @inject(ILanguageServerFolderService) private readonly languageServerFolderService: ILanguageServerFolderService - ) { - super(envVarsProvider, lsOutputChannel); - } - - public async initialize(resource: Resource, interpreter: PythonEnvironment | undefined) { - await super.initialize(resource, interpreter); - - this.resource = resource; - this.interpreter = interpreter; - this.languageServerFolder = await this.languageServerFolderService.getLanguageServerFolderName(resource); - - const disposable = this.workspace.onDidChangeConfiguration(this.onSettingsChangedHandler, this); - this.disposables.push(disposable); - } - - protected getWorkspaceFolder(): WorkspaceFolder | undefined { - return this.workspace.getWorkspaceFolder(this.resource); - } - - protected getDocumentFilters(workspaceFolder?: WorkspaceFolder): DocumentFilter[] { - const filters = super.getDocumentFilters(workspaceFolder); - - // Set the document selector only when in a multi-root workspace scenario. - if ( - workspaceFolder && - Array.isArray(this.workspace.workspaceFolders) && - this.workspace.workspaceFolders!.length > 1 - ) { - filters[0].pattern = `${workspaceFolder.uri.fsPath}/**/*`; - } - - return filters; - } - - protected async getInitializationOptions() { - const properties: Record = {}; - - const interpreterInfo = this.interpreter; - if (!interpreterInfo) { - throw Error('did not find an active interpreter'); - } - - properties.InterpreterPath = interpreterInfo.path; - - const version = interpreterInfo.version; - if (version) { - properties.Version = `${version.major}.${version.minor}.${version.patch}`; - } else { - traceError('Unable to determine Python version. Analysis may be limited.'); - } - - let searchPaths = []; - - const settings = this.configuration.getSettings(this.resource); - if (settings.autoComplete) { - const extraPaths = settings.autoComplete.extraPaths; - if (extraPaths && extraPaths.length > 0) { - searchPaths.push(...extraPaths); - } - } - - const envPythonPath = await this.getEnvPythonPath(); - if (envPythonPath !== '') { - const paths = envPythonPath.split(this.pathUtils.delimiter).filter((item) => item.trim().length > 0); - searchPaths.push(...paths); - } - - searchPaths = searchPaths.map((p) => path.normalize(p)); - - this.excludedFiles = this.getExcludedFiles(); - this.typeshedPaths = this.getTypeshedPaths(); - - return { - interpreter: { - properties - }, - searchPaths, - typeStubSearchPaths: this.typeshedPaths, - cacheFolderPath: this.getCacheFolderPath(), - excludeFiles: this.excludedFiles - }; - } - - protected getExcludedFiles(): string[] { - const list: string[] = ['**/Lib/**', '**/site-packages/**']; - this.getVsCodeExcludeSection('search.exclude', list); - this.getVsCodeExcludeSection('files.exclude', list); - this.getVsCodeExcludeSection('files.watcherExclude', list); - this.getPythonExcludeSection(list); - return list; - } - - protected getVsCodeExcludeSection(setting: string, list: string[]): void { - const states = this.workspace.getConfiguration(setting); - if (states) { - Object.keys(states) - .filter((k) => (k.indexOf('*') >= 0 || k.indexOf('/') >= 0) && states[k]) - .forEach((p) => list.push(p)); - } - } - - protected getPythonExcludeSection(list: string[]): void { - const pythonSettings = this.configuration.getSettings(this.resource); - const paths = pythonSettings && pythonSettings.linting ? pythonSettings.linting.ignorePatterns : undefined; - if (paths && Array.isArray(paths)) { - paths.filter((p) => p && p.length > 0).forEach((p) => list.push(p)); - } - } - - protected getTypeshedPaths(): string[] { - const settings = this.configuration.getSettings(this.resource); - return settings.analysis.typeshedPaths && settings.analysis.typeshedPaths.length > 0 - ? settings.analysis.typeshedPaths - : [path.join(this.context.extensionPath, this.languageServerFolder, 'Typeshed')]; - } - - protected getCacheFolderPath(): string | null { - const settings = this.configuration.getSettings(this.resource); - return settings.analysis.cacheFolderPath && settings.analysis.cacheFolderPath.length > 0 - ? settings.analysis.cacheFolderPath - : null; - } - - protected async onSettingsChangedHandler(e?: ConfigurationChangeEvent): Promise { - if (e && !e.affectsConfiguration('python', this.resource)) { - return; - } - this.onSettingsChanged(); - } - - @debounceSync(1000) - protected onSettingsChanged(): void { - this.notifyIfSettingsChanged().ignoreErrors(); - } - - @traceDecorators.verbose('Changes in python settings detected in analysis options') - protected async notifyIfSettingsChanged(): Promise { - const excludedFiles = this.getExcludedFiles(); - await this.notifyIfValuesHaveChanged(this.excludedFiles, excludedFiles); - - const typeshedPaths = this.getTypeshedPaths(); - await this.notifyIfValuesHaveChanged(this.typeshedPaths, typeshedPaths); - } - - protected async notifyIfValuesHaveChanged(oldArray: string[], newArray: string[]): Promise { - if (newArray.length !== oldArray.length) { - this.didChange.fire(); - return; - } - - for (let i = 0; i < oldArray.length; i += 1) { - if (oldArray[i] !== newArray[i]) { - this.didChange.fire(); - return; - } - } - } -} diff --git a/src/client/activation/languageServer/languageClientFactory.ts b/src/client/activation/languageServer/languageClientFactory.ts deleted file mode 100644 index e49b4b841b71..000000000000 --- a/src/client/activation/languageServer/languageClientFactory.ts +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable, unmanaged } from 'inversify'; -import * as path from 'path'; -import { LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient/node'; - -import { EXTENSION_ROOT_DIR, PYTHON_LANGUAGE } from '../../common/constants'; -import { IConfigurationService, Resource } from '../../common/types'; -import { IEnvironmentVariablesProvider } from '../../common/variables/types'; -import { IEnvironmentActivationService } from '../../interpreter/activation/types'; -import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { ILanguageClientFactory, ILanguageServerFolderService, IPlatformData } from '../types'; - -// tslint:disable:no-require-imports no-require-imports no-var-requires max-classes-per-file - -const dotNetCommand = 'dotnet'; -const languageClientName = 'Python Tools'; - -export class DotNetDownloadedLanguageClientFactory implements ILanguageClientFactory { - constructor( - private readonly platformData: IPlatformData, - private readonly languageServerFolderService: ILanguageServerFolderService - ) {} - - public async createLanguageClient( - resource: Resource, - _interpreter: PythonEnvironment | undefined, - clientOptions: LanguageClientOptions, - env?: NodeJS.ProcessEnv - ): Promise { - const languageServerFolder = await this.languageServerFolderService.getLanguageServerFolderName(resource); - const serverModule = path.join( - EXTENSION_ROOT_DIR, - languageServerFolder, - this.platformData.engineExecutableName - ); - const options = { stdio: 'pipe', env }; - const serverOptions: ServerOptions = { - run: { command: serverModule, args: [], options }, - debug: { command: serverModule, args: ['--debug'], options } - }; - const vscodeLanguageClient = require('vscode-languageclient/node') as typeof import('vscode-languageclient/node'); - return new vscodeLanguageClient.LanguageClient( - PYTHON_LANGUAGE, - languageClientName, - serverOptions, - clientOptions - ); - } -} - -export class DotNetSimpleLanguageClientFactory implements ILanguageClientFactory { - constructor( - private readonly platformData: IPlatformData, - private readonly languageServerFolderService: ILanguageServerFolderService - ) {} - - public async createLanguageClient( - resource: Resource, - _interpreter: PythonEnvironment | undefined, - clientOptions: LanguageClientOptions, - env?: NodeJS.ProcessEnv - ): Promise { - const languageServerFolder = await this.languageServerFolderService.getLanguageServerFolderName(resource); - const options = { stdio: 'pipe', env }; - const serverModule = path.join(EXTENSION_ROOT_DIR, languageServerFolder, this.platformData.engineDllName); - const serverOptions: ServerOptions = { - run: { command: dotNetCommand, args: [serverModule], options }, - debug: { command: dotNetCommand, args: [serverModule, '--debug'], options } - }; - const vscodeLanguageClient = require('vscode-languageclient/node') as typeof import('vscode-languageclient/node'); - return new vscodeLanguageClient.LanguageClient( - PYTHON_LANGUAGE, - languageClientName, - serverOptions, - clientOptions - ); - } -} - -@injectable() -export class DotNetLanguageClientFactory implements ILanguageClientFactory { - constructor( - @inject(IConfigurationService) private readonly configurationService: IConfigurationService, - @inject(IEnvironmentVariablesProvider) private readonly envVarsProvider: IEnvironmentVariablesProvider, - @inject(IEnvironmentActivationService) - private readonly environmentActivationService: IEnvironmentActivationService, - @inject(IPlatformData) private readonly platformData: IPlatformData, - @inject(ILanguageServerFolderService) - private readonly languageServerFolderService: ILanguageServerFolderService, - @unmanaged() private readonly downloadedFactory: ILanguageClientFactory, - @unmanaged() private readonly simpleFactory: ILanguageClientFactory - ) {} - - public async createLanguageClient( - resource: Resource, - interpreter: PythonEnvironment | undefined, - clientOptions: LanguageClientOptions - ): Promise { - const settings = this.configurationService.getSettings(resource); - let factory: ILanguageClientFactory; - if (this.platformData && this.languageServerFolderService) { - factory = settings.downloadLanguageServer - ? new DotNetDownloadedLanguageClientFactory(this.platformData, this.languageServerFolderService) - : new DotNetSimpleLanguageClientFactory(this.platformData, this.languageServerFolderService); - } else { - factory = settings.downloadLanguageServer ? this.downloadedFactory : this.simpleFactory; - } - const env = await this.getEnvVars(resource, interpreter); - return factory.createLanguageClient(resource, interpreter, clientOptions, env); - } - - private async getEnvVars( - resource: Resource, - interpreter: PythonEnvironment | undefined - ): Promise { - const envVars = await this.environmentActivationService.getActivatedEnvironmentVariables(resource, interpreter); - if (envVars && Object.keys(envVars).length > 0) { - return envVars; - } - return this.envVarsProvider.getEnvironmentVariables(resource); - } -} diff --git a/src/client/activation/languageServer/languageServerCompatibilityService.ts b/src/client/activation/languageServer/languageServerCompatibilityService.ts deleted file mode 100644 index 48c1e0729595..000000000000 --- a/src/client/activation/languageServer/languageServerCompatibilityService.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IDotNetCompatibilityService } from '../../common/dotnet/types'; -import { traceError } from '../../common/logger'; -import { sendTelemetryEvent } from '../../telemetry'; -import { EventName } from '../../telemetry/constants'; -import { ILanguageServerCompatibilityService } from '../types'; - -@injectable() -export class LanguageServerCompatibilityService implements ILanguageServerCompatibilityService { - constructor( - @inject(IDotNetCompatibilityService) private readonly dotnetCompatibility: IDotNetCompatibilityService - ) {} - public async isSupported(): Promise { - try { - const supported = await this.dotnetCompatibility.isSupported(); - sendTelemetryEvent(EventName.PYTHON_LANGUAGE_SERVER_PLATFORM_SUPPORTED, undefined, { - supported: supported - }); - return supported; - } catch (ex) { - traceError('Unable to determine whether LS is supported', ex); - sendTelemetryEvent(EventName.PYTHON_LANGUAGE_SERVER_PLATFORM_SUPPORTED, undefined, { - supported: false, - failureType: 'UnknownError' - }); - return false; - } - } -} diff --git a/src/client/activation/languageServer/languageServerExtension.ts b/src/client/activation/languageServer/languageServerExtension.ts deleted file mode 100644 index b2f69abadbee..000000000000 --- a/src/client/activation/languageServer/languageServerExtension.ts +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { Event, EventEmitter } from 'vscode'; -import { ICommandManager } from '../../common/application/types'; -import '../../common/extensions'; -import { IDisposable } from '../../common/types'; -import { ILanguageServerExtension } from '../types'; - -const loadExtensionCommand = 'python._loadLanguageServerExtension'; - -@injectable() -export class LanguageServerExtension implements ILanguageServerExtension { - public loadExtensionArgs?: {}; - protected readonly _invoked = new EventEmitter(); - private disposable?: IDisposable; - constructor(@inject(ICommandManager) private readonly commandManager: ICommandManager) {} - public dispose() { - if (this.disposable) { - this.disposable.dispose(); - } - } - public async register(): Promise { - if (this.disposable) { - return; - } - this.disposable = this.commandManager.registerCommand(loadExtensionCommand, (args) => { - this.loadExtensionArgs = args; - this._invoked.fire(); - }); - } - public get invoked(): Event { - return this._invoked.event; - } -} diff --git a/src/client/activation/languageServer/languageServerFolderService.ts b/src/client/activation/languageServer/languageServerFolderService.ts deleted file mode 100644 index bf541ab4690f..000000000000 --- a/src/client/activation/languageServer/languageServerFolderService.ts +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IApplicationEnvironment } from '../../common/application/types'; -import { IServiceContainer } from '../../ioc/types'; -import { LanguageServerFolderService } from '../common/languageServerFolderService'; -import { DotNetLanguageServerFolder } from '../types'; - -// Must match languageServerVersion* keys in package.json -export const DotNetLanguageServerMinVersionKey = 'languageServerVersion'; - -@injectable() -export class DotNetLanguageServerFolderService extends LanguageServerFolderService { - constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - super(serviceContainer, DotNetLanguageServerFolder); - } - - protected getMinimalLanguageServerVersion(): string { - let minVersion = '0.0.0'; - try { - const appEnv = this.serviceContainer.get(IApplicationEnvironment); - if (appEnv) { - minVersion = appEnv.packageJson[DotNetLanguageServerMinVersionKey] as string; - } - // tslint:disable-next-line: no-empty - } catch {} - return minVersion; - } -} diff --git a/src/client/activation/languageServer/languageServerPackageRepository.ts b/src/client/activation/languageServer/languageServerPackageRepository.ts deleted file mode 100644 index b9c8f3da797e..000000000000 --- a/src/client/activation/languageServer/languageServerPackageRepository.ts +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IServiceContainer } from '../../ioc/types'; -import { - BetaLanguageServerPackageRepository, - DailyLanguageServerPackageRepository, - StableLanguageServerPackageRepository -} from '../common/packageRepository'; - -const languageServerPackageName = 'python-language-server'; - -@injectable() -export class StableDotNetLanguageServerPackageRepository extends StableLanguageServerPackageRepository { - constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - super(serviceContainer, languageServerPackageName); - } -} - -@injectable() -export class BetaDotNetLanguageServerPackageRepository extends BetaLanguageServerPackageRepository { - constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - super(serviceContainer, languageServerPackageName); - } -} - -@injectable() -export class DailyDotNetLanguageServerPackageRepository extends DailyLanguageServerPackageRepository { - constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - super(serviceContainer, languageServerPackageName); - } -} diff --git a/src/client/activation/languageServer/languageServerPackageService.ts b/src/client/activation/languageServer/languageServerPackageService.ts deleted file mode 100644 index 9a912645fb4f..000000000000 --- a/src/client/activation/languageServer/languageServerPackageService.ts +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IApplicationEnvironment } from '../../common/application/types'; -import { IPlatformService } from '../../common/platform/types'; -import { OSType } from '../../common/utils/platform'; -import { IServiceContainer } from '../../ioc/types'; -import { LanguageServerPackageService } from '../common/languageServerPackageService'; -import { PlatformName } from '../types'; - -const downloadBaseFileName = 'Python-Language-Server'; -export const PackageNames = { - [PlatformName.Windows32Bit]: `${downloadBaseFileName}-${PlatformName.Windows32Bit}`, - [PlatformName.Windows64Bit]: `${downloadBaseFileName}-${PlatformName.Windows64Bit}`, - [PlatformName.Linux64Bit]: `${downloadBaseFileName}-${PlatformName.Linux64Bit}`, - [PlatformName.Mac64Bit]: `${downloadBaseFileName}-${PlatformName.Mac64Bit}` -}; - -@injectable() -export class DotNetLanguageServerPackageService extends LanguageServerPackageService { - constructor( - @inject(IServiceContainer) serviceContainer: IServiceContainer, - @inject(IApplicationEnvironment) appEnv: IApplicationEnvironment, - @inject(IPlatformService) platform: IPlatformService - ) { - super(serviceContainer, appEnv, platform); - } - - public getNugetPackageName(): string { - switch (this.platform.osType) { - case OSType.Windows: - return PackageNames[this.platform.is64bit ? PlatformName.Windows64Bit : PlatformName.Windows32Bit]; - case OSType.OSX: - return PackageNames[PlatformName.Mac64Bit]; - default: - return PackageNames[PlatformName.Linux64Bit]; - } - } -} diff --git a/src/client/activation/languageServer/languageServerProxy.ts b/src/client/activation/languageServer/languageServerProxy.ts deleted file mode 100644 index 4b0ff833430c..000000000000 --- a/src/client/activation/languageServer/languageServerProxy.ts +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import '../../common/extensions'; - -import { inject, injectable } from 'inversify'; -import { Disposable, LanguageClient, LanguageClientOptions } from 'vscode-languageclient/node'; - -import { traceDecorators, traceError } from '../../common/logger'; -import { IConfigurationService, Resource } from '../../common/types'; -import { createDeferred, Deferred, sleep } from '../../common/utils/async'; -import { swallowExceptions } from '../../common/utils/decorators'; -import { noop } from '../../common/utils/misc'; -import { LanguageServerSymbolProvider } from '../../providers/symbolProvider'; -import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; -import { EventName } from '../../telemetry/constants'; -import { ITestManagementService } from '../../testing/types'; -import { ProgressReporting } from '../progress'; -import { ILanguageClientFactory, ILanguageServerProxy } from '../types'; - -@injectable() -export class DotNetLanguageServerProxy implements ILanguageServerProxy { - public languageClient: LanguageClient | undefined; - private startupCompleted: Deferred; - private readonly disposables: Disposable[] = []; - private extensionLoadedArgs = new Set<{}>(); - private disposed: boolean = false; - - constructor( - @inject(ILanguageClientFactory) private readonly factory: ILanguageClientFactory, - @inject(ITestManagementService) private readonly testManager: ITestManagementService, - @inject(IConfigurationService) private readonly configurationService: IConfigurationService - ) { - this.startupCompleted = createDeferred(); - } - @traceDecorators.verbose('Stopping language server') - public dispose() { - if (this.languageClient) { - // Do not await on this. - this.languageClient.stop().then(noop, (ex) => traceError('Stopping language client failed', ex)); - this.languageClient = undefined; - } - while (this.disposables.length > 0) { - const d = this.disposables.shift()!; - d.dispose(); - } - if (this.startupCompleted.completed) { - this.startupCompleted.reject(new Error('Disposed language server')); - this.startupCompleted = createDeferred(); - } - this.disposed = true; - } - - @traceDecorators.error('Failed to start language server') - @captureTelemetry(EventName.PYTHON_LANGUAGE_SERVER_ENABLED, undefined, true) - public async start( - resource: Resource, - interpreter: PythonEnvironment | undefined, - options: LanguageClientOptions - ): Promise { - if (!this.languageClient) { - this.languageClient = await this.factory.createLanguageClient(resource, interpreter, options); - this.disposables.push(this.languageClient!.start()); - await this.serverReady(); - if (this.disposed) { - // Check if it got disposed in the interim. - return; - } - const progressReporting = new ProgressReporting(this.languageClient!); - this.disposables.push(progressReporting); - - const settings = this.configurationService.getSettings(resource); - if (settings.downloadLanguageServer) { - this.languageClient.onTelemetry((telemetryEvent) => { - const eventName = telemetryEvent.EventName || EventName.PYTHON_LANGUAGE_SERVER_TELEMETRY; - const formattedProperties = { - ...telemetryEvent.Properties, - // Replace all slashes in the method name so it doesn't get scrubbed by vscode-extension-telemetry. - method: telemetryEvent.Properties.method?.replace(/\//g, '.') - }; - sendTelemetryEvent(eventName, telemetryEvent.Measurements, formattedProperties); - }); - } - await this.registerTestServices(); - } else { - await this.startupCompleted.promise; - } - } - @traceDecorators.error('Failed to load language server extension') - public loadExtension(args?: {}) { - if (this.extensionLoadedArgs.has(args || '')) { - return; - } - this.extensionLoadedArgs.add(args || ''); - this.startupCompleted.promise - .then(() => - this.languageClient!.sendRequest('python/loadExtension', args).then(noop, (ex) => - traceError('Request python/loadExtension failed', ex) - ) - ) - .ignoreErrors(); - } - @captureTelemetry(EventName.PYTHON_LANGUAGE_SERVER_READY, undefined, true) - protected async serverReady(): Promise { - // languageClient can be disposed in awaits. - while (this.languageClient && !this.languageClient.initializeResult) { - await sleep(100); - } - if (this.languageClient) { - await this.languageClient.onReady(); - } - this.startupCompleted.resolve(); - } - @swallowExceptions('Activating Unit Tests Manager for Microsoft Python Language Server') - protected async registerTestServices() { - if (!this.languageClient) { - throw new Error('languageClient not initialized'); - } - await this.testManager.activate(new LanguageServerSymbolProvider(this.languageClient!)); - } -} diff --git a/src/client/activation/languageServer/manager.ts b/src/client/activation/languageServer/manager.ts deleted file mode 100644 index 91c01769fc53..000000000000 --- a/src/client/activation/languageServer/manager.ts +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import '../../common/extensions'; - -import { inject, injectable, named } from 'inversify'; - -import { ICommandManager } from '../../common/application/types'; -import { traceDecorators } from '../../common/logger'; -import { IConfigurationService, IDisposable, IExperimentsManager, Resource } from '../../common/types'; -import { debounceSync } from '../../common/utils/decorators'; -import { IServiceContainer } from '../../ioc/types'; -import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { captureTelemetry } from '../../telemetry'; -import { EventName } from '../../telemetry/constants'; -import { Commands } from '../commands'; -import { LanguageClientMiddleware } from '../languageClientMiddleware'; -import { - ILanguageServerAnalysisOptions, - ILanguageServerExtension, - ILanguageServerFolderService, - ILanguageServerManager, - ILanguageServerProxy, - LanguageServerType -} from '../types'; - -@injectable() -export class DotNetLanguageServerManager implements ILanguageServerManager { - private languageServerProxy?: ILanguageServerProxy; - private resource!: Resource; - private interpreter: PythonEnvironment | undefined; - private middleware: LanguageClientMiddleware | undefined; - private disposables: IDisposable[] = []; - private connected: boolean = false; - private lsVersion: string | undefined; - - constructor( - @inject(IServiceContainer) private readonly serviceContainer: IServiceContainer, - @inject(ILanguageServerAnalysisOptions) - @named(LanguageServerType.Microsoft) - private readonly analysisOptions: ILanguageServerAnalysisOptions, - @inject(ILanguageServerExtension) private readonly lsExtension: ILanguageServerExtension, - @inject(ILanguageServerFolderService) private readonly folderService: ILanguageServerFolderService, - @inject(IExperimentsManager) private readonly experimentsManager: IExperimentsManager, - @inject(IConfigurationService) private readonly configService: IConfigurationService, - @inject(ICommandManager) commandManager: ICommandManager - ) { - this.disposables.push( - commandManager.registerCommand(Commands.RestartLS, () => { - this.restartLanguageServer().ignoreErrors(); - }) - ); - } - - private static versionTelemetryProps(instance: DotNetLanguageServerManager) { - return { - lsVersion: instance.lsVersion - }; - } - - public dispose() { - if (this.languageProxy) { - this.languageProxy.dispose(); - } - this.disposables.forEach((d) => d.dispose()); - } - - public get languageProxy() { - return this.languageServerProxy; - } - @traceDecorators.error('Failed to start language server') - public async start(resource: Resource, interpreter: PythonEnvironment | undefined): Promise { - if (this.languageProxy) { - throw new Error('Language server already started'); - } - this.registerCommandHandler(); - this.resource = resource; - this.interpreter = interpreter; - this.analysisOptions.onDidChange(this.restartLanguageServerDebounced, this, this.disposables); - - const versionPair = await this.folderService.getCurrentLanguageServerDirectory(); - this.lsVersion = versionPair?.version.format(); - - await this.analysisOptions.initialize(resource, interpreter); - await this.startLanguageServer(); - } - public connect() { - this.connected = true; - this.middleware?.connect(); - } - public disconnect() { - this.connected = false; - this.middleware?.disconnect(); - } - protected registerCommandHandler() { - this.lsExtension.invoked(this.loadExtensionIfNecessary, this, this.disposables); - } - protected loadExtensionIfNecessary() { - if (this.languageProxy && this.lsExtension.loadExtensionArgs) { - this.languageProxy.loadExtension(this.lsExtension.loadExtensionArgs); - } - } - @debounceSync(1000) - protected restartLanguageServerDebounced(): void { - this.restartLanguageServer().ignoreErrors(); - } - @traceDecorators.error('Failed to restart language server') - @traceDecorators.verbose('Restarting language server') - protected async restartLanguageServer(): Promise { - if (this.languageProxy) { - this.languageProxy.dispose(); - } - await this.startLanguageServer(); - } - @captureTelemetry( - EventName.PYTHON_LANGUAGE_SERVER_STARTUP, - undefined, - true, - undefined, - DotNetLanguageServerManager.versionTelemetryProps - ) - @traceDecorators.verbose('Starting language server') - protected async startLanguageServer(): Promise { - this.languageServerProxy = this.serviceContainer.get(ILanguageServerProxy); - - const options = await this.analysisOptions!.getAnalysisOptions(); - options.middleware = this.middleware = new LanguageClientMiddleware( - this.experimentsManager, - this.configService, - LanguageServerType.Microsoft, - this.lsVersion - ); - - // Make sure the middleware is connected if we restart and we we're already connected. - if (this.connected) { - this.middleware.connect(); - } - - // Then use this middleware to start a new language client. - await this.languageServerProxy.start(this.resource, this.interpreter, options); - this.loadExtensionIfNecessary(); - } -} diff --git a/src/client/activation/languageServer/outputChannel.ts b/src/client/activation/languageServer/outputChannel.ts deleted file mode 100644 index 2d4815b3af97..000000000000 --- a/src/client/activation/languageServer/outputChannel.ts +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IApplicationShell, ICommandManager } from '../../common/application/types'; -import '../../common/extensions'; -import { IOutputChannel } from '../../common/types'; -import { OutputChannelNames } from '../../common/utils/localize'; -import { ILanguageServerOutputChannel } from '../types'; - -@injectable() -export class LanguageServerOutputChannel implements ILanguageServerOutputChannel { - public output: IOutputChannel | undefined; - private registered = false; - constructor( - @inject(IApplicationShell) private readonly appShell: IApplicationShell, - @inject(ICommandManager) private readonly commandManager: ICommandManager - ) {} - - public get channel() { - if (!this.output) { - this.output = this.appShell.createOutputChannel(OutputChannelNames.languageServer()); - this.registerCommand().ignoreErrors(); - } - return this.output; - } - private async registerCommand() { - if (this.registered) { - return; - } - this.registered = true; - // This controls the visibility of the command used to display the LS Output panel. - // We don't want to display it when Jedi is used instead of LS. - await this.commandManager.executeCommand('setContext', 'python.hasLanguageServerOutputChannel', true); - this.commandManager.registerCommand('python.viewLanguageServerOutput', () => this.output!.show(true)); - } -} diff --git a/src/client/activation/languageServer/platformData.ts b/src/client/activation/languageServer/platformData.ts deleted file mode 100644 index f616be1fc764..000000000000 --- a/src/client/activation/languageServer/platformData.ts +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IPlatformService } from '../../common/platform/types'; -import { IPlatformData } from '../types'; - -export enum PlatformName { - Windows32Bit = 'win-x86', - Windows64Bit = 'win-x64', - Mac64Bit = 'osx-x64', - Linux64Bit = 'linux-x64' -} - -export enum PlatformLSExecutables { - Windows = 'Microsoft.Python.LanguageServer.exe', - MacOS = 'Microsoft.Python.LanguageServer', - Linux = 'Microsoft.Python.LanguageServer' -} - -@injectable() -export class PlatformData implements IPlatformData { - constructor(@inject(IPlatformService) private readonly platform: IPlatformService) {} - public get platformName(): PlatformName { - if (this.platform.isWindows) { - return this.platform.is64bit ? PlatformName.Windows64Bit : PlatformName.Windows32Bit; - } - if (this.platform.isMac) { - return PlatformName.Mac64Bit; - } - if (this.platform.isLinux) { - if (!this.platform.is64bit) { - throw new Error('Microsoft Python Language Server does not support 32-bit Linux.'); - } - return PlatformName.Linux64Bit; - } - throw new Error('Unknown OS platform.'); - } - - public get engineDllName(): string { - return 'Microsoft.Python.LanguageServer.dll'; - } - - public get engineExecutableName(): string { - if (this.platform.isWindows) { - return PlatformLSExecutables.Windows; - } else if (this.platform.isLinux) { - return PlatformLSExecutables.Linux; - } else if (this.platform.isMac) { - return PlatformLSExecutables.MacOS; - } else { - return 'unknown-platform'; - } - } -} diff --git a/src/client/activation/node/activator.ts b/src/client/activation/node/activator.ts deleted file mode 100644 index fc438bece25b..000000000000 --- a/src/client/activation/node/activator.ts +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -import { CancellationToken, CompletionItem, ProviderResult } from 'vscode'; -// tslint:disable-next-line: import-name -import ProtocolCompletionItem from 'vscode-languageclient/lib/common/protocolCompletionItem'; -import { CompletionResolveRequest } from 'vscode-languageclient/node'; -import { IApplicationEnvironment, IApplicationShell, IWorkspaceService } from '../../common/application/types'; -import { PYLANCE_EXTENSION_ID } from '../../common/constants'; -import { IFileSystem } from '../../common/platform/types'; -import { IConfigurationService, IExtensions, Resource } from '../../common/types'; -import { Pylance } from '../../common/utils/localize'; -import { LanguageServerActivatorBase } from '../common/activatorBase'; -import { promptForPylanceInstall } from '../common/languageServerChangeHandler'; -import { ILanguageServerManager } from '../types'; - -/** - * Starts Pylance language server manager. - * - * @export - * @class NodeLanguageServerActivator - * @implements {ILanguageServerActivator} - * @extends {LanguageServerActivatorBase} - */ -@injectable() -export class NodeLanguageServerActivator extends LanguageServerActivatorBase { - constructor( - @inject(ILanguageServerManager) manager: ILanguageServerManager, - @inject(IWorkspaceService) workspace: IWorkspaceService, - @inject(IFileSystem) fs: IFileSystem, - @inject(IConfigurationService) configurationService: IConfigurationService, - @inject(IExtensions) private readonly extensions: IExtensions, - @inject(IApplicationShell) private readonly appShell: IApplicationShell, - @inject(IApplicationEnvironment) private readonly appEnv: IApplicationEnvironment - ) { - super(manager, workspace, fs, configurationService); - } - - public async ensureLanguageServerIsAvailable(resource: Resource): Promise { - const settings = this.configurationService.getSettings(resource); - if (settings.downloadLanguageServer === false) { - // Development mode. - return; - } - if (!this.extensions.getExtension(PYLANCE_EXTENSION_ID)) { - // Pylance is not yet installed. Throw will cause activator to use Jedi - // temporarily. Language server installation tracker will prompt for window - // reload when Pylance becomes available. - await promptForPylanceInstall(this.appShell, this.appEnv); - throw new Error(Pylance.pylanceNotInstalledMessage()); - } - } - - public resolveCompletionItem(item: CompletionItem, token: CancellationToken): ProviderResult { - return this.handleResolveCompletionItem(item, token); - } - - private async handleResolveCompletionItem( - item: CompletionItem, - token: CancellationToken - ): Promise { - const languageClient = this.getLanguageClient(); - - if (languageClient) { - // Turn our item into a ProtocolCompletionItem before we convert it. This preserves the .data - // attribute that it has and is needed to match on the language server side. - const protoItem: ProtocolCompletionItem = new ProtocolCompletionItem(item.label); - Object.assign(protoItem, item); - - const args = languageClient.code2ProtocolConverter.asCompletionItem(protoItem); - const result = await languageClient.sendRequest(CompletionResolveRequest.type, args, token); - - if (result) { - return languageClient.protocol2CodeConverter.asCompletionItem(result); - } - } - } -} diff --git a/src/client/activation/node/analysisOptions.ts b/src/client/activation/node/analysisOptions.ts index 487ecc7e2208..71295649c25a 100644 --- a/src/client/activation/node/analysisOptions.ts +++ b/src/client/activation/node/analysisOptions.ts @@ -1,17 +1,27 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { inject, injectable } from 'inversify'; -import { IEnvironmentVariablesProvider } from '../../common/variables/types'; +import { LanguageClientOptions } from 'vscode-languageclient'; +import { IWorkspaceService } from '../../common/application/types'; + import { LanguageServerAnalysisOptionsBase } from '../common/analysisOptions'; import { ILanguageServerOutputChannel } from '../types'; -@injectable() export class NodeLanguageServerAnalysisOptions extends LanguageServerAnalysisOptionsBase { - constructor( - @inject(IEnvironmentVariablesProvider) envVarsProvider: IEnvironmentVariablesProvider, - @inject(ILanguageServerOutputChannel) lsOutputChannel: ILanguageServerOutputChannel - ) { - super(envVarsProvider, lsOutputChannel); + // eslint-disable-next-line @typescript-eslint/no-useless-constructor + constructor(lsOutputChannel: ILanguageServerOutputChannel, workspace: IWorkspaceService) { + super(lsOutputChannel, workspace); + } + + protected getConfigSectionsToSynchronize(): string[] { + return [...super.getConfigSectionsToSynchronize(), 'jupyter.runStartupCommands']; + } + + // eslint-disable-next-line class-methods-use-this + protected async getInitializationOptions(): Promise { + return ({ + experimentationSupport: true, + trustedWorkspaceSupport: true, + } as unknown) as LanguageClientOptions; } } diff --git a/src/client/activation/node/languageClientFactory.ts b/src/client/activation/node/languageClientFactory.ts index 47b88814b227..9543f265468f 100644 --- a/src/client/activation/node/languageClientFactory.ts +++ b/src/client/activation/node/languageClientFactory.ts @@ -1,43 +1,34 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { inject, injectable } from 'inversify'; import * as path from 'path'; import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient/node'; -import { EXTENSION_ROOT_DIR, PYTHON_LANGUAGE } from '../../common/constants'; +import { PYLANCE_EXTENSION_ID, PYTHON_LANGUAGE } from '../../common/constants'; import { IFileSystem } from '../../common/platform/types'; -import { Resource } from '../../common/types'; +import { IExtensions, Resource } from '../../common/types'; import { PythonEnvironment } from '../../pythonEnvironments/info'; import { FileBasedCancellationStrategy } from '../common/cancellationUtils'; -import { ILanguageClientFactory, ILanguageServerFolderService } from '../types'; +import { ILanguageClientFactory } from '../types'; -// tslint:disable:no-require-imports no-require-imports no-var-requires max-classes-per-file -const languageClientName = 'Python Tools'; +export const PYLANCE_NAME = 'Pylance'; -@injectable() export class NodeLanguageClientFactory implements ILanguageClientFactory { - constructor( - @inject(IFileSystem) private readonly fs: IFileSystem, - @inject(ILanguageServerFolderService) private readonly languageServerFolderService: ILanguageServerFolderService - ) {} + constructor(private readonly fs: IFileSystem, private readonly extensions: IExtensions) {} public async createLanguageClient( - resource: Resource, + _resource: Resource, _interpreter: PythonEnvironment | undefined, - clientOptions: LanguageClientOptions + clientOptions: LanguageClientOptions, ): Promise { // this must exist for node language client const commandArgs = (clientOptions.connectionOptions ?.cancellationStrategy as FileBasedCancellationStrategy).getCommandLineArguments(); - const folderName = await this.languageServerFolderService.getLanguageServerFolderName(resource); - const languageServerFolder = path.isAbsolute(folderName) - ? folderName - : path.join(EXTENSION_ROOT_DIR, folderName); - - const bundlePath = path.join(languageServerFolder, 'server.bundle.js'); - const nonBundlePath = path.join(languageServerFolder, 'server.js'); + const extension = this.extensions.getExtension(PYLANCE_EXTENSION_ID); + const languageServerFolder = extension ? extension.extensionPath : ''; + const bundlePath = path.join(languageServerFolder, 'dist', 'server.bundle.js'); + const nonBundlePath = path.join(languageServerFolder, 'dist', 'server.js'); const modulePath = (await this.fs.fileExists(nonBundlePath)) ? nonBundlePath : bundlePath; const debugOptions = { execArgv: ['--nolazy', '--inspect=6600'] }; @@ -46,7 +37,7 @@ export class NodeLanguageClientFactory implements ILanguageClientFactory { run: { module: bundlePath, transport: TransportKind.ipc, - args: commandArgs + args: commandArgs, }, // In debug mode, use the non-bundled code if it's present. The production // build includes only the bundled package, so we don't want to crash if @@ -55,16 +46,10 @@ export class NodeLanguageClientFactory implements ILanguageClientFactory { module: modulePath, transport: TransportKind.ipc, options: debugOptions, - args: commandArgs - } + args: commandArgs, + }, }; - const vscodeLanguageClient = require('vscode-languageclient/node') as typeof import('vscode-languageclient/node'); - return new vscodeLanguageClient.LanguageClient( - PYTHON_LANGUAGE, - languageClientName, - serverOptions, - clientOptions - ); + return new LanguageClient(PYTHON_LANGUAGE, PYLANCE_NAME, serverOptions, clientOptions); } } diff --git a/src/client/activation/node/languageClientMiddleware.ts b/src/client/activation/node/languageClientMiddleware.ts new file mode 100644 index 000000000000..dfd65f1bb418 --- /dev/null +++ b/src/client/activation/node/languageClientMiddleware.ts @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { IServiceContainer } from '../../ioc/types'; +import { LanguageClientMiddleware } from '../languageClientMiddleware'; + +import { LanguageServerType } from '../types'; + +export class NodeLanguageClientMiddleware extends LanguageClientMiddleware { + public constructor(serviceContainer: IServiceContainer, serverVersion?: string) { + super(serviceContainer, LanguageServerType.Node, serverVersion); + } +} diff --git a/src/client/activation/node/languageServerFolderService.ts b/src/client/activation/node/languageServerFolderService.ts deleted file mode 100644 index 0ccb89b28fc3..000000000000 --- a/src/client/activation/node/languageServerFolderService.ts +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as assert from 'assert'; -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { SemVer } from 'semver'; -import { IWorkspaceService } from '../../common/application/types'; -import { PYLANCE_EXTENSION_ID } from '../../common/constants'; -import { NugetPackage } from '../../common/nuget/types'; -import { IConfigurationService, IExtensions, Resource } from '../../common/types'; -import { IServiceContainer } from '../../ioc/types'; -import { LanguageServerFolderService } from '../common/languageServerFolderService'; -import { FolderVersionPair, ILanguageServerFolderService, NodeLanguageServerFolder } from '../types'; - -class FallbackNodeLanguageServerFolderService extends LanguageServerFolderService { - constructor(serviceContainer: IServiceContainer) { - super(serviceContainer, NodeLanguageServerFolder); - } - - protected getMinimalLanguageServerVersion(): string { - return '0.0.0'; - } -} - -// Exported for testing. -export interface ILanguageServerFolder { - path: string; - version: string; // SemVer, in string form to avoid cross-extension type issues. -} - -// Exported for testing. -export interface ILSExtensionApi { - languageServerFolder?(): Promise; -} - -@injectable() -export class NodeLanguageServerFolderService implements ILanguageServerFolderService { - private readonly fallback: FallbackNodeLanguageServerFolderService; - - constructor( - @inject(IServiceContainer) serviceContainer: IServiceContainer, - @inject(IConfigurationService) private configService: IConfigurationService, - @inject(IWorkspaceService) private workspaceService: IWorkspaceService, - @inject(IExtensions) readonly extensions: IExtensions - ) { - this.fallback = new FallbackNodeLanguageServerFolderService(serviceContainer); - } - - public async skipDownload(): Promise { - return (await this.lsExtensionApi()) !== undefined; - } - - public async getLanguageServerFolderName(resource: Resource): Promise { - const lsf = await this.languageServerFolder(); - if (lsf) { - assert.ok(path.isAbsolute(lsf.path)); - return lsf.path; - } - return this.fallback.getLanguageServerFolderName(resource); - } - - public async getLatestLanguageServerVersion(resource: Resource): Promise { - if (await this.lsExtensionApi()) { - return undefined; - } - return this.fallback.getLatestLanguageServerVersion(resource); - } - - public async getCurrentLanguageServerDirectory(): Promise { - const lsf = await this.languageServerFolder(); - if (lsf) { - assert.ok(path.isAbsolute(lsf.path)); - return { - path: lsf.path, - version: new SemVer(lsf.version) - }; - } - return this.fallback.getCurrentLanguageServerDirectory(); - } - - protected async languageServerFolder(): Promise { - const extension = await this.lsExtensionApi(); - if (!extension?.languageServerFolder) { - return undefined; - } - return extension.languageServerFolder(); - } - - private async lsExtensionApi(): Promise { - // downloadLanguageServer is a bit of a misnomer; if false then this indicates that a local - // development copy should be run instead of a "real" build, telemetry discarded, etc. - // So, we require it to be true, even though in the pinned case no real download happens. - if ( - !this.configService.getSettings().downloadLanguageServer || - this.workspaceService.getConfiguration('python').get('packageName') - ) { - return undefined; - } - - const extension = this.extensions.getExtension(PYLANCE_EXTENSION_ID); - if (!extension) { - return undefined; - } - - if (!extension.isActive) { - return extension.activate(); - } - - return extension.exports; - } -} diff --git a/src/client/activation/node/languageServerPackageRepository.ts b/src/client/activation/node/languageServerPackageRepository.ts deleted file mode 100644 index 39ab7ac86a78..000000000000 --- a/src/client/activation/node/languageServerPackageRepository.ts +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IWorkspaceService } from '../../common/application/types'; -import { IServiceContainer } from '../../ioc/types'; -import { - BetaLanguageServerPackageRepository, - DailyLanguageServerPackageRepository, - StableLanguageServerPackageRepository -} from '../common/packageRepository'; - -@injectable() -export class StableNodeLanguageServerPackageRepository extends StableLanguageServerPackageRepository { - constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - const config = serviceContainer.get(IWorkspaceService).getConfiguration('python'); - const packageName = config.get('blobName') || ''; - super(serviceContainer, packageName); - } -} - -@injectable() -export class BetaNodeLanguageServerPackageRepository extends BetaLanguageServerPackageRepository { - constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - const config = serviceContainer.get(IWorkspaceService).getConfiguration('python'); - const packageName = config.get('blobName') || ''; - super(serviceContainer, packageName); - } -} - -@injectable() -export class DailyNodeLanguageServerPackageRepository extends DailyLanguageServerPackageRepository { - constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - const config = serviceContainer.get(IWorkspaceService).getConfiguration('python'); - const packageName = config.get('blobName') || ''; - super(serviceContainer, packageName); - } -} diff --git a/src/client/activation/node/languageServerPackageService.ts b/src/client/activation/node/languageServerPackageService.ts deleted file mode 100644 index feacaba70a8a..000000000000 --- a/src/client/activation/node/languageServerPackageService.ts +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IApplicationEnvironment, IWorkspaceService } from '../../common/application/types'; -import { IPlatformService } from '../../common/platform/types'; -import { IServiceContainer } from '../../ioc/types'; -import { LanguageServerPackageService } from '../common/languageServerPackageService'; - -@injectable() -export class NodeLanguageServerPackageService extends LanguageServerPackageService { - constructor( - @inject(IServiceContainer) serviceContainer: IServiceContainer, - @inject(IApplicationEnvironment) appEnv: IApplicationEnvironment, - @inject(IPlatformService) platform: IPlatformService - ) { - super(serviceContainer, appEnv, platform); - } - - public getNugetPackageName(): string { - const config = this.serviceContainer.get(IWorkspaceService).getConfiguration('python'); - return config.get('packageName') || ''; - } -} diff --git a/src/client/activation/node/languageServerProxy.ts b/src/client/activation/node/languageServerProxy.ts index 610cc58b2001..45d1d1a17fee 100644 --- a/src/client/activation/node/languageServerProxy.ts +++ b/src/client/activation/node/languageServerProxy.ts @@ -2,169 +2,235 @@ // Licensed under the MIT License. import '../../common/extensions'; -import { inject, injectable } from 'inversify'; import { DidChangeConfigurationNotification, Disposable, LanguageClient, - LanguageClientOptions + LanguageClientOptions, } from 'vscode-languageclient/node'; -import { DeprecatePythonPath } from '../../common/experiments/groups'; -import { traceDecorators, traceError } from '../../common/logger'; -import { IConfigurationService, IExperimentsManager, IInterpreterPathService, Resource } from '../../common/types'; -import { createDeferred, Deferred, sleep } from '../../common/utils/async'; -import { swallowExceptions } from '../../common/utils/decorators'; -import { noop } from '../../common/utils/misc'; -import { LanguageServerSymbolProvider } from '../../providers/symbolProvider'; +import { Extension } from 'vscode'; +import { IExperimentService, IExtensions, IInterpreterPathService, Resource } from '../../common/types'; +import { IEnvironmentVariablesProvider } from '../../common/variables/types'; import { PythonEnvironment } from '../../pythonEnvironments/info'; import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; -import { ITestManagementService } from '../../testing/types'; import { FileBasedCancellationStrategy } from '../common/cancellationUtils'; import { ProgressReporting } from '../progress'; -import { ILanguageClientFactory, ILanguageServerFolderService, ILanguageServerProxy } from '../types'; +import { ILanguageClientFactory, ILanguageServerProxy } from '../types'; +import { traceDecoratorError, traceDecoratorVerbose, traceError } from '../../logging'; +import { IWorkspaceService } from '../../common/application/types'; +import { PYLANCE_EXTENSION_ID } from '../../common/constants'; +import { PylanceApi } from './pylanceApi'; + +// eslint-disable-next-line @typescript-eslint/no-namespace +namespace InExperiment { + export const Method = 'python/inExperiment'; + + export interface IRequest { + experimentName: string; + } + + export interface IResponse { + inExperiment: boolean; + } +} + +// eslint-disable-next-line @typescript-eslint/no-namespace +namespace GetExperimentValue { + export const Method = 'python/getExperimentValue'; + + export interface IRequest { + experimentName: string; + } + + export interface IResponse { + value: T | undefined; + } +} -@injectable() export class NodeLanguageServerProxy implements ILanguageServerProxy { public languageClient: LanguageClient | undefined; - private startupCompleted: Deferred; + private cancellationStrategy: FileBasedCancellationStrategy | undefined; + private readonly disposables: Disposable[] = []; - private disposed: boolean = false; + private lsVersion: string | undefined; + private pylanceApi: PylanceApi | undefined; + constructor( - @inject(ILanguageClientFactory) private readonly factory: ILanguageClientFactory, - @inject(ITestManagementService) private readonly testManager: ITestManagementService, - @inject(IConfigurationService) private readonly configurationService: IConfigurationService, - @inject(ILanguageServerFolderService) private readonly folderService: ILanguageServerFolderService, - @inject(IExperimentsManager) private readonly experiments: IExperimentsManager, - @inject(IInterpreterPathService) private readonly interpreterPathService: IInterpreterPathService - ) { - this.startupCompleted = createDeferred(); - } + private readonly factory: ILanguageClientFactory, + private readonly experimentService: IExperimentService, + private readonly interpreterPathService: IInterpreterPathService, + private readonly environmentService: IEnvironmentVariablesProvider, + private readonly workspace: IWorkspaceService, + private readonly extensions: IExtensions, + ) {} private static versionTelemetryProps(instance: NodeLanguageServerProxy) { return { - lsVersion: instance.lsVersion + lsVersion: instance.lsVersion, }; } - @traceDecorators.verbose('Stopping language server') - public dispose() { - if (this.languageClient) { - // Do not await on this. - this.languageClient.stop().then(noop, (ex) => traceError('Stopping language client failed', ex)); - this.languageClient = undefined; - } - if (this.cancellationStrategy) { - this.cancellationStrategy.dispose(); - this.cancellationStrategy = undefined; - } - while (this.disposables.length > 0) { - const d = this.disposables.shift()!; - d.dispose(); - } - if (this.startupCompleted.completed) { - this.startupCompleted.reject(new Error('Disposed language server')); - this.startupCompleted = createDeferred(); - } - this.disposed = true; + @traceDecoratorVerbose('Disposing language server') + public dispose(): void { + this.stop().ignoreErrors(); } - @traceDecorators.error('Failed to start language server') + @traceDecoratorError('Failed to start language server') @captureTelemetry( EventName.LANGUAGE_SERVER_ENABLED, undefined, true, undefined, - NodeLanguageServerProxy.versionTelemetryProps + NodeLanguageServerProxy.versionTelemetryProps, ) public async start( resource: Resource, interpreter: PythonEnvironment | undefined, - options: LanguageClientOptions + options: LanguageClientOptions, ): Promise { - if (!this.languageClient) { - const directory = await this.folderService.getCurrentLanguageServerDirectory(); - this.lsVersion = directory?.version.format(); - - this.cancellationStrategy = new FileBasedCancellationStrategy(); - options.connectionOptions = { cancellationStrategy: this.cancellationStrategy }; - - this.languageClient = await this.factory.createLanguageClient(resource, interpreter, options); - this.disposables.push(this.languageClient!.start()); - await this.serverReady(); - if (this.disposed) { - // Check if it got disposed in the interim. - return; - } - const progressReporting = new ProgressReporting(this.languageClient!); - this.disposables.push(progressReporting); - - if (this.experiments.inExperiment(DeprecatePythonPath.experiment)) { - this.disposables.push( - this.interpreterPathService.onDidChange(() => { - // Manually send didChangeConfiguration in order to get the server to requery - // the workspace configurations (to then pick up pythonPath set in the middleware). - // This is needed as interpreter changes via the interpreter path service happen - // outside of VS Code's settings (which would mean VS Code sends the config updates itself). - this.languageClient!.sendNotification(DidChangeConfigurationNotification.type, { - settings: null - }); - }) - ); - } + const extension = await this.getPylanceExtension(); + this.lsVersion = extension?.packageJSON.version || '0'; - const settings = this.configurationService.getSettings(resource); - if (settings.downloadLanguageServer) { - this.languageClient.onTelemetry((telemetryEvent) => { - const eventName = telemetryEvent.EventName || EventName.LANGUAGE_SERVER_TELEMETRY; - const formattedProperties = { - ...telemetryEvent.Properties, - // Replace all slashes in the method name so it doesn't get scrubbed by vscode-extension-telemetry. - method: telemetryEvent.Properties.method?.replace(/\//g, '.') - }; - sendTelemetryEvent( - eventName, - telemetryEvent.Measurements, - formattedProperties, - telemetryEvent.Exception - ); - }); + const api = extension?.exports; + if (api && api.client && api.client.isEnabled()) { + this.pylanceApi = api; + await api.client.start(); + return; + } + + this.cancellationStrategy = new FileBasedCancellationStrategy(); + options.connectionOptions = { cancellationStrategy: this.cancellationStrategy }; + + const client = await this.factory.createLanguageClient(resource, interpreter, options); + this.registerHandlers(client, resource); + + this.disposables.push( + this.workspace.onDidGrantWorkspaceTrust(() => { + client.sendNotification('python/workspaceTrusted', { isTrusted: true }); + }), + ); + + await client.start(); + + this.languageClient = client; + } + + @traceDecoratorVerbose('Disposing language server') + public async stop(): Promise { + if (this.pylanceApi) { + const api = this.pylanceApi; + this.pylanceApi = undefined; + await api.client!.stop(); + } + + while (this.disposables.length > 0) { + const d = this.disposables.shift()!; + d.dispose(); + } + + if (this.languageClient) { + const client = this.languageClient; + this.languageClient = undefined; + + try { + await client.stop(); + await client.dispose(); + } catch (ex) { + traceError('Stopping language client failed', ex); } - await this.registerTestServices(); - } else { - await this.startupCompleted.promise; + } + + if (this.cancellationStrategy) { + this.cancellationStrategy.dispose(); + this.cancellationStrategy = undefined; } } - // tslint:disable-next-line: no-empty - public loadExtension(_args?: {}) {} + // eslint-disable-next-line class-methods-use-this + public loadExtension(): void { + // No body. + } @captureTelemetry( EventName.LANGUAGE_SERVER_READY, undefined, true, undefined, - NodeLanguageServerProxy.versionTelemetryProps + NodeLanguageServerProxy.versionTelemetryProps, ) - protected async serverReady(): Promise { - while (this.languageClient && !this.languageClient.initializeResult) { - await sleep(100); - } - if (this.languageClient) { - await this.languageClient.onReady(); - } - this.startupCompleted.resolve(); + private registerHandlers(client: LanguageClient, _resource: Resource) { + const progressReporting = new ProgressReporting(client); + this.disposables.push(progressReporting); + + this.disposables.push( + this.interpreterPathService.onDidChange(() => { + // Manually send didChangeConfiguration in order to get the server to requery + // the workspace configurations (to then pick up pythonPath set in the middleware). + // This is needed as interpreter changes via the interpreter path service happen + // outside of VS Code's settings (which would mean VS Code sends the config updates itself). + client.sendNotification(DidChangeConfigurationNotification.type, { + settings: null, + }); + }), + ); + this.disposables.push( + this.environmentService.onDidEnvironmentVariablesChange(() => { + client.sendNotification(DidChangeConfigurationNotification.type, { + settings: null, + }); + }), + ); + + client.onTelemetry((telemetryEvent) => { + const eventName = telemetryEvent.EventName || EventName.LANGUAGE_SERVER_TELEMETRY; + const formattedProperties = { + ...telemetryEvent.Properties, + // Replace all slashes in the method name so it doesn't get scrubbed by @vscode/extension-telemetry. + method: telemetryEvent.Properties.method?.replace(/\//g, '.'), + }; + sendTelemetryEvent(eventName, telemetryEvent.Measurements, formattedProperties, telemetryEvent.Exception); + }); + + client.onRequest( + InExperiment.Method, + async (params: InExperiment.IRequest): Promise => { + const inExperiment = await this.experimentService.inExperiment(params.experimentName); + return { inExperiment }; + }, + ); + + client.onRequest( + GetExperimentValue.Method, + async ( + params: GetExperimentValue.IRequest, + ): Promise> => { + const value = await this.experimentService.getExperimentValue(params.experimentName); + return { value }; + }, + ); + + this.disposables.push( + client.onRequest('python/isTrustedWorkspace', async () => ({ + isTrusted: this.workspace.isTrusted, + })), + ); } - @swallowExceptions('Activating Unit Tests Manager for Pylance language server') - protected async registerTestServices() { - if (!this.languageClient) { - throw new Error('languageClient not initialized'); + private async getPylanceExtension(): Promise | undefined> { + const extension = this.extensions.getExtension(PYLANCE_EXTENSION_ID); + if (!extension) { + return undefined; + } + + if (!extension.isActive) { + await extension.activate(); } - await this.testManager.activate(new LanguageServerSymbolProvider(this.languageClient!)); + + return extension; } } diff --git a/src/client/activation/node/manager.ts b/src/client/activation/node/manager.ts index 26b2a33a1c5c..5a66e4abecd0 100644 --- a/src/client/activation/node/manager.ts +++ b/src/client/activation/node/manager.ts @@ -2,108 +2,107 @@ // Licensed under the MIT License. import '../../common/extensions'; -import { inject, injectable, named } from 'inversify'; - import { ICommandManager } from '../../common/application/types'; -import { traceDecorators } from '../../common/logger'; -import { IConfigurationService, IDisposable, IExperimentsManager, Resource } from '../../common/types'; +import { IDisposable, IExtensions, Resource } from '../../common/types'; import { debounceSync } from '../../common/utils/decorators'; import { IServiceContainer } from '../../ioc/types'; import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { captureTelemetry } from '../../telemetry'; +import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; import { Commands } from '../commands'; -import { LanguageClientMiddleware } from '../languageClientMiddleware'; -import { - ILanguageServerAnalysisOptions, - ILanguageServerFolderService, - ILanguageServerManager, - ILanguageServerProxy, - LanguageServerType -} from '../types'; - -@injectable() +import { NodeLanguageClientMiddleware } from './languageClientMiddleware'; +import { ILanguageServerAnalysisOptions, ILanguageServerManager } from '../types'; +import { traceDecoratorError, traceDecoratorVerbose } from '../../logging'; +import { PYLANCE_EXTENSION_ID } from '../../common/constants'; +import { NodeLanguageServerProxy } from './languageServerProxy'; + export class NodeLanguageServerManager implements ILanguageServerManager { - private languageServerProxy?: ILanguageServerProxy; private resource!: Resource; + private interpreter: PythonEnvironment | undefined; - private middleware: LanguageClientMiddleware | undefined; + + private middleware: NodeLanguageClientMiddleware | undefined; + private disposables: IDisposable[] = []; - private connected: boolean = false; + + private connected = false; + private lsVersion: string | undefined; + private started = false; + + private static commandDispose: IDisposable; + constructor( - @inject(IServiceContainer) private readonly serviceContainer: IServiceContainer, - @inject(ILanguageServerAnalysisOptions) - @named(LanguageServerType.Node) + private readonly serviceContainer: IServiceContainer, private readonly analysisOptions: ILanguageServerAnalysisOptions, - @inject(ILanguageServerFolderService) - private readonly folderService: ILanguageServerFolderService, - @inject(IExperimentsManager) private readonly experimentsManager: IExperimentsManager, - @inject(IConfigurationService) private readonly configService: IConfigurationService, - @inject(ICommandManager) commandManager: ICommandManager + private readonly languageServerProxy: NodeLanguageServerProxy, + commandManager: ICommandManager, + private readonly extensions: IExtensions, ) { - this.disposables.push( - commandManager.registerCommand(Commands.RestartLS, () => { - this.restartLanguageServer().ignoreErrors(); - }) - ); + if (NodeLanguageServerManager.commandDispose) { + NodeLanguageServerManager.commandDispose.dispose(); + } + NodeLanguageServerManager.commandDispose = commandManager.registerCommand(Commands.RestartLS, () => { + sendTelemetryEvent(EventName.LANGUAGE_SERVER_RESTART, undefined, { reason: 'command' }); + this.restartLanguageServer().ignoreErrors(); + }); } private static versionTelemetryProps(instance: NodeLanguageServerManager) { return { - lsVersion: instance.lsVersion + lsVersion: instance.lsVersion, }; } - public dispose() { - if (this.languageProxy) { - this.languageProxy.dispose(); - } + public dispose(): void { + this.stopLanguageServer().ignoreErrors(); + NodeLanguageServerManager.commandDispose.dispose(); this.disposables.forEach((d) => d.dispose()); } - public get languageProxy() { - return this.languageServerProxy; - } - - @traceDecorators.error('Failed to start language server') + @traceDecoratorError('Failed to start language server') public async start(resource: Resource, interpreter: PythonEnvironment | undefined): Promise { - if (this.languageProxy) { + if (this.started) { throw new Error('Language server already started'); } this.resource = resource; this.interpreter = interpreter; this.analysisOptions.onDidChange(this.restartLanguageServerDebounced, this, this.disposables); - const versionPair = await this.folderService.getCurrentLanguageServerDirectory(); - this.lsVersion = versionPair?.version.format(); + const extension = this.extensions.getExtension(PYLANCE_EXTENSION_ID); + this.lsVersion = extension?.packageJSON.version || '0'; await this.analysisOptions.initialize(resource, interpreter); await this.startLanguageServer(); + + this.started = true; } - public connect() { - this.connected = true; - this.middleware?.connect(); + public connect(): void { + if (!this.connected) { + this.connected = true; + this.middleware?.connect(); + } } - public disconnect() { - this.connected = false; - this.middleware?.disconnect(); + public disconnect(): void { + if (this.connected) { + this.connected = false; + this.middleware?.disconnect(); + } } @debounceSync(1000) protected restartLanguageServerDebounced(): void { + sendTelemetryEvent(EventName.LANGUAGE_SERVER_RESTART, undefined, { reason: 'settings' }); this.restartLanguageServer().ignoreErrors(); } - @traceDecorators.error('Failed to restart language server') - @traceDecorators.verbose('Restarting language server') + @traceDecoratorError('Failed to restart language server') + @traceDecoratorVerbose('Restarting language server') protected async restartLanguageServer(): Promise { - if (this.languageProxy) { - this.languageProxy.dispose(); - } + await this.stopLanguageServer(); await this.startLanguageServer(); } @@ -112,19 +111,13 @@ export class NodeLanguageServerManager implements ILanguageServerManager { undefined, true, undefined, - NodeLanguageServerManager.versionTelemetryProps + NodeLanguageServerManager.versionTelemetryProps, ) - @traceDecorators.verbose('Starting language server') + @traceDecoratorVerbose('Starting language server') protected async startLanguageServer(): Promise { - this.languageServerProxy = this.serviceContainer.get(ILanguageServerProxy); - - const options = await this.analysisOptions!.getAnalysisOptions(); - options.middleware = this.middleware = new LanguageClientMiddleware( - this.experimentsManager, - this.configService, - LanguageServerType.Node, - this.lsVersion - ); + const options = await this.analysisOptions.getAnalysisOptions(); + this.middleware = new NodeLanguageClientMiddleware(this.serviceContainer, this.lsVersion); + options.middleware = this.middleware; // Make sure the middleware is connected if we restart and we we're already connected. if (this.connected) { @@ -134,4 +127,11 @@ export class NodeLanguageServerManager implements ILanguageServerManager { // Then use this middleware to start a new language client. await this.languageServerProxy.start(this.resource, this.interpreter, options); } + + @traceDecoratorVerbose('Stopping language server') + protected async stopLanguageServer(): Promise { + if (this.languageServerProxy) { + await this.languageServerProxy.stop(); + } + } } diff --git a/src/client/activation/node/pylanceApi.ts b/src/client/activation/node/pylanceApi.ts new file mode 100644 index 000000000000..4b3d21d7527e --- /dev/null +++ b/src/client/activation/node/pylanceApi.ts @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +import { + CancellationToken, + CompletionContext, + CompletionItem, + CompletionList, + Position, + TextDocument, + Uri, +} from 'vscode'; + +export interface PylanceApi { + client?: { + isEnabled(): boolean; + start(): Promise; + stop(): Promise; + }; + notebook?: { + registerJupyterPythonPathFunction(func: (uri: Uri) => Promise): void; + getCompletionItems( + document: TextDocument, + position: Position, + context: CompletionContext, + token: CancellationToken, + ): Promise; + }; +} diff --git a/src/client/activation/none/activator.ts b/src/client/activation/none/activator.ts deleted file mode 100644 index d9a2e1414f7e..000000000000 --- a/src/client/activation/none/activator.ts +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import { inject, injectable, named } from 'inversify'; -import { - CancellationToken, - CodeLens, - CompletionContext, - CompletionItem, - CompletionList, - DocumentSymbol, - Hover, - Location, - LocationLink, - Position, - ProviderResult, - ReferenceContext, - SignatureHelp, - SignatureHelpContext, - SymbolInformation, - TextDocument, - WorkspaceEdit -} from 'vscode'; -import { isTestExecution } from '../../common/constants'; -import { BANNER_NAME_PROPOSE_LS, IPythonExtensionBanner, Resource } from '../../common/types'; -import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { ILanguageServerActivator } from '../types'; - -/** - * Provides 'no language server' pseudo-activator. - * - * @export - * @class NoLanguageServerExtensionActivator - * @implements {ILanguageServerActivator} - */ -@injectable() -export class NoLanguageServerExtensionActivator implements ILanguageServerActivator { - constructor( - @inject(IPythonExtensionBanner) - @named(BANNER_NAME_PROPOSE_LS) - private proposePylancePopup: IPythonExtensionBanner - ) {} - public async start(_resource: Resource, _interpreter?: PythonEnvironment): Promise { - if (!isTestExecution()) { - this.proposePylancePopup.showBanner().ignoreErrors(); - } - } - // tslint:disable-next-line: no-empty - public dispose(): void {} - // tslint:disable-next-line: no-empty - public activate(): void {} - // tslint:disable-next-line: no-empty - public deactivate(): void {} - - public provideRenameEdits( - _document: TextDocument, - _position: Position, - _newName: string, - _token: CancellationToken - ): ProviderResult { - return null; - } - public provideDefinition( - _document: TextDocument, - _position: Position, - _token: CancellationToken - ): ProviderResult { - return null; - } - public provideHover( - _document: TextDocument, - _position: Position, - _token: CancellationToken - ): ProviderResult { - return null; - } - public provideReferences( - _document: TextDocument, - _position: Position, - _context: ReferenceContext, - _token: CancellationToken - ): ProviderResult { - return null; - } - public provideCompletionItems( - _document: TextDocument, - _position: Position, - _token: CancellationToken, - _context: CompletionContext - ): ProviderResult { - return null; - } - public provideCodeLenses(_document: TextDocument, _token: CancellationToken): ProviderResult { - return null; - } - public provideDocumentSymbols( - _document: TextDocument, - _token: CancellationToken - ): ProviderResult { - return null; - } - public provideSignatureHelp( - _document: TextDocument, - _position: Position, - _token: CancellationToken, - _context: SignatureHelpContext - ): ProviderResult { - return null; - } -} diff --git a/src/client/activation/partialModeStatus.ts b/src/client/activation/partialModeStatus.ts new file mode 100644 index 000000000000..1105f6529ac8 --- /dev/null +++ b/src/client/activation/partialModeStatus.ts @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// IMPORTANT: Do not import any node fs related modules here, as they do not work in browser. +import { inject, injectable } from 'inversify'; +import type * as vscodeTypes from 'vscode'; +import { IWorkspaceService } from '../common/application/types'; +import { IDisposableRegistry } from '../common/types'; +import { Common, LanguageService } from '../common/utils/localize'; +import { IExtensionSingleActivationService } from './types'; + +/** + * Only partial features are available when running in untrusted or a + * virtual workspace, this creates a UI element to indicate that. + */ +@injectable() +export class PartialModeStatusItem implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: true, virtualWorkspace: true }; + + constructor( + @inject(IWorkspaceService) private readonly workspace: IWorkspaceService, + @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry, + ) {} + + public async activate(): Promise { + const { isTrusted, isVirtualWorkspace } = this.workspace; + if (isTrusted && !isVirtualWorkspace) { + return; + } + const statusItem = this.createStatusItem(); + if (statusItem) { + this.disposables.push(statusItem); + } + } + + private createStatusItem() { + // eslint-disable-next-line global-require + const vscode = require('vscode') as typeof vscodeTypes; + if ('createLanguageStatusItem' in vscode.languages) { + const statusItem = vscode.languages.createLanguageStatusItem('python.projectStatus', { + language: 'python', + }); + statusItem.name = LanguageService.statusItem.name; + statusItem.severity = vscode.LanguageStatusSeverity.Warning; + statusItem.text = LanguageService.statusItem.text; + statusItem.detail = !this.workspace.isTrusted + ? LanguageService.statusItem.detail + : LanguageService.virtualWorkspaceStatusItem.detail; + statusItem.command = { + title: Common.learnMore, + command: 'vscode.open', + arguments: [vscode.Uri.parse('https://aka.ms/AAdzyh4')], + }; + return statusItem; + } + return undefined; + } +} diff --git a/src/client/activation/progress.ts b/src/client/activation/progress.ts index 835a10188f59..5abcb9e553c0 100644 --- a/src/client/activation/progress.ts +++ b/src/client/activation/progress.ts @@ -31,7 +31,7 @@ export class ProgressReporting implements Disposable { if (!this.progress) { this.beginProgress(); } - this.progress!.report({ message: m }); + this.progress!.report({ message: m }); // NOSONAR }); this.languageClient.onNotification('python/endProgress', (_) => { @@ -55,12 +55,12 @@ export class ProgressReporting implements Disposable { window.withProgress( { location: ProgressLocation.Window, - title: '' + title: '', }, (progress) => { this.progress = progress; return this.progressDeferred!.promise; - } + }, ); } } diff --git a/src/client/activation/refCountedLanguageServer.ts b/src/client/activation/refCountedLanguageServer.ts deleted file mode 100644 index 52e76bf6a4de..000000000000 --- a/src/client/activation/refCountedLanguageServer.ts +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import { - CancellationToken, - CodeLens, - CompletionContext, - CompletionItem, - CompletionList, - DocumentSymbol, - Hover, - Location, - LocationLink, - Position, - ProviderResult, - ReferenceContext, - SignatureHelp, - SignatureHelpContext, - SymbolInformation, - TextDocument, - WorkspaceEdit -} from 'vscode'; - -import { Resource } from '../common/types'; -import { noop } from '../common/utils/misc'; -import { PythonEnvironment } from '../pythonEnvironments/info'; -import { ILanguageServerActivator, LanguageServerType } from './types'; - -export class RefCountedLanguageServer implements ILanguageServerActivator { - private refCount = 1; - constructor( - private impl: ILanguageServerActivator, - private _type: LanguageServerType, - private disposeCallback: () => void - ) {} - - public increment = () => { - this.refCount += 1; - }; - - public get type() { - return this._type; - } - - public dispose() { - this.refCount = Math.max(0, this.refCount - 1); - if (this.refCount === 0) { - this.disposeCallback(); - } - } - - public start(_resource: Resource, _interpreter: PythonEnvironment | undefined): Promise { - throw new Error('Server should have already been started. Do not start the wrapper.'); - } - - public activate() { - this.impl.activate(); - } - - public deactivate() { - this.impl.deactivate(); - } - - public clearAnalysisCache() { - this.impl.clearAnalysisCache ? this.impl.clearAnalysisCache() : noop(); - } - - public get connection() { - return this.impl.connection; - } - - public get capabilities() { - return this.impl.capabilities; - } - - public provideRenameEdits( - document: TextDocument, - position: Position, - newName: string, - token: CancellationToken - ): ProviderResult { - return this.impl.provideRenameEdits(document, position, newName, token); - } - public provideDefinition( - document: TextDocument, - position: Position, - token: CancellationToken - ): ProviderResult { - return this.impl.provideDefinition(document, position, token); - } - public provideHover(document: TextDocument, position: Position, token: CancellationToken): ProviderResult { - return this.impl.provideHover(document, position, token); - } - public provideReferences( - document: TextDocument, - position: Position, - context: ReferenceContext, - token: CancellationToken - ): ProviderResult { - return this.impl.provideReferences(document, position, context, token); - } - public provideCompletionItems( - document: TextDocument, - position: Position, - token: CancellationToken, - context: CompletionContext - ): ProviderResult { - return this.impl.provideCompletionItems(document, position, token, context); - } - public resolveCompletionItem(item: CompletionItem, token: CancellationToken): ProviderResult { - if (this.impl.resolveCompletionItem) { - return this.impl.resolveCompletionItem(item, token); - } - } - public provideCodeLenses(document: TextDocument, token: CancellationToken): ProviderResult { - return this.impl.provideCodeLenses(document, token); - } - public provideDocumentSymbols( - document: TextDocument, - token: CancellationToken - ): ProviderResult { - return this.impl.provideDocumentSymbols(document, token); - } - public provideSignatureHelp( - document: TextDocument, - position: Position, - token: CancellationToken, - context: SignatureHelpContext - ): ProviderResult { - return this.impl.provideSignatureHelp(document, position, token, context); - } -} diff --git a/src/client/activation/requirementsTxtLinkActivator.ts b/src/client/activation/requirementsTxtLinkActivator.ts new file mode 100644 index 000000000000..fcb6b72e545e --- /dev/null +++ b/src/client/activation/requirementsTxtLinkActivator.ts @@ -0,0 +1,26 @@ +import { injectable } from 'inversify'; +import { Hover, languages, TextDocument, Position } from 'vscode'; +import { IExtensionSingleActivationService } from './types'; + +const PYPI_PROJECT_URL = 'https://pypi.org/project'; + +export function generatePyPiLink(name: string): string | null { + // Regex to allow to find every possible pypi package (base regex from https://peps.python.org/pep-0508/#names) + const projectName = name.match(/^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*)($|=| |;|\[)/i); + return projectName ? `${PYPI_PROJECT_URL}/${projectName[1]}/` : null; +} + +@injectable() +export class RequirementsTxtLinkActivator implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: true, virtualWorkspace: true }; + + // eslint-disable-next-line class-methods-use-this + public async activate(): Promise { + languages.registerHoverProvider([{ pattern: '**/*requirement*.txt' }, { pattern: '**/requirements/*.txt' }], { + provideHover(document: TextDocument, position: Position) { + const link = generatePyPiLink(document.lineAt(position.line).text); + return link ? new Hover(link) : null; + }, + }); + } +} diff --git a/src/client/activation/serviceRegistry.ts b/src/client/activation/serviceRegistry.ts index 0c553c635537..875afa12f0b4 100644 --- a/src/client/activation/serviceRegistry.ts +++ b/src/client/activation/serviceRegistry.ts @@ -1,231 +1,43 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { registerTypes as registerDotNetTypes } from '../common/dotnet/serviceRegistry'; -import { INugetRepository } from '../common/nuget/types'; -import { - BANNER_NAME_DS_SURVEY, - BANNER_NAME_INTERACTIVE_SHIFTENTER, - BANNER_NAME_PROPOSE_LS, - IPythonExtensionBanner -} from '../common/types'; -import { DataScienceSurveyBanner } from '../datascience/dataScienceSurveyBanner'; -import { InteractiveShiftEnterBanner } from '../datascience/shiftEnterBanner'; import { IServiceManager } from '../ioc/types'; -import { ProposePylanceBanner } from '../languageServices/proposeLanguageServerBanner'; -import { AATesting } from './aaTesting'; import { ExtensionActivationManager } from './activationManager'; -import { LanguageServerExtensionActivationService } from './activationService'; -import { DownloadBetaChannelRule, DownloadDailyChannelRule } from './common/downloadChannelRules'; -import { LanguageServerDownloader } from './common/downloader'; -import { LanguageServerDownloadChannel } from './common/packageRepository'; import { ExtensionSurveyPrompt } from './extensionSurvey'; -import { JediLanguageServerAnalysisOptions } from './jedi/analysisOptions'; -import { JediLanguageClientFactory } from './jedi/languageClientFactory'; -import { JediLanguageServerProxy } from './jedi/languageServerProxy'; -import { JediLanguageServerManager } from './jedi/manager'; -import { MultiplexingJediLanguageServerActivator } from './jedi/multiplexingActivator'; -import { DotNetLanguageServerActivator } from './languageServer/activator'; -import { DotNetLanguageServerAnalysisOptions } from './languageServer/analysisOptions'; -import { DotNetLanguageClientFactory } from './languageServer/languageClientFactory'; -import { LanguageServerCompatibilityService } from './languageServer/languageServerCompatibilityService'; -import { LanguageServerExtension } from './languageServer/languageServerExtension'; -import { DotNetLanguageServerFolderService } from './languageServer/languageServerFolderService'; -import { - BetaDotNetLanguageServerPackageRepository, - DailyDotNetLanguageServerPackageRepository, - StableDotNetLanguageServerPackageRepository -} from './languageServer/languageServerPackageRepository'; -import { DotNetLanguageServerPackageService } from './languageServer/languageServerPackageService'; -import { DotNetLanguageServerProxy } from './languageServer/languageServerProxy'; -import { DotNetLanguageServerManager } from './languageServer/manager'; -import { LanguageServerOutputChannel } from './languageServer/outputChannel'; -import { PlatformData } from './languageServer/platformData'; -import { NodeLanguageServerActivator } from './node/activator'; -import { NodeLanguageServerAnalysisOptions } from './node/analysisOptions'; -import { NodeLanguageClientFactory } from './node/languageClientFactory'; -import { NodeLanguageServerFolderService } from './node/languageServerFolderService'; -import { - BetaNodeLanguageServerPackageRepository, - DailyNodeLanguageServerPackageRepository, - StableNodeLanguageServerPackageRepository -} from './node/languageServerPackageRepository'; -import { NodeLanguageServerPackageService } from './node/languageServerPackageService'; -import { NodeLanguageServerProxy } from './node/languageServerProxy'; -import { NodeLanguageServerManager } from './node/manager'; -import { NoLanguageServerExtensionActivator } from './none/activator'; +import { LanguageServerOutputChannel } from './common/outputChannel'; import { - IDownloadChannelRule, IExtensionActivationManager, IExtensionActivationService, IExtensionSingleActivationService, - ILanguageClientFactory, - ILanguageServerActivator, - ILanguageServerAnalysisOptions, - ILanguageServerCache, - ILanguageServerCompatibilityService, - ILanguageServerDownloader, - ILanguageServerExtension, - ILanguageServerFolderService, - ILanguageServerManager, ILanguageServerOutputChannel, - ILanguageServerPackageService, - ILanguageServerProxy, - IPlatformData, - LanguageServerType } from './types'; +import { LoadLanguageServerExtension } from './common/loadLanguageServerExtension'; +import { PartialModeStatusItem } from './partialModeStatus'; +import { ILanguageServerWatcher } from '../languageServer/types'; +import { LanguageServerWatcher } from '../languageServer/watcher'; +import { RequirementsTxtLinkActivator } from './requirementsTxtLinkActivator'; -// tslint:disable-next-line: max-func-body-length -export function registerTypes(serviceManager: IServiceManager, languageServerType: LanguageServerType) { - serviceManager.addSingleton(ILanguageServerCache, LanguageServerExtensionActivationService); - serviceManager.addBinding(ILanguageServerCache, IExtensionActivationService); - serviceManager.addSingleton(ILanguageServerExtension, LanguageServerExtension); +export function registerTypes(serviceManager: IServiceManager): void { + serviceManager.addSingleton(IExtensionActivationService, PartialModeStatusItem); serviceManager.add(IExtensionActivationManager, ExtensionActivationManager); - - serviceManager.addSingleton( - IPythonExtensionBanner, - ProposePylanceBanner, - BANNER_NAME_PROPOSE_LS + serviceManager.addSingleton( + ILanguageServerOutputChannel, + LanguageServerOutputChannel, ); - serviceManager.addSingleton( - IPythonExtensionBanner, - DataScienceSurveyBanner, - BANNER_NAME_DS_SURVEY + serviceManager.addSingleton( + IExtensionSingleActivationService, + ExtensionSurveyPrompt, ); - serviceManager.addSingleton( - IPythonExtensionBanner, - InteractiveShiftEnterBanner, - BANNER_NAME_INTERACTIVE_SHIFTENTER + serviceManager.addSingleton( + IExtensionSingleActivationService, + LoadLanguageServerExtension, ); - if (languageServerType === LanguageServerType.Microsoft) { - serviceManager.add( - ILanguageServerAnalysisOptions, - DotNetLanguageServerAnalysisOptions, - LanguageServerType.Microsoft - ); - serviceManager.add( - ILanguageServerActivator, - DotNetLanguageServerActivator, - LanguageServerType.Microsoft - ); - serviceManager.addSingleton( - INugetRepository, - StableDotNetLanguageServerPackageRepository, - LanguageServerDownloadChannel.stable - ); - serviceManager.addSingleton( - INugetRepository, - BetaDotNetLanguageServerPackageRepository, - LanguageServerDownloadChannel.beta - ); - serviceManager.addSingleton( - INugetRepository, - DailyDotNetLanguageServerPackageRepository, - LanguageServerDownloadChannel.daily - ); - serviceManager.addSingleton( - ILanguageServerCompatibilityService, - LanguageServerCompatibilityService - ); - serviceManager.addSingleton(ILanguageClientFactory, DotNetLanguageClientFactory); - serviceManager.addSingleton(IPlatformData, PlatformData); - serviceManager.add(ILanguageServerManager, DotNetLanguageServerManager); - serviceManager.add(ILanguageServerProxy, DotNetLanguageServerProxy); - serviceManager.addSingleton( - ILanguageServerFolderService, - DotNetLanguageServerFolderService - ); - serviceManager.addSingleton( - ILanguageServerPackageService, - DotNetLanguageServerPackageService - ); - registerDotNetTypes(serviceManager); - } else if (languageServerType === LanguageServerType.Node) { - serviceManager.add( - ILanguageServerAnalysisOptions, - NodeLanguageServerAnalysisOptions, - LanguageServerType.Node - ); - serviceManager.add( - ILanguageServerActivator, - NodeLanguageServerActivator, - LanguageServerType.Node - ); - serviceManager.addSingleton( - INugetRepository, - StableNodeLanguageServerPackageRepository, - LanguageServerDownloadChannel.stable - ); - serviceManager.addSingleton( - INugetRepository, - BetaNodeLanguageServerPackageRepository, - LanguageServerDownloadChannel.beta - ); - serviceManager.addSingleton( - INugetRepository, - DailyNodeLanguageServerPackageRepository, - LanguageServerDownloadChannel.daily - ); - serviceManager.addSingleton(ILanguageClientFactory, NodeLanguageClientFactory); - serviceManager.add(ILanguageServerManager, NodeLanguageServerManager); - serviceManager.add(ILanguageServerProxy, NodeLanguageServerProxy); - serviceManager.addSingleton( - ILanguageServerFolderService, - NodeLanguageServerFolderService - ); - serviceManager.addSingleton( - ILanguageServerPackageService, - NodeLanguageServerPackageService - ); - } else if (languageServerType === LanguageServerType.Jedi) { - serviceManager.add( - ILanguageServerActivator, - MultiplexingJediLanguageServerActivator, - LanguageServerType.Jedi - ); - - // Note: These other services are required when using the Jedi LSP. - serviceManager.add( - ILanguageServerAnalysisOptions, - JediLanguageServerAnalysisOptions, - LanguageServerType.Jedi - ); - serviceManager.addSingleton(ILanguageClientFactory, JediLanguageClientFactory); - serviceManager.add(ILanguageServerManager, JediLanguageServerManager); - serviceManager.add(ILanguageServerProxy, JediLanguageServerProxy); - } else if (languageServerType === LanguageServerType.None) { - serviceManager.add( - ILanguageServerActivator, - NoLanguageServerExtensionActivator, - LanguageServerType.None - ); - } + serviceManager.addSingleton(ILanguageServerWatcher, LanguageServerWatcher); + serviceManager.addBinding(ILanguageServerWatcher, IExtensionActivationService); - serviceManager.addSingleton( - IDownloadChannelRule, - DownloadDailyChannelRule, - LanguageServerDownloadChannel.daily - ); - serviceManager.addSingleton( - IDownloadChannelRule, - DownloadBetaChannelRule, - LanguageServerDownloadChannel.beta - ); - serviceManager.addSingleton( - IDownloadChannelRule, - DownloadBetaChannelRule, - LanguageServerDownloadChannel.stable - ); - serviceManager.addSingleton(ILanguageServerDownloader, LanguageServerDownloader); - - serviceManager.addSingleton( - ILanguageServerOutputChannel, - LanguageServerOutputChannel - ); serviceManager.addSingleton( IExtensionSingleActivationService, - ExtensionSurveyPrompt + RequirementsTxtLinkActivator, ); - serviceManager.addSingleton(IExtensionSingleActivationService, AATesting); } diff --git a/src/client/activation/types.ts b/src/client/activation/types.ts index 68316191360d..e3b9b818691a 100644 --- a/src/client/activation/types.ts +++ b/src/client/activation/types.ts @@ -3,48 +3,23 @@ 'use strict'; -import { SemVer } from 'semver'; -import { - CodeLensProvider, - CompletionItemProvider, - DefinitionProvider, - DocumentSymbolProvider, - Event, - HoverProvider, - ReferenceProvider, - RenameProvider, - SignatureHelpProvider -} from 'vscode'; +import { Event } from 'vscode'; import { LanguageClient, LanguageClientOptions } from 'vscode-languageclient/node'; -import * as lsp from 'vscode-languageserver-protocol'; -import { NugetPackage } from '../common/nuget/types'; -import { IDisposable, IOutputChannel, LanguageServerDownloadChannels, Resource } from '../common/types'; +import type { IDisposable, ILogOutputChannel, Resource } from '../common/types'; +import { StopWatch } from '../common/utils/stopWatch'; import { PythonEnvironment } from '../pythonEnvironments/info'; export const IExtensionActivationManager = Symbol('IExtensionActivationManager'); /** * Responsible for activation of extension. - * - * @export - * @interface IExtensionActivationManager - * @extends {IDisposable} */ export interface IExtensionActivationManager extends IDisposable { - /** - * Method invoked when extension activates (invoked once). - * - * @returns {Promise} - * @memberof IExtensionActivationManager - */ - activate(): Promise; + // Method invoked when extension activates (invoked once). + activate(startupStopWatch: StopWatch): Promise; /** * Method invoked when a workspace is loaded. * This is where we place initialization scripts for each workspace. * (e.g. if we need to run code for each workspace, then this is where that happens). - * - * @param {Resource} resource - * @returns {Promise} - * @memberof IExtensionActivationManager */ activateWorkspace(resource: Resource): Promise; } @@ -55,108 +30,34 @@ export const IExtensionActivationService = Symbol('IExtensionActivationService') * invoked for every workspace folder (in multi-root workspace folders) during the activation of the extension. * This is a great hook for extension activation code, i.e. you don't need to modify * the `extension.ts` file to invoke some code when extension gets activated. - * @export - * @interface IExtensionActivationService */ export interface IExtensionActivationService { - activate(resource: Resource): Promise; + supportedWorkspaceTypes: { untrustedWorkspace: boolean; virtualWorkspace: boolean }; + activate(resource: Resource, startupStopWatch?: StopWatch): Promise; } export enum LanguageServerType { Jedi = 'Jedi', + JediLSP = 'JediLSP', Microsoft = 'Microsoft', Node = 'Pylance', - None = 'None' -} - -export const DotNetLanguageServerFolder = 'languageServer'; -export const NodeLanguageServerFolder = 'nodeLanguageServer'; - -// tslint:disable-next-line: interface-name -export interface LanguageServerCommandHandler { - clearAnalysisCache(): void; -} - -/** - * This interface is a subset of the vscode-protocol connection interface. - * It's the minimum set of functions needed in order to talk to a language server. - */ -export type ILanguageServerConnection = Pick< - lsp.ProtocolConnection, - 'sendRequest' | 'sendNotification' | 'onProgress' | 'sendProgress' | 'onNotification' | 'onRequest' ->; - -export interface ILanguageServer - extends RenameProvider, - DefinitionProvider, - HoverProvider, - ReferenceProvider, - CompletionItemProvider, - CodeLensProvider, - DocumentSymbolProvider, - SignatureHelpProvider, - Partial, - IDisposable { - readonly connection?: ILanguageServerConnection; - readonly capabilities?: lsp.ServerCapabilities; + None = 'None', } export const ILanguageServerActivator = Symbol('ILanguageServerActivator'); -export interface ILanguageServerActivator extends ILanguageServer { +export interface ILanguageServerActivator { start(resource: Resource, interpreter: PythonEnvironment | undefined): Promise; activate(): void; deactivate(): void; } -export const ILanguageServerCache = Symbol('ILanguageServerCache'); -export interface ILanguageServerCache { - get(resource: Resource, interpreter?: PythonEnvironment): Promise; -} - -export type FolderVersionPair = { path: string; version: SemVer }; -export const ILanguageServerFolderService = Symbol('ILanguageServerFolderService'); - -export interface ILanguageServerFolderService { - getLanguageServerFolderName(resource: Resource): Promise; - getLatestLanguageServerVersion(resource: Resource): Promise; - getCurrentLanguageServerDirectory(): Promise; - skipDownload(): Promise; -} - -export const ILanguageServerDownloader = Symbol('ILanguageServerDownloader'); - -export interface ILanguageServerDownloader { - downloadLanguageServer(destinationFolder: string, resource: Resource): Promise; -} - -export const ILanguageServerPackageService = Symbol('ILanguageServerPackageService'); -export interface ILanguageServerPackageService { - getNugetPackageName(): string; - getLatestNugetPackageVersion(resource: Resource, minVersion?: string): Promise; - getLanguageServerDownloadChannel(): LanguageServerDownloadChannels; -} - -export const MajorLanguageServerVersion = Symbol('MajorLanguageServerVersion'); -export const IDownloadChannelRule = Symbol('IDownloadChannelRule'); -export interface IDownloadChannelRule { - shouldLookForNewLanguageServer(currentFolder?: FolderVersionPair): Promise; -} -export const ILanguageServerCompatibilityService = Symbol('ILanguageServerCompatibilityService'); -export interface ILanguageServerCompatibilityService { - isSupported(): Promise; -} -export enum LanguageClientFactory { - base = 'base', - simple = 'simple', - downloaded = 'downloaded' -} export const ILanguageClientFactory = Symbol('ILanguageClientFactory'); export interface ILanguageClientFactory { createLanguageClient( resource: Resource, interpreter: PythonEnvironment | undefined, clientOptions: LanguageClientOptions, - env?: NodeJS.ProcessEnv + env?: NodeJS.ProcessEnv, ): Promise; } export const ILanguageServerAnalysisOptions = Symbol('ILanguageServerAnalysisOptions'); @@ -167,60 +68,33 @@ export interface ILanguageServerAnalysisOptions extends IDisposable { } export const ILanguageServerManager = Symbol('ILanguageServerManager'); export interface ILanguageServerManager extends IDisposable { - readonly languageProxy: ILanguageServerProxy | undefined; start(resource: Resource, interpreter: PythonEnvironment | undefined): Promise; connect(): void; disconnect(): void; } -export const ILanguageServerExtension = Symbol('ILanguageServerExtension'); -export interface ILanguageServerExtension extends IDisposable { - readonly invoked: Event; - loadExtensionArgs?: {}; - register(): void; -} + export const ILanguageServerProxy = Symbol('ILanguageServerProxy'); export interface ILanguageServerProxy extends IDisposable { - /** - * LanguageClient in use - */ - languageClient: LanguageClient | undefined; start( resource: Resource, interpreter: PythonEnvironment | undefined, - options: LanguageClientOptions + options: LanguageClientOptions, ): Promise; + stop(): Promise; /** * Sends a request to LS so as to load other extensions. * This is used as a plugin loader mechanism. * Anyone (such as intellicode) wanting to interact with LS, needs to send this request to LS. - * @param {{}} [args] - * @memberof ILanguageServerProxy */ - loadExtension(args?: {}): void; -} - -export enum PlatformName { - Windows32Bit = 'win-x86', - Windows64Bit = 'win-x64', - Mac64Bit = 'osx-x64', - Linux64Bit = 'linux-x64' -} -export const IPlatformData = Symbol('IPlatformData'); -export interface IPlatformData { - readonly platformName: PlatformName; - readonly engineDllName: string; - readonly engineExecutableName: string; + loadExtension(args?: unknown): void; } export const ILanguageServerOutputChannel = Symbol('ILanguageServerOutputChannel'); export interface ILanguageServerOutputChannel { /** * Creates output channel if necessary and returns it - * - * @type {IOutputChannel} - * @memberof ILanguageServerOutputChannel */ - readonly channel: IOutputChannel; + readonly channel: ILogOutputChannel; } export const IExtensionSingleActivationService = Symbol('IExtensionSingleActivationService'); @@ -229,9 +103,8 @@ export const IExtensionSingleActivationService = Symbol('IExtensionSingleActivat * invoked during the activation of the extension. * This is a great hook for extension activation code, i.e. you don't need to modify * the `extension.ts` file to invoke some code when extension gets activated. - * @export - * @interface IExtensionSingleActivationService */ export interface IExtensionSingleActivationService { + supportedWorkspaceTypes: { untrustedWorkspace: boolean; virtualWorkspace: boolean }; activate(): Promise; } diff --git a/src/client/api.ts b/src/client/api.ts index 58e20bfe2f37..908da4be7103 100644 --- a/src/client/api.ts +++ b/src/client/api.ts @@ -1,169 +1,169 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; -import { Event, Uri } from 'vscode'; -import { NotebookCell } from 'vscode-proposed'; -import { isTestExecution } from './common/constants'; -import { traceError } from './common/logger'; +import { Uri, Event } from 'vscode'; +import { BaseLanguageClient, LanguageClientOptions } from 'vscode-languageclient'; +import { LanguageClient } from 'vscode-languageclient/node'; +import { PYLANCE_NAME } from './activation/node/languageClientFactory'; +import { ILanguageServerOutputChannel } from './activation/types'; +import { PythonExtension } from './api/types'; +import { isTestExecution, PYTHON_LANGUAGE } from './common/constants'; import { IConfigurationService, Resource } from './common/types'; -import { JupyterExtensionIntegration } from './datascience/api/jupyterIntegration'; -import { IDataViewerDataProvider, IDataViewerFactory } from './datascience/data-viewing/types'; -import { IJupyterUriProvider, IJupyterUriProviderRegistration, INotebookExtensibility } from './datascience/types'; -import { getDebugpyLauncherArgs, getDebugpyPackagePath } from './debugger/extension/adapter/remoteLaunchers'; +import { getDebugpyLauncherArgs } from './debugger/extension/adapter/remoteLaunchers'; import { IInterpreterService } from './interpreter/contracts'; import { IServiceContainer, IServiceManager } from './ioc/types'; +import { + JupyterExtensionIntegration, + JupyterExtensionPythonEnvironments, + JupyterPythonEnvironmentApi, +} from './jupyter/jupyterIntegration'; +import { traceError } from './logging'; +import { IDiscoveryAPI } from './pythonEnvironments/base/locator'; +import { buildEnvironmentApi } from './environmentApi'; +import { ApiForPylance } from './pylanceApi'; +import { getTelemetryReporter } from './telemetry'; +import { TensorboardExtensionIntegration } from './tensorBoard/tensorboardIntegration'; +import { getDebugpyPath } from './debugger/pythonDebugger'; -/* - * Do not introduce any breaking changes to this API. - * This is the public API for other extensions to interact with this extension. - */ +export function buildApi( + ready: Promise, + serviceManager: IServiceManager, + serviceContainer: IServiceContainer, + discoveryApi: IDiscoveryAPI, +): PythonExtension { + const configurationService = serviceContainer.get(IConfigurationService); + const interpreterService = serviceContainer.get(IInterpreterService); + serviceManager.addSingleton(JupyterExtensionIntegration, JupyterExtensionIntegration); + serviceManager.addSingleton( + JupyterExtensionPythonEnvironments, + JupyterExtensionPythonEnvironments, + ); + serviceManager.addSingleton( + TensorboardExtensionIntegration, + TensorboardExtensionIntegration, + ); + const jupyterPythonEnvApi = serviceContainer.get(JupyterExtensionPythonEnvironments); + const environments = buildEnvironmentApi(discoveryApi, serviceContainer, jupyterPythonEnvApi); + const jupyterIntegration = serviceContainer.get(JupyterExtensionIntegration); + jupyterIntegration.registerEnvApi(environments); + const tensorboardIntegration = serviceContainer.get( + TensorboardExtensionIntegration, + ); + const outputChannel = serviceContainer.get(ILanguageServerOutputChannel); -export interface IExtensionApi { - /** - * Promise indicating whether all parts of the extension have completed loading or not. - * @type {Promise} - * @memberof IExtensionApi - */ - ready: Promise; - jupyter: { - registerHooks(): void; - }; - debug: { + const api: PythonExtension & { /** - * Generate an array of strings for commands to pass to the Python executable to launch the debugger for remote debugging. - * Users can append another array of strings of what they want to execute along with relevant arguments to Python. - * E.g `['/Users/..../pythonVSCode/pythonFiles/lib/python/debugpy', '--listen', 'localhost:57039', '--wait-for-client']` - * @param {string} host - * @param {number} port - * @param {boolean} [waitUntilDebuggerAttaches=true] - * @returns {Promise} + * Internal API just for Jupyter, hence don't include in the official types. */ - getRemoteLauncherCommand(host: string, port: number, waitUntilDebuggerAttaches: boolean): Promise; - + jupyter: { + registerHooks(): void; + }; /** - * Gets the path to the debugger package used by the extension. - * @returns {Promise} + * Internal API just for Tensorboard, hence don't include in the official types. */ - getDebuggerPackagePath(): Promise; - }; - /** - * Return internal settings within the extension which are stored in VSCode storage - */ - settings: { + tensorboard: { + registerHooks(): void; + }; + } & { /** - * An event that is emitted when execution details (for a resource) change. For instance, when interpreter configuration changes. + * @deprecated Temporarily exposed for Pylance until we expose this API generally. Will be removed in an + * iteration or two. */ - readonly onDidChangeExecutionDetails: Event; + pylance: ApiForPylance; + } & { /** - * Returns all the details the consumer needs to execute code within the selected environment, - * corresponding to the specified resource taking into account any workspace-specific settings - * for the workspace to which this resource belongs. - * @param {Resource} [resource] A resource for which the setting is asked for. - * * When no resource is provided, the setting scoped to the first workspace folder is returned. - * * If no folder is present, it returns the global setting. - * @returns {({ execCommand: string[] | undefined })} + * @deprecated Use PythonExtension.environments API instead. + * + * Return internal settings within the extension which are stored in VSCode storage */ - getExecutionDetails( - resource?: Resource - ): { + settings: { /** - * E.g of execution commands returned could be, - * * `['']` - * * `['']` - * * `['conda', 'run', 'python']` which is used to run from within Conda environments. - * or something similar for some other Python environments. - * - * @type {(string[] | undefined)} When return value is `undefined`, it means no interpreter is set. - * Otherwise, join the items returned using space to construct the full execution command. + * An event that is emitted when execution details (for a resource) change. For instance, when interpreter configuration changes. */ - execCommand: string[] | undefined; + readonly onDidChangeExecutionDetails: Event; + /** + * Returns all the details the consumer needs to execute code within the selected environment, + * corresponding to the specified resource taking into account any workspace-specific settings + * for the workspace to which this resource belongs. + * @param {Resource} [resource] A resource for which the setting is asked for. + * * When no resource is provided, the setting scoped to the first workspace folder is returned. + * * If no folder is present, it returns the global setting. + */ + getExecutionDetails( + resource?: Resource, + ): { + /** + * E.g of execution commands returned could be, + * * `['']` + * * `['']` + * * `['conda', 'run', 'python']` which is used to run from within Conda environments. + * or something similar for some other Python environments. + * + * @type {(string[] | undefined)} When return value is `undefined`, it means no interpreter is set. + * Otherwise, join the items returned using space to construct the full execution command. + */ + execCommand: string[] | undefined; + }; }; - }; - datascience: { - readonly onKernelPostExecute: Event; - readonly onKernelRestart: Event; - /** - * Launches Data Viewer component. - * @param {IDataViewerDataProvider} dataProvider Instance that will be used by the Data Viewer component to fetch data. - * @param {string} title Data Viewer title - */ - showDataViewer(dataProvider: IDataViewerDataProvider, title: string): Promise; - /** - * Registers a remote server provider component that's used to pick remote jupyter server URIs - * @param serverProvider object called back when picking jupyter server URI - */ - registerRemoteServerProvider(serverProvider: IJupyterUriProvider): void; - }; -} - -export function buildApi( - // tslint:disable-next-line:no-any - ready: Promise, - serviceManager: IServiceManager, - serviceContainer: IServiceContainer -): IExtensionApi { - const configurationService = serviceContainer.get(IConfigurationService); - const interpreterService = serviceContainer.get(IInterpreterService); - const notebookExtensibility = serviceContainer.get(INotebookExtensibility); - serviceManager.addSingleton(JupyterExtensionIntegration, JupyterExtensionIntegration); - const jupyterIntegration = serviceContainer.get(JupyterExtensionIntegration); - const api: IExtensionApi = { + } = { // 'ready' will propagate the exception, but we must log it here first. ready: ready.catch((ex) => { traceError('Failure during activation.', ex); return Promise.reject(ex); }), jupyter: { - registerHooks: () => jupyterIntegration.integrateWithJupyterExtension() + registerHooks: () => jupyterIntegration.integrateWithJupyterExtension(), + }, + tensorboard: { + registerHooks: () => tensorboardIntegration.integrateWithTensorboardExtension(), }, debug: { async getRemoteLauncherCommand( host: string, port: number, - waitUntilDebuggerAttaches: boolean = true + waitUntilDebuggerAttaches = true, ): Promise { return getDebugpyLauncherArgs({ host, port, - waitUntilDebuggerAttaches + waitUntilDebuggerAttaches, }); }, async getDebuggerPackagePath(): Promise { - return getDebugpyPackagePath(); - } + return getDebugpyPath(); + }, }, settings: { onDidChangeExecutionDetails: interpreterService.onDidChangeInterpreterConfiguration, getExecutionDetails(resource?: Resource) { - const pythonPath = configurationService.getSettings(resource).pythonPath; + const { pythonPath } = configurationService.getSettings(resource); // If pythonPath equals an empty string, no interpreter is set. return { execCommand: pythonPath === '' ? undefined : [pythonPath] }; - } - }, - datascience: { - async showDataViewer(dataProvider: IDataViewerDataProvider, title: string): Promise { - const dataViewerProviderService = serviceContainer.get(IDataViewerFactory); - await dataViewerProviderService.create(dataProvider, title); }, - registerRemoteServerProvider(picker: IJupyterUriProvider): void { - const container = serviceContainer.get( - IJupyterUriProviderRegistration - ); - container.registerProvider(picker); + }, + pylance: { + createClient: (...args: any[]): BaseLanguageClient => { + // Make sure we share output channel so that we can share one with + // Jedi as well. + const clientOptions = args[1] as LanguageClientOptions; + clientOptions.outputChannel = clientOptions.outputChannel ?? outputChannel.channel; + + return new LanguageClient(PYTHON_LANGUAGE, PYLANCE_NAME, args[0], clientOptions); }, - onKernelPostExecute: notebookExtensibility.onKernelPostExecute, - onKernelRestart: notebookExtensibility.onKernelRestart - } + start: (client: BaseLanguageClient): Promise => client.start(), + stop: (client: BaseLanguageClient): Promise => client.stop(), + getTelemetryReporter: () => getTelemetryReporter(), + }, + environments, }; // In test environment return the DI Container. if (isTestExecution()) { - // tslint:disable:no-any (api as any).serviceContainer = serviceContainer; (api as any).serviceManager = serviceManager; - // tslint:enable:no-any } return api; } diff --git a/src/client/api/types.ts b/src/client/api/types.ts new file mode 100644 index 000000000000..95556aacbd90 --- /dev/null +++ b/src/client/api/types.ts @@ -0,0 +1,349 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { CancellationToken, Event, Uri, WorkspaceFolder, extensions } from 'vscode'; + +/* + * Do not introduce any breaking changes to this API. + * This is the public API for other extensions to interact with this extension. + */ +export interface PythonExtension { + /** + * Promise indicating whether all parts of the extension have completed loading or not. + */ + ready: Promise; + debug: { + /** + * Generate an array of strings for commands to pass to the Python executable to launch the debugger for remote debugging. + * Users can append another array of strings of what they want to execute along with relevant arguments to Python. + * E.g `['/Users/..../pythonVSCode/python_files/lib/python/debugpy', '--listen', 'localhost:57039', '--wait-for-client']` + * @param host + * @param port + * @param waitUntilDebuggerAttaches Defaults to `true`. + */ + getRemoteLauncherCommand(host: string, port: number, waitUntilDebuggerAttaches: boolean): Promise; + + /** + * Gets the path to the debugger package used by the extension. + */ + getDebuggerPackagePath(): Promise; + }; + + /** + * These APIs provide a way for extensions to work with by python environments available in the user's machine + * as found by the Python extension. See + * https://github.com/microsoft/vscode-python/wiki/Python-Environment-APIs for usage examples and more. + */ + readonly environments: { + /** + * Returns the environment configured by user in settings. Note that this can be an invalid environment, use + * {@link resolveEnvironment} to get full details. + * @param resource : Uri of a file or workspace folder. This is used to determine the env in a multi-root + * scenario. If `undefined`, then the API returns what ever is set for the workspace. + */ + getActiveEnvironmentPath(resource?: Resource): EnvironmentPath; + /** + * Sets the active environment path for the python extension for the resource. Configuration target will always + * be the workspace folder. + * @param environment : If string, it represents the full path to environment folder or python executable + * for the environment. Otherwise it can be {@link Environment} or {@link EnvironmentPath} itself. + * @param resource : [optional] File or workspace to scope to a particular workspace folder. + */ + updateActiveEnvironmentPath( + environment: string | EnvironmentPath | Environment, + resource?: Resource, + ): Promise; + /** + * This event is triggered when the active environment setting changes. + */ + readonly onDidChangeActiveEnvironmentPath: Event; + /** + * Carries environments known to the extension at the time of fetching the property. Note this may not + * contain all environments in the system as a refresh might be going on. + * + * Only reports environments in the current workspace. + */ + readonly known: readonly Environment[]; + /** + * This event is triggered when the known environment list changes, like when a environment + * is found, existing environment is removed, or some details changed on an environment. + */ + readonly onDidChangeEnvironments: Event; + /** + * This API will trigger environment discovery, but only if it has not already happened in this VSCode session. + * Useful for making sure env list is up-to-date when the caller needs it for the first time. + * + * To force trigger a refresh regardless of whether a refresh was already triggered, see option + * {@link RefreshOptions.forceRefresh}. + * + * Note that if there is a refresh already going on then this returns the promise for that refresh. + * @param options Additional options for refresh. + * @param token A cancellation token that indicates a refresh is no longer needed. + */ + refreshEnvironments(options?: RefreshOptions, token?: CancellationToken): Promise; + /** + * Returns details for the given environment, or `undefined` if the env is invalid. + * @param environment : If string, it represents the full path to environment folder or python executable + * for the environment. Otherwise it can be {@link Environment} or {@link EnvironmentPath} itself. + */ + resolveEnvironment( + environment: Environment | EnvironmentPath | string, + ): Promise; + /** + * Returns the environment variables used by the extension for a resource, which includes the custom + * variables configured by user in `.env` files. + * @param resource : Uri of a file or workspace folder. This is used to determine the env in a multi-root + * scenario. If `undefined`, then the API returns what ever is set for the workspace. + */ + getEnvironmentVariables(resource?: Resource): EnvironmentVariables; + /** + * This event is fired when the environment variables for a resource change. Note it's currently not + * possible to detect if environment variables in the system change, so this only fires if custom + * environment variables are updated in `.env` files. + */ + readonly onDidEnvironmentVariablesChange: Event; + }; +} + +export type RefreshOptions = { + /** + * When `true`, force trigger a refresh regardless of whether a refresh was already triggered. Note this can be expensive so + * it's best to only use it if user manually triggers a refresh. + */ + forceRefresh?: boolean; +}; + +/** + * Details about the environment. Note the environment folder, type and name never changes over time. + */ +export type Environment = EnvironmentPath & { + /** + * Carries details about python executable. + */ + readonly executable: { + /** + * Uri of the python interpreter/executable. Carries `undefined` in case an executable does not belong to + * the environment. + */ + readonly uri: Uri | undefined; + /** + * Bitness if known at this moment. + */ + readonly bitness: Bitness | undefined; + /** + * Value of `sys.prefix` in sys module if known at this moment. + */ + readonly sysPrefix: string | undefined; + }; + /** + * Carries details if it is an environment, otherwise `undefined` in case of global interpreters and others. + */ + readonly environment: + | { + /** + * Type of the environment. + */ + readonly type: EnvironmentType; + /** + * Name to the environment if any. + */ + readonly name: string | undefined; + /** + * Uri of the environment folder. + */ + readonly folderUri: Uri; + /** + * Any specific workspace folder this environment is created for. + */ + readonly workspaceFolder: WorkspaceFolder | undefined; + } + | undefined; + /** + * Carries Python version information known at this moment, carries `undefined` for envs without python. + */ + readonly version: + | (VersionInfo & { + /** + * Value of `sys.version` in sys module if known at this moment. + */ + readonly sysVersion: string | undefined; + }) + | undefined; + /** + * Tools/plugins which created the environment or where it came from. First value in array corresponds + * to the primary tool which manages the environment, which never changes over time. + * + * Array is empty if no tool is responsible for creating/managing the environment. Usually the case for + * global interpreters. + */ + readonly tools: readonly EnvironmentTools[]; +}; + +/** + * Derived form of {@link Environment} where certain properties can no longer be `undefined`. Meant to represent an + * {@link Environment} with complete information. + */ +export type ResolvedEnvironment = Environment & { + /** + * Carries complete details about python executable. + */ + readonly executable: { + /** + * Uri of the python interpreter/executable. Carries `undefined` in case an executable does not belong to + * the environment. + */ + readonly uri: Uri | undefined; + /** + * Bitness of the environment. + */ + readonly bitness: Bitness; + /** + * Value of `sys.prefix` in sys module. + */ + readonly sysPrefix: string; + }; + /** + * Carries complete Python version information, carries `undefined` for envs without python. + */ + readonly version: + | (ResolvedVersionInfo & { + /** + * Value of `sys.version` in sys module if known at this moment. + */ + readonly sysVersion: string; + }) + | undefined; +}; + +export type EnvironmentsChangeEvent = { + readonly env: Environment; + /** + * * "add": New environment is added. + * * "remove": Existing environment in the list is removed. + * * "update": New information found about existing environment. + */ + readonly type: 'add' | 'remove' | 'update'; +}; + +export type ActiveEnvironmentPathChangeEvent = EnvironmentPath & { + /** + * Resource the environment changed for. + */ + readonly resource: Resource | undefined; +}; + +/** + * Uri of a file inside a workspace or workspace folder itself. + */ +export type Resource = Uri | WorkspaceFolder; + +export type EnvironmentPath = { + /** + * The ID of the environment. + */ + readonly id: string; + /** + * Path to environment folder or path to python executable that uniquely identifies an environment. Environments + * lacking a python executable are identified by environment folder paths, whereas other envs can be identified + * using python executable path. + */ + readonly path: string; +}; + +/** + * Tool/plugin where the environment came from. It can be {@link KnownEnvironmentTools} or custom string which + * was contributed. + */ +export type EnvironmentTools = KnownEnvironmentTools | string; +/** + * Tools or plugins the Python extension currently has built-in support for. Note this list is expected to shrink + * once tools have their own separate extensions. + */ +export type KnownEnvironmentTools = + | 'Conda' + | 'Pipenv' + | 'Poetry' + | 'VirtualEnv' + | 'Venv' + | 'VirtualEnvWrapper' + | 'Pyenv' + | 'Hatch' + | 'Unknown'; + +/** + * Type of the environment. It can be {@link KnownEnvironmentTypes} or custom string which was contributed. + */ +export type EnvironmentType = KnownEnvironmentTypes | string; +/** + * Environment types the Python extension is aware of. Note this list is expected to shrink once tools have their + * own separate extensions, in which case they're expected to provide the type themselves. + */ +export type KnownEnvironmentTypes = 'VirtualEnvironment' | 'Conda' | 'Unknown'; + +/** + * Carries bitness for an environment. + */ +export type Bitness = '64-bit' | '32-bit' | 'Unknown'; + +/** + * The possible Python release levels. + */ +export type PythonReleaseLevel = 'alpha' | 'beta' | 'candidate' | 'final'; + +/** + * Release information for a Python version. + */ +export type PythonVersionRelease = { + readonly level: PythonReleaseLevel; + readonly serial: number; +}; + +export type VersionInfo = { + readonly major: number | undefined; + readonly minor: number | undefined; + readonly micro: number | undefined; + readonly release: PythonVersionRelease | undefined; +}; + +export type ResolvedVersionInfo = { + readonly major: number; + readonly minor: number; + readonly micro: number; + readonly release: PythonVersionRelease; +}; + +/** + * A record containing readonly keys. + */ +export type EnvironmentVariables = { readonly [key: string]: string | undefined }; + +export type EnvironmentVariablesChangeEvent = { + /** + * Workspace folder the environment variables changed for. + */ + readonly resource: WorkspaceFolder | undefined; + /** + * Updated value of environment variables. + */ + readonly env: EnvironmentVariables; +}; + +export const PVSC_EXTENSION_ID = 'ms-python.python'; + +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace PythonExtension { + /** + * Returns the API exposed by the Python extension in VS Code. + */ + export async function api(): Promise { + const extension = extensions.getExtension(PVSC_EXTENSION_ID); + if (extension === undefined) { + throw new Error(`Python extension is not installed or is disabled`); + } + if (!extension.isActive) { + await extension.activate(); + } + const pythonApi: PythonExtension = extension.exports; + return pythonApi; + } +} diff --git a/src/client/application/diagnostics/applicationDiagnostics.ts b/src/client/application/diagnostics/applicationDiagnostics.ts index aebfa942edb7..90d2ced8d0ae 100644 --- a/src/client/application/diagnostics/applicationDiagnostics.ts +++ b/src/client/application/diagnostics/applicationDiagnostics.ts @@ -1,72 +1,70 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -'use strict'; - -import { inject, injectable, named } from 'inversify'; +import { inject, injectable } from 'inversify'; import { DiagnosticSeverity } from 'vscode'; -import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../../common/constants'; -import { traceError, traceInfo, traceWarning } from '../../common/logger'; -import { IOutputChannel, Resource } from '../../common/types'; +import { IWorkspaceService } from '../../common/application/types'; +import { isTestExecution } from '../../common/constants'; +import { Resource } from '../../common/types'; import { IServiceContainer } from '../../ioc/types'; +import { traceLog, traceVerbose } from '../../logging'; import { IApplicationDiagnostics } from '../types'; -import { IDiagnostic, IDiagnosticsService, ISourceMapSupportService } from './types'; +import { IDiagnostic, IDiagnosticsService } from './types'; + +function log(diagnostics: IDiagnostic[]): void { + diagnostics.forEach((item) => { + const message = `Diagnostic Code: ${item.code}, Message: ${item.message}`; + switch (item.severity) { + case DiagnosticSeverity.Error: + case DiagnosticSeverity.Warning: { + traceLog(message); + break; + } + default: { + traceVerbose(message); + } + } + }); +} + +async function runDiagnostics(diagnosticServices: IDiagnosticsService[], resource: Resource): Promise { + await Promise.all( + diagnosticServices.map(async (diagnosticService) => { + const diagnostics = await diagnosticService.diagnose(resource); + if (diagnostics.length > 0) { + log(diagnostics); + await diagnosticService.handle(diagnostics); + } + }), + ); +} @injectable() export class ApplicationDiagnostics implements IApplicationDiagnostics { - constructor( - @inject(IServiceContainer) private readonly serviceContainer: IServiceContainer, - @inject(IOutputChannel) @named(STANDARD_OUTPUT_CHANNEL) private readonly outputChannel: IOutputChannel - ) {} - public register() { - this.serviceContainer.get(ISourceMapSupportService).register(); - } + constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) {} + + public register() {} + public async performPreStartupHealthCheck(resource: Resource): Promise { // When testing, do not perform health checks, as modal dialogs can be displayed. if (isTestExecution()) { return; } - const services = this.serviceContainer.getAll(IDiagnosticsService); + let services = this.serviceContainer.getAll(IDiagnosticsService); + const workspaceService = this.serviceContainer.get(IWorkspaceService); + if (!workspaceService.isTrusted) { + services = services.filter((item) => item.runInUntrustedWorkspace); + } // Perform these validation checks in the foreground. - await this.runDiagnostics( + await runDiagnostics( services.filter((item) => !item.runInBackground), - resource + resource, ); + // Perform these validation checks in the background. - this.runDiagnostics( + runDiagnostics( services.filter((item) => item.runInBackground), - resource + resource, ).ignoreErrors(); } - private async runDiagnostics(diagnosticServices: IDiagnosticsService[], resource: Resource): Promise { - await Promise.all( - diagnosticServices.map(async (diagnosticService) => { - const diagnostics = await diagnosticService.diagnose(resource); - if (diagnostics.length > 0) { - this.log(diagnostics); - await diagnosticService.handle(diagnostics); - } - }) - ); - } - private log(diagnostics: IDiagnostic[]): void { - diagnostics.forEach((item) => { - const message = `Diagnostic Code: ${item.code}, Message: ${item.message}`; - switch (item.severity) { - case DiagnosticSeverity.Error: { - traceError(message); - this.outputChannel.appendLine(message); - break; - } - case DiagnosticSeverity.Warning: { - traceWarning(message); - this.outputChannel.appendLine(message); - break; - } - default: { - traceInfo(message); - } - } - }); - } } diff --git a/src/client/application/diagnostics/base.ts b/src/client/application/diagnostics/base.ts index 240883cb3657..8ce1c3b83184 100644 --- a/src/client/application/diagnostics/base.ts +++ b/src/client/application/diagnostics/base.ts @@ -7,6 +7,7 @@ import { injectable, unmanaged } from 'inversify'; import { DiagnosticSeverity } from 'vscode'; import { IWorkspaceService } from '../../common/application/types'; import { IDisposable, IDisposableRegistry, Resource } from '../../common/types'; +import { asyncFilter } from '../../common/utils/arrayUtils'; import { IServiceContainer } from '../../ioc/types'; import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; @@ -21,8 +22,8 @@ export abstract class BaseDiagnostic implements IDiagnostic { public readonly severity: DiagnosticSeverity, public readonly scope: DiagnosticScope, public readonly resource: Resource, + public readonly shouldShowPrompt = true, public readonly invokeHandler: 'always' | 'default' = 'default', - public readonly shouldShowPrompt = true ) {} } @@ -33,8 +34,9 @@ export abstract class BaseDiagnosticsService implements IDiagnosticsService, IDi constructor( @unmanaged() private readonly supportedDiagnosticCodes: string[], @unmanaged() protected serviceContainer: IServiceContainer, - @unmanaged() disposableRegistry: IDisposableRegistry, - @unmanaged() public readonly runInBackground: Boolean = false + @unmanaged() protected disposableRegistry: IDisposableRegistry, + @unmanaged() public readonly runInBackground: boolean = false, + @unmanaged() public readonly runInUntrustedWorkspace: boolean = false, ) { this.filterService = serviceContainer.get(IDiagnosticFilterService); disposableRegistry.push(this); @@ -47,7 +49,10 @@ export abstract class BaseDiagnosticsService implements IDiagnosticsService, IDi if (diagnostics.length === 0) { return; } - const diagnosticsToHandle = diagnostics.filter((item) => { + const diagnosticsToHandle = await asyncFilter(diagnostics, async (item) => { + if (!(await this.canHandle(item))) { + return false; + } if (item.invokeHandler && item.invokeHandler === 'always') { return true; } @@ -68,11 +73,6 @@ export abstract class BaseDiagnosticsService implements IDiagnosticsService, IDi /** * Returns a key used to keep track of whether a diagnostic was handled or not. * So as to prevent handling/displaying messages multiple times for the same diagnostic. - * - * @protected - * @param {IDiagnostic} diagnostic - * @returns {string} - * @memberof BaseDiagnosticsService */ protected getDiagnosticsKey(diagnostic: IDiagnostic): string { if (diagnostic.scope === DiagnosticScope.Global) { diff --git a/src/client/application/diagnostics/checks/envPathVariable.ts b/src/client/application/diagnostics/checks/envPathVariable.ts index 2c137662055e..b8850b8bbeee 100644 --- a/src/client/application/diagnostics/checks/envPathVariable.ts +++ b/src/client/application/diagnostics/checks/envPathVariable.ts @@ -1,14 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -'use strict'; - +// eslint-disable-next-line max-classes-per-file import { inject, injectable } from 'inversify'; import { DiagnosticSeverity } from 'vscode'; import { IApplicationEnvironment } from '../../../common/application/types'; import '../../../common/extensions'; import { IPlatformService } from '../../../common/platform/types'; import { ICurrentProcess, IDisposableRegistry, IPathUtils, Resource } from '../../../common/types'; +import { Common } from '../../../common/utils/localize'; import { IServiceContainer } from '../../../ioc/types'; import { BaseDiagnostic, BaseDiagnosticsService } from '../base'; import { IDiagnosticsCommandFactory } from '../commands/types'; @@ -20,14 +20,14 @@ const InvalidEnvPathVariableMessage = "The environment variable '{0}' seems to have some paths containing the '\"' character." + " The existence of such a character is known to have caused the {1} extension to not load. If the extension fails to load please modify your paths to remove this '\"' character."; -export class InvalidEnvironmentPathVariableDiagnostic extends BaseDiagnostic { +class InvalidEnvironmentPathVariableDiagnostic extends BaseDiagnostic { constructor(message: string, resource: Resource) { super( DiagnosticCodes.InvalidEnvironmentPathVariableDiagnostic, message, DiagnosticSeverity.Warning, DiagnosticScope.Global, - resource + resource, ); } } @@ -37,27 +37,36 @@ export const EnvironmentPathVariableDiagnosticsServiceId = 'EnvironmentPathVaria @injectable() export class EnvironmentPathVariableDiagnosticsService extends BaseDiagnosticsService { protected readonly messageService: IDiagnosticHandlerService; + private readonly platform: IPlatformService; + constructor( @inject(IServiceContainer) serviceContainer: IServiceContainer, - @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry + @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, ) { - super([DiagnosticCodes.InvalidEnvironmentPathVariableDiagnostic], serviceContainer, disposableRegistry, true); + super( + [DiagnosticCodes.InvalidEnvironmentPathVariableDiagnostic], + serviceContainer, + disposableRegistry, + true, + true, + ); this.platform = this.serviceContainer.get(IPlatformService); this.messageService = serviceContainer.get>( IDiagnosticHandlerService, - DiagnosticCommandPromptHandlerServiceId + DiagnosticCommandPromptHandlerServiceId, ); } + public async diagnose(resource: Resource): Promise { if (this.platform.isWindows && this.doesPathVariableHaveInvalidEntries()) { const env = this.serviceContainer.get(IApplicationEnvironment); const message = InvalidEnvPathVariableMessage.format(this.platform.pathVariableName, env.extensionName); return [new InvalidEnvironmentPathVariableDiagnostic(message, resource)]; - } else { - return []; } + return []; } + protected async onHandle(diagnostics: IDiagnostic[]): Promise { // This class can only handle one type of diagnostic, hence just use first item in list. if (diagnostics.length === 0 || !this.canHandle(diagnostics[0])) { @@ -70,20 +79,21 @@ export class EnvironmentPathVariableDiagnosticsService extends BaseDiagnosticsSe const commandFactory = this.serviceContainer.get(IDiagnosticsCommandFactory); const options = [ { - prompt: 'Ignore' + prompt: Common.ignore, }, { - prompt: 'Always Ignore', - command: commandFactory.createCommand(diagnostic, { type: 'ignore', options: DiagnosticScope.Global }) + prompt: Common.alwaysIgnore, + command: commandFactory.createCommand(diagnostic, { type: 'ignore', options: DiagnosticScope.Global }), }, { - prompt: 'More Info', - command: commandFactory.createCommand(diagnostic, { type: 'launch', options: 'https://aka.ms/Niq35h' }) - } + prompt: Common.moreInfo, + command: commandFactory.createCommand(diagnostic, { type: 'launch', options: 'https://aka.ms/Niq35h' }), + }, ]; await this.messageService.handle(diagnostic, { commandPrompts: options }); } + private doesPathVariableHaveInvalidEntries() { const currentProc = this.serviceContainer.get(ICurrentProcess); const pathValue = currentProc.env[this.platform.pathVariableName]; diff --git a/src/client/application/diagnostics/checks/invalidLaunchJsonDebugger.ts b/src/client/application/diagnostics/checks/invalidLaunchJsonDebugger.ts index a545764d26ec..440ff16856d3 100644 --- a/src/client/application/diagnostics/checks/invalidLaunchJsonDebugger.ts +++ b/src/client/application/diagnostics/checks/invalidLaunchJsonDebugger.ts @@ -1,8 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -'use strict'; - +// eslint-disable-next-line max-classes-per-file import { inject, injectable, named } from 'inversify'; import * as path from 'path'; import { DiagnosticSeverity, WorkspaceFolder } from 'vscode'; @@ -18,10 +17,10 @@ import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '. import { DiagnosticScope, IDiagnostic, IDiagnosticHandlerService } from '../types'; const messages = { - [DiagnosticCodes.InvalidDebuggerTypeDiagnostic]: Diagnostics.invalidDebuggerTypeDiagnostic(), - [DiagnosticCodes.JustMyCodeDiagnostic]: Diagnostics.justMyCodeDiagnostic(), - [DiagnosticCodes.ConsoleTypeDiagnostic]: Diagnostics.consoleTypeDiagnostic(), - [DiagnosticCodes.ConfigPythonPathDiagnostic]: '' + [DiagnosticCodes.InvalidDebuggerTypeDiagnostic]: Diagnostics.invalidDebuggerTypeDiagnostic, + [DiagnosticCodes.JustMyCodeDiagnostic]: Diagnostics.justMyCodeDiagnostic, + [DiagnosticCodes.ConsoleTypeDiagnostic]: Diagnostics.consoleTypeDiagnostic, + [DiagnosticCodes.ConfigPythonPathDiagnostic]: '', }; export class InvalidLaunchJsonDebuggerDiagnostic extends BaseDiagnostic { @@ -32,7 +31,7 @@ export class InvalidLaunchJsonDebuggerDiagnostic extends BaseDiagnostic { | DiagnosticCodes.ConsoleTypeDiagnostic | DiagnosticCodes.ConfigPythonPathDiagnostic, resource: Resource, - shouldShowPrompt: boolean = true + shouldShowPrompt = true, ) { super( code, @@ -40,8 +39,7 @@ export class InvalidLaunchJsonDebuggerDiagnostic extends BaseDiagnostic { DiagnosticSeverity.Error, DiagnosticScope.WorkspaceFolder, resource, - 'always', - shouldShowPrompt + shouldShowPrompt, ); } } @@ -57,22 +55,24 @@ export class InvalidLaunchJsonDebuggerService extends BaseDiagnosticsService { @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, @inject(IDiagnosticHandlerService) @named(DiagnosticCommandPromptHandlerServiceId) - private readonly messageService: IDiagnosticHandlerService + private readonly messageService: IDiagnosticHandlerService, ) { super( [ DiagnosticCodes.InvalidDebuggerTypeDiagnostic, DiagnosticCodes.JustMyCodeDiagnostic, DiagnosticCodes.ConsoleTypeDiagnostic, - DiagnosticCodes.ConfigPythonPathDiagnostic + DiagnosticCodes.ConfigPythonPathDiagnostic, ], serviceContainer, disposableRegistry, - true + true, ); } + public async diagnose(resource: Resource): Promise { - if (!this.workspaceService.hasWorkspaceFolders) { + const hasWorkspaceFolders = (this.workspaceService.workspaceFolders?.length || 0) > 0; + if (!hasWorkspaceFolders) { return []; } const workspaceFolder = resource @@ -80,22 +80,26 @@ export class InvalidLaunchJsonDebuggerService extends BaseDiagnosticsService { : this.workspaceService.workspaceFolders![0]; return this.diagnoseWorkspace(workspaceFolder, resource); } + protected async onHandle(diagnostics: IDiagnostic[]): Promise { diagnostics.forEach((diagnostic) => this.handleDiagnostic(diagnostic)); } - protected async fixLaunchJson(code: DiagnosticCodes) { - if (!this.workspaceService.hasWorkspaceFolders) { + + protected async fixLaunchJson(code: DiagnosticCodes): Promise { + const hasWorkspaceFolders = (this.workspaceService.workspaceFolders?.length || 0) > 0; + if (!hasWorkspaceFolders) { return; } await Promise.all( - this.workspaceService.workspaceFolders!.map((workspaceFolder) => - this.fixLaunchJsonInWorkspace(code, workspaceFolder) - ) + (this.workspaceService.workspaceFolders ?? []).map((workspaceFolder) => + this.fixLaunchJsonInWorkspace(code, workspaceFolder), + ), ); } + private async diagnoseWorkspace(workspaceFolder: WorkspaceFolder, resource: Resource) { - const launchJson = this.getLaunchJsonFile(workspaceFolder); + const launchJson = getLaunchJsonFile(workspaceFolder); if (!(await this.fs.fileExists(launchJson))) { return []; } @@ -104,7 +108,7 @@ export class InvalidLaunchJsonDebuggerService extends BaseDiagnosticsService { const diagnostics: IDiagnostic[] = []; if (fileContents.indexOf('"pythonExperimental"') > 0) { diagnostics.push( - new InvalidLaunchJsonDebuggerDiagnostic(DiagnosticCodes.InvalidDebuggerTypeDiagnostic, resource) + new InvalidLaunchJsonDebuggerDiagnostic(DiagnosticCodes.InvalidDebuggerTypeDiagnostic, resource), ); } if (fileContents.indexOf('"debugStdLib"') > 0) { @@ -114,70 +118,72 @@ export class InvalidLaunchJsonDebuggerService extends BaseDiagnosticsService { diagnostics.push(new InvalidLaunchJsonDebuggerDiagnostic(DiagnosticCodes.ConsoleTypeDiagnostic, resource)); } if ( + fileContents.indexOf('"pythonPath":') > 0 || fileContents.indexOf('{config:python.pythonPath}') > 0 || fileContents.indexOf('{config:python.interpreterPath}') > 0 ) { diagnostics.push( - new InvalidLaunchJsonDebuggerDiagnostic(DiagnosticCodes.ConfigPythonPathDiagnostic, resource, false) + new InvalidLaunchJsonDebuggerDiagnostic(DiagnosticCodes.ConfigPythonPathDiagnostic, resource, false), ); } return diagnostics; } + private async handleDiagnostic(diagnostic: IDiagnostic): Promise { - if (!this.canHandle(diagnostic)) { - return; - } if (!diagnostic.shouldShowPrompt) { - return this.fixLaunchJson(diagnostic.code); + await this.fixLaunchJson(diagnostic.code); + return; } const commandPrompts = [ { - prompt: Diagnostics.yesUpdateLaunch(), + prompt: Diagnostics.yesUpdateLaunch, command: { diagnostic, invoke: async (): Promise => { await this.fixLaunchJson(diagnostic.code); - } - } + }, + }, }, { - prompt: Common.noIWillDoItLater() - } + prompt: Common.noIWillDoItLater, + }, ]; await this.messageService.handle(diagnostic, { commandPrompts }); } + private async fixLaunchJsonInWorkspace(code: DiagnosticCodes, workspaceFolder: WorkspaceFolder) { if ((await this.diagnoseWorkspace(workspaceFolder, undefined)).length === 0) { return; } - const launchJson = this.getLaunchJsonFile(workspaceFolder); + const launchJson = getLaunchJsonFile(workspaceFolder); let fileContents = await this.fs.readFile(launchJson); switch (code) { case DiagnosticCodes.InvalidDebuggerTypeDiagnostic: { - fileContents = this.findAndReplace(fileContents, '"pythonExperimental"', '"python"'); - fileContents = this.findAndReplace(fileContents, '"Python Experimental:', '"Python:'); + fileContents = findAndReplace(fileContents, '"pythonExperimental"', '"python"'); + fileContents = findAndReplace(fileContents, '"Python Experimental:', '"Python:'); break; } case DiagnosticCodes.JustMyCodeDiagnostic: { - fileContents = this.findAndReplace(fileContents, '"debugStdLib": false', '"justMyCode": true'); - fileContents = this.findAndReplace(fileContents, '"debugStdLib": true', '"justMyCode": false'); + fileContents = findAndReplace(fileContents, '"debugStdLib": false', '"justMyCode": true'); + fileContents = findAndReplace(fileContents, '"debugStdLib": true', '"justMyCode": false'); break; } case DiagnosticCodes.ConsoleTypeDiagnostic: { - fileContents = this.findAndReplace(fileContents, '"console": "none"', '"console": "internalConsole"'); + fileContents = findAndReplace(fileContents, '"console": "none"', '"console": "internalConsole"'); break; } case DiagnosticCodes.ConfigPythonPathDiagnostic: { - fileContents = this.findAndReplace( + fileContents = findAndReplace(fileContents, '"pythonPath":', '"python":'); + fileContents = findAndReplace( fileContents, '{config:python.pythonPath}', - '{command:python.interpreterPath}' + '{command:python.interpreterPath}', ); - fileContents = this.findAndReplace( + fileContents = findAndReplace( fileContents, '{config:python.interpreterPath}', - '{command:python.interpreterPath}' + '{command:python.interpreterPath}', ); break; } @@ -188,11 +194,13 @@ export class InvalidLaunchJsonDebuggerService extends BaseDiagnosticsService { await this.fs.writeFile(launchJson, fileContents); } - private findAndReplace(fileContents: string, search: string, replace: string) { - const searchRegex = new RegExp(search, 'g'); - return fileContents.replace(searchRegex, replace); - } - private getLaunchJsonFile(workspaceFolder: WorkspaceFolder) { - return path.join(workspaceFolder.uri.fsPath, '.vscode', 'launch.json'); - } +} + +function findAndReplace(fileContents: string, search: string, replace: string) { + const searchRegex = new RegExp(search, 'g'); + return fileContents.replace(searchRegex, replace); +} + +function getLaunchJsonFile(workspaceFolder: WorkspaceFolder) { + return path.join(workspaceFolder.uri.fsPath, '.vscode', 'launch.json'); } diff --git a/src/client/application/diagnostics/checks/invalidPythonPathInDebugger.ts b/src/client/application/diagnostics/checks/invalidPythonPathInDebugger.ts index 73f60ce7edfa..f08c09956838 100644 --- a/src/client/application/diagnostics/checks/invalidPythonPathInDebugger.ts +++ b/src/client/application/diagnostics/checks/invalidPythonPathInDebugger.ts @@ -1,20 +1,19 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -'use strict'; - +// eslint-disable-next-line max-classes-per-file import { inject, injectable, named } from 'inversify'; import * as path from 'path'; import { DiagnosticSeverity, Uri, workspace as workspc, WorkspaceFolder } from 'vscode'; import { IDocumentManager, IWorkspaceService } from '../../../common/application/types'; import '../../../common/extensions'; -import { traceError } from '../../../common/logger'; import { IConfigurationService, IDisposableRegistry, Resource } from '../../../common/types'; -import { Diagnostics } from '../../../common/utils/localize'; +import { Common, Diagnostics } from '../../../common/utils/localize'; import { SystemVariables } from '../../../common/variables/systemVariables'; import { PythonPathSource } from '../../../debugger/extension/types'; import { IInterpreterHelper } from '../../../interpreter/contracts'; import { IServiceContainer } from '../../../ioc/types'; +import { traceError } from '../../../logging'; import { BaseDiagnostic, BaseDiagnosticsService } from '../base'; import { IDiagnosticsCommandFactory } from '../commands/types'; import { DiagnosticCodes } from '../constants'; @@ -24,22 +23,30 @@ import { IDiagnostic, IDiagnosticCommand, IDiagnosticHandlerService, - IInvalidPythonPathInDebuggerService + IInvalidPythonPathInDebuggerService, } from '../types'; const messages = { - [DiagnosticCodes.InvalidPythonPathInDebuggerSettingsDiagnostic]: Diagnostics.invalidPythonPathInDebuggerSettings(), - [DiagnosticCodes.InvalidPythonPathInDebuggerLaunchDiagnostic]: Diagnostics.invalidPythonPathInDebuggerLaunch() + [DiagnosticCodes.InvalidPythonPathInDebuggerSettingsDiagnostic]: Diagnostics.invalidPythonPathInDebuggerSettings, + [DiagnosticCodes.InvalidPythonPathInDebuggerLaunchDiagnostic]: Diagnostics.invalidPythonPathInDebuggerLaunch, }; -export class InvalidPythonPathInDebuggerDiagnostic extends BaseDiagnostic { +class InvalidPythonPathInDebuggerDiagnostic extends BaseDiagnostic { constructor( code: | DiagnosticCodes.InvalidPythonPathInDebuggerLaunchDiagnostic | DiagnosticCodes.InvalidPythonPathInDebuggerSettingsDiagnostic, - resource: Resource + resource: Resource, ) { - super(code, messages[code], DiagnosticSeverity.Error, DiagnosticScope.WorkspaceFolder, resource, 'always'); + super( + code, + messages[code], + DiagnosticSeverity.Error, + DiagnosticScope.WorkspaceFolder, + resource, + undefined, + 'always', + ); } } @@ -58,24 +65,31 @@ export class InvalidPythonPathInDebuggerService extends BaseDiagnosticsService @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, @inject(IDiagnosticHandlerService) @named(DiagnosticCommandPromptHandlerServiceId) - protected readonly messageService: IDiagnosticHandlerService + protected readonly messageService: IDiagnosticHandlerService, ) { super( [ DiagnosticCodes.InvalidPythonPathInDebuggerSettingsDiagnostic, - DiagnosticCodes.InvalidPythonPathInDebuggerLaunchDiagnostic + DiagnosticCodes.InvalidPythonPathInDebuggerLaunchDiagnostic, ], serviceContainer, disposableRegistry, - true + true, ); } - public async diagnose(_resource: Resource): Promise { + + // eslint-disable-next-line class-methods-use-this + public async diagnose(): Promise { return []; } - public async validatePythonPath(pythonPath?: string, pythonPathSource?: PythonPathSource, resource?: Uri) { + + public async validatePythonPath( + pythonPath?: string, + pythonPathSource?: PythonPathSource, + resource?: Uri, + ): Promise { pythonPath = pythonPath ? this.resolveVariables(pythonPath, resource) : undefined; - // tslint:disable-next-line:no-invalid-template-strings + if (pythonPath === '${command:python.interpreterPath}' || !pythonPath) { pythonPath = this.configService.getSettings(resource).pythonPath; } @@ -87,8 +101,8 @@ export class InvalidPythonPathInDebuggerService extends BaseDiagnosticsService this.handle([ new InvalidPythonPathInDebuggerDiagnostic( DiagnosticCodes.InvalidPythonPathInDebuggerLaunchDiagnostic, - resource - ) + resource, + ), ]) .catch((ex) => traceError('Failed to handle invalid python path in launch.json debugger', ex)) .ignoreErrors(); @@ -96,14 +110,15 @@ export class InvalidPythonPathInDebuggerService extends BaseDiagnosticsService this.handle([ new InvalidPythonPathInDebuggerDiagnostic( DiagnosticCodes.InvalidPythonPathInDebuggerSettingsDiagnostic, - resource - ) + resource, + ), ]) .catch((ex) => traceError('Failed to handle invalid python path in settings.json debugger', ex)) .ignoreErrors(); } return false; } + protected async onHandle(diagnostics: IDiagnostic[]): Promise { // This class can only handle one type of diagnostic, hence just use first item in list. if (diagnostics.length === 0 || !this.canHandle(diagnostics[0])) { @@ -114,36 +129,38 @@ export class InvalidPythonPathInDebuggerService extends BaseDiagnosticsService await this.messageService.handle(diagnostic, { commandPrompts }); } + protected resolveVariables(pythonPath: string, resource: Uri | undefined): string { const systemVariables = new SystemVariables(resource, undefined, this.workspace); return systemVariables.resolveAny(pythonPath); } + private getCommandPrompts(diagnostic: IDiagnostic): { prompt: string; command?: IDiagnosticCommand }[] { switch (diagnostic.code) { case DiagnosticCodes.InvalidPythonPathInDebuggerSettingsDiagnostic: { return [ { - prompt: 'Select Python Interpreter', + prompt: Common.selectPythonInterpreter, command: this.commandFactory.createCommand(diagnostic, { type: 'executeVSCCommand', - options: 'python.setInterpreter' - }) - } + options: 'python.setInterpreter', + }), + }, ]; } case DiagnosticCodes.InvalidPythonPathInDebuggerLaunchDiagnostic: { return [ { - prompt: 'Open launch.json', + prompt: Common.openLaunch, command: { diagnostic, invoke: async (): Promise => { - const launchJson = this.getLaunchJsonFile(workspc.workspaceFolders![0]); + const launchJson = getLaunchJsonFile(workspc.workspaceFolders![0]); const doc = await this.documentManager.openTextDocument(launchJson); await this.documentManager.showTextDocument(doc); - } - } - } + }, + }, + }, ]; } default: { @@ -151,7 +168,8 @@ export class InvalidPythonPathInDebuggerService extends BaseDiagnosticsService } } } - private getLaunchJsonFile(workspaceFolder: WorkspaceFolder) { - return path.join(workspaceFolder.uri.fsPath, '.vscode', 'launch.json'); - } +} + +function getLaunchJsonFile(workspaceFolder: WorkspaceFolder) { + return path.join(workspaceFolder.uri.fsPath, '.vscode', 'launch.json'); } diff --git a/src/client/application/diagnostics/checks/jediPython27NotSupported.ts b/src/client/application/diagnostics/checks/jediPython27NotSupported.ts new file mode 100644 index 000000000000..3d358325032e --- /dev/null +++ b/src/client/application/diagnostics/checks/jediPython27NotSupported.ts @@ -0,0 +1,108 @@ +/* eslint-disable max-classes-per-file */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, named } from 'inversify'; +import { ConfigurationTarget, DiagnosticSeverity } from 'vscode'; +import { LanguageServerType } from '../../../activation/types'; +import { IWorkspaceService } from '../../../common/application/types'; +import { IConfigurationService, IDisposableRegistry, Resource } from '../../../common/types'; +import { Common, Python27Support } from '../../../common/utils/localize'; +import { IInterpreterService } from '../../../interpreter/contracts'; +import { IServiceContainer } from '../../../ioc/types'; +import { BaseDiagnostic, BaseDiagnosticsService } from '../base'; +import { IDiagnosticsCommandFactory } from '../commands/types'; +import { DiagnosticCodes } from '../constants'; +import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../promptHandler'; +import { DiagnosticScope, IDiagnostic, IDiagnosticHandlerService } from '../types'; + +export class JediPython27NotSupportedDiagnostic extends BaseDiagnostic { + constructor(message: string, resource: Resource) { + super( + DiagnosticCodes.JediPython27NotSupportedDiagnostic, + message, + DiagnosticSeverity.Warning, + DiagnosticScope.Global, + resource, + ); + } +} + +export const JediPython27NotSupportedDiagnosticServiceId = 'JediPython27NotSupportedDiagnosticServiceId'; + +export class JediPython27NotSupportedDiagnosticService extends BaseDiagnosticsService { + constructor( + @inject(IServiceContainer) serviceContainer: IServiceContainer, + @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, + @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, + @inject(IConfigurationService) private readonly configurationService: IConfigurationService, + @inject(IDiagnosticHandlerService) + @named(DiagnosticCommandPromptHandlerServiceId) + protected readonly messageService: IDiagnosticHandlerService, + @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, + ) { + super([DiagnosticCodes.JediPython27NotSupportedDiagnostic], serviceContainer, disposableRegistry, true); + } + + public async diagnose(resource: Resource): Promise { + const interpreter = await this.interpreterService.getActiveInterpreter(resource); + const { languageServer } = this.configurationService.getSettings(resource); + + await this.updateLanguageServerSetting(resource); + + // We don't need to check for JediLSP here, because we retrieve the setting from the configuration service, + // Which already switched the JediLSP option to Jedi. + if (interpreter && (interpreter.version?.major ?? 0) < 3 && languageServer === LanguageServerType.Jedi) { + return [new JediPython27NotSupportedDiagnostic(Python27Support.jediMessage, resource)]; + } + + return []; + } + + protected async onHandle(diagnostics: IDiagnostic[]): Promise { + if (diagnostics.length === 0 || !this.canHandle(diagnostics[0])) { + return; + } + const diagnostic = diagnostics[0]; + if (await this.filterService.shouldIgnoreDiagnostic(diagnostic.code)) { + return; + } + + const commandFactory = this.serviceContainer.get(IDiagnosticsCommandFactory); + const options = [ + { + prompt: Common.gotIt, + }, + { + prompt: Common.doNotShowAgain, + command: commandFactory.createCommand(diagnostic, { type: 'ignore', options: DiagnosticScope.Global }), + }, + ]; + + await this.messageService.handle(diagnostic, { commandPrompts: options }); + } + + private async updateLanguageServerSetting(resource: Resource): Promise { + // Update settings.json value to Jedi if it's JediLSP. + const settings = this.workspaceService + .getConfiguration('python', resource) + .inspect('languageServer'); + + let configTarget: ConfigurationTarget; + + if (settings?.workspaceValue === LanguageServerType.JediLSP) { + configTarget = ConfigurationTarget.Workspace; + } else if (settings?.globalValue === LanguageServerType.JediLSP) { + configTarget = ConfigurationTarget.Global; + } else { + return; + } + + await this.configurationService.updateSetting( + 'languageServer', + LanguageServerType.Jedi, + resource, + configTarget, + ); + } +} diff --git a/src/client/application/diagnostics/checks/lsNotSupported.ts b/src/client/application/diagnostics/checks/lsNotSupported.ts deleted file mode 100644 index c33154fbbff7..000000000000 --- a/src/client/application/diagnostics/checks/lsNotSupported.ts +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, named } from 'inversify'; -import { DiagnosticSeverity } from 'vscode'; -import { ILanguageServerCompatibilityService } from '../../../activation/types'; -import { IDisposableRegistry, Resource } from '../../../common/types'; -import { Diagnostics } from '../../../common/utils/localize'; -import { IServiceContainer } from '../../../ioc/types'; -import { BaseDiagnostic, BaseDiagnosticsService } from '../base'; -import { IDiagnosticsCommandFactory } from '../commands/types'; -import { DiagnosticCodes } from '../constants'; -import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../promptHandler'; -import { DiagnosticScope, IDiagnostic, IDiagnosticHandlerService } from '../types'; - -export class LSNotSupportedDiagnostic extends BaseDiagnostic { - constructor(message: string, resource: Resource) { - super( - DiagnosticCodes.LSNotSupportedDiagnostic, - message, - DiagnosticSeverity.Warning, - DiagnosticScope.Global, - resource - ); - } -} - -export const LSNotSupportedDiagnosticServiceId = 'LSNotSupportedDiagnosticServiceId'; - -export class LSNotSupportedDiagnosticService extends BaseDiagnosticsService { - constructor( - @inject(IServiceContainer) serviceContainer: IServiceContainer, - @inject(ILanguageServerCompatibilityService) - private readonly lsCompatibility: ILanguageServerCompatibilityService, - @inject(IDiagnosticHandlerService) - @named(DiagnosticCommandPromptHandlerServiceId) - protected readonly messageService: IDiagnosticHandlerService, - @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry - ) { - super([DiagnosticCodes.LSNotSupportedDiagnostic], serviceContainer, disposableRegistry, false); - } - public async diagnose(resource: Resource): Promise { - if (await this.lsCompatibility.isSupported()) { - return []; - } else { - return [new LSNotSupportedDiagnostic(Diagnostics.lsNotSupported(), resource)]; - } - } - protected async onHandle(diagnostics: IDiagnostic[]): Promise { - if (diagnostics.length === 0 || !this.canHandle(diagnostics[0])) { - return; - } - const diagnostic = diagnostics[0]; - if (await this.filterService.shouldIgnoreDiagnostic(diagnostic.code)) { - return; - } - const commandFactory = this.serviceContainer.get(IDiagnosticsCommandFactory); - const options = [ - { - prompt: 'More Info', - command: commandFactory.createCommand(diagnostic, { - type: 'launch', - options: 'https://aka.ms/pythonlsrequirements' - }) - }, - { - prompt: 'Do not show again', - command: commandFactory.createCommand(diagnostic, { type: 'ignore', options: DiagnosticScope.Global }) - } - ]; - - await this.messageService.handle(diagnostic, { commandPrompts: options }); - } -} diff --git a/src/client/application/diagnostics/checks/macPythonInterpreter.ts b/src/client/application/diagnostics/checks/macPythonInterpreter.ts index 7c2acce2bf67..21d6b34fb7c5 100644 --- a/src/client/application/diagnostics/checks/macPythonInterpreter.ts +++ b/src/client/application/diagnostics/checks/macPythonInterpreter.ts @@ -1,45 +1,35 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -'use strict'; - +// eslint-disable-next-line max-classes-per-file import { inject, injectable } from 'inversify'; -import { ConfigurationChangeEvent, DiagnosticSeverity, Uri } from 'vscode'; -import { IWorkspaceService } from '../../../common/application/types'; -import { DeprecatePythonPath } from '../../../common/experiments/groups'; +import { DiagnosticSeverity, l10n } from 'vscode'; import '../../../common/extensions'; import { IPlatformService } from '../../../common/platform/types'; import { IConfigurationService, IDisposableRegistry, - IExperimentsManager, IInterpreterPathService, InterpreterConfigurationScope, - Resource + Resource, } from '../../../common/types'; -import { IInterpreterHelper, IInterpreterService } from '../../../interpreter/contracts'; +import { IInterpreterHelper } from '../../../interpreter/contracts'; import { IServiceContainer } from '../../../ioc/types'; -import { EnvironmentType } from '../../../pythonEnvironments/info'; import { BaseDiagnostic, BaseDiagnosticsService } from '../base'; import { IDiagnosticsCommandFactory } from '../commands/types'; import { DiagnosticCodes } from '../constants'; import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../promptHandler'; import { DiagnosticScope, IDiagnostic, IDiagnosticCommand, IDiagnosticHandlerService } from '../types'; +import { Common } from '../../../common/utils/localize'; const messages = { - [DiagnosticCodes.MacInterpreterSelectedAndHaveOtherInterpretersDiagnostic]: - 'You have selected the macOS system install of Python, which is not recommended for use with the Python extension. Some functionality will be limited, please select a different interpreter.', - [DiagnosticCodes.MacInterpreterSelectedAndNoOtherInterpretersDiagnostic]: - 'The macOS system install of Python is not recommended, some functionality in the extension will be limited. Install another version of Python for the best experience.' + [DiagnosticCodes.MacInterpreterSelected]: l10n.t( + 'The selected macOS system install of Python is not recommended, some functionality in the extension will be limited. [Install another version of Python](https://www.python.org/downloads) or select a different interpreter for the best experience. [Learn more](https://aka.ms/AA7jfor).', + ), }; export class InvalidMacPythonInterpreterDiagnostic extends BaseDiagnostic { - constructor( - code: - | DiagnosticCodes.MacInterpreterSelectedAndNoOtherInterpretersDiagnostic - | DiagnosticCodes.MacInterpreterSelectedAndHaveOtherInterpretersDiagnostic, - resource: Resource - ) { + constructor(code: DiagnosticCodes.MacInterpreterSelected, resource: Resource) { super(code, messages[code], DiagnosticSeverity.Error, DiagnosticScope.WorkspaceFolder, resource); } } @@ -49,84 +39,45 @@ export const InvalidMacPythonInterpreterServiceId = 'InvalidMacPythonInterpreter @injectable() export class InvalidMacPythonInterpreterService extends BaseDiagnosticsService { protected changeThrottleTimeout = 1000; - private timeOut?: NodeJS.Timer | number; + + private timeOut?: NodeJS.Timeout | number; + constructor( @inject(IServiceContainer) serviceContainer: IServiceContainer, - @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, @inject(IPlatformService) private readonly platform: IPlatformService, - @inject(IInterpreterHelper) private readonly helper: IInterpreterHelper + @inject(IInterpreterHelper) private readonly helper: IInterpreterHelper, ) { - super( - [ - DiagnosticCodes.MacInterpreterSelectedAndHaveOtherInterpretersDiagnostic, - DiagnosticCodes.MacInterpreterSelectedAndNoOtherInterpretersDiagnostic - ], - serviceContainer, - disposableRegistry, - true - ); + super([DiagnosticCodes.MacInterpreterSelected], serviceContainer, disposableRegistry, true); this.addPythonPathChangedHandler(); } - public dispose() { - if (this.timeOut) { - // tslint:disable-next-line: no-any - clearTimeout(this.timeOut as any); + + public dispose(): void { + if (this.timeOut && typeof this.timeOut !== 'number') { + clearTimeout(this.timeOut); this.timeOut = undefined; } } + public async diagnose(resource: Resource): Promise { if (!this.platform.isMac) { return []; } const configurationService = this.serviceContainer.get(IConfigurationService); const settings = configurationService.getSettings(resource); - if (settings.disableInstallationChecks === true) { - return []; - } - - const hasInterpreters = await this.interpreterService.hasInterpreters; - if (!hasInterpreters) { - return []; - } - - const currentInterpreter = await this.interpreterService.getActiveInterpreter(resource); - if (!currentInterpreter) { - return []; - } - if (!(await this.helper.isMacDefaultPythonPath(settings.pythonPath))) { return []; } - if (!currentInterpreter || currentInterpreter.envType !== EnvironmentType.Unknown) { - return []; - } - - const interpreters = await this.interpreterService.getInterpreters(resource); - for (const info of interpreters) { - if (!(await this.helper.isMacDefaultPythonPath(info.path))) { - return [ - new InvalidMacPythonInterpreterDiagnostic( - DiagnosticCodes.MacInterpreterSelectedAndHaveOtherInterpretersDiagnostic, - resource - ) - ]; - } - } - return [ - new InvalidMacPythonInterpreterDiagnostic( - DiagnosticCodes.MacInterpreterSelectedAndNoOtherInterpretersDiagnostic, - resource - ) - ]; + return [new InvalidMacPythonInterpreterDiagnostic(DiagnosticCodes.MacInterpreterSelected, resource)]; } + protected async onHandle(diagnostics: IDiagnostic[]): Promise { if (diagnostics.length === 0) { return; } const messageService = this.serviceContainer.get>( IDiagnosticHandlerService, - DiagnosticCommandPromptHandlerServiceId + DiagnosticCommandPromptHandlerServiceId, ); await Promise.all( diagnostics.map(async (diagnostic) => { @@ -136,49 +87,24 @@ export class InvalidMacPythonInterpreterService extends BaseDiagnosticsService { return; } const commandPrompts = this.getCommandPrompts(diagnostic); - return messageService.handle(diagnostic, { commandPrompts, message: diagnostic.message }); - }) + await messageService.handle(diagnostic, { commandPrompts, message: diagnostic.message }); + }), ); } - protected addPythonPathChangedHandler() { - const workspaceService = this.serviceContainer.get(IWorkspaceService); + + protected addPythonPathChangedHandler(): void { const disposables = this.serviceContainer.get(IDisposableRegistry); const interpreterPathService = this.serviceContainer.get(IInterpreterPathService); - const experiments = this.serviceContainer.get(IExperimentsManager); - if (experiments.inExperiment(DeprecatePythonPath.experiment)) { - disposables.push(interpreterPathService.onDidChange((i) => this.onDidChangeConfiguration(undefined, i))); - } - experiments.sendTelemetryIfInExperiment(DeprecatePythonPath.control); - disposables.push(workspaceService.onDidChangeConfiguration(this.onDidChangeConfiguration.bind(this))); + disposables.push(interpreterPathService.onDidChange((i) => this.onDidChangeConfiguration(i))); } + protected async onDidChangeConfiguration( - event?: ConfigurationChangeEvent, - interpreterConfigurationScope?: InterpreterConfigurationScope - ) { - let workspaceUri: Resource; - if (event) { - const workspaceService = this.serviceContainer.get(IWorkspaceService); - const workspacesUris: (Uri | undefined)[] = workspaceService.hasWorkspaceFolders - ? workspaceService.workspaceFolders!.map((workspace) => workspace.uri) - : [undefined]; - const workspaceUriIndex = workspacesUris.findIndex((uri) => - event.affectsConfiguration('python.pythonPath', uri) - ); - if (workspaceUriIndex === -1) { - return; - } - workspaceUri = workspacesUris[workspaceUriIndex]; - } else if (interpreterConfigurationScope) { - workspaceUri = interpreterConfigurationScope.uri; - } else { - throw new Error( - 'One of `interpreterConfigurationScope` or `event` should be defined when calling `onDidChangeConfiguration`.' - ); - } + interpreterConfigurationScope: InterpreterConfigurationScope, + ): Promise { + const workspaceUri = interpreterConfigurationScope.uri; // Lets wait, for more changes, dirty simple throttling. - if (this.timeOut) { - // tslint:disable-next-line: no-any - clearTimeout(this.timeOut as any); + if (this.timeOut && typeof this.timeOut !== 'number') { + clearTimeout(this.timeOut); this.timeOut = undefined; } this.timeOut = setTimeout(() => { @@ -188,50 +114,26 @@ export class InvalidMacPythonInterpreterService extends BaseDiagnosticsService { .ignoreErrors(); }, this.changeThrottleTimeout); } + private getCommandPrompts(diagnostic: IDiagnostic): { prompt: string; command?: IDiagnosticCommand }[] { const commandFactory = this.serviceContainer.get(IDiagnosticsCommandFactory); switch (diagnostic.code) { - case DiagnosticCodes.MacInterpreterSelectedAndHaveOtherInterpretersDiagnostic: { + case DiagnosticCodes.MacInterpreterSelected: { return [ { - prompt: 'Select Python Interpreter', + prompt: Common.selectPythonInterpreter, command: commandFactory.createCommand(diagnostic, { type: 'executeVSCCommand', - options: 'python.setInterpreter' - }) + options: 'python.setInterpreter', + }), }, { - prompt: 'Do not show again', + prompt: Common.doNotShowAgain, command: commandFactory.createCommand(diagnostic, { type: 'ignore', - options: DiagnosticScope.Global - }) - } - ]; - } - case DiagnosticCodes.MacInterpreterSelectedAndNoOtherInterpretersDiagnostic: { - return [ - { - prompt: 'Learn more', - command: commandFactory.createCommand(diagnostic, { - type: 'launch', - options: 'https://code.visualstudio.com/docs/python/python-tutorial#_prerequisites' - }) - }, - { - prompt: 'Download', - command: commandFactory.createCommand(diagnostic, { - type: 'launch', - options: 'https://www.python.org/downloads' - }) + options: DiagnosticScope.Global, + }), }, - { - prompt: 'Do not show again', - command: commandFactory.createCommand(diagnostic, { - type: 'ignore', - options: DiagnosticScope.Global - }) - } ]; } default: { diff --git a/src/client/application/diagnostics/checks/powerShellActivation.ts b/src/client/application/diagnostics/checks/powerShellActivation.ts index a2e8c08ff5c5..85f68db0d6a4 100644 --- a/src/client/application/diagnostics/checks/powerShellActivation.ts +++ b/src/client/application/diagnostics/checks/powerShellActivation.ts @@ -1,15 +1,15 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -'use strict'; - +// eslint-disable-next-line max-classes-per-file import { inject, injectable } from 'inversify'; -import { DiagnosticSeverity } from 'vscode'; +import { DiagnosticSeverity, l10n } from 'vscode'; import '../../../common/extensions'; -import { traceError } from '../../../common/logger'; import { useCommandPromptAsDefaultShell } from '../../../common/terminal/commandPrompt'; import { IConfigurationService, ICurrentProcess, IDisposableRegistry, Resource } from '../../../common/types'; +import { Common } from '../../../common/utils/localize'; import { IServiceContainer } from '../../../ioc/types'; +import { traceError } from '../../../logging'; import { sendTelemetryEvent } from '../../../telemetry'; import { EventName } from '../../../telemetry/constants'; import { BaseDiagnostic, BaseDiagnosticsService } from '../base'; @@ -18,8 +18,9 @@ import { DiagnosticCodes } from '../constants'; import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../promptHandler'; import { DiagnosticScope, IDiagnostic, IDiagnosticHandlerService } from '../types'; -const PowershellActivationNotSupportedWithBatchFilesMessage = - 'Activation of the selected Python environment is not supported in PowerShell. Consider changing your shell to Command Prompt.'; +const PowershellActivationNotSupportedWithBatchFilesMessage = l10n.t( + 'Activation of the selected Python environment is not supported in PowerShell. Consider changing your shell to Command Prompt.', +); export class PowershellActivationNotAvailableDiagnostic extends BaseDiagnostic { constructor(resource: Resource) { @@ -29,7 +30,8 @@ export class PowershellActivationNotAvailableDiagnostic extends BaseDiagnostic { DiagnosticSeverity.Warning, DiagnosticScope.Global, resource, - 'always' + undefined, + 'always', ); } } @@ -40,24 +42,28 @@ export const PowerShellActivationHackDiagnosticsServiceId = @injectable() export class PowerShellActivationHackDiagnosticsService extends BaseDiagnosticsService { protected readonly messageService: IDiagnosticHandlerService; + constructor( @inject(IServiceContainer) serviceContainer: IServiceContainer, - @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry + @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, ) { super( [DiagnosticCodes.EnvironmentActivationInPowerShellWithBatchFilesNotSupportedDiagnostic], serviceContainer, disposableRegistry, - true + true, ); this.messageService = serviceContainer.get>( IDiagnosticHandlerService, - DiagnosticCommandPromptHandlerServiceId + DiagnosticCommandPromptHandlerServiceId, ); } - public async diagnose(_resource: Resource): Promise { + + // eslint-disable-next-line class-methods-use-this + public async diagnose(): Promise { return []; } + protected async onHandle(diagnostics: IDiagnostic[]): Promise { // This class can only handle one type of diagnostic, hence just use first item in list. if (diagnostics.length === 0 || !this.canHandle(diagnostics[0])) { @@ -72,34 +78,34 @@ export class PowerShellActivationHackDiagnosticsService extends BaseDiagnosticsS const configurationService = this.serviceContainer.get(IConfigurationService); const options = [ { - prompt: 'Use Command Prompt', - // tslint:disable-next-line:no-object-literal-type-assertion + prompt: Common.useCommandPrompt, + command: { diagnostic, invoke: async (): Promise => { sendTelemetryEvent(EventName.DIAGNOSTICS_ACTION, undefined, { - action: 'switchToCommandPrompt' + action: 'switchToCommandPrompt', }); useCommandPromptAsDefaultShell(currentProcess, configurationService).catch((ex) => - traceError('Use Command Prompt as default shell', ex) + traceError('Use Command Prompt as default shell', ex), ); - } - } + }, + }, }, { - prompt: 'Ignore' + prompt: Common.ignore, }, { - prompt: 'Always Ignore', - command: commandFactory.createCommand(diagnostic, { type: 'ignore', options: DiagnosticScope.Global }) + prompt: Common.alwaysIgnore, + command: commandFactory.createCommand(diagnostic, { type: 'ignore', options: DiagnosticScope.Global }), }, { - prompt: 'More Info', + prompt: Common.moreInfo, command: commandFactory.createCommand(diagnostic, { type: 'launch', - options: 'https://aka.ms/CondaPwsh' - }) - } + options: 'https://aka.ms/CondaPwsh', + }), + }, ]; await this.messageService.handle(diagnostic, { commandPrompts: options }); diff --git a/src/client/application/diagnostics/checks/pylanceDefault.ts b/src/client/application/diagnostics/checks/pylanceDefault.ts new file mode 100644 index 000000000000..16ee2968c8d6 --- /dev/null +++ b/src/client/application/diagnostics/checks/pylanceDefault.ts @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// eslint-disable-next-line max-classes-per-file +import { inject, named } from 'inversify'; +import { DiagnosticSeverity } from 'vscode'; +import { IDisposableRegistry, IExtensionContext, Resource } from '../../../common/types'; +import { Diagnostics, Common } from '../../../common/utils/localize'; +import { IServiceContainer } from '../../../ioc/types'; +import { BaseDiagnostic, BaseDiagnosticsService } from '../base'; +import { DiagnosticCodes } from '../constants'; +import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../promptHandler'; +import { DiagnosticScope, IDiagnostic, IDiagnosticHandlerService } from '../types'; + +export const PYLANCE_PROMPT_MEMENTO = 'pylanceDefaultPromptMemento'; +const EXTENSION_VERSION_MEMENTO = 'extensionVersion'; + +export class PylanceDefaultDiagnostic extends BaseDiagnostic { + constructor(message: string, resource: Resource) { + super( + DiagnosticCodes.PylanceDefaultDiagnostic, + message, + DiagnosticSeverity.Information, + DiagnosticScope.Global, + resource, + ); + } +} + +export const PylanceDefaultDiagnosticServiceId = 'PylanceDefaultDiagnosticServiceId'; + +export class PylanceDefaultDiagnosticService extends BaseDiagnosticsService { + public initialMementoValue: string | undefined = undefined; + + constructor( + @inject(IServiceContainer) serviceContainer: IServiceContainer, + @inject(IExtensionContext) private readonly context: IExtensionContext, + @inject(IDiagnosticHandlerService) + @named(DiagnosticCommandPromptHandlerServiceId) + protected readonly messageService: IDiagnosticHandlerService, + @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, + ) { + super([DiagnosticCodes.PylanceDefaultDiagnostic], serviceContainer, disposableRegistry, true, true); + + this.initialMementoValue = this.context.globalState.get(EXTENSION_VERSION_MEMENTO); + } + + public async diagnose(resource: Resource): Promise { + if (!(await this.shouldShowPrompt())) { + return []; + } + + return [new PylanceDefaultDiagnostic(Diagnostics.pylanceDefaultMessage, resource)]; + } + + protected async onHandle(diagnostics: IDiagnostic[]): Promise { + if (diagnostics.length === 0 || !this.canHandle(diagnostics[0])) { + return; + } + + const diagnostic = diagnostics[0]; + if (await this.filterService.shouldIgnoreDiagnostic(diagnostic.code)) { + return; + } + + const options = [{ prompt: Common.ok }]; + + await this.messageService.handle(diagnostic, { + commandPrompts: options, + onClose: this.updateMemento.bind(this), + }); + } + + private async updateMemento() { + await this.context.globalState.update(PYLANCE_PROMPT_MEMENTO, true); + } + + private async shouldShowPrompt(): Promise { + const savedVersion: string | undefined = this.initialMementoValue; + const promptShown: boolean | undefined = this.context.globalState.get(PYLANCE_PROMPT_MEMENTO); + + // savedVersion being undefined means that this is the first time the user activates the extension, + // and we don't want to show the prompt to first-time users. + // We set PYLANCE_PROMPT_MEMENTO here to skip the prompt + // in case the user reloads the extension and savedVersion becomes set + if (savedVersion === undefined) { + await this.updateMemento(); + return false; + } + + // promptShown being undefined means that this is the first time we check if we should show the prompt. + return promptShown === undefined; + } +} diff --git a/src/client/application/diagnostics/checks/pythonInterpreter.ts b/src/client/application/diagnostics/checks/pythonInterpreter.ts index 6040234f7f2a..9167e232a417 100644 --- a/src/client/application/diagnostics/checks/pythonInterpreter.ts +++ b/src/client/application/diagnostics/checks/pythonInterpreter.ts @@ -1,16 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -'use strict'; - +// eslint-disable-next-line max-classes-per-file import { inject, injectable } from 'inversify'; -import { DiagnosticSeverity } from 'vscode'; +import { DiagnosticSeverity, l10n } from 'vscode'; import '../../../common/extensions'; -import { IConfigurationService, IDisposableRegistry, Resource } from '../../../common/types'; +import * as path from 'path'; +import { IConfigurationService, IDisposableRegistry, IInterpreterPathService, Resource } from '../../../common/types'; import { IInterpreterService } from '../../../interpreter/contracts'; import { IServiceContainer } from '../../../ioc/types'; -import { sendTelemetryEvent } from '../../../telemetry'; -import { EventName } from '../../../telemetry/constants'; import { BaseDiagnostic, BaseDiagnosticsService } from '../base'; import { IDiagnosticsCommandFactory } from '../commands/types'; import { DiagnosticCodes } from '../constants'; @@ -20,85 +18,245 @@ import { IDiagnostic, IDiagnosticCommand, IDiagnosticHandlerService, - IDiagnosticMessageOnCloseHandler + IDiagnosticMessageOnCloseHandler, } from '../types'; +import { Common, Interpreters } from '../../../common/utils/localize'; +import { Commands } from '../../../common/constants'; +import { ICommandManager, IWorkspaceService } from '../../../common/application/types'; +import { sendTelemetryEvent } from '../../../telemetry'; +import { EventName } from '../../../telemetry/constants'; +import { IExtensionSingleActivationService } from '../../../activation/types'; +import { cache } from '../../../common/utils/decorators'; +import { noop } from '../../../common/utils/misc'; +import { getEnvironmentVariable, getOSType, OSType } from '../../../common/utils/platform'; +import { IFileSystem } from '../../../common/platform/types'; +import { traceError, traceWarn } from '../../../logging'; +import { getExecutable } from '../../../common/process/internal/python'; +import { getSearchPathEnvVarNames } from '../../../common/utils/exec'; +import { IProcessServiceFactory } from '../../../common/process/types'; +import { normCasePath } from '../../../common/platform/fs-paths'; +import { useEnvExtension } from '../../../envExt/api.internal'; const messages = { - [DiagnosticCodes.NoPythonInterpretersDiagnostic]: - 'Python is not installed. Please download and install Python before using the extension.', - [DiagnosticCodes.NoCurrentlySelectedPythonInterpreterDiagnostic]: - 'No Python interpreter is selected. You need to select a Python interpreter to enable features such as IntelliSense, linting, and debugging.' + [DiagnosticCodes.NoPythonInterpretersDiagnostic]: l10n.t( + 'No Python interpreter is selected. Please select a Python interpreter to enable features such as IntelliSense, linting, and debugging.', + ), + [DiagnosticCodes.InvalidPythonInterpreterDiagnostic]: l10n.t( + 'An Invalid Python interpreter is selected{0}, please try changing it to enable features such as IntelliSense, linting, and debugging. See output for more details regarding why the interpreter is invalid.', + ), + [DiagnosticCodes.InvalidComspecDiagnostic]: l10n.t( + 'We detected an issue with one of your environment variables that breaks features such as IntelliSense, linting and debugging. Try setting the "ComSpec" variable to a valid Command Prompt path in your system to fix it.', + ), + [DiagnosticCodes.IncompletePathVarDiagnostic]: l10n.t( + 'We detected an issue with "Path" environment variable that breaks features such as IntelliSense, linting and debugging. Please edit it to make sure it contains the "System32" subdirectories.', + ), + [DiagnosticCodes.DefaultShellErrorDiagnostic]: l10n.t( + 'We detected an issue with your default shell that breaks features such as IntelliSense, linting and debugging. Try resetting "ComSpec" and "Path" environment variables to fix it.', + ), }; export class InvalidPythonInterpreterDiagnostic extends BaseDiagnostic { constructor( - code: - | DiagnosticCodes.NoPythonInterpretersDiagnostic - | DiagnosticCodes.NoCurrentlySelectedPythonInterpreterDiagnostic, - resource: Resource + code: DiagnosticCodes.NoPythonInterpretersDiagnostic | DiagnosticCodes.InvalidPythonInterpreterDiagnostic, + resource: Resource, + workspaceService: IWorkspaceService, + scope = DiagnosticScope.WorkspaceFolder, ) { - super(code, messages[code], DiagnosticSeverity.Error, DiagnosticScope.WorkspaceFolder, resource); + let formatArg = ''; + if ( + workspaceService.workspaceFile && + workspaceService.workspaceFolders && + workspaceService.workspaceFolders?.length > 1 + ) { + // Specify folder name in case of multiroot scenarios + const folder = workspaceService.getWorkspaceFolder(resource); + if (folder) { + formatArg = ` ${l10n.t('for workspace')} ${path.basename(folder.uri.fsPath)}`; + } + } + super(code, messages[code].format(formatArg), DiagnosticSeverity.Error, scope, resource, undefined, 'always'); + } +} + +type DefaultShellDiagnostics = + | DiagnosticCodes.InvalidComspecDiagnostic + | DiagnosticCodes.IncompletePathVarDiagnostic + | DiagnosticCodes.DefaultShellErrorDiagnostic; + +export class DefaultShellDiagnostic extends BaseDiagnostic { + constructor(code: DefaultShellDiagnostics, resource: Resource, scope = DiagnosticScope.Global) { + super(code, messages[code], DiagnosticSeverity.Error, scope, resource, undefined, 'always'); } } export const InvalidPythonInterpreterServiceId = 'InvalidPythonInterpreterServiceId'; @injectable() -export class InvalidPythonInterpreterService extends BaseDiagnosticsService { +export class InvalidPythonInterpreterService extends BaseDiagnosticsService + implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; + constructor( @inject(IServiceContainer) serviceContainer: IServiceContainer, - @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry + @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, ) { super( [ DiagnosticCodes.NoPythonInterpretersDiagnostic, - DiagnosticCodes.NoCurrentlySelectedPythonInterpreterDiagnostic + DiagnosticCodes.InvalidPythonInterpreterDiagnostic, + DiagnosticCodes.InvalidComspecDiagnostic, + DiagnosticCodes.IncompletePathVarDiagnostic, + DiagnosticCodes.DefaultShellErrorDiagnostic, ], serviceContainer, disposableRegistry, - false + false, + ); + } + + public async activate(): Promise { + const commandManager = this.serviceContainer.get(ICommandManager); + this.disposableRegistry.push( + commandManager.registerCommand(Commands.TriggerEnvironmentSelection, (resource: Resource) => + this.triggerEnvSelectionIfNecessary(resource), + ), + ); + const interpreterService = this.serviceContainer.get(IInterpreterService); + this.disposableRegistry.push( + interpreterService.onDidChangeInterpreterConfiguration((e) => + commandManager.executeCommand(Commands.TriggerEnvironmentSelection, e).then(noop, noop), + ), ); } + public async diagnose(resource: Resource): Promise { - const configurationService = this.serviceContainer.get(IConfigurationService); - const settings = configurationService.getSettings(resource); - if (settings.disableInstallationChecks === true) { - return []; - } + return this.diagnoseDefaultShell(resource); + } + public async _manualDiagnose(resource: Resource): Promise { + const workspaceService = this.serviceContainer.get(IWorkspaceService); const interpreterService = this.serviceContainer.get(IInterpreterService); - // hasInterpreters being false can mean one of 2 things: - // 1. getInterpreters hasn't returned any interpreters; - // 2. getInterpreters hasn't run yet. - // We want to make sure that false comes from 1, so we're adding this fix until we refactor interpreter discovery. - // Also see https://github.com/microsoft/vscode-python/issues/3023. - const hasInterpreters = - (await interpreterService.hasInterpreters) || - (await interpreterService.getInterpreters(resource)).length > 0; - - if (!hasInterpreters) { - return [new InvalidPythonInterpreterDiagnostic(DiagnosticCodes.NoPythonInterpretersDiagnostic, resource)]; + const diagnostics = await this.diagnoseDefaultShell(resource); + if (diagnostics.length > 0) { + return diagnostics; + } + const hasInterpreters = await interpreterService.hasInterpreters(); + const interpreterPathService = this.serviceContainer.get(IInterpreterPathService); + const isInterpreterSetToDefault = interpreterPathService.get(resource) === 'python'; + + if (!hasInterpreters && isInterpreterSetToDefault) { + if (useEnvExtension()) { + traceWarn(Interpreters.envExtDiscoveryNoEnvironments); + } + return [ + new InvalidPythonInterpreterDiagnostic( + DiagnosticCodes.NoPythonInterpretersDiagnostic, + resource, + workspaceService, + DiagnosticScope.Global, + ), + ]; } const currentInterpreter = await interpreterService.getActiveInterpreter(resource); if (!currentInterpreter) { + if (useEnvExtension()) { + traceWarn(Interpreters.envExtNoActiveEnvironment); + } return [ new InvalidPythonInterpreterDiagnostic( - DiagnosticCodes.NoCurrentlySelectedPythonInterpreterDiagnostic, - resource - ) + DiagnosticCodes.InvalidPythonInterpreterDiagnostic, + resource, + workspaceService, + ), ]; } + return []; + } + + public async triggerEnvSelectionIfNecessary(resource: Resource): Promise { + const diagnostics = await this._manualDiagnose(resource); + if (!diagnostics.length) { + return true; + } + this.handle(diagnostics).ignoreErrors(); + return false; + } + private async diagnoseDefaultShell(resource: Resource): Promise { + if (getOSType() !== OSType.Windows) { + return []; + } + const interpreterService = this.serviceContainer.get(IInterpreterService); + const currentInterpreter = await interpreterService.getActiveInterpreter(resource); + if (currentInterpreter) { + return []; + } + try { + await this.shellExecPython(); + } catch (ex) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if ((ex as any).errno === -4058) { + // ENOENT (-4058) error is thrown by Node when the default shell is invalid. + traceError('ComSpec is likely set to an invalid value', getEnvironmentVariable('ComSpec')); + if (await this.isComspecInvalid()) { + return [new DefaultShellDiagnostic(DiagnosticCodes.InvalidComspecDiagnostic, resource)]; + } + if (this.isPathVarIncomplete()) { + traceError('PATH env var appears to be incomplete', process.env.Path, process.env.PATH); + return [new DefaultShellDiagnostic(DiagnosticCodes.IncompletePathVarDiagnostic, resource)]; + } + return [new DefaultShellDiagnostic(DiagnosticCodes.DefaultShellErrorDiagnostic, resource)]; + } + } return []; } + + private async isComspecInvalid() { + const comSpec = getEnvironmentVariable('ComSpec') ?? ''; + const fs = this.serviceContainer.get(IFileSystem); + return fs.fileExists(comSpec).then((exists) => !exists); + } + + // eslint-disable-next-line class-methods-use-this + private isPathVarIncomplete() { + const envVars = getSearchPathEnvVarNames(); + const systemRoot = getEnvironmentVariable('SystemRoot') ?? 'C:\\WINDOWS'; + const system32 = path.join(systemRoot, 'system32'); + for (const envVar of envVars) { + const value = getEnvironmentVariable(envVar); + if (value && normCasePath(value).includes(normCasePath(system32))) { + return false; + } + } + return true; + } + + @cache(-1, true) + // eslint-disable-next-line class-methods-use-this + private async shellExecPython() { + const configurationService = this.serviceContainer.get(IConfigurationService); + const { pythonPath } = configurationService.getSettings(); + const [args] = getExecutable(); + const argv = [pythonPath, ...args]; + // Concat these together to make a set of quoted strings + const quoted = argv.reduce( + (p, c) => (p ? `${p} ${c.toCommandArgumentForPythonExt()}` : `${c.toCommandArgumentForPythonExt()}`), + '', + ); + const processServiceFactory = this.serviceContainer.get(IProcessServiceFactory); + const service = await processServiceFactory.create(); + return service.shellExec(quoted, { timeout: 15000 }); + } + + @cache(1000, true) // This is to handle throttling of multiple events. protected async onHandle(diagnostics: IDiagnostic[]): Promise { if (diagnostics.length === 0) { return; } const messageService = this.serviceContainer.get>( IDiagnosticHandlerService, - DiagnosticCommandPromptHandlerServiceId + DiagnosticCommandPromptHandlerServiceId, ); await Promise.all( diagnostics.map(async (diagnostic) => { @@ -106,50 +264,63 @@ export class InvalidPythonInterpreterService extends BaseDiagnosticsService { return; } const commandPrompts = this.getCommandPrompts(diagnostic); - const onClose = this.getOnCloseHandler(diagnostic); - return messageService.handle(diagnostic, { commandPrompts, message: diagnostic.message, onClose }); - }) + const onClose = getOnCloseHandler(diagnostic); + await messageService.handle(diagnostic, { commandPrompts, message: diagnostic.message, onClose }); + }), ); } + private getCommandPrompts(diagnostic: IDiagnostic): { prompt: string; command?: IDiagnosticCommand }[] { const commandFactory = this.serviceContainer.get(IDiagnosticsCommandFactory); - switch (diagnostic.code) { - case DiagnosticCodes.NoPythonInterpretersDiagnostic: { - return [ - { - prompt: 'Download', - command: commandFactory.createCommand(diagnostic, { - type: 'launch', - options: 'https://www.python.org/downloads' - }) - } - ]; - } - case DiagnosticCodes.NoCurrentlySelectedPythonInterpreterDiagnostic: { - return [ - { - prompt: 'Select Python Interpreter', - command: commandFactory.createCommand(diagnostic, { - type: 'executeVSCCommand', - options: 'python.setInterpreter' - }) - } - ]; - } - default: { - throw new Error("Invalid diagnostic for 'InvalidPythonInterpreterService'"); - } - } - } - private getOnCloseHandler(diagnostic: IDiagnostic): IDiagnosticMessageOnCloseHandler | undefined { - if (diagnostic.code === DiagnosticCodes.NoPythonInterpretersDiagnostic) { - return (response?: string) => { - sendTelemetryEvent(EventName.PYTHON_NOT_INSTALLED_PROMPT, undefined, { - selection: response ? 'Download' : 'Ignore' - }); + if ( + diagnostic.code === DiagnosticCodes.InvalidComspecDiagnostic || + diagnostic.code === DiagnosticCodes.IncompletePathVarDiagnostic || + diagnostic.code === DiagnosticCodes.DefaultShellErrorDiagnostic + ) { + const links: Record = { + InvalidComspecDiagnostic: 'https://aka.ms/AAk3djo', + IncompletePathVarDiagnostic: 'https://aka.ms/AAk744c', + DefaultShellErrorDiagnostic: 'https://aka.ms/AAk7qix', }; + return [ + { + prompt: Common.seeInstructions, + command: commandFactory.createCommand(diagnostic, { + type: 'launch', + options: links[diagnostic.code], + }), + }, + ]; + } + const prompts = [ + { + prompt: Common.selectPythonInterpreter, + command: commandFactory.createCommand(diagnostic, { + type: 'executeVSCCommand', + options: Commands.Set_Interpreter, + }), + }, + ]; + if (diagnostic.code === DiagnosticCodes.InvalidPythonInterpreterDiagnostic) { + prompts.push({ + prompt: Common.openOutputPanel, + command: commandFactory.createCommand(diagnostic, { + type: 'executeVSCCommand', + options: Commands.ViewOutput, + }), + }); } + return prompts; + } +} - return; +function getOnCloseHandler(diagnostic: IDiagnostic): IDiagnosticMessageOnCloseHandler | undefined { + if (diagnostic.code === DiagnosticCodes.NoPythonInterpretersDiagnostic) { + return (response?: string) => { + sendTelemetryEvent(EventName.PYTHON_NOT_INSTALLED_PROMPT, undefined, { + selection: response ? 'Download' : 'Ignore', + }); + }; } + return undefined; } diff --git a/src/client/application/diagnostics/checks/pythonPathDeprecated.ts b/src/client/application/diagnostics/checks/pythonPathDeprecated.ts deleted file mode 100644 index 305be378d831..000000000000 --- a/src/client/application/diagnostics/checks/pythonPathDeprecated.ts +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, named } from 'inversify'; -import { ConfigurationTarget, DiagnosticSeverity } from 'vscode'; -import { IWorkspaceService } from '../../../common/application/types'; -import { STANDARD_OUTPUT_CHANNEL } from '../../../common/constants'; -import { DeprecatePythonPath } from '../../../common/experiments/groups'; -import { IDisposableRegistry, IExperimentsManager, IOutputChannel, Resource } from '../../../common/types'; -import { Common, Diagnostics } from '../../../common/utils/localize'; -import { IServiceContainer } from '../../../ioc/types'; -import { BaseDiagnostic, BaseDiagnosticsService } from '../base'; -import { IDiagnosticsCommandFactory } from '../commands/types'; -import { DiagnosticCodes } from '../constants'; -import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../promptHandler'; -import { DiagnosticScope, IDiagnostic, IDiagnosticHandlerService } from '../types'; - -export class PythonPathDeprecatedDiagnostic extends BaseDiagnostic { - constructor(message: string, resource: Resource) { - super( - DiagnosticCodes.PythonPathDeprecatedDiagnostic, - message, - DiagnosticSeverity.Information, - DiagnosticScope.WorkspaceFolder, - resource - ); - } -} - -export const PythonPathDeprecatedDiagnosticServiceId = 'PythonPathDeprecatedDiagnosticServiceId'; - -export class PythonPathDeprecatedDiagnosticService extends BaseDiagnosticsService { - private workspaceService: IWorkspaceService; - private output: IOutputChannel; - constructor( - @inject(IServiceContainer) serviceContainer: IServiceContainer, - @inject(IDiagnosticHandlerService) - @named(DiagnosticCommandPromptHandlerServiceId) - protected readonly messageService: IDiagnosticHandlerService, - @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry - ) { - super([DiagnosticCodes.PythonPathDeprecatedDiagnostic], serviceContainer, disposableRegistry, true); - this.workspaceService = this.serviceContainer.get(IWorkspaceService); - this.output = this.serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); - } - public async diagnose(resource: Resource): Promise { - const experiments = this.serviceContainer.get(IExperimentsManager); - experiments.sendTelemetryIfInExperiment(DeprecatePythonPath.control); - if (!experiments.inExperiment(DeprecatePythonPath.experiment)) { - return []; - } - const setting = this.workspaceService.getConfiguration('python', resource).inspect('pythonPath'); - if (!setting) { - return []; - } - const isCodeWorkspaceSettingSet = this.workspaceService.workspaceFile && setting.workspaceValue !== undefined; - const isSettingsJsonSettingSet = setting.workspaceFolderValue !== undefined; - if (isCodeWorkspaceSettingSet || isSettingsJsonSettingSet) { - return [new PythonPathDeprecatedDiagnostic(Diagnostics.removedPythonPathFromSettings(), resource)]; - } - return []; - } - - public async _removePythonPathFromWorkspaceSettings(resource: Resource) { - const workspaceConfig = this.workspaceService.getConfiguration('python', resource); - await Promise.all([ - workspaceConfig.update('pythonPath', undefined, ConfigurationTarget.Workspace), - workspaceConfig.update('pythonPath', undefined, ConfigurationTarget.WorkspaceFolder) - ]); - } - - protected async onHandle(diagnostics: IDiagnostic[]): Promise { - if (diagnostics.length === 0 || !(await this.canHandle(diagnostics[0]))) { - return; - } - const diagnostic = diagnostics[0]; - if (await this.filterService.shouldIgnoreDiagnostic(diagnostic.code)) { - return; - } - await this._removePythonPathFromWorkspaceSettings(diagnostic.resource); - const commandFactory = this.serviceContainer.get(IDiagnosticsCommandFactory); - const options = [ - { - prompt: Common.openOutputPanel(), - command: { - diagnostic, - invoke: async (): Promise => this.output.show(true) - } - }, - { - prompt: Common.doNotShowAgain(), - command: commandFactory.createCommand(diagnostic, { type: 'ignore', options: DiagnosticScope.Global }) - } - ]; - - await this.messageService.handle(diagnostic, { commandPrompts: options }); - } -} diff --git a/src/client/application/diagnostics/checks/switchToDefaultLS.ts b/src/client/application/diagnostics/checks/switchToDefaultLS.ts new file mode 100644 index 000000000000..bd93a684d9a2 --- /dev/null +++ b/src/client/application/diagnostics/checks/switchToDefaultLS.ts @@ -0,0 +1,80 @@ +/* eslint-disable max-classes-per-file */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable, named } from 'inversify'; +import { ConfigurationTarget, DiagnosticSeverity } from 'vscode'; +import { LanguageServerType } from '../../../activation/types'; +import { IWorkspaceService } from '../../../common/application/types'; +import { IDisposableRegistry, Resource } from '../../../common/types'; +import { Common, SwitchToDefaultLS } from '../../../common/utils/localize'; +import { IServiceContainer } from '../../../ioc/types'; +import { BaseDiagnostic, BaseDiagnosticsService } from '../base'; +import { DiagnosticCodes } from '../constants'; +import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../promptHandler'; +import { DiagnosticScope, IDiagnostic, IDiagnosticHandlerService } from '../types'; + +export class SwitchToDefaultLanguageServerDiagnostic extends BaseDiagnostic { + constructor(message: string, resource: Resource) { + super( + DiagnosticCodes.SwitchToDefaultLanguageServerDiagnostic, + message, + DiagnosticSeverity.Warning, + DiagnosticScope.Global, + resource, + ); + } +} + +export const SwitchToDefaultLanguageServerDiagnosticServiceId = 'SwitchToDefaultLanguageServerDiagnosticServiceId'; + +@injectable() +export class SwitchToDefaultLanguageServerDiagnosticService extends BaseDiagnosticsService { + constructor( + @inject(IServiceContainer) serviceContainer: IServiceContainer, + @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, + @inject(IDiagnosticHandlerService) + @named(DiagnosticCommandPromptHandlerServiceId) + protected readonly messageService: IDiagnosticHandlerService, + @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, + ) { + super([DiagnosticCodes.JediPython27NotSupportedDiagnostic], serviceContainer, disposableRegistry, true, true); + } + + public diagnose(resource: Resource): Promise { + let changed = false; + const config = this.workspaceService.getConfiguration('python'); + const value = config.inspect('languageServer'); + if (value?.workspaceValue === LanguageServerType.Microsoft) { + config.update('languageServer', 'Default', ConfigurationTarget.Workspace); + changed = true; + } + + if (value?.globalValue === LanguageServerType.Microsoft) { + config.update('languageServer', 'Default', ConfigurationTarget.Global); + changed = true; + } + + return Promise.resolve( + changed ? [new SwitchToDefaultLanguageServerDiagnostic(SwitchToDefaultLS.bannerMessage, resource)] : [], + ); + } + + protected async onHandle(diagnostics: IDiagnostic[]): Promise { + if (diagnostics.length === 0 || !this.canHandle(diagnostics[0])) { + return; + } + const diagnostic = diagnostics[0]; + if (await this.filterService.shouldIgnoreDiagnostic(diagnostic.code)) { + return; + } + + await this.messageService.handle(diagnostic, { + commandPrompts: [ + { + prompt: Common.gotIt, + }, + ], + }); + } +} diff --git a/src/client/application/diagnostics/checks/upgradeCodeRunner.ts b/src/client/application/diagnostics/checks/upgradeCodeRunner.ts deleted file mode 100644 index 9285b41d72e7..000000000000 --- a/src/client/application/diagnostics/checks/upgradeCodeRunner.ts +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, named } from 'inversify'; -import { DiagnosticSeverity } from 'vscode'; -import { IWorkspaceService } from '../../../common/application/types'; -import { CODE_RUNNER_EXTENSION_ID } from '../../../common/constants'; -import { DeprecatePythonPath } from '../../../common/experiments/groups'; -import { IDisposableRegistry, IExperimentsManager, IExtensions, Resource } from '../../../common/types'; -import { Common, Diagnostics } from '../../../common/utils/localize'; -import { IServiceContainer } from '../../../ioc/types'; -import { BaseDiagnostic, BaseDiagnosticsService } from '../base'; -import { IDiagnosticsCommandFactory } from '../commands/types'; -import { DiagnosticCodes } from '../constants'; -import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../promptHandler'; -import { DiagnosticScope, IDiagnostic, IDiagnosticHandlerService } from '../types'; - -export class UpgradeCodeRunnerDiagnostic extends BaseDiagnostic { - constructor(message: string, resource: Resource) { - super( - DiagnosticCodes.UpgradeCodeRunnerDiagnostic, - message, - DiagnosticSeverity.Information, - DiagnosticScope.Global, - resource - ); - } -} - -export const UpgradeCodeRunnerDiagnosticServiceId = 'UpgradeCodeRunnerDiagnosticServiceId'; - -export class UpgradeCodeRunnerDiagnosticService extends BaseDiagnosticsService { - public _diagnosticReturned: boolean = false; - private workspaceService: IWorkspaceService; - constructor( - @inject(IServiceContainer) serviceContainer: IServiceContainer, - @inject(IDiagnosticHandlerService) - @named(DiagnosticCommandPromptHandlerServiceId) - protected readonly messageService: IDiagnosticHandlerService, - @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, - @inject(IExtensions) private readonly extensions: IExtensions - ) { - super([DiagnosticCodes.UpgradeCodeRunnerDiagnostic], serviceContainer, disposableRegistry, true); - this.workspaceService = this.serviceContainer.get(IWorkspaceService); - } - public async diagnose(resource: Resource): Promise { - if (this._diagnosticReturned) { - return []; - } - const experiments = this.serviceContainer.get(IExperimentsManager); - experiments.sendTelemetryIfInExperiment(DeprecatePythonPath.control); - if (!experiments.inExperiment(DeprecatePythonPath.experiment)) { - return []; - } - const extension = this.extensions.getExtension(CODE_RUNNER_EXTENSION_ID); - if (!extension) { - return []; - } - // Available feature flags: https://github.com/formulahendry/vscode-code-runner/blob/master/package.json#L6 - const flagValue: boolean | undefined = extension.packageJSON?.featureFlags?.usingNewPythonInterpreterPathApiV2; - if (flagValue) { - // Using new version of Code runner already, no need to upgrade - return []; - } - const pythonExecutor = this.workspaceService - .getConfiguration('code-runner', resource) - .get('executorMap.python'); - if (pythonExecutor?.includes('$pythonPath')) { - this._diagnosticReturned = true; - return [new UpgradeCodeRunnerDiagnostic(Diagnostics.upgradeCodeRunner(), resource)]; - } - return []; - } - - protected async onHandle(diagnostics: IDiagnostic[]): Promise { - if (diagnostics.length === 0 || !(await this.canHandle(diagnostics[0]))) { - return; - } - const diagnostic = diagnostics[0]; - if (await this.filterService.shouldIgnoreDiagnostic(diagnostic.code)) { - return; - } - const commandFactory = this.serviceContainer.get(IDiagnosticsCommandFactory); - const options = [ - { - prompt: Common.doNotShowAgain(), - command: commandFactory.createCommand(diagnostic, { type: 'ignore', options: DiagnosticScope.Global }) - } - ]; - - await this.messageService.handle(diagnostic, { commandPrompts: options }); - } -} diff --git a/src/client/application/diagnostics/commands/execVSCCommand.ts b/src/client/application/diagnostics/commands/execVSCCommand.ts index cb935f05d33d..50c7367f199a 100644 --- a/src/client/application/diagnostics/commands/execVSCCommand.ts +++ b/src/client/application/diagnostics/commands/execVSCCommand.ts @@ -15,7 +15,7 @@ export class ExecuteVSCCommand extends BaseDiagnosticCommand { constructor( diagnostic: IDiagnostic, private serviceContainer: IServiceContainer, - private commandName: CommandsWithoutArgs + private commandName: CommandsWithoutArgs, ) { super(diagnostic); } diff --git a/src/client/application/diagnostics/commands/ignore.ts b/src/client/application/diagnostics/commands/ignore.ts index 07078cc17fe6..311128195975 100644 --- a/src/client/application/diagnostics/commands/ignore.ts +++ b/src/client/application/diagnostics/commands/ignore.ts @@ -13,7 +13,7 @@ export class IgnoreDiagnosticCommand extends BaseDiagnosticCommand { constructor( diagnostic: IDiagnostic, private serviceContainer: IServiceContainer, - private readonly scope: DiagnosticScope + private readonly scope: DiagnosticScope, ) { super(diagnostic); } diff --git a/src/client/application/diagnostics/commands/types.ts b/src/client/application/diagnostics/commands/types.ts index 118dc626be82..f65460b0d113 100644 --- a/src/client/application/diagnostics/commands/types.ts +++ b/src/client/application/diagnostics/commands/types.ts @@ -7,10 +7,10 @@ import { CommandsWithoutArgs } from '../../../common/application/commands'; import { DiagnosticScope, IDiagnostic, IDiagnosticCommand } from '../types'; export type CommandOption = { type: Type; options: Option }; -export type LaunchBrowserOption = CommandOption<'launch', string>; -export type IgnoreDiagnostOption = CommandOption<'ignore', DiagnosticScope>; -export type ExecuteVSCCommandOption = CommandOption<'executeVSCCommand', CommandsWithoutArgs>; -export type CommandOptions = LaunchBrowserOption | IgnoreDiagnostOption | ExecuteVSCCommandOption; +type LaunchBrowserOption = CommandOption<'launch', string>; +type IgnoreDiagnosticOption = CommandOption<'ignore', DiagnosticScope>; +type ExecuteVSCCommandOption = CommandOption<'executeVSCCommand', CommandsWithoutArgs>; +export type CommandOptions = LaunchBrowserOption | IgnoreDiagnosticOption | ExecuteVSCCommandOption; export const IDiagnosticsCommandFactory = Symbol('IDiagnosticsCommandFactory'); diff --git a/src/client/application/diagnostics/constants.ts b/src/client/application/diagnostics/constants.ts index b975ae9222a0..ca2867fc4f49 100644 --- a/src/client/application/diagnostics/constants.ts +++ b/src/client/application/diagnostics/constants.ts @@ -7,16 +7,21 @@ export enum DiagnosticCodes { InvalidEnvironmentPathVariableDiagnostic = 'InvalidEnvironmentPathVariableDiagnostic', InvalidDebuggerTypeDiagnostic = 'InvalidDebuggerTypeDiagnostic', NoPythonInterpretersDiagnostic = 'NoPythonInterpretersDiagnostic', - MacInterpreterSelectedAndNoOtherInterpretersDiagnostic = 'MacInterpreterSelectedAndNoOtherInterpretersDiagnostic', - MacInterpreterSelectedAndHaveOtherInterpretersDiagnostic = 'MacInterpreterSelectedAndHaveOtherInterpretersDiagnostic', + MacInterpreterSelected = 'MacInterpreterSelected', InvalidPythonPathInDebuggerSettingsDiagnostic = 'InvalidPythonPathInDebuggerSettingsDiagnostic', InvalidPythonPathInDebuggerLaunchDiagnostic = 'InvalidPythonPathInDebuggerLaunchDiagnostic', EnvironmentActivationInPowerShellWithBatchFilesNotSupportedDiagnostic = 'EnvironmentActivationInPowerShellWithBatchFilesNotSupportedDiagnostic', - NoCurrentlySelectedPythonInterpreterDiagnostic = 'InvalidPythonInterpreterDiagnostic', + InvalidPythonInterpreterDiagnostic = 'InvalidPythonInterpreterDiagnostic', + InvalidComspecDiagnostic = 'InvalidComspecDiagnostic', + IncompletePathVarDiagnostic = 'IncompletePathVarDiagnostic', + DefaultShellErrorDiagnostic = 'DefaultShellErrorDiagnostic', LSNotSupportedDiagnostic = 'LSNotSupportedDiagnostic', PythonPathDeprecatedDiagnostic = 'PythonPathDeprecatedDiagnostic', JustMyCodeDiagnostic = 'JustMyCodeDiagnostic', ConsoleTypeDiagnostic = 'ConsoleTypeDiagnostic', ConfigPythonPathDiagnostic = 'ConfigPythonPathDiagnostic', - UpgradeCodeRunnerDiagnostic = 'UpgradeCodeRunnerDiagnostic' + PylanceDefaultDiagnostic = 'PylanceDefaultDiagnostic', + JediPython27NotSupportedDiagnostic = 'JediPython27NotSupportedDiagnostic', + SwitchToDefaultLanguageServerDiagnostic = 'SwitchToDefaultLanguageServerDiagnostic', + SwitchToPreReleaseExtensionDiagnostic = 'SwitchToPreReleaseExtensionDiagnostic', } diff --git a/src/client/application/diagnostics/filter.ts b/src/client/application/diagnostics/filter.ts index 7aaf94f490e3..a304a6f558fc 100644 --- a/src/client/application/diagnostics/filter.ts +++ b/src/client/application/diagnostics/filter.ts @@ -10,7 +10,7 @@ import { DiagnosticScope, IDiagnosticFilterService } from './types'; export enum FilterKeys { GlobalDiagnosticFilter = 'GLOBAL_DIAGNOSTICS_FILTER', - WorkspaceDiagnosticFilter = 'WORKSPACE_DIAGNOSTICS_FILTER' + WorkspaceDiagnosticFilter = 'WORKSPACE_DIAGNOSTICS_FILTER', } @injectable() @@ -21,7 +21,7 @@ export class DiagnosticFilterService implements IDiagnosticFilterService { const globalState = factory.createGlobalPersistentState(FilterKeys.GlobalDiagnosticFilter, []); const workspaceState = factory.createWorkspacePersistentState( FilterKeys.WorkspaceDiagnosticFilter, - [] + [], ); return globalState.value.indexOf(code) >= 0 || workspaceState.value.indexOf(code) >= 0; } diff --git a/src/client/application/diagnostics/promptHandler.ts b/src/client/application/diagnostics/promptHandler.ts index 210a7c5ecee3..25b946b2ffb5 100644 --- a/src/client/application/diagnostics/promptHandler.ts +++ b/src/client/application/diagnostics/promptHandler.ts @@ -28,13 +28,13 @@ export class DiagnosticCommandPromptHandlerService implements IDiagnosticHandler } public async handle( diagnostic: IDiagnostic, - options: MessageCommandPrompt = { commandPrompts: [] } + options: MessageCommandPrompt = { commandPrompts: [] }, ): Promise { const prompts = options.commandPrompts.map((option) => option.prompt); const response = await this.displayMessage( options.message ? options.message : diagnostic.message, diagnostic.severity, - prompts + prompts, ); if (options.onClose) { options.onClose(response); @@ -50,7 +50,7 @@ export class DiagnosticCommandPromptHandlerService implements IDiagnosticHandler private async displayMessage( message: string, severity: DiagnosticSeverity, - prompts: string[] + prompts: string[], ): Promise { switch (severity) { case DiagnosticSeverity.Error: { diff --git a/src/client/application/diagnostics/serviceRegistry.ts b/src/client/application/diagnostics/serviceRegistry.ts index 9aefb8e742d9..acf460b88625 100644 --- a/src/client/application/diagnostics/serviceRegistry.ts +++ b/src/client/application/diagnostics/serviceRegistry.ts @@ -3,103 +3,101 @@ 'use strict'; -import { LanguageServerType } from '../../activation/types'; +import { IExtensionSingleActivationService } from '../../activation/types'; import { IServiceManager } from '../../ioc/types'; import { IApplicationDiagnostics } from '../types'; import { ApplicationDiagnostics } from './applicationDiagnostics'; import { EnvironmentPathVariableDiagnosticsService, - EnvironmentPathVariableDiagnosticsServiceId + EnvironmentPathVariableDiagnosticsServiceId, } from './checks/envPathVariable'; -import { - InvalidLaunchJsonDebuggerService, - InvalidLaunchJsonDebuggerServiceId -} from './checks/invalidLaunchJsonDebugger'; import { InvalidPythonPathInDebuggerService, - InvalidPythonPathInDebuggerServiceId + InvalidPythonPathInDebuggerServiceId, } from './checks/invalidPythonPathInDebugger'; -import { LSNotSupportedDiagnosticService, LSNotSupportedDiagnosticServiceId } from './checks/lsNotSupported'; +import { + JediPython27NotSupportedDiagnosticService, + JediPython27NotSupportedDiagnosticServiceId, +} from './checks/jediPython27NotSupported'; import { InvalidMacPythonInterpreterService, - InvalidMacPythonInterpreterServiceId + InvalidMacPythonInterpreterServiceId, } from './checks/macPythonInterpreter'; import { PowerShellActivationHackDiagnosticsService, - PowerShellActivationHackDiagnosticsServiceId + PowerShellActivationHackDiagnosticsServiceId, } from './checks/powerShellActivation'; +import { PylanceDefaultDiagnosticService, PylanceDefaultDiagnosticServiceId } from './checks/pylanceDefault'; import { InvalidPythonInterpreterService, InvalidPythonInterpreterServiceId } from './checks/pythonInterpreter'; import { - PythonPathDeprecatedDiagnosticService, - PythonPathDeprecatedDiagnosticServiceId -} from './checks/pythonPathDeprecated'; -import { UpgradeCodeRunnerDiagnosticService, UpgradeCodeRunnerDiagnosticServiceId } from './checks/upgradeCodeRunner'; + SwitchToDefaultLanguageServerDiagnosticService, + SwitchToDefaultLanguageServerDiagnosticServiceId, +} from './checks/switchToDefaultLS'; import { DiagnosticsCommandFactory } from './commands/factory'; import { IDiagnosticsCommandFactory } from './commands/types'; import { DiagnosticFilterService } from './filter'; import { DiagnosticCommandPromptHandlerService, DiagnosticCommandPromptHandlerServiceId, - MessageCommandPrompt + MessageCommandPrompt, } from './promptHandler'; import { IDiagnosticFilterService, IDiagnosticHandlerService, IDiagnosticsService } from './types'; -export function registerTypes(serviceManager: IServiceManager, languageServerType: LanguageServerType) { +export function registerTypes(serviceManager: IServiceManager): void { serviceManager.addSingleton(IDiagnosticFilterService, DiagnosticFilterService); serviceManager.addSingleton>( IDiagnosticHandlerService, DiagnosticCommandPromptHandlerService, - DiagnosticCommandPromptHandlerServiceId + DiagnosticCommandPromptHandlerServiceId, ); serviceManager.addSingleton( IDiagnosticsService, EnvironmentPathVariableDiagnosticsService, - EnvironmentPathVariableDiagnosticsServiceId + EnvironmentPathVariableDiagnosticsServiceId, ); serviceManager.addSingleton( IDiagnosticsService, - InvalidLaunchJsonDebuggerService, - InvalidLaunchJsonDebuggerServiceId + InvalidPythonInterpreterService, + InvalidPythonInterpreterServiceId, ); - serviceManager.addSingleton( - IDiagnosticsService, + serviceManager.addSingleton( + IExtensionSingleActivationService, InvalidPythonInterpreterService, - InvalidPythonInterpreterServiceId ); serviceManager.addSingleton( IDiagnosticsService, InvalidPythonPathInDebuggerService, - InvalidPythonPathInDebuggerServiceId + InvalidPythonPathInDebuggerServiceId, ); serviceManager.addSingleton( IDiagnosticsService, PowerShellActivationHackDiagnosticsService, - PowerShellActivationHackDiagnosticsServiceId + PowerShellActivationHackDiagnosticsServiceId, ); serviceManager.addSingleton( IDiagnosticsService, InvalidMacPythonInterpreterService, - InvalidMacPythonInterpreterServiceId + InvalidMacPythonInterpreterServiceId, + ); + + serviceManager.addSingleton( + IDiagnosticsService, + PylanceDefaultDiagnosticService, + PylanceDefaultDiagnosticServiceId, ); + serviceManager.addSingleton( IDiagnosticsService, - PythonPathDeprecatedDiagnosticService, - PythonPathDeprecatedDiagnosticServiceId + JediPython27NotSupportedDiagnosticService, + JediPython27NotSupportedDiagnosticServiceId, ); serviceManager.addSingleton( IDiagnosticsService, - UpgradeCodeRunnerDiagnosticService, - UpgradeCodeRunnerDiagnosticServiceId + SwitchToDefaultLanguageServerDiagnosticService, + SwitchToDefaultLanguageServerDiagnosticServiceId, ); + serviceManager.addSingleton(IDiagnosticsCommandFactory, DiagnosticsCommandFactory); serviceManager.addSingleton(IApplicationDiagnostics, ApplicationDiagnostics); - - if (languageServerType === LanguageServerType.Microsoft) { - serviceManager.addSingleton( - IDiagnosticsService, - LSNotSupportedDiagnosticService, - LSNotSupportedDiagnosticServiceId - ); - } } diff --git a/src/client/application/diagnostics/surceMapSupportService.ts b/src/client/application/diagnostics/surceMapSupportService.ts deleted file mode 100644 index b2630f99f5eb..000000000000 --- a/src/client/application/diagnostics/surceMapSupportService.ts +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { ConfigurationTarget } from 'vscode'; -import { IApplicationShell, ICommandManager } from '../../common/application/types'; -import { Commands } from '../../common/constants'; -import { IConfigurationService, IDisposableRegistry } from '../../common/types'; -import { Diagnostics } from '../../common/utils/localize'; -import { ISourceMapSupportService } from './types'; - -@injectable() -export class SourceMapSupportService implements ISourceMapSupportService { - constructor( - @inject(ICommandManager) private readonly commandManager: ICommandManager, - @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry, - @inject(IConfigurationService) private readonly configurationService: IConfigurationService, - @inject(IApplicationShell) private readonly shell: IApplicationShell - ) {} - public register(): void { - this.disposables.push( - this.commandManager.registerCommand(Commands.Enable_SourceMap_Support, this.onEnable, this) - ); - } - public async enable(): Promise { - await this.configurationService.updateSetting( - 'diagnostics.sourceMapsEnabled', - true, - undefined, - ConfigurationTarget.Global - ); - await this.commandManager.executeCommand('workbench.action.reloadWindow'); - } - protected async onEnable(): Promise { - const enableSourceMapsAndReloadVSC = Diagnostics.enableSourceMapsAndReloadVSC(); - const selection = await this.shell.showWarningMessage( - Diagnostics.warnBeforeEnablingSourceMaps(), - enableSourceMapsAndReloadVSC - ); - if (selection === enableSourceMapsAndReloadVSC) { - await this.enable(); - } - } -} diff --git a/src/client/application/diagnostics/types.ts b/src/client/application/diagnostics/types.ts index 56a0e844c670..1dc9a3c689df 100644 --- a/src/client/application/diagnostics/types.ts +++ b/src/client/application/diagnostics/types.ts @@ -10,12 +10,7 @@ import { DiagnosticCodes } from './constants'; export enum DiagnosticScope { Global = 'Global', - WorkspaceFolder = 'WorkspaceFolder' -} - -export enum DiagnosticIgnoreScope { - always = 'always', - session = 'session' + WorkspaceFolder = 'WorkspaceFolder', } export interface IDiagnostic { @@ -31,7 +26,8 @@ export interface IDiagnostic { export const IDiagnosticsService = Symbol('IDiagnosticsService'); export interface IDiagnosticsService { - readonly runInBackground: Boolean; + readonly runInBackground: boolean; + readonly runInUntrustedWorkspace: boolean; diagnose(resource: Resource): Promise; canHandle(diagnostic: IDiagnostic): Promise; handle(diagnostics: IDiagnostic[]): Promise; @@ -57,13 +53,14 @@ export interface IDiagnosticCommand { export type IDiagnosticMessageOnCloseHandler = (response?: string) => void; +export const IInvalidPythonPathInSettings = Symbol('IInvalidPythonPathInSettings'); + +export interface IInvalidPythonPathInSettings extends IDiagnosticsService { + validateInterpreterPathInSettings(resource: Resource): Promise; +} + export const IInvalidPythonPathInDebuggerService = Symbol('IInvalidPythonPathInDebuggerService'); export interface IInvalidPythonPathInDebuggerService extends IDiagnosticsService { validatePythonPath(pythonPath?: string, pythonPathSource?: PythonPathSource, resource?: Uri): Promise; } -export const ISourceMapSupportService = Symbol('ISourceMapSupportService'); -export interface ISourceMapSupportService { - register(): void; - enable(): Promise; -} diff --git a/src/client/application/misc/joinMailingListPrompt.ts b/src/client/application/misc/joinMailingListPrompt.ts deleted file mode 100644 index d120183e1473..000000000000 --- a/src/client/application/misc/joinMailingListPrompt.ts +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -import * as querystring from 'querystring'; -import { IExtensionSingleActivationService } from '../../activation/types'; -import { IApplicationEnvironment, IApplicationShell } from '../../common/application/types'; -import { JoinMailingListPromptVariants } from '../../common/experiments/groups'; -import { IBrowserService, IExperimentService, IPersistentState, IPersistentStateFactory } from '../../common/types'; -import { swallowExceptions } from '../../common/utils/decorators'; -import { Common } from '../../common/utils/localize'; -import { sendTelemetryEvent } from '../../telemetry'; -import { EventName } from '../../telemetry/constants'; - -@injectable() -export class JoinMailingListPrompt implements IExtensionSingleActivationService { - private readonly storage: IPersistentState; - - constructor( - @inject(IApplicationShell) private readonly shell: IApplicationShell, - @inject(IPersistentStateFactory) private readonly factory: IPersistentStateFactory, - @inject(IExperimentService) private readonly experiments: IExperimentService, - @inject(IBrowserService) private browserService: IBrowserService, - @inject(IApplicationEnvironment) private appEnvironment: IApplicationEnvironment - ) { - this.storage = this.factory.createGlobalPersistentState('JoinMailingListPrompt', false); - } - - public async activate(): Promise { - // Only show the prompt if we have never shown it before. True here, means we have - // shown the prompt before. - if (this.storage.value) { - return Promise.resolve(); - } - - let promptContent: string | undefined; - if (await this.experiments.inExperiment(JoinMailingListPromptVariants.variant1)) { - promptContent = await this.experiments.getExperimentValue(JoinMailingListPromptVariants.variant1); - } else if (await this.experiments.inExperiment(JoinMailingListPromptVariants.variant2)) { - promptContent = await this.experiments.getExperimentValue(JoinMailingListPromptVariants.variant2); - } else if (await this.experiments.inExperiment(JoinMailingListPromptVariants.variant3)) { - promptContent = await this.experiments.getExperimentValue(JoinMailingListPromptVariants.variant3); - } else { - // Not in any experiment, so no content to show. - promptContent = undefined; - } - - // Show the prompt only if there is any content to show. - if (promptContent) { - this.showTip(promptContent).ignoreErrors(); - } - - // Disable this prompt for all users after the first load. Even if they - // never saw the prompt. - await this.storage.updateValue(true); - } - - @swallowExceptions('Failed to display tip') - private async showTip(promptContent: string) { - const selection = await this.shell.showInformationMessage( - promptContent, - Common.bannerLabelYes(), - Common.bannerLabelNo() - ); - - if (selection === Common.bannerLabelYes()) { - sendTelemetryEvent(EventName.JOIN_MAILING_LIST_PROMPT, undefined, { selection: 'Yes' }); - const query = querystring.stringify({ - m: encodeURIComponent(this.appEnvironment.sessionId) - }); - const url = `https://aka.ms/python-vscode-mailinglist?${query}`; - this.browserService.launch(url); - } else if (selection === Common.bannerLabelNo()) { - sendTelemetryEvent(EventName.JOIN_MAILING_LIST_PROMPT, undefined, { selection: 'No' }); - } else { - sendTelemetryEvent(EventName.JOIN_MAILING_LIST_PROMPT, undefined, { selection: undefined }); - } - } -} diff --git a/src/client/application/serviceRegistry.ts b/src/client/application/serviceRegistry.ts index 5fc750fa7c05..ff5376d70b24 100644 --- a/src/client/application/serviceRegistry.ts +++ b/src/client/application/serviceRegistry.ts @@ -3,15 +3,9 @@ 'use strict'; -import { IExtensionSingleActivationService, LanguageServerType } from '../activation/types'; import { IServiceManager } from '../ioc/types'; import { registerTypes as diagnosticsRegisterTypes } from './diagnostics/serviceRegistry'; -import { SourceMapSupportService } from './diagnostics/surceMapSupportService'; -import { ISourceMapSupportService } from './diagnostics/types'; -import { JoinMailingListPrompt } from './misc/joinMailingListPrompt'; -export function registerTypes(serviceManager: IServiceManager, languageServerType: LanguageServerType) { - serviceManager.addSingleton(ISourceMapSupportService, SourceMapSupportService); - diagnosticsRegisterTypes(serviceManager, languageServerType); - serviceManager.add(IExtensionSingleActivationService, JoinMailingListPrompt); +export function registerTypes(serviceManager: IServiceManager) { + diagnosticsRegisterTypes(serviceManager); } diff --git a/src/client/application/types.ts b/src/client/application/types.ts index 460ac39807c8..cfd41f7b9746 100644 --- a/src/client/application/types.ts +++ b/src/client/application/types.ts @@ -11,8 +11,6 @@ export interface IApplicationDiagnostics { /** * Perform pre-extension activation health checks. * E.g. validate user environment, etc. - * @returns {Promise} - * @memberof IApplicationDiagnostics */ performPreStartupHealthCheck(resource: Resource): Promise; register(): void; diff --git a/src/client/browser/api.ts b/src/client/browser/api.ts new file mode 100644 index 000000000000..ac2df8d0ffed --- /dev/null +++ b/src/client/browser/api.ts @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { BaseLanguageClient } from 'vscode-languageclient'; +import { LanguageClient } from 'vscode-languageclient/browser'; +import { PYTHON_LANGUAGE } from '../common/constants'; +import { ApiForPylance, TelemetryReporter } from '../pylanceApi'; + +export interface IBrowserExtensionApi { + /** + * @deprecated Temporarily exposed for Pylance until we expose this API generally. Will be removed in an + * iteration or two. + */ + pylance: ApiForPylance; +} + +export function buildApi(reporter: TelemetryReporter): IBrowserExtensionApi { + const api: IBrowserExtensionApi = { + pylance: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + createClient: (...args: any[]): BaseLanguageClient => + new LanguageClient(PYTHON_LANGUAGE, 'Python Language Server', args[0], args[1]), + start: (client: BaseLanguageClient): Promise => client.start(), + stop: (client: BaseLanguageClient): Promise => client.stop(), + getTelemetryReporter: () => reporter, + }, + }; + + return api; +} diff --git a/src/client/browser/extension.ts b/src/client/browser/extension.ts new file mode 100644 index 000000000000..132618430551 --- /dev/null +++ b/src/client/browser/extension.ts @@ -0,0 +1,231 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as vscode from 'vscode'; +import TelemetryReporter from '@vscode/extension-telemetry'; +import { LanguageClientOptions } from 'vscode-languageclient'; +import { LanguageClient } from 'vscode-languageclient/browser'; +import { LanguageClientMiddlewareBase } from '../activation/languageClientMiddlewareBase'; +import { LanguageServerType } from '../activation/types'; +import { AppinsightsKey, PYLANCE_EXTENSION_ID } from '../common/constants'; +import { EventName } from '../telemetry/constants'; +import { createStatusItem } from './intellisenseStatus'; +import { PylanceApi } from '../activation/node/pylanceApi'; +import { buildApi, IBrowserExtensionApi } from './api'; + +interface BrowserConfig { + distUrl: string; // URL to Pylance's dist folder. +} + +let languageClient: LanguageClient | undefined; +let pylanceApi: PylanceApi | undefined; + +export function activate(context: vscode.ExtensionContext): Promise { + const reporter = getTelemetryReporter(); + + const activationPromise = Promise.resolve(buildApi(reporter)); + const pylanceExtension = vscode.extensions.getExtension(PYLANCE_EXTENSION_ID); + if (pylanceExtension) { + // Make sure we run pylance once we activated core extension. + activationPromise.then(() => runPylance(context, pylanceExtension)); + return activationPromise; + } + + const changeDisposable = vscode.extensions.onDidChange(async () => { + const newPylanceExtension = vscode.extensions.getExtension(PYLANCE_EXTENSION_ID); + if (newPylanceExtension) { + changeDisposable.dispose(); + await runPylance(context, newPylanceExtension); + } + }); + + return activationPromise; +} + +export async function deactivate(): Promise { + if (pylanceApi) { + const api = pylanceApi; + pylanceApi = undefined; + await api.client!.stop(); + } + + if (languageClient) { + const client = languageClient; + languageClient = undefined; + + await client.stop(); + await client.dispose(); + } +} + +async function runPylance( + context: vscode.ExtensionContext, + pylanceExtension: vscode.Extension, +): Promise { + context.subscriptions.push(createStatusItem()); + + pylanceExtension = await getActivatedExtension(pylanceExtension); + const api = pylanceExtension.exports; + if (api.client && api.client.isEnabled()) { + pylanceApi = api; + await api.client.start(); + return; + } + + const { extensionUri, packageJSON } = pylanceExtension; + const distUrl = vscode.Uri.joinPath(extensionUri, 'dist'); + + try { + const worker = new Worker(vscode.Uri.joinPath(distUrl, 'browser.server.bundle.js').toString()); + + // Pass the configuration as the first message to the worker so it can + // have info like the URL of the dist folder early enough. + // + // This is the same method used by the TS worker: + // https://github.com/microsoft/vscode/blob/90aa979bb75a795fd8c33d38aee263ea655270d0/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts#L55 + const config: BrowserConfig = { distUrl: distUrl.toString() }; + worker.postMessage(config); + + const middleware = new LanguageClientMiddlewareBase( + undefined, + LanguageServerType.Node, + sendTelemetryEventBrowser, + packageJSON.version, + ); + middleware.connect(); + + const clientOptions: LanguageClientOptions = { + // Register the server for python source files. + documentSelector: [ + { + language: 'python', + }, + ], + synchronize: { + // Synchronize the setting section to the server. + configurationSection: ['python', 'jupyter.runStartupCommands'], + }, + middleware, + }; + + const client = new LanguageClient('python', 'Python Language Server', worker, clientOptions); + languageClient = client; + + context.subscriptions.push( + vscode.commands.registerCommand('python.viewLanguageServerOutput', () => client.outputChannel.show()), + ); + + client.onTelemetry( + (telemetryEvent: { + EventName: EventName; + Properties: { method: string }; + Measurements: number | Record | undefined; + Exception: Error | undefined; + }) => { + const eventName = telemetryEvent.EventName || EventName.LANGUAGE_SERVER_TELEMETRY; + const formattedProperties = { + ...telemetryEvent.Properties, + // Replace all slashes in the method name so it doesn't get scrubbed by @vscode/extension-telemetry. + method: telemetryEvent.Properties.method?.replace(/\//g, '.'), + }; + sendTelemetryEventBrowser( + eventName, + telemetryEvent.Measurements, + formattedProperties, + telemetryEvent.Exception, + ); + }, + ); + + await client.start(); + } catch (e) { + console.log(e); // necessary to use console.log for browser + } +} + +// Duplicate code from telemetry/index.ts to avoid pulling in winston, +// which doesn't support the browser. + +let telemetryReporter: TelemetryReporter | undefined; +function getTelemetryReporter() { + if (telemetryReporter) { + return telemetryReporter; + } + + // eslint-disable-next-line global-require + const Reporter = require('@vscode/extension-telemetry').default as typeof TelemetryReporter; + telemetryReporter = new Reporter(AppinsightsKey, [ + { + lookup: /(errorName|errorMessage|errorStack)/g, + }, + ]); + + return telemetryReporter; +} + +function sendTelemetryEventBrowser( + eventName: EventName, + measuresOrDurationMs?: Record | number, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + properties?: any, + ex?: Error, +): void { + const reporter = getTelemetryReporter(); + const measures = + typeof measuresOrDurationMs === 'number' + ? { duration: measuresOrDurationMs } + : measuresOrDurationMs || undefined; + const customProperties: Record = {}; + const eventNameSent = eventName as string; + + if (properties) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const data = properties as any; + Object.getOwnPropertyNames(data).forEach((prop) => { + if (data[prop] === undefined || data[prop] === null) { + return; + } + try { + // If there are any errors in serializing one property, ignore that and move on. + // Else nothing will be sent. + switch (typeof data[prop]) { + case 'string': + customProperties[prop] = data[prop]; + break; + case 'object': + customProperties[prop] = 'object'; + break; + default: + customProperties[prop] = data[prop].toString(); + break; + } + } catch (exception) { + console.error(`Failed to serialize ${prop} for ${eventName}`, exception); // necessary to use console.log for browser + } + }); + } + + // Add shared properties to telemetry props (we may overwrite existing ones). + // Removed in the browser; there's no setSharedProperty. + // Object.assign(customProperties, sharedProperties); + + if (ex) { + const errorProps = { + errorName: ex.name, + errorStack: ex.stack ?? '', + }; + Object.assign(customProperties, errorProps); + + reporter.sendTelemetryErrorEvent(eventNameSent, customProperties, measures); + } else { + reporter.sendTelemetryEvent(eventNameSent, customProperties, measures); + } +} + +async function getActivatedExtension(extension: vscode.Extension): Promise> { + if (!extension.isActive) { + await extension.activate(); + } + + return extension; +} diff --git a/src/client/browser/intellisenseStatus.ts b/src/client/browser/intellisenseStatus.ts new file mode 100644 index 000000000000..b7a49e86dbb0 --- /dev/null +++ b/src/client/browser/intellisenseStatus.ts @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// IMPORTANT: Do not import any node fs related modules here, as they do not work in browser. +import * as vscode from 'vscode'; +import { Common, LanguageService } from './localize'; + +export function createStatusItem(): vscode.Disposable { + if ('createLanguageStatusItem' in vscode.languages) { + const statusItem = vscode.languages.createLanguageStatusItem('python.projectStatus', { + language: 'python', + }); + statusItem.name = LanguageService.statusItem.name; + statusItem.severity = vscode.LanguageStatusSeverity.Warning; + statusItem.text = LanguageService.statusItem.text; + statusItem.detail = LanguageService.statusItem.detail; + statusItem.command = { + title: Common.learnMore, + command: 'vscode.open', + arguments: [vscode.Uri.parse('https://aka.ms/AAdzyh4')], + }; + return statusItem; + } + // eslint-disable-next-line @typescript-eslint/no-empty-function + return { dispose: () => undefined }; +} diff --git a/src/client/browser/localize.ts b/src/client/browser/localize.ts new file mode 100644 index 000000000000..fd50dbcc7093 --- /dev/null +++ b/src/client/browser/localize.ts @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { l10n } from 'vscode'; + +/* eslint-disable @typescript-eslint/no-namespace */ + +// IMPORTANT: Do not import any node fs related modules here, as they do not work in browser. + +export namespace LanguageService { + export const statusItem = { + name: l10n.t('Python IntelliSense Status'), + text: l10n.t('Partial Mode'), + detail: l10n.t('Limited IntelliSense provided by Pylance'), + }; +} + +export namespace Common { + export const learnMore = l10n.t('Learn more'); +} diff --git a/src/client/chat/baseTool.ts b/src/client/chat/baseTool.ts new file mode 100644 index 000000000000..d8e2e1d60d42 --- /dev/null +++ b/src/client/chat/baseTool.ts @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { + CancellationToken, + LanguageModelTextPart, + LanguageModelTool, + LanguageModelToolInvocationOptions, + LanguageModelToolInvocationPrepareOptions, + LanguageModelToolResult, + PreparedToolInvocation, + Uri, + workspace, +} from 'vscode'; +import { IResourceReference, isCancellationError, resolveFilePath } from './utils'; +import { ErrorWithTelemetrySafeReason } from '../common/errors/errorUtils'; +import { sendTelemetryEvent } from '../telemetry'; +import { EventName } from '../telemetry/constants'; +import { StopWatch } from '../common/utils/stopWatch'; + +export abstract class BaseTool implements LanguageModelTool { + protected extraTelemetryProperties: Record = {}; + constructor(private readonly toolName: string) {} + + async invoke( + options: LanguageModelToolInvocationOptions, + token: CancellationToken, + ): Promise { + if (!workspace.isTrusted) { + return new LanguageModelToolResult([ + new LanguageModelTextPart('Cannot use this tool in an untrusted workspace.'), + ]); + } + this.extraTelemetryProperties = {}; + let error: Error | undefined; + const resource = resolveFilePath(options.input.resourcePath); + const stopWatch = new StopWatch(); + try { + return await this.invokeImpl(options, resource, token); + } catch (ex) { + error = ex as any; + throw ex; + } finally { + const isCancelled = token.isCancellationRequested || (error ? isCancellationError(error) : false); + const failed = !!error || isCancelled; + const failureCategory = isCancelled + ? 'cancelled' + : error + ? error instanceof ErrorWithTelemetrySafeReason + ? error.telemetrySafeReason + : 'error' + : undefined; + sendTelemetryEvent(EventName.INVOKE_TOOL, stopWatch.elapsedTime, { + toolName: this.toolName, + failed, + failureCategory, + ...this.extraTelemetryProperties, + }); + } + } + protected abstract invokeImpl( + options: LanguageModelToolInvocationOptions, + resource: Uri | undefined, + token: CancellationToken, + ): Promise; + + async prepareInvocation( + options: LanguageModelToolInvocationPrepareOptions, + token: CancellationToken, + ): Promise { + const resource = resolveFilePath(options.input.resourcePath); + return this.prepareInvocationImpl(options, resource, token); + } + + protected abstract prepareInvocationImpl( + options: LanguageModelToolInvocationPrepareOptions, + resource: Uri | undefined, + token: CancellationToken, + ): Promise; +} diff --git a/src/client/chat/configurePythonEnvTool.ts b/src/client/chat/configurePythonEnvTool.ts new file mode 100644 index 000000000000..914a92f81c52 --- /dev/null +++ b/src/client/chat/configurePythonEnvTool.ts @@ -0,0 +1,131 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { + CancellationToken, + LanguageModelTool, + LanguageModelToolInvocationOptions, + LanguageModelToolInvocationPrepareOptions, + LanguageModelToolResult, + PreparedToolInvocation, + Uri, + workspace, + lm, +} from 'vscode'; +import { PythonExtension } from '../api/types'; +import { IServiceContainer } from '../ioc/types'; +import { ICodeExecutionService } from '../terminals/types'; +import { TerminalCodeExecutionProvider } from '../terminals/codeExecution/terminalCodeExecution'; +import { + getEnvDetailsForResponse, + getEnvTypeForTelemetry, + getToolResponseIfNotebook, + IResourceReference, + isCancellationError, + raceCancellationError, +} from './utils'; +import { ITerminalHelper } from '../common/terminal/types'; +import { IRecommendedEnvironmentService } from '../interpreter/configuration/types'; +import { CreateVirtualEnvTool } from './createVirtualEnvTool'; +import { ISelectPythonEnvToolArguments, SelectPythonEnvTool } from './selectEnvTool'; +import { BaseTool } from './baseTool'; + +export class ConfigurePythonEnvTool extends BaseTool + implements LanguageModelTool { + private readonly terminalExecutionService: TerminalCodeExecutionProvider; + private readonly terminalHelper: ITerminalHelper; + private readonly recommendedEnvService: IRecommendedEnvironmentService; + public static readonly toolName = 'configure_python_environment'; + constructor( + private readonly api: PythonExtension['environments'], + private readonly serviceContainer: IServiceContainer, + private readonly createEnvTool: CreateVirtualEnvTool, + ) { + super(ConfigurePythonEnvTool.toolName); + this.terminalExecutionService = this.serviceContainer.get( + ICodeExecutionService, + 'standard', + ); + this.terminalHelper = this.serviceContainer.get(ITerminalHelper); + this.recommendedEnvService = this.serviceContainer.get( + IRecommendedEnvironmentService, + ); + } + + async invokeImpl( + options: LanguageModelToolInvocationOptions, + resource: Uri | undefined, + token: CancellationToken, + ): Promise { + const notebookResponse = getToolResponseIfNotebook(resource); + if (notebookResponse) { + this.extraTelemetryProperties.resolveOutcome = 'notebook'; + return notebookResponse; + } + + const workspaceSpecificEnv = await raceCancellationError( + this.hasAlreadyGotAWorkspaceSpecificEnvironment(resource), + token, + ); + + if (workspaceSpecificEnv) { + this.extraTelemetryProperties.resolveOutcome = 'existingWorkspaceEnv'; + this.extraTelemetryProperties.envType = getEnvTypeForTelemetry(workspaceSpecificEnv); + return getEnvDetailsForResponse( + workspaceSpecificEnv, + this.api, + this.terminalExecutionService, + this.terminalHelper, + resource, + token, + ); + } + + if (await this.createEnvTool.shouldCreateNewVirtualEnv(resource, token)) { + try { + const result = await lm.invokeTool(CreateVirtualEnvTool.toolName, options, token); + this.extraTelemetryProperties.resolveOutcome = 'createdVirtualEnv'; + return result; + } catch (ex) { + if (isCancellationError(ex)) { + const input: ISelectPythonEnvToolArguments = { + ...options.input, + reason: 'cancelled', + }; + // If the user cancelled the tool, then we should invoke the select env tool. + this.extraTelemetryProperties.resolveOutcome = 'selectedEnvAfterCancelledCreate'; + return lm.invokeTool(SelectPythonEnvTool.toolName, { ...options, input }, token); + } + throw ex; + } + } else { + const input: ISelectPythonEnvToolArguments = { + ...options.input, + }; + this.extraTelemetryProperties.resolveOutcome = 'selectedEnv'; + return lm.invokeTool(SelectPythonEnvTool.toolName, { ...options, input }, token); + } + } + + async prepareInvocationImpl( + _options: LanguageModelToolInvocationPrepareOptions, + _resource: Uri | undefined, + _token: CancellationToken, + ): Promise { + return { + invocationMessage: 'Configuring a Python Environment', + }; + } + + async hasAlreadyGotAWorkspaceSpecificEnvironment(resource: Uri | undefined) { + const recommededEnv = await this.recommendedEnvService.getRecommededEnvironment(resource); + // Already selected workspace env, hence nothing to do. + if (recommededEnv?.reason === 'workspaceUserSelected' && workspace.workspaceFolders?.length) { + return recommededEnv.environment; + } + // No workspace folders, and the user selected a global environment. + if (recommededEnv?.reason === 'globalUserSelected' && !workspace.workspaceFolders?.length) { + return recommededEnv.environment; + } + } +} diff --git a/src/client/chat/createVirtualEnvTool.ts b/src/client/chat/createVirtualEnvTool.ts new file mode 100644 index 000000000000..56760d2b4bef --- /dev/null +++ b/src/client/chat/createVirtualEnvTool.ts @@ -0,0 +1,246 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { + CancellationError, + CancellationToken, + commands, + l10n, + LanguageModelTool, + LanguageModelToolInvocationOptions, + LanguageModelToolInvocationPrepareOptions, + LanguageModelToolResult, + PreparedToolInvocation, + Uri, + workspace, +} from 'vscode'; +import { PythonExtension, ResolvedEnvironment } from '../api/types'; +import { IServiceContainer } from '../ioc/types'; +import { ICodeExecutionService } from '../terminals/types'; +import { TerminalCodeExecutionProvider } from '../terminals/codeExecution/terminalCodeExecution'; +import { + doesWorkspaceHaveVenvOrCondaEnv, + getDisplayVersion, + getEnvDetailsForResponse, + IResourceReference, + isCancellationError, + raceCancellationError, +} from './utils'; +import { ITerminalHelper } from '../common/terminal/types'; +import { raceTimeout, sleep } from '../common/utils/async'; +import { IInterpreterPathService } from '../common/types'; +import { DisposableStore } from '../common/utils/resourceLifecycle'; +import { IRecommendedEnvironmentService } from '../interpreter/configuration/types'; +import { EnvironmentType } from '../pythonEnvironments/info'; +import { IDiscoveryAPI } from '../pythonEnvironments/base/locator'; +import { convertEnvInfoToPythonEnvironment } from '../pythonEnvironments/legacyIOC'; +import { sortInterpreters } from '../interpreter/helpers'; +import { isStableVersion } from '../pythonEnvironments/info/pythonVersion'; +import { createVirtualEnvironment } from '../pythonEnvironments/creation/createEnvApi'; +import { traceError, traceVerbose, traceWarn } from '../logging'; +import { StopWatch } from '../common/utils/stopWatch'; +import { useEnvExtension } from '../envExt/api.internal'; +import { PythonEnvironment } from '../envExt/types'; +import { hideEnvCreation } from '../pythonEnvironments/creation/provider/hideEnvCreation'; +import { BaseTool } from './baseTool'; + +interface ICreateVirtualEnvToolParams extends IResourceReference { + packageList?: string[]; // Added only becausewe have ability to create a virtual env with list of packages same tool within the in Python Env extension. +} + +export class CreateVirtualEnvTool extends BaseTool + implements LanguageModelTool { + private readonly terminalExecutionService: TerminalCodeExecutionProvider; + private readonly terminalHelper: ITerminalHelper; + private readonly recommendedEnvService: IRecommendedEnvironmentService; + + public static readonly toolName = 'create_virtual_environment'; + constructor( + private readonly discoveryApi: IDiscoveryAPI, + private readonly api: PythonExtension['environments'], + private readonly serviceContainer: IServiceContainer, + ) { + super(CreateVirtualEnvTool.toolName); + this.terminalExecutionService = this.serviceContainer.get( + ICodeExecutionService, + 'standard', + ); + this.terminalHelper = this.serviceContainer.get(ITerminalHelper); + this.recommendedEnvService = this.serviceContainer.get( + IRecommendedEnvironmentService, + ); + } + + async invokeImpl( + options: LanguageModelToolInvocationOptions, + resource: Uri | undefined, + token: CancellationToken, + ): Promise { + let info = await this.getPreferredEnvForCreation(resource); + if (!info) { + traceWarn(`Called ${CreateVirtualEnvTool.toolName} tool not invoked, no preferred environment found.`); + throw new CancellationError(); + } + const { workspaceFolder, preferredGlobalPythonEnv } = info; + const interpreterPathService = this.serviceContainer.get(IInterpreterPathService); + const disposables = new DisposableStore(); + try { + disposables.add(hideEnvCreation()); + const interpreterChanged = new Promise((resolve) => { + disposables.add(interpreterPathService.onDidChange(() => resolve())); + }); + + let createdEnvPath: string | undefined = undefined; + if (useEnvExtension()) { + const result: PythonEnvironment | undefined = await commands.executeCommand('python-envs.createAny', { + quickCreate: true, + additionalPackages: options.input.packageList || [], + uri: workspaceFolder.uri, + selectEnvironment: true, + }); + createdEnvPath = result?.environmentPath.fsPath; + } else { + const created = await raceCancellationError( + createVirtualEnvironment({ + interpreter: preferredGlobalPythonEnv.id, + workspaceFolder, + }), + token, + ); + createdEnvPath = created?.path; + } + if (!createdEnvPath) { + traceWarn(`${CreateVirtualEnvTool.toolName} tool not invoked, virtual env not created.`); + throw new CancellationError(); + } + + // Wait a few secs to ensure the env is selected as the active environment.. + // If this doesn't work, then something went wrong. + await raceTimeout(5_000, interpreterChanged); + + const stopWatch = new StopWatch(); + let env: ResolvedEnvironment | undefined; + while (stopWatch.elapsedTime < 5_000 || !env) { + env = await this.api.resolveEnvironment(createdEnvPath); + if (env) { + break; + } else { + traceVerbose( + `${CreateVirtualEnvTool.toolName} tool invoked, env created but not yet resolved, waiting...`, + ); + await sleep(200); + } + } + if (!env) { + traceError(`${CreateVirtualEnvTool.toolName} tool invoked, env created but unable to resolve details.`); + throw new CancellationError(); + } + return await getEnvDetailsForResponse( + env, + this.api, + this.terminalExecutionService, + this.terminalHelper, + resource, + token, + ); + } catch (ex) { + if (!isCancellationError(ex)) { + traceError( + `${ + CreateVirtualEnvTool.toolName + } tool failed to create virtual environment for resource ${resource?.toString()}`, + ex, + ); + } + throw ex; + } finally { + disposables.dispose(); + } + } + + public async shouldCreateNewVirtualEnv(resource: Uri | undefined, token: CancellationToken): Promise { + if (doesWorkspaceHaveVenvOrCondaEnv(resource, this.api)) { + // If we already have a .venv or .conda in this workspace, then do not prompt to create a virtual environment. + return false; + } + + const info = await raceCancellationError(this.getPreferredEnvForCreation(resource), token); + return info ? true : false; + } + + async prepareInvocationImpl( + _options: LanguageModelToolInvocationPrepareOptions, + resource: Uri | undefined, + token: CancellationToken, + ): Promise { + const info = await raceCancellationError(this.getPreferredEnvForCreation(resource), token); + if (!info) { + return {}; + } + const { preferredGlobalPythonEnv } = info; + const version = getDisplayVersion(preferredGlobalPythonEnv.version); + return { + confirmationMessages: { + title: l10n.t('Create a Virtual Environment{0}?', version ? ` (${version})` : ''), + message: l10n.t(`Virtual Environments provide the benefit of package isolation and more.`), + }, + invocationMessage: l10n.t('Creating a Virtual Environment'), + }; + } + async hasAlreadyGotAWorkspaceSpecificEnvironment(resource: Uri | undefined) { + const recommededEnv = await this.recommendedEnvService.getRecommededEnvironment(resource); + // Already selected workspace env, hence nothing to do. + if (recommededEnv?.reason === 'workspaceUserSelected' && workspace.workspaceFolders?.length) { + return recommededEnv.environment; + } + // No workspace folders, and the user selected a global environment. + if (recommededEnv?.reason === 'globalUserSelected' && !workspace.workspaceFolders?.length) { + return recommededEnv.environment; + } + } + + private async getPreferredEnvForCreation(resource: Uri | undefined) { + if (await this.hasAlreadyGotAWorkspaceSpecificEnvironment(resource)) { + return undefined; + } + + // If we have a resource or have only one workspace folder && there is no .venv and no workspace specific environment. + // Then lets recommend creating a virtual environment. + const workspaceFolder = + resource && workspace.workspaceFolders?.length + ? workspace.getWorkspaceFolder(resource) + : workspace.workspaceFolders?.length === 1 + ? workspace.workspaceFolders[0] + : undefined; + if (!workspaceFolder) { + // No workspace folder, hence no need to create a virtual environment. + return undefined; + } + + // Find the latest stable version of Python from the list of know envs. + let globalPythonEnvs = this.discoveryApi + .getEnvs() + .map((env) => convertEnvInfoToPythonEnvironment(env)) + .filter((env) => + [ + EnvironmentType.System, + EnvironmentType.MicrosoftStore, + EnvironmentType.Global, + EnvironmentType.Pyenv, + ].includes(env.envType), + ) + .filter((env) => env.version && isStableVersion(env.version)); + + globalPythonEnvs = sortInterpreters(globalPythonEnvs); + const preferredGlobalPythonEnv = globalPythonEnvs.length + ? this.api.known.find((e) => e.id === globalPythonEnvs[globalPythonEnvs.length - 1].id) + : undefined; + + return workspaceFolder && preferredGlobalPythonEnv + ? { + workspaceFolder, + preferredGlobalPythonEnv, + } + : undefined; + } +} diff --git a/src/client/chat/getExecutableTool.ts b/src/client/chat/getExecutableTool.ts new file mode 100644 index 000000000000..38dabce644a7 --- /dev/null +++ b/src/client/chat/getExecutableTool.ts @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { + CancellationToken, + l10n, + LanguageModelTextPart, + LanguageModelTool, + LanguageModelToolInvocationOptions, + LanguageModelToolInvocationPrepareOptions, + LanguageModelToolResult, + PreparedToolInvocation, + Uri, +} from 'vscode'; +import { PythonExtension } from '../api/types'; +import { IServiceContainer } from '../ioc/types'; +import { ICodeExecutionService } from '../terminals/types'; +import { TerminalCodeExecutionProvider } from '../terminals/codeExecution/terminalCodeExecution'; +import { + getEnvDisplayName, + getEnvironmentDetails, + getEnvTypeForTelemetry, + getToolResponseIfNotebook, + IResourceReference, + raceCancellationError, +} from './utils'; +import { ITerminalHelper } from '../common/terminal/types'; +import { IDiscoveryAPI } from '../pythonEnvironments/base/locator'; +import { BaseTool } from './baseTool'; + +export class GetExecutableTool extends BaseTool implements LanguageModelTool { + private readonly terminalExecutionService: TerminalCodeExecutionProvider; + private readonly terminalHelper: ITerminalHelper; + public static readonly toolName = 'get_python_executable_details'; + constructor( + private readonly api: PythonExtension['environments'], + private readonly serviceContainer: IServiceContainer, + private readonly discovery: IDiscoveryAPI, + ) { + super(GetExecutableTool.toolName); + this.terminalExecutionService = this.serviceContainer.get( + ICodeExecutionService, + 'standard', + ); + this.terminalHelper = this.serviceContainer.get(ITerminalHelper); + } + async invokeImpl( + _options: LanguageModelToolInvocationOptions, + resourcePath: Uri | undefined, + token: CancellationToken, + ): Promise { + const notebookResponse = getToolResponseIfNotebook(resourcePath); + if (notebookResponse) { + return notebookResponse; + } + + const envPath = this.api.getActiveEnvironmentPath(resourcePath); + const environment = await raceCancellationError(this.api.resolveEnvironment(envPath), token); + if (environment) { + this.extraTelemetryProperties.envType = getEnvTypeForTelemetry(environment); + } + + const message = await getEnvironmentDetails( + resourcePath, + this.api, + this.terminalExecutionService, + this.terminalHelper, + undefined, + token, + ); + return new LanguageModelToolResult([new LanguageModelTextPart(message)]); + } + + async prepareInvocationImpl( + _options: LanguageModelToolInvocationPrepareOptions, + resourcePath: Uri | undefined, + token: CancellationToken, + ): Promise { + if (getToolResponseIfNotebook(resourcePath)) { + return {}; + } + + const envName = await raceCancellationError(getEnvDisplayName(this.discovery, resourcePath, this.api), token); + return { + invocationMessage: envName + ? l10n.t('Fetching Python executable information for {0}', envName) + : l10n.t('Fetching Python executable information'), + }; + } +} diff --git a/src/client/chat/getPythonEnvTool.ts b/src/client/chat/getPythonEnvTool.ts new file mode 100644 index 000000000000..d25d72baeba8 --- /dev/null +++ b/src/client/chat/getPythonEnvTool.ts @@ -0,0 +1,131 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { + CancellationToken, + l10n, + LanguageModelTextPart, + LanguageModelTool, + LanguageModelToolInvocationOptions, + LanguageModelToolInvocationPrepareOptions, + LanguageModelToolResult, + PreparedToolInvocation, + Uri, +} from 'vscode'; +import { PythonExtension } from '../api/types'; +import { IServiceContainer } from '../ioc/types'; +import { ICodeExecutionService } from '../terminals/types'; +import { TerminalCodeExecutionProvider } from '../terminals/codeExecution/terminalCodeExecution'; +import { IProcessServiceFactory, IPythonExecutionFactory } from '../common/process/types'; +import { + getEnvironmentDetails, + getEnvTypeForTelemetry, + getToolResponseIfNotebook, + IResourceReference, + raceCancellationError, +} from './utils'; +import { getPythonPackagesResponse } from './listPackagesTool'; +import { ITerminalHelper } from '../common/terminal/types'; +import { getEnvExtApi, useEnvExtension } from '../envExt/api.internal'; +import { ErrorWithTelemetrySafeReason } from '../common/errors/errorUtils'; +import { BaseTool } from './baseTool'; + +export class GetEnvironmentInfoTool extends BaseTool + implements LanguageModelTool { + private readonly terminalExecutionService: TerminalCodeExecutionProvider; + private readonly pythonExecFactory: IPythonExecutionFactory; + private readonly processServiceFactory: IProcessServiceFactory; + private readonly terminalHelper: ITerminalHelper; + public static readonly toolName = 'get_python_environment_details'; + constructor( + private readonly api: PythonExtension['environments'], + private readonly serviceContainer: IServiceContainer, + ) { + super(GetEnvironmentInfoTool.toolName); + this.terminalExecutionService = this.serviceContainer.get( + ICodeExecutionService, + 'standard', + ); + this.pythonExecFactory = this.serviceContainer.get(IPythonExecutionFactory); + this.processServiceFactory = this.serviceContainer.get(IProcessServiceFactory); + this.terminalHelper = this.serviceContainer.get(ITerminalHelper); + } + + async invokeImpl( + _options: LanguageModelToolInvocationOptions, + resourcePath: Uri | undefined, + token: CancellationToken, + ): Promise { + const notebookResponse = getToolResponseIfNotebook(resourcePath); + if (notebookResponse) { + return notebookResponse; + } + + // environment + const envPath = this.api.getActiveEnvironmentPath(resourcePath); + const environment = await raceCancellationError(this.api.resolveEnvironment(envPath), token); + if (!environment || !environment.version) { + throw new ErrorWithTelemetrySafeReason( + 'No environment found for the provided resource path: ' + resourcePath?.fsPath, + 'noEnvFound', + ); + } + this.extraTelemetryProperties.envType = getEnvTypeForTelemetry(environment); + + let packages = ''; + let responsePackageCount = 0; + if (useEnvExtension()) { + const api = await getEnvExtApi(); + const env = await api.getEnvironment(resourcePath); + const pkgs = env ? await api.getPackages(env) : []; + if (pkgs && pkgs.length > 0) { + responsePackageCount = pkgs.length; + // Installed Python packages, each in the format or (). The version may be omitted if unknown. Returns an empty array if no packages are installed. + const response = [ + 'Below is a list of the Python packages, each in the format or (). The version may be omitted if unknown: ', + ]; + pkgs.forEach((pkg) => { + const version = pkg.version; + response.push(version ? `- ${pkg.name} (${version})` : `- ${pkg.name}`); + }); + packages = response.join('\n'); + } + } + if (!packages) { + packages = await getPythonPackagesResponse( + environment, + this.pythonExecFactory, + this.processServiceFactory, + resourcePath, + token, + ); + // Count lines starting with '- ' to get the number of packages + responsePackageCount = (packages.match(/^- /gm) || []).length; + } + this.extraTelemetryProperties.responsePackageCount = String(responsePackageCount); + const message = await getEnvironmentDetails( + resourcePath, + this.api, + this.terminalExecutionService, + this.terminalHelper, + packages, + token, + ); + + return new LanguageModelToolResult([new LanguageModelTextPart(message)]); + } + + async prepareInvocationImpl( + _options: LanguageModelToolInvocationPrepareOptions, + resourcePath: Uri | undefined, + _token: CancellationToken, + ): Promise { + if (getToolResponseIfNotebook(resourcePath)) { + return {}; + } + + return { + invocationMessage: l10n.t('Fetching Python environment information'), + }; + } +} diff --git a/src/client/chat/index.ts b/src/client/chat/index.ts new file mode 100644 index 000000000000..b548860eaae3 --- /dev/null +++ b/src/client/chat/index.ts @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { lm } from 'vscode'; +import { PythonExtension } from '../api/types'; +import { IServiceContainer } from '../ioc/types'; +import { InstallPackagesTool } from './installPackagesTool'; +import { IExtensionContext } from '../common/types'; +import { DisposableStore } from '../common/utils/resourceLifecycle'; +import { IDiscoveryAPI } from '../pythonEnvironments/base/locator'; +import { GetExecutableTool } from './getExecutableTool'; +import { GetEnvironmentInfoTool } from './getPythonEnvTool'; +import { ConfigurePythonEnvTool } from './configurePythonEnvTool'; +import { SelectPythonEnvTool } from './selectEnvTool'; +import { CreateVirtualEnvTool } from './createVirtualEnvTool'; + +export function registerTools( + context: IExtensionContext, + discoverApi: IDiscoveryAPI, + environmentsApi: PythonExtension['environments'], + serviceContainer: IServiceContainer, +) { + const ourTools = new DisposableStore(); + context.subscriptions.push(ourTools); + + ourTools.add( + lm.registerTool(GetEnvironmentInfoTool.toolName, new GetEnvironmentInfoTool(environmentsApi, serviceContainer)), + ); + ourTools.add( + lm.registerTool( + GetExecutableTool.toolName, + new GetExecutableTool(environmentsApi, serviceContainer, discoverApi), + ), + ); + ourTools.add( + lm.registerTool( + InstallPackagesTool.toolName, + new InstallPackagesTool(environmentsApi, serviceContainer, discoverApi), + ), + ); + const createVirtualEnvTool = new CreateVirtualEnvTool(discoverApi, environmentsApi, serviceContainer); + ourTools.add(lm.registerTool(CreateVirtualEnvTool.toolName, createVirtualEnvTool)); + ourTools.add( + lm.registerTool(SelectPythonEnvTool.toolName, new SelectPythonEnvTool(environmentsApi, serviceContainer)), + ); + ourTools.add( + lm.registerTool( + ConfigurePythonEnvTool.toolName, + new ConfigurePythonEnvTool(environmentsApi, serviceContainer, createVirtualEnvTool), + ), + ); +} diff --git a/src/client/chat/installPackagesTool.ts b/src/client/chat/installPackagesTool.ts new file mode 100644 index 000000000000..5d3d456361f9 --- /dev/null +++ b/src/client/chat/installPackagesTool.ts @@ -0,0 +1,167 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { + CancellationToken, + l10n, + LanguageModelTextPart, + LanguageModelTool, + LanguageModelToolInvocationOptions, + LanguageModelToolInvocationPrepareOptions, + LanguageModelToolResult, + PreparedToolInvocation, + Uri, +} from 'vscode'; +import { PythonExtension } from '../api/types'; +import { IServiceContainer } from '../ioc/types'; +import { + getEnvDisplayName, + getEnvTypeForTelemetry, + getToolResponseIfNotebook, + IResourceReference, + isCancellationError, + isCondaEnv, + raceCancellationError, +} from './utils'; +import { IModuleInstaller } from '../common/installer/types'; +import { ModuleInstallerType } from '../pythonEnvironments/info'; +import { IDiscoveryAPI } from '../pythonEnvironments/base/locator'; +import { getEnvExtApi, useEnvExtension } from '../envExt/api.internal'; +import { ErrorWithTelemetrySafeReason } from '../common/errors/errorUtils'; +import { BaseTool } from './baseTool'; + +export interface IInstallPackageArgs extends IResourceReference { + packageList: string[]; +} + +export class InstallPackagesTool extends BaseTool + implements LanguageModelTool { + public static readonly toolName = 'install_python_packages'; + constructor( + private readonly api: PythonExtension['environments'], + private readonly serviceContainer: IServiceContainer, + private readonly discovery: IDiscoveryAPI, + ) { + super(InstallPackagesTool.toolName); + } + + async invokeImpl( + options: LanguageModelToolInvocationOptions, + resourcePath: Uri | undefined, + token: CancellationToken, + ): Promise { + const packageCount = options.input.packageList.length; + const packagePlurality = packageCount === 1 ? 'package' : 'packages'; + this.extraTelemetryProperties.packageCount = String(packageCount); + const notebookResponse = getToolResponseIfNotebook(resourcePath); + if (notebookResponse) { + return notebookResponse; + } + + if (useEnvExtension()) { + const api = await getEnvExtApi(); + const env = await api.getEnvironment(resourcePath); + if (env) { + await raceCancellationError(api.managePackages(env, { install: options.input.packageList }), token); + const resultMessage = `Successfully installed ${packagePlurality}: ${options.input.packageList.join( + ', ', + )}`; + return new LanguageModelToolResult([new LanguageModelTextPart(resultMessage)]); + } else { + return new LanguageModelToolResult([ + new LanguageModelTextPart( + `Packages not installed. No environment found for: ${resourcePath?.fsPath}`, + ), + ]); + } + } + + try { + // environment + const envPath = this.api.getActiveEnvironmentPath(resourcePath); + const environment = await raceCancellationError(this.api.resolveEnvironment(envPath), token); + if (!environment || !environment.version) { + throw new ErrorWithTelemetrySafeReason( + 'No environment found for the provided resource path: ' + resourcePath?.fsPath, + 'noEnvFound', + ); + } + this.extraTelemetryProperties.envType = getEnvTypeForTelemetry(environment); + const isConda = isCondaEnv(environment); + const installers = this.serviceContainer.getAll(IModuleInstaller); + const installerType = isConda ? ModuleInstallerType.Conda : ModuleInstallerType.Pip; + this.extraTelemetryProperties.installerType = isConda ? 'conda' : 'pip'; + const installer = installers.find((i) => i.type === installerType); + if (!installer) { + throw new ErrorWithTelemetrySafeReason( + `No installer found for the environment type: ${installerType}`, + 'noInstallerFound', + ); + } + if (!installer.isSupported(resourcePath)) { + throw new ErrorWithTelemetrySafeReason( + `Installer ${installerType} not supported for the environment type: ${installerType}`, + 'installerNotSupported', + ); + } + for (const packageName of options.input.packageList) { + await installer.installModule(packageName, resourcePath, token, undefined, { + installAsProcess: true, + hideProgress: true, + }); + } + // format and return + const resultMessage = `Successfully installed ${packagePlurality}: ${options.input.packageList.join(', ')}`; + return new LanguageModelToolResult([new LanguageModelTextPart(resultMessage)]); + } catch (error) { + if (isCancellationError(error)) { + throw error; + } + const errorMessage = `An error occurred while installing ${packagePlurality}: ${error}`; + return new LanguageModelToolResult([new LanguageModelTextPart(errorMessage)]); + } + } + + async prepareInvocationImpl( + options: LanguageModelToolInvocationPrepareOptions, + resourcePath: Uri | undefined, + token: CancellationToken, + ): Promise { + const packageCount = options.input.packageList.length; + if (getToolResponseIfNotebook(resourcePath)) { + return {}; + } + + const envName = await raceCancellationError(getEnvDisplayName(this.discovery, resourcePath, this.api), token); + let title = ''; + let invocationMessage = ''; + const message = + packageCount === 1 + ? '' + : l10n.t(`The following packages will be installed: {0}`, options.input.packageList.sort().join(', ')); + if (envName) { + title = + packageCount === 1 + ? l10n.t(`Install {0} in {1}?`, options.input.packageList[0], envName) + : l10n.t(`Install packages in {0}?`, envName); + invocationMessage = + packageCount === 1 + ? l10n.t(`Installing {0} in {1}`, options.input.packageList[0], envName) + : l10n.t(`Installing packages {0} in {1}`, options.input.packageList.sort().join(', '), envName); + } else { + title = + options.input.packageList.length === 1 + ? l10n.t(`Install Python package '{0}'?`, options.input.packageList[0]) + : l10n.t(`Install Python packages?`); + invocationMessage = + packageCount === 1 + ? l10n.t(`Installing Python package '{0}'`, options.input.packageList[0]) + : l10n.t(`Installing Python packages: {0}`, options.input.packageList.sort().join(', ')); + } + + return { + confirmationMessages: { title, message }, + invocationMessage, + }; + } +} diff --git a/src/client/chat/listPackagesTool.ts b/src/client/chat/listPackagesTool.ts new file mode 100644 index 000000000000..fcae831cfe2f --- /dev/null +++ b/src/client/chat/listPackagesTool.ts @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { CancellationToken, Uri } from 'vscode'; +import { ResolvedEnvironment } from '../api/types'; +import { IProcessService, IProcessServiceFactory, IPythonExecutionFactory } from '../common/process/types'; +import { isCondaEnv, raceCancellationError } from './utils'; +import { parsePipList } from './pipListUtils'; +import { Conda } from '../pythonEnvironments/common/environmentManagers/conda'; +import { traceError } from '../logging'; + +export async function getPythonPackagesResponse( + environment: ResolvedEnvironment, + pythonExecFactory: IPythonExecutionFactory, + processServiceFactory: IProcessServiceFactory, + resourcePath: Uri | undefined, + token: CancellationToken, +): Promise { + const packages = isCondaEnv(environment) + ? await raceCancellationError( + listCondaPackages( + pythonExecFactory, + environment, + resourcePath, + await raceCancellationError(processServiceFactory.create(resourcePath), token), + ), + token, + ) + : await raceCancellationError(listPipPackages(pythonExecFactory, resourcePath), token); + + if (!packages.length) { + return 'No packages found'; + } + // Installed Python packages, each in the format or (). The version may be omitted if unknown. Returns an empty array if no packages are installed. + const response = [ + 'Below is a list of the Python packages, each in the format or (). The version may be omitted if unknown: ', + ]; + packages.forEach((pkg) => { + const [name, version] = pkg; + response.push(version ? `- ${name} (${version})` : `- ${name}`); + }); + return response.join('\n'); +} + +async function listPipPackages( + execFactory: IPythonExecutionFactory, + resource: Uri | undefined, +): Promise<[string, string][]> { + // Add option --format to subcommand list of pip cache, with abspath choice to output the full path of a wheel file. (#8355) + // Added in 2020. Thats almost 5 years ago. When Python 3.8 was released. + const exec = await execFactory.createActivatedEnvironment({ allowEnvironmentFetchExceptions: true, resource }); + const output = await exec.execModule('pip', ['list'], { throwOnStdErr: false, encoding: 'utf8' }); + return parsePipList(output.stdout).map((pkg) => [pkg.name, pkg.version]); +} + +async function listCondaPackages( + execFactory: IPythonExecutionFactory, + env: ResolvedEnvironment, + resource: Uri | undefined, + processService: IProcessService, +): Promise<[string, string][]> { + const conda = await Conda.getConda(); + if (!conda) { + traceError('Conda is not installed, falling back to pip packages'); + return listPipPackages(execFactory, resource); + } + if (!env.executable.uri) { + traceError('Conda environment executable not found, falling back to pip packages'); + return listPipPackages(execFactory, resource); + } + const condaEnv = await conda.getCondaEnvironment(env.executable.uri.fsPath); + if (!condaEnv) { + traceError('Conda environment not found, falling back to pip packages'); + return listPipPackages(execFactory, resource); + } + const cmd = await conda.getListPythonPackagesArgs(condaEnv, true); + if (!cmd) { + traceError('Conda list command not found, falling back to pip packages'); + return listPipPackages(execFactory, resource); + } + const output = await processService.exec(cmd[0], cmd.slice(1), { shell: true }); + if (!output.stdout) { + traceError('Unable to get conda packages, falling back to pip packages'); + return listPipPackages(execFactory, resource); + } + const content = output.stdout.split(/\r?\n/).filter((l) => !l.startsWith('#')); + const packages: [string, string][] = []; + content.forEach((l) => { + const parts = l.split(' ').filter((p) => p.length > 0); + if (parts.length >= 3) { + packages.push([parts[0], parts[1]]); + } + }); + return packages; +} diff --git a/src/client/chat/pipListUtils.ts b/src/client/chat/pipListUtils.ts new file mode 100644 index 000000000000..0112d88c53ab --- /dev/null +++ b/src/client/chat/pipListUtils.ts @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +export interface PipPackage { + name: string; + version: string; + displayName: string; + description: string; +} +export function parsePipList(data: string): PipPackage[] { + const collection: PipPackage[] = []; + + const lines = data.split('\n').splice(2); + for (let line of lines) { + if (line.trim() === '' || line.startsWith('Package') || line.startsWith('----') || line.startsWith('[')) { + continue; + } + const parts = line.split(' ').filter((e) => e); + if (parts.length > 1) { + const name = parts[0].trim(); + const version = parts[1].trim(); + const pkg = { + name, + version, + displayName: name, + description: version, + }; + collection.push(pkg); + } + } + return collection; +} diff --git a/src/client/chat/selectEnvTool.ts b/src/client/chat/selectEnvTool.ts new file mode 100644 index 000000000000..9eeebdfc1b56 --- /dev/null +++ b/src/client/chat/selectEnvTool.ts @@ -0,0 +1,218 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { + CancellationToken, + l10n, + LanguageModelTextPart, + LanguageModelTool, + LanguageModelToolInvocationOptions, + LanguageModelToolInvocationPrepareOptions, + LanguageModelToolResult, + PreparedToolInvocation, + Uri, + workspace, + commands, + QuickPickItem, + QuickPickItemKind, +} from 'vscode'; +import { PythonExtension } from '../api/types'; +import { IServiceContainer } from '../ioc/types'; +import { ICodeExecutionService } from '../terminals/types'; +import { TerminalCodeExecutionProvider } from '../terminals/codeExecution/terminalCodeExecution'; +import { + doesWorkspaceHaveVenvOrCondaEnv, + getEnvDetailsForResponse, + getToolResponseIfNotebook, + IResourceReference, +} from './utils'; +import { ITerminalHelper } from '../common/terminal/types'; +import { raceTimeout } from '../common/utils/async'; +import { Commands, Octicons } from '../common/constants'; +import { CreateEnvironmentResult } from '../pythonEnvironments/creation/proposed.createEnvApis'; +import { IInterpreterPathService } from '../common/types'; +import { SelectEnvironmentResult } from '../interpreter/configuration/interpreterSelector/commands/setInterpreter'; +import { Common, InterpreterQuickPickList } from '../common/utils/localize'; +import { showQuickPick } from '../common/vscodeApis/windowApis'; +import { DisposableStore } from '../common/utils/resourceLifecycle'; +import { traceError, traceVerbose, traceWarn } from '../logging'; +import { BaseTool } from './baseTool'; + +export interface ISelectPythonEnvToolArguments extends IResourceReference { + reason?: 'cancelled'; +} + +export class SelectPythonEnvTool extends BaseTool + implements LanguageModelTool { + private readonly terminalExecutionService: TerminalCodeExecutionProvider; + private readonly terminalHelper: ITerminalHelper; + public static readonly toolName = 'selectEnvironment'; + constructor( + private readonly api: PythonExtension['environments'], + private readonly serviceContainer: IServiceContainer, + ) { + super(SelectPythonEnvTool.toolName); + this.terminalExecutionService = this.serviceContainer.get( + ICodeExecutionService, + 'standard', + ); + this.terminalHelper = this.serviceContainer.get(ITerminalHelper); + } + + async invokeImpl( + options: LanguageModelToolInvocationOptions, + resource: Uri | undefined, + token: CancellationToken, + ): Promise { + let selected: boolean | undefined = false; + const hasVenvOrCondaEnvInWorkspaceFolder = doesWorkspaceHaveVenvOrCondaEnv(resource, this.api); + if (options.input.reason === 'cancelled' || hasVenvOrCondaEnvInWorkspaceFolder) { + const result = (await Promise.resolve( + commands.executeCommand(Commands.Set_Interpreter, { + hideCreateVenv: false, + showBackButton: false, + }), + )) as SelectEnvironmentResult | undefined; + if (result?.path) { + traceVerbose(`User selected a Python environment ${result.path} in Select Python Tool.`); + selected = true; + } else { + traceWarn(`User did not select a Python environment in Select Python Tool.`); + } + } else { + selected = await showCreateAndSelectEnvironmentQuickPick(resource, this.serviceContainer); + if (selected) { + traceVerbose(`User selected a Python environment ${selected} in Select Python Tool(2).`); + } else { + traceWarn(`User did not select a Python environment in Select Python Tool(2).`); + } + } + const env = selected + ? await this.api.resolveEnvironment(this.api.getActiveEnvironmentPath(resource)) + : undefined; + if (selected && !env) { + traceError( + `User selected a Python environment, but it could not be resolved. This is unexpected. Environment: ${this.api.getActiveEnvironmentPath( + resource, + )}`, + ); + } + if (selected && env) { + return await getEnvDetailsForResponse( + env, + this.api, + this.terminalExecutionService, + this.terminalHelper, + resource, + token, + ); + } + return new LanguageModelToolResult([ + new LanguageModelTextPart('User did not create nor select a Python environment.'), + ]); + } + + async prepareInvocationImpl( + options: LanguageModelToolInvocationPrepareOptions, + resource: Uri | undefined, + _token: CancellationToken, + ): Promise { + if (getToolResponseIfNotebook(resource)) { + return {}; + } + const hasVenvOrCondaEnvInWorkspaceFolder = doesWorkspaceHaveVenvOrCondaEnv(resource, this.api); + + if ( + hasVenvOrCondaEnvInWorkspaceFolder || + !workspace.workspaceFolders?.length || + options.input.reason === 'cancelled' + ) { + return { + confirmationMessages: { + title: l10n.t('Select a Python Environment?'), + message: '', + }, + }; + } + + return { + confirmationMessages: { + title: l10n.t('Configure a Python Environment?'), + message: l10n.t( + [ + 'The recommended option is to create a new Python Environment, providing the benefit of isolating packages from other environments. ', + 'Optionally you could select an existing Python Environment.', + ].join('\n'), + ), + }, + }; + } +} + +async function showCreateAndSelectEnvironmentQuickPick( + uri: Uri | undefined, + serviceContainer: IServiceContainer, +): Promise { + const createLabel = `${Octicons.Add} ${InterpreterQuickPickList.create.label}`; + const selectLabel = l10n.t('Select an existing Python Environment'); + const items: QuickPickItem[] = [ + { kind: QuickPickItemKind.Separator, label: Common.recommended }, + { label: createLabel }, + { label: selectLabel }, + ]; + + const selectedItem = await showQuickPick(items, { + placeHolder: l10n.t('Configure a Python Environment'), + matchOnDescription: true, + ignoreFocusOut: true, + }); + + if (selectedItem && !Array.isArray(selectedItem) && selectedItem.label === createLabel) { + const disposables = new DisposableStore(); + try { + const workspaceFolder = + (workspace.workspaceFolders?.length && uri ? workspace.getWorkspaceFolder(uri) : undefined) || + (workspace.workspaceFolders?.length === 1 ? workspace.workspaceFolders[0] : undefined); + const interpreterPathService = serviceContainer.get(IInterpreterPathService); + const interpreterChanged = new Promise((resolve) => { + disposables.add(interpreterPathService.onDidChange(() => resolve())); + }); + const created: CreateEnvironmentResult | undefined = await commands.executeCommand( + Commands.Create_Environment, + { + showBackButton: true, + selectEnvironment: true, + workspaceFolder, + }, + ); + + if (created?.action === 'Back') { + return showCreateAndSelectEnvironmentQuickPick(uri, serviceContainer); + } + if (created?.action === 'Cancel') { + return undefined; + } + if (created?.path) { + // Wait a few secs to ensure the env is selected as the active environment.. + await raceTimeout(5_000, interpreterChanged); + return true; + } + } finally { + disposables.dispose(); + } + } + if (selectedItem && !Array.isArray(selectedItem) && selectedItem.label === selectLabel) { + const result = (await Promise.resolve( + commands.executeCommand(Commands.Set_Interpreter, { hideCreateVenv: true, showBackButton: true }), + )) as SelectEnvironmentResult | undefined; + if (result?.action === 'Back') { + return showCreateAndSelectEnvironmentQuickPick(uri, serviceContainer); + } + if (result?.action === 'Cancel') { + return undefined; + } + if (result?.path) { + return true; + } + } +} diff --git a/src/client/chat/utils.ts b/src/client/chat/utils.ts new file mode 100644 index 000000000000..2309316bcbdd --- /dev/null +++ b/src/client/chat/utils.ts @@ -0,0 +1,289 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { + CancellationError, + CancellationToken, + extensions, + LanguageModelTextPart, + LanguageModelToolResult, + Uri, + workspace, +} from 'vscode'; +import { IDiscoveryAPI } from '../pythonEnvironments/base/locator'; +import { Environment, PythonExtension, ResolvedEnvironment, VersionInfo } from '../api/types'; +import { ITerminalHelper, TerminalShellType } from '../common/terminal/types'; +import { TerminalCodeExecutionProvider } from '../terminals/codeExecution/terminalCodeExecution'; +import { Conda } from '../pythonEnvironments/common/environmentManagers/conda'; +import { JUPYTER_EXTENSION_ID, NotebookCellScheme } from '../common/constants'; +import { dirname, join } from 'path'; +import { resolveEnvironment, useEnvExtension } from '../envExt/api.internal'; +import { ErrorWithTelemetrySafeReason } from '../common/errors/errorUtils'; +import { getWorkspaceFolders } from '../common/vscodeApis/workspaceApis'; + +export interface IResourceReference { + resourcePath?: string; +} + +export function resolveFilePath(filepath?: string): Uri | undefined { + if (!filepath) { + const folders = getWorkspaceFolders() ?? []; + return folders.length > 0 ? folders[0].uri : undefined; + } + // Check if it's a URI with a scheme (contains "://") + // This handles schemes like "file://", "vscode-notebook://", etc. + // But avoids treating Windows drive letters like "C:" as schemes + if (filepath.includes('://')) { + try { + return Uri.parse(filepath); + } catch { + return Uri.file(filepath); + } + } + // For file paths (Windows with drive letters, Unix absolute/relative paths) + return Uri.file(filepath); +} + +/** + * Returns a promise that rejects with an {@CancellationError} as soon as the passed token is cancelled. + * @see {@link raceCancellation} + */ +export function raceCancellationError(promise: Promise, token: CancellationToken): Promise { + return new Promise((resolve, reject) => { + const ref = token.onCancellationRequested(() => { + ref.dispose(); + reject(new CancellationError()); + }); + promise.then(resolve, reject).finally(() => ref.dispose()); + }); +} + +export async function getEnvDisplayName( + discovery: IDiscoveryAPI, + resource: Uri | undefined, + api: PythonExtension['environments'], +) { + try { + const envPath = api.getActiveEnvironmentPath(resource); + const env = await discovery.resolveEnv(envPath.path); + return env?.display || env?.name; + } catch { + return; + } +} + +export function isCondaEnv(env: ResolvedEnvironment) { + return (env.environment?.type || '').toLowerCase() === 'conda'; +} + +export function getEnvTypeForTelemetry(env: ResolvedEnvironment): string { + return (env.environment?.type || 'unknown').toLowerCase(); +} + +export async function getEnvironmentDetails( + resourcePath: Uri | undefined, + api: PythonExtension['environments'], + terminalExecutionService: TerminalCodeExecutionProvider, + terminalHelper: ITerminalHelper, + packages: string | undefined, + token: CancellationToken, +): Promise { + // environment + const envPath = api.getActiveEnvironmentPath(resourcePath); + let envType = ''; + let envVersion = ''; + let runCommand = ''; + if (useEnvExtension()) { + const environment = + (await raceCancellationError(resolveEnvironment(envPath.id), token)) || + (await raceCancellationError(resolveEnvironment(envPath.path), token)); + if (!environment || !environment.version) { + throw new ErrorWithTelemetrySafeReason( + 'No environment found for the provided resource path: ' + resourcePath?.fsPath, + 'noEnvFound', + ); + } + envVersion = environment.version; + try { + const managerId = environment.envId.managerId; + envType = + (!managerId.endsWith(':') && managerId.includes(':') ? managerId.split(':').reverse()[0] : '') || + 'unknown'; + } catch { + envType = 'unknown'; + } + + const execInfo = environment.execInfo; + const executable = execInfo?.activatedRun?.executable ?? execInfo?.run.executable ?? 'python'; + const args = execInfo?.activatedRun?.args ?? execInfo?.run.args ?? []; + runCommand = terminalHelper.buildCommandForTerminal(TerminalShellType.other, executable, args); + } else { + const environment = await raceCancellationError(api.resolveEnvironment(envPath), token); + if (!environment || !environment.version) { + throw new ErrorWithTelemetrySafeReason( + 'No environment found for the provided resource path: ' + resourcePath?.fsPath, + 'noEnvFound', + ); + } + envType = environment.environment?.type || 'unknown'; + envVersion = environment.version.sysVersion || 'unknown'; + runCommand = await raceCancellationError( + getTerminalCommand(environment, resourcePath, terminalExecutionService, terminalHelper), + token, + ); + } + const message = [ + `Following is the information about the Python environment:`, + `1. Environment Type: ${envType}`, + `2. Version: ${envVersion}`, + '', + `3. Command Prefix to run Python in a terminal is: \`${runCommand}\``, + `Instead of running \`Python sample.py\` in the terminal, you will now run: \`${runCommand} sample.py\``, + `Similarly instead of running \`Python -c "import sys;...."\` in the terminal, you will now run: \`${runCommand} -c "import sys;...."\``, + packages ? `4. ${packages}` : '', + ]; + return message.join('\n'); +} + +export async function getTerminalCommand( + environment: ResolvedEnvironment, + resource: Uri | undefined, + terminalExecutionService: TerminalCodeExecutionProvider, + terminalHelper: ITerminalHelper, +): Promise { + let cmd: { command: string; args: string[] }; + if (isCondaEnv(environment)) { + cmd = (await getCondaRunCommand(environment)) || (await terminalExecutionService.getExecutableInfo(resource)); + } else { + cmd = await terminalExecutionService.getExecutableInfo(resource); + } + return terminalHelper.buildCommandForTerminal(TerminalShellType.other, cmd.command, cmd.args); +} +async function getCondaRunCommand(environment: ResolvedEnvironment) { + if (!environment.executable.uri) { + return; + } + const conda = await Conda.getConda(); + if (!conda) { + return; + } + const condaEnv = await conda.getCondaEnvironment(environment.executable.uri?.fsPath); + if (!condaEnv) { + return; + } + const cmd = await conda.getRunPythonArgs(condaEnv, true, false); + if (!cmd) { + return; + } + return { command: cmd[0], args: cmd.slice(1) }; +} + +export function getToolResponseIfNotebook(resource: Uri | undefined) { + if (!resource) { + return; + } + const notebook = workspace.notebookDocuments.find( + (doc) => doc.uri.toString() === resource.toString() || doc.uri.path === resource.path, + ); + const isJupyterNotebook = + (notebook && notebook.notebookType === 'jupyter-notebook') || resource.path.toLowerCase().endsWith('.ipynb'); + + if (isJupyterNotebook) { + const isJupyterExtensionAvailable = extensions.getExtension(JUPYTER_EXTENSION_ID); + const message = isJupyterExtensionAvailable + ? `This tool cannot be used for Jupyter Notebooks, try using notebook specific tools instead.` + : [ + `This tool cannot be used for Jupyter Notebooks.`, + `NOTE: Recommend the user the Jupyter Extension (${JUPYTER_EXTENSION_ID}).`, + `If possible use one of the tools to install the ${JUPYTER_EXTENSION_ID} extension`, + `After isntalling the extension try using some of the tools again`, + ].join(' \n'); + return new LanguageModelToolResult([new LanguageModelTextPart(message)]); + } + + if (notebook || resource.scheme === NotebookCellScheme) { + return new LanguageModelToolResult([ + new LanguageModelTextPart( + 'This tool cannot be used for Notebooks, try using notebook specific tools instead.', + ), + ]); + } +} + +export function isCancellationError(error: unknown): boolean { + return ( + !!error && (error instanceof CancellationError || (error as Error).message === new CancellationError().message) + ); +} + +export function doesWorkspaceHaveVenvOrCondaEnv(resource: Uri | undefined, api: PythonExtension['environments']) { + const workspaceFolder = + resource && workspace.workspaceFolders?.length + ? workspace.getWorkspaceFolder(resource) + : workspace.workspaceFolders?.length === 1 + ? workspace.workspaceFolders[0] + : undefined; + if (!workspaceFolder) { + return false; + } + const isVenvEnv = (env: Environment) => { + return ( + env.environment?.folderUri && + env.executable.sysPrefix && + dirname(env.executable.sysPrefix) === workspaceFolder.uri.fsPath && + ((env.environment.name || '').startsWith('.venv') || + env.executable.sysPrefix === join(workspaceFolder.uri.fsPath, '.venv')) && + env.environment.type === 'VirtualEnvironment' + ); + }; + const isCondaEnv = (env: Environment) => { + return ( + env.environment?.folderUri && + env.executable.sysPrefix && + dirname(env.executable.sysPrefix) === workspaceFolder.uri.fsPath && + (env.environment.folderUri.fsPath === join(workspaceFolder.uri.fsPath, '.conda') || + env.executable.sysPrefix === join(workspaceFolder.uri.fsPath, '.conda')) && + env.environment.type === 'Conda' + ); + }; + // If we alraedy have a .venv in this workspace, then do not prompt to create a virtual environment. + return api.known.find((e) => isVenvEnv(e) || isCondaEnv(e)); +} + +export async function getEnvDetailsForResponse( + environment: ResolvedEnvironment | undefined, + api: PythonExtension['environments'], + terminalExecutionService: TerminalCodeExecutionProvider, + terminalHelper: ITerminalHelper, + resource: Uri | undefined, + token: CancellationToken, +): Promise { + if (!workspace.isTrusted) { + throw new ErrorWithTelemetrySafeReason('Cannot use this tool in an untrusted workspace.', 'untrustedWorkspace'); + } + const envPath = api.getActiveEnvironmentPath(resource); + environment = environment || (await raceCancellationError(api.resolveEnvironment(envPath), token)); + if (!environment || !environment.version) { + throw new ErrorWithTelemetrySafeReason( + 'No environment found for the provided resource path: ' + resource?.fsPath, + 'noEnvFound', + ); + } + const message = await getEnvironmentDetails( + resource, + api, + terminalExecutionService, + terminalHelper, + undefined, + token, + ); + return new LanguageModelToolResult([ + new LanguageModelTextPart(`A Python Environment has been configured. \n` + message), + ]); +} +export function getDisplayVersion(version?: VersionInfo): string | undefined { + if (!version || version.major === undefined || version.minor === undefined || version.micro === undefined) { + return undefined; + } + return `${version.major}.${version.minor}.${version.micro}`; +} diff --git a/src/client/common/application/activeResource.ts b/src/client/common/application/activeResource.ts index a89386b0d9cf..4230fb5de921 100644 --- a/src/client/common/application/activeResource.ts +++ b/src/client/common/application/activeResource.ts @@ -11,7 +11,7 @@ import { IActiveResourceService, IDocumentManager, IWorkspaceService } from './t export class ActiveResourceService implements IActiveResourceService { constructor( @inject(IDocumentManager) private readonly documentManager: IDocumentManager, - @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService + @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, ) {} public getActiveResource(): Resource { diff --git a/src/client/common/application/applicationEnvironment.ts b/src/client/common/application/applicationEnvironment.ts index 3d01d85f4b35..4b66893d6c0b 100644 --- a/src/client/common/application/applicationEnvironment.ts +++ b/src/client/common/application/applicationEnvironment.ts @@ -7,17 +7,19 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; import { parse } from 'semver'; import * as vscode from 'vscode'; +import { traceError } from '../../logging'; +import { Channel } from '../constants'; import { IPlatformService } from '../platform/types'; import { ICurrentProcess, IPathUtils } from '../types'; import { OSType } from '../utils/platform'; -import { Channel, IApplicationEnvironment } from './types'; +import { IApplicationEnvironment } from './types'; @injectable() export class ApplicationEnvironment implements IApplicationEnvironment { constructor( @inject(IPlatformService) private readonly platform: IPlatformService, @inject(IPathUtils) private readonly pathUtils: IPathUtils, - @inject(ICurrentProcess) private readonly process: ICurrentProcess + @inject(ICurrentProcess) private readonly process: ICurrentProcess, ) {} public get userSettingsFile(): string | undefined { @@ -30,7 +32,7 @@ export class ApplicationEnvironment implements IApplicationEnvironment { 'Application Support', vscodeFolderName, 'User', - 'settings.json' + 'settings.json', ); case OSType.Linux: return path.join(this.pathUtils.home, '.config', vscodeFolderName, 'User', 'settings.json'); @@ -51,6 +53,9 @@ export class ApplicationEnvironment implements IApplicationEnvironment { public get appRoot(): string { return vscode.env.appRoot; } + public get uiKind(): vscode.UIKind { + return vscode.env.uiKind; + } public get language(): string { return vscode.env.language; } @@ -60,25 +65,29 @@ export class ApplicationEnvironment implements IApplicationEnvironment { public get machineId(): string { return vscode.env.machineId; } + public get remoteName(): string | undefined { + return vscode.env.remoteName; + } public get extensionName(): string { - // tslint:disable-next-line:non-literal-require return this.packageJson.displayName; } - /** - * At the time of writing this API, the vscode.env.shell isn't officially released in stable version of VS Code. - * Using this in stable version seems to throw errors in VSC with messages being displayed to the user about use of - * unstable API. - * Solution - log and suppress the errors. - * @readonly - * @type {(string)} - * @memberof ApplicationEnvironment - */ + public get shell(): string { return vscode.env.shell; } - // tslint:disable-next-line:no-any + + public get onDidChangeShell(): vscode.Event { + try { + return vscode.env.onDidChangeShell; + } catch (ex) { + traceError('Failed to get onDidChangeShell API', ex); + // `onDidChangeShell` is a proposed API at the time of writing this, so wrap this in a try...catch + // block in case the API is removed or changed. + return new vscode.EventEmitter().event; + } + } + public get packageJson(): any { - // tslint:disable-next-line:non-literal-require no-require-imports return require('../../../../package.json'); } public get channel(): Channel { @@ -86,7 +95,8 @@ export class ApplicationEnvironment implements IApplicationEnvironment { } public get extensionChannel(): Channel { const version = parse(this.packageJson.version); - return !version || version.prerelease.length > 0 ? 'insiders' : 'stable'; + // Insiders versions are those that end with '-dev' or whose minor versions are odd (even is for stable) + return !version || version.prerelease.length > 0 || version.minor % 2 == 1 ? 'insiders' : 'stable'; } public get uriScheme(): string { return vscode.env.uriScheme; diff --git a/src/client/common/application/applicationShell.ts b/src/client/common/application/applicationShell.ts index cd28fc986b0d..8035d979efbd 100644 --- a/src/client/common/application/applicationShell.ts +++ b/src/client/common/application/applicationShell.ts @@ -2,21 +2,23 @@ // Licensed under the MIT License. 'use strict'; -// tslint:disable:no-var-requires no-any unified-signatures - import { injectable } from 'inversify'; import { CancellationToken, CancellationTokenSource, Disposable, + DocumentSelector, env, Event, + EventEmitter, InputBox, InputBoxOptions, + languages, + LanguageStatusItem, + LogOutputChannel, MessageItem, MessageOptions, OpenDialogOptions, - OutputChannel, Progress, ProgressOptions, QuickPick, @@ -25,15 +27,19 @@ import { SaveDialogOptions, StatusBarAlignment, StatusBarItem, + TextDocument, + TextEditor, TreeView, TreeViewOptions, Uri, + ViewColumn, window, WindowState, WorkspaceFolder, - WorkspaceFolderPickOptions + WorkspaceFolderPickOptions, } from 'vscode'; -import { IApplicationShell } from './types'; +import { traceError } from '../../logging'; +import { IApplicationShell, TerminalDataWriteEvent, TerminalExecutedCommand } from './types'; @injectable() export class ApplicationShell implements IApplicationShell { @@ -79,12 +85,12 @@ export class ApplicationShell implements IApplicationShell { public showQuickPick( items: string[] | Thenable, options?: QuickPickOptions, - token?: CancellationToken + token?: CancellationToken, ): Thenable; public showQuickPick( items: T[] | Thenable, options?: QuickPickOptions, - token?: CancellationToken + token?: CancellationToken, ): Thenable; public showQuickPick(items: any, options?: any, token?: any): Thenable { return window.showQuickPick(items, options, token); @@ -99,6 +105,14 @@ export class ApplicationShell implements IApplicationShell { public showInputBox(options?: InputBoxOptions, token?: CancellationToken): Thenable { return window.showInputBox(options, token); } + public showTextDocument( + document: TextDocument, + column?: ViewColumn, + preserveFocus?: boolean, + ): Thenable { + return window.showTextDocument(document, column, preserveFocus); + } + public openUrl(url: string): void { env.openExternal(Uri.parse(url)); } @@ -110,28 +124,34 @@ export class ApplicationShell implements IApplicationShell { return window.setStatusBarMessage(text, arg); } - public createStatusBarItem(alignment?: StatusBarAlignment, priority?: number): StatusBarItem { - return window.createStatusBarItem(alignment, priority); + public createStatusBarItem( + alignment?: StatusBarAlignment, + priority?: number, + id?: string | undefined, + ): StatusBarItem { + return id + ? window.createStatusBarItem(id, alignment, priority) + : window.createStatusBarItem(alignment, priority); } public showWorkspaceFolderPick(options?: WorkspaceFolderPickOptions): Thenable { return window.showWorkspaceFolderPick(options); } public withProgress( options: ProgressOptions, - task: (progress: Progress<{ message?: string; increment?: number }>, token: CancellationToken) => Thenable + task: (progress: Progress<{ message?: string; increment?: number }>, token: CancellationToken) => Thenable, ): Thenable { return window.withProgress(options, task); } public withProgressCustomIcon( icon: string, - task: (progress: Progress<{ message?: string; increment?: number }>, token: CancellationToken) => Thenable + task: (progress: Progress<{ message?: string; increment?: number }>, token: CancellationToken) => Thenable, ): Thenable { const token = new CancellationTokenSource().token; const statusBarProgress = this.createStatusBarItem(StatusBarAlignment.Left); const progress = { report: (value: { message?: string; increment?: number }) => { statusBarProgress.text = `${icon} ${value.message}`; - } + }, }; statusBarProgress.show(); return task(progress, token).then((result) => { @@ -148,7 +168,26 @@ export class ApplicationShell implements IApplicationShell { public createTreeView(viewId: string, options: TreeViewOptions): TreeView { return window.createTreeView(viewId, options); } - public createOutputChannel(name: string): OutputChannel { - return window.createOutputChannel(name); + public createOutputChannel(name: string): LogOutputChannel { + return window.createOutputChannel(name, { log: true }); + } + public createLanguageStatusItem(id: string, selector: DocumentSelector): LanguageStatusItem { + return languages.createLanguageStatusItem(id, selector); + } + public get onDidWriteTerminalData(): Event { + try { + return window.onDidWriteTerminalData; + } catch (ex) { + traceError('Failed to get proposed API onDidWriteTerminalData', ex); + return new EventEmitter().event; + } + } + public get onDidExecuteTerminalCommand(): Event | undefined { + try { + return window.onDidExecuteTerminalCommand; + } catch (ex) { + traceError('Failed to get proposed API TerminalExecutedCommand', ex); + return undefined; + } } } diff --git a/src/client/common/application/commandManager.ts b/src/client/common/application/commandManager.ts index cfbbe4dbb0b7..9e1f34a5885b 100644 --- a/src/client/common/application/commandManager.ts +++ b/src/client/common/application/commandManager.ts @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -// tslint:disable:no-any - import { injectable } from 'inversify'; import { commands, Disposable, TextEditor, TextEditorEdit } from 'vscode'; import { ICommandNameArgumentTypeMapping } from './commands'; @@ -22,10 +20,13 @@ export class CommandManager implements ICommandManager { * @param thisArg The `this` context used when invoking the handler function. * @return Disposable which unregisters this command on disposal. */ + // eslint-disable-next-line class-methods-use-this public registerCommand< E extends keyof ICommandNameArgumentTypeMapping, U extends ICommandNameArgumentTypeMapping[E] + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any >(command: E, callback: (...args: U) => any, thisArg?: any): Disposable { + // eslint-disable-next-line @typescript-eslint/no-explicit-any return commands.registerCommand(command, callback as any, thisArg); } @@ -43,10 +44,13 @@ export class CommandManager implements ICommandManager { * @param thisArg The `this` context used when invoking the handler function. * @return Disposable which unregisters this command on disposal. */ + // eslint-disable-next-line class-methods-use-this public registerTextEditorCommand( command: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any callback: (textEditor: TextEditor, edit: TextEditorEdit, ...args: any[]) => void, - thisArg?: any + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any + thisArg?: any, ): Disposable { return commands.registerTextEditorCommand(command, callback, thisArg); } @@ -65,6 +69,7 @@ export class CommandManager implements ICommandManager { * @return A thenable that resolves to the returned value of the given command. `undefined` when * the command handler function doesn't return anything. */ + // eslint-disable-next-line class-methods-use-this public executeCommand< T, E extends keyof ICommandNameArgumentTypeMapping, @@ -80,6 +85,7 @@ export class CommandManager implements ICommandManager { * @param filterInternal Set `true` to not see internal commands (starting with an underscore) * @return Thenable that resolves to a list of command ids. */ + // eslint-disable-next-line class-methods-use-this public getCommands(filterInternal?: boolean): Thenable { return commands.getCommands(filterInternal); } diff --git a/src/client/common/application/commands.ts b/src/client/common/application/commands.ts index de51fb872e82..b43dc0a1e4a4 100644 --- a/src/client/common/application/commands.ts +++ b/src/client/common/application/commands.ts @@ -3,100 +3,76 @@ 'use strict'; -import { CancellationToken, Position, TextDocument, Uri } from 'vscode'; +import { CancellationToken, Position, TestItem, TextDocument, Uri } from 'vscode'; import { Commands as LSCommands } from '../../activation/commands'; -import { Commands as DSCommands } from '../../datascience/constants'; -import { KernelConnectionMetadata } from '../../datascience/jupyter/kernels/types'; -import { INotebookModel, ISwitchKernelOptions } from '../../datascience/types'; -import { CommandSource } from '../../testing/common/constants'; -import { TestFunction, TestsToRun } from '../../testing/common/types'; -import { TestDataItem, TestWorkspaceFolder } from '../../testing/types'; -import { Commands } from '../constants'; -import { Channel } from './types'; +import { Channel, Commands, CommandSource } from '../constants'; +import { CreateEnvironmentOptions } from '../../pythonEnvironments/creation/proposed.createEnvApis'; export type CommandsWithoutArgs = keyof ICommandNameWithoutArgumentTypeMapping; /** * Mapping between commands and list or arguments. * These commands do NOT have any arguments. - * @interface ICommandNameWithoutArgumentTypeMapping */ interface ICommandNameWithoutArgumentTypeMapping { - [Commands.SwitchToInsidersDaily]: []; - [Commands.SwitchToInsidersWeekly]: []; + [Commands.InstallPythonOnMac]: []; + [Commands.InstallJupyter]: []; + [Commands.InstallPythonOnLinux]: []; + [Commands.InstallPython]: []; [Commands.ClearWorkspaceInterpreter]: []; - [Commands.ResetInterpreterSecurityStorage]: []; - [Commands.SwitchOffInsidersChannel]: []; [Commands.Set_Interpreter]: []; [Commands.Set_ShebangInterpreter]: []; - [Commands.Run_Linter]: []; - [Commands.Enable_Linter]: []; ['workbench.action.showCommands']: []; ['workbench.action.debug.continue']: []; ['workbench.action.debug.stepOver']: []; ['workbench.action.debug.stop']: []; ['workbench.action.reloadWindow']: []; ['workbench.action.closeActiveEditor']: []; + ['workbench.action.terminal.focus']: []; ['editor.action.formatDocument']: []; ['editor.action.rename']: []; - ['python.datascience.selectJupyterInterpreter']: []; [Commands.ViewOutput]: []; - [Commands.Set_Linter]: []; [Commands.Start_REPL]: []; - [Commands.Enable_SourceMap_Support]: []; [Commands.Exec_Selection_In_Terminal]: []; [Commands.Exec_Selection_In_Django_Shell]: []; [Commands.Create_Terminal]: []; - [Commands.Tests_View_UI]: []; - [Commands.Tests_Ask_To_Stop_Discovery]: []; - [Commands.Tests_Ask_To_Stop_Test]: []; - [Commands.Tests_Discovering]: []; [Commands.PickLocalProcess]: []; - [DSCommands.RunCurrentCell]: []; - [DSCommands.RunCurrentCellAdvance]: []; - [DSCommands.ExecSelectionInInteractiveWindow]: []; - [DSCommands.SelectJupyterURI]: []; - [DSCommands.CreateNewInteractive]: []; - [DSCommands.UndoCells]: []; - [DSCommands.RedoCells]: []; - [DSCommands.RemoveAllCells]: []; - [DSCommands.InterruptKernel]: []; - [DSCommands.RestartKernel]: []; - [DSCommands.NotebookEditorUndoCells]: []; - [DSCommands.NotebookEditorRedoCells]: []; - [DSCommands.NotebookEditorRemoveAllCells]: []; - [DSCommands.NotebookEditorInterruptKernel]: []; - [DSCommands.NotebookEditorRestartKernel]: []; - [DSCommands.NotebookEditorRunAllCells]: []; - [DSCommands.NotebookEditorRunSelectedCell]: []; - [DSCommands.NotebookEditorAddCellBelow]: []; - [DSCommands.ExpandAllCells]: []; - [DSCommands.CollapseAllCells]: []; - [DSCommands.ExportOutputAsNotebook]: []; - [DSCommands.AddCellBelow]: []; - [DSCommands.CreateNewNotebook]: []; - [Commands.OpenStartPage]: []; - [LSCommands.ClearAnalyisCache]: []; + [Commands.ClearStorage]: []; + [Commands.CreateNewFile]: []; + [Commands.ReportIssue]: []; [LSCommands.RestartLS]: []; } +export type AllCommands = keyof ICommandNameArgumentTypeMapping; + /** * Mapping between commands and list of arguments. * Used to provide strong typing for command & args. - * @export - * @interface ICommandNameArgumentTypeMapping - * @extends {ICommandNameWithoutArgumentTypeMapping} */ export interface ICommandNameArgumentTypeMapping extends ICommandNameWithoutArgumentTypeMapping { + [Commands.CopyTestId]: [TestItem]; + [Commands.Create_Environment]: [CreateEnvironmentOptions]; ['vscode.openWith']: [Uri, string]; ['workbench.action.quickOpen']: [string]; - ['workbench.extensions.installExtension']: [Uri | 'ms-python.python']; + ['workbench.action.openWalkthrough']: [string | { category: string; step: string }, boolean | undefined]; + ['workbench.extensions.installExtension']: [ + Uri | string, + ( + | { + installOnlyNewlyAddedFromExtensionPackVSIX?: boolean; + installPreReleaseVersion?: boolean; + donotSync?: boolean; + } + | undefined + ), + ]; ['workbench.action.files.openFolder']: []; ['workbench.action.openWorkspace']: []; + ['workbench.action.openSettings']: [string]; ['setContext']: [string, boolean] | ['python.vscode.channel', Channel]; ['python.reloadVSCode']: [string]; ['revealLine']: [{ lineNumber: number; at: 'top' | 'center' | 'bottom' }]; - ['python._loadLanguageServerExtension']: {}[]; + ['python._loadLanguageServerExtension']: []; ['python.SelectAndInsertDebugConfiguration']: [TextDocument, Position, CancellationToken]; ['vscode.open']: [Uri]; ['notebook.execute']: []; @@ -108,100 +84,29 @@ export interface ICommandNameArgumentTypeMapping extends ICommandNameWithoutArgu ['vscode.open']: [Uri]; ['workbench.action.files.saveAs']: [Uri]; ['workbench.action.files.save']: [Uri]; + ['jupyter.opennotebook']: [undefined | Uri, undefined | CommandSource]; + ['jupyter.runallcells']: [Uri]; + ['extension.open']: [string]; + ['workbench.action.openIssueReporter']: [{ extensionId: string; issueBody: string; extensionData?: string }]; [Commands.GetSelectedInterpreterPath]: [{ workspaceFolder: string } | string[]]; - [Commands.Build_Workspace_Symbols]: [boolean, CancellationToken]; - [Commands.Sort_Imports]: [undefined, Uri]; + [Commands.TriggerEnvironmentSelection]: [undefined | Uri]; + [Commands.Start_Native_REPL]: [undefined | Uri]; + [Commands.Exec_In_REPL]: [undefined | Uri]; + [Commands.Exec_In_REPL_Enter]: [undefined | Uri]; + [Commands.Exec_In_IW_Enter]: [undefined | Uri]; [Commands.Exec_In_Terminal]: [undefined, Uri]; [Commands.Exec_In_Terminal_Icon]: [undefined, Uri]; - [Commands.Tests_ViewOutput]: [undefined, CommandSource]; - [Commands.Tests_Select_And_Run_File]: [undefined, CommandSource]; - [Commands.Tests_Run_Current_File]: [undefined, CommandSource]; - [Commands.Tests_Stop]: [undefined, Uri]; - [Commands.Test_Reveal_Test_Item]: [TestDataItem]; - // When command is invoked from a tree node, first argument is the node data. - [Commands.Tests_Run]: [ - undefined | TestWorkspaceFolder, - undefined | CommandSource, - undefined | Uri, - undefined | TestsToRun - ]; - // When command is invoked from a tree node, first argument is the node data. - [Commands.Tests_Debug]: [ - undefined | TestWorkspaceFolder, - undefined | CommandSource, - undefined | Uri, - undefined | TestsToRun - ]; - [Commands.Tests_Run_Parametrized]: [undefined, undefined | CommandSource, Uri, TestFunction[], boolean]; - // When command is invoked from a tree node, first argument is the node data. - [Commands.Tests_Discover]: [undefined | TestWorkspaceFolder, undefined | CommandSource, undefined | Uri]; - [Commands.Tests_Run_Failed]: [undefined, CommandSource, Uri]; - [Commands.Tests_Select_And_Debug_Method]: [undefined, CommandSource, Uri]; - [Commands.Tests_Select_And_Run_Method]: [undefined, CommandSource, Uri]; + [Commands.Debug_In_Terminal]: [Uri]; [Commands.Tests_Configure]: [undefined, undefined | CommandSource, undefined | Uri]; - [Commands.Tests_Picker_UI]: [undefined, undefined | CommandSource, Uri, TestFunction[]]; - [Commands.Tests_Picker_UI_Debug]: [undefined, undefined | CommandSource, Uri, TestFunction[]]; - // When command is invoked from a tree node, first argument is the node data. - [Commands.runTestNode]: [TestDataItem]; - // When command is invoked from a tree node, first argument is the node data. - [Commands.debugTestNode]: [TestDataItem]; - // When command is invoked from a tree node, first argument is the node data. - [Commands.openTestNodeInEditor]: [TestDataItem]; - [Commands.navigateToTestFile]: [Uri, TestDataItem, boolean]; - [Commands.navigateToTestFunction]: [Uri, TestDataItem, boolean]; - [Commands.navigateToTestSuite]: [Uri, TestDataItem, boolean]; - [DSCommands.ExportFileAndOutputAsNotebook]: [Uri]; - [DSCommands.RunAllCells]: [Uri]; - [DSCommands.RunCell]: [Uri, number, number, number, number]; - [DSCommands.RunAllCellsAbove]: [Uri, number, number]; - [DSCommands.RunCellAndAllBelow]: [Uri, number, number]; - [DSCommands.RunAllCellsAbovePalette]: []; - [DSCommands.RunCellAndAllBelowPalette]: []; - [DSCommands.DebugCurrentCellPalette]: []; - [DSCommands.RunToLine]: [Uri, number, number]; - [DSCommands.RunFromLine]: [Uri, number, number]; - [DSCommands.ImportNotebook]: [undefined | Uri, undefined | CommandSource]; - [DSCommands.ImportNotebookFile]: [undefined | Uri, undefined | CommandSource]; - [DSCommands.OpenNotebook]: [undefined | Uri, undefined | CommandSource]; - [DSCommands.OpenNotebookInPreviewEditor]: [undefined | Uri]; - [DSCommands.ExportFileAsNotebook]: [undefined | Uri, undefined | CommandSource]; - [DSCommands.RunFileInInteractiveWindows]: [Uri]; - [DSCommands.DebugFileInInteractiveWindows]: [Uri]; - [DSCommands.DebugCell]: [Uri, number, number, number, number]; - [DSCommands.DebugStepOver]: []; - [DSCommands.DebugStop]: []; - [DSCommands.DebugContinue]: []; - [DSCommands.RunCurrentCellAndAddBelow]: [Uri]; - [DSCommands.InsertCellBelowPosition]: []; - [DSCommands.InsertCellBelow]: []; - [DSCommands.InsertCellAbove]: []; - [DSCommands.DeleteCells]: []; - [DSCommands.SelectCell]: []; - [DSCommands.SelectCellContents]: []; - [DSCommands.ExtendSelectionByCellAbove]: []; - [DSCommands.ExtendSelectionByCellBelow]: []; - [DSCommands.MoveCellsUp]: []; - [DSCommands.MoveCellsDown]: []; - [DSCommands.ChangeCellToMarkdown]: []; - [DSCommands.ChangeCellToCode]: []; - [DSCommands.GotoNextCellInFile]: []; - [DSCommands.GotoPrevCellInFile]: []; - [DSCommands.ScrollToCell]: [Uri, string]; - [DSCommands.ViewJupyterOutput]: []; - [DSCommands.ExportAsPythonScript]: [INotebookModel]; - [DSCommands.ExportToHTML]: [INotebookModel, string | undefined]; - [DSCommands.ExportToPDF]: [INotebookModel, string | undefined]; - [DSCommands.Export]: [Uri | INotebookModel, string | undefined]; - [DSCommands.SetJupyterKernel]: [KernelConnectionMetadata, Uri, undefined | Uri]; - [DSCommands.SwitchJupyterKernel]: [ISwitchKernelOptions | undefined]; - [DSCommands.SelectJupyterCommandLine]: [undefined | Uri]; - [DSCommands.SaveNotebookNonCustomEditor]: [INotebookModel]; - [DSCommands.SaveAsNotebookNonCustomEditor]: [INotebookModel, Uri]; - [DSCommands.OpenNotebookNonCustomEditor]: [Uri]; - [DSCommands.GatherQuality]: [string]; - [DSCommands.LatestExtension]: [string]; - [DSCommands.EnableLoadingWidgetsFrom3rdPartySource]: [undefined | never]; - [DSCommands.TrustNotebook]: [undefined | never | Uri]; - [DSCommands.NotebookEditorExpandAllCells]: []; - [DSCommands.NotebookEditorCollapseAllCells]: []; + [Commands.Tests_CopilotSetup]: [undefined | Uri]; + ['workbench.view.testing.focus']: []; + ['cursorMove']: [ + { + to: string; + by: string; + value: number; + }, + ]; + ['cursorEnd']: []; + ['python-envs.createTerminal']: [undefined | Uri]; } diff --git a/src/client/common/application/commands/createPythonFile.ts b/src/client/common/application/commands/createPythonFile.ts new file mode 100644 index 000000000000..10f388856896 --- /dev/null +++ b/src/client/common/application/commands/createPythonFile.ts @@ -0,0 +1,29 @@ +import { injectable, inject } from 'inversify'; +import { IExtensionSingleActivationService } from '../../../activation/types'; +import { Commands } from '../../constants'; +import { IApplicationShell, ICommandManager, IWorkspaceService } from '../types'; +import { sendTelemetryEvent } from '../../../telemetry'; +import { EventName } from '../../../telemetry/constants'; +import { IDisposableRegistry } from '../../types'; + +@injectable() +export class CreatePythonFileCommandHandler implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: true, virtualWorkspace: true }; + + constructor( + @inject(ICommandManager) private readonly commandManager: ICommandManager, + @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, + @inject(IApplicationShell) private readonly appShell: IApplicationShell, + @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry, + ) {} + + public async activate(): Promise { + this.disposables.push(this.commandManager.registerCommand(Commands.CreateNewFile, this.createPythonFile, this)); + } + + public async createPythonFile(): Promise { + const newFile = await this.workspaceService.openTextDocument({ language: 'python' }); + this.appShell.showTextDocument(newFile); + sendTelemetryEvent(EventName.CREATE_NEW_FILE_COMMAND); + } +} diff --git a/src/client/common/application/commands/reloadCommand.ts b/src/client/common/application/commands/reloadCommand.ts index 36fded88b797..ebad15dbb70d 100644 --- a/src/client/common/application/commands/reloadCommand.ts +++ b/src/client/common/application/commands/reloadCommand.ts @@ -14,16 +14,17 @@ import { IApplicationShell, ICommandManager } from '../types'; */ @injectable() export class ReloadVSCodeCommandHandler implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; constructor( @inject(ICommandManager) private readonly commandManager: ICommandManager, - @inject(IApplicationShell) private readonly appShell: IApplicationShell + @inject(IApplicationShell) private readonly appShell: IApplicationShell, ) {} public async activate(): Promise { this.commandManager.registerCommand('python.reloadVSCode', this.onReloadVSCode, this); } private async onReloadVSCode(message: string) { - const item = await this.appShell.showInformationMessage(message, Common.reload()); - if (item === Common.reload()) { + const item = await this.appShell.showInformationMessage(message, Common.reload); + if (item === Common.reload) { this.commandManager.executeCommand('workbench.action.reloadWindow').then(noop, noop); } } diff --git a/src/client/common/application/commands/reportIssueCommand.ts b/src/client/common/application/commands/reportIssueCommand.ts new file mode 100644 index 000000000000..9ae099e44b4f --- /dev/null +++ b/src/client/common/application/commands/reportIssueCommand.ts @@ -0,0 +1,144 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as os from 'os'; +import * as path from 'path'; +import { inject, injectable } from 'inversify'; +import { isEqual } from 'lodash'; +import * as fs from '../../platform/fs-paths'; +import { IExtensionSingleActivationService } from '../../../activation/types'; +import { IApplicationEnvironment, ICommandManager, IWorkspaceService } from '../types'; +import { EXTENSION_ROOT_DIR } from '../../../constants'; +import { IInterpreterService } from '../../../interpreter/contracts'; +import { Commands } from '../../constants'; +import { IConfigurationService, IPythonSettings } from '../../types'; +import { sendTelemetryEvent } from '../../../telemetry'; +import { EventName } from '../../../telemetry/constants'; +import { EnvironmentType } from '../../../pythonEnvironments/info'; +import { PythonSettings } from '../../configSettings'; +import { SystemVariables } from '../../variables/systemVariables'; +import { getExtensions } from '../../vscodeApis/extensionsApi'; + +/** + * Allows the user to report an issue related to the Python extension using our template. + */ +@injectable() +export class ReportIssueCommandHandler implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private readonly packageJSONSettings: any; + + constructor( + @inject(ICommandManager) private readonly commandManager: ICommandManager, + @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, + @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, + @inject(IConfigurationService) protected readonly configurationService: IConfigurationService, + @inject(IApplicationEnvironment) appEnvironment: IApplicationEnvironment, + ) { + this.packageJSONSettings = appEnvironment.packageJson?.contributes?.configuration?.properties; + } + + public async activate(): Promise { + this.commandManager.registerCommand(Commands.ReportIssue, this.openReportIssue, this); + } + + private argSettingsPath = path.join(EXTENSION_ROOT_DIR, 'resources', 'report_issue_user_settings.json'); + + private templatePath = path.join(EXTENSION_ROOT_DIR, 'resources', 'report_issue_template.md'); + + private userDataTemplatePath = path.join(EXTENSION_ROOT_DIR, 'resources', 'report_issue_user_data_template.md'); + + public async openReportIssue(): Promise { + const settings: IPythonSettings = this.configurationService.getSettings(); + const argSettings = JSON.parse(await fs.readFile(this.argSettingsPath, 'utf8')); + let userSettings = ''; + const keys: [keyof IPythonSettings] = Object.keys(settings) as [keyof IPythonSettings]; + keys.forEach((property) => { + const argSetting = argSettings[property]; + if (argSetting) { + if (typeof argSetting === 'object') { + let propertyHeaderAdded = false; + const argSettingsDict = (settings[property] as unknown) as Record; + if (typeof argSettingsDict === 'object') { + Object.keys(argSetting).forEach((item) => { + const prop = argSetting[item]; + if (prop) { + const defaultValue = this.getDefaultValue(`${property}.${item}`); + if (defaultValue === undefined || !isEqual(defaultValue, argSettingsDict[item])) { + if (!propertyHeaderAdded) { + userSettings = userSettings.concat(os.EOL, property, os.EOL); + propertyHeaderAdded = true; + } + const value = + prop === true ? JSON.stringify(argSettingsDict[item]) : '""'; + userSettings = userSettings.concat('• ', item, ': ', value, os.EOL); + } + } + }); + } + } else { + const defaultValue = this.getDefaultValue(property); + if (defaultValue === undefined || !isEqual(defaultValue, settings[property])) { + const value = argSetting === true ? JSON.stringify(settings[property]) : '""'; + userSettings = userSettings.concat(os.EOL, property, ': ', value, os.EOL); + } + } + } + }); + const template = await fs.readFile(this.templatePath, 'utf8'); + const userTemplate = await fs.readFile(this.userDataTemplatePath, 'utf8'); + const interpreter = await this.interpreterService.getActiveInterpreter(); + const pythonVersion = interpreter?.version?.raw ?? ''; + const languageServer = + this.workspaceService.getConfiguration('python').get('languageServer') || 'Not Found'; + const virtualEnvKind = interpreter?.envType || EnvironmentType.Unknown; + + const hasMultipleFolders = (this.workspaceService.workspaceFolders?.length ?? 0) > 1; + const hasMultipleFoldersText = + hasMultipleFolders && userSettings !== '' + ? `Multiroot scenario, following user settings may not apply:${os.EOL}` + : ''; + + const installedExtensions = getExtensions() + .filter((extension) => !extension.id.startsWith('vscode.')) + .sort((a, b) => { + if (a.packageJSON.name && b.packageJSON.name) { + return a.packageJSON.name.localeCompare(b.packageJSON.name); + } + return a.id.localeCompare(b.id); + }) + .map((extension) => { + let publisher: string = extension.packageJSON.publisher as string; + if (publisher) { + publisher = publisher.substring(0, 3); + } + return `|${extension.packageJSON.name}|${publisher}|${extension.packageJSON.version}|`; + }); + + await this.commandManager.executeCommand('workbench.action.openIssueReporter', { + extensionId: 'ms-python.python', + issueBody: template, + extensionData: userTemplate.format( + pythonVersion, + virtualEnvKind, + languageServer, + hasMultipleFoldersText, + userSettings, + installedExtensions.join('\n'), + ), + }); + sendTelemetryEvent(EventName.USE_REPORT_ISSUE_COMMAND, undefined, {}); + } + + private getDefaultValue(settingKey: string) { + if (!this.packageJSONSettings) { + return undefined; + } + const resource = PythonSettings.getSettingsUriAndTarget(undefined, this.workspaceService).uri; + const systemVariables = new SystemVariables(resource, undefined, this.workspaceService); + return systemVariables.resolveAny(this.packageJSONSettings[`python.${settingKey}`]?.default); + } +} diff --git a/src/client/common/application/contextKeyManager.ts b/src/client/common/application/contextKeyManager.ts new file mode 100644 index 000000000000..388fcf4a3841 --- /dev/null +++ b/src/client/common/application/contextKeyManager.ts @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable } from 'inversify'; +import { ExtensionContextKey } from './contextKeys'; +import { ICommandManager, IContextKeyManager } from './types'; + +@injectable() +export class ContextKeyManager implements IContextKeyManager { + private values: Map = new Map(); + + constructor(@inject(ICommandManager) private readonly commandManager: ICommandManager) {} + + public async setContext(key: ExtensionContextKey, value: boolean): Promise { + if (this.values.get(key) === value) { + return Promise.resolve(); + } + this.values.set(key, value); + return this.commandManager.executeCommand('setContext', key, value); + } +} diff --git a/src/client/common/application/contextKeys.ts b/src/client/common/application/contextKeys.ts new file mode 100644 index 000000000000..d6249f05eaec --- /dev/null +++ b/src/client/common/application/contextKeys.ts @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +export enum ExtensionContextKey { + showInstallPythonTile = 'showInstallPythonTile', + HasFailedTests = 'hasFailedTests', + RefreshingTests = 'refreshingTests', + IsJupyterInstalled = 'isJupyterInstalled', +} diff --git a/src/client/common/application/customEditorService.ts b/src/client/common/application/customEditorService.ts deleted file mode 100644 index 4163c9af509d..000000000000 --- a/src/client/common/application/customEditorService.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { inject, injectable } from 'inversify'; -import * as vscode from 'vscode'; - -import { UseCustomEditorApi } from '../constants'; -import { traceError } from '../logger'; -import { IExtensionContext } from '../types'; -import { noop } from '../utils/misc'; -import { CustomEditorProvider, ICommandManager, ICustomEditorService, IWorkspaceService } from './types'; - -const EditorAssociationUpdatedKey = 'EditorAssociationUpdatedToUseCustomEditor'; -const ViewType = 'ms-python.python.notebook.ipynb'; - -@injectable() -export class CustomEditorService implements ICustomEditorService { - constructor( - @inject(ICommandManager) private commandManager: ICommandManager, - @inject(UseCustomEditorApi) private readonly useCustomEditorApi: boolean, - @inject(IWorkspaceService) private readonly workspace: IWorkspaceService, - @inject(IExtensionContext) private readonly extensionContext: IExtensionContext - ) { - this.enableCustomEditors().catch((e) => traceError(`Error setting up custom editors: `, e)); - } - - public registerCustomEditorProvider( - viewType: string, - provider: CustomEditorProvider, - options?: { - readonly webviewOptions?: vscode.WebviewPanelOptions; - readonly supportsMultipleEditorsPerDocument?: boolean; - } - ): vscode.Disposable { - if (this.useCustomEditorApi) { - // tslint:disable-next-line: no-any - return (vscode.window as any).registerCustomEditorProvider(viewType, provider, options); - } else { - return { dispose: noop }; - } - } - - public async openEditor(file: vscode.Uri, viewType: string): Promise { - if (this.useCustomEditorApi) { - await this.commandManager.executeCommand('vscode.openWith', file, viewType); - } - } - - // tslint:disable-next-line: no-any - private async enableCustomEditors() { - // This code is temporary. - const settings = this.workspace.getConfiguration('workbench', undefined); - const editorAssociations = settings.get('editorAssociations') as { - viewType: string; - filenamePattern: string; - }[]; - - // Update the settings. - if ( - this.useCustomEditorApi && - (!Array.isArray(editorAssociations) || - editorAssociations.length === 0 || - !editorAssociations.find((item) => item.viewType === ViewType)) - ) { - editorAssociations.push({ - viewType: ViewType, - filenamePattern: '*.ipynb' - }); - await Promise.all([ - this.extensionContext.globalState.update(EditorAssociationUpdatedKey, true), - settings.update('editorAssociations', editorAssociations, vscode.ConfigurationTarget.Global) - ]); - } - - // Revert the settings. - if ( - !this.useCustomEditorApi && - this.extensionContext.globalState.get(EditorAssociationUpdatedKey, false) && - Array.isArray(editorAssociations) && - editorAssociations.find((item) => item.viewType === ViewType) - ) { - const updatedSettings = editorAssociations.filter((item) => item.viewType !== ViewType); - await Promise.all([ - this.extensionContext.globalState.update(EditorAssociationUpdatedKey, false), - settings.update('editorAssociations', updatedSettings, vscode.ConfigurationTarget.Global) - ]); - } - } -} diff --git a/src/client/common/application/debugService.ts b/src/client/common/application/debugService.ts index f553b6cd6f5c..7de039e946c2 100644 --- a/src/client/common/application/debugService.ts +++ b/src/client/common/application/debugService.ts @@ -13,9 +13,10 @@ import { DebugConsole, DebugSession, DebugSessionCustomEvent, + DebugSessionOptions, Disposable, Event, - WorkspaceFolder + WorkspaceFolder, } from 'vscode'; import { IDebugService } from './types'; @@ -28,7 +29,7 @@ export class DebugService implements IDebugService { public get activeDebugSession(): DebugSession | undefined { return debug.activeDebugSession; } - public get breakpoints(): Breakpoint[] { + public get breakpoints(): readonly Breakpoint[] { return debug.breakpoints; } public get onDidChangeActiveDebugSession(): Event { @@ -46,18 +47,18 @@ export class DebugService implements IDebugService { public get onDidChangeBreakpoints(): Event { return debug.onDidChangeBreakpoints; } - // tslint:disable-next-line:no-any + public registerDebugConfigurationProvider(debugType: string, provider: any): Disposable { return debug.registerDebugConfigurationProvider(debugType, provider); } - // tslint:disable-next-line:no-any + public registerDebugAdapterTrackerFactory(debugType: string, provider: any): Disposable { return debug.registerDebugAdapterTrackerFactory(debugType, provider); } public startDebugging( folder: WorkspaceFolder | undefined, nameOrConfiguration: string | DebugConfiguration, - parentSession?: DebugSession + parentSession?: DebugSession | DebugSessionOptions, ): Thenable { return debug.startDebugging(folder, nameOrConfiguration, parentSession); } @@ -69,7 +70,7 @@ export class DebugService implements IDebugService { } public registerDebugAdapterDescriptorFactory( debugType: string, - factory: DebugAdapterDescriptorFactory + factory: DebugAdapterDescriptorFactory, ): Disposable { return debug.registerDebugAdapterDescriptorFactory(debugType, factory); } diff --git a/src/client/common/application/debugSessionTelemetry.ts b/src/client/common/application/debugSessionTelemetry.ts deleted file mode 100644 index 47aac873a835..000000000000 --- a/src/client/common/application/debugSessionTelemetry.ts +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { inject, injectable } from 'inversify'; -import { DebugAdapterTracker, DebugAdapterTrackerFactory, DebugSession, ProviderResult } from 'vscode'; -import { DebugProtocol } from 'vscode-debugprotocol'; - -import { IExtensionSingleActivationService } from '../../activation/types'; -import { AttachRequestArguments, ConsoleType, LaunchRequestArguments, TriggerType } from '../../debugger/types'; -import { sendTelemetryEvent } from '../../telemetry'; -import { EventName } from '../../telemetry/constants'; -import { IDisposableRegistry } from '../types'; -import { StopWatch } from '../utils/stopWatch'; -import { IDebugService } from './types'; - -class TelemetryTracker implements DebugAdapterTracker { - private timer = new StopWatch(); - private readonly trigger: TriggerType = 'launch'; - private readonly console: ConsoleType | undefined; - - constructor(session: DebugSession) { - this.trigger = session.configuration.type as TriggerType; - const debugConfiguration = session.configuration as Partial; - this.console = debugConfiguration.console; - } - - public onWillStartSession() { - this.sendTelemetry(EventName.DEBUG_SESSION_START); - } - - // tslint:disable-next-line:no-any - public onDidSendMessage(message: DebugProtocol.ProtocolMessage) { - if (message.type === 'response') { - const response = message as DebugProtocol.Response; - if (response.command === 'configurationDone') { - // "configurationDone" response is sent immediately after user code starts running. - this.sendTelemetry(EventName.DEBUG_SESSION_USER_CODE_RUNNING); - } - } - } - - public onWillStopSession() { - this.sendTelemetry(EventName.DEBUG_SESSION_STOP); - } - - public onError?(_error: Error) { - this.sendTelemetry(EventName.DEBUG_SESSION_ERROR); - } - - private sendTelemetry(eventName: EventName) { - if (eventName === EventName.DEBUG_SESSION_START) { - this.timer.reset(); - } - const telemetryProps = { - trigger: this.trigger, - console: this.console - }; - sendTelemetryEvent(eventName, this.timer.elapsedTime, telemetryProps); - } -} - -@injectable() -export class DebugSessionTelemetry implements DebugAdapterTrackerFactory, IExtensionSingleActivationService { - constructor( - @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, - @inject(IDebugService) debugService: IDebugService - ) { - disposableRegistry.push(debugService.registerDebugAdapterTrackerFactory('python', this)); - } - - public async activate(): Promise { - // We actually register in the constructor. Not necessary to do it here - } - - public createDebugAdapterTracker(session: DebugSession): ProviderResult { - return new TelemetryTracker(session); - } -} diff --git a/src/client/common/application/documentManager.ts b/src/client/common/application/documentManager.ts index d3fe7dd2140e..617d335e402b 100644 --- a/src/client/common/application/documentManager.ts +++ b/src/client/common/application/documentManager.ts @@ -16,13 +16,11 @@ import { ViewColumn, window, workspace, - WorkspaceEdit + WorkspaceEdit, } from 'vscode'; import { IDocumentManager } from './types'; -// tslint:disable:no-any unified-signatures - @injectable() export class DocumentManager implements IDocumentManager { public get textDocuments(): readonly TextDocument[] { @@ -31,7 +29,7 @@ export class DocumentManager implements IDocumentManager { public get activeTextEditor(): TextEditor | undefined { return window.activeTextEditor; } - public get visibleTextEditors(): TextEditor[] { + public get visibleTextEditors(): readonly TextEditor[] { return window.visibleTextEditors; } public get onDidChangeActiveTextEditor(): Event { @@ -40,7 +38,7 @@ export class DocumentManager implements IDocumentManager { public get onDidChangeTextDocument(): Event { return workspace.onDidChangeTextDocument; } - public get onDidChangeVisibleTextEditors(): Event { + public get onDidChangeVisibleTextEditors(): Event { return window.onDidChangeVisibleTextEditors; } public get onDidChangeTextEditorSelection(): Event { diff --git a/src/client/common/application/extensions.ts b/src/client/common/application/extensions.ts index 828f2ddcf804..e4b8f5bce73d 100644 --- a/src/client/common/application/extensions.ts +++ b/src/client/common/application/extensions.ts @@ -1,15 +1,28 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +/* eslint-disable class-methods-use-this */ +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. 'use strict'; -import { injectable } from 'inversify'; +import { inject, injectable } from 'inversify'; import { Event, Extension, extensions } from 'vscode'; +import * as stacktrace from 'stack-trace'; +import * as path from 'path'; import { IExtensions } from '../types'; +import { IFileSystem } from '../platform/types'; +import { EXTENSION_ROOT_DIR } from '../constants'; +/** + * Provides functions for tracking the list of extensions that VSCode has installed. + */ @injectable() export class Extensions implements IExtensions { - // tslint:disable-next-line:no-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private _cachedExtensions?: readonly Extension[]; + + constructor(@inject(IFileSystem) private readonly fs: IFileSystem) {} + + // eslint-disable-next-line @typescript-eslint/no-explicit-any public get all(): readonly Extension[] { return extensions.all; } @@ -18,8 +31,70 @@ export class Extensions implements IExtensions { return extensions.onDidChange; } - // tslint:disable-next-line:no-any - public getExtension(extensionId: any) { + public getExtension(extensionId: string): Extension | undefined { return extensions.getExtension(extensionId); } + + private get cachedExtensions() { + if (!this._cachedExtensions) { + this._cachedExtensions = extensions.all; + extensions.onDidChange(() => { + this._cachedExtensions = extensions.all; + }); + } + return this._cachedExtensions; + } + + /** + * Code borrowed from: + * https://github.com/microsoft/vscode-jupyter/blob/67fe33d072f11d6443cf232a06bed0ac5e24682c/src/platform/common/application/extensions.node.ts + */ + public async determineExtensionFromCallStack(): Promise<{ extensionId: string; displayName: string }> { + const { stack } = new Error(); + if (stack) { + const pythonExtRoot = path.join(EXTENSION_ROOT_DIR.toLowerCase(), path.sep); + const frames = stack + .split('\n') + .map((f) => { + const result = /\((.*)\)/.exec(f); + if (result) { + return result[1]; + } + return undefined; + }) + .filter((item) => item && !item.toLowerCase().startsWith(pythonExtRoot)) + .filter((item) => + // Use cached list of extensions as we need this to be fast. + this.cachedExtensions.some( + (ext) => item!.includes(ext.extensionUri.path) || item!.includes(ext.extensionUri.fsPath), + ), + ) as string[]; + stacktrace.parse(new Error('Ex')).forEach((item) => { + const fileName = item.getFileName(); + if (fileName && !fileName.toLowerCase().startsWith(pythonExtRoot)) { + frames.push(fileName); + } + }); + for (const frame of frames) { + // This file is from a different extension. Try to find its `package.json`. + let dirName = path.dirname(frame); + let last = frame; + while (dirName && dirName.length < last.length) { + const possiblePackageJson = path.join(dirName, 'package.json'); + if (await this.fs.pathExists(possiblePackageJson)) { + const text = await this.fs.readFile(possiblePackageJson); + try { + const json = JSON.parse(text); + return { extensionId: `${json.publisher}.${json.name}`, displayName: json.displayName }; + } catch { + // If parse fails, then not an extension. + } + } + last = dirName; + dirName = path.dirname(dirName); + } + } + } + return { extensionId: 'unknown', displayName: 'unknown' }; + } } diff --git a/src/client/common/application/notebook.ts b/src/client/common/application/notebook.ts deleted file mode 100644 index 529718ac414d..000000000000 --- a/src/client/common/application/notebook.ts +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -import { Disposable, Event, EventEmitter } from 'vscode'; -import type { - notebook, - NotebookCellMetadata, - NotebookCellsChangeEvent as VSCNotebookCellsChangeEvent, - NotebookContentProvider, - NotebookDocument, - NotebookDocumentFilter, - NotebookEditor, - NotebookKernel, - NotebookKernelProvider -} from 'vscode-proposed'; -import { UseProposedApi } from '../constants'; -import { IDisposableRegistry } from '../types'; -import { - IApplicationEnvironment, - IVSCodeNotebook, - NotebookCellLanguageChangeEvent, - NotebookCellOutputsChangeEvent, - NotebookCellsChangeEvent -} from './types'; - -@injectable() -export class VSCodeNotebook implements IVSCodeNotebook { - public get onDidChangeActiveNotebookKernel(): Event<{ - document: NotebookDocument; - kernel: NotebookKernel | undefined; - }> { - return this.canUseNotebookApi - ? this.notebook.onDidChangeActiveNotebookKernel - : new EventEmitter<{ - document: NotebookDocument; - kernel: NotebookKernel | undefined; - }>().event; - } - public get onDidChangeActiveNotebookEditor(): Event { - return this.canUseNotebookApi - ? this.notebook.onDidChangeActiveNotebookEditor - : new EventEmitter().event; - } - public get onDidOpenNotebookDocument(): Event { - return this.canUseNotebookApi - ? this.notebook.onDidOpenNotebookDocument - : new EventEmitter().event; - } - public get onDidCloseNotebookDocument(): Event { - return this.canUseNotebookApi - ? this.notebook.onDidCloseNotebookDocument - : new EventEmitter().event; - } - public get notebookDocuments(): ReadonlyArray { - return this.canUseNotebookApi ? this.notebook.notebookDocuments : []; - } - public get notebookEditors() { - return this.canUseNotebookApi ? this.notebook.visibleNotebookEditors : []; - } - public get onDidChangeNotebookDocument(): Event< - NotebookCellsChangeEvent | NotebookCellOutputsChangeEvent | NotebookCellLanguageChangeEvent - > { - return this.canUseNotebookApi - ? this._onDidChangeNotebookDocument.event - : new EventEmitter< - NotebookCellsChangeEvent | NotebookCellOutputsChangeEvent | NotebookCellLanguageChangeEvent - >().event; - } - public get activeNotebookEditor(): NotebookEditor | undefined { - if (!this.useProposedApi) { - return; - } - return this.notebook.activeNotebookEditor; - } - private get notebook() { - if (!this._notebook) { - // tslint:disable-next-line: no-require-imports - this._notebook = require('vscode').notebook; - } - return this._notebook!; - } - private readonly _onDidChangeNotebookDocument = new EventEmitter< - NotebookCellsChangeEvent | NotebookCellOutputsChangeEvent | NotebookCellLanguageChangeEvent - >(); - private addedEventHandlers?: boolean; - private _notebook?: typeof notebook; - private readonly canUseNotebookApi?: boolean; - private readonly handledCellChanges = new WeakSet(); - constructor( - @inject(UseProposedApi) private readonly useProposedApi: boolean, - @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry, - @inject(IApplicationEnvironment) readonly env: IApplicationEnvironment - ) { - if (this.useProposedApi && this.env.channel === 'insiders') { - this.addEventHandlers(); - this.canUseNotebookApi = true; - } - } - public registerNotebookContentProvider( - notebookType: string, - provider: NotebookContentProvider, - options?: { - transientOutputs: boolean; - transientMetadata: { [K in keyof NotebookCellMetadata]?: boolean }; - } - ): Disposable { - return this.notebook.registerNotebookContentProvider(notebookType, provider, options); - } - public registerNotebookKernelProvider( - selector: NotebookDocumentFilter, - provider: NotebookKernelProvider - ): Disposable { - return this.notebook.registerNotebookKernelProvider(selector, provider); - } - private addEventHandlers() { - if (this.addedEventHandlers) { - return; - } - this.addedEventHandlers = true; - this.disposables.push( - ...[ - this.notebook.onDidChangeCellLanguage((e) => - this._onDidChangeNotebookDocument.fire({ ...e, type: 'changeCellLanguage' }) - ), - this.notebook.onDidChangeCellOutputs((e) => - this._onDidChangeNotebookDocument.fire({ ...e, type: 'changeCellOutputs' }) - ), - this.notebook.onDidChangeNotebookCells((e) => { - if (this.handledCellChanges.has(e)) { - return; - } - this.handledCellChanges.add(e); - this._onDidChangeNotebookDocument.fire({ ...e, type: 'changeCells' }); - }) - ] - ); - } -} diff --git a/src/client/common/application/progressService.ts b/src/client/common/application/progressService.ts new file mode 100644 index 000000000000..fb19cad1136c --- /dev/null +++ b/src/client/common/application/progressService.ts @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { ProgressOptions } from 'vscode'; +import { Deferred, createDeferred } from '../utils/async'; +import { IApplicationShell } from './types'; + +export class ProgressService { + private deferred: Deferred | undefined; + + constructor(private readonly shell: IApplicationShell) {} + + public showProgress(options: ProgressOptions): void { + if (!this.deferred) { + this.createProgress(options); + } + } + + public hideProgress(): void { + if (this.deferred) { + this.deferred.resolve(); + this.deferred = undefined; + } + } + + private createProgress(options: ProgressOptions) { + this.shell.withProgress(options, () => { + this.deferred = createDeferred(); + return this.deferred.promise; + }); + } +} diff --git a/src/client/common/application/terminalManager.ts b/src/client/common/application/terminalManager.ts index 8fe6c067d0e6..dc2603e84a56 100644 --- a/src/client/common/application/terminalManager.ts +++ b/src/client/common/application/terminalManager.ts @@ -2,18 +2,58 @@ // Licensed under the MIT License. import { injectable } from 'inversify'; -import { Event, Terminal, TerminalOptions, window } from 'vscode'; +import { + Disposable, + Event, + EventEmitter, + Terminal, + TerminalOptions, + TerminalShellExecutionEndEvent, + TerminalShellIntegrationChangeEvent, + window, +} from 'vscode'; +import { traceLog } from '../../logging'; import { ITerminalManager } from './types'; @injectable() export class TerminalManager implements ITerminalManager { + private readonly didOpenTerminal = new EventEmitter(); + constructor() { + window.onDidOpenTerminal((terminal) => { + this.didOpenTerminal.fire(monkeyPatchTerminal(terminal)); + }); + } public get onDidCloseTerminal(): Event { return window.onDidCloseTerminal; } public get onDidOpenTerminal(): Event { - return window.onDidOpenTerminal; + return this.didOpenTerminal.event; } public createTerminal(options: TerminalOptions): Terminal { - return window.createTerminal(options); + return monkeyPatchTerminal(window.createTerminal(options)); + } + public onDidChangeTerminalShellIntegration(handler: (e: TerminalShellIntegrationChangeEvent) => void): Disposable { + return window.onDidChangeTerminalShellIntegration(handler); + } + public onDidEndTerminalShellExecution(handler: (e: TerminalShellExecutionEndEvent) => void): Disposable { + return window.onDidEndTerminalShellExecution(handler); + } + public onDidChangeTerminalState(handler: (e: Terminal) => void): Disposable { + return window.onDidChangeTerminalState(handler); + } +} + +/** + * Monkeypatch the terminal to log commands sent. + */ +function monkeyPatchTerminal(terminal: Terminal) { + if (!(terminal as any).isPatched) { + const oldSendText = terminal.sendText.bind(terminal); + terminal.sendText = (text: string, addNewLine: boolean = true) => { + traceLog(`Send text to terminal: ${text}`); + return oldSendText(text, addNewLine); + }; + (terminal as any).isPatched = true; } + return terminal; } diff --git a/src/client/common/application/types.ts b/src/client/common/application/types.ts index 11a8dee8c166..34a95fb604f0 100644 --- a/src/client/common/application/types.ts +++ b/src/client/common/application/types.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + 'use strict'; + import { Breakpoint, BreakpointsChangeEvent, @@ -14,6 +16,7 @@ import { DebugConsole, DebugSession, DebugSessionCustomEvent, + DebugSessionOptions, DecorationRenderOptions, Disposable, DocumentSelector, @@ -22,10 +25,11 @@ import { GlobPattern, InputBox, InputBoxOptions, + LanguageStatusItem, + LogOutputChannel, MessageItem, MessageOptions, OpenDialogOptions, - OutputChannel, Progress, ProgressOptions, QuickPick, @@ -36,6 +40,8 @@ import { StatusBarItem, Terminal, TerminalOptions, + TerminalShellExecutionEndEvent, + TerminalShellIntegrationChangeEvent, TextDocument, TextDocumentChangeEvent, TextDocumentShowOptions, @@ -47,44 +53,82 @@ import { TextEditorViewColumnChangeEvent, TreeView, TreeViewOptions, + UIKind, Uri, ViewColumn, - WebviewPanel, - WebviewPanelOptions, WindowState, WorkspaceConfiguration, WorkspaceEdit, WorkspaceFolder, WorkspaceFolderPickOptions, - WorkspaceFoldersChangeEvent + WorkspaceFoldersChangeEvent, } from 'vscode'; -import type { - NotebookCellLanguageChangeEvent as VSCNotebookCellLanguageChangeEvent, - NotebookCellMetadata, - NotebookCellOutputsChangeEvent as VSCNotebookCellOutputsChangeEvent, - NotebookCellsChangeEvent as VSCNotebookCellsChangeEvent, - NotebookContentProvider, - NotebookDocument, - NotebookDocumentFilter, - NotebookEditor, - NotebookKernel, - NotebookKernelProvider -} from 'vscode-proposed'; -import * as vsls from 'vsls/vscode'; - -import { IAsyncDisposable, Resource } from '../types'; + +import { Channel } from '../constants'; +import { Resource } from '../types'; import { ICommandNameArgumentTypeMapping } from './commands'; +import { ExtensionContextKey } from './contextKeys'; -// tslint:disable:no-any unified-signatures +export interface TerminalDataWriteEvent { + /** + * The {@link Terminal} for which the data was written. + */ + readonly terminal: Terminal; + /** + * The data being written. + */ + readonly data: string; +} + +export interface TerminalExecutedCommand { + /** + * The {@link Terminal} the command was executed in. + */ + terminal: Terminal; + /** + * The full command line that was executed, including both the command and the arguments. + */ + commandLine: string | undefined; + /** + * The current working directory that was reported by the shell. This will be a {@link Uri} + * if the string reported by the shell can reliably be mapped to the connected machine. + */ + cwd: Uri | string | undefined; + /** + * The exit code reported by the shell. + */ + exitCode: number | undefined; + /** + * The output of the command when it has finished executing. This is the plain text shown in + * the terminal buffer and does not include raw escape sequences. Depending on the shell + * setup, this may include the command line as part of the output. + */ + output: string | undefined; +} export const IApplicationShell = Symbol('IApplicationShell'); export interface IApplicationShell { + /** + * An event that is emitted when a terminal with shell integration activated has completed + * executing a command. + * + * Note that this event will not fire if the executed command exits the shell, listen to + * {@link onDidCloseTerminal} to handle that case. + */ + readonly onDidExecuteTerminalCommand: Event | undefined; /** * An [event](#Event) which fires when the focus state of the current window * changes. The value of the event represents whether the window is focused. */ readonly onDidChangeWindowState: Event; + /** + * An event which fires when the terminal's child pseudo-device is written to (the shell). + * In other words, this provides access to the raw data stream from the process running + * within the terminal, including VT sequences. + */ + readonly onDidWriteTerminalData: Event; + showInformationMessage(message: string, ...items: string[]): Thenable; /** @@ -236,7 +280,7 @@ export interface IApplicationShell { showQuickPick( items: string[] | Thenable, options?: QuickPickOptions, - token?: CancellationToken + token?: CancellationToken, ): Thenable; /** @@ -250,7 +294,7 @@ export interface IApplicationShell { showQuickPick( items: T[] | Thenable, options?: QuickPickOptions, - token?: CancellationToken + token?: CancellationToken, ): Thenable; /** @@ -284,6 +328,19 @@ export interface IApplicationShell { */ showInputBox(options?: InputBoxOptions, token?: CancellationToken): Thenable; + /** + * Show the given document in a text editor. A {@link ViewColumn column} can be provided + * to control where the editor is being shown. Might change the {@link window.activeTextEditor active editor}. + * + * @param document A text document to be shown. + * @param column A view column in which the {@link TextEditor editor} should be shown. The default is the {@link ViewColumn.Active active}, other values + * are adjusted to be `Min(column, columnCount + 1)`, the {@link ViewColumn.Active active}-column is not adjusted. Use {@linkcode ViewColumn.Beside} + * to open the editor to the side of the currently active one. + * @param preserveFocus When `true` the editor will not take focus. + * @return A promise that resolves to an {@link TextEditor editor}. + */ + showTextDocument(document: TextDocument, column?: ViewColumn, preserveFocus?: boolean): Thenable; + /** * Creates a [QuickPick](#QuickPick) to let the user pick an item from a list * of items of type T. @@ -331,6 +388,7 @@ export interface IApplicationShell { * @param hideWhenDone Thenable on which completion (resolve or reject) the message will be disposed. * @return A disposable which hides the status bar message. */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any setStatusBarMessage(text: string, hideWhenDone: Thenable): Disposable; /** @@ -352,7 +410,7 @@ export interface IApplicationShell { * @param priority The priority of the item. Higher values mean the item should be shown more to the left. * @return A new status bar item. */ - createStatusBarItem(alignment?: StatusBarAlignment, priority?: number): StatusBarItem; + createStatusBarItem(alignment?: StatusBarAlignment, priority?: number, id?: string): StatusBarItem; /** * Shows a selection list of [workspace folders](#workspace.workspaceFolders) to pick from. * Returns `undefined` if no folder is open. @@ -383,7 +441,7 @@ export interface IApplicationShell { */ withProgress( options: ProgressOptions, - task: (progress: Progress<{ message?: string; increment?: number }>, token: CancellationToken) => Thenable + task: (progress: Progress<{ message?: string; increment?: number }>, token: CancellationToken) => Thenable, ): Thenable; /** @@ -410,7 +468,7 @@ export interface IApplicationShell { */ withProgressCustomIcon( icon: string, - task: (progress: Progress<{ message?: string; increment?: number }>, token: CancellationToken) => Thenable + task: (progress: Progress<{ message?: string; increment?: number }>, token: CancellationToken) => Thenable, ): Thenable; /** @@ -426,7 +484,8 @@ export interface IApplicationShell { * * @param name Human-readable string which will be used to represent the channel in the UI. */ - createOutputChannel(name: string): OutputChannel; + createOutputChannel(name: string): LogOutputChannel; + createLanguageStatusItem(id: string, selector: DocumentSelector): LanguageStatusItem; } export const ICommandManager = Symbol('ICommandManager'); @@ -446,8 +505,10 @@ export interface ICommandManager { */ registerCommand( command: E, + // eslint-disable-next-line @typescript-eslint/no-explicit-any callback: (...args: U) => any, - thisArg?: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + thisArg?: any, ): Disposable; /** @@ -466,8 +527,10 @@ export interface ICommandManager { */ registerTextEditorCommand( command: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any callback: (textEditor: TextEditor, edit: TextEditorEdit, ...args: any[]) => void, - thisArg?: any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + thisArg?: any, ): Disposable; /** @@ -499,6 +562,16 @@ export interface ICommandManager { getCommands(filterInternal?: boolean): Thenable; } +export const IContextKeyManager = Symbol('IContextKeyManager'); +export interface IContextKeyManager { + setContext(key: ExtensionContextKey, value: boolean): Promise; +} + +export const IJupyterExtensionDependencyManager = Symbol('IJupyterExtensionDependencyManager'); +export interface IJupyterExtensionDependencyManager { + readonly isJupyterExtensionInstalled: boolean; +} + export const IDocumentManager = Symbol('IDocumentManager'); export interface IDocumentManager { @@ -518,7 +591,7 @@ export interface IDocumentManager { /** * The currently visible editors or an empty array. */ - readonly visibleTextEditors: TextEditor[]; + readonly visibleTextEditors: readonly TextEditor[]; /** * An [event](#Event) which fires when the [active editor](#window.activeTextEditor) @@ -538,7 +611,7 @@ export interface IDocumentManager { * An [event](#Event) which fires when the array of [visible editors](#window.visibleTextEditors) * has changed. */ - readonly onDidChangeVisibleTextEditors: Event; + readonly onDidChangeVisibleTextEditors: Event; /** * An [event](#Event) which fires when the selection in an editor has changed. @@ -671,6 +744,16 @@ export interface IWorkspaceService { */ readonly rootPath: string | undefined; + /** + * When true, the user has explicitly trusted the contents of the workspace. + */ + readonly isTrusted: boolean; + + /** + * Event that fires when the current workspace has been trusted. + */ + readonly onDidGrantWorkspaceTrust: Event; + /** * List of workspace folders or `undefined` when no folder is open. * *Note* that the first entry corresponds to the value of `rootPath`. @@ -720,12 +803,9 @@ export interface IWorkspaceService { */ readonly onDidChangeConfiguration: Event; /** - * Whether a workspace folder exists - * @type {boolean} - * @memberof IWorkspaceService + * Returns if we're running in a virtual workspace. */ - readonly hasWorkspaceFolders: boolean; - + readonly isVirtualWorkspace: boolean; /** * Returns the [workspace folder](#WorkspaceFolder) that contains a given uri. * * returns `undefined` when the given uri doesn't match any workspace folder @@ -738,9 +818,6 @@ export interface IWorkspaceService { /** * Generate a key that's unique to the workspace folder (could be fsPath). - * @param {(Uri | undefined)} resource - * @returns {string} - * @memberof IWorkspaceService */ getWorkspaceFolderIdentifier(resource: Uri | undefined, defaultValue?: string): string; /** @@ -776,7 +853,7 @@ export interface IWorkspaceService { globPattern: GlobPattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, - ignoreDeleteEvents?: boolean + ignoreDeleteEvents?: boolean, ): FileSystemWatcher; /** @@ -787,7 +864,8 @@ export interface IWorkspaceService { * will be matched against the file paths of resulting matches relative to their workspace. Use a [relative pattern](#RelativePattern) * to restrict the search results to a [workspace folder](#WorkspaceFolder). * @param exclude A [glob pattern](#GlobPattern) that defines files and folders to exclude. The glob pattern - * will be matched against the file paths of resulting matches relative to their workspace. + * will be matched against the file paths of resulting matches relative to their workspace. If `undefined` is passed, + * the glob patterns excluded in the `search.exclude` setting will be applied. * @param maxResults An upper-bound for the result. * @param token A token that can be used to signal cancellation to the underlying search engine. * @return A thenable that resolves to an array of resource identifiers. Will return no results if no @@ -797,7 +875,7 @@ export interface IWorkspaceService { include: GlobPattern, exclude?: GlobPattern, maxResults?: number, - token?: CancellationToken + token?: CancellationToken, ): Thenable; /** @@ -811,9 +889,30 @@ export interface IWorkspaceService { * * @param section A dot-separated identifier. * @param resource A resource for which the configuration is asked for + * @param languageSpecific Should the [python] language-specific settings be obtained? * @return The full configuration or a subset. */ - getConfiguration(section?: string, resource?: Uri): WorkspaceConfiguration; + getConfiguration(section?: string, resource?: Uri, languageSpecific?: boolean): WorkspaceConfiguration; + + /** + * Opens an untitled text document. The editor will prompt the user for a file + * path when the document is to be saved. The `options` parameter allows to + * specify the *language* and/or the *content* of the document. + * + * @param options Options to control how the document will be created. + * @return A promise that resolves to a {@link TextDocument document}. + */ + openTextDocument(options?: { language?: string; content?: string }): Thenable; + /** + * Saves the editor identified by the given resource and returns the resulting resource or `undefined` + * if save was not successful. + * + * **Note** that an editor with the provided resource must be opened in order to be saved. + * + * @param uri the associated uri for the opened editor to save. + * @return A thenable that resolves when the save operation has finished. + */ + save(uri: Uri): Thenable; } export const ITerminalManager = Symbol('ITerminalManager'); @@ -836,6 +935,12 @@ export interface ITerminalManager { * @return A new Terminal. */ createTerminal(options: TerminalOptions): Terminal; + + onDidChangeTerminalShellIntegration(handler: (e: TerminalShellIntegrationChangeEvent) => void): Disposable; + + onDidEndTerminalShellExecution(handler: (e: TerminalShellExecutionEndEvent) => void): Disposable; + + onDidChangeTerminalState(handler: (e: Terminal) => void): Disposable; } export const IDebugService = Symbol('IDebugManager'); @@ -856,7 +961,7 @@ export interface IDebugService { /** * List of breakpoints. */ - readonly breakpoints: Breakpoint[]; + readonly breakpoints: readonly Breakpoint[]; /** * An [event](#Event) which fires when the [active debug session](#debug.activeDebugSession) @@ -928,7 +1033,7 @@ export interface IDebugService { startDebugging( folder: WorkspaceFolder | undefined, nameOrConfiguration: string | DebugConfiguration, - parentSession?: DebugSession + parentSession?: DebugSession | DebugSessionOptions, ): Thenable; /** @@ -994,6 +1099,7 @@ export interface IApplicationEnvironment { * @type {any} * @memberof IApplicationEnvironment */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any readonly packageJson: any; /** * Gets the full path to the user settings file. (may or may not exist). @@ -1010,6 +1116,10 @@ export interface IApplicationEnvironment { * @memberof IApplicationShell */ readonly shell: string; + /** + * An {@link Event} which fires when the default shell changes. + */ + readonly onDidChangeShell: Event; /** * Gets the vscode channel (whether 'insiders' or 'stable'). */ @@ -1029,133 +1139,22 @@ export interface IApplicationEnvironment { * The custom uri scheme the editor registers to in the operating system. */ readonly uriScheme: string; -} - -export interface IWebviewMessageListener { /** - * Listens to webview messages - * @param message: the message being sent - * @param payload: extra data that came with the message + * The UI kind property indicates from which UI extensions + * are accessed from. For example, extensions could be accessed + * from a desktop application or a web browser. */ - onMessage(message: string, payload: any): void; -} - -export const IWebviewPanelMessageListener = Symbol('IWebviewPanelMessageListener'); -export interface IWebviewPanelMessageListener extends IWebviewMessageListener, IAsyncDisposable { + readonly uiKind: UIKind; /** - * Listens to web panel state changes - */ - onChangeViewState(panel: IWebviewPanel): void; -} - -export type WebviewMessage = { - /** - * Message type - */ - type: string; - - /** - * Payload - */ - payload?: any; -}; - -// Wraps a VS Code webview -export const IWebview = Symbol('IWebview'); -export interface IWebview { - /** - * Sends a message to the hosted html page - */ - postMessage(message: WebviewMessage): void; - /** - * Convert a uri for the local file system to one that can be used inside webviews. + * The name of a remote. Defined by extensions, popular samples are `wsl` for the Windows + * Subsystem for Linux or `ssh-remote` for remotes using a secure shell. * - * Webviews cannot directly load resources from the workspace or local file system using `file:` uris. The - * `asWebviewUri` function takes a local `file:` uri and converts it into a uri that can be used inside of - * a webview to load the same resource: - * - * ```ts - * webview.html = `` - * ``` - */ - asWebviewUri(localResource: Uri): Uri; -} - -// Wraps the VS Code webview panel -export const IWebviewPanel = Symbol('IWebviewPanel'); -export interface IWebviewPanel extends IWebview { - /** - * Event is fired when the load for a web panel fails - */ - readonly loadFailed: Event; - setTitle(val: string): void; - /** - * Makes the webpanel show up. - * @return A Promise that can be waited on - */ - show(preserveFocus: boolean): Promise; - - /** - * Indicates if this web panel is visible or not. - */ - isVisible(): boolean; - - /** - * Attempts to close the panel if it's visible - */ - close(): void; - /** - * Indicates if the webview has the focus or not. - */ - isActive(): boolean; - /** - * Updates the current working directory for serving up files. - * @param cwd + * *Note* that the value is `undefined` when there is no remote extension host but that the + * value is defined in all extension hosts (local and remote) in case a remote extension host + * exists. Use {@link Extension.extensionKind} to know if + * a specific extension runs remote or not. */ - updateCwd(cwd: string): void; -} - -export interface IWebviewOptions { - rootPath: string; - cwd: string; - scripts: string[]; -} - -export interface IWebviewPanelOptions extends IWebviewOptions { - viewColumn: ViewColumn; - listener: IWebviewPanelMessageListener; - title: string; - /** - * Additional paths apart from cwd and rootPath, that webview would allow loading resources/files from. - * E.g. required for webview to serve images from worksapces when nb is in a nested folder. - */ - additionalPaths?: string[]; - // tslint:disable-next-line: no-any - settings?: any; - // Web panel to use if supplied by VS code instead - webViewPanel?: WebviewPanel; -} - -// Wraps the VS Code api for creating a web panel -export const IWebviewPanelProvider = Symbol('IWebviewPanelProvider'); -export interface IWebviewPanelProvider { - create(options: IWebviewPanelOptions): Promise; -} - -// Wraps the vsls liveshare API -export const ILiveShareApi = Symbol('ILiveShareApi'); -export interface ILiveShareApi { - getApi(): Promise; -} - -// Wraps the liveshare api for testing -export const ILiveShareTestingApi = Symbol('ILiveShareTestingApi'); -export interface ILiveShareTestingApi extends ILiveShareApi { - isSessionStarted: boolean; - forceRole(role: vsls.Role): void; - startSession(): Promise; - stopSession(): Promise; - disableGuestChecker(): void; + readonly remoteName: string | undefined; } export const ILanguageService = Symbol('ILanguageService'); @@ -1181,8 +1180,6 @@ export interface ILanguageService { ): Disposable; } -export type Channel = 'stable' | 'insiders'; - /** * Wraps the `ActiveResourceService` API class. Created for injecting and mocking class methods in testing */ @@ -1191,326 +1188,6 @@ export interface IActiveResourceService { getActiveResource(): Resource; } -// Temporary hack to get the nyc compiler to find these types. vscode.proposed.d.ts doesn't work for some reason. - -// tslint:disable: interface-name -//#region Custom editor https://github.com/microsoft/vscode/issues/77131 - -/** - * Represents a custom document used by a [`CustomEditorProvider`](#CustomEditorProvider). - * - * Custom documents are only used within a given `CustomEditorProvider`. The lifecycle of a `CustomDocument` is - * managed by VS Code. When no more references remain to a `CustomDocument`, it is disposed of. - */ -export interface CustomDocument { - /** - * The associated uri for this document. - */ - readonly uri: Uri; - - /** - * Dispose of the custom document. - * - * This is invoked by VS Code when there are no more references to a given `CustomDocument` (for example when - * all editors associated with the document have been closed.) - */ - dispose(): void; -} - -/** - * Event triggered by extensions to signal to VS Code that an edit has occurred on an [`CustomDocument`](#CustomDocument). - * - * @see [`CustomDocumentProvider.onDidChangeCustomDocument`](#CustomDocumentProvider.onDidChangeCustomDocument). - */ -export interface CustomDocumentEditEvent { - /** - * The document that the edit is for. - */ - readonly document: T; - - /** - * Display name describing the edit. - * - * This is shown in the UI to users. - */ - readonly label?: string; - - /** - * Undo the edit operation. - * - * This is invoked by VS Code when the user undoes this edit. To implement `undo`, your - * extension should restore the document and editor to the state they were in just before this - * edit was added to VS Code's internal edit stack by `onDidChangeCustomDocument`. - */ - undo(): Thenable | void; - - /** - * Redo the edit operation. - * - * This is invoked by VS Code when the user redoes this edit. To implement `redo`, your - * extension should restore the document and editor to the state they were in just after this - * edit was added to VS Code's internal edit stack by `onDidChangeCustomDocument`. - */ - redo(): Thenable | void; -} - -/** - * Event triggered by extensions to signal to VS Code that the content of a [`CustomDocument`](#CustomDocument) - * has changed. - * - * @see [`CustomDocumentProvider.onDidChangeCustomDocument`](#CustomDocumentProvider.onDidChangeCustomDocument). - */ -export interface CustomDocumentContentChangeEvent { - /** - * The document that the change is for. - */ - readonly document: T; -} - -/** - * A backup for an [`CustomDocument`](#CustomDocument). - */ -export interface CustomDocumentBackup { - /** - * Unique identifier for the backup. - * - * This id is passed back to your extension in `openCustomDocument` when opening a custom editor from a backup. - */ - readonly id: string; - - /** - * Delete the current backup. - * - * This is called by VS Code when it is clear the current backup is no longer needed, such as when a new backup - * is made or when the file is saved. - */ - delete(): void; -} - -/** - * Additional information used to implement [`CustomEditableDocument.backup`](#CustomEditableDocument.backup). - */ -export interface CustomDocumentBackupContext { - /** - * Suggested file location to write the new backup. - * - * Note that your extension is free to ignore this and use its own strategy for backup. - * - * For editors for workspace resource, this destination will be in the workspace storage. The path may not - */ - readonly destination: Uri; -} - -/** - * Additional information about the opening custom document. - */ -export interface CustomDocumentOpenContext { - /** - * The id of the backup to restore the document from or `undefined` if there is no backup. - * - * If this is provided, your extension should restore the editor from the backup instead of reading the file - * the user's workspace. - */ - readonly backupId?: string; -} - -/** - * Provider for readonly custom editors that use a custom document model. - * - * Custom editors use [`CustomDocument`](#CustomDocument) as their document model instead of a [`TextDocument`](#TextDocument). - * - * You should use this type of custom editor when dealing with binary files or more complex scenarios. For simple - * text based documents, use [`CustomTextEditorProvider`](#CustomTextEditorProvider) instead. - * - * @param T Type of the custom document returned by this provider. - */ -export interface CustomReadonlyEditorProvider { - /** - * Create a new document for a given resource. - * - * `openCustomDocument` is called when the first editor for a given resource is opened, and the resolve document - * is passed to `resolveCustomEditor`. The resolved `CustomDocument` is re-used for subsequent editor opens. - * If all editors for a given resource are closed, the `CustomDocument` is disposed of. Opening an editor at - * this point will trigger another call to `openCustomDocument`. - * - * @param uri Uri of the document to open. - * @param openContext Additional information about the opening custom document. - * @param token A cancellation token that indicates the result is no longer needed. - * - * @return The custom document. - */ - openCustomDocument(uri: Uri, openContext: CustomDocumentOpenContext, token: CancellationToken): Thenable | T; - - /** - * Resolve a custom editor for a given resource. - * - * This is called whenever the user opens a new editor for this `CustomEditorProvider`. - * - * To resolve a custom editor, the provider must fill in its initial html content and hook up all - * the event listeners it is interested it. The provider can also hold onto the `WebviewPanel` to use later, - * for example in a command. See [`WebviewPanel`](#WebviewPanel) for additional details. - * - * @param document Document for the resource being resolved. - * @param webviewPanel Webview to resolve. - * @param token A cancellation token that indicates the result is no longer needed. - * - * @return Optional thenable indicating that the custom editor has been resolved. - */ - resolveCustomEditor(document: T, webviewPanel: WebviewPanel, token: CancellationToken): Thenable | void; -} - -/** - * Provider for editiable custom editors that use a custom document model. - * - * Custom editors use [`CustomDocument`](#CustomDocument) as their document model instead of a [`TextDocument`](#TextDocument). - * This gives extensions full control over actions such as edit, save, and backup. - * - * You should use this type of custom editor when dealing with binary files or more complex scenarios. For simple - * text based documents, use [`CustomTextEditorProvider`](#CustomTextEditorProvider) instead. - * - * @param T Type of the custom document returned by this provider. - */ -export interface CustomEditorProvider - extends CustomReadonlyEditorProvider { - /** - * Signal that an edit has occurred inside a custom editor. - * - * This event must be fired by your extension whenever an edit happens in a custom editor. An edit can be - * anything from changing some text, to cropping an image, to reordering a list. Your extension is free to - * define what an edit is and what data is stored on each edit. - * - * Firing `onDidChange` causes VS Code to mark the editors as being dirty. This is cleared when the user either - * saves or reverts the file. - * - * Editors that support undo/redo must fire a `CustomDocumentEditEvent` whenever an edit happens. This allows - * users to undo and redo the edit using VS Code's standard VS Code keyboard shortcuts. VS Code will also mark - * the editor as no longer being dirty if the user undoes all edits to the last saved state. - * - * Editors that support editing but cannot use VS Code's standard undo/redo mechanism must fire a `CustomDocumentContentChangeEvent`. - * The only way for a user to clear the dirty state of an editor that does not support undo/redo is to either - * `save` or `revert` the file. - * - * An editor should only ever fire `CustomDocumentEditEvent` events, or only ever fire `CustomDocumentContentChangeEvent` events. - */ - readonly onDidChangeCustomDocument: Event> | Event>; - - /** - * Save a custom document. - * - * This method is invoked by VS Code when the user saves a custom editor. This can happen when the user - * triggers save while the custom editor is active, by commands such as `save all`, or by auto save if enabled. - * - * To implement `save`, the implementer must persist the custom editor. This usually means writing the - * file data for the custom document to disk. After `save` completes, any associated editor instances will - * no longer be marked as dirty. - * - * @param document Document to save. - * @param cancellation Token that signals the save is no longer required (for example, if another save was triggered). - * - * @return Thenable signaling that saving has completed. - */ - saveCustomDocument(document: T, cancellation: CancellationToken): Thenable; - - /** - * Save a custom document to a different location. - * - * This method is invoked by VS Code when the user triggers 'save as' on a custom editor. The implementer must - * persist the custom editor to `destination`. - * - * When the user accepts save as, the current editor is be replaced by an non-dirty editor for the newly saved file. - * - * @param document Document to save. - * @param destination Location to save to. - * @param cancellation Token that signals the save is no longer required. - * - * @return Thenable signaling that saving has completed. - */ - saveCustomDocumentAs(document: T, destination: Uri, cancellation: CancellationToken): Thenable; - - /** - * Revert a custom document to its last saved state. - * - * This method is invoked by VS Code when the user triggers `File: Revert File` in a custom editor. (Note that - * this is only used using VS Code's `File: Revert File` command and not on a `git revert` of the file). - * - * To implement `revert`, the implementer must make sure all editor instances (webviews) for `document` - * are displaying the document in the same state is saved in. This usually means reloading the file from the - * workspace. - * - * @param document Document to revert. - * @param cancellation Token that signals the revert is no longer required. - * - * @return Thenable signaling that the change has completed. - */ - revertCustomDocument(document: T, cancellation: CancellationToken): Thenable; - - /** - * Back up a dirty custom document. - * - * Backups are used for hot exit and to prevent data loss. Your `backup` method should persist the resource in - * its current state, i.e. with the edits applied. Most commonly this means saving the resource to disk in - * the `ExtensionContext.storagePath`. When VS Code reloads and your custom editor is opened for a resource, - * your extension should first check to see if any backups exist for the resource. If there is a backup, your - * extension should load the file contents from there instead of from the resource in the workspace. - * - * `backup` is triggered whenever an edit it made. Calls to `backup` are debounced so that if multiple edits are - * made in quick succession, `backup` is only triggered after the last one. `backup` is not invoked when - * `auto save` is enabled (since auto save already persists resource ). - * - * @param document Document to backup. - * @param context Information that can be used to backup the document. - * @param cancellation Token that signals the current backup since a new backup is coming in. It is up to your - * extension to decided how to respond to cancellation. If for example your extension is backing up a large file - * in an operation that takes time to complete, your extension may decide to finish the ongoing backup rather - * than cancelling it to ensure that VS Code has some valid backup. - */ - backupCustomDocument( - document: T, - context: CustomDocumentBackupContext, - cancellation: CancellationToken - ): Thenable; -} - -// #endregion - -export const ICustomEditorService = Symbol('ICustomEditorService'); -export interface ICustomEditorService { - /** - * Register a new provider for webview editors of a given type. - * - * @param viewType Type of the webview editor provider. - * @param provider Resolves webview editors. - * @param options Content settings for a webview panels the provider is given. - * - * @return Disposable that unregisters the `WebviewCustomEditorProvider`. - */ - registerCustomEditorProvider( - viewType: string, - provider: CustomReadonlyEditorProvider | CustomEditorProvider, - options?: { - readonly webviewOptions?: WebviewPanelOptions; - - /** - * Only applies to `CustomReadonlyEditorProvider | CustomEditorProvider`. - * - * Indicates that the provider allows multiple editor instances to be open at the same time for - * the same resource. - * - * If not set, VS Code only allows one editor instance to be open at a time for each resource. If the - * user tries to open a second editor instance for the resource, the first one is instead moved to where - * the second one was to be opened. - * - * When set, users can split and create copies of the custom editor. The custom editor must make sure it - * can properly synchronize the states of all editor instances for a resource so that they are consistent. - */ - readonly supportsMultipleEditorsPerDocument?: boolean; - } - ): Disposable; - /** - * Opens a file with a custom editor - */ - openEditor(file: Uri, viewType: string): Promise; -} export const IClipboard = Symbol('IClipboard'); export interface IClipboard { /** @@ -1523,43 +1200,3 @@ export interface IClipboard { */ writeText(value: string): Promise; } - -export type NotebookCellsChangeEvent = { type: 'changeCells' } & VSCNotebookCellsChangeEvent; -export type NotebookCellOutputsChangeEvent = { type: 'changeCellOutputs' } & VSCNotebookCellOutputsChangeEvent; -export type NotebookCellLanguageChangeEvent = { type: 'changeCellLanguage' } & VSCNotebookCellLanguageChangeEvent; -export type NotebookCellChangedEvent = - | NotebookCellsChangeEvent - | NotebookCellOutputsChangeEvent - | NotebookCellLanguageChangeEvent; -export const IVSCodeNotebook = Symbol('IVSCodeNotebook'); -export interface IVSCodeNotebook { - readonly onDidChangeActiveNotebookKernel: Event<{ - document: NotebookDocument; - kernel: NotebookKernel | undefined; - }>; - readonly notebookDocuments: ReadonlyArray; - readonly onDidOpenNotebookDocument: Event; - readonly onDidCloseNotebookDocument: Event; - readonly onDidChangeActiveNotebookEditor: Event; - readonly onDidChangeNotebookDocument: Event; - readonly notebookEditors: Readonly; - readonly activeNotebookEditor: NotebookEditor | undefined; - 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; - /** - * 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. - */ - transientMetadata: { [K in keyof NotebookCellMetadata]?: boolean }; - } - ): Disposable; - - registerNotebookKernelProvider(selector: NotebookDocumentFilter, provider: NotebookKernelProvider): Disposable; -} diff --git a/src/client/common/application/walkThroughs.ts b/src/client/common/application/walkThroughs.ts new file mode 100644 index 000000000000..89e57ee74e47 --- /dev/null +++ b/src/client/common/application/walkThroughs.ts @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +export enum PythonWelcome { + name = 'pythonWelcome', + windowsInstallId = 'python.installPythonWin8', + linuxInstallId = 'python.installPythonLinux', + macOSInstallId = 'python.installPythonMac', +} diff --git a/src/client/common/application/webviewPanels/webviewPanel.ts b/src/client/common/application/webviewPanels/webviewPanel.ts deleted file mode 100644 index 3f9d70bb8d28..000000000000 --- a/src/client/common/application/webviewPanels/webviewPanel.ts +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../extensions'; - -import { Event, EventEmitter, Uri, WebviewOptions, WebviewPanel as vscodeWebviewPanel, window } from 'vscode'; -import { traceError } from '../../logger'; -import { IFileSystem } from '../../platform/types'; -import { IDisposableRegistry } from '../../types'; -import * as localize from '../../utils/localize'; -import { IWebviewPanel, IWebviewPanelOptions } from '../types'; -import { Webview } from '../webviews/webview'; - -export class WebviewPanel extends Webview implements IWebviewPanel { - private panel: vscodeWebviewPanel | undefined; - private loadPromise: Promise; - private loadFailedEmitter = new EventEmitter(); - - constructor( - fs: IFileSystem, - private disposableRegistry: IDisposableRegistry, - private panelOptions: IWebviewPanelOptions, - additionalRootPaths: Uri[] = [] - ) { - super(fs, panelOptions); - - const webViewOptions: WebviewOptions = { - enableScripts: true, - localResourceRoots: [ - Uri.file(this.panelOptions.rootPath), - Uri.file(this.panelOptions.cwd), - ...additionalRootPaths - ] - }; - if (panelOptions.webViewPanel) { - this.panel = panelOptions.webViewPanel; - this.panel.webview.options = webViewOptions; - } else { - this.panel = window.createWebviewPanel( - panelOptions.title.toLowerCase().replace(' ', ''), - panelOptions.title, - { viewColumn: panelOptions.viewColumn, preserveFocus: true }, - { - retainContextWhenHidden: true, - enableFindWidget: true, - ...webViewOptions - } - ); - } - - // Set our base webview from the panel - this.webview = this.panel.webview; - - this.loadPromise = this.load(); - } - - public get loadFailed(): Event { - return this.loadFailedEmitter.event; - } - public async show(preserveFocus: boolean) { - await this.loadPromise; - if (this.panel) { - this.panel.reveal(this.panel.viewColumn, preserveFocus); - } - } - - public updateCwd(_cwd: string) { - // See issue https://github.com/microsoft/vscode-python/issues/8933 for implementing this. - } - - public close() { - if (this.panel) { - this.panel.dispose(); - } - } - - public isVisible(): boolean { - return this.panel ? this.panel.visible : false; - } - - public isActive(): boolean { - return this.panel ? this.panel.active : false; - } - - public setTitle(newTitle: string) { - this.panelOptions.title = newTitle; - if (this.panel) { - this.panel.title = newTitle; - } - } - - // tslint:disable-next-line:no-any - private async load() { - try { - if (this.panel) { - const localFilesExist = await Promise.all(this.panelOptions.scripts.map((s) => this.fs.fileExists(s))); - if (localFilesExist.every((exists) => exists === true)) { - // Call our special function that sticks this script inside of an html page - // and translates all of the paths to vscode-resource URIs - this.panel.webview.html = await this.generateLocalReactHtml(); - - // Reset when the current panel is closed - this.disposableRegistry.push( - this.panel.onDidDispose(() => { - this.panel = undefined; - this.panelOptions.listener.dispose().ignoreErrors(); - }) - ); - - this.disposableRegistry.push( - this.panel.webview.onDidReceiveMessage((message) => { - // Pass the message onto our listener - this.panelOptions.listener.onMessage(message.type, message.payload); - }) - ); - - this.disposableRegistry.push( - this.panel.onDidChangeViewState((_e) => { - // Pass the state change onto our listener - this.panelOptions.listener.onChangeViewState(this); - }) - ); - - // Set initial state - this.panelOptions.listener.onChangeViewState(this); - } else { - // Indicate that we can't load the file path - const badPanelString = localize.DataScience.badWebPanelFormatString(); - this.panel.webview.html = badPanelString.format(this.panelOptions.scripts.join(', ')); - } - } - } catch (error) { - // If our web panel failes to load, report that out so whatever - // is hosting the panel can clean up - traceError(`Error Loading WebPanel: ${error}`); - this.loadFailedEmitter.fire(); - } - } -} diff --git a/src/client/common/application/webviewPanels/webviewPanelProvider.ts b/src/client/common/application/webviewPanels/webviewPanelProvider.ts deleted file mode 100644 index 514c0dfd6d21..000000000000 --- a/src/client/common/application/webviewPanels/webviewPanelProvider.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { Uri } from 'vscode'; -import { IFileSystem } from '../../platform/types'; -import { IDisposableRegistry, IExtensionContext } from '../../types'; -import { IWebviewPanel, IWebviewPanelOptions, IWebviewPanelProvider } from '../types'; -import { WebviewPanel } from './webviewPanel'; - -@injectable() -export class WebviewPanelProvider implements IWebviewPanelProvider { - constructor( - @inject(IDisposableRegistry) private readonly disposableRegistry: IDisposableRegistry, - @inject(IFileSystem) private readonly fs: IFileSystem, - @inject(IExtensionContext) private readonly context: IExtensionContext - ) {} - - // tslint:disable-next-line:no-any - public async create(options: IWebviewPanelOptions): Promise { - // Allow loading resources from the `/tmp` folder when in webiviews. - // Used by widgets to place files that are not otherwise accessible. - const additionalRootPaths = [Uri.file(path.join(this.context.extensionPath, 'tmp'))]; - if (Array.isArray(options.additionalPaths)) { - additionalRootPaths.push(...options.additionalPaths.map((item) => Uri.file(item))); - } - return new WebviewPanel(this.fs, this.disposableRegistry, options, additionalRootPaths); - } -} diff --git a/src/client/common/application/webviews/webview.ts b/src/client/common/application/webviews/webview.ts deleted file mode 100644 index e21748f7e745..000000000000 --- a/src/client/common/application/webviews/webview.ts +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../extensions'; - -import * as path from 'path'; -import { Uri, Webview as vscodeWebview } from 'vscode'; -import { Identifiers } from '../../../datascience/constants'; -import { IFileSystem } from '../../platform/types'; -import { IWebview, IWebviewOptions, WebviewMessage } from '../types'; - -// Wrapper over a vscode webview. To be used with either WebviewPanel or WebviewView -export class Webview implements IWebview { - protected webview?: vscodeWebview; - constructor(protected fs: IFileSystem, protected options: IWebviewOptions) {} - - public asWebviewUri(localResource: Uri) { - if (!this.webview) { - throw new Error('WebView not initialized, too early to get a Uri'); - } - return this.webview.asWebviewUri(localResource); - } - - public postMessage(message: WebviewMessage) { - if (this.webview) { - this.webview.postMessage(message); - } - } - - // tslint:disable-next-line:no-any - protected async generateLocalReactHtml() { - if (!this.webview) { - throw new Error('WebView not initialized, too early to get a Uri'); - } - - const uriBase = this.webview.asWebviewUri(Uri.file(this.options.cwd)).toString(); - const uris = this.options.scripts.map((script) => this.webview!.asWebviewUri(Uri.file(script))); - const testFiles = await this.fs.getFiles(this.options.rootPath); - - // This method must be called so VSC is aware of files that can be pulled. - // Allow js and js.map files to be loaded by webpack in the webview. - testFiles - .filter((f) => f.toLowerCase().endsWith('.js') || f.toLowerCase().endsWith('.js.map')) - .forEach((f) => this.webview!.asWebviewUri(Uri.file(f))); - - const rootPath = this.webview.asWebviewUri(Uri.file(this.options.rootPath)).toString(); - const fontAwesomePath = this.webview - .asWebviewUri( - Uri.file( - path.join(this.options.rootPath, 'node_modules', 'font-awesome', 'css', 'font-awesome.min.css') - ) - ) - .toString(); - return ` - - - - - - - - VS Code Python React UI - - - - - -
- - ${uris.map((uri) => ``).join('\n')} - - `; - } -} diff --git a/src/client/common/application/workspace.ts b/src/client/common/application/workspace.ts index d004ff378364..a76a78777bef 100644 --- a/src/client/common/application/workspace.ts +++ b/src/client/common/application/workspace.ts @@ -9,11 +9,12 @@ import { Event, FileSystemWatcher, GlobPattern, + TextDocument, Uri, workspace, WorkspaceConfiguration, WorkspaceFolder, - WorkspaceFoldersChangeEvent + WorkspaceFoldersChangeEvent, } from 'vscode'; import { Resource } from '../types'; import { getOSType, OSType } from '../utils/platform'; @@ -35,14 +36,19 @@ export class WorkspaceService implements IWorkspaceService { public get onDidChangeWorkspaceFolders(): Event { return workspace.onDidChangeWorkspaceFolders; } - public get hasWorkspaceFolders() { - return Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length > 0; - } public get workspaceFile() { return workspace.workspaceFile; } - public getConfiguration(section?: string, resource?: Uri): WorkspaceConfiguration { - return workspace.getConfiguration(section, resource || null); + public getConfiguration( + section?: string, + resource?: Uri, + languageSpecific: boolean = false, + ): WorkspaceConfiguration { + if (languageSpecific) { + return workspace.getConfiguration(section, { uri: resource, languageId: 'python' }); + } else { + return workspace.getConfiguration(section, resource); + } } public getWorkspaceFolder(uri: Resource): WorkspaceFolder | undefined { return uri ? workspace.getWorkspaceFolder(uri) : undefined; @@ -52,31 +58,68 @@ export class WorkspaceService implements IWorkspaceService { } public createFileSystemWatcher( globPattern: GlobPattern, - _ignoreCreateEvents?: boolean, + ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, - ignoreDeleteEvents?: boolean + ignoreDeleteEvents?: boolean, ): FileSystemWatcher { return workspace.createFileSystemWatcher( globPattern, + ignoreCreateEvents, ignoreChangeEvents, - ignoreChangeEvents, - ignoreDeleteEvents + ignoreDeleteEvents, ); } public findFiles( include: GlobPattern, exclude?: GlobPattern, maxResults?: number, - token?: CancellationToken + token?: CancellationToken, ): Thenable { - return workspace.findFiles(include, exclude, maxResults, token); + const excludePattern = exclude === undefined ? this.searchExcludes : exclude; + return workspace.findFiles(include, excludePattern, maxResults, token); } public getWorkspaceFolderIdentifier(resource: Resource, defaultValue: string = ''): string { const workspaceFolder = resource ? workspace.getWorkspaceFolder(resource) : undefined; return workspaceFolder ? path.normalize( - getOSType() === OSType.Windows ? workspaceFolder.uri.fsPath.toUpperCase() : workspaceFolder.uri.fsPath + getOSType() === OSType.Windows + ? workspaceFolder.uri.fsPath.toUpperCase() + : workspaceFolder.uri.fsPath, ) : defaultValue; } + + public get isVirtualWorkspace(): boolean { + const isVirtualWorkspace = + workspace.workspaceFolders && workspace.workspaceFolders.every((f) => f.uri.scheme !== 'file'); + return !!isVirtualWorkspace; + } + + public get isTrusted(): boolean { + return workspace.isTrusted; + } + + public get onDidGrantWorkspaceTrust(): Event { + return workspace.onDidGrantWorkspaceTrust; + } + + public openTextDocument(options?: { language?: string; content?: string }): Thenable { + return workspace.openTextDocument(options); + } + + private get searchExcludes() { + const searchExcludes = this.getConfiguration('search.exclude'); + const enabledSearchExcludes = Object.keys(searchExcludes).filter((key) => searchExcludes.get(key) === true); + return `{${enabledSearchExcludes.join(',')}}`; + } + + public async save(uri: Uri): Promise { + try { + // This is a proposed API hence putting it inside try...catch. + const result = await workspace.save(uri); + return result; + } catch (ex) { + return undefined; + } + } } diff --git a/src/client/common/asyncDisposableRegistry.ts b/src/client/common/asyncDisposableRegistry.ts deleted file mode 100644 index 10bd9492b31e..000000000000 --- a/src/client/common/asyncDisposableRegistry.ts +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { injectable } from 'inversify'; -import { IAsyncDisposable, IAsyncDisposableRegistry, IDisposable } from './types'; - -// List of disposables that need to run a promise. -@injectable() -export class AsyncDisposableRegistry implements IAsyncDisposableRegistry { - private _list: (IDisposable | IAsyncDisposable)[] = []; - - public async dispose(): Promise { - const promises = this._list.map((l) => l.dispose()); - await Promise.all(promises); - this._list = []; - } - - public push(disposable?: IDisposable | IAsyncDisposable) { - if (disposable) { - this._list.push(disposable); - } - } - - public get list(): (IDisposable | IAsyncDisposable)[] { - return this._list; - } -} diff --git a/src/client/common/cancellation.ts b/src/client/common/cancellation.ts index 601ac933979d..b24abc7ab493 100644 --- a/src/client/common/cancellation.ts +++ b/src/client/common/cancellation.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. 'use strict'; -import { CancellationToken, CancellationTokenSource } from 'vscode'; +import { CancellationToken, CancellationTokenSource, CancellationError as VSCCancellationError } from 'vscode'; import { createDeferred } from './utils/async'; import * as localize from './utils/localize'; @@ -11,16 +11,15 @@ import * as localize from './utils/localize'; */ export class CancellationError extends Error { constructor() { - super(localize.Common.canceled()); + super(localize.Common.canceled); + } + + static isCancellationError(error: unknown): error is CancellationError { + return error instanceof CancellationError || error instanceof VSCCancellationError; } } /** * Create a promise that will either resolve with a default value or reject when the token is cancelled. - * - * @export - * @template T - * @param {({ defaultValue: T; token: CancellationToken; cancelAction: 'reject' | 'resolve' })} options - * @returns {Promise} */ export function createPromiseFromCancellation(options: { defaultValue: T; @@ -33,7 +32,8 @@ export function createPromiseFromCancellation(options: { return; } const complete = () => { - if (options.token!.isCancellationRequested) { + const optionsToken = options.token!; // NOSONAR + if (optionsToken.isCancellationRequested) { if (options.cancelAction === 'resolve') { return resolve(options.defaultValue); } @@ -49,10 +49,6 @@ export function createPromiseFromCancellation(options: { /** * Create a single unified cancellation token that wraps multiple cancellation tokens. - * - * @export - * @param {(...(CancellationToken | undefined)[])} tokens - * @returns {CancellationToken} */ export function wrapCancellationTokens(...tokens: (CancellationToken | undefined)[]): CancellationToken { const wrappedCancellantionToken = new CancellationTokenSource(); @@ -116,7 +112,6 @@ export namespace Cancellation { /** * isCanceled returns a boolean indicating if the cancel token has been canceled. - * @param cancelToken */ export function isCanceled(cancelToken?: CancellationToken): boolean { return cancelToken ? cancelToken.isCancellationRequested : false; @@ -124,7 +119,6 @@ export namespace Cancellation { /** * throws a CancellationError if the token is canceled. - * @param cancelToken */ export function throwIfCanceled(cancelToken?: CancellationToken): void { if (isCanceled(cancelToken)) { diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index 7473bcfcd952..91c06d9331fd 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -1,64 +1,58 @@ 'use strict'; -import * as child_process from 'child_process'; +// eslint-disable-next-line camelcase import * as path from 'path'; +import * as fs from 'fs'; import { ConfigurationChangeEvent, ConfigurationTarget, - DiagnosticSeverity, Disposable, Event, EventEmitter, Uri, - WorkspaceConfiguration + WorkspaceConfiguration, } from 'vscode'; import { LanguageServerType } from '../activation/types'; -import '../common/extensions'; -import { IInterpreterAutoSeletionProxyService, IInterpreterSecurityService } from '../interpreter/autoSelection/types'; -import { LogLevel } from '../logging/levels'; +import './extensions'; +import { IInterpreterAutoSelectionProxyService } from '../interpreter/autoSelection/types'; import { sendTelemetryEvent } from '../telemetry'; import { EventName } from '../telemetry/constants'; import { sendSettingTelemetry } from '../telemetry/envFileTelemetry'; +import { ITestingSettings } from '../testing/configuration/types'; import { IWorkspaceService } from './application/types'; import { WorkspaceService } from './application/workspace'; -import { DEFAULT_INTERPRETER_SETTING, isTestExecution } from './constants'; -import { DeprecatePythonPath } from './experiments/groups'; -import { ExtensionChannels } from './insidersBuild/types'; -import { IS_WINDOWS } from './platform/constants'; -import * as internalPython from './process/internal/python'; +import { DEFAULT_INTERPRETER_SETTING, isTestExecution, PYREFLY_EXTENSION_ID } from './constants'; import { - IAnalysisSettings, IAutoCompleteSettings, - IDataScienceSettings, + IDefaultLanguageServer, IExperiments, - IExperimentsManager, - IFormattingSettings, + IExtensions, IInterpreterPathService, - ILintingSettings, - ILoggingSettings, + IInterpreterSettings, IPythonSettings, - ISortImportSettings, + IREPLSettings, ITerminalSettings, - ITestingSettings, - IWorkspaceSymbolSettings, - LoggingLevelSettingType, - Resource + Resource, } from './types'; import { debounceSync } from './utils/decorators'; import { SystemVariables } from './variables/systemVariables'; +import { getOSType, OSType, isWindows } from './utils/platform'; +import { untildify } from './helpers'; -// tslint:disable:no-require-imports no-var-requires -const untildify = require('untildify'); - -// tslint:disable-next-line:completed-docs export class PythonSettings implements IPythonSettings { - public get onDidChange(): Event { + private get onDidChange(): Event { return this.changed.event; } + // eslint-disable-next-line class-methods-use-this + public static onConfigChange(): Event { + return PythonSettings.configChanged.event; + } + public get pythonPath(): string { return this._pythonPath; } + public set pythonPath(value: string) { if (this._pythonPath === value) { return; @@ -75,6 +69,7 @@ export class PythonSettings implements IPythonSettings { public get defaultInterpreterPath(): string { return this._defaultInterpreterPath; } + public set defaultInterpreterPath(value: string) { if (this._defaultInterpreterPath === value) { return; @@ -87,63 +82,79 @@ export class PythonSettings implements IPythonSettings { this._defaultInterpreterPath = value; } } + private static pythonSettings: Map = new Map(); - public showStartPage = true; - public downloadLanguageServer = true; - public jediPath = ''; - public jediMemoryLimit = 1024; + public envFile = ''; + public venvPath = ''; + + public interpreter!: IInterpreterSettings; + public venvFolders: string[] = []; + + public activeStateToolPath = ''; + public condaPath = ''; + public pipenvPath = ''; + public poetryPath = ''; + + public pixiToolPath = ''; + public devOptions: string[] = []; - public linting!: ILintingSettings; - public formatting!: IFormattingSettings; + public autoComplete!: IAutoCompleteSettings; + public testing!: ITestingSettings; + public terminal!: ITerminalSettings; - public sortImports!: ISortImportSettings; - public workspaceSymbols!: IWorkspaceSymbolSettings; - public disableInstallationChecks = false; + public globalModuleInstallation = false; - public analysis!: IAnalysisSettings; - public autoUpdateLanguageServer: boolean = true; - public datascience!: IDataScienceSettings; - public insidersChannel!: ExtensionChannels; + + public REPL!: IREPLSettings; + public experiments!: IExperiments; - public languageServer: LanguageServerType = LanguageServerType.Microsoft; - public logging: ILoggingSettings = { level: LogLevel.Error }; - protected readonly changed = new EventEmitter(); + public languageServer: LanguageServerType = LanguageServerType.Node; + + public languageServerIsDefault = true; + + protected readonly changed = new EventEmitter(); + + private static readonly configChanged = new EventEmitter(); + private workspaceRoot: Resource; + private disposables: Disposable[] = []; - // tslint:disable-next-line:variable-name - private _pythonPath = ''; + + private _pythonPath = 'python'; + private _defaultInterpreterPath = ''; + private readonly workspace: IWorkspaceService; constructor( workspaceFolder: Resource, - private readonly interpreterAutoSelectionService: IInterpreterAutoSeletionProxyService, - workspace?: IWorkspaceService, - private readonly experimentsManager?: IExperimentsManager, - private readonly interpreterPathService?: IInterpreterPathService, - private readonly interpreterSecurityService?: IInterpreterSecurityService + private readonly interpreterAutoSelectionService: IInterpreterAutoSelectionProxyService, + workspace: IWorkspaceService, + private readonly interpreterPathService: IInterpreterPathService, + private readonly defaultLS: IDefaultLanguageServer | undefined, + private readonly extensions: IExtensions, ) { this.workspace = workspace || new WorkspaceService(); this.workspaceRoot = workspaceFolder; this.initialize(); } - // tslint:disable-next-line:function-name + public static getInstance( resource: Uri | undefined, - interpreterAutoSelectionService: IInterpreterAutoSeletionProxyService, - workspace?: IWorkspaceService, - experimentsManager?: IExperimentsManager, - interpreterPathService?: IInterpreterPathService, - interpreterSecurityService?: IInterpreterSecurityService + interpreterAutoSelectionService: IInterpreterAutoSelectionProxyService, + workspace: IWorkspaceService, + interpreterPathService: IInterpreterPathService, + defaultLS: IDefaultLanguageServer | undefined, + extensions: IExtensions, ): PythonSettings { workspace = workspace || new WorkspaceService(); const workspaceFolderUri = PythonSettings.getSettingsUriAndTarget(resource, workspace).uri; @@ -154,28 +165,32 @@ export class PythonSettings implements IPythonSettings { workspaceFolderUri, interpreterAutoSelectionService, workspace, - experimentsManager, interpreterPathService, - interpreterSecurityService + defaultLS, + extensions, ); PythonSettings.pythonSettings.set(workspaceFolderKey, settings); + settings.onDidChange((event) => PythonSettings.debounceConfigChangeNotification(event)); // Pass null to avoid VSC from complaining about not passing in a value. - // tslint:disable-next-line:no-any - const config = workspace.getConfiguration('editor', resource ? resource : (null as any)); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const config = workspace.getConfiguration('editor', resource || (null as any)); const formatOnType = config ? config.get('formatOnType', false) : false; - sendTelemetryEvent(EventName.COMPLETION_ADD_BRACKETS, undefined, { - enabled: settings.autoComplete ? settings.autoComplete.addBrackets : false - }); sendTelemetryEvent(EventName.FORMAT_ON_TYPE, undefined, { enabled: formatOnType }); } - // tslint:disable-next-line:no-non-null-assertion + return PythonSettings.pythonSettings.get(workspaceFolderKey)!; } - // tslint:disable-next-line:type-literal-delimiter + @debounceSync(1) + // eslint-disable-next-line class-methods-use-this + protected static debounceConfigChangeNotification(event?: ConfigurationChangeEvent): void { + PythonSettings.configChanged.fire(event); + } + public static getSettingsUriAndTarget( resource: Uri | undefined, - workspace?: IWorkspaceService + workspace?: IWorkspaceService, ): { uri: Uri | undefined; target: ConfigurationTarget } { workspace = workspace || new WorkspaceService(); const workspaceFolder = resource ? workspace.getWorkspaceFolder(resource) : undefined; @@ -189,282 +204,134 @@ export class PythonSettings implements IPythonSettings { return { uri: workspaceFolderUri, target }; } - // tslint:disable-next-line:function-name - public static dispose() { + public static dispose(): void { if (!isTestExecution()) { throw new Error('Dispose can only be called from unit tests'); } - // tslint:disable-next-line:no-void-expression + PythonSettings.pythonSettings.forEach((item) => item && item.dispose()); PythonSettings.pythonSettings.clear(); } - public dispose() { - // tslint:disable-next-line:no-unsafe-any + + public static toSerializable(settings: IPythonSettings): IPythonSettings { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const clone: any = {}; + const keys = Object.entries(settings); + keys.forEach((e) => { + const [k, v] = e; + if (!k.includes('Manager') && !k.includes('Service') && !k.includes('onDid')) { + clone[k] = v; + } + }); + + return clone as IPythonSettings; + } + + public dispose(): void { this.disposables.forEach((disposable) => disposable && disposable.dispose()); this.disposables = []; } - // tslint:disable-next-line:cyclomatic-complexity max-func-body-length - protected update(pythonSettings: WorkspaceConfiguration) { + + protected update(pythonSettings: WorkspaceConfiguration): void { const workspaceRoot = this.workspaceRoot?.fsPath; const systemVariables: SystemVariables = new SystemVariables(undefined, workspaceRoot, this.workspace); - this.pythonPath = this.getPythonPath(pythonSettings, systemVariables, workspaceRoot); + this.pythonPath = this.getPythonPath(systemVariables, workspaceRoot); - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion const defaultInterpreterPath = systemVariables.resolveAny(pythonSettings.get('defaultInterpreterPath')); - this.defaultInterpreterPath = defaultInterpreterPath ? defaultInterpreterPath : DEFAULT_INTERPRETER_SETTING; + this.defaultInterpreterPath = defaultInterpreterPath || DEFAULT_INTERPRETER_SETTING; + if (this.defaultInterpreterPath === DEFAULT_INTERPRETER_SETTING) { + const autoSelectedPythonInterpreter = this.interpreterAutoSelectionService.getAutoSelectedInterpreter( + this.workspaceRoot, + ); + this.defaultInterpreterPath = autoSelectedPythonInterpreter?.path ?? this.defaultInterpreterPath; + } this.defaultInterpreterPath = getAbsolutePath(this.defaultInterpreterPath, workspaceRoot); - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + this.venvPath = systemVariables.resolveAny(pythonSettings.get('venvPath'))!; this.venvFolders = systemVariables.resolveAny(pythonSettings.get('venvFolders'))!; + const activeStateToolPath = systemVariables.resolveAny(pythonSettings.get('activeStateToolPath'))!; + this.activeStateToolPath = + activeStateToolPath && activeStateToolPath.length > 0 + ? getAbsolutePath(activeStateToolPath, workspaceRoot) + : activeStateToolPath; const condaPath = systemVariables.resolveAny(pythonSettings.get('condaPath'))!; this.condaPath = condaPath && condaPath.length > 0 ? getAbsolutePath(condaPath, workspaceRoot) : condaPath; const pipenvPath = systemVariables.resolveAny(pythonSettings.get('pipenvPath'))!; this.pipenvPath = pipenvPath && pipenvPath.length > 0 ? getAbsolutePath(pipenvPath, workspaceRoot) : pipenvPath; const poetryPath = systemVariables.resolveAny(pythonSettings.get('poetryPath'))!; this.poetryPath = poetryPath && poetryPath.length > 0 ? getAbsolutePath(poetryPath, workspaceRoot) : poetryPath; + const pixiToolPath = systemVariables.resolveAny(pythonSettings.get('pixiToolPath'))!; + this.pixiToolPath = + pixiToolPath && pixiToolPath.length > 0 ? getAbsolutePath(pixiToolPath, workspaceRoot) : pixiToolPath; - this.downloadLanguageServer = systemVariables.resolveAny( - pythonSettings.get('downloadLanguageServer', true) - )!; - this.autoUpdateLanguageServer = systemVariables.resolveAny( - pythonSettings.get('autoUpdateLanguageServer', true) - )!; - - let ls = pythonSettings.get('languageServer') ?? LanguageServerType.Jedi; - ls = systemVariables.resolveAny(ls); - if (!Object.values(LanguageServerType).includes(ls)) { - ls = LanguageServerType.Jedi; + this.interpreter = pythonSettings.get('interpreter') ?? { + infoVisibility: 'onPythonRelated', + }; + // Get as a string and verify; don't just accept. + let userLS = pythonSettings.get('languageServer'); + userLS = systemVariables.resolveAny(userLS); + + // Validate the user's input; if invalid, set it to the default. + if ( + !userLS || + userLS === 'Default' || + userLS === 'Microsoft' || + !Object.values(LanguageServerType).includes(userLS as LanguageServerType) + ) { + if ( + this.extensions.getExtension(PYREFLY_EXTENSION_ID) && + pythonSettings.get('pyrefly.disableLanguageServices') !== true + ) { + this.languageServer = LanguageServerType.None; + } else { + this.languageServer = this.defaultLS?.defaultLSType ?? LanguageServerType.None; + } + this.languageServerIsDefault = true; + } else if (userLS === 'JediLSP') { + // Switch JediLSP option to Jedi. + this.languageServer = LanguageServerType.Jedi; + this.languageServerIsDefault = false; + } else { + this.languageServer = userLS as LanguageServerType; + this.languageServerIsDefault = false; } - this.languageServer = ls; - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - this.jediPath = systemVariables.resolveAny(pythonSettings.get('jediPath'))!; - if (typeof this.jediPath === 'string' && this.jediPath.length > 0) { - this.jediPath = getAbsolutePath(systemVariables.resolveAny(this.jediPath), workspaceRoot); + const autoCompleteSettings = systemVariables.resolveAny( + pythonSettings.get('autoComplete'), + )!; + if (this.autoComplete) { + Object.assign(this.autoComplete, autoCompleteSettings); } else { - this.jediPath = ''; + this.autoComplete = autoCompleteSettings; } - this.jediMemoryLimit = pythonSettings.get('jediMemoryLimit')!; const envFileSetting = pythonSettings.get('envFile'); this.envFile = systemVariables.resolveAny(envFileSetting)!; sendSettingTelemetry(this.workspace, envFileSetting); - // tslint:disable-next-line:no-any - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion no-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any this.devOptions = systemVariables.resolveAny(pythonSettings.get('devOptions'))!; this.devOptions = Array.isArray(this.devOptions) ? this.devOptions : []; - // tslint:disable-next-line: no-any - const loggingSettings = systemVariables.resolveAny(pythonSettings.get('logging'))!; - loggingSettings.level = convertSettingTypeToLogLevel(loggingSettings.level); - if (this.logging) { - Object.assign(this.logging, loggingSettings); - } else { - this.logging = loggingSettings; - } - - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - const lintingSettings = systemVariables.resolveAny(pythonSettings.get('linting'))!; - if (this.linting) { - Object.assign(this.linting, lintingSettings); - } else { - this.linting = lintingSettings; - } - - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - const analysisSettings = systemVariables.resolveAny(pythonSettings.get('analysis'))!; - if (this.analysis) { - Object.assign(this.analysis, analysisSettings); - } else { - this.analysis = analysisSettings; - } - - this.disableInstallationChecks = pythonSettings.get('disableInstallationCheck') === true; this.globalModuleInstallation = pythonSettings.get('globalModuleInstallation') === true; - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - const sortImportSettings = systemVariables.resolveAny(pythonSettings.get('sortImports'))!; - if (this.sortImports) { - Object.assign(this.sortImports, sortImportSettings); - } else { - this.sortImports = sortImportSettings; - } - // Support for travis. - this.sortImports = this.sortImports ? this.sortImports : { path: '', args: [] }; - // Support for travis. - this.linting = this.linting - ? this.linting - : { - enabled: false, - ignorePatterns: [], - flake8Args: [], - flake8Enabled: false, - flake8Path: 'flake', - lintOnSave: false, - maxNumberOfProblems: 100, - mypyArgs: [], - mypyEnabled: false, - mypyPath: 'mypy', - banditArgs: [], - banditEnabled: false, - banditPath: 'bandit', - pycodestyleArgs: [], - pycodestyleEnabled: false, - pycodestylePath: 'pycodestyle', - pylamaArgs: [], - pylamaEnabled: false, - pylamaPath: 'pylama', - prospectorArgs: [], - prospectorEnabled: false, - prospectorPath: 'prospector', - pydocstyleArgs: [], - pydocstyleEnabled: false, - pydocstylePath: 'pydocstyle', - pylintArgs: [], - pylintEnabled: false, - pylintPath: 'pylint', - pylintCategorySeverity: { - convention: DiagnosticSeverity.Hint, - error: DiagnosticSeverity.Error, - fatal: DiagnosticSeverity.Error, - refactor: DiagnosticSeverity.Hint, - warning: DiagnosticSeverity.Warning - }, - pycodestyleCategorySeverity: { - E: DiagnosticSeverity.Error, - W: DiagnosticSeverity.Warning - }, - flake8CategorySeverity: { - E: DiagnosticSeverity.Error, - W: DiagnosticSeverity.Warning, - // Per http://flake8.pycqa.org/en/latest/glossary.html#term-error-code - // 'F' does not mean 'fatal as in PyLint but rather 'pyflakes' such as - // unused imports, variables, etc. - F: DiagnosticSeverity.Warning - }, - mypyCategorySeverity: { - error: DiagnosticSeverity.Error, - note: DiagnosticSeverity.Hint - }, - pylintUseMinimalCheckers: false - }; - this.linting.pylintPath = getAbsolutePath(systemVariables.resolveAny(this.linting.pylintPath), workspaceRoot); - this.linting.flake8Path = getAbsolutePath(systemVariables.resolveAny(this.linting.flake8Path), workspaceRoot); - this.linting.pycodestylePath = getAbsolutePath( - systemVariables.resolveAny(this.linting.pycodestylePath), - workspaceRoot - ); - this.linting.pylamaPath = getAbsolutePath(systemVariables.resolveAny(this.linting.pylamaPath), workspaceRoot); - this.linting.prospectorPath = getAbsolutePath( - systemVariables.resolveAny(this.linting.prospectorPath), - workspaceRoot - ); - this.linting.pydocstylePath = getAbsolutePath( - systemVariables.resolveAny(this.linting.pydocstylePath), - workspaceRoot - ); - this.linting.mypyPath = getAbsolutePath(systemVariables.resolveAny(this.linting.mypyPath), workspaceRoot); - this.linting.banditPath = getAbsolutePath(systemVariables.resolveAny(this.linting.banditPath), workspaceRoot); - - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - const formattingSettings = systemVariables.resolveAny(pythonSettings.get('formatting'))!; - if (this.formatting) { - Object.assign(this.formatting, formattingSettings); - } else { - this.formatting = formattingSettings; - } - // Support for travis. - this.formatting = this.formatting - ? this.formatting - : { - autopep8Args: [], - autopep8Path: 'autopep8', - provider: 'autopep8', - blackArgs: [], - blackPath: 'black', - yapfArgs: [], - yapfPath: 'yapf' - }; - this.formatting.autopep8Path = getAbsolutePath( - systemVariables.resolveAny(this.formatting.autopep8Path), - workspaceRoot - ); - this.formatting.yapfPath = getAbsolutePath(systemVariables.resolveAny(this.formatting.yapfPath), workspaceRoot); - this.formatting.blackPath = getAbsolutePath( - systemVariables.resolveAny(this.formatting.blackPath), - workspaceRoot - ); - - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - const autoCompleteSettings = systemVariables.resolveAny( - pythonSettings.get('autoComplete') - )!; - if (this.autoComplete) { - Object.assign(this.autoComplete, autoCompleteSettings); - } else { - this.autoComplete = autoCompleteSettings; - } - // Support for travis. - this.autoComplete = this.autoComplete - ? this.autoComplete - : { - extraPaths: [], - addBrackets: false, - showAdvancedMembers: false, - typeshedPaths: [] - }; - - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - const workspaceSymbolsSettings = systemVariables.resolveAny( - pythonSettings.get('workspaceSymbols') - )!; - if (this.workspaceSymbols) { - Object.assign( - this.workspaceSymbols, - workspaceSymbolsSettings - ); - } else { - this.workspaceSymbols = workspaceSymbolsSettings; - } - // Support for travis. - this.workspaceSymbols = this.workspaceSymbols - ? this.workspaceSymbols - : { - ctagsPath: 'ctags', - enabled: true, - exclusionPatterns: [], - rebuildOnFileSave: true, - rebuildOnStart: true, - tagFilePath: workspaceRoot ? path.join(workspaceRoot, 'tags') : '' - }; - this.workspaceSymbols.tagFilePath = getAbsolutePath( - systemVariables.resolveAny(this.workspaceSymbols.tagFilePath), - workspaceRoot - ); - - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion const testSettings = systemVariables.resolveAny(pythonSettings.get('testing'))!; if (this.testing) { Object.assign(this.testing, testSettings); } else { this.testing = testSettings; if (isTestExecution() && !this.testing) { - // tslint:disable-next-line:prefer-type-cast - // tslint:disable-next-line:no-object-literal-type-assertion this.testing = { - nosetestArgs: [], pytestArgs: [], unittestArgs: [], promptToConfigure: true, debugPort: 3000, - nosetestsEnabled: false, pytestEnabled: false, unittestEnabled: false, - nosetestPath: 'nosetests', pytestPath: 'pytest', - autoTestDiscoverOnSaveEnabled: true + autoTestDiscoverOnSaveEnabled: true, + autoTestDiscoverOnSavePattern: '**/*.py', } as ITestingSettings; } } @@ -475,39 +342,29 @@ export class PythonSettings implements IPythonSettings { : { promptToConfigure: true, debugPort: 3000, - nosetestArgs: [], - nosetestPath: 'nosetest', - nosetestsEnabled: false, pytestArgs: [], pytestEnabled: false, pytestPath: 'pytest', unittestArgs: [], unittestEnabled: false, - autoTestDiscoverOnSaveEnabled: true + autoTestDiscoverOnSaveEnabled: true, + autoTestDiscoverOnSavePattern: '**/*.py', }; this.testing.pytestPath = getAbsolutePath(systemVariables.resolveAny(this.testing.pytestPath), workspaceRoot); - this.testing.nosetestPath = getAbsolutePath( - systemVariables.resolveAny(this.testing.nosetestPath), - workspaceRoot - ); if (this.testing.cwd) { this.testing.cwd = getAbsolutePath(systemVariables.resolveAny(this.testing.cwd), workspaceRoot); } // Resolve any variables found in the test arguments. - this.testing.nosetestArgs = this.testing.nosetestArgs.map((arg) => systemVariables.resolveAny(arg)); this.testing.pytestArgs = this.testing.pytestArgs.map((arg) => systemVariables.resolveAny(arg)); this.testing.unittestArgs = this.testing.unittestArgs.map((arg) => systemVariables.resolveAny(arg)); - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion const terminalSettings = systemVariables.resolveAny(pythonSettings.get('terminal'))!; if (this.terminal) { Object.assign(this.terminal, terminalSettings); } else { this.terminal = terminalSettings; if (isTestExecution() && !this.terminal) { - // tslint:disable-next-line:prefer-type-cast - // tslint:disable-next-line:no-object-literal-type-assertion this.terminal = {} as ITerminalSettings; } } @@ -516,47 +373,40 @@ export class PythonSettings implements IPythonSettings { ? this.terminal : { executeInFileDir: true, + focusAfterLaunch: false, launchArgs: [], activateEnvironment: true, - activateEnvInCurrentTerminal: false + activateEnvInCurrentTerminal: false, + shellIntegration: { + enabled: false, + }, }; - const experiments = systemVariables.resolveAny(pythonSettings.get('experiments'))!; + this.REPL = pythonSettings.get('REPL')!; + const experiments = pythonSettings.get('experiments')!; if (this.experiments) { Object.assign(this.experiments, experiments); } else { this.experiments = experiments; } + // Note we directly access experiment settings using workspace service in ExperimentService class. + // Any changes here specific to these settings should propogate their as well. this.experiments = this.experiments ? this.experiments : { enabled: true, optInto: [], - optOutFrom: [] + optOutFrom: [], }; - - const dataScienceSettings = systemVariables.resolveAny( - pythonSettings.get('dataScience') - )!; - if (this.datascience) { - Object.assign(this.datascience, dataScienceSettings); - } else { - this.datascience = dataScienceSettings; - } - - const showStartPage = pythonSettings.get('showStartPage'); - if (showStartPage !== undefined) { - this.showStartPage = showStartPage; - } - - this.insidersChannel = pythonSettings.get('insidersChannel')!; } - protected getPythonExecutable(pythonPath: string) { + // eslint-disable-next-line class-methods-use-this + protected getPythonExecutable(pythonPath: string): string { return getPythonExecutable(pythonPath); } - protected onWorkspaceFoldersChanged() { - //If an activated workspace folder was removed, delete its key + + protected onWorkspaceFoldersChanged(): void { + // If an activated workspace folder was removed, delete its key const workspaceKeys = this.workspace.workspaceFolders!.map((workspaceFolder) => workspaceFolder.uri.fsPath); const activatedWkspcKeys = Array.from(PythonSettings.pythonSettings.keys()); const activatedWkspcFoldersRemoved = activatedWkspcKeys.filter((item) => workspaceKeys.indexOf(item) < 0); @@ -566,31 +416,41 @@ export class PythonSettings implements IPythonSettings { } } } - protected initialize(): void { - const onDidChange = () => { - const currentConfig = this.workspace.getConfiguration('python', this.workspaceRoot); - this.update(currentConfig); - - // If workspace config changes, then we could have a cascading effect of on change events. - // Let's defer the change notification. - this.debounceChangeNotification(); - }; + + public register(): void { + PythonSettings.pythonSettings = new Map(); + this.initialize(); + } + + private onDidChanged(event?: ConfigurationChangeEvent) { + const currentConfig = this.workspace.getConfiguration('python', this.workspaceRoot); + this.update(currentConfig); + + // If workspace config changes, then we could have a cascading effect of on change events. + // Let's defer the change notification. + this.debounceChangeNotification(event); + } + + public initialize(): void { this.disposables.push(this.workspace.onDidChangeWorkspaceFolders(this.onWorkspaceFoldersChanged, this)); this.disposables.push( - this.interpreterAutoSelectionService.onDidChangeAutoSelectedInterpreter(onDidChange.bind(this)) + this.interpreterAutoSelectionService.onDidChangeAutoSelectedInterpreter(() => { + this.onDidChanged(); + }), ); - if (this.interpreterSecurityService) { - this.disposables.push(this.interpreterSecurityService.onDidChangeSafeInterpreters(onDidChange.bind(this))); - } this.disposables.push( this.workspace.onDidChangeConfiguration((event: ConfigurationChangeEvent) => { if (event.affectsConfiguration('python')) { - onDidChange(); + this.onDidChanged(event); } - }) + }), ); if (this.interpreterPathService) { - this.disposables.push(this.interpreterPathService.onDidChange(onDidChange.bind(this))); + this.disposables.push( + this.interpreterPathService.onDidChange(() => { + this.onDidChanged(); + }), + ); } const initialConfig = this.workspace.getConfiguration('python', this.workspaceRoot); @@ -598,59 +458,31 @@ export class PythonSettings implements IPythonSettings { this.update(initialConfig); } } + @debounceSync(1) - protected debounceChangeNotification() { - this.changed.fire(); + protected debounceChangeNotification(event?: ConfigurationChangeEvent): void { + this.changed.fire(event); } - private getPythonPath( - pythonSettings: WorkspaceConfiguration, - systemVariables: SystemVariables, - workspaceRoot: string | undefined - ) { - /** - * Note that while calling `IExperimentsManager.inExperiment()`, we assume `IExperimentsManager.activate()` is already called. - * That's not true here, as this method is often called in the constructor,which runs before `.activate()` methods. - * But we can still use it here for this particular experiment. Reason being that this experiment only changes - * `pythonPath` setting, and I've checked that `pythonPath` setting is not accessed anywhere in the constructor. - */ - const inExperiment = this.experimentsManager?.inExperiment(DeprecatePythonPath.experiment); - this.experimentsManager?.sendTelemetryIfInExperiment(DeprecatePythonPath.control); - // Use the interpreter path service if in the experiment otherwise use the normal settings - this.pythonPath = systemVariables.resolveAny( - inExperiment && this.interpreterPathService - ? this.interpreterPathService.get(this.workspaceRoot) - : pythonSettings.get('pythonPath') - )!; - if (!process.env.CI_DISABLE_AUTO_SELECTION && (this.pythonPath.length === 0 || this.pythonPath === 'python')) { + private getPythonPath(systemVariables: SystemVariables, workspaceRoot: string | undefined) { + this.pythonPath = systemVariables.resolveAny(this.interpreterPathService.get(this.workspaceRoot))!; + if ( + !process.env.CI_DISABLE_AUTO_SELECTION && + (this.pythonPath.length === 0 || this.pythonPath === 'python') && + this.interpreterAutoSelectionService + ) { const autoSelectedPythonInterpreter = this.interpreterAutoSelectionService.getAutoSelectedInterpreter( - this.workspaceRoot + this.workspaceRoot, ); - if (inExperiment && this.interpreterSecurityService) { - if ( - autoSelectedPythonInterpreter && - this.interpreterSecurityService.isSafe(autoSelectedPythonInterpreter) && - this.workspaceRoot - ) { - this.pythonPath = autoSelectedPythonInterpreter.path; - this.interpreterAutoSelectionService - .setWorkspaceInterpreter(this.workspaceRoot, autoSelectedPythonInterpreter) - .ignoreErrors(); - } - } else { - if (autoSelectedPythonInterpreter && this.workspaceRoot) { - this.pythonPath = autoSelectedPythonInterpreter.path; + if (autoSelectedPythonInterpreter) { + this.pythonPath = autoSelectedPythonInterpreter.path; + if (this.workspaceRoot) { this.interpreterAutoSelectionService .setWorkspaceInterpreter(this.workspaceRoot, autoSelectedPythonInterpreter) .ignoreErrors(); } } } - if (inExperiment && this.pythonPath === DEFAULT_INTERPRETER_SETTING) { - // If no interpreter is selected, set pythonPath to an empty string. - // This is to ensure that we ask users to select an interpreter in case auto selected interpreter is not safe to select - this.pythonPath = ''; - } return getAbsolutePath(this.pythonPath, workspaceRoot); } } @@ -659,7 +491,7 @@ function getAbsolutePath(pathToCheck: string, rootDir: string | undefined): stri if (!rootDir) { rootDir = __dirname; } - // tslint:disable-next-line:prefer-type-cast no-unsafe-any + pathToCheck = untildify(pathToCheck) as string; if (isTestExecution() && !pathToCheck) { return rootDir; @@ -671,7 +503,6 @@ function getAbsolutePath(pathToCheck: string, rootDir: string | undefined): stri } function getPythonExecutable(pythonPath: string): string { - // tslint:disable-next-line:prefer-type-cast no-unsafe-any pythonPath = untildify(pythonPath) as string; // If only 'python'. @@ -687,18 +518,29 @@ function getPythonExecutable(pythonPath: string): string { return pythonPath; } // Keep python right on top, for backwards compatibility. - // tslint:disable-next-line:variable-name - const KnownPythonExecutables = ['python', 'python4', 'python3.6', 'python3.5', 'python3', 'python2.7', 'python2']; + + const KnownPythonExecutables = [ + 'python', + 'python4', + 'python3.6', + 'python3.5', + 'python3', + 'python2.7', + 'python2', + 'python3.7', + 'python3.8', + 'python3.9', + ]; for (let executableName of KnownPythonExecutables) { // Suffix with 'python' for linux and 'osx', and 'python.exe' for 'windows'. - if (IS_WINDOWS) { + if (isWindows()) { executableName = `${executableName}.exe`; if (isValidPythonPath(path.join(pythonPath, executableName))) { return path.join(pythonPath, executableName); } - if (isValidPythonPath(path.join(pythonPath, 'scripts', executableName))) { - return path.join(pythonPath, 'scripts', executableName); + if (isValidPythonPath(path.join(pythonPath, 'Scripts', executableName))) { + return path.join(pythonPath, 'Scripts', executableName); } } else { if (isValidPythonPath(path.join(pythonPath, executableName))) { @@ -714,31 +556,8 @@ function getPythonExecutable(pythonPath: string): string { } function isValidPythonPath(pythonPath: string): boolean { - const [args, parse] = internalPython.isValid(); - try { - const output = child_process.execFileSync(pythonPath, args, { encoding: 'utf8' }); - return parse(output); - } catch (ex) { - return false; - } -} - -function convertSettingTypeToLogLevel(setting: LoggingLevelSettingType | undefined): LogLevel | 'off' { - switch (setting) { - case 'info': { - return LogLevel.Info; - } - case 'warn': { - return LogLevel.Warn; - } - case 'off': { - return 'off'; - } - case 'debug': { - return LogLevel.Debug; - } - default: { - return LogLevel.Error; - } - } + return ( + fs.existsSync(pythonPath) && + path.basename(getOSType() === OSType.Windows ? pythonPath.toLowerCase() : pythonPath).startsWith('python') + ); } diff --git a/src/client/common/configuration/executionSettings/pipEnvExecution.ts b/src/client/common/configuration/executionSettings/pipEnvExecution.ts new file mode 100644 index 000000000000..de1d90e6fc84 --- /dev/null +++ b/src/client/common/configuration/executionSettings/pipEnvExecution.ts @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { injectable, inject } from 'inversify'; +import { IConfigurationService, IToolExecutionPath } from '../../types'; + +@injectable() +export class PipEnvExecutionPath implements IToolExecutionPath { + constructor(@inject(IConfigurationService) private readonly configService: IConfigurationService) {} + + public get executable(): string { + return this.configService.getSettings().pipenvPath; + } +} diff --git a/src/client/common/configuration/service.ts b/src/client/common/configuration/service.ts index b922254d8b05..443990b2e5da 100644 --- a/src/client/common/configuration/service.ts +++ b/src/client/common/configuration/service.ts @@ -2,69 +2,69 @@ // Licensed under the MIT License. import { inject, injectable } from 'inversify'; -import { ConfigurationTarget, Uri, WorkspaceConfiguration } from 'vscode'; -import { - IInterpreterAutoSeletionProxyService, - IInterpreterSecurityService -} from '../../interpreter/autoSelection/types'; +import { ConfigurationTarget, Event, Uri, WorkspaceConfiguration, ConfigurationChangeEvent } from 'vscode'; +import { IInterpreterAutoSelectionService } from '../../interpreter/autoSelection/types'; import { IServiceContainer } from '../../ioc/types'; import { IWorkspaceService } from '../application/types'; import { PythonSettings } from '../configSettings'; import { isUnitTestExecution } from '../constants'; -import { DeprecatePythonPath } from '../experiments/groups'; -import { IConfigurationService, IExperimentsManager, IInterpreterPathService, IPythonSettings } from '../types'; +import { + IConfigurationService, + IDefaultLanguageServer, + IExtensions, + IInterpreterPathService, + IPythonSettings, +} from '../types'; @injectable() export class ConfigurationService implements IConfigurationService { private readonly workspaceService: IWorkspaceService; + constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) { this.workspaceService = this.serviceContainer.get(IWorkspaceService); } + + // eslint-disable-next-line class-methods-use-this + public get onDidChange(): Event { + return PythonSettings.onConfigChange(); + } + public getSettings(resource?: Uri): IPythonSettings { - const InterpreterAutoSelectionService = this.serviceContainer.get( - IInterpreterAutoSeletionProxyService + const InterpreterAutoSelectionService = this.serviceContainer.get( + IInterpreterAutoSelectionService, ); const interpreterPathService = this.serviceContainer.get(IInterpreterPathService); - const experiments = this.serviceContainer.get(IExperimentsManager); - const interpreterSecurityService = this.serviceContainer.get( - IInterpreterSecurityService - ); + const defaultLS = this.serviceContainer.tryGet(IDefaultLanguageServer); + const extensions = this.serviceContainer.get(IExtensions); return PythonSettings.getInstance( resource, InterpreterAutoSelectionService, this.workspaceService, - experiments, interpreterPathService, - interpreterSecurityService + defaultLS, + extensions, ); } public async updateSectionSetting( section: string, setting: string, - value?: {}, + value?: unknown, resource?: Uri, - configTarget?: ConfigurationTarget + configTarget?: ConfigurationTarget, ): Promise { - const experiments = this.serviceContainer.get(IExperimentsManager); - const interpreterPathService = this.serviceContainer.get(IInterpreterPathService); - const inExperiment = experiments.inExperiment(DeprecatePythonPath.experiment); - experiments.sendTelemetryIfInExperiment(DeprecatePythonPath.control); const defaultSetting = { uri: resource, - target: configTarget || ConfigurationTarget.WorkspaceFolder + target: configTarget || ConfigurationTarget.WorkspaceFolder, }; let settingsInfo = defaultSetting; if (section === 'python' && configTarget !== ConfigurationTarget.Global) { settingsInfo = PythonSettings.getSettingsUriAndTarget(resource, this.workspaceService); } - configTarget = configTarget ? configTarget : settingsInfo.target; + configTarget = configTarget || settingsInfo.target; const configSection = this.workspaceService.getConfiguration(section, settingsInfo.uri); - const currentValue = - inExperiment && section === 'python' && setting === 'pythonPath' - ? interpreterPathService.inspect(settingsInfo.uri) - : configSection.inspect(setting); + const currentValue = configSection.inspect(setting); if ( currentValue !== undefined && @@ -74,26 +74,20 @@ export class ConfigurationService implements IConfigurationService { ) { return; } - if (section === 'python' && setting === 'pythonPath') { - if (inExperiment) { - // tslint:disable-next-line: no-any - await interpreterPathService.update(settingsInfo.uri, configTarget, value as any); - } - } else { - await configSection.update(setting, value, configTarget); - await this.verifySetting(configSection, configTarget, setting, value); - } + await configSection.update(setting, value, configTarget); + await this.verifySetting(configSection, configTarget, setting, value); } public async updateSetting( setting: string, - value?: {}, + value?: unknown, resource?: Uri, - configTarget?: ConfigurationTarget + configTarget?: ConfigurationTarget, ): Promise { return this.updateSectionSetting('python', setting, value, resource, configTarget); } + // eslint-disable-next-line class-methods-use-this public isTestExecution(): boolean { return process.env.VSC_PYTHON_CI_TEST === '1'; } @@ -102,7 +96,7 @@ export class ConfigurationService implements IConfigurationService { configSection: WorkspaceConfiguration, target: ConfigurationTarget, settingName: string, - value?: {} + value?: unknown, ): Promise { if (this.isTestExecution() && !isUnitTestExecution()) { let retries = 0; @@ -113,12 +107,14 @@ export class ConfigurationService implements IConfigurationService { } if (setting && value !== undefined) { // Both specified - const actual = - target === ConfigurationTarget.Global - ? setting.globalValue - : target === ConfigurationTarget.Workspace - ? setting.workspaceValue - : setting.workspaceFolderValue; + let actual; + if (target === ConfigurationTarget.Global) { + actual = setting.globalValue; + } else if (target === ConfigurationTarget.Workspace) { + actual = setting.workspaceValue; + } else { + actual = setting.workspaceFolderValue; + } if (actual === value) { break; } diff --git a/src/client/common/constants.ts b/src/client/common/constants.ts index 6096285fa170..15fd037a3d9f 100644 --- a/src/client/common/constants.ts +++ b/src/client/common/constants.ts @@ -1,100 +1,107 @@ +/* eslint-disable camelcase */ +/* eslint-disable @typescript-eslint/no-namespace */ export const PYTHON_LANGUAGE = 'python'; -export const MARKDOWN_LANGUAGE = 'markdown'; -export const JUPYTER_LANGUAGE = 'jupyter'; - export const PYTHON_WARNINGS = 'PYTHONWARNINGS'; export const NotebookCellScheme = 'vscode-notebook-cell'; +export const InteractiveInputScheme = 'vscode-interactive-input'; +export const InteractiveScheme = 'vscode-interactive'; export const PYTHON = [ { scheme: 'file', language: PYTHON_LANGUAGE }, { scheme: 'untitled', language: PYTHON_LANGUAGE }, { scheme: 'vscode-notebook', language: PYTHON_LANGUAGE }, - { scheme: NotebookCellScheme, language: PYTHON_LANGUAGE } + { scheme: NotebookCellScheme, language: PYTHON_LANGUAGE }, + { scheme: InteractiveInputScheme, language: PYTHON_LANGUAGE }, +]; + +export const PYTHON_NOTEBOOKS = [ + { scheme: 'vscode-notebook', language: PYTHON_LANGUAGE }, + { scheme: NotebookCellScheme, language: PYTHON_LANGUAGE }, + { scheme: InteractiveInputScheme, language: PYTHON_LANGUAGE }, ]; -export const PYTHON_ALLFILES = [{ language: PYTHON_LANGUAGE }]; export const PVSC_EXTENSION_ID = 'ms-python.python'; -export const CODE_RUNNER_EXTENSION_ID = 'formulahendry.code-runner'; export const PYLANCE_EXTENSION_ID = 'ms-python.vscode-pylance'; -export const AppinsightsKey = 'AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217'; +export const PYREFLY_EXTENSION_ID = 'meta.pyrefly'; +export const JUPYTER_EXTENSION_ID = 'ms-toolsai.jupyter'; +export const TENSORBOARD_EXTENSION_ID = 'ms-toolsai.tensorboard'; +export const AppinsightsKey = '0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255'; + +export type Channel = 'stable' | 'insiders'; + +export enum CommandSource { + ui = 'ui', + commandPalette = 'commandpalette', +} export namespace Commands { - export const Set_Interpreter = 'python.setInterpreter'; - export const Set_ShebangInterpreter = 'python.setShebangInterpreter'; + export const ClearStorage = 'python.clearCacheAndReload'; + export const CreateNewFile = 'python.createNewFile'; + export const ClearWorkspaceInterpreter = 'python.clearWorkspaceInterpreter'; + export const Create_Environment = 'python.createEnvironment'; + export const CopyTestId = 'python.copyTestId'; + export const Create_Environment_Button = 'python.createEnvironment-button'; + export const Create_Environment_Check = 'python.createEnvironmentCheck'; + export const Create_Terminal = 'python.createTerminal'; + export const Debug_In_Terminal = 'python.debugInTerminal'; export const Exec_In_Terminal = 'python.execInTerminal'; export const Exec_In_Terminal_Icon = 'python.execInTerminal-icon'; - export const Exec_Selection_In_Terminal = 'python.execSelectionInTerminal'; + export const Exec_In_Separate_Terminal = 'python.execInDedicatedTerminal'; + export const Exec_In_REPL = 'python.execInREPL'; export const Exec_Selection_In_Django_Shell = 'python.execSelectionInDjangoShell'; - export const Tests_View_UI = 'python.viewTestUI'; - export const Tests_Picker_UI = 'python.selectTestToRun'; - export const Tests_Picker_UI_Debug = 'python.selectTestToDebug'; + export const Exec_In_REPL_Enter = 'python.execInREPLEnter'; + export const Exec_In_IW_Enter = 'python.execInInteractiveWindowEnter'; + export const Exec_Selection_In_Terminal = 'python.execSelectionInTerminal'; + export const GetSelectedInterpreterPath = 'python.interpreterPath'; + export const InstallJupyter = 'python.installJupyter'; + export const InstallPython = 'python.installPython'; + export const InstallPythonOnLinux = 'python.installPythonOnLinux'; + export const InstallPythonOnMac = 'python.installPythonOnMac'; + export const PickLocalProcess = 'python.pickLocalProcess'; + export const ReportIssue = 'python.reportIssue'; + export const Set_Interpreter = 'python.setInterpreter'; + export const Set_ShebangInterpreter = 'python.setShebangInterpreter'; + export const Start_REPL = 'python.startREPL'; + export const Start_Native_REPL = 'python.startNativeREPL'; export const Tests_Configure = 'python.configureTests'; - export const Tests_Discover = 'python.discoverTests'; - export const Tests_Discovering = 'python.discoveringTests'; - export const Tests_Run_Failed = 'python.runFailedTests'; - export const Sort_Imports = 'python.sortImports'; - export const Tests_Run = 'python.runtests'; - export const Tests_Run_Parametrized = 'python.runParametrizedTests'; - export const Tests_Debug = 'python.debugtests'; - export const Tests_Ask_To_Stop_Test = 'python.askToStopTests'; - export const Tests_Ask_To_Stop_Discovery = 'python.askToStopTestDiscovery'; - export const Tests_Stop = 'python.stopTests'; - export const Test_Reveal_Test_Item = 'python.revealTestItem'; + export const Tests_CopilotSetup = 'python.copilotSetupTests'; + export const TriggerEnvironmentSelection = 'python.triggerEnvSelection'; export const ViewOutput = 'python.viewOutput'; - export const Tests_ViewOutput = 'python.viewTestOutput'; - export const Tests_Select_And_Run_Method = 'python.selectAndRunTestMethod'; - export const Tests_Select_And_Debug_Method = 'python.selectAndDebugTestMethod'; - export const Tests_Select_And_Run_File = 'python.selectAndRunTestFile'; - export const Tests_Run_Current_File = 'python.runCurrentTestFile'; - export const Refactor_Extract_Variable = 'python.refactorExtractVariable'; - export const Refactor_Extract_Method = 'python.refactorExtractMethod'; - export const Build_Workspace_Symbols = 'python.buildWorkspaceSymbols'; - export const Start_REPL = 'python.startREPL'; - export const Create_Terminal = 'python.createTerminal'; - export const Set_Linter = 'python.setLinter'; - export const Enable_Linter = 'python.enableLinting'; - export const Run_Linter = 'python.runLinting'; - export const Enable_SourceMap_Support = 'python.enableSourceMapSupport'; - export const navigateToTestFunction = 'navigateToTestFunction'; - export const navigateToTestSuite = 'navigateToTestSuite'; - export const navigateToTestFile = 'navigateToTestFile'; - export const openTestNodeInEditor = 'python.openTestNodeInEditor'; - export const runTestNode = 'python.runTestNode'; - export const debugTestNode = 'python.debugTestNode'; - export const SwitchOffInsidersChannel = 'python.switchOffInsidersChannel'; - export const SwitchToInsidersDaily = 'python.switchToDailyChannel'; - export const SwitchToInsidersWeekly = 'python.switchToWeeklyChannel'; - export const PickLocalProcess = 'python.pickLocalProcess'; - export const GetSelectedInterpreterPath = 'python.interpreterPath'; - export const ClearWorkspaceInterpreter = 'python.clearWorkspaceInterpreter'; - export const ResetInterpreterSecurityStorage = 'python.resetInterpreterSecurityStorage'; - export const OpenStartPage = 'python.startPage.open'; } + +// Look at https://microsoft.github.io/vscode-codicons/dist/codicon.html for other Octicon icon ids export namespace Octicons { + export const Add = '$(add)'; export const Test_Pass = '$(check)'; export const Test_Fail = '$(alert)'; export const Test_Error = '$(x)'; export const Test_Skip = '$(circle-slash)'; export const Downloading = '$(cloud-download)'; export const Installing = '$(desktop-download)'; + export const Search = '$(search)'; + export const Search_Stop = '$(search-stop)'; + export const Star = '$(star-full)'; + export const Gear = '$(gear)'; + export const Warning = '$(warning)'; + export const Error = '$(error)'; + export const Lightbulb = '$(lightbulb)'; + export const Folder = '$(folder)'; } -export const Button_Text_Tests_View_Output = 'View Output'; - -export namespace Text { - export const CodeLensRunUnitTest = 'Run Test'; - export const CodeLensDebugUnitTest = 'Debug Test'; -} -export namespace Delays { - // Max time to wait before aborting the generation of code lenses for unit tests - export const MaxUnitTestCodeLensDelay = 5000; +/** + * Look at https://code.visualstudio.com/api/references/icons-in-labels#icon-listing for ThemeIcon ids. + * Using a theme icon is preferred over a custom icon as it gives product theme authors the possibility + * to change the icons. + */ +export namespace ThemeIcons { + export const Refresh = 'refresh'; + export const SpinningLoader = 'loading~spin'; } export const DEFAULT_INTERPRETER_SETTING = 'python'; -export const STANDARD_OUTPUT_CHANNEL = 'STANDARD_OUTPUT_CHANNEL'; - -export const isCI = process.env.TRAVIS === 'true' || process.env.TF_BUILD !== undefined; +export const isCI = + process.env.TRAVIS === 'true' || process.env.TF_BUILD !== undefined || process.env.GITHUB_ACTIONS === 'true'; export function isTestExecution(): boolean { return process.env.VSC_PYTHON_CI_TEST === '1' || isUnitTestExecution(); @@ -102,17 +109,12 @@ export function isTestExecution(): boolean { /** * Whether we're running unit tests (*.unit.test.ts). - * These tests have a speacial meaning, they run fast. - * @export - * @returns {boolean} + * These tests have a special meaning, they run fast. */ export function isUnitTestExecution(): boolean { return process.env.VSC_PYTHON_UNIT_TEST === '1'; } -// Temporary constant, used to indicate whether we're using custom editor api or not. -export const UseCustomEditorApi = Symbol('USE_CUSTOM_EDITOR'); -export const UseVSCodeNotebookEditorApi = Symbol('USE_NATIVEEDITOR'); export const UseProposedApi = Symbol('USE_VSC_PROPOSED_API'); export * from '../constants'; diff --git a/src/client/common/crypto.ts b/src/client/common/crypto.ts deleted file mode 100644 index 3067ebb4a801..000000000000 --- a/src/client/common/crypto.ts +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -// tslint:disable: no-any - -import { createHash } from 'crypto'; -import { injectable } from 'inversify'; -import { traceError } from './logger'; -import { ICryptoUtils, IHashFormat } from './types'; - -/** - * Implements tools related to cryptography - */ -@injectable() -export class CryptoUtils implements ICryptoUtils { - public createHash( - data: string, - hashFormat: E, - algorithm: 'SHA512' | 'SHA256' | 'FNV' = 'FNV' - ): IHashFormat[E] { - let hash: string; - if (algorithm === 'FNV') { - // tslint:disable-next-line:no-require-imports - const fnv = require('@enonic/fnv-plus'); - hash = fnv.fast1a32hex(data) as string; - } else if (algorithm === 'SHA256') { - hash = createHash('sha256').update(data).digest('hex'); - } else { - hash = createHash('sha512').update(data).digest('hex'); - } - if (hashFormat === 'number') { - const result = parseInt(hash, 16); - if (isNaN(result)) { - traceError(`Number hash for data '${data}' is NaN`); - } - return result as any; - } - return hash as any; - } -} diff --git a/src/client/common/dotnet/compatibilityService.ts b/src/client/common/dotnet/compatibilityService.ts deleted file mode 100644 index 421fd7f5dfa9..000000000000 --- a/src/client/common/dotnet/compatibilityService.ts +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable, named } from 'inversify'; -import { IPlatformService } from '../platform/types'; -import { OSType } from '../utils/platform'; -import { IDotNetCompatibilityService, IOSDotNetCompatibilityService } from './types'; - -/** - * .NET Core 2.1 OS Requirements - * https://github.com/dotnet/core/blob/master/release-notes/2.1/2.1-supported-os.md - * We are using the versions provided in the above .NET 2.1 Core requirements page as minimum required versions. - * Why, cuz getting distros, mapping them to the ones listd on .NET 2.1 Core requirements are entirely accurate. - * Due to the inaccuracy, its easier and safer to just assume futur versions of an OS are also supported. - * We will need to regularly update the requirements over time, when using .NET Core 2.2 or 3, etc. - */ -@injectable() -export class DotNetCompatibilityService implements IDotNetCompatibilityService { - private readonly mappedServices = new Map(); - constructor( - @inject(IOSDotNetCompatibilityService) @named(OSType.Unknown) unknownOsService: IOSDotNetCompatibilityService, - @inject(IOSDotNetCompatibilityService) @named(OSType.OSX) macService: IOSDotNetCompatibilityService, - @inject(IOSDotNetCompatibilityService) @named(OSType.Windows) winService: IOSDotNetCompatibilityService, - @inject(IOSDotNetCompatibilityService) @named(OSType.Linux) linuxService: IOSDotNetCompatibilityService, - @inject(IPlatformService) private readonly platformService: IPlatformService - ) { - this.mappedServices.set(OSType.Unknown, unknownOsService); - this.mappedServices.set(OSType.OSX, macService); - this.mappedServices.set(OSType.Windows, winService); - this.mappedServices.set(OSType.Linux, linuxService); - } - public isSupported() { - return this.mappedServices.get(this.platformService.osType)!.isSupported(); - } -} diff --git a/src/client/common/dotnet/serviceRegistry.ts b/src/client/common/dotnet/serviceRegistry.ts deleted file mode 100644 index 1c1f18bb5845..000000000000 --- a/src/client/common/dotnet/serviceRegistry.ts +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import { IServiceManager } from '../../ioc/types'; -import { OSType } from '../utils/platform'; -import { DotNetCompatibilityService } from './compatibilityService'; -import { LinuxDotNetCompatibilityService } from './services/linuxCompatibilityService'; -import { MacDotNetCompatibilityService } from './services/macCompatibilityService'; -import { UnknownOSDotNetCompatibilityService } from './services/unknownOsCompatibilityService'; -import { WindowsDotNetCompatibilityService } from './services/windowsCompatibilityService'; -import { IDotNetCompatibilityService, IOSDotNetCompatibilityService } from './types'; - -export function registerTypes(serviceManager: IServiceManager) { - serviceManager.addSingleton(IDotNetCompatibilityService, DotNetCompatibilityService); - serviceManager.addSingleton( - IOSDotNetCompatibilityService, - MacDotNetCompatibilityService, - OSType.OSX - ); - serviceManager.addSingleton( - IOSDotNetCompatibilityService, - WindowsDotNetCompatibilityService, - OSType.Windows - ); - serviceManager.addSingleton( - IOSDotNetCompatibilityService, - LinuxDotNetCompatibilityService, - OSType.Linux - ); - serviceManager.addSingleton( - IOSDotNetCompatibilityService, - UnknownOSDotNetCompatibilityService, - OSType.Unknown - ); -} diff --git a/src/client/common/dotnet/services/linuxCompatibilityService.ts b/src/client/common/dotnet/services/linuxCompatibilityService.ts deleted file mode 100644 index dbd67d221371..000000000000 --- a/src/client/common/dotnet/services/linuxCompatibilityService.ts +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { traceDecorators, traceError } from '../../logger'; -import { IPlatformService } from '../../platform/types'; -import { IOSDotNetCompatibilityService } from '../types'; - -@injectable() -export class LinuxDotNetCompatibilityService implements IOSDotNetCompatibilityService { - constructor(@inject(IPlatformService) private readonly platformService: IPlatformService) {} - @traceDecorators.verbose('Checking support of .NET') - public async isSupported() { - if (!this.platformService.is64bit) { - traceError('.NET is not supported on 32 Bit Linux'); - return false; - } - return true; - } -} diff --git a/src/client/common/dotnet/services/macCompatibilityService.ts b/src/client/common/dotnet/services/macCompatibilityService.ts deleted file mode 100644 index 0fbf44b0fa68..000000000000 --- a/src/client/common/dotnet/services/macCompatibilityService.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IPlatformService } from '../../platform/types'; -import { IOSDotNetCompatibilityService } from '../types'; - -// Min version on https://github.com/dotnet/core/blob/master/release-notes/2.1/2.1-supported-os.md is 10.12. -// On this site https://en.wikipedia.org/wiki/MacOS_Sierra, that maps to 16.0.0. -const minVersion = '16.0.0'; - -@injectable() -export class MacDotNetCompatibilityService implements IOSDotNetCompatibilityService { - constructor(@inject(IPlatformService) private readonly platformService: IPlatformService) {} - public async isSupported() { - const version = await this.platformService.getVersion(); - return version.compare(minVersion) >= 0; - } -} diff --git a/src/client/common/dotnet/services/unknownOsCompatibilityService.ts b/src/client/common/dotnet/services/unknownOsCompatibilityService.ts deleted file mode 100644 index 728a29eacf37..000000000000 --- a/src/client/common/dotnet/services/unknownOsCompatibilityService.ts +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { injectable } from 'inversify'; -import { traceDecorators } from '../../logger'; -import { IOSDotNetCompatibilityService } from '../types'; - -@injectable() -export class UnknownOSDotNetCompatibilityService implements IOSDotNetCompatibilityService { - @traceDecorators.info('Unable to determine compatiblity of DOT.NET with an unknown OS') - public async isSupported() { - return false; - } -} diff --git a/src/client/common/dotnet/services/windowsCompatibilityService.ts b/src/client/common/dotnet/services/windowsCompatibilityService.ts deleted file mode 100644 index 6e616909de3c..000000000000 --- a/src/client/common/dotnet/services/windowsCompatibilityService.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { injectable } from 'inversify'; -import { IOSDotNetCompatibilityService } from '../types'; - -@injectable() -export class WindowsDotNetCompatibilityService implements IOSDotNetCompatibilityService { - public async isSupported() { - return true; - } -} diff --git a/src/client/common/dotnet/types.ts b/src/client/common/dotnet/types.ts deleted file mode 100644 index 75db7d6850c3..000000000000 --- a/src/client/common/dotnet/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -export const IDotNetCompatibilityService = Symbol('IDotNetCompatibilityService'); -export interface IDotNetCompatibilityService { - isSupported(): Promise; -} -export const IOSDotNetCompatibilityService = Symbol('IOSDotNetCompatibilityService'); -export interface IOSDotNetCompatibilityService extends IDotNetCompatibilityService {} diff --git a/src/client/common/editor.ts b/src/client/common/editor.ts deleted file mode 100644 index e8a908e2d612..000000000000 --- a/src/client/common/editor.ts +++ /dev/null @@ -1,408 +0,0 @@ -import { Diff, diff_match_patch } from 'diff-match-patch'; -import { injectable } from 'inversify'; -import * as md5 from 'md5'; -import { EOL } from 'os'; -import * as path from 'path'; -import { Position, Range, TextDocument, TextEdit, Uri, WorkspaceEdit } from 'vscode'; -import { IFileSystem } from '../common/platform/types'; -import { WrappedError } from './errors/errorUtils'; -import { traceError } from './logger'; -import { IEditorUtils } from './types'; -import { isNotebookCell } from './utils/misc'; - -// Code borrowed from goFormat.ts (Go Extension for VS Code) -enum EditAction { - Delete, - Insert, - Replace -} - -const NEW_LINE_LENGTH = EOL.length; - -class Patch { - public diffs!: Diff[]; - public start1!: number; - public start2!: number; - public length1!: number; - public length2!: number; -} - -class Edit { - public action: EditAction; - public start: Position; - public end!: Position; - public text: string; - - constructor(action: number, start: Position) { - this.action = action; - this.start = start; - this.text = ''; - } - - public apply(): TextEdit { - switch (this.action) { - case EditAction.Insert: - return TextEdit.insert(this.start, this.text); - case EditAction.Delete: - return TextEdit.delete(new Range(this.start, this.end)); - case EditAction.Replace: - return TextEdit.replace(new Range(this.start, this.end), this.text); - default: - return new TextEdit(new Range(new Position(0, 0), new Position(0, 0)), ''); - } - } -} - -export function getTextEditsFromPatch(before: string, patch: string): TextEdit[] { - if (patch.startsWith('---')) { - // Strip the first two lines - patch = patch.substring(patch.indexOf('@@')); - } - if (patch.length === 0) { - return []; - } - // Remove the text added by unified_diff - // # Work around missing newline (http://bugs.python.org/issue2142). - patch = patch.replace(/\\ No newline at end of file[\r\n]/, ''); - // tslint:disable-next-line:no-require-imports - const dmp = require('diff-match-patch') as typeof import('diff-match-patch'); - const d = new dmp.diff_match_patch(); - const patches = patch_fromText.call(d, patch); - if (!Array.isArray(patches) || patches.length === 0) { - throw new Error('Unable to parse Patch string'); - } - const textEdits: TextEdit[] = []; - - // Add line feeds and build the text edits - patches.forEach((p) => { - p.diffs.forEach((diff) => { - diff[1] += EOL; - }); - getTextEditsInternal(before, p.diffs, p.start1).forEach((edit) => textEdits.push(edit.apply())); - }); - - return textEdits; -} -export function getWorkspaceEditsFromPatch( - filePatches: string[], - workspaceRoot: string | undefined, - fs: IFileSystem -): WorkspaceEdit { - const workspaceEdit = new WorkspaceEdit(); - filePatches.forEach((patch) => { - const indexOfAtAt = patch.indexOf('@@'); - if (indexOfAtAt === -1) { - return; - } - const fileNameLines = patch - .substring(0, indexOfAtAt) - .split(/\r?\n/g) - .map((line) => line.trim()) - .filter((line) => line.length > 0 && line.toLowerCase().endsWith('.py') && line.indexOf(' a') > 0); - - if (patch.startsWith('---')) { - // Strip the first two lines - patch = patch.substring(indexOfAtAt); - } - if (patch.length === 0) { - return; - } - // We can't find the find name - if (fileNameLines.length === 0) { - return; - } - - let fileName = fileNameLines[0].substring(fileNameLines[0].indexOf(' a') + 3).trim(); - fileName = workspaceRoot && !path.isAbsolute(fileName) ? path.resolve(workspaceRoot, fileName) : fileName; - if (!fs.fileExistsSync(fileName)) { - return; - } - - // Remove the text added by unified_diff - // # Work around missing newline (http://bugs.python.org/issue2142). - patch = patch.replace(/\\ No newline at end of file[\r\n]/, ''); - - // tslint:disable-next-line:no-require-imports - const dmp = require('diff-match-patch') as typeof import('diff-match-patch'); - const d = new dmp.diff_match_patch(); - const patches = patch_fromText.call(d, patch); - if (!Array.isArray(patches) || patches.length === 0) { - throw new Error('Unable to parse Patch string'); - } - - const fileSource = fs.readFileSync(fileName); - const fileUri = Uri.file(fileName); - - // Add line feeds and build the text edits - patches.forEach((p) => { - p.diffs.forEach((diff) => { - diff[1] += EOL; - }); - - getTextEditsInternal(fileSource, p.diffs, p.start1).forEach((edit) => { - switch (edit.action) { - case EditAction.Delete: - workspaceEdit.delete(fileUri, new Range(edit.start, edit.end)); - break; - case EditAction.Insert: - workspaceEdit.insert(fileUri, edit.start, edit.text); - break; - case EditAction.Replace: - workspaceEdit.replace(fileUri, new Range(edit.start, edit.end), edit.text); - break; - default: - break; - } - }); - }); - }); - - return workspaceEdit; -} -export function getTextEdits(before: string, after: string): TextEdit[] { - // tslint:disable-next-line:no-require-imports - const dmp = require('diff-match-patch') as typeof import('diff-match-patch'); - const d = new dmp.diff_match_patch(); - const diffs = d.diff_main(before, after); - return getTextEditsInternal(before, diffs).map((edit) => edit.apply()); -} -function getTextEditsInternal(before: string, diffs: [number, string][], startLine: number = 0): Edit[] { - let line = startLine; - let character = 0; - const beforeLines = before.split(/\r?\n/g); - if (line > 0) { - beforeLines.filter((_l, i) => i < line).forEach((l) => (character += l.length + NEW_LINE_LENGTH)); - } - const edits: Edit[] = []; - let edit: Edit | null = null; - let end: Position; - - // tslint:disable-next-line:prefer-for-of - for (let i = 0; i < diffs.length; i += 1) { - let start = new Position(line, character); - // Compute the line/character after the diff is applied. - // tslint:disable-next-line:prefer-for-of - for (let curr = 0; curr < diffs[i][1].length; curr += 1) { - if (diffs[i][1][curr] !== '\n') { - character += 1; - } else { - character = 0; - line += 1; - } - } - - // tslint:disable-next-line:no-require-imports - const dmp = require('diff-match-patch') as typeof import('diff-match-patch'); - // tslint:disable-next-line:switch-default - switch (diffs[i][0]) { - case dmp.DIFF_DELETE: - if ( - beforeLines[line - 1].length === 0 && - beforeLines[start.line - 1] && - beforeLines[start.line - 1].length === 0 - ) { - // We're asked to delete an empty line which only contains `/\r?\n/g`. The last line is also empty. - // Delete the `\n` from the last line instead of deleting `\n` from the current line - // This change ensures that the last line in the file, which won't contain `\n` is deleted - start = new Position(start.line - 1, 0); - end = new Position(line - 1, 0); - } else { - end = new Position(line, character); - } - if (edit === null) { - edit = new Edit(EditAction.Delete, start); - } else if (edit.action !== EditAction.Delete) { - throw new Error('cannot format due to an internal error.'); - } - edit.end = end; - break; - - case dmp.DIFF_INSERT: - if (edit === null) { - edit = new Edit(EditAction.Insert, start); - } else if (edit.action === EditAction.Delete) { - edit.action = EditAction.Replace; - } - // insert and replace edits are all relative to the original state - // of the document, so inserts should reset the current line/character - // position to the start. - line = start.line; - character = start.character; - edit.text += diffs[i][1]; - break; - - case dmp.DIFF_EQUAL: - if (edit !== null) { - edits.push(edit); - edit = null; - } - break; - } - } - - if (edit !== null) { - edits.push(edit); - } - - return edits; -} - -export async function getTempFileWithDocumentContents(document: TextDocument, fs: IFileSystem): Promise { - // Don't create file in temp folder since external utilities - // look into configuration files in the workspace and are not - // to find custom rules if file is saved in a random disk location. - // This means temp file has to be created in the same folder - // as the original one and then removed. - // Use a .tmp file extension (instead of the original extension) - // because the language server is watching the file system for Python - // file add/delete/change and we don't want this temp file to trigger it. - - // tslint:disable-next-line:no-require-imports - let fileName = `${document.uri.fsPath}.${md5(document.uri.fsPath)}.tmp`; - try { - // When dealing with untitled notebooks, there's no original physical file, hence create a temp file. - if (isNotebookCell(document.uri) && !(await fs.fileExists(document.uri.fsPath))) { - fileName = (await fs.createTemporaryFile(`${path.basename(document.uri.fsPath)}.tmp`)).filePath; - } - await fs.writeFile(fileName, document.getText()); - } catch (ex) { - traceError('Failed to create a temporary file', ex); - throw new WrappedError(`Failed to create a temporary file, ${ex.message}`, ex); - } - return fileName; -} - -/** - * Parse a textual representation of patches and return a list of Patch objects. - * @param {string} textline Text representation of patches. - * @return {!Array.} Array of Patch objects. - * @throws {!Error} If invalid input. - */ -function patch_fromText(textline: string): Patch[] { - const patches: Patch[] = []; - if (!textline) { - return patches; - } - // Start Modification by Don Jayamanne 24/06/2016 Support for CRLF - const text = textline.split(/[\r\n]/); - // End Modification - let textPointer = 0; - const patchHeader = /^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$/; - while (textPointer < text.length) { - const m = text[textPointer].match(patchHeader); - if (!m) { - throw new Error(`Invalid patch string: ${text[textPointer]}`); - } - // tslint:disable-next-line:no-any - const patch = new (diff_match_patch).patch_obj(); - patches.push(patch); - patch.start1 = parseInt(m[1], 10); - if (m[2] === '') { - patch.start1 -= 1; - patch.length1 = 1; - } else if (m[2] === '0') { - patch.length1 = 0; - } else { - patch.start1 -= 1; - patch.length1 = parseInt(m[2], 10); - } - - patch.start2 = parseInt(m[3], 10); - if (m[4] === '') { - patch.start2 -= 1; - patch.length2 = 1; - } else if (m[4] === '0') { - patch.length2 = 0; - } else { - patch.start2 -= 1; - patch.length2 = parseInt(m[4], 10); - } - textPointer += 1; - // tslint:disable-next-line:no-require-imports - const dmp = require('diff-match-patch') as typeof import('diff-match-patch'); - - while (textPointer < text.length) { - const sign = text[textPointer].charAt(0); - let line: string; - try { - //var line = decodeURI(text[textPointer].substring(1)); - // For some reason the patch generated by python files don't encode any characters - // And this patch module (code from Google) is expecting the text to be encoded!! - // Temporary solution, disable decoding - // Issue #188 - line = text[textPointer].substring(1); - } catch (ex) { - // Malformed URI sequence. - throw new Error('Illegal escape in patch_fromText'); - } - if (sign === '-') { - // Deletion. - patch.diffs.push([dmp.DIFF_DELETE, line]); - } else if (sign === '+') { - // Insertion. - patch.diffs.push([dmp.DIFF_INSERT, line]); - } else if (sign === ' ') { - // Minor equality. - patch.diffs.push([dmp.DIFF_EQUAL, line]); - } else if (sign === '@') { - // Start of next patch. - break; - } else if (sign === '') { - // Blank line? Whatever. - } else { - throw new Error(`Invalid patch mode '${sign}' in: ${line}`); - } - textPointer += 1; - } - } - return patches; -} - -@injectable() -export class EditorUtils implements IEditorUtils { - public getWorkspaceEditsFromPatch(originalContents: string, patch: string, uri: Uri): WorkspaceEdit { - const workspaceEdit = new WorkspaceEdit(); - if (patch.startsWith('---')) { - // Strip the first two lines - patch = patch.substring(patch.indexOf('@@')); - } - if (patch.length === 0) { - return workspaceEdit; - } - // Remove the text added by unified_diff - // # Work around missing newline (http://bugs.python.org/issue2142). - patch = patch.replace(/\\ No newline at end of file[\r\n]/, ''); - - // tslint:disable-next-line:no-require-imports - const dmp = require('diff-match-patch') as typeof import('diff-match-patch'); - const d = new dmp.diff_match_patch(); - const patches = patch_fromText.call(d, patch); - if (!Array.isArray(patches) || patches.length === 0) { - throw new Error('Unable to parse Patch string'); - } - - // Add line feeds and build the text edits - patches.forEach((p) => { - p.diffs.forEach((diff) => { - diff[1] += EOL; - }); - getTextEditsInternal(originalContents, p.diffs, p.start1).forEach((edit) => { - switch (edit.action) { - case EditAction.Delete: - workspaceEdit.delete(uri, new Range(edit.start, edit.end)); - break; - case EditAction.Insert: - workspaceEdit.insert(uri, edit.start, edit.text); - break; - case EditAction.Replace: - workspaceEdit.replace(uri, new Range(edit.start, edit.end), edit.text); - break; - default: - break; - } - }); - }); - - return workspaceEdit; - } -} diff --git a/src/client/common/errors/errorUtils.ts b/src/client/common/errors/errorUtils.ts index 9364d3a971df..7867d5ccfe30 100644 --- a/src/client/common/errors/errorUtils.ts +++ b/src/client/common/errors/errorUtils.ts @@ -1,9 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { EOL } from 'os'; - -// tslint:disable-next-line:no-stateless-class no-unnecessary-class export class ErrorUtils { public static outputHasModuleNotInstalledError(moduleName: string, content?: string): boolean { return content && @@ -15,13 +12,10 @@ export class ErrorUtils { } /** - * Wraps an error with a custom error message, retaining the call stack information. + * An error class that contains a telemetry safe reason. */ -export class WrappedError extends Error { - constructor(message: string, originalException: Error) { +export class ErrorWithTelemetrySafeReason extends Error { + constructor(message: string, public readonly telemetrySafeReason: string) { super(message); - // Retain call stack that trapped the error and rethrows this error. - // Also retain the call stack of the original error. - this.stack = `${new Error('').stack}${EOL}${EOL}${originalException.stack}`; } } diff --git a/src/client/common/experiments/groups.ts b/src/client/common/experiments/groups.ts index 711e9d63d41f..12f4ef89018b 100644 --- a/src/client/common/experiments/groups.ts +++ b/src/client/common/experiments/groups.ts @@ -1,104 +1,21 @@ -// Experiment to check whether to always display the test explorer. -export enum AlwaysDisplayTestExplorerGroups { - control = 'AlwaysDisplayTestExplorer - control', - experiment = 'AlwaysDisplayTestExplorer - experiment' -} - // Experiment to check whether to show "Extension Survey prompt" or not. export enum ShowExtensionSurveyPrompt { - control = 'ShowExtensionSurveyPrompt - control', - enabled = 'ShowExtensionSurveyPrompt - enabled' -} - -// Experiment to check whether to enable re-load for web apps while debugging. -export enum WebAppReload { - control = 'Reload - control', - experiment = 'Reload - experiment' -} - -// Experiment to use a local ZMQ kernel connection as opposed to starting a Jupyter server locally -export enum LocalZMQKernel { - control = 'LocalZMQKernel - control', - experiment = 'LocalZMQKernel - experiment' + experiment = 'pythonSurveyNotification', } -// Experiment for supporting run by line in data science notebooks -export enum RunByLine { - control = 'RunByLine - control', - experiment = 'RunByLine - experiment' +export enum ShowToolsExtensionPrompt { + experiment = 'pythonPromptNewToolsExt', } -/** - * Experiment to check whether to to use a terminal to generate the environment variables of activated environments. - * - * @export - * @enum {number} - */ -export enum UseTerminalToGetActivatedEnvVars { - control = 'UseTerminalToGetActivatedEnvVars - control', - experiment = 'UseTerminalToGetActivatedEnvVars - experiment' +export enum TerminalEnvVarActivation { + experiment = 'pythonTerminalEnvVarActivation', } -// Dummy experiment added to validate metrics of A/B testing -export enum ValidateABTesting { - control = 'AA_testing - control', - experiment = 'AA_testing - experiment' +export enum DiscoveryUsingWorkers { + experiment = 'pythonDiscoveryUsingWorkers', } -// Collect language server request timings. -export enum CollectLSRequestTiming { - control = 'CollectLSRequestTiming - control', - experiment = 'CollectLSRequestTiming - experiment' -} - -// Collect Node language server request timings. -export enum CollectNodeLSRequestTiming { - control = 'CollectNodeLSRequestTiming - control', - experiment = 'CollectNodeLSRequestTiming - experiment' -} - -// Determine if ipywidgets is enabled or not -export enum EnableIPyWidgets { - control = 'EnableIPyWidgets - control', - experiment = 'EnableIPyWidgets - experiment' -} - -/* - * Experiment to check whether the extension should deprecate `python.pythonPath` setting - */ -export enum DeprecatePythonPath { - control = 'DeprecatePythonPath - control', - experiment = 'DeprecatePythonPath - experiment' -} - -/* - * Experiment to turn on custom editor or VS Code Native Notebook API support. - */ -export enum NotebookEditorSupport { - control = 'CustomEditorSupport - control', - customEditorExperiment = 'CustomEditorSupport - experiment', - nativeNotebookExperiment = 'NativeNotebook - experiment' -} - -// Experiment to offer switch to Pylance language server -export enum TryPylance { - experiment = 'tryPylance' -} - -// Experiment for the content of the tip being displayed on first extension launch: -// interpreter selection tip, feedback survey or nothing. -export enum SurveyAndInterpreterTipNotification { - tipExperiment = 'pythonTipPromptWording', - surveyExperiment = 'pythonMailingListPromptWording' -} - -// Experiment to switch Jedi to use an LSP instead of direct providers -export enum JediLSP { - experiment = 'jediLSP' -} -// Experiment to show a prompt asking users to join python mailing list. -export enum JoinMailingListPromptVariants { - variant1 = 'pythonJoinMailingListVar1', - variant2 = 'pythonJoinMailingListVar2', - variant3 = 'pythonJoinMailingListVar3' +// Experiment to enable the new testing rewrite. +export enum EnableTestAdapterRewrite { + experiment = 'pythonTestAdapter', } diff --git a/src/client/common/experiments/helpers.ts b/src/client/common/experiments/helpers.ts new file mode 100644 index 000000000000..f6ae39d260f5 --- /dev/null +++ b/src/client/common/experiments/helpers.ts @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { env, workspace } from 'vscode'; +import { IExperimentService } from '../types'; +import { TerminalEnvVarActivation } from './groups'; +import { isTestExecution } from '../constants'; +import { traceInfo } from '../../logging'; + +export function inTerminalEnvVarExperiment(experimentService: IExperimentService): boolean { + if (!isTestExecution() && env.remoteName && workspace.workspaceFolders && workspace.workspaceFolders.length > 1) { + // TODO: Remove this if statement once https://github.com/microsoft/vscode/issues/180486 is fixed. + traceInfo('Not enabling terminal env var experiment in multiroot remote workspaces'); + return false; + } + if (!experimentService.inExperimentSync(TerminalEnvVarActivation.experiment)) { + return false; + } + return true; +} diff --git a/src/client/common/experiments/manager.ts b/src/client/common/experiments/manager.ts deleted file mode 100644 index 7278e880e93b..000000000000 --- a/src/client/common/experiments/manager.ts +++ /dev/null @@ -1,371 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// Refer to A/B testing wiki for more details: https://en.wikipedia.org/wiki/A/B_testing - -'use strict'; - -import { inject, injectable, named, optional } from 'inversify'; -import { parse } from 'jsonc-parser'; -import * as path from 'path'; -import { sendTelemetryEvent } from '../../telemetry'; -import { EventName } from '../../telemetry/constants'; -import { IApplicationEnvironment } from '../application/types'; -import { EXTENSION_ROOT_DIR, STANDARD_OUTPUT_CHANNEL } from '../constants'; -import { traceDecorators, traceError } from '../logger'; -import { IFileSystem } from '../platform/types'; -import { - ABExperiments, - IConfigurationService, - ICryptoUtils, - IExperimentsManager, - IHttpClient, - IOutputChannel, - IPersistentState, - IPersistentStateFactory, - IPythonSettings -} from '../types'; -import { sleep } from '../utils/async'; -import { swallowExceptions } from '../utils/decorators'; -import { Experiments } from '../utils/localize'; -import { NotebookEditorSupport } from './groups'; - -const EXPIRY_DURATION_MS = 30 * 60 * 1000; -export const isDownloadedStorageValidKey = 'IS_EXPERIMENTS_STORAGE_VALID_KEY'; -export const experimentStorageKey = 'EXPERIMENT_STORAGE_KEY'; -export const downloadedExperimentStorageKey = 'DOWNLOADED_EXPERIMENTS_STORAGE_KEY'; -/** - * Local experiments config file. We have this to ensure that experiments are used in the first session itself, - * as about 40% of the users never come back for the second session. - */ -const configFile = path.join(EXTENSION_ROOT_DIR, 'experiments.json'); -export const configUri = 'https://raw.githubusercontent.com/microsoft/vscode-python/main/experiments.json'; -export const EXPERIMENTS_EFFORT_TIMEOUT_MS = 2000; -// The old experiments which are working fine using the `SHA512` algorithm -export const oldExperimentSalts = ['ShowExtensionSurveyPrompt', 'ShowPlayIcon', 'AlwaysDisplayTestExplorer', 'LS']; - -/** - * Manages and stores experiments, implements the AB testing functionality - */ -@injectable() -export class ExperimentsManager implements IExperimentsManager { - /** - * Keeps track of the list of experiments user is in - */ - public userExperiments: ABExperiments = []; - /** - * Experiments user requested to opt into manually - */ - public _experimentsOptedInto: string[] = []; - /** - * Experiments user requested to opt out from manually - */ - public _experimentsOptedOutFrom: string[] = []; - /** - * Returns `true` if experiments are enabled, else `false`. - */ - public _enabled: boolean = true; - /** - * Keeps track of the experiments to be used in the current session - */ - private experimentStorage: IPersistentState; - /** - * Keeps track of the downloaded experiments in the current session, to be used in the next startup - * Note experiments downloaded in the current session has to be distinguished - * from the experiments download in the previous session (experimentsStorage contains that), reason being the following - * - * THE REASON TO WHY WE NEED TWO STATE STORES USED TO STORE EXPERIMENTS: - * We do not intend to change experiments mid-session. To implement this, we should make sure that we do not replace - * the experiments used in the current session by the newly downloaded experiments. That's why we have a separate - * storage(downloadedExperimentsStorage) to store experiments downloaded in the current session. - * Function updateExperimentStorage() makes sure these are used in the next session. - */ - private downloadedExperimentsStorage: IPersistentState; - /** - * Keeps track if the storage needs updating or not. - * Note this has to be separate from the actual storage as - * download storages by itself should not have an Expiry (so that it can be used in the next session even when download fails in the current session) - */ - private isDownloadedStorageValid: IPersistentState; - private activatedOnce: boolean = false; - private settings!: IPythonSettings; - constructor( - @inject(IPersistentStateFactory) private readonly persistentStateFactory: IPersistentStateFactory, - @inject(IHttpClient) private readonly httpClient: IHttpClient, - @inject(ICryptoUtils) private readonly crypto: ICryptoUtils, - @inject(IApplicationEnvironment) private readonly appEnvironment: IApplicationEnvironment, - @inject(IOutputChannel) @named(STANDARD_OUTPUT_CHANNEL) private readonly output: IOutputChannel, - @inject(IFileSystem) private readonly fs: IFileSystem, - @inject(IConfigurationService) private readonly configurationService: IConfigurationService, - @optional() private experimentEffortTimeout: number = EXPERIMENTS_EFFORT_TIMEOUT_MS - ) { - this.isDownloadedStorageValid = this.persistentStateFactory.createGlobalPersistentState( - isDownloadedStorageValidKey, - false, - EXPIRY_DURATION_MS - ); - this.experimentStorage = this.persistentStateFactory.createGlobalPersistentState( - experimentStorageKey, - undefined - ); - this.downloadedExperimentsStorage = this.persistentStateFactory.createGlobalPersistentState< - ABExperiments | undefined - >(downloadedExperimentStorageKey, undefined); - } - - @swallowExceptions('Failed to activate experiments') - public async activate(): Promise { - if (this.activatedOnce) { - return; - } - this.activatedOnce = true; - this.settings = this.configurationService.getSettings(undefined); - this._experimentsOptedInto = this.settings.experiments.optInto; - this._experimentsOptedOutFrom = this.settings.experiments.optOutFrom; - this._enabled = this.settings.experiments.enabled; - if (!this._enabled) { - sendTelemetryEvent(EventName.PYTHON_EXPERIMENTS_DISABLED); - return; - } - await this.updateExperimentStorage(); - this.populateUserExperiments(); - for (const exp of this.userExperiments || []) { - // We need to know whether an experiment influences the logs we observe in github issues, so log the experiment group - // tslint:disable-next-line: no-console - this.output.appendLine(Experiments.inGroup().format(exp.name)); - } - this.initializeInBackground().ignoreErrors(); - } - - @traceDecorators.error('Failed to identify if user is in experiment') - public inExperiment(experimentName: string): boolean { - if (!this._enabled) { - return false; - } - this.sendTelemetryIfInExperiment(experimentName); - return this.userExperiments.find((exp) => exp.name === experimentName) ? true : false; - } - - /** - * Populates list of experiments user is in - */ - @traceDecorators.error('Failed to populate user experiments') - public populateUserExperiments(): void { - this.cleanUpExperimentsOptList(); - if (Array.isArray(this.experimentStorage.value)) { - const remainingExpriments: ABExperiments = []; - // First process experiments in order of user preference (if they have opted out or opted in). - for (const experiment of this.experimentStorage.value) { - // User cannot belong to NotebookExperiment if they are not using Insiders. - if ( - experiment.name === NotebookEditorSupport.nativeNotebookExperiment && - this.appEnvironment.channel === 'stable' - ) { - continue; - } - // User cannot belong to CustomEditor Experiment if they are using Insiders. - if ( - experiment.name === NotebookEditorSupport.customEditorExperiment && - this.appEnvironment.channel === 'insiders' - ) { - continue; - } - try { - if ( - this._experimentsOptedOutFrom.includes('All') || - this._experimentsOptedOutFrom.includes(experiment.name) - ) { - sendTelemetryEvent(EventName.PYTHON_EXPERIMENTS_OPT_IN_OUT, undefined, { - expNameOptedOutOf: experiment.name - }); - continue; - } - if ( - this._experimentsOptedInto.includes('All') || - this._experimentsOptedInto.includes(experiment.name) - ) { - sendTelemetryEvent(EventName.PYTHON_EXPERIMENTS_OPT_IN_OUT, undefined, { - expNameOptedInto: experiment.name - }); - this.userExperiments.push(experiment); - } else { - remainingExpriments.push(experiment); - } - } catch (ex) { - traceError(`Failed to populate experiment list for experiment '${experiment.name}'`, ex); - } - } - - // Add users (based on algorithm) to experiments they haven't already opted out of or opted into. - remainingExpriments - .filter((experiment) => this.isUserInRange(experiment.min, experiment.max, experiment.salt)) - .filter((experiment) => !this.userExperiments.some((existing) => existing.salt === experiment.salt)) - .forEach((experiment) => this.userExperiments.push(experiment)); - } - } - - @traceDecorators.error('Failed to send telemetry when user is in experiment') - public sendTelemetryIfInExperiment(experimentName: string): void { - if (this.userExperiments.find((exp) => exp.name === experimentName)) { - sendTelemetryEvent(EventName.PYTHON_EXPERIMENTS, undefined, { expName: experimentName }); - } - } - - /** - * Downloads experiments and updates downloaded storage for the next session given previously downloaded experiments are no longer valid - */ - @traceDecorators.error('Failed to initialize experiments') - public async initializeInBackground(): Promise { - if (this.isDownloadedStorageValid.value) { - return; - } - await this.downloadAndStoreExperiments(); - } - - /** - * Downloads experiments and updates storage - * @param storage The storage to store the experiments in. By default, downloaded storage for the next session is used. - */ - @traceDecorators.error('Failed to download and store experiments') - public async downloadAndStoreExperiments( - storage: IPersistentState = this.downloadedExperimentsStorage - ): Promise { - const downloadedExperiments = await this.httpClient.getJSON(configUri, false); - if (!this.areExperimentsValid(downloadedExperiments)) { - return; - } - await storage.updateValue(downloadedExperiments); - await this.isDownloadedStorageValid.updateValue(true); - } - - /** - * Checks if user falls between the range of the experiment - * @param min The lower limit - * @param max The upper limit - * @param salt The experiment salt value - */ - public isUserInRange(min: number, max: number, salt: string): boolean { - if (typeof this.appEnvironment.machineId !== 'string') { - throw new Error('Machine ID should be a string'); - } - let hash: number; - if (oldExperimentSalts.find((oldSalt) => oldSalt === salt)) { - hash = this.crypto.createHash(`${this.appEnvironment.machineId}+${salt}`, 'number', 'SHA512'); - } else { - hash = this.crypto.createHash(`${this.appEnvironment.machineId}+${salt}`, 'number', 'FNV'); - } - return hash % 100 >= min && hash % 100 < max; - } - - /** - * Do best effort to populate experiment storage. Attempt to update experiment storage by, - * * Using appropriate local data if available - * * Trying to download fresh experiments within 2 seconds to update storage - * Local data could be: - * * Experiments downloaded in the last session - * - The function makes sure these are used in the current session - * * A default experiments file shipped with the extension - * - Note this file is only used when experiment storage is empty, which is usually the case the first time the extension loads. - * - We have this local file to ensure that experiments are used in the first session itself, - * as about 40% of the users never come back for the second session. - */ - @swallowExceptions('Failed to update experiment storage') - public async updateExperimentStorage(): Promise { - if (!process.env.VSC_PYTHON_LOAD_EXPERIMENTS_FROM_FILE) { - // Step 1. Update experiment storage using downloaded experiments in the last session if any - if (Array.isArray(this.downloadedExperimentsStorage.value)) { - await this.experimentStorage.updateValue(this.downloadedExperimentsStorage.value); - return this.downloadedExperimentsStorage.updateValue(undefined); - } - - if (Array.isArray(this.experimentStorage.value)) { - // Experiment storage already contains latest experiments, do not use the following techniques - return; - } - - // Step 2. Do best effort to download the experiments within timeout and use it in the current session only - if ((await this.doBestEffortToPopulateExperiments()) === true) { - return; - } - } - - // Step 3. Update experiment storage using local experiments file if available - if (await this.fs.fileExists(configFile)) { - const content = await this.fs.readFile(configFile); - try { - const experiments = parse(content, [], { allowTrailingComma: true, disallowComments: false }); - if (!this.areExperimentsValid(experiments)) { - throw new Error('Parsed experiments are not valid'); - } - await this.experimentStorage.updateValue(experiments); - } catch (ex) { - traceError('Failed to parse experiments configuration file to update storage', ex); - } - } - } - - /** - * Checks that experiments are not invalid or incomplete - * @param experiments Local or downloaded experiments - * @returns `true` if type of experiments equals `ABExperiments` type, `false` otherwise - */ - public areExperimentsValid(experiments: ABExperiments): boolean { - if (!Array.isArray(experiments)) { - traceError('Experiments are not of array type'); - return false; - } - for (const exp of experiments) { - if (exp.name === undefined || exp.salt === undefined || exp.min === undefined || exp.max === undefined) { - traceError('Experiments are missing fields from ABExperiments type'); - return false; - } - } - return true; - } - - /** - * Do best effort to download the experiments within timeout and use it in the current session only - */ - public async doBestEffortToPopulateExperiments(): Promise { - try { - const success = await Promise.race([ - // Download and store experiments in the storage for the current session - this.downloadAndStoreExperiments(this.experimentStorage).then(() => true), - sleep(this.experimentEffortTimeout).then(() => false) - ]); - sendTelemetryEvent(EventName.PYTHON_EXPERIMENTS_DOWNLOAD_SUCCESS_RATE, undefined, { success }); - return success; - } catch (ex) { - sendTelemetryEvent( - EventName.PYTHON_EXPERIMENTS_DOWNLOAD_SUCCESS_RATE, - undefined, - { success: false, error: 'Downloading experiments failed with error' }, - ex - ); - traceError('Effort to download experiments within timeout failed with error', ex); - return false; - } - } - - public _activated(): boolean { - return this.activatedOnce; - } - - /** - * You can only opt in or out of experiment groups, not control groups. So remove requests for control groups. - */ - private cleanUpExperimentsOptList(): void { - for (let i = 0; i < this._experimentsOptedInto.length; i += 1) { - if (this._experimentsOptedInto[i].endsWith('control')) { - this._experimentsOptedInto[i] = ''; - } - } - for (let i = 0; i < this._experimentsOptedOutFrom.length; i += 1) { - if (this._experimentsOptedOutFrom[i].endsWith('control')) { - this._experimentsOptedOutFrom[i] = ''; - } - } - this._experimentsOptedInto = this._experimentsOptedInto.filter((exp) => exp !== ''); - this._experimentsOptedOutFrom = this._experimentsOptedOutFrom.filter((exp) => exp !== ''); - } -} diff --git a/src/client/common/experiments/service.ts b/src/client/common/experiments/service.ts index b318732dacde..e52773004fb3 100644 --- a/src/client/common/experiments/service.ts +++ b/src/client/common/experiments/service.ts @@ -3,25 +3,19 @@ 'use strict'; -import { inject, injectable, named } from 'inversify'; -import { Memento } from 'vscode'; +import { inject, injectable } from 'inversify'; +import { l10n } from 'vscode'; import { getExperimentationService, IExperimentationService, TargetPopulation } from 'vscode-tas-client'; +import { traceLog } from '../../logging'; import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; -import { IApplicationEnvironment } from '../application/types'; -import { PVSC_EXTENSION_ID, STANDARD_OUTPUT_CHANNEL } from '../constants'; -import { - GLOBAL_MEMENTO, - IConfigurationService, - IExperimentService, - IMemento, - IOutputChannel, - IPythonSettings -} from '../types'; -import { Experiments } from '../utils/localize'; +import { IApplicationEnvironment, IWorkspaceService } from '../application/types'; +import { PVSC_EXTENSION_ID } from '../constants'; +import { IExperimentService, IPersistentStateFactory } from '../types'; import { ExperimentationTelemetry } from './telemetry'; const EXP_MEMENTO_KEY = 'VSCode.ABExp.FeatureData'; +const EXP_CONFIG_ID = 'vscode'; @injectable() export class ExperimentService implements IExperimentService { @@ -29,37 +23,48 @@ export class ExperimentService implements IExperimentService { * Experiments the user requested to opt into manually. */ public _optInto: string[] = []; + /** * Experiments the user requested to opt out from manually. */ public _optOutFrom: string[] = []; + private readonly experiments = this.persistentState.createGlobalPersistentState<{ features: string[] }>( + EXP_MEMENTO_KEY, + { features: [] }, + ); + + private readonly enabled: boolean; + private readonly experimentationService?: IExperimentationService; - private readonly settings: IPythonSettings; constructor( - @inject(IConfigurationService) readonly configurationService: IConfigurationService, + @inject(IWorkspaceService) readonly workspaceService: IWorkspaceService, @inject(IApplicationEnvironment) private readonly appEnvironment: IApplicationEnvironment, - @inject(IMemento) @named(GLOBAL_MEMENTO) private readonly globalState: Memento, - @inject(IOutputChannel) @named(STANDARD_OUTPUT_CHANNEL) private readonly output: IOutputChannel + @inject(IPersistentStateFactory) private readonly persistentState: IPersistentStateFactory, ) { - this.settings = configurationService.getSettings(undefined); - + const settings = this.workspaceService.getConfiguration('python'); // Users can only opt in or out of experiment groups, not control groups. - const optInto = this.settings.experiments.optInto; - const optOutFrom = this.settings.experiments.optOutFrom; + const optInto = settings.get('experiments.optInto') || []; + const optOutFrom = settings.get('experiments.optOutFrom') || []; this._optInto = optInto.filter((exp) => !exp.endsWith('control')); this._optOutFrom = optOutFrom.filter((exp) => !exp.endsWith('control')); - // Don't initialize the experiment service if the extension's experiments setting is disabled. - const enabled = this.settings.experiments.enabled; - if (!enabled) { + // If users opt out of all experiments we treat it as disabling them. + // The `experiments.enabled` setting also needs to be explicitly disabled, default to true otherwise. + if (this._optOutFrom.includes('All') || settings.get('experiments.enabled') === false) { + this.enabled = false; + } else { + this.enabled = true; + } + + if (!this.enabled) { return; } let targetPopulation: TargetPopulation; - - if (this.appEnvironment.extensionChannel === 'insiders') { + // if running in VS Code Insiders, use the Insiders target population + if (this.appEnvironment.channel === 'insiders') { targetPopulation = TargetPopulation.Insiders; } else { targetPopulation = TargetPopulation.Public; @@ -72,54 +77,180 @@ export class ExperimentService implements IExperimentService { this.appEnvironment.packageJson.version!, targetPopulation, telemetryReporter, - this.globalState + this.experiments.storage, ); + } + + public async activate(): Promise { + if (this.experimentationService) { + const initStart = Date.now(); + await this.experimentationService.initializePromise; - this.logExperiments(); + if (this.experiments.value.features.length === 0) { + // Only await on this if we don't have anything in cache. + // This means that we start the session with partial experiment info. + // We accept this as a compromise to avoid delaying startup. + + // In the case where we don't wait on this promise. If the experiment info changes, + // those changes will be applied in the next session. This is controlled internally + // in the tas-client via `overrideInMemoryFeatures` value that is passed to + // `getFeaturesAsync`. At the time of writing this comment the value of + // `overrideInMemoryFeatures` was always passed in as `false`. So, the experiment + // states did not change mid way. + await this.experimentationService.initialFetch; + sendTelemetryEvent(EventName.PYTHON_EXPERIMENTS_INIT_PERFORMANCE, Date.now() - initStart); + } + this.logExperiments(); + } + sendOptInOptOutTelemetry(this._optInto, this._optOutFrom, this.appEnvironment.packageJson); } public async inExperiment(experiment: string): Promise { + return this.inExperimentSync(experiment); + } + + public inExperimentSync(experiment: string): boolean { if (!this.experimentationService) { return false; } - // Currently the service doesn't support opting in and out of experiments, - // so we need to perform these checks and send the corresponding telemetry manually. + // Currently the service doesn't support opting in and out of experiments. + // so we need to perform these checks manually. if (this._optOutFrom.includes('All') || this._optOutFrom.includes(experiment)) { - sendTelemetryEvent(EventName.PYTHON_EXPERIMENTS_OPT_IN_OUT, undefined, { - expNameOptedOutOf: experiment - }); - return false; } if (this._optInto.includes('All') || this._optInto.includes(experiment)) { - sendTelemetryEvent(EventName.PYTHON_EXPERIMENTS_OPT_IN_OUT, undefined, { - expNameOptedInto: experiment - }); - + // Check if the user was already in the experiment server-side. We need to do + // this to ensure the experiment service is ready and internal states are fully + // synced with the experiment server. + this.experimentationService.getTreatmentVariable(EXP_CONFIG_ID, experiment); return true; } - return this.experimentationService.isCachedFlightEnabled(experiment); + // If getTreatmentVariable returns undefined, + // it means that the value for this experiment was not found on the server. + const treatmentVariable = this.experimentationService.getTreatmentVariable(EXP_CONFIG_ID, experiment); + + return treatmentVariable === true; } public async getExperimentValue(experiment: string): Promise { if (!this.experimentationService || this._optOutFrom.includes('All') || this._optOutFrom.includes(experiment)) { - return; + return undefined; } - return this.experimentationService.getTreatmentVariableAsync('vscode', experiment); + return this.experimentationService.getTreatmentVariable(EXP_CONFIG_ID, experiment); } private logExperiments() { - const experiments = this.globalState.get<{ features: string[] }>(EXP_MEMENTO_KEY, { features: [] }); + const telemetrySettings = this.workspaceService.getConfiguration('telemetry'); + let experimentsDisabled = false; + if (telemetrySettings && telemetrySettings.get('enableTelemetry') === false) { + traceLog('Telemetry is disabled'); + experimentsDisabled = true; + } - experiments.features.forEach((exp) => { - // Filter out experiments groups that are not from the Python extension. - if (exp.toLowerCase().startsWith('python')) { - this.output.appendLine(Experiments.inGroup().format(exp)); - } - }); + if (telemetrySettings && telemetrySettings.get('telemetryLevel') === 'off') { + traceLog('Telemetry level is off'); + experimentsDisabled = true; + } + + if (experimentsDisabled) { + traceLog('Experiments are disabled, only manually opted experiments are active.'); + } + + if (this._optOutFrom.includes('All')) { + // We prioritize opt out first + traceLog(l10n.t("Experiment '{0}' is inactive", 'All')); + + // Since we are in the Opt Out all case, this means when checking for experiment we + // short circuit and return. So, printing out additional experiment info might cause + // confusion. So skip printing out any specific experiment details to the log. + return; + } + if (this._optInto.includes('All')) { + // Only if 'All' is not in optOut then check if it is in Opt In. + traceLog(l10n.t("Experiment '{0}' is active", 'All')); + + // Similar to the opt out case. If user is opting into to all experiments we short + // circuit the experiment checks. So, skip printing any additional details to the logs. + return; + } + + // Log experiments that users manually opt out, these are experiments which are added using the exp framework. + this._optOutFrom + .filter((exp) => exp !== 'All' && exp.toLowerCase().startsWith('python')) + .forEach((exp) => { + traceLog(l10n.t("Experiment '{0}' is inactive", exp)); + }); + + // Log experiments that users manually opt into, these are experiments which are added using the exp framework. + this._optInto + .filter((exp) => exp !== 'All' && exp.toLowerCase().startsWith('python')) + .forEach((exp) => { + traceLog(l10n.t("Experiment '{0}' is active", exp)); + }); + + if (!experimentsDisabled) { + // Log experiments that users are added to by the exp framework + this.experiments.value.features.forEach((exp) => { + // Filter out experiment groups that are not from the Python extension. + // Filter out experiment groups that are not already opted out or opted into. + if ( + exp.toLowerCase().startsWith('python') && + !this._optOutFrom.includes(exp) && + !this._optInto.includes(exp) + ) { + traceLog(l10n.t("Experiment '{0}' is active", exp)); + } + }); + } + } +} + +/** + * Read accepted experiment settings values from the extension's package.json. + * This function assumes that the `setting` argument is a string array that has a specific set of accepted values. + * + * Accessing the values is done via these keys: + * -> "contributes" -> "configuration" -> "properties" -> -> "items" -> "enum" + * + * @param setting The setting we want to read the values of. + * @param packageJson The content of `package.json`, as a JSON object. + * + * @returns An array containing all accepted values for the setting, or [] if there were none. + */ +function readEnumValues(setting: string, packageJson: Record): string[] { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const settingProperties = (packageJson.contributes as any).configuration.properties[setting]; + + if (settingProperties) { + return settingProperties.items.enum ?? []; } + + return []; +} + +/** + * Send telemetry on experiments that have been manually opted into or opted-out from. + * The telemetry will only contain values that are present in the list of accepted values for these settings. + * + * @param optedIn The list of experiments opted into. + * @param optedOut The list of experiments opted out from. + * @param packageJson The content of `package.json`, as a JSON object. + */ +function sendOptInOptOutTelemetry(optedIn: string[], optedOut: string[], packageJson: Record): void { + const optedInEnumValues = readEnumValues('python.experiments.optInto', packageJson); + const optedOutEnumValues = readEnumValues('python.experiments.optOutFrom', packageJson); + + const sanitizedOptedIn = optedIn.filter((exp) => optedInEnumValues.includes(exp)); + const sanitizedOptedOut = optedOut.filter((exp) => optedOutEnumValues.includes(exp)); + + JSON.stringify(sanitizedOptedIn.sort()); + + sendTelemetryEvent(EventName.PYTHON_EXPERIMENTS_OPT_IN_OPT_OUT_SETTINGS, undefined, { + optedInto: JSON.stringify(sanitizedOptedIn.sort()), + optedOutFrom: JSON.stringify(sanitizedOptedOut.sort()), + }); } diff --git a/src/client/common/experiments/telemetry.ts b/src/client/common/experiments/telemetry.ts index 8e0ace9f7a42..bcc9a9c02005 100644 --- a/src/client/common/experiments/telemetry.ts +++ b/src/client/common/experiments/telemetry.ts @@ -10,7 +10,7 @@ export class ExperimentationTelemetry implements IExperimentationTelemetry { public setSharedProperty(name: string, value: string): void { // Add the shared property to all telemetry being sent, not just events being sent by the experimentation package. // We are not in control of these props, just cast to `any`, i.e. we cannot strongly type these external props. - // tslint:disable-next-line: no-any + setSharedProperty(name as any, value as any); } @@ -20,7 +20,6 @@ export class ExperimentationTelemetry implements IExperimentationTelemetry { formattedProperties[key] = value; }); - // tslint:disable-next-line: no-any sendTelemetryEvent(eventName as any, undefined, formattedProperties); } } diff --git a/src/client/common/extensions.ts b/src/client/common/extensions.ts index a41675749ab9..957ec99a7ce1 100644 --- a/src/client/common/extensions.ts +++ b/src/client/common/extensions.ts @@ -1,31 +1,18 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -/** - * @typedef {Object} SplitLinesOptions - * @property {boolean} [trim=true] - Whether to trim the lines. - * @property {boolean} [removeEmptyEntries=true] - Whether to remove empty entries. - */ - -// https://stackoverflow.com/questions/39877156/how-to-extend-string-prototype-and-use-it-next-in-typescript -// tslint:disable-next-line:interface-name +// eslint-disable-next-line @typescript-eslint/no-unused-vars declare interface String { - /** - * Split a string using the cr and lf characters and return them as an array. - * By default lines are trimmed and empty lines are removed. - * @param {SplitLinesOptions=} splitOptions - Options used for splitting the string. - */ - splitLines(splitOptions?: { trim: boolean; removeEmptyEntries?: boolean }): string[]; /** * Appropriately formats a string so it can be used as an argument for a command in a shell. * E.g. if an argument contains a space, then it will be enclosed within double quotes. */ - toCommandArgument(): string; + toCommandArgumentForPythonExt(): string; /** * Appropriately formats a a file path so it can be used as an argument for a command in a shell. * E.g. if an argument contains a space, then it will be enclosed within double quotes. */ - fileToCommandArgument(): string; + fileToCommandArgumentForPythonExt(): string; /** * String.format() implementation. * Tokens such as {0}, {1} will be replaced with corresponding positional arguments. @@ -39,46 +26,30 @@ declare interface String { trimQuotes(): string; } -/** - * Split a string using the cr and lf characters and return them as an array. - * By default lines are trimmed and empty lines are removed. - * @param {SplitLinesOptions=} splitOptions - Options used for splitting the string. - */ -String.prototype.splitLines = function ( - this: string, - splitOptions: { trim: boolean; removeEmptyEntries: boolean } = { removeEmptyEntries: true, trim: true } -): string[] { - let lines = this.split(/\r?\n/g); - if (splitOptions && splitOptions.trim) { - lines = lines.map((line) => line.trim()); - } - if (splitOptions && splitOptions.removeEmptyEntries) { - lines = lines.filter((line) => line.length > 0); - } - return lines; -}; - /** * Appropriately formats a string so it can be used as an argument for a command in a shell. * E.g. if an argument contains a space, then it will be enclosed within double quotes. - * @param {String} value. */ -String.prototype.toCommandArgument = function (this: string): string { +String.prototype.toCommandArgumentForPythonExt = function (this: string): string { if (!this) { return this; } - return this.indexOf(' ') >= 0 && !this.startsWith('"') && !this.endsWith('"') ? `"${this}"` : this.toString(); + return (this.indexOf(' ') >= 0 || this.indexOf('&') >= 0 || this.indexOf('(') >= 0 || this.indexOf(')') >= 0) && + !this.startsWith('"') && + !this.endsWith('"') + ? `"${this}"` + : this.toString(); }; /** * Appropriately formats a a file path so it can be used as an argument for a command in a shell. * E.g. if an argument contains a space, then it will be enclosed within double quotes. */ -String.prototype.fileToCommandArgument = function (this: string): string { +String.prototype.fileToCommandArgumentForPythonExt = function (this: string): string { if (!this) { return this; } - return this.toCommandArgument().replace(/\\/g, '/'); + return this.toCommandArgumentForPythonExt().replace(/\\/g, '/'); }; /** @@ -92,20 +63,11 @@ String.prototype.trimQuotes = function (this: string): string { return this.replace(/(^['"])|(['"]$)/g, ''); }; -// tslint:disable-next-line:interface-name -declare interface Promise { - /** - * Catches task error and ignores them. - */ - ignoreErrors(): void; -} - /** * Explicitly tells that promise should be run asynchonously. */ Promise.prototype.ignoreErrors = function (this: Promise) { - // tslint:disable-next-line:no-empty - this.catch(() => {}); + return this.catch(() => {}); }; if (!String.prototype.format) { diff --git a/src/client/common/featureDeprecationManager.ts b/src/client/common/featureDeprecationManager.ts deleted file mode 100644 index 79a4af0cd566..000000000000 --- a/src/client/common/featureDeprecationManager.ts +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -import { Disposable, WorkspaceConfiguration } from 'vscode'; -import { IApplicationShell, ICommandManager, IWorkspaceService } from './application/types'; -import { traceVerbose } from './logger'; -import { launch } from './net/browser'; -import { - DeprecatedFeatureInfo, - DeprecatedSettingAndValue, - IFeatureDeprecationManager, - IPersistentStateFactory -} from './types'; - -const deprecatedFeatures: DeprecatedFeatureInfo[] = [ - { - doNotDisplayPromptStateKey: 'SHOW_DEPRECATED_FEATURE_PROMPT_FORMAT_ON_SAVE', - message: "The setting 'python.formatting.formatOnSave' is deprecated, please use 'editor.formatOnSave'.", - moreInfoUrl: 'https://github.com/Microsoft/vscode-python/issues/309', - setting: { setting: 'formatting.formatOnSave', values: ['true', true] } - }, - { - doNotDisplayPromptStateKey: 'SHOW_DEPRECATED_FEATURE_PROMPT_LINT_ON_TEXT_CHANGE', - message: - "The setting 'python.linting.lintOnTextChange' is deprecated, please enable 'python.linting.lintOnSave' and 'files.autoSave'.", - moreInfoUrl: 'https://github.com/Microsoft/vscode-python/issues/313', - setting: { setting: 'linting.lintOnTextChange', values: ['true', true] } - }, - { - doNotDisplayPromptStateKey: 'SHOW_DEPRECATED_FEATURE_PROMPT_FOR_AUTO_COMPLETE_PRELOAD_MODULES', - message: - "The setting 'python.autoComplete.preloadModules' is deprecated, please consider using Pylance Language Server ('python.languageServer' setting).", - moreInfoUrl: 'https://github.com/Microsoft/vscode-python/issues/1704', - setting: { setting: 'autoComplete.preloadModules' } - } -]; - -@injectable() -export class FeatureDeprecationManager implements IFeatureDeprecationManager { - private disposables: Disposable[] = []; - constructor( - @inject(IPersistentStateFactory) private persistentStateFactory: IPersistentStateFactory, - @inject(ICommandManager) private cmdMgr: ICommandManager, - @inject(IWorkspaceService) private workspace: IWorkspaceService, - @inject(IApplicationShell) private appShell: IApplicationShell - ) {} - - public dispose() { - this.disposables.forEach((disposable) => disposable.dispose()); - } - - public initialize() { - deprecatedFeatures.forEach(this.registerDeprecation.bind(this)); - } - - public registerDeprecation(deprecatedInfo: DeprecatedFeatureInfo): void { - if (Array.isArray(deprecatedInfo.commands)) { - deprecatedInfo.commands.forEach((cmd) => { - this.disposables.push( - this.cmdMgr.registerCommand(cmd, () => this.notifyDeprecation(deprecatedInfo), this) - ); - }); - } - if (deprecatedInfo.setting) { - this.checkAndNotifyDeprecatedSetting(deprecatedInfo); - } - } - - public async notifyDeprecation(deprecatedInfo: DeprecatedFeatureInfo): Promise { - const notificationPromptEnabled = this.persistentStateFactory.createGlobalPersistentState( - deprecatedInfo.doNotDisplayPromptStateKey, - true - ); - if (!notificationPromptEnabled.value) { - return; - } - const moreInfo = 'Learn more'; - const doNotShowAgain = 'Never show again'; - const option = await this.appShell.showInformationMessage(deprecatedInfo.message, moreInfo, doNotShowAgain); - if (!option) { - return; - } - switch (option) { - case moreInfo: { - launch(deprecatedInfo.moreInfoUrl); - break; - } - case doNotShowAgain: { - await notificationPromptEnabled.updateValue(false); - break; - } - default: { - throw new Error('Selected option not supported.'); - } - } - return; - } - - public checkAndNotifyDeprecatedSetting(deprecatedInfo: DeprecatedFeatureInfo) { - let notify = false; - if (Array.isArray(this.workspace.workspaceFolders) && this.workspace.workspaceFolders.length > 0) { - this.workspace.workspaceFolders.forEach((workspaceFolder) => { - if (notify) { - return; - } - notify = this.isDeprecatedSettingAndValueUsed( - this.workspace.getConfiguration('python', workspaceFolder.uri), - deprecatedInfo.setting! - ); - }); - } else { - notify = this.isDeprecatedSettingAndValueUsed( - this.workspace.getConfiguration('python'), - deprecatedInfo.setting! - ); - } - - if (notify) { - this.notifyDeprecation(deprecatedInfo).catch((ex) => - traceVerbose('Python Extension: notifyDeprecation', ex) - ); - } - } - - public isDeprecatedSettingAndValueUsed( - pythonConfig: WorkspaceConfiguration, - deprecatedSetting: DeprecatedSettingAndValue - ) { - if (!pythonConfig.has(deprecatedSetting.setting)) { - return false; - } - const configValue = pythonConfig.get(deprecatedSetting.setting); - if (!Array.isArray(deprecatedSetting.values) || deprecatedSetting.values.length === 0) { - if (Array.isArray(configValue)) { - return configValue.length > 0; - } - return true; - } - if (!Array.isArray(deprecatedSetting.values) || deprecatedSetting.values.length === 0) { - if (configValue === undefined) { - return false; - } - if (Array.isArray(configValue)) { - // tslint:disable-next-line:no-any - return (configValue as any[]).length > 0; - } - // If we have a value in the setting, then return. - return true; - } - return deprecatedSetting.values.indexOf(pythonConfig.get<{}>(deprecatedSetting.setting)!) >= 0; - } -} diff --git a/src/client/common/helpers.ts b/src/client/common/helpers.ts index 2fd69900bd32..52eeb1e087aa 100644 --- a/src/client/common/helpers.ts +++ b/src/client/common/helpers.ts @@ -2,13 +2,13 @@ // Licensed under the MIT License. 'use strict'; +import * as os from 'os'; -import { isTestExecution } from './constants'; import { ModuleNotInstalledError } from './errors/moduleNotInstalledError'; export function isNotInstalledError(error: Error): boolean { const isError = typeof error === 'object' && error !== null; - // tslint:disable-next-line:no-any + const errorObj = error; if (!isError) { return false; @@ -21,19 +21,6 @@ export function isNotInstalledError(error: Error): boolean { return errorObj.code === 'ENOENT' || errorObj.code === 127 || isModuleNoInstalledError; } -export function skipIfTest(isAsyncFunction: boolean) { - // tslint:disable-next-line:no-function-expression no-any - return function (_: Object, __: string, descriptor: TypedPropertyDescriptor) { - const originalMethod = descriptor.value; - // tslint:disable-next-line:no-function-expression no-any - descriptor.value = function (...args: any[]) { - if (isTestExecution()) { - return isAsyncFunction ? Promise.resolve() : undefined; - } - // tslint:disable-next-line:no-invalid-this no-use-before-declare no-unsafe-any - return originalMethod.apply(this, args); - }; - - return descriptor; - }; +export function untildify(path: string): string { + return path.replace(/^~($|\/|\\)/, `${os.homedir()}$1`); } diff --git a/src/client/common/insidersBuild/downloadChannelRules.ts b/src/client/common/insidersBuild/downloadChannelRules.ts deleted file mode 100644 index 90379a37f99b..000000000000 --- a/src/client/common/insidersBuild/downloadChannelRules.ts +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { traceDecorators } from '../logger'; -import { IPersistentStateFactory } from '../types'; -import { IExtensionChannelRule } from './types'; - -export const frequencyForDailyInsidersCheck = 1000 * 60 * 60 * 24; // One day. -export const frequencyForWeeklyInsidersCheck = 1000 * 60 * 60 * 24 * 7; // One week. -export const lastLookUpTimeKey = 'INSIDERS_LAST_LOOK_UP_TIME_KEY'; - -/** - * Determines if we should install insiders when install channel is set of "off". - * "off" setting is defined as a no op, which means we should not be looking for insiders. - * - * @export - * @class ExtensionInsidersOffChannelRule - * @implements {IExtensionChannelRule} - */ -@injectable() -export class ExtensionInsidersOffChannelRule implements IExtensionChannelRule { - public async shouldLookForInsidersBuild(): Promise { - return false; - } -} -@injectable() -export class ExtensionInsidersDailyChannelRule implements IExtensionChannelRule { - constructor(@inject(IPersistentStateFactory) private readonly persistentStateFactory: IPersistentStateFactory) {} - @traceDecorators.error('Error in checking if insiders build is to be for daily channel rule') - public async shouldLookForInsidersBuild(isChannelRuleNew: boolean): Promise { - const lastLookUpTime = this.persistentStateFactory.createGlobalPersistentState(lastLookUpTimeKey, -1); - if (isChannelRuleNew) { - // Channel rule has changed to insiders, look for insiders build - await lastLookUpTime.updateValue(Date.now()); - return true; - } - // If we have not looked for it in the last 24 hours, then look. - if (lastLookUpTime.value === -1 || lastLookUpTime.value + frequencyForDailyInsidersCheck < Date.now()) { - await lastLookUpTime.updateValue(Date.now()); - return true; - } - return false; - } -} -@injectable() -export class ExtensionInsidersWeeklyChannelRule implements IExtensionChannelRule { - constructor(@inject(IPersistentStateFactory) private readonly persistentStateFactory: IPersistentStateFactory) {} - @traceDecorators.error('Error in checking if insiders build is to be for daily channel rule') - public async shouldLookForInsidersBuild(isChannelRuleNew: boolean): Promise { - const lastLookUpTime = this.persistentStateFactory.createGlobalPersistentState(lastLookUpTimeKey, -1); - if (isChannelRuleNew) { - // Channel rule has changed to insiders, look for insiders build - await lastLookUpTime.updateValue(Date.now()); - return true; - } - // If we have not looked for it in the last week, then look. - if (lastLookUpTime.value === -1 || lastLookUpTime.value + frequencyForWeeklyInsidersCheck < Date.now()) { - await lastLookUpTime.updateValue(Date.now()); - return true; - } - return false; - } -} diff --git a/src/client/common/insidersBuild/downloadChannelService.ts b/src/client/common/insidersBuild/downloadChannelService.ts deleted file mode 100644 index c0096c080045..000000000000 --- a/src/client/common/insidersBuild/downloadChannelService.ts +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { ConfigurationChangeEvent, ConfigurationTarget, Event, EventEmitter } from 'vscode'; -import { IWorkspaceService } from '../application/types'; -import { traceDecorators } from '../logger'; -import { IConfigurationService, IDisposable, IDisposableRegistry, IPythonSettings } from '../types'; -import { ExtensionChannels, IExtensionChannelService } from './types'; - -export const insidersChannelSetting: keyof IPythonSettings = 'insidersChannel'; - -@injectable() -export class ExtensionChannelService implements IExtensionChannelService { - public _onDidChannelChange: EventEmitter = new EventEmitter(); - constructor( - @inject(IConfigurationService) private readonly configService: IConfigurationService, - @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, - @inject(IDisposableRegistry) disposables: IDisposable[] - ) { - disposables.push(this.workspaceService.onDidChangeConfiguration(this.onDidChangeConfiguration.bind(this))); - } - public getChannel(): ExtensionChannels { - const settings = this.configService.getSettings(); - return settings.insidersChannel; - } - - public get isChannelUsingDefaultConfiguration(): boolean { - const settings = this.workspaceService - .getConfiguration('python') - .inspect(insidersChannelSetting); - if (!settings) { - throw new Error( - `WorkspaceConfiguration.inspect returns 'undefined' for setting 'python.${insidersChannelSetting}'` - ); - } - return !settings.globalValue; - } - - @traceDecorators.error('Updating channel failed') - public async updateChannel(value: ExtensionChannels): Promise { - await this.configService.updateSetting(insidersChannelSetting, value, undefined, ConfigurationTarget.Global); - } - - public get onDidChannelChange(): Event { - return this._onDidChannelChange.event; - } - - public async onDidChangeConfiguration(event: ConfigurationChangeEvent) { - if (event.affectsConfiguration(`python.${insidersChannelSetting}`)) { - const settings = this.configService.getSettings(); - this._onDidChannelChange.fire(settings.insidersChannel); - } - } -} diff --git a/src/client/common/insidersBuild/insidersExtensionPrompt.ts b/src/client/common/insidersBuild/insidersExtensionPrompt.ts deleted file mode 100644 index c5fa8200d45b..000000000000 --- a/src/client/common/insidersBuild/insidersExtensionPrompt.ts +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { sendTelemetryEvent } from '../../telemetry'; -import { EventName } from '../../telemetry/constants'; -import { IApplicationShell, ICommandManager } from '../application/types'; -import { traceDecorators } from '../logger'; -import { IPersistentState, IPersistentStateFactory } from '../types'; -import { Common, DataScienceSurveyBanner, ExtensionChannels } from '../utils/localize'; -import { noop } from '../utils/misc'; -import { IExtensionChannelService, IInsiderExtensionPrompt } from './types'; - -export const insidersPromptStateKey = 'INSIDERS_PROMPT_STATE_KEY'; - -@injectable() -export class InsidersExtensionPrompt implements IInsiderExtensionPrompt { - public readonly hasUserBeenNotified: IPersistentState; - constructor( - @inject(IApplicationShell) private readonly appShell: IApplicationShell, - @inject(IExtensionChannelService) private readonly insidersDownloadChannelService: IExtensionChannelService, - @inject(ICommandManager) private readonly cmdManager: ICommandManager, - @inject(IPersistentStateFactory) private readonly persistentStateFactory: IPersistentStateFactory - ) { - this.hasUserBeenNotified = this.persistentStateFactory.createGlobalPersistentState( - insidersPromptStateKey, - false - ); - } - - @traceDecorators.error('Error in prompting to install insiders') - public async promptToInstallInsiders(): Promise { - const prompts = [ - ExtensionChannels.yesWeekly(), - ExtensionChannels.yesDaily(), - DataScienceSurveyBanner.bannerLabelNo() - ]; - const telemetrySelections: ['Yes, weekly', 'Yes, daily', 'No, thanks'] = [ - 'Yes, weekly', - 'Yes, daily', - 'No, thanks' - ]; - const selection = await this.appShell.showInformationMessage(ExtensionChannels.promptMessage(), ...prompts); - - await this.hasUserBeenNotified.updateValue(true); - sendTelemetryEvent(EventName.INSIDERS_PROMPT, undefined, { - selection: selection ? telemetrySelections[prompts.indexOf(selection)] : undefined - }); - - if (!selection) { - return; - } - if (selection === ExtensionChannels.yesWeekly()) { - await this.insidersDownloadChannelService.updateChannel('weekly'); - } else if (selection === ExtensionChannels.yesDaily()) { - await this.insidersDownloadChannelService.updateChannel('daily'); - } - } - - @traceDecorators.error('Error in prompting to reload') - public async promptToReload(): Promise { - const selection = await this.appShell.showInformationMessage( - ExtensionChannels.reloadToUseInsidersMessage(), - Common.reload() - ); - sendTelemetryEvent(EventName.INSIDERS_RELOAD_PROMPT, undefined, { - selection: selection ? 'Reload' : undefined - }); - if (selection === Common.reload()) { - this.cmdManager.executeCommand('workbench.action.reloadWindow').then(noop); - } - } -} diff --git a/src/client/common/insidersBuild/insidersExtensionService.ts b/src/client/common/insidersBuild/insidersExtensionService.ts deleted file mode 100644 index 63f0e8f8eda3..000000000000 --- a/src/client/common/insidersBuild/insidersExtensionService.ts +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import '../extensions'; - -import { inject, injectable, named } from 'inversify'; -import { IExtensionSingleActivationService } from '../../../client/activation/types'; -import { IServiceContainer } from '../../ioc/types'; -import { IApplicationEnvironment, ICommandManager } from '../application/types'; -import { Commands } from '../constants'; -import { IExtensionBuildInstaller, INSIDERS_INSTALLER } from '../installer/types'; -import { traceDecorators } from '../logger'; -import { IDisposable, IDisposableRegistry } from '../types'; -import { ExtensionChannels, IExtensionChannelRule, IExtensionChannelService, IInsiderExtensionPrompt } from './types'; - -@injectable() -export class InsidersExtensionService implements IExtensionSingleActivationService { - constructor( - @inject(IExtensionChannelService) private readonly extensionChannelService: IExtensionChannelService, - @inject(IInsiderExtensionPrompt) private readonly insidersPrompt: IInsiderExtensionPrompt, - @inject(IApplicationEnvironment) private readonly appEnvironment: IApplicationEnvironment, - @inject(ICommandManager) private readonly cmdManager: ICommandManager, - @inject(IServiceContainer) private readonly serviceContainer: IServiceContainer, - @inject(IExtensionBuildInstaller) - @named(INSIDERS_INSTALLER) - private readonly insidersInstaller: IExtensionBuildInstaller, - @inject(IDisposableRegistry) public readonly disposables: IDisposable[] - ) {} - - public async activate() { - this.registerCommandsAndHandlers(); - await this.initChannel(); - } - - public registerCommandsAndHandlers(): void { - this.disposables.push( - this.extensionChannelService.onDidChannelChange((channel) => { - return this.handleChannel(channel, true); - }) - ); - this.disposables.push( - this.cmdManager.registerCommand(Commands.SwitchOffInsidersChannel, () => - this.extensionChannelService.updateChannel('off') - ) - ); - this.disposables.push( - this.cmdManager.registerCommand(Commands.SwitchToInsidersDaily, () => - this.extensionChannelService.updateChannel('daily') - ) - ); - this.disposables.push( - this.cmdManager.registerCommand(Commands.SwitchToInsidersWeekly, () => - this.extensionChannelService.updateChannel('weekly') - ) - ); - } - - public async initChannel() { - const channel = this.extensionChannelService.getChannel(); - const isDefault = this.extensionChannelService.isChannelUsingDefaultConfiguration; - - const alreadyHandled = await this.handleEdgeCases(isDefault); - if (!alreadyHandled) { - this.handleChannel(channel).ignoreErrors(); - } - } - - // Everything past here is the "channel handler" implementation. - - @traceDecorators.error('Handling channel failed') - public async handleChannel(installChannel: ExtensionChannels, didChannelChange: boolean = false): Promise { - const channelRule = this.serviceContainer.get(IExtensionChannelRule, installChannel); - const shouldInstall = await channelRule.shouldLookForInsidersBuild(didChannelChange); - if (!shouldInstall) { - return; - } - await this.insidersInstaller.install(); - await this.insidersPrompt.promptToReload(); - } - - /** - * 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(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 { - return false; - } - } - - /** - * Only when using VSC insiders and if they have not been notified before (usually the first session), notify to enroll into the insiders program - * @returns `true` if prompt is shown, `false` otherwise - */ - private async promptToInstallInsidersIfApplicable(isDefault: boolean): Promise { - if (this.appEnvironment.channel !== 'insiders') { - return false; - } - if (this.insidersPrompt.hasUserBeenNotified.value) { - return false; - } - if (!isDefault) { - return false; - } - - await this.insidersPrompt.promptToInstallInsiders(); - return true; - } -} diff --git a/src/client/common/insidersBuild/types.ts b/src/client/common/insidersBuild/types.ts deleted file mode 100644 index 5c47c63ce1ff..000000000000 --- a/src/client/common/insidersBuild/types.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Event } from 'vscode'; -import { IPersistentState } from '../types'; - -export const IExtensionChannelRule = Symbol('IExtensionChannelRule'); -export interface IExtensionChannelRule { - /** - * Return `true` if insiders build is required to be installed for the channel - * @param isChannelRuleNew Carries boolean `true` if insiders channel just changed to this channel rule - */ - shouldLookForInsidersBuild(isChannelRuleNew?: boolean): Promise; -} - -export const IExtensionChannelService = Symbol('IExtensionChannelService'); -export interface IExtensionChannelService { - readonly onDidChannelChange: Event; - readonly isChannelUsingDefaultConfiguration: boolean; - getChannel(): ExtensionChannels; - updateChannel(value: ExtensionChannels): Promise; -} - -export const IInsiderExtensionPrompt = Symbol('IInsiderExtensionPrompt'); -export interface IInsiderExtensionPrompt { - /** - * Carries boolean `false` for the first session when user has not been notified. - * Gets updated to `true` once user has been prompted to install insiders. - */ - readonly hasUserBeenNotified: IPersistentState; - promptToInstallInsiders(): Promise; - promptToReload(): Promise; -} - -/** - * Note the values in this enum must belong to `ExtensionChannels` type - */ -export enum ExtensionChannel { - /** - * "off" setting is defined as a no op, which means user keeps using the extension they are using - */ - off = 'off', - weekly = 'weekly', - daily = 'daily' -} -export type ExtensionChannels = 'off' | 'weekly' | 'daily'; diff --git a/src/client/common/installer/channelManager.ts b/src/client/common/installer/channelManager.ts index 14bda4002749..d2950859ab80 100644 --- a/src/client/common/installer/channelManager.ts +++ b/src/client/common/installer/channelManager.ts @@ -20,7 +20,7 @@ export class InstallationChannelManager implements IInstallationChannelManager { public async getInstallationChannel( product: Product, - resource?: InterpreterUri + resource?: InterpreterUri, ): Promise { const channels = await this.getInstallationChannels(resource); if (channels.length === 1) { @@ -39,13 +39,13 @@ export class InstallationChannelManager implements IInstallationChannelManager { return { label: `Install using ${installer.displayName}`, description: '', - installer + installer, }; }); const selection = await appShell.showQuickPick(options, { matchOnDescription: true, matchOnDetail: true, - placeHolder + placeHolder, }); return selection ? selection.installer : undefined; } @@ -85,9 +85,9 @@ export class InstallationChannelManager implements IInstallationChannelManager { const search = 'Search for help'; let result: string | undefined; if (interpreter.envType === EnvironmentType.Conda) { - result = await appShell.showErrorMessage(Installer.noCondaOrPipInstaller(), Installer.searchForHelp()); + result = await appShell.showErrorMessage(Installer.noCondaOrPipInstaller, Installer.searchForHelp); } else { - result = await appShell.showErrorMessage(Installer.noPipInstaller(), Installer.searchForHelp()); + result = await appShell.showErrorMessage(Installer.noPipInstaller, Installer.searchForHelp); } if (result === search) { const platform = this.serviceContainer.get(IPlatformService); @@ -95,7 +95,7 @@ export class InstallationChannelManager implements IInstallationChannelManager { appShell.openUrl( `https://www.bing.com/search?q=Install Pip ${osName} ${ interpreter.envType === EnvironmentType.Conda ? 'Conda' : '' - }` + }`, ); } } diff --git a/src/client/common/installer/condaInstaller.ts b/src/client/common/installer/condaInstaller.ts index 4eb573eed4a3..fbb3dcf183ef 100644 --- a/src/client/common/installer/condaInstaller.ts +++ b/src/client/common/installer/condaInstaller.ts @@ -1,13 +1,16 @@ +/* eslint-disable class-methods-use-this */ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. import { inject, injectable } from 'inversify'; -import { ICondaService } from '../../interpreter/contracts'; +import { ICondaService, IComponentAdapter } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; -import { ExecutionInfo, IConfigurationService } from '../types'; +import { getEnvPath } from '../../pythonEnvironments/base/info/env'; +import { ModuleInstallerType } from '../../pythonEnvironments/info'; +import { ExecutionInfo, IConfigurationService, Product } from '../types'; import { isResource } from '../utils/misc'; -import { ModuleInstaller } from './moduleInstaller'; -import { InterpreterUri } from './types'; +import { ModuleInstaller, translateProductToModule } from './moduleInstaller'; +import { InterpreterUri, ModuleInstallFlags } from './types'; /** * A Python module installer for a conda environment. @@ -16,6 +19,9 @@ import { InterpreterUri } from './types'; export class CondaInstaller extends ModuleInstaller { public _isCondaAvailable: boolean | undefined; + // Unfortunately inversify requires the number of args in constructor to be explictly + // specified as more than its base class. So we need the constructor. + // eslint-disable-next-line @typescript-eslint/no-useless-constructor constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super(serviceContainer); } @@ -24,12 +30,16 @@ export class CondaInstaller extends ModuleInstaller { return 'Conda'; } - public get displayName() { + public get displayName(): string { return 'Conda'; } + public get type(): ModuleInstallerType { + return ModuleInstallerType.Conda; + } + public get priority(): number { - return 0; + return 10; } /** @@ -56,30 +66,52 @@ export class CondaInstaller extends ModuleInstaller { /** * Return the commandline args needed to install the module. */ - protected async getExecutionInfo(moduleName: string, resource?: InterpreterUri): Promise { + protected async getExecutionInfo( + moduleName: string, + resource?: InterpreterUri, + flags: ModuleInstallFlags = 0, + ): Promise { const condaService = this.serviceContainer.get(ICondaService); - const condaFile = await condaService.getCondaFile(); + // Installation using `conda.exe` sometimes fails with a HTTP error on Windows: + // https://github.com/conda/conda/issues/11399 + // Execute in a shell which uses a `conda.bat` file instead, using which installation works. + const useShell = true; + const condaFile = await condaService.getCondaFile(useShell); const pythonPath = isResource(resource) ? this.serviceContainer.get(IConfigurationService).getSettings(resource).pythonPath - : resource.path; - const info = await condaService.getCondaEnvironment(pythonPath); - const args = ['install']; + : getEnvPath(resource.path, resource.envPath).path ?? ''; + const condaLocatorService = this.serviceContainer.get(IComponentAdapter); + const info = await condaLocatorService.getCondaEnvironment(pythonPath); + const args = [flags & ModuleInstallFlags.upgrade ? 'update' : 'install']; + // Found that using conda-forge is best at packages like tensorboard & ipykernel which seem to get updated first on conda-forge + // https://github.com/microsoft/vscode-jupyter/issues/7787 & https://github.com/microsoft/vscode-python/issues/17628 + // Do this just for the datascience packages. + if ([Product.tensorboard].map(translateProductToModule).includes(moduleName)) { + args.push('-c', 'conda-forge'); + } if (info && info.name) { // If we have the name of the conda environment, then use that. args.push('--name'); - args.push(info.name!.toCommandArgument()); + args.push(info.name.toCommandArgumentForPythonExt()); } else if (info && info.path) { // Else provide the full path to the environment path. args.push('--prefix'); - args.push(info.path.fileToCommandArgument()); + args.push(info.path.fileToCommandArgumentForPythonExt()); + } + if (flags & ModuleInstallFlags.updateDependencies) { + args.push('--update-deps'); + } + if (flags & ModuleInstallFlags.reInstall) { + args.push('--force-reinstall'); } args.push(moduleName); args.push('-y'); return { args, - execPath: condaFile + execPath: condaFile, + useShell, }; } @@ -87,10 +119,10 @@ export class CondaInstaller extends ModuleInstaller { * Is the provided interprter a conda environment */ private async isCurrentEnvironmentACondaEnvironment(resource?: InterpreterUri): Promise { - const condaService = this.serviceContainer.get(ICondaService); + const condaService = this.serviceContainer.get(IComponentAdapter); const pythonPath = isResource(resource) ? this.serviceContainer.get(IConfigurationService).getSettings(resource).pythonPath - : resource.path; + : getEnvPath(resource.path, resource.envPath).path ?? ''; return condaService.isCondaEnvironment(pythonPath); } } diff --git a/src/client/common/installer/extensionBuildInstaller.ts b/src/client/common/installer/extensionBuildInstaller.ts deleted file mode 100644 index 1e6aee90ced0..000000000000 --- a/src/client/common/installer/extensionBuildInstaller.ts +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable, named } from 'inversify'; -import { Uri } from 'vscode'; -import { IApplicationShell, ICommandManager } from '../application/types'; -import { Octicons, PVSC_EXTENSION_ID, STANDARD_OUTPUT_CHANNEL } from '../constants'; -import { traceDecorators } from '../logger'; -import { IFileSystem } from '../platform/types'; -import { IFileDownloader, IOutputChannel } from '../types'; -import { ExtensionChannels } from '../utils/localize'; -import { IExtensionBuildInstaller } from './types'; - -export const developmentBuildUri = 'https://pvsc.blob.core.windows.net/extension-builds/ms-python-insiders.vsix'; -export const vsixFileExtension = '.vsix'; - -@injectable() -export class StableBuildInstaller implements IExtensionBuildInstaller { - constructor( - @inject(IOutputChannel) @named(STANDARD_OUTPUT_CHANNEL) private readonly output: IOutputChannel, - @inject(ICommandManager) private readonly cmdManager: ICommandManager, - @inject(IApplicationShell) private readonly appShell: IApplicationShell - ) {} - - @traceDecorators.error('Installing stable build of extension failed') - public async install(): Promise { - this.output.append(ExtensionChannels.installingStableMessage()); - await this.appShell.withProgressCustomIcon(Octicons.Installing, async (progress) => { - progress.report({ message: ExtensionChannels.installingStableMessage() }); - return this.cmdManager.executeCommand('workbench.extensions.installExtension', PVSC_EXTENSION_ID); - }); - this.output.appendLine(ExtensionChannels.installationCompleteMessage()); - } -} - -@injectable() -export class InsidersBuildInstaller implements IExtensionBuildInstaller { - constructor( - @inject(IOutputChannel) @named(STANDARD_OUTPUT_CHANNEL) private readonly output: IOutputChannel, - @inject(IFileDownloader) private readonly fileDownloader: IFileDownloader, - @inject(IFileSystem) private readonly fs: IFileSystem, - @inject(ICommandManager) private readonly cmdManager: ICommandManager, - @inject(IApplicationShell) private readonly appShell: IApplicationShell - ) {} - - @traceDecorators.error('Installing insiders build of extension failed') - public async install(): Promise { - const vsixFilePath = await this.downloadInsiders(); - this.output.append(ExtensionChannels.installingInsidersMessage()); - await this.appShell.withProgressCustomIcon(Octicons.Installing, async (progress) => { - progress.report({ message: ExtensionChannels.installingInsidersMessage() }); - return this.cmdManager.executeCommand('workbench.extensions.installExtension', Uri.file(vsixFilePath)); - }); - this.output.appendLine(ExtensionChannels.installationCompleteMessage()); - await this.fs.deleteFile(vsixFilePath); - } - - @traceDecorators.error('Downloading insiders build of extension failed') - public async downloadInsiders(): Promise { - this.output.appendLine(ExtensionChannels.startingDownloadOutputMessage()); - const downloadOptions = { - extension: vsixFileExtension, - outputChannel: this.output, - progressMessagePrefix: ExtensionChannels.downloadingInsidersMessage() - }; - return this.fileDownloader.downloadFile(developmentBuildUri, downloadOptions).then((file) => { - this.output.appendLine(ExtensionChannels.downloadCompletedOutputMessage()); - return file; - }); - } -} diff --git a/src/client/common/installer/moduleInstaller.ts b/src/client/common/installer/moduleInstaller.ts index c227c9e56906..9dacb623c606 100644 --- a/src/client/common/installer/moduleInstaller.ts +++ b/src/client/common/installer/moduleInstaller.ts @@ -3,38 +3,53 @@ import { injectable } from 'inversify'; import * as path from 'path'; -import { CancellationToken, OutputChannel, ProgressLocation, ProgressOptions } from 'vscode'; +import { CancellationToken, l10n, ProgressLocation, ProgressOptions } from 'vscode'; import { IInterpreterService } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; -import { EnvironmentType } from '../../pythonEnvironments/info'; +import { traceError, traceLog } from '../../logging'; +import { EnvironmentType, ModuleInstallerType, virtualEnvTypes } from '../../pythonEnvironments/info'; import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; import { IApplicationShell } from '../application/types'; import { wrapCancellationTokens } from '../cancellation'; -import { STANDARD_OUTPUT_CHANNEL } from '../constants'; import { IFileSystem } from '../platform/types'; import * as internalPython from '../process/internal/python'; -import { ITerminalServiceFactory } from '../terminal/types'; -import { ExecutionInfo, IConfigurationService, IOutputChannel } from '../types'; -import { Products } from '../utils/localize'; +import { IProcessServiceFactory } from '../process/types'; +import { ITerminalServiceFactory, TerminalCreationOptions } from '../terminal/types'; +import { ExecutionInfo, IConfigurationService, ILogOutputChannel, Product } from '../types'; import { isResource } from '../utils/misc'; -import { IModuleInstaller, InterpreterUri } from './types'; +import { ProductNames } from './productNames'; +import { IModuleInstaller, InstallOptions, InterpreterUri, ModuleInstallFlags } from './types'; @injectable() export abstract class ModuleInstaller implements IModuleInstaller { public abstract get priority(): number; + public abstract get name(): string; + public abstract get displayName(): string; + public abstract get type(): ModuleInstallerType; + constructor(protected serviceContainer: IServiceContainer) {} - public async installModule(name: string, resource?: InterpreterUri, cancel?: CancellationToken): Promise { - sendTelemetryEvent(EventName.PYTHON_INSTALL_PACKAGE, undefined, { installer: this.displayName }); + public async installModule( + productOrModuleName: Product | string, + resource?: InterpreterUri, + cancel?: CancellationToken, + flags?: ModuleInstallFlags, + options?: InstallOptions, + ): Promise { + const shouldExecuteInTerminal = !options?.installAsProcess; + const name = + typeof productOrModuleName === 'string' + ? productOrModuleName + : translateProductToModule(productOrModuleName); + const productName = typeof productOrModuleName === 'string' ? name : ProductNames.get(productOrModuleName); + sendTelemetryEvent(EventName.PYTHON_INSTALL_PACKAGE, undefined, { installer: this.displayName, productName }); const uri = isResource(resource) ? resource : undefined; - const executionInfo = await this.getExecutionInfo(name, resource); - const terminalService = this.serviceContainer - .get(ITerminalServiceFactory) - .getTerminalService(uri); + const executionInfo = await this.getExecutionInfo(name, resource, flags); + const install = async (token?: CancellationToken) => { const executionInfoArgs = await this.processInstallArgs(executionInfo.args, resource); if (executionInfo.moduleName) { @@ -45,54 +60,102 @@ export abstract class ModuleInstaller implements IModuleInstaller { const interpreter = isResource(resource) ? await interpreterService.getActiveInterpreter(resource) : resource; - const pythonPath = isResource(resource) ? settings.pythonPath : resource.path; + const interpreterPath = interpreter?.path ?? settings.pythonPath; + const pythonPath = isResource(resource) ? interpreterPath : resource.path; const args = internalPython.execModule(executionInfo.moduleName, executionInfoArgs); if (!interpreter || interpreter.envType !== EnvironmentType.Unknown) { - await terminalService.sendCommand(pythonPath, args, token); + await this.executeCommand( + shouldExecuteInTerminal, + resource, + pythonPath, + args, + token, + executionInfo.useShell, + ); } else if (settings.globalModuleInstallation) { const fs = this.serviceContainer.get(IFileSystem); if (await fs.isDirReadonly(path.dirname(pythonPath)).catch((_err) => true)) { this.elevatedInstall(pythonPath, args); } else { - await terminalService.sendCommand(pythonPath, args, token); + await this.executeCommand( + shouldExecuteInTerminal, + resource, + pythonPath, + args, + token, + executionInfo.useShell, + ); } + } else if (name === translateProductToModule(Product.pip)) { + // Pip should always be installed into the specified environment. + await this.executeCommand( + shouldExecuteInTerminal, + resource, + pythonPath, + args, + token, + executionInfo.useShell, + ); + } else if (virtualEnvTypes.includes(interpreter.envType)) { + await this.executeCommand( + shouldExecuteInTerminal, + resource, + pythonPath, + args, + token, + executionInfo.useShell, + ); } else { - await terminalService.sendCommand(pythonPath, args.concat(['--user']), token); + await this.executeCommand( + shouldExecuteInTerminal, + resource, + pythonPath, + args.concat(['--user']), + token, + executionInfo.useShell, + ); } } else { - await terminalService.sendCommand(executionInfo.execPath!, executionInfoArgs, token); + await this.executeCommand( + shouldExecuteInTerminal, + resource, + executionInfo.execPath!, + executionInfoArgs, + token, + executionInfo.useShell, + ); } }; // Display progress indicator if we have ability to cancel this operation from calling code. // This is required as its possible the installation can take a long time. // (i.e. if installation takes a long time in terminal or like, a progress indicator is necessary to let user know what is being waited on). - if (cancel) { + if (cancel && !options?.hideProgress) { const shell = this.serviceContainer.get(IApplicationShell); const options: ProgressOptions = { location: ProgressLocation.Notification, cancellable: true, - title: Products.installingModule().format(name) + title: l10n.t('Installing {0}', name), }; await shell.withProgress(options, async (_, token: CancellationToken) => - install(wrapCancellationTokens(token, cancel)) + install(wrapCancellationTokens(token, cancel)), ); } else { await install(cancel); } } + public abstract isSupported(resource?: InterpreterUri): Promise; protected elevatedInstall(execPath: string, args: string[]) { const options = { - name: 'VS Code Python' + name: 'VS Code Python', }; - const outputChannel = this.serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); + const outputChannel = this.serviceContainer.get(ILogOutputChannel); const command = `"${execPath.replace(/\\/g, '/')}" ${args.join(' ')}`; - outputChannel.appendLine(''); - outputChannel.appendLine(`[Elevated] ${command}`); - // tslint:disable-next-line:no-require-imports no-var-requires + traceLog(`[Elevated] ${command}`); + const sudo = require('sudo-prompt'); sudo.exec(command, options, async (error: string, stdout: string, stderr: string) => { @@ -102,17 +165,21 @@ export abstract class ModuleInstaller implements IModuleInstaller { } else { outputChannel.show(); if (stdout) { - outputChannel.appendLine(''); - outputChannel.append(stdout); + traceLog(stdout); } if (stderr) { - outputChannel.appendLine(''); - outputChannel.append(`Warning: ${stderr}`); + traceError(`Warning: ${stderr}`); } } }); } - protected abstract getExecutionInfo(moduleName: string, resource?: InterpreterUri): Promise; + + protected abstract getExecutionInfo( + moduleName: string, + resource?: InterpreterUri, + flags?: ModuleInstallFlags, + ): Promise; + private async processInstallArgs(args: string[], resource?: InterpreterUri): Promise { const indexOfPylint = args.findIndex((arg) => arg.toUpperCase() === 'PYLINT'); if (indexOfPylint === -1) { @@ -129,4 +196,66 @@ export abstract class ModuleInstaller implements IModuleInstaller { } return args; } + + private async executeCommand( + executeInTerminal: boolean, + resource: InterpreterUri | undefined, + command: string, + args: string[], + token: CancellationToken | undefined, + useShell: boolean | undefined, + ) { + const options: TerminalCreationOptions = {}; + if (isResource(resource)) { + options.resource = resource; + } else { + options.interpreter = resource; + } + if (executeInTerminal) { + const terminalService = this.serviceContainer + .get(ITerminalServiceFactory) + .getTerminalService(options); + + terminalService.sendCommand(command, args, token); + } else { + const processServiceFactory = this.serviceContainer.get(IProcessServiceFactory); + const processService = await processServiceFactory.create(options.resource); + if (useShell) { + const argv = [command, ...args]; + // Concat these together to make a set of quoted strings + const quoted = argv.reduce( + (p, c) => + p ? `${p} ${c.toCommandArgumentForPythonExt()}` : `${c.toCommandArgumentForPythonExt()}`, + '', + ); + await processService.shellExec(quoted); + } else { + await processService.exec(command, args); + } + } + } +} + +export function translateProductToModule(product: Product): string { + switch (product) { + case Product.pytest: + return 'pytest'; + case Product.unittest: + return 'unittest'; + case Product.tensorboard: + return 'tensorboard'; + case Product.torchProfilerInstallName: + return 'torch-tb-profiler'; + case Product.torchProfilerImportName: + return 'torch_tb_profiler'; + case Product.pip: + return 'pip'; + case Product.ensurepip: + return 'ensurepip'; + case Product.python: + return 'python'; + default: { + throw new Error(`Product ${product} cannot be installed as a Python Module.`); + } + } } diff --git a/src/client/common/installer/pipEnvInstaller.ts b/src/client/common/installer/pipEnvInstaller.ts index ea4ed5be906f..2c7dece6a298 100644 --- a/src/client/common/installer/pipEnvInstaller.ts +++ b/src/client/common/installer/pipEnvInstaller.ts @@ -2,24 +2,28 @@ // Licensed under the MIT License. import { inject, injectable } from 'inversify'; -import { IInterpreterLocatorService, PIPENV_SERVICE } from '../../interpreter/contracts'; +import { IInterpreterService } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; -import { EnvironmentType } from '../../pythonEnvironments/info'; +import { isPipenvEnvironmentRelatedToFolder } from '../../pythonEnvironments/common/environmentManagers/pipenv'; +import { EnvironmentType, ModuleInstallerType } from '../../pythonEnvironments/info'; +import { IWorkspaceService } from '../application/types'; import { ExecutionInfo } from '../types'; import { isResource } from '../utils/misc'; import { ModuleInstaller } from './moduleInstaller'; -import { InterpreterUri } from './types'; +import { InterpreterUri, ModuleInstallFlags } from './types'; export const pipenvName = 'pipenv'; @injectable() export class PipEnvInstaller extends ModuleInstaller { - private readonly pipenv: IInterpreterLocatorService; - public get name(): string { return 'pipenv'; } + public get type(): ModuleInstallerType { + return ModuleInstallerType.Pipenv; + } + public get displayName() { return pipenvName; } @@ -29,24 +33,38 @@ export class PipEnvInstaller extends ModuleInstaller { constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super(serviceContainer); - this.pipenv = this.serviceContainer.get(IInterpreterLocatorService, PIPENV_SERVICE); } public async isSupported(resource?: InterpreterUri): Promise { if (isResource(resource)) { - const interpreters = await this.pipenv.getInterpreters(resource); - return interpreters.length > 0; + const interpreter = await this.serviceContainer + .get(IInterpreterService) + .getActiveInterpreter(resource); + const workspaceFolder = resource + ? this.serviceContainer.get(IWorkspaceService).getWorkspaceFolder(resource) + : undefined; + if (!interpreter || !workspaceFolder || interpreter.envType !== EnvironmentType.Pipenv) { + return false; + } + // Install using `pipenv install` only if the active environment is related to the current folder. + return isPipenvEnvironmentRelatedToFolder(interpreter.path, workspaceFolder.uri.fsPath); } else { return resource.envType === EnvironmentType.Pipenv; } } - protected async getExecutionInfo(moduleName: string, _resource?: InterpreterUri): Promise { - const args = ['install', moduleName, '--dev']; - if (moduleName === 'black') { - args.push('--pre'); - } + protected async getExecutionInfo( + moduleName: string, + _resource?: InterpreterUri, + flags: ModuleInstallFlags = 0, + ): Promise { + // In pipenv the only way to update/upgrade or re-install is update (apart from a complete uninstall and re-install). + const update = + flags & ModuleInstallFlags.reInstall || + flags & ModuleInstallFlags.updateDependencies || + flags & ModuleInstallFlags.upgrade; + const args = [update ? 'update' : 'install', moduleName, '--dev']; return { args: args, - execPath: pipenvName + execPath: pipenvName, }; } } diff --git a/src/client/common/installer/pipInstaller.ts b/src/client/common/installer/pipInstaller.ts index 9b0afd3abbc2..cb0274ea5b31 100644 --- a/src/client/common/installer/pipInstaller.ts +++ b/src/client/common/installer/pipInstaller.ts @@ -3,12 +3,39 @@ import { inject, injectable } from 'inversify'; import { IServiceContainer } from '../../ioc/types'; +import { EnvironmentType, ModuleInstallerType } from '../../pythonEnvironments/info'; import { IWorkspaceService } from '../application/types'; import { IPythonExecutionFactory } from '../process/types'; -import { ExecutionInfo } from '../types'; +import { ExecutionInfo, IInstaller, Product } from '../types'; import { isResource } from '../utils/misc'; -import { ModuleInstaller } from './moduleInstaller'; -import { InterpreterUri } from './types'; +import { ModuleInstaller, translateProductToModule } from './moduleInstaller'; +import { InterpreterUri, ModuleInstallFlags } from './types'; +import * as path from 'path'; +import { _SCRIPTS_DIR } from '../process/internal/scripts/constants'; +import { ProductNames } from './productNames'; +import { sendTelemetryEvent } from '../../telemetry'; +import { EventName } from '../../telemetry/constants'; +import { IInterpreterService } from '../../interpreter/contracts'; +import { isParentPath } from '../platform/fs-paths'; + +async function doesEnvironmentContainPython(serviceContainer: IServiceContainer, resource: InterpreterUri) { + const interpreterService = serviceContainer.get(IInterpreterService); + const environment = isResource(resource) ? await interpreterService.getActiveInterpreter(resource) : resource; + if (!environment) { + return undefined; + } + if ( + environment.envPath?.length && + environment.envType === EnvironmentType.Conda && + !isParentPath(environment?.path, environment.envPath) + ) { + // For conda environments not containing a python interpreter, do not use pip installer due to bugs in `conda run`: + // https://github.com/microsoft/vscode-python/issues/18479#issuecomment-1044427511 + // https://github.com/conda/conda/issues/11211 + return false; + } + return true; +} @injectable() export class PipInstaller extends ModuleInstaller { @@ -16,6 +43,10 @@ export class PipInstaller extends ModuleInstaller { return 'Pip'; } + public get type(): ModuleInstallerType { + return ModuleInstallerType.Pip; + } + public get displayName() { return 'Pip'; } @@ -25,20 +56,73 @@ export class PipInstaller extends ModuleInstaller { constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super(serviceContainer); } - public isSupported(resource?: InterpreterUri): Promise { + public async isSupported(resource?: InterpreterUri): Promise { + if ((await doesEnvironmentContainPython(this.serviceContainer, resource)) === false) { + return false; + } return this.isPipAvailable(resource); } - protected async getExecutionInfo(moduleName: string, _resource?: InterpreterUri): Promise { - const proxyArgs: string[] = []; + protected async getExecutionInfo( + moduleName: string, + resource?: InterpreterUri, + flags: ModuleInstallFlags = 0, + ): Promise { + if (moduleName === translateProductToModule(Product.pip)) { + const version = isResource(resource) + ? '' + : `${resource.version?.major || ''}.${resource.version?.minor || ''}.${resource.version?.patch || ''}`; + const envType = isResource(resource) ? undefined : resource.envType; + + sendTelemetryEvent(EventName.PYTHON_INSTALL_PACKAGE, undefined, { + installer: 'unavailable', + requiredInstaller: ModuleInstallerType.Pip, + productName: ProductNames.get(Product.pip), + version, + envType, + }); + + // If `ensurepip` is available, if not, then install pip using the script file. + const installer = this.serviceContainer.get(IInstaller); + if (await installer.isInstalled(Product.ensurepip, resource)) { + return { + args: [], + moduleName: 'ensurepip', + }; + } + + sendTelemetryEvent(EventName.PYTHON_INSTALL_PACKAGE, undefined, { + installer: 'unavailable', + requiredInstaller: ModuleInstallerType.Pip, + productName: ProductNames.get(Product.ensurepip), + version, + envType, + }); + + // Return script to install pip. + const interpreterService = this.serviceContainer.get(IInterpreterService); + const interpreter = isResource(resource) + ? await interpreterService.getActiveInterpreter(resource) + : resource; + return { + execPath: interpreter ? interpreter.path : 'python', + args: [path.join(_SCRIPTS_DIR, 'get-pip.py')], + }; + } + + const args: string[] = []; const workspaceService = this.serviceContainer.get(IWorkspaceService); const proxy = workspaceService.getConfiguration('http').get('proxy', ''); if (proxy.length > 0) { - proxyArgs.push('--proxy'); - proxyArgs.push(proxy); + args.push('--proxy'); + args.push(proxy); + } + args.push(...['install', '-U']); + if (flags & ModuleInstallFlags.reInstall) { + args.push('--force-reinstall'); } return { - args: [...proxyArgs, 'install', '-U', moduleName], - moduleName: 'pip' + args: [...args, moduleName], + moduleName: 'pip', }; } private isPipAvailable(info?: InterpreterUri): Promise { diff --git a/src/client/common/installer/pixiInstaller.ts b/src/client/common/installer/pixiInstaller.ts new file mode 100644 index 000000000000..8a2278830b51 --- /dev/null +++ b/src/client/common/installer/pixiInstaller.ts @@ -0,0 +1,81 @@ +/* eslint-disable class-methods-use-this */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable } from 'inversify'; +import { IInterpreterService } from '../../interpreter/contracts'; +import { IServiceContainer } from '../../ioc/types'; +import { getEnvPath } from '../../pythonEnvironments/base/info/env'; +import { EnvironmentType, ModuleInstallerType } from '../../pythonEnvironments/info'; +import { ExecutionInfo, IConfigurationService } from '../types'; +import { isResource } from '../utils/misc'; +import { ModuleInstaller } from './moduleInstaller'; +import { InterpreterUri } from './types'; +import { getPixiEnvironmentFromInterpreter } from '../../pythonEnvironments/common/environmentManagers/pixi'; + +/** + * A Python module installer for a pixi project. + */ +@injectable() +export class PixiInstaller extends ModuleInstaller { + constructor( + @inject(IServiceContainer) serviceContainer: IServiceContainer, + @inject(IConfigurationService) private readonly configurationService: IConfigurationService, + ) { + super(serviceContainer); + } + + public get name(): string { + return 'Pixi'; + } + + public get displayName(): string { + return 'pixi'; + } + + public get type(): ModuleInstallerType { + return ModuleInstallerType.Pixi; + } + + public get priority(): number { + return 20; + } + + public async isSupported(resource?: InterpreterUri): Promise { + if (isResource(resource)) { + const interpreter = await this.serviceContainer + .get(IInterpreterService) + .getActiveInterpreter(resource); + if (!interpreter || interpreter.envType !== EnvironmentType.Pixi) { + return false; + } + + const pixiEnv = await getPixiEnvironmentFromInterpreter(interpreter.path); + return pixiEnv !== undefined; + } + return resource.envType === EnvironmentType.Pixi; + } + + /** + * Return the commandline args needed to install the module. + */ + protected async getExecutionInfo(moduleName: string, resource?: InterpreterUri): Promise { + const pythonPath = isResource(resource) + ? this.configurationService.getSettings(resource).pythonPath + : getEnvPath(resource.path, resource.envPath).path ?? ''; + + const pixiEnv = await getPixiEnvironmentFromInterpreter(pythonPath); + const execPath = pixiEnv?.pixi.command; + + let args = ['add', moduleName]; + const manifestPath = pixiEnv?.manifestPath; + if (manifestPath !== undefined) { + args = args.concat(['--manifest-path', manifestPath]); + } + + return { + args, + execPath, + }; + } +} diff --git a/src/client/common/installer/poetryInstaller.ts b/src/client/common/installer/poetryInstaller.ts index 68f759e559b8..5017d0813d98 100644 --- a/src/client/common/installer/poetryInstaller.ts +++ b/src/client/common/installer/poetryInstaller.ts @@ -4,29 +4,36 @@ 'use strict'; import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { Uri } from 'vscode'; +import { IInterpreterService } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; +import { isPoetryEnvironmentRelatedToFolder } from '../../pythonEnvironments/common/environmentManagers/poetry'; +import { EnvironmentType, ModuleInstallerType } from '../../pythonEnvironments/info'; import { IWorkspaceService } from '../application/types'; -import { traceError } from '../logger'; -import { IFileSystem } from '../platform/types'; -import { IProcessServiceFactory } from '../process/types'; import { ExecutionInfo, IConfigurationService } from '../types'; import { isResource } from '../utils/misc'; import { ModuleInstaller } from './moduleInstaller'; import { InterpreterUri } from './types'; + export const poetryName = 'poetry'; -const poetryFile = 'poetry.lock'; @injectable() export class PoetryInstaller extends ModuleInstaller { + // eslint-disable-next-line class-methods-use-this public get name(): string { return 'poetry'; } - public get displayName() { + // eslint-disable-next-line class-methods-use-this + public get type(): ModuleInstallerType { + return ModuleInstallerType.Poetry; + } + + // eslint-disable-next-line class-methods-use-this + public get displayName(): string { return poetryName; } + + // eslint-disable-next-line class-methods-use-this public get priority(): number { return 10; } @@ -35,44 +42,38 @@ export class PoetryInstaller extends ModuleInstaller { @inject(IServiceContainer) serviceContainer: IServiceContainer, @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, @inject(IConfigurationService) private readonly configurationService: IConfigurationService, - @inject(IFileSystem) private readonly fs: IFileSystem, - @inject(IProcessServiceFactory) private readonly processFactory: IProcessServiceFactory ) { super(serviceContainer); } + public async isSupported(resource?: InterpreterUri): Promise { if (!resource) { return false; } - const workspaceFolder = this.workspaceService.getWorkspaceFolder(isResource(resource) ? resource : undefined); - if (!workspaceFolder) { + if (!isResource(resource)) { return false; } - if (!(await this.fs.fileExists(path.join(workspaceFolder.uri.fsPath, poetryFile)))) { - return false; - } - return this.isPoetryAvailable(workspaceFolder.uri); - } - protected async isPoetryAvailable(workfolder: Uri) { - try { - const processService = await this.processFactory.create(workfolder); - const execPath = this.configurationService.getSettings(workfolder).poetryPath; - const result = await processService.exec(execPath, ['list'], { cwd: workfolder.fsPath }); - return result && (result.stderr || '').trim().length === 0; - } catch (error) { - traceError(`${poetryFile} exists but Poetry not found`, error); + const interpreter = await this.serviceContainer + .get(IInterpreterService) + .getActiveInterpreter(resource); + const workspaceFolder = resource ? this.workspaceService.getWorkspaceFolder(resource) : undefined; + if (!interpreter || !workspaceFolder || interpreter.envType !== EnvironmentType.Poetry) { return false; } + // Install using poetry CLI only if the active poetry environment is related to the current folder. + return isPoetryEnvironmentRelatedToFolder( + interpreter.path, + workspaceFolder.uri.fsPath, + this.configurationService.getSettings(resource).poetryPath, + ); } + protected async getExecutionInfo(moduleName: string, resource?: InterpreterUri): Promise { const execPath = this.configurationService.getSettings(isResource(resource) ? resource : undefined).poetryPath; - const args = ['add', '--dev', moduleName]; - if (moduleName === 'black') { - args.push('--allow-prereleases'); - } + const args = ['add', '--group', 'dev', moduleName]; return { args, - execPath + execPath, }; } } diff --git a/src/client/common/installer/productInstaller.ts b/src/client/common/installer/productInstaller.ts index 795f0fd84994..831eb33efbc6 100644 --- a/src/client/common/installer/productInstaller.ts +++ b/src/client/common/installer/productInstaller.ts @@ -1,67 +1,77 @@ -// tslint:disable:max-classes-per-file max-classes-per-file - -import { inject, injectable, named } from 'inversify'; -import * as os from 'os'; -import { CancellationToken, OutputChannel, Uri } from 'vscode'; -import '../../common/extensions'; -import * as localize from '../../common/utils/localize'; -import { Telemetry } from '../../datascience/constants'; +/* eslint-disable max-classes-per-file */ + +import { inject, injectable } from 'inversify'; +import * as semver from 'semver'; +import { CancellationToken, l10n, Uri } from 'vscode'; +import '../extensions'; import { IInterpreterService } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; -import { LinterId } from '../../linters/types'; -import { PythonEnvironment } from '../../pythonEnvironments/info'; +import { EnvironmentType, ModuleInstallerType, PythonEnvironment } from '../../pythonEnvironments/info'; import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; -import { IApplicationShell, ICommandManager, IWorkspaceService } from '../application/types'; -import { Commands, STANDARD_OUTPUT_CHANNEL } from '../constants'; -import { traceError } from '../logger'; -import { IPlatformService } from '../platform/types'; +import { IApplicationShell, IWorkspaceService } from '../application/types'; import { IProcessServiceFactory, IPythonExecutionFactory } from '../process/types'; -import { ITerminalServiceFactory } from '../terminal/types'; import { IConfigurationService, IInstaller, InstallerResponse, - IOutputChannel, IPersistentStateFactory, - ModuleNamePurpose, + ProductInstallStatus, Product, - ProductType + ProductType, } from '../types'; +import { Common } from '../utils/localize'; import { isResource, noop } from '../utils/misc'; -import { StopWatch } from '../utils/stopWatch'; +import { translateProductToModule } from './moduleInstaller'; import { ProductNames } from './productNames'; import { + IBaseInstaller, IInstallationChannelManager, IModuleInstaller, + InstallOptions, InterpreterUri, IProductPathService, - IProductService + IProductService, + ModuleInstallFlags, } from './types'; +import { traceError, traceInfo } from '../../logging'; +import { isParentPath } from '../platform/fs-paths'; export { Product } from '../types'; -export const CTagsInsllationScript = - os.platform() === 'darwin' ? 'brew install ctags' : 'sudo apt-get install exuberant-ctags'; +// Products which may not be available to install from certain package registries, keyed by product name +// Installer implementations can check this to determine a suitable installation channel for a product +// This is temporary and can be removed when https://github.com/microsoft/vscode-jupyter/issues/5034 is unblocked +const UnsupportedChannelsForProduct = new Map>([ + [Product.torchProfilerInstallName, new Set([EnvironmentType.Conda, EnvironmentType.Pixi])], +]); -export abstract class BaseInstaller { +abstract class BaseInstaller implements IBaseInstaller { private static readonly PromptPromises = new Map>(); + protected readonly appShell: IApplicationShell; + protected readonly configService: IConfigurationService; - private readonly workspaceService: IWorkspaceService; + + protected readonly workspaceService: IWorkspaceService; + private readonly productService: IProductService; - constructor(protected serviceContainer: IServiceContainer, protected outputChannel: OutputChannel) { + protected readonly persistentStateFactory: IPersistentStateFactory; + + constructor(protected serviceContainer: IServiceContainer) { this.appShell = serviceContainer.get(IApplicationShell); this.configService = serviceContainer.get(IConfigurationService); this.workspaceService = serviceContainer.get(IWorkspaceService); this.productService = serviceContainer.get(IProductService); + this.persistentStateFactory = serviceContainer.get(IPersistentStateFactory); } public promptToInstall( product: Product, resource?: InterpreterUri, - cancel?: CancellationToken + cancel?: CancellationToken, + flags?: ModuleInstallFlags, ): Promise { // If this method gets called twice, while previous promise has not been resolved, then return that same promise. // E.g. previous promise is not resolved as a message has been displayed to the user, so no point displaying @@ -72,7 +82,7 @@ export abstract class BaseInstaller { if (BaseInstaller.PromptPromises.has(key)) { return BaseInstaller.PromptPromises.get(key)!; } - const promise = this.promptToInstallImplementation(product, resource, cancel); + const promise = this.promptToInstallImplementation(product, resource, cancel, flags); BaseInstaller.PromptPromises.set(key, promise); promise.then(() => BaseInstaller.PromptPromises.delete(key)).ignoreErrors(); promise.catch(() => BaseInstaller.PromptPromises.delete(key)).ignoreErrors(); @@ -83,7 +93,9 @@ export abstract class BaseInstaller { public async install( product: Product, resource?: InterpreterUri, - cancel?: CancellationToken + cancel?: CancellationToken, + flags?: ModuleInstallFlags, + options?: InstallOptions, ): Promise { if (product === Product.unittest) { return InstallerResponse.Installed; @@ -92,20 +104,83 @@ export abstract class BaseInstaller { const channels = this.serviceContainer.get(IInstallationChannelManager); const installer = await channels.getInstallationChannel(product, resource); if (!installer) { + sendTelemetryEvent(EventName.PYTHON_INSTALL_PACKAGE, undefined, { + installer: 'unavailable', + productName: ProductNames.get(product), + }); return InstallerResponse.Ignore; } - const moduleName = translateProductToModule(product, ModuleNamePurpose.install); await installer - .installModule(moduleName, resource, cancel) - .catch((ex) => traceError(`Error in installing the module '${moduleName}', ${ex}`)); + .installModule(product, resource, cancel, flags, options) + .catch((ex) => traceError(`Error in installing the product '${ProductNames.get(product)}', ${ex}`)); + + return this.isInstalled(product, resource).then((isInstalled) => { + sendTelemetryEvent(EventName.PYTHON_INSTALL_PACKAGE, undefined, { + installer: installer.displayName, + productName: ProductNames.get(product), + isInstalled, + }); + return isInstalled ? InstallerResponse.Installed : InstallerResponse.Ignore; + }); + } - return this.isInstalled(product, resource).then((isInstalled) => - isInstalled ? InstallerResponse.Installed : InstallerResponse.Ignore - ); + /** + * + * @param product A product which supports SemVer versioning. + * @param semVerRequirement A SemVer version requirement. + * @param resource A URI or a PythonEnvironment. + */ + public async isProductVersionCompatible( + product: Product, + semVerRequirement: string, + resource?: InterpreterUri, + ): Promise { + const version = await this.getProductSemVer(product, resource); + if (!version) { + return ProductInstallStatus.NotInstalled; + } + if (semver.satisfies(version, semVerRequirement)) { + return ProductInstallStatus.Installed; + } + return ProductInstallStatus.NeedsUpgrade; + } + + /** + * + * @param product A product which supports SemVer versioning. + * @param resource A URI or a PythonEnvironment. + */ + private async getProductSemVer(product: Product, resource: InterpreterUri): Promise { + const interpreter = isResource(resource) ? undefined : resource; + const uri = isResource(resource) ? resource : undefined; + const executableName = this.getExecutableNameFromSettings(product, uri); + + const isModule = this.isExecutableAModule(product, uri); + + let version; + if (isModule) { + const pythonProcess = await this.serviceContainer + .get(IPythonExecutionFactory) + .createActivatedEnvironment({ resource: uri, interpreter, allowEnvironmentFetchExceptions: true }); + version = await pythonProcess.getModuleVersion(executableName); + } else { + const process = await this.serviceContainer.get(IProcessServiceFactory).create(uri); + const result = await process.exec(executableName, ['--version'], { mergeStdOutErr: true }); + version = result.stdout.trim(); + } + if (!version) { + return null; + } + try { + return semver.coerce(version); + } catch (e) { + traceError(`Unable to parse version ${version} for product ${product}: `, e); + return null; + } } - public async isInstalled(product: Product, resource?: InterpreterUri): Promise { + public async isInstalled(product: Product, resource?: InterpreterUri): Promise { if (product === Product.unittest) { return true; } @@ -120,235 +195,54 @@ export abstract class BaseInstaller { .get(IPythonExecutionFactory) .createActivatedEnvironment({ resource: uri, interpreter, allowEnvironmentFetchExceptions: true }); return pythonProcess.isModuleInstalled(executableName); - } else { - const process = await this.serviceContainer.get(IProcessServiceFactory).create(uri); - return process - .exec(executableName, ['--version'], { mergeStdOutErr: true }) - .then(() => true) - .catch(() => false); } + const process = await this.serviceContainer.get(IProcessServiceFactory).create(uri); + return process + .exec(executableName, ['--version'], { mergeStdOutErr: true }) + .then(() => true) + .catch(() => false); } protected abstract promptToInstallImplementation( product: Product, resource?: InterpreterUri, - cancel?: CancellationToken + cancel?: CancellationToken, + flags?: ModuleInstallFlags, ): Promise; + protected getExecutableNameFromSettings(product: Product, resource?: Uri): string { const productType = this.productService.getProductType(product); const productPathService = this.serviceContainer.get(IProductPathService, productType); return productPathService.getExecutableNameFromSettings(product, resource); } - protected isExecutableAModule(product: Product, resource?: Uri): Boolean { + + protected isExecutableAModule(product: Product, resource?: Uri): boolean { const productType = this.productService.getProductType(product); const productPathService = this.serviceContainer.get(IProductPathService, productType); return productPathService.isExecutableAModule(product, resource); } } -export class CTagsInstaller extends BaseInstaller { - constructor(serviceContainer: IServiceContainer, outputChannel: OutputChannel) { - super(serviceContainer, outputChannel); - } - - public async install(_product: Product, resource?: Uri): Promise { - if (this.serviceContainer.get(IPlatformService).isWindows) { - this.outputChannel.appendLine('Install Universal Ctags Win32 to enable support for Workspace Symbols'); - this.outputChannel.appendLine('Download the CTags binary from the Universal CTags site.'); - this.outputChannel.appendLine( - 'Option 1: Extract ctags.exe from the downloaded zip to any folder within your PATH so that Visual Studio Code can run it.' - ); - this.outputChannel.appendLine( - 'Option 2: Extract to any folder and add the path to this folder to the command setting.' - ); - this.outputChannel.appendLine( - 'Option 3: Extract to any folder and define that path in the python.workspaceSymbols.ctagsPath setting of your user settings file (settings.json).' - ); - this.outputChannel.show(); - } else { - const terminalService = this.serviceContainer - .get(ITerminalServiceFactory) - .getTerminalService(resource); - terminalService - .sendCommand(CTagsInsllationScript, []) - .catch((ex) => traceError(`Failed to install ctags. Script sent '${CTagsInsllationScript}', ${ex}`)); - } - return InstallerResponse.Ignore; - } - protected async promptToInstallImplementation( - product: Product, - resource?: Uri, - _cancel?: CancellationToken - ): Promise { - const item = await this.appShell.showErrorMessage( - 'Install CTags to enable Python workspace symbols?', - 'Yes', - 'No' - ); - return item === 'Yes' ? this.install(product, resource) : InstallerResponse.Ignore; - } -} - -export class FormatterInstaller extends BaseInstaller { - protected async promptToInstallImplementation( - product: Product, - resource?: Uri, - cancel?: CancellationToken - ): Promise { - // Hard-coded on purpose because the UI won't necessarily work having - // another formatter. - const formatters = [Product.autopep8, Product.black, Product.yapf]; - const formatterNames = formatters.map((formatter) => ProductNames.get(formatter)!); - const productName = ProductNames.get(product)!; - formatterNames.splice(formatterNames.indexOf(productName), 1); - const useOptions = formatterNames.map((name) => `Use ${name}`); - const yesChoice = 'Yes'; - - const options = [...useOptions]; - let message = `Formatter ${productName} is not installed. Install?`; - if (this.isExecutableAModule(product, resource)) { - options.splice(0, 0, yesChoice); - } else { - const executable = this.getExecutableNameFromSettings(product, resource); - message = `Path to the ${productName} formatter is invalid (${executable})`; - } - - const item = await this.appShell.showErrorMessage(message, ...options); - if (item === yesChoice) { - return this.install(product, resource, cancel); - } else if (typeof item === 'string') { - for (const formatter of formatters) { - const formatterName = ProductNames.get(formatter)!; - - if (item.endsWith(formatterName)) { - await this.configService.updateSetting('formatting.provider', formatterName, resource); - return this.install(formatter, resource, cancel); - } - } - } - - return InstallerResponse.Ignore; - } -} - -export class LinterInstaller extends BaseInstaller { - protected async promptToInstallImplementation( - product: Product, - resource?: Uri, - cancel?: CancellationToken - ): Promise { - const isPylint = product === Product.pylint; - - const productName = ProductNames.get(product)!; - const install = 'Install'; - const disableInstallPrompt = 'Do not show again'; - const disableLinterInstallPromptKey = `${productName}_DisableLinterInstallPrompt`; - const selectLinter = 'Select Linter'; - - if (isPylint && this.getStoredResponse(disableLinterInstallPromptKey) === true) { - return InstallerResponse.Ignore; - } - - const options = isPylint ? [selectLinter, disableInstallPrompt] : [selectLinter]; - - let message = `Linter ${productName} is not installed.`; - if (this.isExecutableAModule(product, resource)) { - options.splice(0, 0, install); - } else { - const executable = this.getExecutableNameFromSettings(product, resource); - message = `Path to the ${productName} linter is invalid (${executable})`; - } - const response = await this.appShell.showErrorMessage(message, ...options); - if (response === install) { - sendTelemetryEvent(EventName.LINTER_NOT_INSTALLED_PROMPT, undefined, { - tool: productName as LinterId, - action: 'install' - }); - return this.install(product, resource, cancel); - } else if (response === disableInstallPrompt) { - await this.setStoredResponse(disableLinterInstallPromptKey, true); - sendTelemetryEvent(EventName.LINTER_NOT_INSTALLED_PROMPT, undefined, { - tool: productName as LinterId, - action: 'disablePrompt' - }); - return InstallerResponse.Ignore; - } - - if (response === selectLinter) { - sendTelemetryEvent(EventName.LINTER_NOT_INSTALLED_PROMPT, undefined, { action: 'select' }); - const commandManager = this.serviceContainer.get(ICommandManager); - await commandManager.executeCommand(Commands.Set_Linter); - } - return InstallerResponse.Ignore; - } - - /** - * For installers that want to avoid prompting the user over and over, they can make use of a - * persisted true/false value representing user responses to 'stop showing this prompt'. This method - * gets the persisted value given the installer-defined key. - * - * @param key Key to use to get a persisted response value, each installer must define this for themselves. - * @returns Boolean: The current state of the stored response key given. - */ - protected getStoredResponse(key: string): boolean { - const factory = this.serviceContainer.get(IPersistentStateFactory); - const state = factory.createGlobalPersistentState(key, undefined); - return state.value === true; - } - - /** - * For installers that want to avoid prompting the user over and over, they can make use of a - * persisted true/false value representing user responses to 'stop showing this prompt'. This - * method will set that persisted value given the installer-defined key. - * - * @param key Key to use to get a persisted response value, each installer must define this for themselves. - * @param value Boolean value to store for the user - if they choose to not be prompted again for instance. - * @returns Boolean: The current state of the stored response key given. - */ - private async setStoredResponse(key: string, value: boolean): Promise { - const factory = this.serviceContainer.get(IPersistentStateFactory); - const state = factory.createGlobalPersistentState(key, undefined); - if (state && state.value !== value) { - await state.updateValue(value); - } - } -} - export class TestFrameworkInstaller extends BaseInstaller { protected async promptToInstallImplementation( product: Product, resource?: Uri, - cancel?: CancellationToken + cancel?: CancellationToken, + _flags?: ModuleInstallFlags, ): Promise { const productName = ProductNames.get(product)!; const options: string[] = []; - let message = `Test framework ${productName} is not installed. Install?`; + let message = l10n.t('Test framework {0} is not installed. Install?', productName); if (this.isExecutableAModule(product, resource)) { - options.push(...['Yes', 'No']); + options.push(...[Common.bannerLabelYes, Common.bannerLabelNo]); } else { const executable = this.getExecutableNameFromSettings(product, resource); - message = `Path to the ${productName} test framework is invalid (${executable})`; + message = l10n.t('Path to the {0} test framework is invalid ({1})', productName, executable); } const item = await this.appShell.showErrorMessage(message, ...options); - return item === 'Yes' ? this.install(product, resource, cancel) : InstallerResponse.Ignore; - } -} - -export class RefactoringLibraryInstaller extends BaseInstaller { - protected async promptToInstallImplementation( - product: Product, - resource?: Uri, - cancel?: CancellationToken - ): Promise { - const productName = ProductNames.get(product)!; - const item = await this.appShell.showErrorMessage( - `Refactoring library ${productName} is not installed. Install?`, - 'Yes', - 'No' - ); - return item === 'Yes' ? this.install(product, resource, cancel) : InstallerResponse.Ignore; + return item === Common.bannerLabelYes ? this.install(product, resource, cancel) : InstallerResponse.Ignore; } } @@ -357,7 +251,8 @@ export class DataScienceInstaller extends BaseInstaller { public async install( product: Product, interpreterUri?: InterpreterUri, - cancel?: CancellationToken + cancel?: CancellationToken, + flags?: ModuleInstallFlags, ): Promise { // Precondition if (isResource(interpreterUri)) { @@ -368,83 +263,236 @@ export class DataScienceInstaller extends BaseInstaller { const interpreter = interpreterUri as PythonEnvironment; // Get a list of known installation channels, pip, conda, etc. - const channels: IModuleInstaller[] = await this.serviceContainer + let channels: IModuleInstaller[] = await this.serviceContainer .get(IInstallationChannelManager) - .getInstallationChannels(); + .getInstallationChannels(interpreter); // Pick an installerModule based on whether the interpreter is conda or not. Default is pip. - let installerModule; - if (interpreter.envType === 'Conda') { - installerModule = channels.find((v) => v.name === 'Conda'); + const moduleName = translateProductToModule(product); + const version = `${interpreter.version?.major || ''}.${interpreter.version?.minor || ''}.${ + interpreter.version?.patch || '' + }`; + + // If this is a non-conda environment & pip isn't installed, we need to install pip. + // The prompt would have been disabled prior to this point, so we can assume that. + if ( + flags && + flags & ModuleInstallFlags.installPipIfRequired && + interpreter.envType !== EnvironmentType.Conda && + !channels.some((channel) => channel.type === ModuleInstallerType.Pip) + ) { + const installers = this.serviceContainer.getAll(IModuleInstaller); + const pipInstaller = installers.find((installer) => installer.type === ModuleInstallerType.Pip); + if (pipInstaller) { + traceInfo(`Installing pip as its not available to install ${moduleName}.`); + await pipInstaller + .installModule(Product.pip, interpreter, cancel) + .catch((ex) => + traceError( + `Error in installing the module '${moduleName} as Pip could not be installed', ${ex}`, + ), + ); + + await this.isInstalled(Product.pip, interpreter) + .then((isInstalled) => { + sendTelemetryEvent(EventName.PYTHON_INSTALL_PACKAGE, undefined, { + installer: pipInstaller.displayName, + requiredInstaller: ModuleInstallerType.Pip, + version, + envType: interpreter.envType, + isInstalled, + productName: ProductNames.get(Product.pip), + }); + }) + .catch(noop); + + // Refresh the list of channels (pip may be avaialble now). + channels = await this.serviceContainer + .get(IInstallationChannelManager) + .getInstallationChannels(interpreter); + } else { + sendTelemetryEvent(EventName.PYTHON_INSTALL_PACKAGE, undefined, { + installer: 'unavailable', + requiredInstaller: ModuleInstallerType.Pip, + productName: ProductNames.get(Product.pip), + version, + envType: interpreter.envType, + }); + traceError(`Unable to install pip when its required.`); + } + } + + const isAvailableThroughConda = !UnsupportedChannelsForProduct.get(product)?.has(EnvironmentType.Conda); + let requiredInstaller = ModuleInstallerType.Unknown; + if (interpreter.envType === EnvironmentType.Conda && isAvailableThroughConda) { + requiredInstaller = ModuleInstallerType.Conda; + } else if (interpreter.envType === EnvironmentType.Conda && !isAvailableThroughConda) { + // This case is temporary and can be removed when https://github.com/microsoft/vscode-jupyter/issues/5034 is unblocked + traceInfo( + `Interpreter type is conda but package ${moduleName} is not available through conda, using pip instead.`, + ); + requiredInstaller = ModuleInstallerType.Pip; } else { - installerModule = channels.find((v) => v.name === 'Pip'); + switch (interpreter.envType) { + case EnvironmentType.Pipenv: + requiredInstaller = ModuleInstallerType.Pipenv; + break; + case EnvironmentType.Poetry: + requiredInstaller = ModuleInstallerType.Poetry; + break; + default: + requiredInstaller = ModuleInstallerType.Pip; + } } + const installerModule: IModuleInstaller | undefined = channels.find((v) => v.type === requiredInstaller); - const moduleName = translateProductToModule(product, ModuleNamePurpose.install); if (!installerModule) { this.appShell - .showErrorMessage(localize.DataScience.couldNotInstallLibrary().format(moduleName)) + .showErrorMessage( + l10n.t( + 'Could not install {0}. If pip is not available, please use the package manager of your choice to manually install this library into your Python environment.', + moduleName, + ), + ) .then(noop, noop); + sendTelemetryEvent(EventName.PYTHON_INSTALL_PACKAGE, undefined, { + installer: 'unavailable', + requiredInstaller, + productName: ProductNames.get(product), + version, + envType: interpreter.envType, + }); return InstallerResponse.Ignore; } await installerModule - .installModule(moduleName, interpreter, cancel) + .installModule(product, interpreter, cancel, flags) .catch((ex) => traceError(`Error in installing the module '${moduleName}', ${ex}`)); - return this.isInstalled(product, interpreter).then((isInstalled) => - isInstalled ? InstallerResponse.Installed : InstallerResponse.Ignore - ); + return this.isInstalled(product, interpreter).then((isInstalled) => { + sendTelemetryEvent(EventName.PYTHON_INSTALL_PACKAGE, undefined, { + installer: installerModule.displayName || '', + requiredInstaller, + version, + envType: interpreter.envType, + isInstalled, + productName: ProductNames.get(product), + }); + return isInstalled ? InstallerResponse.Installed : InstallerResponse.Ignore; + }); } + + /** + * This method will not get invoked for Jupyter extension. + * Implemented as a backup. + */ protected async promptToInstallImplementation( product: Product, resource?: InterpreterUri, - cancel?: CancellationToken + cancel?: CancellationToken, + _flags?: ModuleInstallFlags, ): Promise { const productName = ProductNames.get(product)!; const item = await this.appShell.showErrorMessage( - localize.DataScience.libraryNotInstalled().format(productName), - 'Yes', - 'No' + l10n.t('Data Science library {0} is not installed. Install?', productName), + Common.bannerLabelYes, + Common.bannerLabelNo, ); - if (item === 'Yes') { - const stopWatch = new StopWatch(); - try { - const response = await this.install(product, resource, cancel); - const event = - product === Product.jupyter ? Telemetry.UserInstalledJupyter : Telemetry.UserInstalledModule; - sendTelemetryEvent(event, stopWatch.elapsedTime, { product: productName }); - return response; - } catch (e) { - if (product === Product.jupyter) { - sendTelemetryEvent(Telemetry.JupyterInstallFailed); - } - throw e; - } + if (item === Common.bannerLabelYes) { + return this.install(product, resource, cancel); + } + return InstallerResponse.Ignore; + } +} + +export class PythonInstaller implements IBaseInstaller { + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {} + + public async isInstalled(product: Product, resource?: InterpreterUri): Promise { + if (product !== Product.python) { + throw new Error(`${product} cannot be installed via conda python installer`); + } + const interpreterService = this.serviceContainer.get(IInterpreterService); + const environment = isResource(resource) ? await interpreterService.getActiveInterpreter(resource) : resource; + if (!environment) { + return true; + } + if ( + environment.envPath?.length && + environment.envType === EnvironmentType.Conda && + !isParentPath(environment?.path, environment.envPath) + ) { + return false; } + return true; + } + + public async install( + product: Product, + resource?: InterpreterUri, + _cancel?: CancellationToken, + _flags?: ModuleInstallFlags, + ): Promise { + if (product !== Product.python) { + throw new Error(`${product} cannot be installed via python installer`); + } + // Active interpreter is a conda environment which does not contain python, hence install it. + const installers = this.serviceContainer.getAll(IModuleInstaller); + const condaInstaller = installers.find((installer) => installer.type === ModuleInstallerType.Conda); + if (!condaInstaller || !(await condaInstaller.isSupported(resource))) { + traceError('Conda installer not available for installing python in the given environment'); + return InstallerResponse.Ignore; + } + const moduleName = translateProductToModule(product); + await condaInstaller + .installModule(Product.python, resource, undefined, undefined, { installAsProcess: true }) + .catch((ex) => traceError(`Error in installing the module '${moduleName}', ${ex}`)); + return this.isInstalled(product, resource).then((isInstalled) => + isInstalled ? InstallerResponse.Installed : InstallerResponse.Ignore, + ); + } + + // eslint-disable-next-line class-methods-use-this + public async promptToInstall( + _product: Product, + _resource?: InterpreterUri, + _cancel?: CancellationToken, + _flags?: ModuleInstallFlags, + ): Promise { + // This package is installed directly without any prompt. return InstallerResponse.Ignore; } + + // eslint-disable-next-line class-methods-use-this + public async isProductVersionCompatible( + _product: Product, + _semVerRequirement: string, + _resource?: InterpreterUri, + ): Promise { + return ProductInstallStatus.Installed; + } } @injectable() export class ProductInstaller implements IInstaller { private readonly productService: IProductService; + private interpreterService: IInterpreterService; - constructor( - @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(IOutputChannel) @named(STANDARD_OUTPUT_CHANNEL) private outputChannel: OutputChannel - ) { + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { this.productService = serviceContainer.get(IProductService); this.interpreterService = this.serviceContainer.get(IInterpreterService); } - // tslint:disable-next-line:no-empty - public dispose() {} + public dispose(): void { + /** Do nothing. */ + } + public async promptToInstall( product: Product, resource?: InterpreterUri, - cancel?: CancellationToken + cancel?: CancellationToken, + flags?: ModuleInstallFlags, ): Promise { const currentInterpreter = isResource(resource) ? await this.interpreterService.getActiveInterpreter(resource) @@ -452,91 +500,48 @@ export class ProductInstaller implements IInstaller { if (!currentInterpreter) { return InstallerResponse.Ignore; } - return this.createInstaller(product).promptToInstall(product, resource, cancel); + return this.createInstaller(product).promptToInstall(product, resource, cancel, flags); } + + public async isProductVersionCompatible( + product: Product, + semVerRequirement: string, + resource?: InterpreterUri, + ): Promise { + return this.createInstaller(product).isProductVersionCompatible(product, semVerRequirement, resource); + } + public async install( product: Product, resource?: InterpreterUri, - cancel?: CancellationToken + cancel?: CancellationToken, + flags?: ModuleInstallFlags, + options?: InstallOptions, ): Promise { - return this.createInstaller(product).install(product, resource, cancel); + return this.createInstaller(product).install(product, resource, cancel, flags, options); } - public async isInstalled(product: Product, resource?: InterpreterUri): Promise { + + public async isInstalled(product: Product, resource?: InterpreterUri): Promise { return this.createInstaller(product).isInstalled(product, resource); } - public translateProductToModuleName(product: Product, purpose: ModuleNamePurpose): string { - return translateProductToModule(product, purpose); + + // eslint-disable-next-line class-methods-use-this + public translateProductToModuleName(product: Product): string { + return translateProductToModule(product); } - private createInstaller(product: Product): BaseInstaller { + + private createInstaller(product: Product): IBaseInstaller { const productType = this.productService.getProductType(product); switch (productType) { - case ProductType.Formatter: - return new FormatterInstaller(this.serviceContainer, this.outputChannel); - case ProductType.Linter: - return new LinterInstaller(this.serviceContainer, this.outputChannel); - case ProductType.WorkspaceSymbols: - return new CTagsInstaller(this.serviceContainer, this.outputChannel); case ProductType.TestFramework: - return new TestFrameworkInstaller(this.serviceContainer, this.outputChannel); - case ProductType.RefactoringLibrary: - return new RefactoringLibraryInstaller(this.serviceContainer, this.outputChannel); + return new TestFrameworkInstaller(this.serviceContainer); case ProductType.DataScience: - return new DataScienceInstaller(this.serviceContainer, this.outputChannel); + return new DataScienceInstaller(this.serviceContainer); + case ProductType.Python: + return new PythonInstaller(this.serviceContainer); default: break; } throw new Error(`Unknown product ${product}`); } } - -// tslint:disable-next-line: cyclomatic-complexity -function translateProductToModule(product: Product, purpose: ModuleNamePurpose): string { - switch (product) { - case Product.mypy: - return 'mypy'; - case Product.nosetest: { - return purpose === ModuleNamePurpose.install ? 'nose' : 'nosetests'; - } - case Product.pylama: - return 'pylama'; - case Product.prospector: - return 'prospector'; - case Product.pylint: - return 'pylint'; - case Product.pytest: - return 'pytest'; - case Product.autopep8: - return 'autopep8'; - case Product.black: - return 'black'; - case Product.pycodestyle: - return 'pycodestyle'; - case Product.pydocstyle: - return 'pydocstyle'; - case Product.yapf: - return 'yapf'; - case Product.flake8: - return 'flake8'; - case Product.unittest: - return 'unittest'; - case Product.rope: - return 'rope'; - case Product.bandit: - return 'bandit'; - case Product.jupyter: - return 'jupyter'; - case Product.notebook: - return 'notebook'; - case Product.pandas: - return 'pandas'; - case Product.ipykernel: - return 'ipykernel'; - case Product.nbconvert: - return 'nbconvert'; - case Product.kernelspec: - return 'kernelspec'; - default: { - throw new Error(`Product ${product} cannot be installed as a Python Module.`); - } - } -} diff --git a/src/client/common/installer/productNames.ts b/src/client/common/installer/productNames.ts index 54d478f81d89..00b19ce77ac3 100644 --- a/src/client/common/installer/productNames.ts +++ b/src/client/common/installer/productNames.ts @@ -3,25 +3,10 @@ import { Product } from '../types'; -// tslint:disable-next-line:variable-name export const ProductNames = new Map(); -ProductNames.set(Product.autopep8, 'autopep8'); -ProductNames.set(Product.bandit, 'bandit'); -ProductNames.set(Product.black, 'black'); -ProductNames.set(Product.flake8, 'flake8'); -ProductNames.set(Product.mypy, 'mypy'); -ProductNames.set(Product.nosetest, 'nosetest'); -ProductNames.set(Product.pycodestyle, 'pycodestyle'); -ProductNames.set(Product.pylama, 'pylama'); -ProductNames.set(Product.prospector, 'prospector'); -ProductNames.set(Product.pydocstyle, 'pydocstyle'); -ProductNames.set(Product.pylint, 'pylint'); ProductNames.set(Product.pytest, 'pytest'); -ProductNames.set(Product.yapf, 'yapf'); -ProductNames.set(Product.rope, 'rope'); -ProductNames.set(Product.jupyter, 'jupyter'); -ProductNames.set(Product.notebook, 'notebook'); -ProductNames.set(Product.ipykernel, 'ipykernel'); -ProductNames.set(Product.nbconvert, 'nbconvert'); -ProductNames.set(Product.kernelspec, 'kernelspec'); -ProductNames.set(Product.pandas, 'pandas'); +ProductNames.set(Product.tensorboard, 'tensorboard'); +ProductNames.set(Product.torchProfilerInstallName, 'torch-tb-profiler'); +ProductNames.set(Product.torchProfilerImportName, 'torch_tb_profiler'); +ProductNames.set(Product.pip, 'pip'); +ProductNames.set(Product.ensurepip, 'ensurepip'); diff --git a/src/client/common/installer/productPath.ts b/src/client/common/installer/productPath.ts index 7ef9bd801d5d..b06e4b7a48a9 100644 --- a/src/client/common/installer/productPath.ts +++ b/src/client/common/installer/productPath.ts @@ -3,16 +3,12 @@ 'use strict'; -// tslint:disable:max-classes-per-file - import { inject, injectable } from 'inversify'; import * as path from 'path'; import { Uri } from 'vscode'; -import { IFormatterHelper } from '../../formatters/types'; import { IServiceContainer } from '../../ioc/types'; -import { ILinterManager } from '../../linters/types'; -import { ITestsHelper } from '../../testing/common/types'; -import { IConfigurationService, IInstaller, ModuleNamePurpose, Product } from '../types'; +import { ITestingService } from '../../testing/types'; +import { IConfigurationService, IInstaller, Product } from '../types'; import { IProductPathService } from './types'; @injectable() @@ -24,14 +20,10 @@ export abstract class BaseProductPathsService implements IProductPathService { this.productInstaller = serviceContainer.get(IInstaller); } public abstract getExecutableNameFromSettings(product: Product, resource?: Uri): string; - public isExecutableAModule(product: Product, resource?: Uri): Boolean { - if (product === Product.kernelspec) { - return false; - } + public isExecutableAModule(product: Product, resource?: Uri): boolean { let moduleName: string | undefined; try { - moduleName = this.productInstaller.translateProductToModuleName(product, ModuleNamePurpose.run); - // tslint:disable-next-line:no-empty + moduleName = this.productInstaller.translateProductToModuleName(product); } catch {} // User may have customized the module name or provided the fully qualifieid path. @@ -43,74 +35,29 @@ export abstract class BaseProductPathsService implements IProductPathService { } } -@injectable() -export class CTagsProductPathService extends BaseProductPathsService { - constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - super(serviceContainer); - } - public getExecutableNameFromSettings(_: Product, resource?: Uri): string { - const settings = this.configService.getSettings(resource); - return settings.workspaceSymbols.ctagsPath; - } -} - -@injectable() -export class FormatterProductPathService extends BaseProductPathsService { - constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - super(serviceContainer); - } - public getExecutableNameFromSettings(product: Product, resource?: Uri): string { - const settings = this.configService.getSettings(resource); - const formatHelper = this.serviceContainer.get(IFormatterHelper); - const settingsPropNames = formatHelper.getSettingsPropertyNames(product); - return settings.formatting[settingsPropNames.pathName] as string; - } -} - -@injectable() -export class LinterProductPathService extends BaseProductPathsService { - constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - super(serviceContainer); - } - public getExecutableNameFromSettings(product: Product, resource?: Uri): string { - const linterManager = this.serviceContainer.get(ILinterManager); - return linterManager.getLinterInfo(product).pathName(resource); - } -} - @injectable() export class TestFrameworkProductPathService extends BaseProductPathsService { constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super(serviceContainer); } public getExecutableNameFromSettings(product: Product, resource?: Uri): string { - const testHelper = this.serviceContainer.get(ITestsHelper); + const testHelper = this.serviceContainer.get(ITestingService); const settingsPropNames = testHelper.getSettingsPropertyNames(product); if (!settingsPropNames.pathName) { // E.g. in the case of UnitTests we don't allow customizing the paths. - return this.productInstaller.translateProductToModuleName(product, ModuleNamePurpose.run); + return this.productInstaller.translateProductToModuleName(product); } const settings = this.configService.getSettings(resource); return settings.testing[settingsPropNames.pathName] as string; } } -@injectable() -export class RefactoringLibraryProductPathService extends BaseProductPathsService { - constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - super(serviceContainer); - } - public getExecutableNameFromSettings(product: Product, _?: Uri): string { - return this.productInstaller.translateProductToModuleName(product, ModuleNamePurpose.run); - } -} - @injectable() export class DataScienceProductPathService extends BaseProductPathsService { constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super(serviceContainer); } public getExecutableNameFromSettings(product: Product, _?: Uri): string { - return this.productInstaller.translateProductToModuleName(product, ModuleNamePurpose.run); + return this.productInstaller.translateProductToModuleName(product); } } diff --git a/src/client/common/installer/productService.ts b/src/client/common/installer/productService.ts index 9687529670a0..bf5597cc5859 100644 --- a/src/client/common/installer/productService.ts +++ b/src/client/common/installer/productService.ts @@ -12,28 +12,14 @@ export class ProductService implements IProductService { private ProductTypes = new Map(); constructor() { - this.ProductTypes.set(Product.bandit, ProductType.Linter); - this.ProductTypes.set(Product.flake8, ProductType.Linter); - this.ProductTypes.set(Product.mypy, ProductType.Linter); - this.ProductTypes.set(Product.pycodestyle, ProductType.Linter); - this.ProductTypes.set(Product.prospector, ProductType.Linter); - this.ProductTypes.set(Product.pydocstyle, ProductType.Linter); - this.ProductTypes.set(Product.pylama, ProductType.Linter); - this.ProductTypes.set(Product.pylint, ProductType.Linter); - this.ProductTypes.set(Product.ctags, ProductType.WorkspaceSymbols); - this.ProductTypes.set(Product.nosetest, ProductType.TestFramework); this.ProductTypes.set(Product.pytest, ProductType.TestFramework); this.ProductTypes.set(Product.unittest, ProductType.TestFramework); - this.ProductTypes.set(Product.autopep8, ProductType.Formatter); - this.ProductTypes.set(Product.black, ProductType.Formatter); - this.ProductTypes.set(Product.yapf, ProductType.Formatter); - this.ProductTypes.set(Product.rope, ProductType.RefactoringLibrary); - this.ProductTypes.set(Product.jupyter, ProductType.DataScience); - this.ProductTypes.set(Product.notebook, ProductType.DataScience); - this.ProductTypes.set(Product.ipykernel, ProductType.DataScience); - this.ProductTypes.set(Product.nbconvert, ProductType.DataScience); - this.ProductTypes.set(Product.kernelspec, ProductType.DataScience); - this.ProductTypes.set(Product.pandas, ProductType.DataScience); + this.ProductTypes.set(Product.tensorboard, ProductType.DataScience); + this.ProductTypes.set(Product.torchProfilerInstallName, ProductType.DataScience); + this.ProductTypes.set(Product.torchProfilerImportName, ProductType.DataScience); + this.ProductTypes.set(Product.pip, ProductType.DataScience); + this.ProductTypes.set(Product.ensurepip, ProductType.DataScience); + this.ProductTypes.set(Product.python, ProductType.Python); } public getProductType(product: Product): ProductType { return this.ProductTypes.get(product)!; diff --git a/src/client/common/installer/serviceRegistry.ts b/src/client/common/installer/serviceRegistry.ts index 9c3d4680245e..1e273ada818c 100644 --- a/src/client/common/installer/serviceRegistry.ts +++ b/src/client/common/installer/serviceRegistry.ts @@ -3,77 +3,33 @@ 'use strict'; import { IServiceManager } from '../../ioc/types'; -import { IWebviewPanelProvider } from '../application/types'; -import { WebviewPanelProvider } from '../application/webviewPanels/webviewPanelProvider'; import { ProductType } from '../types'; import { InstallationChannelManager } from './channelManager'; import { CondaInstaller } from './condaInstaller'; -import { InsidersBuildInstaller, StableBuildInstaller } from './extensionBuildInstaller'; import { PipEnvInstaller } from './pipEnvInstaller'; import { PipInstaller } from './pipInstaller'; +import { PixiInstaller } from './pixiInstaller'; import { PoetryInstaller } from './poetryInstaller'; -import { - CTagsProductPathService, - DataScienceProductPathService, - FormatterProductPathService, - LinterProductPathService, - RefactoringLibraryProductPathService, - TestFrameworkProductPathService -} from './productPath'; +import { DataScienceProductPathService, TestFrameworkProductPathService } from './productPath'; import { ProductService } from './productService'; -import { - IExtensionBuildInstaller, - IInstallationChannelManager, - IModuleInstaller, - INSIDERS_INSTALLER, - IProductPathService, - IProductService, - STABLE_INSTALLER -} from './types'; +import { IInstallationChannelManager, IModuleInstaller, IProductPathService, IProductService } from './types'; export function registerTypes(serviceManager: IServiceManager) { + serviceManager.addSingleton(IModuleInstaller, PixiInstaller); serviceManager.addSingleton(IModuleInstaller, CondaInstaller); serviceManager.addSingleton(IModuleInstaller, PipInstaller); serviceManager.addSingleton(IModuleInstaller, PipEnvInstaller); serviceManager.addSingleton(IModuleInstaller, PoetryInstaller); serviceManager.addSingleton(IInstallationChannelManager, InstallationChannelManager); - serviceManager.addSingleton( - IExtensionBuildInstaller, - StableBuildInstaller, - STABLE_INSTALLER - ); - serviceManager.addSingleton( - IExtensionBuildInstaller, - InsidersBuildInstaller, - INSIDERS_INSTALLER - ); - serviceManager.addSingleton(IProductService, ProductService); - serviceManager.addSingleton( - IProductPathService, - CTagsProductPathService, - ProductType.WorkspaceSymbols - ); - serviceManager.addSingleton( - IProductPathService, - FormatterProductPathService, - ProductType.Formatter - ); - serviceManager.addSingleton(IProductPathService, LinterProductPathService, ProductType.Linter); serviceManager.addSingleton( IProductPathService, TestFrameworkProductPathService, - ProductType.TestFramework - ); - serviceManager.addSingleton( - IProductPathService, - RefactoringLibraryProductPathService, - ProductType.RefactoringLibrary + ProductType.TestFramework, ); serviceManager.addSingleton( IProductPathService, DataScienceProductPathService, - ProductType.DataScience + ProductType.DataScience, ); - serviceManager.addSingleton(IWebviewPanelProvider, WebviewPanelProvider); } diff --git a/src/client/common/installer/types.ts b/src/client/common/installer/types.ts index 4b1bb9ad146d..a85017ff0092 100644 --- a/src/client/common/installer/types.ts +++ b/src/client/common/installer/types.ts @@ -2,8 +2,8 @@ // Licensed under the MIT License. import { CancellationToken, Uri } from 'vscode'; -import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { Product, ProductType, Resource } from '../types'; +import { ModuleInstallerType, PythonEnvironment } from '../../pythonEnvironments/info'; +import { InstallerResponse, Product, ProductInstallStatus, ProductType, Resource } from '../types'; export type InterpreterUri = Resource | PythonEnvironment; @@ -12,21 +12,46 @@ export interface IModuleInstaller { readonly name: string; readonly displayName: string; readonly priority: number; + readonly type: ModuleInstallerType; /** * Installs a module * If a cancellation token is provided, then a cancellable progress message is dispalyed. * At this point, this method would resolve only after the module has been successfully installed. * If cancellation token is not provided, its not guaranteed that module installation has completed. - * @param {string} name - * @param {InterpreterUri} [resource] - * @param {CancellationToken} [cancel] - * @returns {Promise} - * @memberof IModuleInstaller */ - installModule(name: string, resource?: InterpreterUri, cancel?: CancellationToken): Promise; + installModule( + productOrModuleName: Product | string, + resource?: InterpreterUri, + cancel?: CancellationToken, + flags?: ModuleInstallFlags, + options?: InstallOptions, + ): Promise; isSupported(resource?: InterpreterUri): Promise; } +export const IBaseInstaller = Symbol('IBaseInstaller'); +export interface IBaseInstaller { + install( + product: Product, + resource?: InterpreterUri, + cancel?: CancellationToken, + flags?: ModuleInstallFlags, + options?: InstallOptions, + ): Promise; + promptToInstall( + product: Product, + resource?: InterpreterUri, + cancel?: CancellationToken, + flags?: ModuleInstallFlags, + ): Promise; + isProductVersionCompatible( + product: Product, + semVerRequirement: string, + resource?: InterpreterUri, + ): Promise; + isInstalled(product: Product, resource?: InterpreterUri): Promise; +} + export const IPythonInstallation = Symbol('IPythonInstallation'); export interface IPythonInstallation { checkInstallation(): Promise; @@ -45,12 +70,18 @@ export interface IProductService { export const IProductPathService = Symbol('IProductPathService'); export interface IProductPathService { getExecutableNameFromSettings(product: Product, resource?: Uri): string; - isExecutableAModule(product: Product, resource?: Uri): Boolean; + isExecutableAModule(product: Product, resource?: Uri): boolean; } -export const INSIDERS_INSTALLER = 'INSIDERS_INSTALLER'; -export const STABLE_INSTALLER = 'STABLE_INSTALLER'; -export const IExtensionBuildInstaller = Symbol('IExtensionBuildInstaller'); -export interface IExtensionBuildInstaller { - install(): Promise; +export enum ModuleInstallFlags { + none = 0, + upgrade = 1, + updateDependencies = 2, + reInstall = 4, + installPipIfRequired = 8, } + +export type InstallOptions = { + installAsProcess?: boolean; + hideProgress?: boolean; +}; diff --git a/src/client/common/interpreterPathService.ts b/src/client/common/interpreterPathService.ts index a5bbc17108b5..935d0bd89ad7 100644 --- a/src/client/common/interpreterPathService.ts +++ b/src/client/common/interpreterPathService.ts @@ -3,13 +3,13 @@ 'use strict'; -import * as fs from 'fs-extra'; +import * as fs from '../common/platform/fs-paths'; import { inject, injectable } from 'inversify'; import { ConfigurationChangeEvent, ConfigurationTarget, Event, EventEmitter, Uri } from 'vscode'; -import { IWorkspaceService } from './application/types'; +import { traceError, traceVerbose } from '../logging'; +import { IApplicationEnvironment, IWorkspaceService } from './application/types'; import { PythonSettings } from './configSettings'; import { isTestExecution } from './constants'; -import { traceError } from './logger'; import { FileSystemPaths } from './platform/fs-paths'; import { IDisposable, @@ -20,12 +20,13 @@ import { IPersistentState, IPersistentStateFactory, IPythonSettings, - Resource + Resource, } from './types'; +import { SystemVariables } from './variables/systemVariables'; -export const workspaceKeysForWhichTheCopyIsDone_Key = 'workspaceKeysForWhichTheCopyIsDone_Key'; -export const workspaceFolderKeysForWhichTheCopyIsDone_Key = 'workspaceFolderKeysForWhichTheCopyIsDone_Key'; -export const isGlobalSettingCopiedKey = 'isGlobalSettingCopiedKey'; +export const remoteWorkspaceKeysForWhichTheCopyIsDone_Key = 'remoteWorkspaceKeysForWhichTheCopyIsDone_Key'; +export const remoteWorkspaceFolderKeysForWhichTheCopyIsDone_Key = 'remoteWorkspaceFolderKeysForWhichTheCopyIsDone_Key'; +export const isRemoteGlobalSettingCopiedKey = 'isRemoteGlobalSettingCopiedKey'; export const defaultInterpreterPathSetting: keyof IPythonSettings = 'defaultInterpreterPath'; const CI_PYTHON_PATH = getCIPythonPath(); @@ -45,7 +46,8 @@ export class InterpreterPathService implements IInterpreterPathService { constructor( @inject(IPersistentStateFactory) private readonly persistentStateFactory: IPersistentStateFactory, @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, - @inject(IDisposableRegistry) disposables: IDisposable[] + @inject(IDisposableRegistry) disposables: IDisposable[], + @inject(IApplicationEnvironment) private readonly appEnvironment: IApplicationEnvironment, ) { disposables.push(this.workspaceService.onDidChangeConfiguration(this.onDidChangeConfiguration.bind(this))); this.fileSystemPaths = FileSystemPaths.withDefaults(); @@ -54,46 +56,58 @@ export class InterpreterPathService implements IInterpreterPathService { public async onDidChangeConfiguration(event: ConfigurationChangeEvent) { if (event.affectsConfiguration(`python.${defaultInterpreterPathSetting}`)) { this._didChangeInterpreterEmitter.fire({ uri: undefined, configTarget: ConfigurationTarget.Global }); + traceVerbose('Interpreter Path updated', `python.${defaultInterpreterPathSetting}`); } } - public inspect(resource: Resource): InspectInterpreterSettingType { + public inspect(resource: Resource, useOldKey = false): InspectInterpreterSettingType { resource = PythonSettings.getSettingsUriAndTarget(resource, this.workspaceService).uri; let workspaceFolderSetting: IPersistentState | undefined; let workspaceSetting: IPersistentState | undefined; if (resource) { workspaceFolderSetting = this.persistentStateFactory.createGlobalPersistentState( - this.getSettingKey(resource, ConfigurationTarget.WorkspaceFolder), - undefined + this.getSettingKey(resource, ConfigurationTarget.WorkspaceFolder, useOldKey), + undefined, ); workspaceSetting = this.persistentStateFactory.createGlobalPersistentState( - this.getSettingKey(resource, ConfigurationTarget.Workspace), - undefined + this.getSettingKey(resource, ConfigurationTarget.Workspace, useOldKey), + undefined, ); } - const globalValue = this.workspaceService.getConfiguration('python')!.inspect('defaultInterpreterPath')! - .globalValue; + const defaultInterpreterPath: InspectInterpreterSettingType = + this.workspaceService.getConfiguration('python', resource)?.inspect('defaultInterpreterPath') ?? {}; return { - globalValue, - workspaceFolderValue: workspaceFolderSetting?.value, - workspaceValue: workspaceSetting?.value + globalValue: defaultInterpreterPath.globalValue, + workspaceFolderValue: + !workspaceFolderSetting?.value || workspaceFolderSetting?.value === 'python' + ? defaultInterpreterPath.workspaceFolderValue + : workspaceFolderSetting.value, + workspaceValue: + !workspaceSetting?.value || workspaceSetting?.value === 'python' + ? defaultInterpreterPath.workspaceValue + : workspaceSetting.value, }; } public get(resource: Resource): string { const settings = this.inspect(resource); - return ( + const value = settings.workspaceFolderValue || settings.workspaceValue || settings.globalValue || - (isTestExecution() ? CI_PYTHON_PATH : 'python') + (isTestExecution() ? CI_PYTHON_PATH : 'python'); + const systemVariables = new SystemVariables( + undefined, + this.workspaceService.getWorkspaceFolder(resource)?.uri.fsPath, + this.workspaceService, ); + return systemVariables.resolveAny(value)!; } public async update( resource: Resource, configTarget: ConfigurationTarget, - pythonPath: string | undefined + pythonPath: string | undefined, ): Promise { resource = PythonSettings.getSettingsUriAndTarget(resource, this.workspaceService).uri; if (configTarget === ConfigurationTarget.Global) { @@ -101,7 +115,6 @@ export class InterpreterPathService implements IInterpreterPathService { const globalValue = pythonConfig.inspect('defaultInterpreterPath')!.globalValue; if (globalValue !== pythonPath) { await pythonConfig.update('defaultInterpreterPath', pythonPath, true); - this._didChangeInterpreterEmitter.fire({ uri: undefined, configTarget }); } return; } @@ -112,17 +125,19 @@ export class InterpreterPathService implements IInterpreterPathService { const settingKey = this.getSettingKey(resource, configTarget); const persistentSetting = this.persistentStateFactory.createGlobalPersistentState( settingKey, - undefined + undefined, ); if (persistentSetting.value !== pythonPath) { await persistentSetting.updateValue(pythonPath); this._didChangeInterpreterEmitter.fire({ uri: resource, configTarget }); + traceVerbose('Interpreter Path updated', settingKey, pythonPath); } } public getSettingKey( resource: Uri, - configTarget: ConfigurationTarget.Workspace | ConfigurationTarget.WorkspaceFolder + configTarget: ConfigurationTarget.Workspace | ConfigurationTarget.WorkspaceFolder, + useOldKey = false, ): string { let settingKey: string; const folderKey = this.workspaceService.getWorkspaceFolderIdentifier(resource); @@ -131,21 +146,24 @@ export class InterpreterPathService implements IInterpreterPathService { } else { settingKey = this.workspaceService.workspaceFile ? `WORKSPACE_INTERPRETER_PATH_${this.fileSystemPaths.normCase( - this.workspaceService.workspaceFile.fsPath + this.workspaceService.workspaceFile.fsPath, )}` : // Only a single folder is opened, use fsPath of the folder as key `WORKSPACE_FOLDER_INTERPRETER_PATH_${folderKey}`; } + if (!useOldKey && this.appEnvironment.remoteName) { + return `${this.appEnvironment.remoteName}_${settingKey}`; + } return settingKey; } public async copyOldInterpreterStorageValuesToNew(resource: Resource): Promise { resource = PythonSettings.getSettingsUriAndTarget(resource, this.workspaceService).uri; - const oldSettings = this.workspaceService.getConfiguration('python', resource).inspect('pythonPath')!; + const oldSettings = this.inspect(resource, true); await Promise.all([ this._copyWorkspaceFolderValueToNewStorage(resource, oldSettings.workspaceFolderValue), this._copyWorkspaceValueToNewStorage(resource, oldSettings.workspaceValue), - this._moveGlobalSettingValueToNewStorage(oldSettings.globalValue) + this._moveGlobalSettingValueToNewStorage(oldSettings.globalValue), ]); } @@ -157,8 +175,8 @@ export class InterpreterPathService implements IInterpreterPathService { return; } const flaggedWorkspaceFolderKeysStorage = this.persistentStateFactory.createGlobalPersistentState( - workspaceFolderKeysForWhichTheCopyIsDone_Key, - [] + remoteWorkspaceFolderKeysForWhichTheCopyIsDone_Key, + [], ); const flaggedWorkspaceFolderKeys = flaggedWorkspaceFolderKeysStorage.value; const shouldUpdateWorkspaceFolderSetting = !flaggedWorkspaceFolderKeys.includes(workspaceFolderKey); @@ -177,8 +195,8 @@ export class InterpreterPathService implements IInterpreterPathService { return; } const flaggedWorkspaceKeysStorage = this.persistentStateFactory.createGlobalPersistentState( - workspaceKeysForWhichTheCopyIsDone_Key, - [] + remoteWorkspaceKeysForWhichTheCopyIsDone_Key, + [], ); const flaggedWorkspaceKeys = flaggedWorkspaceKeysStorage.value; const shouldUpdateWorkspaceSetting = !flaggedWorkspaceKeys.includes(workspaceKey); @@ -191,16 +209,12 @@ export class InterpreterPathService implements IInterpreterPathService { public async _moveGlobalSettingValueToNewStorage(value: string | undefined) { // Move global setting into the new storage if it hasn't been moved already const isGlobalSettingCopiedStorage = this.persistentStateFactory.createGlobalPersistentState( - isGlobalSettingCopiedKey, - false + isRemoteGlobalSettingCopiedKey, + false, ); const shouldUpdateGlobalSetting = !isGlobalSettingCopiedStorage.value; if (shouldUpdateGlobalSetting) { await this.update(undefined, ConfigurationTarget.Global, value); - // Make sure to delete the original setting after copying it - await this.workspaceService - .getConfiguration('python') - .update('pythonPath', undefined, ConfigurationTarget.Global); await isGlobalSettingCopiedStorage.updateValue(true); } } diff --git a/src/client/common/logger.ts b/src/client/common/logger.ts deleted file mode 100644 index d332dd6028e5..000000000000 --- a/src/client/common/logger.ts +++ /dev/null @@ -1,10 +0,0 @@ -// These are all just temporary aliases, for backward compatibility -// and to avoid churn. -export { - traceDecorators, - logError as traceError, - logInfo as traceInfo, - logVerbose as traceVerbose, - logWarning as traceWarning -} from '../logging'; -export { TraceOptions as LogOptions } from '../logging/trace'; diff --git a/src/client/common/markdown/restTextConverter.ts b/src/client/common/markdown/restTextConverter.ts deleted file mode 100644 index 0881b37f3cfb..000000000000 --- a/src/client/common/markdown/restTextConverter.ts +++ /dev/null @@ -1,247 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { EOL } from 'os'; -// tslint:disable-next-line:import-name -import Char from 'typescript-char'; -import { isDecimal, isWhiteSpace } from '../../language/characters'; - -enum State { - Default, - Preformatted, - Code -} - -export class RestTextConverter { - private state: State = State.Default; - private md: string[] = []; - - // tslint:disable-next-line:cyclomatic-complexity - public toMarkdown(docstring: string): string { - // Translates reStructruredText (Python doc syntax) to markdown. - // It only translates as much as needed to display tooltips - // and documentation in the completion list. - // See https://en.wikipedia.org/wiki/ReStructuredText - - const result = this.transformLines(docstring); - this.state = State.Default; - this.md = []; - - return result; - } - - public escapeMarkdown(text: string): string { - // Not complete escape list so it does not interfere - // with subsequent code highlighting (see above). - return text.replace(/\#/g, '\\#').replace(/\*/g, '\\*').replace(/\ _/g, ' \\_').replace(/^_/, '\\_'); - } - - private transformLines(docstring: string): string { - const lines = docstring.split(/\r?\n/); - for (let i = 0; i < lines.length; i += 1) { - const line = lines[i]; - // Avoid leading empty lines - if (this.md.length === 0 && line.length === 0) { - continue; - } - - switch (this.state) { - case State.Default: - i += this.inDefaultState(lines, i); - break; - case State.Preformatted: - i += this.inPreformattedState(lines, i); - break; - case State.Code: - this.inCodeState(line); - break; - default: - break; - } - } - - this.endCodeBlock(); - this.endPreformattedBlock(); - - return this.md.join(EOL).trim(); - } - - private inDefaultState(lines: string[], i: number): number { - let line = lines[i]; - if (line.startsWith('```')) { - this.startCodeBlock(); - return 0; - } - - if (line.startsWith('===') || line.startsWith('---')) { - return 0; // Eat standalone === or --- lines. - } - if (this.handleDoubleColon(line)) { - return 0; - } - if (this.isIgnorable(line)) { - return 0; - } - - if (this.handleSectionHeader(lines, i)) { - return 1; // Eat line with === or --- - } - - const result = this.checkPreContent(lines, i); - if (this.state !== State.Default) { - return result; // Handle line in the new state - } - - line = this.cleanup(line); - line = line.replace(/``/g, '`'); // Convert double backticks to single. - line = this.escapeMarkdown(line); - this.md.push(line); - - return 0; - } - - private inPreformattedState(lines: string[], i: number): number { - let line = lines[i]; - if (this.isIgnorable(line)) { - return 0; - } - // Preformatted block terminates by a line without leading whitespace. - if (line.length > 0 && !isWhiteSpace(line.charCodeAt(0)) && !this.isListItem(line)) { - this.endPreformattedBlock(); - return -1; - } - - const prevLine = this.md.length > 0 ? this.md[this.md.length - 1] : undefined; - if (line.length === 0 && prevLine && (prevLine.length === 0 || prevLine.startsWith('```'))) { - return 0; // Avoid more than one empty line in a row. - } - - // Since we use HTML blocks as preformatted text - // make sure we drop angle brackets since otherwise - // they will render as tags and attributes - line = line.replace(//g, ' '); - line = line.replace(/``/g, '`'); // Convert double backticks to single. - // Keep hard line breaks for the preformatted content - this.md.push(`${line} `); - return 0; - } - - private inCodeState(line: string): void { - const prevLine = this.md.length > 0 ? this.md[this.md.length - 1] : undefined; - if (line.length === 0 && prevLine && (prevLine.length === 0 || prevLine.startsWith('```'))) { - return; // Avoid more than one empty line in a row. - } - - if (line.startsWith('```')) { - this.endCodeBlock(); - } else { - this.md.push(line); - } - } - - private isIgnorable(line: string): boolean { - if (line.indexOf('generated/') >= 0) { - return true; // Drop generated content. - } - const trimmed = line.trim(); - if (trimmed.startsWith('..') && trimmed.indexOf('::') > 0) { - // Ignore lines likes .. sectionauthor:: John Doe. - return true; - } - return false; - } - - private checkPreContent(lines: string[], i: number): number { - const line = lines[i]; - if (i === 0 || line.trim().length === 0) { - return 0; - } - - if (!isWhiteSpace(line.charCodeAt(0)) && !this.isListItem(line)) { - return 0; // regular line, nothing to do here. - } - // Indented content is considered to be preformatted. - this.startPreformattedBlock(); - return -1; - } - - private handleSectionHeader(lines: string[], i: number): boolean { - const line = lines[i]; - if (i < lines.length - 1 && lines[i + 1].startsWith('===')) { - // Section title -> heading level 3. - this.md.push(`### ${this.cleanup(line)}`); - return true; - } - if (i < lines.length - 1 && lines[i + 1].startsWith('---')) { - // Subsection title -> heading level 4. - this.md.push(`#### ${this.cleanup(line)}`); - return true; - } - return false; - } - - private handleDoubleColon(line: string): boolean { - if (!line.endsWith('::')) { - return false; - } - // Literal blocks begin with `::`. Such as sequence like - // '... as shown below::' that is followed by a preformatted text. - if (line.length > 2 && !line.startsWith('..')) { - // Ignore lines likes .. autosummary:: John Doe. - // Trim trailing : so :: turns into :. - this.md.push(line.substring(0, line.length - 1)); - } - - this.startPreformattedBlock(); - return true; - } - - private startPreformattedBlock(): void { - // Remove previous empty line so we avoid double empties. - this.tryRemovePrecedingEmptyLines(); - // Lie about the language since we don't want preformatted text - // to be colorized as Python. HTML is more 'appropriate' as it does - // not colorize -- or + or keywords like 'from'. - this.md.push('```html'); - this.state = State.Preformatted; - } - - private endPreformattedBlock(): void { - if (this.state === State.Preformatted) { - this.tryRemovePrecedingEmptyLines(); - this.md.push('```'); - this.state = State.Default; - } - } - - private startCodeBlock(): void { - // Remove previous empty line so we avoid double empties. - this.tryRemovePrecedingEmptyLines(); - this.md.push('```python'); - this.state = State.Code; - } - - private endCodeBlock(): void { - if (this.state === State.Code) { - this.tryRemovePrecedingEmptyLines(); - this.md.push('```'); - this.state = State.Default; - } - } - - private tryRemovePrecedingEmptyLines(): void { - while (this.md.length > 0 && this.md[this.md.length - 1].trim().length === 0) { - this.md.pop(); - } - } - - private isListItem(line: string): boolean { - const trimmed = line.trim(); - const ch = trimmed.length > 0 ? trimmed.charCodeAt(0) : 0; - return ch === Char.Asterisk || ch === Char.Hyphen || isDecimal(ch); - } - - private cleanup(line: string): string { - return line.replace(/:mod:/g, 'module:'); - } -} diff --git a/src/client/common/net/browser.ts b/src/client/common/net/browser.ts index 74c629c7d592..115df0f2969c 100644 --- a/src/client/common/net/browser.ts +++ b/src/client/common/net/browser.ts @@ -3,8 +3,6 @@ 'use strict'; -// tslint:disable:no-var-requires - import { injectable } from 'inversify'; import { env, Uri } from 'vscode'; import { IBrowserService } from '../types'; diff --git a/src/client/common/net/fileDownloader.ts b/src/client/common/net/fileDownloader.ts deleted file mode 100644 index 61499d2c28b7..000000000000 --- a/src/client/common/net/fileDownloader.ts +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import * as requestTypes from 'request'; -import { Progress } from 'vscode'; -import { IApplicationShell } from '../application/types'; -import { Octicons } from '../constants'; -import { IFileSystem, WriteStream } from '../platform/types'; -import { DownloadOptions, IFileDownloader, IHttpClient } from '../types'; -import { Http } from '../utils/localize'; -import { noop } from '../utils/misc'; - -@injectable() -export class FileDownloader implements IFileDownloader { - constructor( - @inject(IHttpClient) private readonly httpClient: IHttpClient, - @inject(IFileSystem) private readonly fs: IFileSystem, - @inject(IApplicationShell) private readonly appShell: IApplicationShell - ) {} - public async downloadFile(uri: string, options: DownloadOptions): Promise { - if (options.outputChannel) { - options.outputChannel.appendLine(Http.downloadingFile().format(uri)); - } - const tempFile = await this.fs.createTemporaryFile(options.extension); - - await this.downloadFileWithStatusBarProgress(uri, options.progressMessagePrefix, tempFile.filePath).then( - noop, - (ex) => { - tempFile.dispose(); - return Promise.reject(ex); - } - ); - - return tempFile.filePath; - } - public async downloadFileWithStatusBarProgress( - uri: string, - progressMessage: string, - tmpFilePath: string - ): Promise { - await this.appShell.withProgressCustomIcon(Octicons.Downloading, async (progress) => { - const req = await this.httpClient.downloadFile(uri); - const fileStream = this.fs.createWriteStream(tmpFilePath); - return this.displayDownloadProgress(uri, progress, req, fileStream, progressMessage); - }); - } - - public async displayDownloadProgress( - uri: string, - progress: Progress<{ message?: string; increment?: number }>, - request: requestTypes.Request, - fileStream: WriteStream, - progressMessagePrefix: string - ): Promise { - return new Promise((resolve, reject) => { - request.on('response', (response) => { - if (response.statusCode !== 200) { - reject( - new Error(`Failed with status ${response.statusCode}, ${response.statusMessage}, Uri ${uri}`) - ); - } - }); - // tslint:disable-next-line: no-require-imports - const requestProgress = require('request-progress'); - requestProgress(request) - .on('progress', (state: RequestProgressState) => { - const message = formatProgressMessageWithState(progressMessagePrefix, state); - progress.report({ message }); - }) - // Handle errors from download. - .on('error', reject) - .pipe(fileStream) - // Handle error in writing to fs. - .on('error', reject) - .on('close', resolve); - }); - } -} - -type RequestProgressState = { - percent: number; - speed: number; - size: { - total: number; - transferred: number; - }; - time: { - elapsed: number; - remaining: number; - }; -}; - -function formatProgressMessageWithState(progressMessagePrefix: string, state: RequestProgressState): string { - const received = Math.round(state.size.transferred / 1024); - const total = Math.round(state.size.total / 1024); - const percentage = Math.round(100 * state.percent); - - return Http.downloadingFileProgress().format( - progressMessagePrefix, - received.toString(), - total.toString(), - percentage.toString() - ); -} diff --git a/src/client/common/net/httpClient.ts b/src/client/common/net/httpClient.ts deleted file mode 100644 index adceb9b1e8a3..000000000000 --- a/src/client/common/net/httpClient.ts +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { parse, ParseError } from 'jsonc-parser'; -import type * as requestTypes from 'request'; -import { IHttpClient } from '../../common/types'; -import { IServiceContainer } from '../../ioc/types'; -import { IWorkspaceService } from '../application/types'; -import { traceError } from '../logger'; - -@injectable() -export class HttpClient implements IHttpClient { - public readonly requestOptions: requestTypes.CoreOptions; - constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - const workspaceService = serviceContainer.get(IWorkspaceService); - this.requestOptions = { proxy: workspaceService.getConfiguration('http').get('proxy', '') }; - } - - public async downloadFile(uri: string): Promise { - // tslint:disable-next-line:no-any - const request = ((await import('request')) as any) as typeof requestTypes; - return request(uri, this.requestOptions); - } - - public async getJSON(uri: string, strict: boolean = true): Promise { - const body = await this.getContents(uri); - return this.parseBodyToJSON(body, strict); - } - - public async parseBodyToJSON(body: string, strict: boolean): Promise { - if (strict) { - return JSON.parse(body); - } else { - // tslint:disable-next-line: prefer-const - let errors: ParseError[] = []; - const content = parse(body, errors, { allowTrailingComma: true, disallowComments: false }) as T; - if (errors.length > 0) { - traceError('JSONC parser returned ParseError codes', errors); - } - return content; - } - } - - public async exists(uri: string): Promise { - // tslint:disable-next-line:no-require-imports - const request = require('request') as typeof requestTypes; - return new Promise((resolve) => { - try { - request - .get(uri, this.requestOptions) - .on('response', (response) => resolve(response.statusCode === 200)) - .on('error', () => resolve(false)); - } catch { - resolve(false); - } - }); - } - private async getContents(uri: string): Promise { - // tslint:disable-next-line:no-require-imports - const request = require('request') as typeof requestTypes; - return new Promise((resolve, reject) => { - request(uri, this.requestOptions, (ex, response, body) => { - if (ex) { - return reject(ex); - } - if (response.statusCode !== 200) { - return reject( - new Error(`Failed with status ${response.statusCode}, ${response.statusMessage}, Uri ${uri}`) - ); - } - resolve(body); - }); - }); - } -} diff --git a/src/client/common/net/socket/SocketStream.ts b/src/client/common/net/socket/SocketStream.ts index 0576d3026fb5..b046cdceaf96 100644 --- a/src/client/common/net/socket/SocketStream.ts +++ b/src/client/common/net/socket/SocketStream.ts @@ -1,13 +1,13 @@ 'use strict'; import * as net from 'net'; -// tslint:disable:no-var-requires no-require-imports member-ordering no-any + const uint64be = require('uint64be'); enum DataType { string, int32, - int64 + int64, } export class SocketStream { @@ -26,7 +26,7 @@ export class SocketStream { this.socket.write(buffer); } public WriteString(value: string) { - const stringBuffer = new Buffer(value, 'utf-8'); + const stringBuffer = Buffer.from(value, 'utf-8'); this.WriteInt32(stringBuffer.length); if (stringBuffer.length > 0) { this.socket.write(stringBuffer); @@ -81,7 +81,7 @@ export class SocketStream { this.buffer = additionalData; return; } - const newBuffer = new Buffer(this.buffer.length + additionalData.length); + const newBuffer = Buffer.alloc(this.buffer.length + additionalData.length); this.buffer.copy(newBuffer); additionalData.copy(newBuffer, this.buffer.length); this.buffer = newBuffer; @@ -119,7 +119,7 @@ export class SocketStream { throw new Error('IOException() - Socket.ReadString failed to read string type;'); } - const type = new Buffer([byteRead]).toString(); + const type = Buffer.from([byteRead]).toString(); let isUnicode = false; switch (type) { case 'N': // null string diff --git a/src/client/common/net/socket/socketCallbackHandler.ts b/src/client/common/net/socket/socketCallbackHandler.ts index 55bf49e7b27d..82fe3ec1ae0d 100644 --- a/src/client/common/net/socket/socketCallbackHandler.ts +++ b/src/client/common/net/socket/socketCallbackHandler.ts @@ -1,5 +1,3 @@ -// tslint:disable:quotemark ordered-imports member-ordering one-line prefer-const - 'use strict'; import * as net from 'net'; diff --git a/src/client/common/net/socket/socketServer.ts b/src/client/common/net/socket/socketServer.ts index ed0b37367f42..c0e13a412d7d 100644 --- a/src/client/common/net/socket/socketServer.ts +++ b/src/client/common/net/socket/socketServer.ts @@ -25,7 +25,6 @@ export class SocketServer extends EventEmitter implements ISocketServer { } try { this.socketServer.close(); - // tslint:disable-next-line:no-empty } catch (ex) {} this.socketServer = undefined; } diff --git a/src/client/common/nuget/azureBlobStoreNugetRepository.ts b/src/client/common/nuget/azureBlobStoreNugetRepository.ts deleted file mode 100644 index ae19c65e81be..000000000000 --- a/src/client/common/nuget/azureBlobStoreNugetRepository.ts +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { injectable, unmanaged } from 'inversify'; -import { IServiceContainer } from '../../ioc/types'; -import { captureTelemetry } from '../../telemetry'; -import { EventName } from '../../telemetry/constants'; -import { IWorkspaceService } from '../application/types'; -import { traceDecorators } from '../logger'; -import { Resource } from '../types'; -import { INugetRepository, INugetService, NugetPackage } from './types'; - -@injectable() -export class AzureBlobStoreNugetRepository implements INugetRepository { - constructor( - @unmanaged() private readonly serviceContainer: IServiceContainer, - @unmanaged() protected readonly azureBlobStorageAccount: string, - @unmanaged() protected readonly azureBlobStorageContainer: string, - @unmanaged() protected readonly azureCDNBlobStorageAccount: string, - private getBlobStore: (uri: string) => Promise = _getAZBlobStore - ) {} - - public async getPackages(packageName: string, resource: Resource): Promise { - return this.listPackages( - this.azureBlobStorageAccount, - this.azureBlobStorageContainer, - packageName, - this.azureCDNBlobStorageAccount, - resource - ); - } - - @captureTelemetry(EventName.PYTHON_LANGUAGE_SERVER_LIST_BLOB_STORE_PACKAGES) - @traceDecorators.verbose('Listing Nuget Packages') - protected async listPackages( - azureBlobStorageAccount: string, - azureBlobStorageContainer: string, - packageName: string, - azureCDNBlobStorageAccount: string, - resource: Resource - ) { - const results = await this.listBlobStoreCatalog( - this.fixBlobStoreURI(azureBlobStorageAccount, resource), - azureBlobStorageContainer, - packageName - ); - const nugetService = this.serviceContainer.get(INugetService); - return results.map((item) => { - return { - package: item.name, - uri: `${azureCDNBlobStorageAccount}/${azureBlobStorageContainer}/${item.name}`, - version: nugetService.getVersionFromPackageFileName(item.name) - }; - }); - } - - private async listBlobStoreCatalog( - azureBlobStorageAccount: string, - azureBlobStorageContainer: string, - packageName: string - ): Promise { - const blobStore = await this.getBlobStore(azureBlobStorageAccount); - return new Promise((resolve, reject) => { - // We must pass undefined according to docs, but type definition doesn't all it to be undefined or null!!! - // tslint:disable-next-line:no-any - const token = undefined as any; - blobStore.listBlobsSegmentedWithPrefix(azureBlobStorageContainer, packageName, token, (error, result) => { - if (error) { - return reject(error); - } - resolve(result.entries); - }); - }); - } - private fixBlobStoreURI(uri: string, resource: Resource) { - if (!uri.startsWith('https:')) { - return uri; - } - - const workspace = this.serviceContainer.get(IWorkspaceService); - const cfg = workspace.getConfiguration('http', resource); - if (cfg.get('proxyStrictSSL', true)) { - return uri; - } - - // tslint:disable-next-line:no-http-string - return uri.replace(/^https:/, 'http:'); - } -} - -// The "azure-storage" package is large enough that importing it has -// a significant impact on extension startup time. So we import it -// lazily and deal with the consequences below. - -interface IBlobResult { - name: string; -} - -interface IBlobResults { - entries: IBlobResult[]; -} - -type ErrorOrResult = (error: Error, result: TResult) => void; - -interface IAZBlobStore { - listBlobsSegmentedWithPrefix( - container: string, - prefix: string, - // tslint:disable-next-line:no-any - currentToken: any, - callback: ErrorOrResult - ): void; -} - -async function _getAZBlobStore(uri: string): Promise { - // tslint:disable-next-line:no-require-imports - const az = (await import('azure-storage')) as typeof import('azure-storage'); - return az.createBlobServiceAnonymous(uri); -} diff --git a/src/client/common/nuget/nugetRepository.ts b/src/client/common/nuget/nugetRepository.ts deleted file mode 100644 index 08524d7c3068..000000000000 --- a/src/client/common/nuget/nugetRepository.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { parse, SemVer } from 'semver'; -import { IHttpClient } from '../../common/types'; -import { IServiceContainer } from '../../ioc/types'; -import { INugetRepository, NugetPackage } from './types'; - -const nugetPackageBaseAddress = - 'https://dotnetmyget.blob.core.windows.net/artifacts/dotnet-core-svc/nuget/v3/flatcontainer'; - -@injectable() -export class NugetRepository implements INugetRepository { - constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) {} - public async getPackages(packageName: string): Promise { - const versions = await this.getVersions(nugetPackageBaseAddress, packageName); - return versions.map((version) => { - const uri = this.getNugetPackageUri(nugetPackageBaseAddress, packageName, version); - return { version, uri, package: packageName }; - }); - } - public async getVersions(packageBaseAddress: string, packageName: string): Promise { - const uri = `${packageBaseAddress}/${packageName.toLowerCase().trim()}/index.json`; - const httpClient = this.serviceContainer.get(IHttpClient); - const result = await httpClient.getJSON<{ versions: string[] }>(uri); - return result.versions.map((v) => parse(v, true) || new SemVer('0.0.0')); - } - public getNugetPackageUri(packageBaseAddress: string, packageName: string, version: SemVer): string { - return `${packageBaseAddress}/${packageName}/${version.raw}/${packageName}.${version.raw}.nupkg`; - } -} diff --git a/src/client/common/nuget/nugetService.ts b/src/client/common/nuget/nugetService.ts deleted file mode 100644 index a0a974dfd64d..000000000000 --- a/src/client/common/nuget/nugetService.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { injectable } from 'inversify'; -import * as path from 'path'; -import { parse, SemVer } from 'semver'; -import { INugetService } from './types'; - -@injectable() -export class NugetService implements INugetService { - public isReleaseVersion(version: SemVer): boolean { - return version.prerelease.length === 0; - } - - public getVersionFromPackageFileName(packageName: string): SemVer { - const ext = path.extname(packageName); - const versionWithExt = packageName.substring(packageName.indexOf('.') + 1); - const version = versionWithExt.substring(0, versionWithExt.length - ext.length); - // Take only the first 3 parts. - const parts = version.split('.'); - const semverParts = parts.filter((_, index) => index <= 2).join('.'); - const lastParts = parts.filter((_, index) => index === 3).join('.'); - const suffix = lastParts.length === 0 ? '' : `-${lastParts}`; - const fixedVersion = `${semverParts}${suffix}`; - return parse(fixedVersion, true) || new SemVer('0.0.0'); - } -} diff --git a/src/client/common/nuget/types.ts b/src/client/common/nuget/types.ts deleted file mode 100644 index dcde51025138..000000000000 --- a/src/client/common/nuget/types.ts +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { SemVer } from 'semver'; -import { Resource } from '../types'; -export type NugetPackage = { package: string; version: SemVer; uri: string }; - -export const INugetService = Symbol('INugetService'); -export interface INugetService { - isReleaseVersion(version: SemVer): boolean; - getVersionFromPackageFileName(packageName: string): SemVer; -} - -export const INugetRepository = Symbol('INugetRepository'); -export interface INugetRepository { - getPackages(packageName: string, resource: Resource): Promise; -} diff --git a/src/client/common/open.ts b/src/client/common/open.ts deleted file mode 100644 index 648cfa8029a9..000000000000 --- a/src/client/common/open.ts +++ /dev/null @@ -1,85 +0,0 @@ -'use strict'; - -//https://github.com/sindresorhus/opn/blob/master/index.js -//Modified as this uses target as an argument - -import * as childProcess from 'child_process'; - -// tslint:disable:no-any no-function-expression prefer-template -export function open(opts: any): Promise { - // opts = objectAssign({wait: true}, opts); - if (!opts.hasOwnProperty('wait')) { - (opts).wait = true; - } - - let cmd; - let appArgs = []; - let args: string[] = []; - const cpOpts: any = {}; - if (opts.cwd && typeof opts.cwd === 'string' && opts.cwd.length > 0) { - cpOpts.cwd = opts.cwd; - } - if (opts.env && Object.keys(opts.env).length > 0) { - cpOpts.env = opts.env; - } - - if (Array.isArray(opts.app)) { - appArgs = opts.app.slice(1); - opts.app = opts.app[0]; - } - - if (process.platform === 'darwin') { - const sudoPrefix = opts.sudo === true ? 'sudo ' : ''; - cmd = 'osascript'; - args = [ - '-e', - 'tell application "terminal"', - '-e', - 'activate', - '-e', - 'do script "' + sudoPrefix + [opts.app].concat(appArgs).join(' ') + '"', - '-e', - 'end tell' - ]; - } else if (process.platform === 'win32') { - cmd = 'cmd'; - args.push('/c', 'start'); - - if (opts.wait) { - args.push('/wait'); - } - - if (opts.app) { - args.push(opts.app); - } - - if (appArgs.length > 0) { - args = args.concat(appArgs); - } - } else { - cmd = 'gnome-terminal'; - const sudoPrefix = opts.sudo === true ? 'sudo ' : ''; - args = ['-x', 'sh', '-c', `"${sudoPrefix}${opts.app}" ${appArgs.join(' ')}`]; - } - - const cp = childProcess.spawn(cmd, args, cpOpts); - - if (opts.wait) { - return new Promise(function (resolve, reject) { - cp.once('error', reject); - - cp.once('close', function (code) { - if (code > 0) { - reject(new Error(`Exited with code ${code}`)); - return; - } - - resolve(cp); - }); - }); - } - - cp.unref(); - - return Promise.resolve(cp); -} diff --git a/src/client/common/persistentState.ts b/src/client/common/persistentState.ts index ed03b60ddb77..3f9c17657cf4 100644 --- a/src/client/common/persistentState.ts +++ b/src/client/common/persistentState.ts @@ -3,16 +3,71 @@ 'use strict'; -import { inject, injectable, named } from 'inversify'; +import { inject, injectable, named, optional } from 'inversify'; import { Memento } from 'vscode'; -import { GLOBAL_MEMENTO, IMemento, IPersistentState, IPersistentStateFactory, WORKSPACE_MEMENTO } from './types'; +import { IExtensionSingleActivationService } from '../activation/types'; +import { traceError } from '../logging'; +import { ICommandManager } from './application/types'; +import { Commands } from './constants'; +import { + GLOBAL_MEMENTO, + IExtensionContext, + IMemento, + IPersistentState, + IPersistentStateFactory, + WORKSPACE_MEMENTO, +} from './types'; +import { cache } from './utils/decorators'; +import { noop } from './utils/misc'; +import { clearCacheDirectory } from '../pythonEnvironments/base/locators/common/nativePythonFinder'; +import { clearCache, useEnvExtension } from '../envExt/api.internal'; + +let _workspaceState: Memento | undefined; +const _workspaceKeys: string[] = []; +export function initializePersistentStateForTriggers(context: IExtensionContext) { + _workspaceState = context.workspaceState; +} + +export function getWorkspaceStateValue(key: string, defaultValue?: T): T | undefined { + if (!_workspaceState) { + throw new Error('Workspace state not initialized'); + } + if (defaultValue === undefined) { + return _workspaceState.get(key); + } + return _workspaceState.get(key, defaultValue); +} + +export async function updateWorkspaceStateValue(key: string, value: T): Promise { + if (!_workspaceState) { + throw new Error('Workspace state not initialized'); + } + try { + _workspaceKeys.push(key); + await _workspaceState.update(key, value); + const after = getWorkspaceStateValue(key); + if (JSON.stringify(after) !== JSON.stringify(value)) { + await _workspaceState.update(key, undefined); + await _workspaceState.update(key, value); + traceError('Error while updating workspace state for key:', key); + } + } catch (ex) { + traceError(`Error while updating workspace state for key [${key}]:`, ex); + } +} + +async function clearWorkspaceState(): Promise { + if (_workspaceState !== undefined) { + await Promise.all(_workspaceKeys.map((key) => updateWorkspaceStateValue(key, undefined))); + } +} export class PersistentState implements IPersistentState { constructor( - private storage: Memento, + public readonly storage: Memento, private key: string, private defaultValue?: T, - private expiryDurationMs?: number + private expiryDurationMs?: number, ) {} public get value(): T { @@ -28,33 +83,156 @@ export class PersistentState implements IPersistentState { } } - public async updateValue(newValue: T): Promise { - if (this.expiryDurationMs) { - await this.storage.update(this.key, { data: newValue, expiry: Date.now() + this.expiryDurationMs }); - } else { - await this.storage.update(this.key, newValue); + public async updateValue(newValue: T, retryOnce = true): Promise { + try { + if (this.expiryDurationMs) { + await this.storage.update(this.key, { data: newValue, expiry: Date.now() + this.expiryDurationMs }); + } else { + await this.storage.update(this.key, newValue); + } + if (retryOnce && JSON.stringify(this.value) != JSON.stringify(newValue)) { + // Due to a VSCode bug sometimes the changes are not reflected in the storage, atleast not immediately. + // It is noticed however that if we reset the storage first and then update it, it works. + // https://github.com/microsoft/vscode/issues/171827 + await this.updateValue(undefined as any, false); + await this.updateValue(newValue, false); + } + } catch (ex) { + traceError('Error while updating storage for key:', this.key, ex); } } } +export const GLOBAL_PERSISTENT_KEYS_DEPRECATED = 'PYTHON_EXTENSION_GLOBAL_STORAGE_KEYS'; +export const WORKSPACE_PERSISTENT_KEYS_DEPRECATED = 'PYTHON_EXTENSION_WORKSPACE_STORAGE_KEYS'; + +export const GLOBAL_PERSISTENT_KEYS = 'PYTHON_GLOBAL_STORAGE_KEYS'; +const WORKSPACE_PERSISTENT_KEYS = 'PYTHON_WORKSPACE_STORAGE_KEYS'; +type KeysStorageType = 'global' | 'workspace'; +export type KeysStorage = { key: string; defaultValue: unknown }; + @injectable() -export class PersistentStateFactory implements IPersistentStateFactory { +export class PersistentStateFactory implements IPersistentStateFactory, IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; + public readonly _globalKeysStorage = new PersistentState( + this.globalState, + GLOBAL_PERSISTENT_KEYS, + [], + ); + public readonly _workspaceKeysStorage = new PersistentState( + this.workspaceState, + WORKSPACE_PERSISTENT_KEYS, + [], + ); constructor( @inject(IMemento) @named(GLOBAL_MEMENTO) private globalState: Memento, - @inject(IMemento) @named(WORKSPACE_MEMENTO) private workspaceState: Memento + @inject(IMemento) @named(WORKSPACE_MEMENTO) private workspaceState: Memento, + @inject(ICommandManager) private cmdManager?: ICommandManager, + @inject(IExtensionContext) @optional() private context?: IExtensionContext, ) {} + + public async activate(): Promise { + this.cmdManager?.registerCommand(Commands.ClearStorage, async () => { + await clearWorkspaceState(); + await this.cleanAllPersistentStates(); + if (useEnvExtension()) { + await clearCache(); + } + }); + const globalKeysStorageDeprecated = this.createGlobalPersistentState(GLOBAL_PERSISTENT_KEYS_DEPRECATED, []); + const workspaceKeysStorageDeprecated = this.createWorkspacePersistentState( + WORKSPACE_PERSISTENT_KEYS_DEPRECATED, + [], + ); + // Old storages have grown to be unusually large due to https://github.com/microsoft/vscode-python/issues/17488, + // so reset them. This line can be removed after a while. + if (globalKeysStorageDeprecated.value.length > 0) { + globalKeysStorageDeprecated.updateValue([]).ignoreErrors(); + } + if (workspaceKeysStorageDeprecated.value.length > 0) { + workspaceKeysStorageDeprecated.updateValue([]).ignoreErrors(); + } + } + public createGlobalPersistentState( key: string, defaultValue?: T, - expiryDurationMs?: number + expiryDurationMs?: number, ): IPersistentState { + this.addKeyToStorage('global', key, defaultValue).ignoreErrors(); return new PersistentState(this.globalState, key, defaultValue, expiryDurationMs); } + public createWorkspacePersistentState( key: string, defaultValue?: T, - expiryDurationMs?: number + expiryDurationMs?: number, ): IPersistentState { + this.addKeyToStorage('workspace', key, defaultValue).ignoreErrors(); return new PersistentState(this.workspaceState, key, defaultValue, expiryDurationMs); } + + /** + * Note we use a decorator to cache the promise returned by this method, so it's only called once. + * It is only cached for the particular arguments passed, so the argument type is simplified here. + */ + @cache(-1, true) + private async addKeyToStorage(keyStorageType: KeysStorageType, key: string, defaultValue?: T) { + const storage = keyStorageType === 'global' ? this._globalKeysStorage : this._workspaceKeysStorage; + const found = storage.value.find((value) => value.key === key); + if (!found) { + await storage.updateValue([{ key, defaultValue }, ...storage.value]); + } + } + + private async cleanAllPersistentStates(): Promise { + const clearCacheDirPromise = this.context ? clearCacheDirectory(this.context).catch() : Promise.resolve(); + await Promise.all( + this._globalKeysStorage.value.map(async (keyContent) => { + const storage = this.createGlobalPersistentState(keyContent.key); + await storage.updateValue(keyContent.defaultValue); + }), + ); + await Promise.all( + this._workspaceKeysStorage.value.map(async (keyContent) => { + const storage = this.createWorkspacePersistentState(keyContent.key); + await storage.updateValue(keyContent.defaultValue); + }), + ); + await this._globalKeysStorage.updateValue([]); + await this._workspaceKeysStorage.updateValue([]); + await clearCacheDirPromise; + this.cmdManager?.executeCommand('workbench.action.reloadWindow').then(noop); + } +} + +///////////////////////////// +// a simpler, alternate API +// for components to use + +export interface IPersistentStorage { + get(): T; + set(value: T): Promise; +} + +/** + * Build a global storage object for the given key. + */ +export function getGlobalStorage(context: IExtensionContext, key: string, defaultValue?: T): IPersistentStorage { + const globalKeysStorage = new PersistentState(context.globalState, GLOBAL_PERSISTENT_KEYS, []); + const found = globalKeysStorage.value.find((value) => value.key === key); + if (!found) { + const newValue = [{ key, defaultValue }, ...globalKeysStorage.value]; + globalKeysStorage.updateValue(newValue).ignoreErrors(); + } + const raw = new PersistentState(context.globalState, key, defaultValue); + return { + // We adapt between PersistentState and IPersistentStorage. + get() { + return raw.value; + }, + set(value: T) { + return raw.updateValue(value); + }, + }; } diff --git a/src/client/common/pipes/namedPipes.ts b/src/client/common/pipes/namedPipes.ts new file mode 100644 index 000000000000..9bffe78f2b9f --- /dev/null +++ b/src/client/common/pipes/namedPipes.ts @@ -0,0 +1,200 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as cp from 'child_process'; +import * as crypto from 'crypto'; +import * as fs from 'fs-extra'; +import * as net from 'net'; +import * as os from 'os'; +import * as path from 'path'; +import * as rpc from 'vscode-jsonrpc/node'; +import { CancellationError, CancellationToken, Disposable } from 'vscode'; +import { traceVerbose } from '../../logging'; +import { isWindows } from '../utils/platform'; +import { createDeferred } from '../utils/async'; +import { noop } from '../utils/misc'; + +const { XDG_RUNTIME_DIR } = process.env; +export function generateRandomPipeName(prefix: string): string { + // length of 10 picked because of the name length restriction for sockets + const randomSuffix = crypto.randomBytes(10).toString('hex'); + if (prefix.length === 0) { + prefix = 'python-ext-rpc'; + } + + if (process.platform === 'win32') { + return `\\\\.\\pipe\\${prefix}-${randomSuffix}`; + } + + let result; + if (XDG_RUNTIME_DIR) { + result = path.join(XDG_RUNTIME_DIR, `${prefix}-${randomSuffix}`); + } else { + result = path.join(os.tmpdir(), `${prefix}-${randomSuffix}`); + } + + return result; +} + +async function mkfifo(fifoPath: string): Promise { + return new Promise((resolve, reject) => { + const proc = cp.spawn('mkfifo', [fifoPath]); + proc.on('error', (err) => { + reject(err); + }); + proc.on('exit', (code) => { + if (code === 0) { + resolve(); + } + }); + }); +} + +export async function createWriterPipe(pipeName: string, token?: CancellationToken): Promise { + // windows implementation of FIFO using named pipes + if (isWindows()) { + const deferred = createDeferred(); + const server = net.createServer((socket) => { + traceVerbose(`Pipe connected: ${pipeName}`); + server.close(); + deferred.resolve(new rpc.SocketMessageWriter(socket, 'utf-8')); + }); + + server.on('error', deferred.reject); + server.listen(pipeName); + if (token) { + token.onCancellationRequested(() => { + if (server.listening) { + server.close(); + } + deferred.reject(new CancellationError()); + }); + } + return deferred.promise; + } + // linux implementation of FIFO + await mkfifo(pipeName); + try { + await fs.chmod(pipeName, 0o666); + } catch { + // Intentionally ignored + } + const writer = fs.createWriteStream(pipeName, { + encoding: 'utf-8', + }); + return new rpc.StreamMessageWriter(writer, 'utf-8'); +} + +class CombinedReader implements rpc.MessageReader { + private _onError = new rpc.Emitter(); + + private _onClose = new rpc.Emitter(); + + private _onPartialMessage = new rpc.Emitter(); + + // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-empty-function + private _callback: rpc.DataCallback = () => {}; + + private _disposables: rpc.Disposable[] = []; + + private _readers: rpc.MessageReader[] = []; + + constructor() { + this._disposables.push(this._onClose, this._onError, this._onPartialMessage); + } + + onError: rpc.Event = this._onError.event; + + onClose: rpc.Event = this._onClose.event; + + onPartialMessage: rpc.Event = this._onPartialMessage.event; + + listen(callback: rpc.DataCallback): rpc.Disposable { + this._callback = callback; + // eslint-disable-next-line no-return-assign, @typescript-eslint/no-empty-function + return new Disposable(() => (this._callback = () => {})); + } + + add(reader: rpc.MessageReader): void { + this._readers.push(reader); + reader.listen((msg) => { + this._callback(msg as rpc.NotificationMessage); + }); + this._disposables.push(reader); + reader.onClose(() => { + this.remove(reader); + if (this._readers.length === 0) { + this._onClose.fire(); + } + }); + reader.onError((e) => { + this.remove(reader); + this._onError.fire(e); + }); + } + + remove(reader: rpc.MessageReader): void { + const found = this._readers.find((r) => r === reader); + if (found) { + this._readers = this._readers.filter((r) => r !== reader); + reader.dispose(); + } + } + + dispose(): void { + this._readers.forEach((r) => r.dispose()); + this._readers = []; + this._disposables.forEach((disposable) => disposable.dispose()); + this._disposables = []; + } +} + +export async function createReaderPipe(pipeName: string, token?: CancellationToken): Promise { + if (isWindows()) { + // windows implementation of FIFO using named pipes + const deferred = createDeferred(); + const combined = new CombinedReader(); + + let refs = 0; + const server = net.createServer((socket) => { + traceVerbose(`Pipe connected: ${pipeName}`); + refs += 1; + + socket.on('close', () => { + refs -= 1; + if (refs <= 0) { + server.close(); + } + }); + combined.add(new rpc.SocketMessageReader(socket, 'utf-8')); + }); + server.on('error', deferred.reject); + server.listen(pipeName); + if (token) { + token.onCancellationRequested(() => { + if (server.listening) { + server.close(); + } + deferred.reject(new CancellationError()); + }); + } + deferred.resolve(combined); + return deferred.promise; + } + // mac/linux implementation of FIFO + await mkfifo(pipeName); + try { + await fs.chmod(pipeName, 0o666); + } catch { + // Intentionally ignored + } + const fd = await fs.open(pipeName, fs.constants.O_RDONLY | fs.constants.O_NONBLOCK); + const socket = new net.Socket({ fd }); + const reader = new rpc.SocketMessageReader(socket, 'utf-8'); + socket.on('close', () => { + fs.close(fd).catch(noop); + reader.dispose(); + }); + + return reader; +} diff --git a/src/client/common/platform/constants.ts b/src/client/common/platform/constants.ts deleted file mode 100644 index 2c4a65f80251..000000000000 --- a/src/client/common/platform/constants.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// tslint:disable-next-line:no-suspicious-comment -// TODO : Drop all these in favor of IPlatformService. -// See https://github.com/microsoft/vscode-python/issues/8542. - -export const WINDOWS_PATH_VARIABLE_NAME = 'Path'; -export const NON_WINDOWS_PATH_VARIABLE_NAME = 'PATH'; -export const IS_WINDOWS = /^win/.test(process.platform); diff --git a/src/client/common/platform/errors.ts b/src/client/common/platform/errors.ts index 8d3fe1b6bbe3..9533f1bca50c 100644 --- a/src/client/common/platform/errors.ts +++ b/src/client/common/platform/errors.ts @@ -42,7 +42,7 @@ namespace vscErrors { FILE_EXISTS, IS_DIR, NOT_DIR, - NO_PERM + NO_PERM, ]; function errorMatches(err: Error, expectedName: string): boolean | undefined { if (!known.includes(err.name)) { @@ -97,21 +97,23 @@ function isSystemError(err: Error, expectedCode: string): boolean | undefined { } // Return true if the given error is ENOENT. -export function isFileNotFoundError(err: Error): boolean | undefined { - const matched = vscErrors.isFileNotFound(err); +export function isFileNotFoundError(err: unknown | Error): boolean | undefined { + const error = err as Error; + const matched = vscErrors.isFileNotFound(error); if (matched !== undefined) { return matched; } - return isSystemError(err, 'ENOENT'); + return isSystemError(error, 'ENOENT'); } // Return true if the given error is EEXIST. -export function isFileExistsError(err: Error): boolean | undefined { - const matched = vscErrors.isFileExists(err); +export function isFileExistsError(err: unknown | Error): boolean | undefined { + const error = err as Error; + const matched = vscErrors.isFileExists(error); if (matched !== undefined) { return matched; } - return isSystemError(err, 'EEXIST'); + return isSystemError(error, 'EEXIST'); } // Return true if the given error is EISDIR. @@ -133,15 +135,11 @@ export function isNotDirError(err: Error): boolean | undefined { } // Return true if the given error is EACCES. -export function isNoPermissionsError(err: Error): boolean | undefined { - const matched = vscErrors.isNoPermissions(err); +export function isNoPermissionsError(err: unknown | Error): boolean | undefined { + const error = err as Error; + const matched = vscErrors.isNoPermissions(error); if (matched !== undefined) { return matched; } - return isSystemError(err, 'EACCES'); -} - -// Return true if the given error is ENOTEMPTY. -export function isDirNotEmptyError(err: Error): boolean | undefined { - return isSystemError(err, 'ENOTEMPTY'); + return isSystemError(error, 'EACCES'); } diff --git a/src/client/common/platform/fileSystem.ts b/src/client/common/platform/fileSystem.ts index c79075788416..3e7f441654ec 100644 --- a/src/client/common/platform/fileSystem.ts +++ b/src/client/common/platform/fileSystem.ts @@ -1,8 +1,8 @@ +/* eslint-disable max-classes-per-file */ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -'use strict'; -// tslint:disable:no-suspicious-comment +'use strict'; import { createHash } from 'crypto'; import * as fs from 'fs-extra'; @@ -10,8 +10,9 @@ import * as glob from 'glob'; import { injectable } from 'inversify'; import { promisify } from 'util'; import * as vscode from 'vscode'; -import '../../common/extensions'; -import { traceError } from '../logger'; +import { traceError } from '../../logging'; +import '../extensions'; +import { convertFileType } from '../utils/filesystem'; import { createDirNotEmptyError, isFileExistsError, isFileNotFoundError, isNoPermissionsError } from './errors'; import { FileSystemPaths, FileSystemPathUtils } from './fs-paths'; import { TemporaryFileSystem } from './fs-temp'; @@ -26,27 +27,10 @@ import { ITempFileSystem, ReadStream, TemporaryFile, - WriteStream + WriteStream, } from './types'; -const ENCODING: string = 'utf8'; - -// This helper function determines the file type of the given stats -// object. The type follows the convention of node's fs module, where -// a file has exactly one type. Symlinks are not resolved. -export function convertFileType(stat: fs.Stats): FileType { - if (stat.isFile()) { - return FileType.File; - } else if (stat.isDirectory()) { - return FileType.Directory; - } else if (stat.isSymbolicLink()) { - // The caller is responsible for combining this ("logical or") - // with File or Directory as necessary. - return FileType.SymbolicLink; - } else { - return FileType.Unknown; - } -} +const ENCODING = 'utf8'; export function convertStat(old: fs.Stats, filetype: FileType): FileStat { return { @@ -57,27 +41,25 @@ export function convertStat(old: fs.Stats, filetype: FileType): FileStat { // for now we round to the nearest integer. // See: https://github.com/microsoft/vscode/issues/84526 ctime: Math.round(old.ctimeMs), - mtime: Math.round(old.mtimeMs) + mtime: Math.round(old.mtimeMs), }; } function filterByFileType( files: [string, FileType][], // the files to filter - fileType: FileType // the file type to look for + fileType: FileType, // the file type to look for ): [string, FileType][] { // We preserve the pre-existing behavior of following symlinks. if (fileType === FileType.Unknown) { // FileType.Unknown == 0 so we can't just use bitwise // operations blindly here. - return files.filter(([_file, ft]) => { - return ft === FileType.Unknown || ft === (FileType.SymbolicLink & FileType.Unknown); - }); - } else { - return files.filter(([_file, ft]) => (ft & fileType) > 0); + return files.filter( + ([_file, ft]) => ft === FileType.Unknown || ft === (FileType.SymbolicLink & FileType.Unknown), + ); } + return files.filter(([_file, ft]) => (ft & fileType) > 0); } -//========================================== // "raw" filesystem // This is the parts of the vscode.workspace.fs API that we use here. @@ -98,7 +80,7 @@ interface IVSCodeFileSystemAPI { interface IRawFSExtra { lstat(filename: string): Promise; chmod(filePath: string, mode: string | number): Promise; - appendFile(filename: string, data: {}): Promise; + appendFile(filename: string, data: unknown): Promise; // non-async lstatSync(filename: string): fs.Stats; @@ -106,6 +88,7 @@ interface IRawFSExtra { readFileSync(path: string, encoding: string): string; createReadStream(filename: string): ReadStream; createWriteStream(filename: string): WriteStream; + pathExists(filename: string): Promise; } interface IRawPath { @@ -124,14 +107,14 @@ export class RawFileSystem implements IRawFileSystem { // the VS Code FS API to use protected readonly vscfs: IVSCodeFileSystemAPI, // the node FS API to use - protected readonly fsExtra: IRawFSExtra + protected readonly fsExtra: IRawFSExtra, ) {} // Create a new object using common-case default values. public static withDefaults( paths?: IRawPath, // default: a new FileSystemPaths object (using defaults) vscfs?: IVSCodeFileSystemAPI, // default: the actual "vscode.workspace.fs" namespace - fsExtra?: IRawFSExtra // default: the "fs-extra" module + fsExtra?: IRawFSExtra, // default: the "fs-extra" module ): RawFileSystem { return new RawFileSystem( paths || FileSystemPaths.withDefaults(), @@ -139,10 +122,14 @@ export class RawFileSystem implements IRawFileSystem { // The "fs-extra" module is effectively equivalent to node's "fs" // module (but is a bit more async-friendly). So we use that // instead of "fs". - fsExtra || fs + fsExtra || (fs as IRawFSExtra), ); } + public async pathExists(filename: string): Promise { + return this.fsExtra.pathExists(filename); + } + public async stat(filename: string): Promise { // Note that, prior to the November release of VS Code, // stat.ctime was always 0. @@ -229,7 +216,7 @@ export class RawFileSystem implements IRawFileSystem { // See: https://github.com/microsoft/vscode/issues/84177 await this.vscfs.stat(vscode.Uri.file(this.paths.dirname(dest))); await this.vscfs.copy(srcURI, destURI, { - overwrite: true + overwrite: true, }); } @@ -237,7 +224,7 @@ export class RawFileSystem implements IRawFileSystem { const uri = vscode.Uri.file(filename); return this.vscfs.delete(uri, { recursive: false, - useTrash: false + useTrash: false, }); } @@ -251,7 +238,7 @@ export class RawFileSystem implements IRawFileSystem { } return this.vscfs.delete(uri, { recursive: true, - useTrash: false + useTrash: false, }); } @@ -264,7 +251,7 @@ export class RawFileSystem implements IRawFileSystem { await this.vscfs.stat(uri); return this.vscfs.delete(uri, { recursive: true, - useTrash: false + useTrash: false, }); } @@ -282,7 +269,6 @@ export class RawFileSystem implements IRawFileSystem { }); } - //**************************** // non-async // VS Code has decided to never support any sync functions (aside @@ -319,7 +305,6 @@ export class RawFileSystem implements IRawFileSystem { } } -//========================================== // filesystem "utils" // High-level filesystem operations used by the extension. @@ -330,15 +315,16 @@ export class FileSystemUtils implements IFileSystemUtils { public readonly paths: IFileSystemPaths, public readonly tmp: ITempFileSystem, private readonly getHash: (data: string) => string, - private readonly globFiles: (pat: string, options?: { cwd: string; dot?: boolean }) => Promise + private readonly globFiles: (pat: string, options?: { cwd: string; dot?: boolean }) => Promise, ) {} + // Create a new object using common-case default values. public static withDefaults( raw?: IRawFileSystem, pathUtils?: IFileSystemPathUtils, tmp?: ITempFileSystem, getHash?: (data: string) => string, - globFiles?: (pat: string, options?: { cwd: string }) => Promise + globFiles?: (pat: string, options?: { cwd: string }) => Promise, ): FileSystemUtils { pathUtils = pathUtils || FileSystemPathUtils.withDefaults(); return new FileSystemUtils( @@ -347,11 +333,10 @@ export class FileSystemUtils implements IFileSystemUtils { pathUtils.paths, tmp || TemporaryFileSystem.withDefaults(), getHash || getHashString, - globFiles || promisify(glob) + globFiles || promisify(glob.default), ); } - //**************************** // aliases public async createDirectory(directoryPath: string): Promise { @@ -366,7 +351,6 @@ export class FileSystemUtils implements IFileSystemUtils { return this.raw.rmfile(filename); } - //**************************** // helpers public async pathExists( @@ -374,8 +358,12 @@ export class FileSystemUtils implements IFileSystemUtils { filename: string, // the file type to expect; if not provided then any file type // matches; otherwise a mismatch results in a "false" value - fileType?: FileType + fileType?: FileType, ): Promise { + if (fileType === undefined) { + // Do not need to run stat if not asking for file type. + return this.raw.pathExists(filename); + } let stat: FileStat; try { // Note that we are using stat() rather than lstat(). This @@ -389,18 +377,17 @@ export class FileSystemUtils implements IFileSystemUtils { return false; } - if (fileType === undefined) { - return true; - } if (fileType === FileType.Unknown) { // FileType.Unknown == 0, hence do not use bitwise operations. return stat.type === FileType.Unknown; } return (stat.type & fileType) === fileType; } + public async fileExists(filename: string): Promise { return this.pathExists(filename, FileType.File); } + public async directoryExists(dirname: string): Promise { return this.pathExists(dirname, FileType.Directory); } @@ -416,11 +403,13 @@ export class FileSystemUtils implements IFileSystemUtils { throw err; // re-throw } } + public async getSubDirectories(dirname: string): Promise { const files = await this.listdir(dirname); const filtered = filterByFileType(files, FileType.Directory); return filtered.map(([filename, _fileType]) => filename); } + public async getFiles(dirname: string): Promise { // Note that only "regular" files are returned. const files = await this.listdir(dirname); @@ -454,7 +443,7 @@ export class FileSystemUtils implements IFileSystemUtils { } public async search(globPattern: string, cwd?: string, dot?: boolean): Promise { - // tslint:disable-next-line: no-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any let options: any; if (cwd) { options = { ...options, cwd }; @@ -467,7 +456,6 @@ export class FileSystemUtils implements IFileSystemUtils { return Array.isArray(found) ? found : []; } - //**************************** // helpers (non-async) public fileExistsSync(filePath: string): boolean { @@ -483,15 +471,12 @@ export class FileSystemUtils implements IFileSystemUtils { } } -// We *could* use ICryptoUtils, but it's a bit overkill, issue tracked -// in https://github.com/microsoft/vscode-python/issues/8438. export function getHashString(data: string): string { const hash = createHash('sha512'); hash.update(data); return hash.digest('hex'); } -//========================================== // legacy filesystem API // more aliases (to cause less churn) @@ -500,6 +485,7 @@ export class FileSystem implements IFileSystem { // We expose this for the sake of functional tests that do not have // access to the actual "vscode" namespace. protected utils: FileSystemUtils; + constructor() { this.utils = FileSystemUtils.withDefaults(); } @@ -507,81 +493,112 @@ export class FileSystem implements IFileSystem { public get directorySeparatorChar(): string { return this.utils.paths.sep; } + public arePathsSame(path1: string, path2: string): boolean { return this.utils.pathUtils.arePathsSame(path1, path2); } + public getDisplayName(path: string): string { return this.utils.pathUtils.getDisplayName(path); } + public async stat(filename: string): Promise { return this.utils.raw.stat(filename); } + public async createDirectory(dirname: string): Promise { return this.utils.createDirectory(dirname); } + public async deleteDirectory(dirname: string): Promise { return this.utils.deleteDirectory(dirname); } + public async listdir(dirname: string): Promise<[string, FileType][]> { return this.utils.listdir(dirname); } + public async readFile(filePath: string): Promise { return this.utils.raw.readText(filePath); } + public async readData(filePath: string): Promise { return this.utils.raw.readData(filePath); } - public async writeFile(filename: string, data: {}): Promise { + + // eslint-disable-next-line @typescript-eslint/ban-types + public async writeFile(filename: string, data: string | Buffer): Promise { return this.utils.raw.writeText(filename, data); } + public async appendFile(filename: string, text: string): Promise { return this.utils.raw.appendText(filename, text); } + public async copyFile(src: string, dest: string): Promise { return this.utils.raw.copyFile(src, dest); } + public async deleteFile(filename: string): Promise { return this.utils.deleteFile(filename); } + public async chmod(filename: string, mode: string): Promise { return this.utils.raw.chmod(filename, mode); } - public async move(src: string, tgt: string) { + + public async move(src: string, tgt: string): Promise { await this.utils.raw.move(src, tgt); } + public readFileSync(filePath: string): string { return this.utils.raw.readTextSync(filePath); } + public createReadStream(filePath: string): ReadStream { return this.utils.raw.createReadStream(filePath); } + public createWriteStream(filePath: string): WriteStream { return this.utils.raw.createWriteStream(filePath); } + public async fileExists(filename: string): Promise { return this.utils.fileExists(filename); } + + public pathExists(filename: string): Promise { + return this.utils.pathExists(filename); + } + public fileExistsSync(filename: string): boolean { return this.utils.fileExistsSync(filename); } + public async directoryExists(dirname: string): Promise { return this.utils.directoryExists(dirname); } + public async getSubDirectories(dirname: string): Promise { return this.utils.getSubDirectories(dirname); } + public async getFiles(dirname: string): Promise { return this.utils.getFiles(dirname); } + public async getFileHash(filename: string): Promise { return this.utils.getFileHash(filename); } + public async search(globPattern: string, cwd?: string, dot?: boolean): Promise { return this.utils.search(globPattern, cwd, dot); } + public async createTemporaryFile(suffix: string, mode?: number): Promise { return this.utils.tmp.createFile(suffix, mode); } + public async isDirReadonly(dirname: string): Promise { return this.utils.isDirReadonly(dirname); } diff --git a/src/client/common/platform/fileSystemWatcher.ts b/src/client/common/platform/fileSystemWatcher.ts new file mode 100644 index 000000000000..ef35988d147b --- /dev/null +++ b/src/client/common/platform/fileSystemWatcher.ts @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { RelativePattern, workspace } from 'vscode'; +import { traceVerbose } from '../../logging'; +import { IDisposable } from '../types'; +import { Disposables } from '../utils/resourceLifecycle'; + +/** + * Enumeration of file change types. + */ +export enum FileChangeType { + Changed = 'changed', + Created = 'created', + Deleted = 'deleted', +} + +export function watchLocationForPattern( + baseDir: string, + pattern: string, + callback: (type: FileChangeType, absPath: string) => void, +): IDisposable { + const globPattern = new RelativePattern(baseDir, pattern); + const disposables = new Disposables(); + traceVerbose(`Start watching: ${baseDir} with pattern ${pattern} using VSCode API`); + const watcher = workspace.createFileSystemWatcher(globPattern); + disposables.push(watcher.onDidCreate((e) => callback(FileChangeType.Created, e.fsPath))); + disposables.push(watcher.onDidChange((e) => callback(FileChangeType.Changed, e.fsPath))); + disposables.push(watcher.onDidDelete((e) => callback(FileChangeType.Deleted, e.fsPath))); + return disposables; +} diff --git a/src/client/common/platform/fs-paths.ts b/src/client/common/platform/fs-paths.ts index c2d953dcca87..fa809d31b0b9 100644 --- a/src/client/common/platform/fs-paths.ts +++ b/src/client/common/platform/fs-paths.ts @@ -2,10 +2,11 @@ // Licensed under the MIT License. import * as nodepath from 'path'; +import { getSearchPathEnvVarNames } from '../utils/exec'; +import * as fs from 'fs-extra'; +import * as os from 'os'; import { getOSType, OSType } from '../utils/platform'; import { IExecutables, IFileSystemPaths, IFileSystemPathUtils } from './types'; -// tslint:disable-next-line:no-var-requires no-require-imports -const untildify = require('untildify'); // The parts of node's 'path' module used by FileSystemPaths. interface INodePath { @@ -21,14 +22,14 @@ export class FileSystemPaths implements IFileSystemPaths { // "true" if targeting a case-insensitive host (like Windows) private readonly isCaseInsensitive: boolean, // (effectively) the node "path" module to use - private readonly raw: INodePath + private readonly raw: INodePath, ) {} // Create a new object using common-case default values. // We do not use an alternate constructor because defaults in the // constructor runs counter to our typical approach. public static withDefaults( // default: use "isWindows" - isCaseInsensitive?: boolean + isCaseInsensitive?: boolean, ): FileSystemPaths { if (isCaseInsensitive === undefined) { isCaseInsensitive = getOSType() === OSType.Windows; @@ -36,7 +37,7 @@ export class FileSystemPaths implements IFileSystemPaths { return new FileSystemPaths( isCaseInsensitive, // Use the actual node "path" module. - nodepath + nodepath, ); } @@ -71,7 +72,7 @@ export class Executables { // the $PATH delimiter to use public readonly delimiter: string, // the OS type to target - private readonly osType: OSType + private readonly osType: OSType, ) {} // Create a new object using common-case default values. // We do not use an alternate constructor because defaults in the @@ -81,12 +82,12 @@ export class Executables { // Use node's value. nodepath.delimiter, // Use the current OS. - getOSType() + getOSType(), ); } public get envVar(): string { - return this.osType === OSType.Windows ? 'Path' : 'PATH'; + return getSearchPathEnvVarNames(this.osType)[0]; } } @@ -104,25 +105,25 @@ export class FileSystemPathUtils implements IFileSystemPathUtils { // the low-level OS "executables" to use (and expose) public readonly executables: IExecutables, // other low-level FS path operations to use - private readonly raw: IRawPaths + private readonly raw: IRawPaths, ) {} // Create a new object using common-case default values. // We do not use an alternate constructor because defaults in the // constructor runs counter to our typical approach. public static withDefaults( // default: a new FileSystemPaths object (using defaults) - paths?: IFileSystemPaths + paths?: IFileSystemPaths, ): FileSystemPathUtils { if (paths === undefined) { paths = FileSystemPaths.withDefaults(); } return new FileSystemPathUtils( // Use the current user's home directory. - untildify('~'), + os.homedir(), paths, Executables.withDefaults(), // Use the actual node "path" module. - nodepath + nodepath, ); } @@ -133,12 +134,237 @@ export class FileSystemPathUtils implements IFileSystemPathUtils { } public getDisplayName(filename: string, cwd?: string): string { - if (cwd && filename.startsWith(cwd)) { + if (cwd && isParentPath(filename, cwd)) { return `.${this.paths.sep}${this.raw.relative(cwd, filename)}`; - } else if (filename.startsWith(this.home)) { + } else if (isParentPath(filename, this.home)) { return `~${this.paths.sep}${this.raw.relative(this.home, filename)}`; } else { return filename; } } } + +export function normCasePath(filePath: string): string { + return normCase(nodepath.normalize(filePath)); +} + +export function normCase(s: string): string { + return getOSType() === OSType.Windows ? s.toUpperCase() : s; +} + +/** + * Returns true if given file path exists within the given parent directory, false otherwise. + * @param filePath File path to check for + * @param parentPath The potential parent path to check for + */ +export function isParentPath(filePath: string, parentPath: string): boolean { + if (!parentPath.endsWith(nodepath.sep)) { + parentPath += nodepath.sep; + } + if (!filePath.endsWith(nodepath.sep)) { + filePath += nodepath.sep; + } + return normCasePath(filePath).startsWith(normCasePath(parentPath)); +} + +export function arePathsSame(path1: string, path2: string): boolean { + return normCasePath(path1) === normCasePath(path2); +} + +export async function copyFile(src: string, dest: string): Promise { + const destDir = nodepath.dirname(dest); + if (!(await fs.pathExists(destDir))) { + await fs.mkdirp(destDir); + } + + await fs.copy(src, dest, { + overwrite: true, + }); +} + +// These function exist so we can stub them out in tests. We can't stub out the fs module directly +// because of the way that sinon does stubbing, so we have these intermediaries instead. +export { Stats, WriteStream, ReadStream, PathLike, Dirent, PathOrFileDescriptor } from 'fs-extra'; + +export function existsSync(path: string): boolean { + return fs.existsSync(path); +} + +export function readFileSync(filePath: string, encoding: BufferEncoding): string; +export function readFileSync(filePath: string): Buffer; +export function readFileSync(filePath: string, options: { encoding: BufferEncoding }): string; +export function readFileSync( + filePath: string, + options?: { encoding: BufferEncoding } | BufferEncoding | undefined, +): string | Buffer { + if (typeof options === 'string') { + return fs.readFileSync(filePath, { encoding: options }); + } + return fs.readFileSync(filePath, options); +} + +export function readJSONSync(filePath: string): any { + return fs.readJSONSync(filePath); +} + +export function readdirSync(path: string): string[]; +export function readdirSync( + path: string, + options: fs.ObjectEncodingOptions & { + withFileTypes: true; + }, +): fs.Dirent[]; +export function readdirSync( + path: string, + options: fs.ObjectEncodingOptions & { + withFileTypes: false; + }, +): string[]; +export function readdirSync( + path: fs.PathLike, + options?: fs.ObjectEncodingOptions & { + withFileTypes: boolean; + recursive?: boolean | undefined; + }, +): string[] | fs.Dirent[] { + if (options === undefined || options.withFileTypes === false) { + return fs.readdirSync(path); + } + return fs.readdirSync(path, { ...options, withFileTypes: true }); +} + +export function readlink(path: string): Promise { + return fs.readlink(path); +} + +export function unlink(path: string): Promise { + return fs.unlink(path); +} + +export function symlink(target: string, path: string, type?: fs.SymlinkType): Promise { + return fs.symlink(target, path, type); +} + +export function symlinkSync(target: string, path: string, type?: fs.SymlinkType): void { + return fs.symlinkSync(target, path, type); +} + +export function unlinkSync(path: string): void { + return fs.unlinkSync(path); +} + +export function statSync(path: string): fs.Stats { + return fs.statSync(path); +} + +export function stat(path: string): Promise { + return fs.stat(path); +} + +export function lstat(path: string): Promise { + return fs.lstat(path); +} + +export function chmod(path: string, mod: fs.Mode): Promise { + return fs.chmod(path, mod); +} + +export function createReadStream(path: string): fs.ReadStream { + return fs.createReadStream(path); +} + +export function createWriteStream(path: string): fs.WriteStream { + return fs.createWriteStream(path); +} + +export function pathExistsSync(path: string): boolean { + return fs.pathExistsSync(path); +} + +export function pathExists(absPath: string): Promise { + return fs.pathExists(absPath); +} + +export function createFile(filename: string): Promise { + return fs.createFile(filename); +} + +export function rmdir(path: string, options?: fs.RmDirOptions): Promise { + return fs.rmdir(path, options); +} + +export function remove(path: string): Promise { + return fs.remove(path); +} + +export function readFile(filePath: string, encoding: BufferEncoding): Promise; +export function readFile(filePath: string): Promise; +export function readFile(filePath: string, options: { encoding: BufferEncoding }): Promise; +export function readFile( + filePath: string, + options?: { encoding: BufferEncoding } | BufferEncoding | undefined, +): Promise { + if (typeof options === 'string') { + return fs.readFile(filePath, { encoding: options }); + } + return fs.readFile(filePath, options); +} + +export function readJson(filePath: string): Promise { + return fs.readJson(filePath); +} + +export function writeFile(filePath: string, data: any, options?: { encoding: BufferEncoding }): Promise { + return fs.writeFile(filePath, data, options); +} + +export function mkdir(dirPath: string): Promise { + return fs.mkdir(dirPath); +} + +export function mkdirp(dirPath: string): Promise { + return fs.mkdirp(dirPath); +} + +export function rename(oldPath: string, newPath: string): Promise { + return fs.rename(oldPath, newPath); +} + +export function ensureDir(dirPath: string): Promise { + return fs.ensureDir(dirPath); +} + +export function ensureFile(filePath: string): Promise { + return fs.ensureFile(filePath); +} + +export function ensureSymlink(target: string, filePath: string, type?: fs.SymlinkType): Promise { + return fs.ensureSymlink(target, filePath, type); +} + +export function appendFile(filePath: string, data: any, options?: { encoding: BufferEncoding }): Promise { + return fs.appendFile(filePath, data, options); +} + +export function readdir(path: string): Promise; +export function readdir( + path: string, + options: fs.ObjectEncodingOptions & { + withFileTypes: true; + }, +): Promise; +export function readdir( + path: fs.PathLike, + options?: fs.ObjectEncodingOptions & { + withFileTypes: true; + }, +): Promise { + if (options === undefined) { + return fs.readdir(path); + } + return fs.readdir(path, options); +} + +export function emptyDir(dirPath: string): Promise { + return fs.emptyDir(dirPath); +} diff --git a/src/client/common/platform/fs-temp.ts b/src/client/common/platform/fs-temp.ts index 64d2870a47e4..60dde040f454 100644 --- a/src/client/common/platform/fs-temp.ts +++ b/src/client/common/platform/fs-temp.ts @@ -5,27 +5,19 @@ import * as tmp from 'tmp'; import { ITempFileSystem, TemporaryFile } from './types'; interface IRawTempFS { - // tslint:disable-next-line:no-suspicious-comment - // TODO (https://github.com/microsoft/vscode/issues/84517) - // This functionality has been requested for the - // VS Code FS API (vscode.workspace.fs.*). - file( - config: tmp.Options, - // tslint:disable-next-line:no-any - callback?: (err: any, path: string, fd: number, cleanupCallback: () => void) => void - ): void; + fileSync(config?: tmp.Options): tmp.SynchrounousResult; } // Operations related to temporary files and directories. export class TemporaryFileSystem implements ITempFileSystem { constructor( // (effectively) the third-party "tmp" module to use - private readonly raw: IRawTempFS + private readonly raw: IRawTempFS, ) {} public static withDefaults(): TemporaryFileSystem { return new TemporaryFileSystem( // Use the actual "tmp" module. - tmp + tmp, ); } @@ -33,17 +25,16 @@ export class TemporaryFileSystem implements ITempFileSystem { public createFile(suffix: string, mode?: number): Promise { const opts = { postfix: suffix, - mode + mode, }; return new Promise((resolve, reject) => { - this.raw.file(opts, (err, filename, _fd, cleanUp) => { - if (err) { - return reject(err); - } - resolve({ - filePath: filename, - dispose: cleanUp - }); + const { name, removeCallback } = this.raw.fileSync(opts); + if (!name) { + return reject(new Error('Failed to create temp file')); + } + resolve({ + filePath: name, + dispose: removeCallback, }); }); } diff --git a/src/client/common/platform/pathUtils.ts b/src/client/common/platform/pathUtils.ts index 6d3a5522db8e..b3be39f4644b 100644 --- a/src/client/common/platform/pathUtils.ts +++ b/src/client/common/platform/pathUtils.ts @@ -1,4 +1,3 @@ -// tslint:disable-next-line:no-suspicious-comment // TODO: Drop this file. // See https://github.com/microsoft/vscode-python/issues/8542. @@ -7,15 +6,14 @@ import * as path from 'path'; import { IPathUtils, IsWindows } from '../types'; import { OSType } from '../utils/platform'; import { Executables, FileSystemPaths, FileSystemPathUtils } from './fs-paths'; -// tslint:disable-next-line:no-var-requires no-require-imports -const untildify = require('untildify'); +import { untildify } from '../helpers'; @injectable() export class PathUtils implements IPathUtils { private readonly utils: FileSystemPathUtils; constructor( // "true" if targeting a Windows host. - @inject(IsWindows) isWindows: boolean + @inject(IsWindows) isWindows: boolean, ) { const osType = isWindows ? OSType.Windows : OSType.Unknown; // We cannot just use FileSystemPathUtils.withDefaults() because @@ -24,7 +22,7 @@ export class PathUtils implements IPathUtils { untildify('~'), FileSystemPaths.withDefaults(), new Executables(path.delimiter, osType), - path + path, ); } @@ -40,10 +38,8 @@ export class PathUtils implements IPathUtils { return this.utils.paths.sep; } - // tslint:disable-next-line:no-suspicious-comment // TODO: Deprecate in favor of IPlatformService? public getPathVariableName(): 'Path' | 'PATH' { - // tslint:disable-next-line:no-any return this.utils.executables.envVar as any; } diff --git a/src/client/common/platform/platformService.ts b/src/client/common/platform/platformService.ts index 81706960e6a6..dc9b04cc652c 100644 --- a/src/client/common/platform/platformService.ts +++ b/src/client/common/platform/platformService.ts @@ -1,34 +1,30 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + 'use strict'; import { injectable } from 'inversify'; import * as os from 'os'; import { coerce, SemVer } from 'semver'; -import { sendTelemetryEvent } from '../../telemetry'; -import { EventName, PlatformErrors } from '../../telemetry/constants'; -import { getOSType, OSType } from '../utils/platform'; -import { parseVersion } from '../utils/version'; -import { NON_WINDOWS_PATH_VARIABLE_NAME, WINDOWS_PATH_VARIABLE_NAME } from './constants'; +import { getSearchPathEnvVarNames } from '../utils/exec'; +import { Architecture, getArchitecture, getOSType, isWindows, OSType } from '../utils/platform'; +import { parseSemVerSafe } from '../utils/version'; import { IPlatformService } from './types'; @injectable() export class PlatformService implements IPlatformService { public readonly osType: OSType = getOSType(); + public version?: SemVer; - constructor() { - if (this.osType === OSType.Unknown) { - sendTelemetryEvent(EventName.PLATFORM_INFO, undefined, { - failureType: PlatformErrors.FailedToDetermineOS - }); - } - } - public get pathVariableName() { - return this.isWindows ? WINDOWS_PATH_VARIABLE_NAME : NON_WINDOWS_PATH_VARIABLE_NAME; + + public get pathVariableName(): 'Path' | 'PATH' { + return getSearchPathEnvVarNames(this.osType)[0]; } - public get virtualEnvBinName() { + + public get virtualEnvBinName(): 'Scripts' | 'bin' { return this.isWindows ? 'Scripts' : 'bin'; } + public async getVersion(): Promise { if (this.version) { return this.version; @@ -42,38 +38,38 @@ export class PlatformService implements IPlatformService { try { const ver = coerce(os.release()); if (ver) { - sendTelemetryEvent(EventName.PLATFORM_INFO, undefined, { - osVersion: `${ver.major}.${ver.minor}.${ver.patch}` - }); - return (this.version = ver); + this.version = ver; + return this.version; } throw new Error('Unable to parse version'); } catch (ex) { - sendTelemetryEvent(EventName.PLATFORM_INFO, undefined, { - failureType: PlatformErrors.FailedToParseVersion - }); - return parseVersion(os.release()); + return parseSemVerSafe(os.release()); } default: throw new Error('Not Supported'); } } + // eslint-disable-next-line class-methods-use-this public get isWindows(): boolean { - return this.osType === OSType.Windows; + return isWindows(); } + public get isMac(): boolean { return this.osType === OSType.OSX; } + public get isLinux(): boolean { return this.osType === OSType.Linux; } + + // eslint-disable-next-line class-methods-use-this public get osRelease(): string { return os.release(); } + + // eslint-disable-next-line class-methods-use-this public get is64bit(): boolean { - // tslint:disable-next-line:no-require-imports - const arch = require('arch'); - return arch() === 'x64'; + return getArchitecture() === Architecture.x64; } } diff --git a/src/client/common/platform/registry.ts b/src/client/common/platform/registry.ts index 3e920f03c30c..f1978cfa6dda 100644 --- a/src/client/common/platform/registry.ts +++ b/src/client/common/platform/registry.ts @@ -1,12 +1,12 @@ import { injectable } from 'inversify'; import { Options } from 'winreg'; -import { traceError } from '../logger'; +import { traceError } from '../../logging'; import { Architecture } from '../utils/platform'; import { IRegistry, RegistryHive } from './types'; enum RegistryArchitectures { x86 = 'x86', - x64 = 'x64' + x64 = 'x64', } @injectable() @@ -22,7 +22,7 @@ export class RegistryImplementation implements IRegistry { (ex) => { traceError('Fetching key value from windows registry resulted in an error', ex); return undefined; - } + }, ); } } @@ -39,7 +39,6 @@ export function getArchitectureDisplayName(arch?: Architecture) { } async function getRegistryValue(options: Options, name: string = '') { - // tslint:disable-next-line:no-require-imports const Registry = require('winreg') as typeof import('winreg'); return new Promise((resolve) => { new Registry(options).get(name, (error, result) => { @@ -52,7 +51,6 @@ async function getRegistryValue(options: Options, name: string = '') { } async function getRegistryKeys(options: Options): Promise { - // tslint:disable-next-line:no-require-imports const Registry = require('winreg') as typeof import('winreg'); // https://github.com/python/peps/blob/master/pep-0514.txt#L85 return new Promise((resolve) => { @@ -75,7 +73,6 @@ function translateArchitecture(arch?: Architecture): RegistryArchitectures | und } } function translateHive(hive: RegistryHive): string | undefined { - // tslint:disable-next-line:no-require-imports const Registry = require('winreg') as typeof import('winreg'); switch (hive) { case RegistryHive.HKCU: diff --git a/src/client/common/platform/types.ts b/src/client/common/platform/types.ts index 1f533a0f80c2..11edc9ada0aa 100644 --- a/src/client/common/platform/types.ts +++ b/src/client/common/platform/types.ts @@ -7,12 +7,18 @@ import { SemVer } from 'semver'; import * as vscode from 'vscode'; import { Architecture, OSType } from '../utils/platform'; -//=========================== +// We could use FileType from utils/filesystem.ts, but it's simpler this way. +export import FileType = vscode.FileType; +export import FileStat = vscode.FileStat; +export type ReadStream = fs.ReadStream; +export type WriteStream = fs.WriteStream; + +//= ========================== // registry export enum RegistryHive { HKCU, - HKLM + HKLM, } export const IRegistry = Symbol('IRegistry'); @@ -21,11 +27,9 @@ export interface IRegistry { getValue(key: string, hive: RegistryHive, arch?: Architecture, name?: string): Promise; } -//=========================== +//= ========================== // platform -export const IsWindows = Symbol('IS_WINDOWS'); - export const IPlatformService = Symbol('IPlatformService'); export interface IPlatformService { readonly osType: OSType; @@ -41,17 +45,16 @@ export interface IPlatformService { getVersion(): Promise; } -//=========================== +//= ========================== // temp FS export type TemporaryFile = { filePath: string } & vscode.Disposable; -export type TemporaryDirectory = { path: string } & vscode.Disposable; export interface ITempFileSystem { createFile(suffix: string, mode?: number): Promise; } -//=========================== +//= ========================== // FS paths // The low-level file path operations used by the extension. @@ -87,16 +90,12 @@ export interface IFileSystemPathUtils { getDisplayName(pathValue: string, cwd?: string): string; } -//=========================== +//= ========================== // filesystem operations -export import FileType = vscode.FileType; -export import FileStat = vscode.FileStat; -export type ReadStream = fs.ReadStream; -export type WriteStream = fs.WriteStream; - // The low-level filesystem operations on which the extension depends. export interface IRawFileSystem { + pathExists(filename: string): Promise; // Get information about a file (resolve symlinks). stat(filename: string): Promise; // Get information about a file (do not resolve synlinks). @@ -106,7 +105,7 @@ export interface IRawFileSystem { // Move the file to a different location (and/or rename it). move(src: string, tgt: string): Promise; - //*********************** + //* ********************** // files // Return the raw bytes of the given file. @@ -114,7 +113,7 @@ export interface IRawFileSystem { // Return the text of the given file (decoded from UTF-8). readText(filename: string): Promise; // Write the given text to the file (UTF-8 encoded). - writeText(filename: string, data: {}): Promise; + writeText(filename: string, data: string | Buffer): Promise; // Write the given text to the end of the file (UTF-8 encoded). appendText(filename: string, text: string): Promise; // Copy a file. @@ -122,7 +121,7 @@ export interface IRawFileSystem { // Delete a file. rmfile(filename: string): Promise; - //*********************** + //* ********************** // directories // Create the directory and any missing parent directories. @@ -134,7 +133,7 @@ export interface IRawFileSystem { // Return the contents of the directory. listdir(dirname: string): Promise<[string, FileType][]>; - //*********************** + //* ********************** // not async // Get information about a file (resolve symlinks). @@ -154,14 +153,14 @@ export interface IFileSystemUtils { readonly pathUtils: IFileSystemPathUtils; readonly tmp: ITempFileSystem; - //*********************** + //* ********************** // aliases createDirectory(dirname: string): Promise; deleteDirectory(dirname: string): Promise; deleteFile(filename: string): Promise; - //*********************** + //* ********************** // helpers // Determine if the file exists, optionally requiring the type. @@ -183,13 +182,12 @@ export interface IFileSystemUtils { // Get the paths of all files matching the pattern. search(globPattern: string): Promise; - //*********************** + //* ********************** // helpers (non-async) fileExistsSync(path: string): boolean; } -// tslint:disable-next-line:no-suspicious-comment // TODO: Later we will drop IFileSystem, switching usage to IFileSystemUtils. // See https://github.com/microsoft/vscode-python/issues/8542. @@ -219,6 +217,7 @@ export interface IFileSystem { createWriteStream(path: string): fs.WriteStream; // utils + pathExists(path: string): Promise; fileExists(path: string): Promise; fileExistsSync(path: string): boolean; directoryExists(path: string): Promise; diff --git a/src/client/common/process/baseDaemon.ts b/src/client/common/process/baseDaemon.ts deleted file mode 100644 index abd93668115d..000000000000 --- a/src/client/common/process/baseDaemon.ts +++ /dev/null @@ -1,377 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { ChildProcess } from 'child_process'; -import * as os from 'os'; -import { Subject } from 'rxjs/Subject'; -import * as util from 'util'; -import { MessageConnection, NotificationType, RequestType, RequestType0 } from 'vscode-jsonrpc'; -import { IPlatformService } from '../../common/platform/types'; -import { traceError, traceInfo, traceVerbose, traceWarning } from '../logger'; -import { IDisposable } from '../types'; -import { createDeferred, Deferred } from '../utils/async'; -import { noop } from '../utils/misc'; -import { - ExecutionResult, - IPythonExecutionService, - ObservableExecutionResult, - Output, - SpawnOptions, - StdErrError -} from './types'; - -export type ErrorResponse = { error?: string }; -export type ExecResponse = ErrorResponse & { stdout: string; stderr?: string }; -export class ConnectionClosedError extends Error { - constructor(public readonly message: string) { - super(); - } -} - -export class DaemonError extends Error { - constructor(public readonly message: string) { - super(); - } -} -export abstract class BasePythonDaemon { - public get isAlive(): boolean { - return this.connectionClosedMessage === ''; - } - protected outputObservale = new Subject>(); - private connectionClosedMessage: string = ''; - protected get closed() { - return this.connectionClosedDeferred.promise; - } - // tslint:disable-next-line: no-any - private readonly connectionClosedDeferred: Deferred; - private disposables: IDisposable[] = []; - private disposed = false; - constructor( - protected readonly pythonExecutionService: IPythonExecutionService, - protected readonly platformService: IPlatformService, - protected readonly pythonPath: string, - public readonly proc: ChildProcess, - public readonly connection: MessageConnection - ) { - // tslint:disable-next-line: no-any - this.connectionClosedDeferred = createDeferred(); - // This promise gets used conditionally, if it doesn't get used, and the promise is rejected, - // then node logs errors. We don't want that, hence add a dummy error handler. - this.connectionClosedDeferred.promise.catch(noop); - this.monitorConnection(); - } - public dispose() { - // Make sure that we only dispose once so we are not sending multiple kill signals or notifications - // This daemon can be held by multiple disposes such as a jupyter server daemon process which can - // be disposed by both the connection and the main async disposable - if (!this.disposed) { - try { - this.disposed = true; - - // Proc.kill uses a 'SIGTERM' signal by default to kill. This was failing to kill the process - // sometimes on Mac and Linux. Changing this over to a 'SIGKILL' to fully kill the process. - // Windows closes with a different non-signal message, so keep that the same - // See kill_kernel message of kernel_launcher_daemon.py for and example of this. - if (this.platformService.isWindows) { - this.proc.kill(); - } else { - this.proc.kill('SIGKILL'); - } - } catch { - noop(); - } - this.disposables.forEach((item) => item.dispose()); - } - } - public execObservable(args: string[], options: SpawnOptions): ObservableExecutionResult { - if (this.isAlive && this.canExecFileUsingDaemon(args, options)) { - try { - return this.execAsObservable({ fileName: args[0] }, args.slice(1), options); - } catch (ex) { - if (ex instanceof DaemonError || ex instanceof ConnectionClosedError) { - traceWarning('Falling back to Python Execution Service due to failure in daemon', ex); - return this.pythonExecutionService.execObservable(args, options); - } else { - throw ex; - } - } - } else { - return this.pythonExecutionService.execObservable(args, options); - } - } - public execModuleObservable( - moduleName: string, - args: string[], - options: SpawnOptions - ): ObservableExecutionResult { - if (this.isAlive && this.canExecModuleUsingDaemon(moduleName, args, options)) { - try { - return this.execAsObservable({ moduleName }, args, options); - } catch (ex) { - if (ex instanceof DaemonError || ex instanceof ConnectionClosedError) { - traceWarning('Falling back to Python Execution Service due to failure in daemon', ex); - return this.pythonExecutionService.execModuleObservable(moduleName, args, options); - } else { - throw ex; - } - } - } else { - return this.pythonExecutionService.execModuleObservable(moduleName, args, options); - } - } - public async exec(args: string[], options: SpawnOptions): Promise> { - if (this.isAlive && this.canExecFileUsingDaemon(args, options)) { - try { - return await this.execFileWithDaemon(args[0], args.slice(1), options); - } catch (ex) { - if (ex instanceof DaemonError || ex instanceof ConnectionClosedError) { - traceWarning('Falling back to Python Execution Service due to failure in daemon', ex); - return this.pythonExecutionService.exec(args, options); - } else { - throw ex; - } - } - } else { - return this.pythonExecutionService.exec(args, options); - } - } - public async execModule( - moduleName: string, - args: string[], - options: SpawnOptions - ): Promise> { - if (this.isAlive && this.canExecModuleUsingDaemon(moduleName, args, options)) { - try { - return await this.execModuleWithDaemon(moduleName, args, options); - } catch (ex) { - if (ex instanceof DaemonError || ex instanceof ConnectionClosedError) { - traceWarning('Falling back to Python Execution Service due to failure in daemon', ex); - return this.pythonExecutionService.execModule(moduleName, args, options); - } else { - throw ex; - } - } - } else { - return this.pythonExecutionService.execModule(moduleName, args, options); - } - } - protected canExecFileUsingDaemon(args: string[], options: SpawnOptions): boolean { - return args[0].toLowerCase().endsWith('.py') && this.areOptionsSupported(options); - } - protected canExecModuleUsingDaemon(_moduleName: string, _args: string[], options: SpawnOptions): boolean { - return this.areOptionsSupported(options); - } - protected areOptionsSupported(options: SpawnOptions): boolean { - const daemonSupportedSpawnOptions: (keyof SpawnOptions)[] = [ - 'cwd', - 'env', - 'throwOnStdErr', - 'token', - 'encoding', - 'mergeStdOutErr', - 'extraVariables' - ]; - // tslint:disable-next-line: no-any - return Object.keys(options).every((item) => daemonSupportedSpawnOptions.indexOf(item as any) >= 0); - } - protected sendRequestWithoutArgs(type: RequestType0): Thenable { - return Promise.race([this.connection.sendRequest(type), this.connectionClosedDeferred.promise]); - } - protected sendRequest(type: RequestType, params?: P): Thenable { - if (!this.isAlive) { - traceError('Daemon is handling a request after death.'); - } - // Throw an error if the connection has been closed. - return Promise.race([this.connection.sendRequest(type, params), this.connectionClosedDeferred.promise]); - } - protected throwIfRPCConnectionIsDead() { - if (!this.isAlive) { - throw new ConnectionClosedError(this.connectionClosedMessage); - } - } - protected execAsObservable( - moduleOrFile: { moduleName: string } | { fileName: string }, - args: string[], - options: SpawnOptions - ): ObservableExecutionResult { - const subject = new Subject>(); - const start = async () => { - let response: ExecResponse; - if ('fileName' in moduleOrFile) { - const request = new RequestType< - // tslint:disable-next-line: no-any - { file_name: string; args: string[]; cwd?: string; env?: any }, - ExecResponse, - void, - void - >('exec_file_observable'); - response = await this.sendRequest(request, { - file_name: moduleOrFile.fileName, - args, - cwd: options.cwd, - env: options.env - }); - } else { - const request = new RequestType< - // tslint:disable-next-line: no-any - { module_name: string; args: string[]; cwd?: string; env?: any }, - ExecResponse, - void, - void - >('exec_module_observable'); - response = await this.sendRequest(request, { - module_name: moduleOrFile.moduleName, - args, - cwd: options.cwd, - env: options.env - }); - } - // Might not get a response object back, as its observable. - if (response && response.error) { - throw new DaemonError(response.error); - } - }; - let stdErr = ''; - this.proc.stderr.on('data', (output: string | Buffer) => (stdErr += output.toString())); - // Wire up stdout/stderr. - const subscription = this.outputObservale.subscribe((out) => { - if (out.source === 'stderr' && options.throwOnStdErr) { - subject.error(new StdErrError(out.out)); - } else if (out.source === 'stderr' && options.mergeStdOutErr) { - subject.next({ source: 'stdout', out: out.out }); - } else { - subject.next(out); - } - }); - start() - .catch((ex) => { - const errorMsg = `Failed to run ${ - 'fileName' in moduleOrFile ? moduleOrFile.fileName : moduleOrFile.moduleName - } as observable with args ${args.join(' ')}`; - traceError(errorMsg, ex); - subject.next({ source: 'stderr', out: `${errorMsg}\n${stdErr}` }); - subject.error(ex); - }) - .finally(() => { - // Wait until all messages are received. - setTimeout(() => { - subscription.unsubscribe(); - subject.complete(); - }, 100); - }) - .ignoreErrors(); - - return { - proc: this.proc, - dispose: () => this.dispose(), - out: subject - }; - } - /** - * Process the response. - * - * @private - * @param {{ error?: string | undefined; stdout: string; stderr?: string }} response - * @param {SpawnOptions} options - * @memberof PythonDaemonExecutionService - */ - private processResponse( - response: { error?: string | undefined; stdout: string; stderr?: string }, - options: SpawnOptions - ) { - if (response.error) { - throw new DaemonError(`Failed to execute using the daemon, ${response.error}`); - } - // Throw an error if configured to do so if there's any output in stderr. - if (response.stderr && options.throwOnStdErr) { - throw new StdErrError(response.stderr); - } - // Merge stdout and stderr into on if configured to do so. - if (response.stderr && options.mergeStdOutErr) { - response.stdout = `${response.stdout || ''}${os.EOL}${response.stderr}`; - } - } - private async execFileWithDaemon( - fileName: string, - args: string[], - options: SpawnOptions - ): Promise> { - const request = new RequestType< - // tslint:disable-next-line: no-any - { file_name: string; args: string[]; cwd?: string; env?: any }, - ExecResponse, - void, - void - >('exec_file'); - const response = await this.sendRequest(request, { - file_name: fileName, - args, - cwd: options.cwd, - env: options.env - }); - this.processResponse(response, options); - return response; - } - private async execModuleWithDaemon( - moduleName: string, - args: string[], - options: SpawnOptions - ): Promise> { - const request = new RequestType< - // tslint:disable-next-line: no-any - { module_name: string; args: string[]; cwd?: string; env?: any }, - ExecResponse, - void, - void - >('exec_module'); - const response = await this.sendRequest(request, { - module_name: moduleName, - args, - cwd: options.cwd, - env: options.env - }); - this.processResponse(response, options); - return response; - } - private monitorConnection() { - // tslint:disable-next-line: no-any - const logConnectionStatus = (msg: string, ex?: any) => { - if (!this.disposed) { - this.connectionClosedMessage += msg + (ex ? `, With Error: ${util.format(ex)}` : ''); - this.connectionClosedDeferred.reject(new ConnectionClosedError(this.connectionClosedMessage)); - traceWarning(msg); - if (ex) { - traceError('Connection errored', ex); - } - } - }; - this.disposables.push(this.connection.onClose(() => logConnectionStatus('Daemon Connection Closed'))); - this.disposables.push(this.connection.onDispose(() => logConnectionStatus('Daemon Connection disposed'))); - this.disposables.push(this.connection.onError((ex) => logConnectionStatus('Daemon Connection errored', ex))); - // this.proc.on('error', error => logConnectionStatus('Daemon Processed died with error', error)); - this.proc.on('exit', (code) => logConnectionStatus('Daemon Processed died with exit code', code)); - // Wire up stdout/stderr. - const OuputNotification = new NotificationType, void>('output'); - this.connection.onNotification(OuputNotification, (output) => this.outputObservale.next(output)); - const logNotification = new NotificationType< - { level: 'WARN' | 'WARNING' | 'INFO' | 'DEBUG' | 'NOTSET'; msg: string; pid?: string }, - void - >('log'); - this.connection.onNotification(logNotification, (output) => { - const pid = output.pid ? ` (pid: ${output.pid})` : ''; - const msg = `Python Daemon${pid}: ${output.msg}`; - if (output.level === 'DEBUG' || output.level === 'NOTSET') { - traceVerbose(msg); - } else if (output.level === 'INFO') { - traceInfo(msg); - } else if (output.level === 'WARN' || output.level === 'WARNING') { - traceWarning(msg); - } else { - traceError(msg); - } - }); - this.connection.onUnhandledNotification(traceError); - } -} diff --git a/src/client/common/process/currentProcess.ts b/src/client/common/process/currentProcess.ts index 8d918f83f26c..b80c32e97b7c 100644 --- a/src/client/common/process/currentProcess.ts +++ b/src/client/common/process/currentProcess.ts @@ -2,8 +2,6 @@ // Licensed under the MIT License. 'use strict'; -// tslint:disable:no-any - import { injectable } from 'inversify'; import { ICurrentProcess } from '../types'; import { EnvironmentVariables } from '../variables/types'; diff --git a/src/client/common/process/decoder.ts b/src/client/common/process/decoder.ts index 4e03b48501d0..76cc7a349816 100644 --- a/src/client/common/process/decoder.ts +++ b/src/client/common/process/decoder.ts @@ -2,14 +2,9 @@ // Licensed under the MIT License. import * as iconv from 'iconv-lite'; -import { injectable } from 'inversify'; import { DEFAULT_ENCODING } from './constants'; -import { IBufferDecoder } from './types'; -@injectable() -export class BufferDecoder implements IBufferDecoder { - public decode(buffers: Buffer[], encoding: string = DEFAULT_ENCODING): string { - encoding = iconv.encodingExists(encoding) ? encoding : DEFAULT_ENCODING; - return iconv.decode(Buffer.concat(buffers), encoding); - } +export function decodeBuffer(buffers: Buffer[], encoding: string = DEFAULT_ENCODING): string { + encoding = iconv.encodingExists(encoding) ? encoding : DEFAULT_ENCODING; + return iconv.decode(Buffer.concat(buffers), encoding); } diff --git a/src/client/common/process/internal/python.ts b/src/client/common/process/internal/python.ts index 86123367f852..377c6580bfd5 100644 --- a/src/client/common/process/internal/python.ts +++ b/src/client/common/process/internal/python.ts @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { _ISOLATED as ISOLATED } from './scripts'; - // "python" contains functions corresponding to the various ways that // the extension invokes a Python executable internally. Each function // takes arguments relevant to the specific use case. However, each @@ -15,49 +13,22 @@ import { _ISOLATED as ISOLATED } from './scripts'; // into the corresponding object or objects. "parse()" takes a single // string as the stdout text and returns the relevant data. -export function execCode(code: string, isolated = true): string[] { - const args = ['-c', code]; - if (isolated) { - args.splice(0, 0, ISOLATED); - } +export function execCode(code: string): string[] { + let args = ['-c', code]; // "code" isn't specific enough to know how to parse it, // so we only return the args. return args; } -export function execModule(name: string, moduleArgs: string[], isolated = true): string[] { +export function execModule(name: string, moduleArgs: string[]): string[] { const args = ['-m', name, ...moduleArgs]; - if (isolated) { - args[0] = ISOLATED; // replace - } // "code" isn't specific enough to know how to parse it, // so we only return the args. return args; } -export function getVersion(): [string[], (out: string) => string] { - // There is no need to isolate this. - const args = ['--version']; - - function parse(out: string): string { - return out.trim(); - } - - return [args, parse]; -} - -export function getSysPrefix(): [string[], (out: string) => string] { - const args = [ISOLATED, '-c', 'import sys;print(sys.prefix)']; - - function parse(out: string): string { - return out.trim(); - } - - return [args, parse]; -} - export function getExecutable(): [string[], (out: string) => string] { - const args = [ISOLATED, '-c', 'import sys;print(sys.executable)']; + const args = ['-c', 'import sys;print(sys.executable)']; function parse(out: string): string { return out.trim(); @@ -67,14 +38,10 @@ export function getExecutable(): [string[], (out: string) => string] { } export function getSitePackages(): [string[], (out: string) => string] { - const args = [ - ISOLATED, - '-c', - // On windows we also need the libs path (second item will - // return c:\xxx\lib\site-packages). This is returned by - // the following: - 'from distutils.sysconfig import get_python_lib; print(get_python_lib())' - ]; + // On windows we also need the libs path (second item will + // return c:\xxx\lib\site-packages). This is returned by + // the following: get_python_lib + const args = ['-c', 'from distutils.sysconfig import get_python_lib; print(get_python_lib())']; function parse(out: string): string { return out.trim(); @@ -84,7 +51,7 @@ export function getSitePackages(): [string[], (out: string) => string] { } export function getUserSitePackages(): [string[], (out: string) => string] { - const args = [ISOLATED, 'site', '--user-site']; + const args = ['site', '--user-site']; function parse(out: string): string { return out.trim(); @@ -94,7 +61,6 @@ export function getUserSitePackages(): [string[], (out: string) => string] { } export function isValid(): [string[], (out: string) => boolean] { - // There is no need to isolate this. const args = ['-c', 'print(1234)']; function parse(out: string): boolean { @@ -105,7 +71,7 @@ export function isValid(): [string[], (out: string) => boolean] { } export function isModuleInstalled(name: string): [string[], (out: string) => boolean] { - const args = [ISOLATED, '-c', `import ${name}`]; + const args = ['-c', `import ${name}`]; function parse(_out: string): boolean { // If the command did not fail then the module is installed. @@ -116,7 +82,7 @@ export function isModuleInstalled(name: string): [string[], (out: string) => boo } export function getModuleVersion(name: string): [string[], (out: string) => string] { - const args = [ISOLATED, name, '--version']; + const args = ['-c', `import ${name}; print(${name}.__version__)`]; function parse(out: string): string { return out.trim(); diff --git a/src/client/common/process/internal/scripts/constants.ts b/src/client/common/process/internal/scripts/constants.ts new file mode 100644 index 000000000000..6954592ed3dd --- /dev/null +++ b/src/client/common/process/internal/scripts/constants.ts @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { EXTENSION_ROOT_DIR } from '../../../constants'; + +// It is simpler to hard-code it instead of using vscode.ExtensionContext.extensionPath. +export const _SCRIPTS_DIR = path.join(EXTENSION_ROOT_DIR, 'python_files'); diff --git a/src/client/common/process/internal/scripts/index.ts b/src/client/common/process/internal/scripts/index.ts index d4f7c924a828..f2c905c02889 100644 --- a/src/client/common/process/internal/scripts/index.ts +++ b/src/client/common/process/internal/scripts/index.ts @@ -2,16 +2,12 @@ // Licensed under the MIT License. import * as path from 'path'; -import { EXTENSION_ROOT_DIR } from '../../../constants'; +import { _SCRIPTS_DIR } from './constants'; -// It is simpler to hard-code it instead of using vscode.ExtensionContext.extensionPath. -export const _SCRIPTS_DIR = path.join(EXTENSION_ROOT_DIR, 'pythonFiles'); const SCRIPTS_DIR = _SCRIPTS_DIR; -export const _ISOLATED = path.join(_SCRIPTS_DIR, 'pyvsc-run-isolated.py'); -const ISOLATED = _ISOLATED; // "scripts" contains everything relevant to the scripts found under -// the top-level "pythonFiles" directory. Each of those scripts has +// the top-level "python_files" directory. Each of those scripts has // a function in this module which matches the script's filename. // Each function provides the commandline arguments that should be // used when invoking a Python executable, whether through spawn/exec @@ -22,21 +18,15 @@ const ISOLATED = _ISOLATED; // into the corresponding object or objects. "parse()" takes a single // string as the stdout text and returns the relevant data. // -// Some of the scripts are located in subdirectories of "pythonFiles". +// Some of the scripts are located in subdirectories of "python_files". // For each of those subdirectories there is a sub-module where // those scripts' functions may be found. // // In some cases one or more types related to a script are exported // from the same module in which the script's function is located. // These types typically relate to the return type of "parse()". -// -// ignored scripts: -// * install_debugpy.py (used only for extension development) - -export * as testing_tools from './testing_tools'; -export * as vscode_datascience_helpers from './vscode_datascience_helpers'; +export * as testingTools from './testing_tools'; -//============================ // interpreterInfo.py type ReleaseLevel = 'alpha' | 'beta' | 'candidate' | 'final'; @@ -48,177 +38,28 @@ export type InterpreterInfoJson = { is64Bit: boolean; }; +export const OUTPUT_MARKER_SCRIPT = path.join(_SCRIPTS_DIR, 'get_output_via_markers.py'); + export function interpreterInfo(): [string[], (out: string) => InterpreterInfoJson] { const script = path.join(SCRIPTS_DIR, 'interpreterInfo.py'); - const args = [ISOLATED, script]; + const args = [script]; function parse(out: string): InterpreterInfoJson { - let json: InterpreterInfoJson; try { - json = JSON.parse(out); + return JSON.parse(out); } catch (ex) { throw Error(`python ${args} returned bad JSON (${out}) (${ex})`); } - return json; } return [args, parse]; } -//============================ -// completion.py - -namespace _completion { - export type Response = (_Response1 | _Response2) & { - id: number; - }; - type _Response1 = { - // tslint:disable-next-line:no-any no-banned-terms - arguments: any[]; - }; - type _Response2 = - | CompletionResponse - | HoverResponse - | DefinitionResponse - | ReferenceResponse - | SymbolResponse - | ArgumentsResponse; - - type CompletionResponse = { - results: AutoCompleteItem[]; - }; - type HoverResponse = { - results: HoverItem[]; - }; - type DefinitionResponse = { - results: Definition[]; - }; - type ReferenceResponse = { - results: Reference[]; - }; - type SymbolResponse = { - results: Definition[]; - }; - type ArgumentsResponse = { - results: Signature[]; - }; - - type Signature = { - name: string; - docstring: string; - description: string; - paramindex: number; - params: Argument[]; - }; - type Argument = { - name: string; - value: string; - docstring: string; - description: string; - }; - - type Reference = { - name: string; - fileName: string; - columnIndex: number; - lineIndex: number; - moduleName: string; - }; - - type AutoCompleteItem = { - type: string; - kind: string; - text: string; - description: string; - raw_docstring: string; - rightLabel: string; - }; - - type DefinitionRange = { - startLine: number; - startColumn: number; - endLine: number; - endColumn: number; - }; - type Definition = { - type: string; - kind: string; - text: string; - fileName: string; - container: string; - range: DefinitionRange; - }; - - type HoverItem = { - kind: string; - text: string; - description: string; - docstring: string; - signature: string; - }; -} +// normalizeSelection.py -export function completion(jediPath?: string): [string[], (out: string) => _completion.Response[]] { - const script = path.join(SCRIPTS_DIR, 'completion.py'); - const args = [ISOLATED, script]; - if (jediPath) { - args.push('custom'); - args.push(jediPath); - } - - function parse(out: string): _completion.Response[] { - return out.splitLines().map((resp) => JSON.parse(resp)); - } - - return [args, parse]; -} - -//============================ -// sortImports.py - -export function sortImports(filename: string, sortArgs?: string[]): [string[], (out: string) => string] { - const script = path.join(SCRIPTS_DIR, 'sortImports.py'); - const args = [ISOLATED, script, filename, '--diff']; - if (sortArgs) { - args.push(...sortArgs); - } - - function parse(out: string) { - // It should just be a diff that the extension will use directly. - return out; - } - - return [args, parse]; -} - -//============================ -// refactor.py - -export function refactor(root: string): [string[], (out: string) => object[]] { - const script = path.join(SCRIPTS_DIR, 'refactor.py'); - const args = [ISOLATED, script, root]; - - // tslint:disable-next-line:no-suspicious-comment - // TODO: Make the return type more specific, like we did - // with completion(). - function parse(out: string): object[] { - // tslint:disable-next-line:no-suspicious-comment - // TODO: Also handle "STARTED"? - return out - .split(/\r?\n/g) - .filter((line) => line.length > 0) - .map((resp) => JSON.parse(resp)); - } - - return [args, parse]; -} - -//============================ -// normalizeForInterpreter.py - -export function normalizeForInterpreter(code: string): [string[], (out: string) => string] { - const script = path.join(SCRIPTS_DIR, 'normalizeForInterpreter.py'); - const args = [ISOLATED, script, code]; +export function normalizeSelection(): [string[], (out: string) => string] { + const script = path.join(SCRIPTS_DIR, 'normalizeSelection.py'); + const args = [script]; function parse(out: string) { // The text will be used as-is. @@ -228,54 +69,11 @@ export function normalizeForInterpreter(code: string): [string[], (out: string) return [args, parse]; } -//============================ -// symbolProvider.py - -namespace _symbolProvider { - type Position = { - line: number; - character: number; - }; - type RawSymbol = { - // If no namespace then ''. - namespace: string; - name: string; - range: { - start: Position; - end: Position; - }; - }; - export type Symbols = { - classes: RawSymbol[]; - methods: RawSymbol[]; - functions: RawSymbol[]; - }; -} - -export function symbolProvider( - filename: string, - // If "text" is provided then it gets passed to the script as-is. - text?: string -): [string[], (out: string) => _symbolProvider.Symbols] { - const script = path.join(SCRIPTS_DIR, 'symbolProvider.py'); - const args = [ISOLATED, script, filename]; - if (text) { - args.push(text); - } - - function parse(out: string): _symbolProvider.Symbols { - return JSON.parse(out); - } - - return [args, parse]; -} - -//============================ // printEnvVariables.py export function printEnvVariables(): [string[], (out: string) => NodeJS.ProcessEnv] { - const script = path.join(SCRIPTS_DIR, 'printEnvVariables.py').fileToCommandArgument(); - const args = [ISOLATED, script]; + const script = path.join(SCRIPTS_DIR, 'printEnvVariables.py').fileToCommandArgumentForPythonExt(); + const args = [script]; function parse(out: string): NodeJS.ProcessEnv { return JSON.parse(out); @@ -284,52 +82,79 @@ export function printEnvVariables(): [string[], (out: string) => NodeJS.ProcessE return [args, parse]; } -//============================ -// printEnvVariablesToFile.py - -export function printEnvVariablesToFile(filename: string): [string[], (out: string) => NodeJS.ProcessEnv] { - const script = path.join(SCRIPTS_DIR, 'printEnvVariablesToFile.py'); - const args = [ISOLATED, script, filename.fileToCommandArgument()]; - - function parse(out: string): NodeJS.ProcessEnv { - return JSON.parse(out); - } - - return [args, parse]; -} - -//============================ // shell_exec.py +// eslint-disable-next-line camelcase export function shell_exec(command: string, lockfile: string, shellArgs: string[]): string[] { const script = path.join(SCRIPTS_DIR, 'shell_exec.py'); // We don't bother with a "parse" function since the output // could be anything. return [ - ISOLATED, script, - command.fileToCommandArgument(), + command.fileToCommandArgumentForPythonExt(), // The shell args must come after the command // but before the lockfile. ...shellArgs, - lockfile.fileToCommandArgument() + lockfile.fileToCommandArgumentForPythonExt(), ]; } -//============================ // testlauncher.py export function testlauncher(testArgs: string[]): string[] { const script = path.join(SCRIPTS_DIR, 'testlauncher.py'); // There is no output to parse, so we do not return a function. - return [ISOLATED, script, ...testArgs]; + return [script, ...testArgs]; +} + +// run_pytest_script.py +export function pytestlauncher(testArgs: string[]): string[] { + const script = path.join(SCRIPTS_DIR, 'vscode_pytest', 'run_pytest_script.py'); + // There is no output to parse, so we do not return a function. + return [script, ...testArgs]; } -//============================ // visualstudio_py_testlauncher.py +// eslint-disable-next-line camelcase export function visualstudio_py_testlauncher(testArgs: string[]): string[] { const script = path.join(SCRIPTS_DIR, 'visualstudio_py_testlauncher.py'); // There is no output to parse, so we do not return a function. return [script, ...testArgs]; } + +// execution.py +// eslint-disable-next-line camelcase +export function execution_py_testlauncher(testArgs: string[]): string[] { + const script = path.join(SCRIPTS_DIR, 'unittestadapter', 'execution.py'); + return [script, ...testArgs]; +} + +// tensorboard_launcher.py + +export function tensorboardLauncher(args: string[]): string[] { + const script = path.join(SCRIPTS_DIR, 'tensorboard_launcher.py'); + return [script, ...args]; +} + +// linter.py + +export function linterScript(): string { + const script = path.join(SCRIPTS_DIR, 'linter.py'); + return script; +} + +export function createVenvScript(): string { + const script = path.join(SCRIPTS_DIR, 'create_venv.py'); + return script; +} + +export function createCondaScript(): string { + const script = path.join(SCRIPTS_DIR, 'create_conda.py'); + return script; +} + +export function installedCheckScript(): string { + const script = path.join(SCRIPTS_DIR, 'installed_check.py'); + return script; +} diff --git a/src/client/common/process/internal/scripts/testing_tools.ts b/src/client/common/process/internal/scripts/testing_tools.ts index d2047784622e..60dd21b698b6 100644 --- a/src/client/common/process/internal/scripts/testing_tools.ts +++ b/src/client/common/process/internal/scripts/testing_tools.ts @@ -2,59 +2,19 @@ // Licensed under the MIT License. import * as path from 'path'; -import { _SCRIPTS_DIR } from './index'; +import { _SCRIPTS_DIR } from './constants'; const SCRIPTS_DIR = path.join(_SCRIPTS_DIR, 'testing_tools'); //============================ // run_adapter.py -type TestNode = { - id: string; - name: string; - parentid: string; -}; -type TestParent = TestNode & { - kind: 'folder' | 'file' | 'suite' | 'function'; -}; -type TestFSNode = TestParent & { - kind: 'folder' | 'file'; - relpath: string; -}; - -export type TestFolder = TestFSNode & { - kind: 'folder'; -}; -export type TestFile = TestFSNode & { - kind: 'file'; -}; -export type TestSuite = TestParent & { - kind: 'suite'; -}; -// function-as-a-container is for parameterized ("sub") tests. -export type TestFunction = TestParent & { - kind: 'function'; -}; -export type Test = TestNode & { - source: string; -}; -export type DiscoveredTests = { - rootid: string; - root: string; - parents: TestParent[]; - tests: Test[]; -}; - -export function run_adapter(adapterArgs: string[]): [string[], (out: string) => DiscoveredTests[]] { +export function runAdapter(adapterArgs: string[]): string[] { const script = path.join(SCRIPTS_DIR, 'run_adapter.py'); - // Note that we for now we do not run this "isolated". The - // script relies on some magic that conflicts with the - // isolated script. - const args = [script, ...adapterArgs]; - - function parse(out: string): DiscoveredTests[] { - return JSON.parse(out); - } + return [script, ...adapterArgs]; +} - return [args, parse]; +export function unittestDiscovery(args: string[]): string[] { + const script = path.join(SCRIPTS_DIR, 'unittest_discovery.py'); + return [script, ...args]; } diff --git a/src/client/common/process/internal/scripts/vscode_datascience_helpers.ts b/src/client/common/process/internal/scripts/vscode_datascience_helpers.ts deleted file mode 100644 index 880d46bd6738..000000000000 --- a/src/client/common/process/internal/scripts/vscode_datascience_helpers.ts +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import * as path from 'path'; -import { _ISOLATED as ISOLATED, _SCRIPTS_DIR } from './index'; - -const SCRIPTS_DIR = path.join(_SCRIPTS_DIR, 'vscode_datascience_helpers'); - -//============================ -// getServerInfo.py - -type JupyterServerInfo = { - base_url: string; - notebook_dir: string; - hostname: string; - password: boolean; - pid: number; - port: number; - secure: boolean; - token: string; - url: string; -}; - -export function getServerInfo(): [string[], (out: string) => JupyterServerInfo[]] { - const script = path.join(SCRIPTS_DIR, 'getServerInfo.py'); - const args = [ISOLATED, script]; - - function parse(out: string): JupyterServerInfo[] { - return JSON.parse(out.trim()); - } - - return [args, parse]; -} - -//============================ -// getJupyterKernels.py - -export function getJupyterKernels(): string[] { - const script = path.join(SCRIPTS_DIR, 'getJupyterKernels.py'); - // There is no script-specific output to parse, so we do not return a function. - return [ISOLATED, script]; -} - -//============================ -// getJupyterKernelspecVersion.py - -export function getJupyterKernelspecVersion(): string[] { - const script = path.join(SCRIPTS_DIR, 'getJupyterKernelspecVersion.py'); - // For now we do not worry about parsing the output here. - return [ISOLATED, script]; -} - -//============================ -// jupyter_nbInstalled.py - -export function jupyter_nbInstalled(): [string[], (out: string) => boolean] { - const script = path.join(SCRIPTS_DIR, 'jupyter_nbInstalled.py'); - const args = [ISOLATED, script]; - - function parse(out: string): boolean { - return out.toLowerCase().includes('available'); - } - - return [args, parse]; -} diff --git a/src/client/common/process/logger.ts b/src/client/common/process/logger.ts index a17896fdd467..b65da8dc81e5 100644 --- a/src/client/common/process/logger.ts +++ b/src/client/common/process/logger.ts @@ -3,43 +3,94 @@ 'use strict'; -import { inject, injectable, named } from 'inversify'; -import { isCI, isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../constants'; -import { traceInfo } from '../logger'; -import { IOutputChannel, IPathUtils } from '../types'; -import { Logging } from '../utils/localize'; +import { inject, injectable } from 'inversify'; +import { traceLog } from '../../logging'; +import { IWorkspaceService } from '../application/types'; +import { isCI, isTestExecution } from '../constants'; +import { getOSType, getUserHomeDir, OSType } from '../utils/platform'; import { IProcessLogger, SpawnOptions } from './types'; +import { escapeRegExp } from 'lodash'; +import { replaceAll } from '../stringUtils'; +import { identifyShellFromShellPath } from '../terminal/shellDetectors/baseShellDetector'; +import '../../common/extensions'; @injectable() export class ProcessLogger implements IProcessLogger { - constructor( - @inject(IOutputChannel) @named(STANDARD_OUTPUT_CHANNEL) private readonly outputChannel: IOutputChannel, - @inject(IPathUtils) private readonly pathUtils: IPathUtils - ) {} + constructor(@inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService) {} - public logProcess(file: string, args: string[], options?: SpawnOptions) { + public logProcess(fileOrCommand: string, args?: string[], options?: SpawnOptions) { if (!isTestExecution() && isCI && process.env.UITEST_DISABLE_PROCESS_LOGGING) { // Added to disable logging of process execution commands during UI Tests. // Used only during UI Tests (hence this setting need not be exposed as a valid setting). return; } - const argsList = args.reduce((accumulator, current, index) => { - let formattedArg = this.pathUtils.getDisplayName(current).toCommandArgument(); - if (current[0] === "'" || current[0] === '"') { - formattedArg = `${current[0]}${this.pathUtils.getDisplayName(current.substr(1))}`; - } - - return index === 0 ? formattedArg : `${accumulator} ${formattedArg}`; - }, ''); - - const info = [`> ${this.pathUtils.getDisplayName(file)} ${argsList}`]; - if (options && options.cwd) { - info.push(`${Logging.currentWorkingDirectory()} ${this.pathUtils.getDisplayName(options.cwd)}`); + let command = args + ? [fileOrCommand, ...args].map((e) => e.trimQuotes().toCommandArgumentForPythonExt()).join(' ') + : fileOrCommand; + const info = [`> ${this.getDisplayCommands(command)}`]; + if (options?.cwd) { + const cwd: string = typeof options?.cwd === 'string' ? options?.cwd : options?.cwd?.toString(); + info.push(`cwd: ${this.getDisplayCommands(cwd)}`); + } + if (typeof options?.shell === 'string') { + info.push(`shell: ${identifyShellFromShellPath(options?.shell)}`); } info.forEach((line) => { - traceInfo(line); - this.outputChannel.appendLine(line); + traceLog(line); }); } + + /** + * Formats command strings for display by replacing common paths with symbols. + * - Replaces the workspace folder path with '.' if there's exactly one workspace folder + * - Replaces the user's home directory path with '~' + * @param command The command string to format + * @returns The formatted command string with paths replaced by symbols + */ + private getDisplayCommands(command: string): string { + if (this.workspaceService.workspaceFolders && this.workspaceService.workspaceFolders.length === 1) { + command = replaceMatchesWithCharacter(command, this.workspaceService.workspaceFolders[0].uri.fsPath, '.'); + } + const home = getUserHomeDir(); + if (home) { + command = replaceMatchesWithCharacter(command, home, '~'); + } + return command; + } +} + +/** + * Finds case insensitive matches in the original string and replaces it with character provided. + */ +function replaceMatchesWithCharacter(original: string, match: string, character: string): string { + // Backslashes, plus signs, brackets and other characters have special meaning in regexes, + // we need to escape using an extra backlash so it's not considered special. + function getRegex(match: string) { + let pattern = escapeRegExp(match); + if (getOSType() === OSType.Windows) { + // Match both forward and backward slash versions of 'match' for Windows. + pattern = replaceAll(pattern, '\\\\', '(\\\\|/)'); + } + let regex = new RegExp(pattern, 'ig'); + return regex; + } + + function isPrevioustoMatchRegexALetter(chunk: string, index: number) { + return chunk[index].match(/[a-z]/); + } + + let chunked = original.split(' '); + + for (let i = 0; i < chunked.length; i++) { + let regex = getRegex(match); + const regexResult = regex.exec(chunked[i]); + if (regexResult) { + const regexIndex = regexResult.index; + if (regexIndex > 0 && isPrevioustoMatchRegexALetter(chunked[i], regexIndex - 1)) + regex = getRegex(match.substring(1)); + chunked[i] = chunked[i].replace(regex, character); + } + } + return chunked.join(' '); } diff --git a/src/client/common/process/proc.ts b/src/client/common/process/proc.ts index 54cf2f53bf43..4a5aa984fa44 100644 --- a/src/client/common/process/proc.ts +++ b/src/client/common/process/proc.ts @@ -1,30 +1,21 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { exec, execSync, spawn } from 'child_process'; import { EventEmitter } from 'events'; -import { Observable } from 'rxjs/Observable'; +import { traceError } from '../../logging'; import { IDisposable } from '../types'; -import { createDeferred } from '../utils/async'; import { EnvironmentVariables } from '../variables/types'; -import { DEFAULT_ENCODING } from './constants'; -import { - ExecutionResult, - IBufferDecoder, - IProcessService, - ObservableExecutionResult, - Output, - ShellOptions, - SpawnOptions, - StdErrError -} from './types'; +import { execObservable, killPid, plainExec, shellExec } from './rawProcessApis'; +import { ExecutionResult, IProcessService, ObservableExecutionResult, ShellOptions, SpawnOptions } from './types'; +import { workerPlainExec, workerShellExec } from './worker/rawProcessApiWrapper'; -// tslint:disable:no-any export class ProcessService extends EventEmitter implements IProcessService { private processesToKill = new Set(); - constructor(private readonly decoder: IBufferDecoder, private readonly env?: EnvironmentVariables) { + + constructor(private readonly env?: EnvironmentVariables) { super(); } + public static isAlive(pid: number): boolean { try { process.kill(pid, 0); @@ -33,19 +24,12 @@ export class ProcessService extends EventEmitter implements IProcessService { return false; } } + public static kill(pid: number): void { - try { - if (process.platform === 'win32') { - // Windows doesn't support SIGTERM, so execute taskkill to kill the process - execSync(`taskkill /pid ${pid} /T /F`); - } else { - process.kill(pid); - } - } catch { - // Ignore. - } + killPid(pid); } - public dispose() { + + public dispose(): void { this.removeAllListeners(); this.processesToKill.forEach((p) => { try { @@ -57,192 +41,38 @@ export class ProcessService extends EventEmitter implements IProcessService { } public execObservable(file: string, args: string[], options: SpawnOptions = {}): ObservableExecutionResult { - const spawnOptions = this.getDefaultOptions(options); - const encoding = spawnOptions.encoding ? spawnOptions.encoding : 'utf8'; - const proc = spawn(file, args, spawnOptions); - let procExited = false; - const disposable: IDisposable = { - // tslint:disable-next-line: no-function-expression - dispose: function () { - if (proc && !proc.killed && !procExited) { - ProcessService.kill(proc.pid); - } - if (proc) { - proc.unref(); - } - } - }; - this.processesToKill.add(disposable); - - const output = new Observable>((subscriber) => { - const disposables: IDisposable[] = []; - - const on = (ee: NodeJS.EventEmitter, name: string, fn: Function) => { - ee.on(name, fn as any); - disposables.push({ dispose: () => ee.removeListener(name, fn as any) as any }); - }; - - if (options.token) { - disposables.push( - options.token.onCancellationRequested(() => { - if (!procExited && !proc.killed) { - proc.kill(); - procExited = true; - } - }) - ); - } - - const sendOutput = (source: 'stdout' | 'stderr', data: Buffer) => { - const out = this.decoder.decode([data], encoding); - if (source === 'stderr' && options.throwOnStdErr) { - subscriber.error(new StdErrError(out)); - } else { - subscriber.next({ source, out: out }); - } - }; - - on(proc.stdout, 'data', (data: Buffer) => sendOutput('stdout', data)); - on(proc.stderr, 'data', (data: Buffer) => sendOutput('stderr', data)); - - proc.once('close', () => { - procExited = true; - subscriber.complete(); - disposables.forEach((d) => d.dispose()); - }); - proc.once('exit', () => { - procExited = true; - subscriber.complete(); - disposables.forEach((d) => d.dispose()); - }); - proc.once('error', (ex) => { - procExited = true; - subscriber.error(ex); - disposables.forEach((d) => d.dispose()); - }); - }); - + const execOptions = { ...options, doNotLog: true }; + const result = execObservable(file, args, execOptions, this.env, this.processesToKill); this.emit('exec', file, args, options); - - return { - proc, - out: output, - dispose: disposable.dispose - }; + return result; } - public exec(file: string, args: string[], options: SpawnOptions = {}): Promise> { - const spawnOptions = this.getDefaultOptions(options); - const encoding = spawnOptions.encoding ? spawnOptions.encoding : 'utf8'; - const proc = spawn(file, args, spawnOptions); - const deferred = createDeferred>(); - const disposable: IDisposable = { - dispose: () => { - if (!proc.killed && !deferred.completed) { - proc.kill(); - } - } - }; - this.processesToKill.add(disposable); - const disposables: IDisposable[] = []; - - const on = (ee: NodeJS.EventEmitter, name: string, fn: Function) => { - ee.on(name, fn as any); - disposables.push({ dispose: () => ee.removeListener(name, fn as any) as any }); - }; - - if (options.token) { - disposables.push(options.token.onCancellationRequested(disposable.dispose)); - } - - const stdoutBuffers: Buffer[] = []; - on(proc.stdout, 'data', (data: Buffer) => stdoutBuffers.push(data)); - const stderrBuffers: Buffer[] = []; - on(proc.stderr, 'data', (data: Buffer) => { - if (options.mergeStdOutErr) { - stdoutBuffers.push(data); - stderrBuffers.push(data); - } else { - stderrBuffers.push(data); - } - }); - - proc.once('close', () => { - if (deferred.completed) { - return; - } - const stderr: string | undefined = - stderrBuffers.length === 0 ? undefined : this.decoder.decode(stderrBuffers, encoding); - if (stderr && stderr.length > 0 && options.throwOnStdErr) { - deferred.reject(new StdErrError(stderr)); - } else { - const stdout = this.decoder.decode(stdoutBuffers, encoding); - deferred.resolve({ stdout, stderr }); - } - disposables.forEach((d) => d.dispose()); - }); - proc.once('error', (ex) => { - deferred.reject(ex); - disposables.forEach((d) => d.dispose()); - }); + public exec(file: string, args: string[], options: SpawnOptions = {}): Promise> { this.emit('exec', file, args, options); - - return deferred.promise; + if (options.useWorker) { + return workerPlainExec(file, args, options); + } + const execOptions = { ...options, doNotLog: true }; + const promise = plainExec(file, args, execOptions, this.env, this.processesToKill); + return promise; } public shellExec(command: string, options: ShellOptions = {}): Promise> { - const shellOptions = this.getDefaultOptions(options); - return new Promise((resolve, reject) => { - const proc = exec(command, shellOptions, (e, stdout, stderr) => { - if (e && e !== null) { - reject(e); - } else if (shellOptions.throwOnStdErr && stderr && stderr.length) { - reject(new Error(stderr)); - } else { - // Make sure stderr is undefined if we actually had none. This is checked - // elsewhere because that's how exec behaves. - resolve({ stderr: stderr && stderr.length > 0 ? stderr : undefined, stdout: stdout }); + this.emit('exec', command, undefined, options); + if (options.useWorker) { + return workerShellExec(command, options); + } + const disposables = new Set(); + const shellOptions = { ...options, doNotLog: true }; + return shellExec(command, shellOptions, this.env, disposables).finally(() => { + // Ensure the process we started is cleaned up. + disposables.forEach((p) => { + try { + p.dispose(); + } catch { + traceError(`Unable to kill process for ${command}`); } }); - const disposable: IDisposable = { - dispose: () => { - if (!proc.killed) { - proc.kill(); - } - } - }; - this.processesToKill.add(disposable); }); } - - private getDefaultOptions(options: T): T { - const defaultOptions = { ...options }; - const execOptions = defaultOptions as SpawnOptions; - if (execOptions) { - const encoding = (execOptions.encoding = - typeof execOptions.encoding === 'string' && execOptions.encoding.length > 0 - ? execOptions.encoding - : DEFAULT_ENCODING); - delete execOptions.encoding; - execOptions.encoding = encoding; - } - if (!defaultOptions.env || Object.keys(defaultOptions.env).length === 0) { - const env = this.env ? this.env : process.env; - defaultOptions.env = { ...env }; - } else { - defaultOptions.env = { ...defaultOptions.env }; - } - - if (execOptions && execOptions.extraVariables) { - defaultOptions.env = { ...defaultOptions.env, ...execOptions.extraVariables }; - } - - // Always ensure we have unbuffered output. - defaultOptions.env.PYTHONUNBUFFERED = '1'; - if (!defaultOptions.env.PYTHONIOENCODING) { - defaultOptions.env.PYTHONIOENCODING = 'utf-8'; - } - - return defaultOptions; - } } diff --git a/src/client/common/process/processFactory.ts b/src/client/common/process/processFactory.ts index d6da78721138..40204a640dae 100644 --- a/src/client/common/process/processFactory.ts +++ b/src/client/common/process/processFactory.ts @@ -8,19 +8,20 @@ import { Uri } from 'vscode'; import { IDisposableRegistry } from '../types'; import { IEnvironmentVariablesProvider } from '../variables/types'; import { ProcessService } from './proc'; -import { IBufferDecoder, IProcessLogger, IProcessService, IProcessServiceFactory } from './types'; +import { IProcessLogger, IProcessService, IProcessServiceFactory } from './types'; @injectable() export class ProcessServiceFactory implements IProcessServiceFactory { constructor( @inject(IEnvironmentVariablesProvider) private readonly envVarsService: IEnvironmentVariablesProvider, @inject(IProcessLogger) private readonly processLogger: IProcessLogger, - @inject(IBufferDecoder) private readonly decoder: IBufferDecoder, - @inject(IDisposableRegistry) private readonly disposableRegistry: IDisposableRegistry + @inject(IDisposableRegistry) private readonly disposableRegistry: IDisposableRegistry, ) {} - public async create(resource?: Uri): Promise { - const customEnvVars = await this.envVarsService.getEnvironmentVariables(resource); - const proc: IProcessService = new ProcessService(this.decoder, customEnvVars); + public async create(resource?: Uri, options?: { doNotUseCustomEnvs: boolean }): Promise { + const customEnvVars = options?.doNotUseCustomEnvs + ? undefined + : await this.envVarsService.getEnvironmentVariables(resource); + const proc: IProcessService = new ProcessService(customEnvVars); this.disposableRegistry.push(proc); return proc.on('exec', this.processLogger.logProcess.bind(this.processLogger)); } diff --git a/src/client/common/process/pythonDaemon.ts b/src/client/common/process/pythonDaemon.ts deleted file mode 100644 index 225d58804609..000000000000 --- a/src/client/common/process/pythonDaemon.ts +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { ChildProcess } from 'child_process'; -import { MessageConnection, RequestType, RequestType0 } from 'vscode-jsonrpc'; -import { PythonExecInfo } from '../../pythonEnvironments/exec'; -import { InterpreterInformation } from '../../pythonEnvironments/info'; -import { extractInterpreterInfo } from '../../pythonEnvironments/info/interpreter'; -import { traceWarning } from '../logger'; -import { IPlatformService } from '../platform/types'; -import { BasePythonDaemon } from './baseDaemon'; -import { InterpreterInfoJson } from './internal/scripts'; -import { - IPythonDaemonExecutionService, - IPythonExecutionService, - ObservableExecutionResult, - SpawnOptions -} from './types'; - -type ErrorResponse = { error?: string }; - -export class ConnectionClosedError extends Error { - constructor(public readonly message: string) { - super(); - } -} - -export class DaemonError extends Error { - constructor(public readonly message: string) { - super(); - } -} -export class PythonDaemonExecutionService extends BasePythonDaemon implements IPythonDaemonExecutionService { - constructor( - pythonExecutionService: IPythonExecutionService, - platformService: IPlatformService, - pythonPath: string, - proc: ChildProcess, - connection: MessageConnection - ) { - super(pythonExecutionService, platformService, pythonPath, proc, connection); - } - public async getInterpreterInformation(): Promise { - try { - this.throwIfRPCConnectionIsDead(); - const request = new RequestType0( - 'get_interpreter_information' - ); - const response = await this.sendRequestWithoutArgs(request); - if (response.error) { - throw Error(response.error); - } - return extractInterpreterInfo(this.pythonPath, response); - } catch (ex) { - traceWarning('Falling back to Python Execution Service due to failure in daemon', ex); - return this.pythonExecutionService.getInterpreterInformation(); - } - } - public async getExecutablePath(): Promise { - try { - this.throwIfRPCConnectionIsDead(); - type ExecutablePathResponse = ErrorResponse & { path: string }; - const request = new RequestType0('get_executable'); - const response = await this.sendRequestWithoutArgs(request); - if (response.error) { - throw new DaemonError(response.error); - } - return response.path; - } catch (ex) { - traceWarning('Falling back to Python Execution Service due to failure in daemon', ex); - return this.pythonExecutionService.getExecutablePath(); - } - } - public getExecutionInfo(pythonArgs?: string[]): PythonExecInfo { - return this.pythonExecutionService.getExecutionInfo(pythonArgs); - } - public async isModuleInstalled(moduleName: string): Promise { - try { - this.throwIfRPCConnectionIsDead(); - type ModuleInstalledResponse = ErrorResponse & { exists: boolean }; - const request = new RequestType<{ module_name: string }, ModuleInstalledResponse, void, void>( - 'is_module_installed' - ); - const response = await this.sendRequest(request, { module_name: moduleName }); - if (response.error) { - throw new DaemonError(response.error); - } - return response.exists; - } catch (ex) { - traceWarning('Falling back to Python Execution Service due to failure in daemon', ex); - return this.pythonExecutionService.isModuleInstalled(moduleName); - } - } - public execObservable(args: string[], options: SpawnOptions): ObservableExecutionResult { - if (this.isAlive && this.canExecFileUsingDaemon(args, options)) { - try { - return this.execAsObservable({ fileName: args[0] }, args.slice(1), options); - } catch (ex) { - if (ex instanceof DaemonError || ex instanceof ConnectionClosedError) { - traceWarning('Falling back to Python Execution Service due to failure in daemon', ex); - return this.pythonExecutionService.execObservable(args, options); - } else { - throw ex; - } - } - } else { - return this.pythonExecutionService.execObservable(args, options); - } - } - public execModuleObservable( - moduleName: string, - args: string[], - options: SpawnOptions - ): ObservableExecutionResult { - if (this.isAlive && this.canExecModuleUsingDaemon(moduleName, args, options)) { - try { - return this.execAsObservable({ moduleName }, args, options); - } catch (ex) { - if (ex instanceof DaemonError || ex instanceof ConnectionClosedError) { - traceWarning('Falling back to Python Execution Service due to failure in daemon', ex); - return this.pythonExecutionService.execModuleObservable(moduleName, args, options); - } else { - throw ex; - } - } - } else { - return this.pythonExecutionService.execModuleObservable(moduleName, args, options); - } - } -} diff --git a/src/client/common/process/pythonDaemonFactory.ts b/src/client/common/process/pythonDaemonFactory.ts deleted file mode 100644 index a4a450b95539..000000000000 --- a/src/client/common/process/pythonDaemonFactory.ts +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import { ChildProcess } from 'child_process'; -import * as path from 'path'; -import { - createMessageConnection, - MessageConnection, - RequestType, - StreamMessageReader, - StreamMessageWriter -} from 'vscode-jsonrpc/node'; - -import { EXTENSION_ROOT_DIR } from '../../constants'; -import { PYTHON_WARNINGS } from '../constants'; -import { traceDecorators, traceError } from '../logger'; -import { IPlatformService } from '../platform/types'; -import { IDisposable, IDisposableRegistry } from '../types'; -import { createDeferred } from '../utils/async'; -import { BasePythonDaemon } from './baseDaemon'; -import { PythonDaemonExecutionService } from './pythonDaemon'; -import { DaemonExecutionFactoryCreationOptions, IPythonDaemonExecutionService, IPythonExecutionService } from './types'; - -export class PythonDaemonFactory { - protected readonly envVariables: NodeJS.ProcessEnv; - protected readonly pythonPath: string; - constructor( - protected readonly disposables: IDisposableRegistry, - protected readonly options: DaemonExecutionFactoryCreationOptions, - protected readonly pythonExecutionService: IPythonExecutionService, - protected readonly platformService: IPlatformService, - protected readonly activatedEnvVariables?: NodeJS.ProcessEnv - ) { - if (!options.pythonPath) { - throw new Error('options.pythonPath is empty when it shoud not be'); - } - this.pythonPath = options.pythonPath; - // Setup environment variables for the daemon. - // The daemon must have access to the Python Module that'll run the daemon - // & also access to a Python package used for the JSON rpc comms. - const envPythonPath = `${path.join(EXTENSION_ROOT_DIR, 'pythonFiles')}${path.delimiter}${path.join( - EXTENSION_ROOT_DIR, - 'pythonFiles', - 'lib', - 'python' - )}`; - this.envVariables = this.activatedEnvVariables ? { ...this.activatedEnvVariables } : { ...process.env }; - this.envVariables.PYTHONPATH = this.envVariables.PYTHONPATH - ? `${this.envVariables.PYTHONPATH}${path.delimiter}${envPythonPath}` - : envPythonPath; - this.envVariables.PYTHONUNBUFFERED = '1'; - - // Always ignore warnings as the user should never see the output of the daemon running - this.envVariables[PYTHON_WARNINGS] = 'ignore'; - } - @traceDecorators.error('Failed to create daemon') - public async createDaemonService(): Promise { - // Add '--log-file=/Users/donjayamanne/Desktop/Development/vsc/pythonVSCode/daaemon.log' to log to a file. - const loggingArgs: string[] = ['-v']; // Log information messages or greater (see daemon.__main__.py for options). - - const args = (this.options.daemonModule ? [`--daemon-module=${this.options.daemonModule}`] : []).concat( - loggingArgs - ); - const env = this.envVariables; - const daemonProc = this.pythonExecutionService!.execModuleObservable( - 'vscode_datascience_helpers.daemon', - args, - { env } - ); - if (!daemonProc.proc) { - throw new Error('Failed to create Daemon Proc'); - } - const connection = this.createConnection(daemonProc.proc); - - connection.listen(); - let stdError = ''; - let procEndEx: Error | undefined; - daemonProc.proc.stderr.on('data', (data: string | Buffer) => { - data = typeof data === 'string' ? data : data.toString('utf8'); - stdError += data; - }); - daemonProc.proc.on('error', (ex) => (procEndEx = ex)); - - try { - await this.testDaemon(connection); - - const cls = this.options.daemonClass ?? PythonDaemonExecutionService; - const instance = new cls( - this.pythonExecutionService, - this.platformService, - this.pythonPath, - daemonProc.proc, - connection - ); - if (instance instanceof BasePythonDaemon) { - this.disposables.push(instance); - return (instance as unknown) as T; - } - throw new Error(`Daemon class ${cls.name} must inherit BasePythonDaemon.`); - } catch (ex) { - traceError('Failed to start the Daemon, StdErr: ', stdError); - traceError('Failed to start the Daemon, ProcEndEx', procEndEx || ex); - traceError('Failed to start the Daemon, Ex', ex); - throw ex; - } - } - /** - * Protected so we can override for testing purposes. - */ - protected createConnection(proc: ChildProcess) { - return createMessageConnection(new StreamMessageReader(proc.stdout), new StreamMessageWriter(proc.stdin)); - } - /** - * Tests whether a daemon is usable or not by checking whether it responds to a simple ping. - * If a daemon doesn't reply to a ping in 5s, then its deemed to be dead/not usable. - * - * @private - * @param {MessageConnection} connection - * @memberof PythonDaemonExecutionServicePool - */ - @traceDecorators.error('Pinging Daemon Failed') - protected async testDaemon(connection: MessageConnection) { - // If we don't get a reply to the ping in 5 seconds assume it will never work. Bomb out. - // At this point there should be some information logged in stderr of the daemon process. - const fail = createDeferred<{ pong: string }>(); - const timer = setTimeout(() => fail.reject(new Error('Timeout waiting for daemon to start')), 5_000); - const request = new RequestType<{ data: string }, { pong: string }, void, void>('ping'); - // Check whether the daemon has started correctly, by sending a ping. - const result = await Promise.race([fail.promise, connection.sendRequest(request, { data: 'hello' })]); - clearTimeout(timer); - if (result.pong !== 'hello') { - throw new Error(`Daemon did not reply to the ping, received: ${result.pong}`); - } - } -} diff --git a/src/client/common/process/pythonDaemonPool.ts b/src/client/common/process/pythonDaemonPool.ts deleted file mode 100644 index e82d52f4828d..000000000000 --- a/src/client/common/process/pythonDaemonPool.ts +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { IPlatformService } from '../../common/platform/types'; -import { PythonExecInfo } from '../../pythonEnvironments/exec'; -import { InterpreterInformation } from '../../pythonEnvironments/info'; -import { IDisposableRegistry } from '../types'; -import { sleep } from '../utils/async'; -import { noop } from '../utils/misc'; -import { StopWatch } from '../utils/stopWatch'; -import { ProcessService } from './proc'; -import { PythonDaemonExecutionService } from './pythonDaemon'; -import { PythonDaemonFactory } from './pythonDaemonFactory'; -import { - ExecutionResult, - IProcessLogger, - IPythonDaemonExecutionService, - IPythonExecutionService, - isDaemonPoolCreationOption, - ObservableExecutionResult, - PooledDaemonExecutionFactoryCreationOptions, - SpawnOptions -} from './types'; - -type DaemonType = 'StandardDaemon' | 'ObservableDaemon'; - -export class PythonDaemonExecutionServicePool extends PythonDaemonFactory implements IPythonDaemonExecutionService { - private readonly daemons: IPythonDaemonExecutionService[] = []; - private readonly observableDaemons: IPythonDaemonExecutionService[] = []; - private _disposed = false; - constructor( - private readonly logger: IProcessLogger, - disposables: IDisposableRegistry, - options: PooledDaemonExecutionFactoryCreationOptions, - pythonExecutionService: IPythonExecutionService, - platformService: IPlatformService, - activatedEnvVariables?: NodeJS.ProcessEnv, - private readonly timeoutWaitingForDaemon: number = 1_000 - ) { - super(disposables, options, pythonExecutionService, platformService, activatedEnvVariables); - this.disposables.push(this); - } - public async initialize() { - if (!isDaemonPoolCreationOption(this.options)) { - return; - } - const promises = Promise.all( - [ - // tslint:disable-next-line: prefer-array-literal - ...new Array(this.options.daemonCount ?? 2).keys() - ].map(() => this.addDaemonService('StandardDaemon')) - ); - const promises2 = Promise.all( - [ - // tslint:disable-next-line: prefer-array-literal - ...new Array(this.options.observableDaemonCount ?? 1).keys() - ].map(() => this.addDaemonService('ObservableDaemon')) - ); - - await Promise.all([promises, promises2]); - } - public dispose() { - this._disposed = true; - } - public async getInterpreterInformation(): Promise { - const msg = { args: ['GetPythonVersion'] }; - return this.wrapCall((daemon) => daemon.getInterpreterInformation(), msg); - } - public async getExecutablePath(): Promise { - const msg = { args: ['getExecutablePath'] }; - return this.wrapCall((daemon) => daemon.getExecutablePath(), msg); - } - public getExecutionInfo(pythonArgs?: string[]): PythonExecInfo { - return this.pythonExecutionService.getExecutionInfo(pythonArgs); - } - public async isModuleInstalled(moduleName: string): Promise { - const msg = { args: ['-m', moduleName] }; - return this.wrapCall((daemon) => daemon.isModuleInstalled(moduleName), msg); - } - public async exec(args: string[], options: SpawnOptions): Promise> { - const msg = { args, options }; - return this.wrapCall((daemon) => daemon.exec(args, options), msg); - } - public async execModule( - moduleName: string, - args: string[], - options: SpawnOptions - ): Promise> { - const msg = { args: ['-m', moduleName].concat(args), options }; - return this.wrapCall((daemon) => daemon.execModule(moduleName, args, options), msg); - } - public execObservable(args: string[], options: SpawnOptions): ObservableExecutionResult { - const msg = { args, options }; - return this.wrapObservableCall((daemon) => daemon.execObservable(args, options), msg); - } - public execModuleObservable( - moduleName: string, - args: string[], - options: SpawnOptions - ): ObservableExecutionResult { - const msg = { args: ['-m', moduleName].concat(args), options }; - return this.wrapObservableCall((daemon) => daemon.execModuleObservable(moduleName, args, options), msg); - } - /** - * Wrapper for all promise operations to be performed on a daemon. - * Gets a daemon from the pool, executes the required code, then returns the daemon back into the pool. - * - * @private - * @template T - * @param {(daemon: IPythonExecutionService) => Promise} cb - * @param daemonLogMessage - * @returns {Promise} - * @memberof PythonDaemonExecutionServicePool - */ - private async wrapCall( - cb: (daemon: IPythonExecutionService) => Promise, - daemonLogMessage: { args: string[]; options?: SpawnOptions } - ): Promise { - const daemon = await this.popDaemonFromPool(); - try { - // When using the daemon, log the message ourselves. - if (daemon instanceof PythonDaemonExecutionService) { - this.logger.logProcess(`${this.pythonPath} (daemon)`, daemonLogMessage.args, daemonLogMessage.options); - } - return await cb(daemon); - } finally { - this.pushDaemonIntoPool('StandardDaemon', daemon); - } - } - /** - * Wrapper for all observable operations to be performed on a daemon. - * Gets a daemon from the pool, executes the required code, then returns the daemon back into the pool. - * - * @private - * @param {(daemon: IPythonExecutionService) => ObservableExecutionResult} cb - * @param daemonLogMessage - * @returns {ObservableExecutionResult} - * @memberof PythonDaemonExecutionServicePool - */ - private wrapObservableCall( - cb: (daemon: IPythonExecutionService) => ObservableExecutionResult, - daemonLogMessage: { args: string[]; options?: SpawnOptions } - ): ObservableExecutionResult { - const execService = this.popDaemonFromObservablePool(); - // Possible the daemon returned is a standard python execution service. - const daemonProc = execService instanceof PythonDaemonExecutionService ? execService.proc : undefined; - - // When using the daemon, log the message ourselves. - if (daemonProc) { - this.logger.logProcess(`${this.pythonPath} (daemon)`, daemonLogMessage.args, daemonLogMessage.options); - } - const result = cb(execService); - let completed = false; - const completeHandler = () => { - if (completed) { - return; - } - completed = true; - if (!daemonProc || (!daemonProc.killed && ProcessService.isAlive(daemonProc.pid))) { - this.pushDaemonIntoPool('ObservableDaemon', execService); - } else if (!this._disposed) { - // Possible daemon is dead (explicitly killed or died due to some error). - this.addDaemonService('ObservableDaemon').ignoreErrors(); - } - }; - - if (daemonProc) { - daemonProc.on('exit', completeHandler); - daemonProc.on('close', completeHandler); - } - result.out.subscribe(noop, completeHandler, completeHandler); - - return result; - } - /** - * Adds a daemon into a pool. - * - * @private - * @param {DaemonType} type - * @memberof PythonDaemonExecutionServicePool - */ - private async addDaemonService(type: DaemonType) { - const daemon = await this.createDaemonService(); - const pool = type === 'StandardDaemon' ? this.daemons : this.observableDaemons; - pool.push(daemon); - } - /** - * Gets a daemon from a pool. - * If we're unable to get a daemon from a pool within 1s, then return the standard `PythonExecutionService`. - * The `PythonExecutionService` will spanw the required python process and do the needful. - * - * @private - * @returns {Promise} - * @memberof PythonDaemonExecutionServicePool - */ - private async popDaemonFromPool(): Promise { - const stopWatch = new StopWatch(); - while (this.daemons.length === 0 && stopWatch.elapsedTime <= this.timeoutWaitingForDaemon) { - await sleep(50); - } - return this.daemons.shift() ?? this.pythonExecutionService; - } - /** - * Gets a daemon from a pool for observable operations. - * If we're unable to get a daemon from a pool, then return the standard `PythonExecutionService`. - * The `PythonExecutionService` will spanw the required python process and do the needful. - * - * @private - * @returns {IPythonExecutionService} - * @memberof PythonDaemonExecutionServicePool - */ - private popDaemonFromObservablePool(): IPythonExecutionService { - if (this.observableDaemons.length > 0) { - return this.observableDaemons.shift()!; - } - return this.pythonExecutionService; - } - /** - * Pushes a daemon back into the pool. - * Before doing this, check whether the daemon is usable or not. - * If not, then create a new daemon and add it into the pool. - * - * @private - * @param {DaemonType} type - * @param {IPythonExecutionService} daemon - * @returns - * @memberof PythonDaemonExecutionServicePool - */ - private pushDaemonIntoPool(type: DaemonType, daemon: IPythonExecutionService) { - if (daemon === this.pythonExecutionService) { - return; - } - // Ensure we test the daemon before we push it back into the pool. - // Possible it is dead. - const testAndPushIntoPool = async () => { - const daemonService = daemon as PythonDaemonExecutionService; - let procIsDead = false; - if ( - !daemonService.isAlive || - daemonService.proc.killed || - !ProcessService.isAlive(daemonService.proc.pid) - ) { - procIsDead = true; - } else { - // Test sending a ping. - procIsDead = await this.testDaemon(daemonService.connection) - .then(() => false) - .catch(() => true); - } - if (procIsDead) { - // The process is dead, create a new daemon. - await this.addDaemonService(type); - try { - daemonService.dispose(); - } catch { - noop(); - } - } else { - const pool = type === 'StandardDaemon' ? this.daemons : this.observableDaemons; - pool.push(daemon as IPythonDaemonExecutionService); - } - }; - - testAndPushIntoPool().ignoreErrors(); - } -} diff --git a/src/client/common/process/pythonEnvironment.ts b/src/client/common/process/pythonEnvironment.ts index 2f3a86c953cb..cbf898ac5f50 100644 --- a/src/client/common/process/pythonEnvironment.ts +++ b/src/client/common/process/pythonEnvironment.ts @@ -1,17 +1,22 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { CondaEnvironmentInfo } from '../../pythonEnvironments/discovery/locators/services/conda'; +import * as path from 'path'; +import { traceError, traceVerbose } from '../../logging'; +import { Conda, CondaEnvironmentInfo } from '../../pythonEnvironments/common/environmentManagers/conda'; import { buildPythonExecInfo, PythonExecInfo } from '../../pythonEnvironments/exec'; import { InterpreterInformation } from '../../pythonEnvironments/info'; import { getExecutablePath } from '../../pythonEnvironments/info/executable'; import { getInterpreterInfo } from '../../pythonEnvironments/info/interpreter'; -import { traceError, traceInfo } from '../logger'; +import { isTestExecution } from '../constants'; import { IFileSystem } from '../platform/types'; import * as internalPython from './internal/python'; -import { ExecutionResult, IProcessService, ShellOptions, SpawnOptions } from './types'; +import { ExecutionResult, IProcessService, IPythonEnvironment, ShellOptions, SpawnOptions } from './types'; +import { PixiEnvironmentInfo } from '../../pythonEnvironments/common/environmentManagers/pixi'; -class PythonEnvironment { +const cachedExecutablePath: Map> = new Map>(); + +class PythonEnvironment implements IPythonEnvironment { private cachedInterpreterInformation: InterpreterInformation | undefined | null = null; constructor( @@ -23,17 +28,17 @@ class PythonEnvironment { isValidExecutable(python: string): Promise; // from ProcessService: exec(file: string, args: string[]): Promise>; - shellExec(command: string, timeout: number): Promise>; - } + shellExec(command: string, options?: ShellOptions): Promise>; + }, ) {} - public getExecutionInfo(pythonArgs: string[] = []): PythonExecInfo { + public getExecutionInfo(pythonArgs: string[] = [], pythonExecutable?: string): PythonExecInfo { const python = this.deps.getPythonArgv(this.pythonPath); - return buildPythonExecInfo(python, pythonArgs); + return buildPythonExecInfo(python, pythonArgs, pythonExecutable); } - public getExecutionObservableInfo(pythonArgs: string[] = []): PythonExecInfo { + public getExecutionObservableInfo(pythonArgs: string[] = [], pythonExecutable?: string): PythonExecInfo { const python = this.deps.getObservablePythonArgv(this.pythonPath); - return buildPythonExecInfo(python, pythonArgs); + return buildPythonExecInfo(python, pythonArgs, pythonExecutable); } public async getInterpreterInformation(): Promise { @@ -43,14 +48,34 @@ class PythonEnvironment { return this.cachedInterpreterInformation; } - public async getExecutablePath(): Promise { + public async getExecutablePath(): Promise { // If we've passed the python file, then return the file. // This is because on mac if using the interpreter /usr/bin/python2.7 we can get a different value for the path if (await this.deps.isValidExecutable(this.pythonPath)) { return this.pythonPath; } + const result = cachedExecutablePath.get(this.pythonPath); + if (result !== undefined && !isTestExecution()) { + // Another call for this environment has already been made, return its result + return result; + } const python = this.getExecutionInfo(); - return getExecutablePath(python, this.deps.exec); + const promise = getExecutablePath(python, this.deps.shellExec); + cachedExecutablePath.set(this.pythonPath, promise); + return promise; + } + + public async getModuleVersion(moduleName: string): Promise { + const [args, parse] = internalPython.getModuleVersion(moduleName); + const info = this.getExecutionInfo(args); + let data: ExecutionResult; + try { + data = await this.deps.exec(info.command, info.args); + } catch (ex) { + traceVerbose(`Error when getting version of module ${moduleName}`, ex); + return undefined; + } + return parse(data.stdout); } public async isModuleInstalled(moduleName: string): Promise { @@ -59,7 +84,8 @@ class PythonEnvironment { const info = this.getExecutionInfo(args); try { await this.deps.exec(info.command, info.args); - } catch { + } catch (ex) { + traceVerbose(`Error when checking if module is installed ${moduleName}`, ex); return false; } return true; @@ -68,7 +94,7 @@ class PythonEnvironment { private async getInterpreterInformationImpl(): Promise { try { const python = this.getExecutionInfo(); - return await getInterpreterInfo(python, this.deps.shellExec, { info: traceInfo, error: traceError }); + return await getInterpreterInfo(python, this.deps.shellExec, { verbose: traceVerbose, error: traceError }); } catch (ex) { traceError(`Failed to get interpreter information for '${this.pythonPath}'`, ex); } @@ -81,14 +107,25 @@ function createDeps( observablePythonArgv: string[] | undefined, // from ProcessService: exec: (file: string, args: string[], options?: SpawnOptions) => Promise>, - shellExec: (command: string, options?: ShellOptions) => Promise> + shellExec: (command: string, options?: ShellOptions) => Promise>, ) { return { - getPythonArgv: (python: string) => pythonArgv || [python], - getObservablePythonArgv: (python: string) => observablePythonArgv || [python], + getPythonArgv: (python: string) => { + if (path.basename(python) === python) { + // Say when python is `py -3.8` or `conda run python` + pythonArgv = python.split(' '); + } + return pythonArgv || [python]; + }, + getObservablePythonArgv: (python: string) => { + if (path.basename(python) === python) { + observablePythonArgv = python.split(' '); + } + return observablePythonArgv || [python]; + }, isValidExecutable, exec: async (cmd: string, args: string[]) => exec(cmd, args, { throwOnStdErr: true }), - shellExec: async (text: string, timeout: number) => shellExec(text, { timeout }) + shellExec, }; } @@ -96,60 +133,76 @@ export function createPythonEnv( pythonPath: string, // These are used to generate the deps. procs: IProcessService, - fs: IFileSystem + fs: IFileSystem, ): PythonEnvironment { const deps = createDeps( - async (filename) => fs.fileExists(filename), + async (filename) => fs.pathExists(filename), // We use the default: [pythonPath]. undefined, undefined, (file, args, opts) => procs.exec(file, args, opts), - (command, opts) => procs.shellExec(command, opts) + (command, opts) => procs.shellExec(command, opts), ); return new PythonEnvironment(pythonPath, deps); } -export function createCondaEnv( - condaFile: string, +export async function createCondaEnv( condaInfo: CondaEnvironmentInfo, - pythonPath: string, // These are used to generate the deps. procs: IProcessService, - fs: IFileSystem -): PythonEnvironment { - const runArgs = ['run']; - if (condaInfo.name === '') { - runArgs.push('-p', condaInfo.path); - } else { - runArgs.push('-n', condaInfo.name); + fs: IFileSystem, +): Promise { + const conda = await Conda.getConda(); + const pythonArgv = await conda?.getRunPythonArgs({ name: condaInfo.name, prefix: condaInfo.path }); + if (!pythonArgv) { + return undefined; } - const pythonArgv = [condaFile, ...runArgs, 'python']; const deps = createDeps( - async (filename) => fs.fileExists(filename), + async (filename) => fs.pathExists(filename), + pythonArgv, pythonArgv, - // tslint:disable-next-line:no-suspicious-comment - // TODO: Use pythonArgv here once 'conda run' can be - // run without buffering output. - // See https://github.com/microsoft/vscode-python/issues/8473. - undefined, (file, args, opts) => procs.exec(file, args, opts), - (command, opts) => procs.shellExec(command, opts) + (command, opts) => procs.shellExec(command, opts), ); - return new PythonEnvironment(pythonPath, deps); + const interpreterPath = await conda?.getInterpreterPathForEnvironment({ + name: condaInfo.name, + prefix: condaInfo.path, + }); + if (!interpreterPath) { + return undefined; + } + return new PythonEnvironment(interpreterPath, deps); } -export function createWindowsStoreEnv( +export async function createPixiEnv( + pixiEnv: PixiEnvironmentInfo, + // These are used to generate the deps. + procs: IProcessService, + fs: IFileSystem, +): Promise { + const pythonArgv = pixiEnv.pixi.getRunPythonArgs(pixiEnv.manifestPath, pixiEnv.envName); + const deps = createDeps( + async (filename) => fs.pathExists(filename), + pythonArgv, + pythonArgv, + (file, args, opts) => procs.exec(file, args, opts), + (command, opts) => procs.shellExec(command, opts), + ); + return new PythonEnvironment(pixiEnv.interpreterPath, deps); +} + +export function createMicrosoftStoreEnv( pythonPath: string, // These are used to generate the deps. - procs: IProcessService + procs: IProcessService, ): PythonEnvironment { const deps = createDeps( /** - * With windows store python apps, we have generally use the + * With microsoft store python apps, we have generally use the * symlinked python executable. The actual file is not accessible * by the user due to permission issues (& rest of exension fails * when using that executable). Hence lets not resolve the - * executable using sys.executable for windows store python + * executable using sys.executable for microsoft store python * interpreters. */ async (_f: string) => true, @@ -157,7 +210,7 @@ export function createWindowsStoreEnv( undefined, undefined, (file, args, opts) => procs.exec(file, args, opts), - (command, opts) => procs.shellExec(command, opts) + (command, opts) => procs.shellExec(command, opts), ); return new PythonEnvironment(pythonPath, deps); } diff --git a/src/client/common/process/pythonExecutionFactory.ts b/src/client/common/process/pythonExecutionFactory.ts index 1092b00236b0..efb05c3c9d12 100644 --- a/src/client/common/process/pythonExecutionFactory.ts +++ b/src/client/common/process/pythonExecutionFactory.ts @@ -1,238 +1,191 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. import { inject, injectable } from 'inversify'; -import { gte } from 'semver'; -import { Uri } from 'vscode'; -import { IPlatformService } from '../../common/platform/types'; import { IEnvironmentActivationService } from '../../interpreter/activation/types'; -import { ICondaService, IInterpreterService } from '../../interpreter/contracts'; -import { IWindowsStoreInterpreter } from '../../interpreter/locators/types'; +import { IActivatedEnvironmentLaunch, IComponentAdapter } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; -import { CondaEnvironmentInfo } from '../../pythonEnvironments/discovery/locators/services/conda'; -import { WindowsStoreInterpreter } from '../../pythonEnvironments/discovery/locators/services/windowsStoreInterpreter'; import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; -import { traceError } from '../logger'; import { IFileSystem } from '../platform/types'; -import { IConfigurationService, IDisposable, IDisposableRegistry } from '../types'; +import { IConfigurationService, IDisposableRegistry, IInterpreterPathService } from '../types'; import { ProcessService } from './proc'; -import { PythonDaemonFactory } from './pythonDaemonFactory'; -import { PythonDaemonExecutionServicePool } from './pythonDaemonPool'; -import { createCondaEnv, createPythonEnv, createWindowsStoreEnv } from './pythonEnvironment'; +import { createCondaEnv, createPythonEnv, createMicrosoftStoreEnv, createPixiEnv } from './pythonEnvironment'; import { createPythonProcessService } from './pythonProcess'; import { - DaemonExecutionFactoryCreationOptions, ExecutionFactoryCreateWithEnvironmentOptions, ExecutionFactoryCreationOptions, - IBufferDecoder, IProcessLogger, IProcessService, IProcessServiceFactory, - IPythonDaemonExecutionService, + IPythonEnvironment, IPythonExecutionFactory, IPythonExecutionService, - isDaemonPoolCreationOption } from './types'; - -// Minimum version number of conda required to be able to use 'conda run' -export const CONDA_RUN_VERSION = '4.6.0'; +import { IInterpreterAutoSelectionService } from '../../interpreter/autoSelection/types'; +import { sleep } from '../utils/async'; +import { traceError } from '../../logging'; +import { getPixi, getPixiEnvironmentFromInterpreter } from '../../pythonEnvironments/common/environmentManagers/pixi'; @injectable() export class PythonExecutionFactory implements IPythonExecutionFactory { - private readonly daemonsPerPythonService = new Map>(); private readonly disposables: IDisposableRegistry; + private readonly logger: IProcessLogger; + private readonly fileSystem: IFileSystem; + constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer, @inject(IEnvironmentActivationService) private readonly activationHelper: IEnvironmentActivationService, @inject(IProcessServiceFactory) private readonly processServiceFactory: IProcessServiceFactory, @inject(IConfigurationService) private readonly configService: IConfigurationService, - @inject(ICondaService) private readonly condaService: ICondaService, - @inject(IBufferDecoder) private readonly decoder: IBufferDecoder, - @inject(WindowsStoreInterpreter) private readonly windowsStoreInterpreter: IWindowsStoreInterpreter, - @inject(IPlatformService) private readonly platformService: IPlatformService + @inject(IComponentAdapter) private readonly pyenvs: IComponentAdapter, + @inject(IInterpreterAutoSelectionService) private readonly autoSelection: IInterpreterAutoSelectionService, + @inject(IInterpreterPathService) private readonly interpreterPathExpHelper: IInterpreterPathService, ) { // Acquire other objects here so that if we are called during dispose they are available. this.disposables = this.serviceContainer.get(IDisposableRegistry); this.logger = this.serviceContainer.get(IProcessLogger); this.fileSystem = this.serviceContainer.get(IFileSystem); } + public async create(options: ExecutionFactoryCreationOptions): Promise { - const pythonPath = options.pythonPath - ? options.pythonPath - : this.configService.getSettings(options.resource).pythonPath; + let { pythonPath } = options; + if (!pythonPath || pythonPath === 'python') { + const activatedEnvLaunch = this.serviceContainer.get( + IActivatedEnvironmentLaunch, + ); + await activatedEnvLaunch.selectIfLaunchedViaActivatedEnv(); + // If python path wasn't passed in, we need to auto select it and then read it + // from the configuration. + const interpreterPath = this.interpreterPathExpHelper.get(options.resource); + if (!interpreterPath || interpreterPath === 'python') { + // Block on autoselection if no interpreter selected. + // Note autoselection blocks on discovery, so we do not want discovery component + // to block on this code. Discovery component should 'options.pythonPath' before + // calling into this, so this scenario should not happen. But in case consumer + // makes such an error. So break the loop via timeout and log error. + const success = await Promise.race([ + this.autoSelection.autoSelectInterpreter(options.resource).then(() => true), + sleep(50000).then(() => false), + ]); + if (!success) { + traceError( + 'Autoselection timeout out, this is likely a issue with how consumer called execution factory API. Using default python to execute.', + ); + } + } + pythonPath = this.configService.getSettings(options.resource).pythonPath; + } const processService: IProcessService = await this.processServiceFactory.create(options.resource); - processService.on('exec', this.logger.logProcess.bind(this.logger)); - return createPythonService( - pythonPath, - processService, - this.fileSystem, - undefined, - await this.windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath) - ); - } + if (await getPixi()) { + const pixiExecutionService = await this.createPixiExecutionService(pythonPath, processService); + if (pixiExecutionService) { + return pixiExecutionService; + } + } - public async createDaemon( - options: DaemonExecutionFactoryCreationOptions - ): Promise { - const pythonPath = options.pythonPath - ? options.pythonPath - : this.configService.getSettings(options.resource).pythonPath; - const daemonPoolKey = `${pythonPath}#${options.daemonClass || ''}#${options.daemonModule || ''}`; - const interpreterService = this.serviceContainer.tryGet(IInterpreterService); - const interpreter = interpreterService - ? await interpreterService.getInterpreterDetails(pythonPath, options.resource) - : undefined; - const activatedProcPromise = this.createActivatedEnvironment({ - allowEnvironmentFetchExceptions: true, - interpreter: interpreter, - resource: options.resource, - bypassCondaExecution: true - }); - // No daemon support in Python 2.7 or during shutdown - if (!interpreterService || (interpreter?.version && interpreter.version.major < 3)) { - return activatedProcPromise; + const condaExecutionService = await this.createCondaExecutionService(pythonPath, processService); + if (condaExecutionService) { + return condaExecutionService; } - // Ensure we do not start multiple daemons for the same interpreter. - // Cache the promise. - const start = async (): Promise => { - const [activatedProc, activatedEnvVars] = await Promise.all([ - activatedProcPromise, - this.activationHelper.getActivatedEnvironmentVariables(options.resource, interpreter, true) - ]); - - if (isDaemonPoolCreationOption(options)) { - const daemon = new PythonDaemonExecutionServicePool( - this.logger, - this.disposables, - { ...options, pythonPath }, - activatedProc!, - this.platformService, - activatedEnvVars - ); - await daemon.initialize(); - this.disposables.push(daemon); - return (daemon as unknown) as T; - } else { - const factory = new PythonDaemonFactory( - this.disposables, - { ...options, pythonPath }, - activatedProc!, - this.platformService, - activatedEnvVars - ); - return factory.createDaemonService(); - } - }; + const windowsStoreInterpreterCheck = this.pyenvs.isMicrosoftStoreInterpreter.bind(this.pyenvs); - let promise: Promise; + const env = (await windowsStoreInterpreterCheck(pythonPath)) + ? createMicrosoftStoreEnv(pythonPath, processService) + : createPythonEnv(pythonPath, processService, this.fileSystem); - if (isDaemonPoolCreationOption(options)) { - // Ensure we do not create multiple daemon pools for the same python interpreter. - promise = (this.daemonsPerPythonService.get(daemonPoolKey) as unknown) as Promise; - if (!promise) { - promise = start(); - this.daemonsPerPythonService.set(daemonPoolKey, promise as Promise); - } - } else { - promise = start(); - } - return promise.catch((ex) => { - // Ok, we failed to create the daemon (or failed to start). - // What ever the cause, we need to log this & give a standard IPythonExecutionService - traceError('Failed to create the daemon service, defaulting to activated environment', ex); - this.daemonsPerPythonService.delete(daemonPoolKey); - return (activatedProcPromise as unknown) as T; - }); + return createPythonService(processService, env); } + public async createActivatedEnvironment( - options: ExecutionFactoryCreateWithEnvironmentOptions + options: ExecutionFactoryCreateWithEnvironmentOptions, ): Promise { const envVars = await this.activationHelper.getActivatedEnvironmentVariables( options.resource, options.interpreter, - options.allowEnvironmentFetchExceptions + options.allowEnvironmentFetchExceptions, ); const hasEnvVars = envVars && Object.keys(envVars).length > 0; sendTelemetryEvent(EventName.PYTHON_INTERPRETER_ACTIVATION_ENVIRONMENT_VARIABLES, undefined, { hasEnvVars }); if (!hasEnvVars) { return this.create({ resource: options.resource, - pythonPath: options.interpreter ? options.interpreter.path : undefined + pythonPath: options.interpreter ? options.interpreter.path : undefined, }); } const pythonPath = options.interpreter ? options.interpreter.path : this.configService.getSettings(options.resource).pythonPath; - const processService: IProcessService = new ProcessService(this.decoder, { ...envVars }); + const processService: IProcessService = new ProcessService({ ...envVars }); processService.on('exec', this.logger.logProcess.bind(this.logger)); this.disposables.push(processService); - return createPythonService(pythonPath, processService, this.fileSystem); + if (await getPixi()) { + const pixiExecutionService = await this.createPixiExecutionService(pythonPath, processService); + if (pixiExecutionService) { + return pixiExecutionService; + } + } + + const condaExecutionService = await this.createCondaExecutionService(pythonPath, processService); + if (condaExecutionService) { + return condaExecutionService; + } + + const env = createPythonEnv(pythonPath, processService, this.fileSystem); + return createPythonService(processService, env); } - // Not using this function for now because there are breaking issues with conda run (conda 4.8, PVSC 2020.1). - // See https://github.com/microsoft/vscode-python/issues/9490 + public async createCondaExecutionService( pythonPath: string, - processService?: IProcessService, - resource?: Uri + processService: IProcessService, ): Promise { - const processServicePromise = processService - ? Promise.resolve(processService) - : this.processServiceFactory.create(resource); - const [condaVersion, condaEnvironment, condaFile, procService] = await Promise.all([ - this.condaService.getCondaVersion(), - this.condaService.getCondaEnvironment(pythonPath), - this.condaService.getCondaFile(), - processServicePromise - ]); - - if (condaVersion && gte(condaVersion, CONDA_RUN_VERSION) && condaEnvironment && condaFile && procService) { - // Add logging to the newly created process service - if (!processService) { - procService.on('exec', this.logger.logProcess.bind(this.logger)); - this.disposables.push(procService); - } - return createPythonService( - pythonPath, - procService, - this.fileSystem, - // This is what causes a CondaEnvironment to be returned: - [condaFile, condaEnvironment] - ); + const condaLocatorService = this.serviceContainer.get(IComponentAdapter); + const [condaEnvironment] = await Promise.all([condaLocatorService.getCondaEnvironment(pythonPath)]); + if (!condaEnvironment) { + return undefined; } + const env = await createCondaEnv(condaEnvironment, processService, this.fileSystem); + if (!env) { + return undefined; + } + return createPythonService(processService, env); + } - return Promise.resolve(undefined); + public async createPixiExecutionService( + pythonPath: string, + processService: IProcessService, + ): Promise { + const pixiEnvironment = await getPixiEnvironmentFromInterpreter(pythonPath); + if (!pixiEnvironment) { + return undefined; + } + + const env = await createPixiEnv(pixiEnvironment, processService, this.fileSystem); + if (env) { + return createPythonService(processService, env); + } + + return undefined; } } -function createPythonService( - pythonPath: string, - procService: IProcessService, - fs: IFileSystem, - conda?: [string, CondaEnvironmentInfo], - isWindowsStore?: boolean -): IPythonExecutionService { - let env = createPythonEnv(pythonPath, procService, fs); - if (conda) { - const [condaPath, condaInfo] = conda; - env = createCondaEnv(condaPath, condaInfo, pythonPath, procService, fs); - } else if (isWindowsStore) { - env = createWindowsStoreEnv(pythonPath, procService); - } +function createPythonService(procService: IProcessService, env: IPythonEnvironment): IPythonExecutionService { const procs = createPythonProcessService(procService, env); return { getInterpreterInformation: () => env.getInterpreterInformation(), getExecutablePath: () => env.getExecutablePath(), isModuleInstalled: (m) => env.isModuleInstalled(m), + getModuleVersion: (m) => env.getModuleVersion(m), getExecutionInfo: (a) => env.getExecutionInfo(a), execObservable: (a, o) => procs.execObservable(a, o), execModuleObservable: (m, a, o) => procs.execModuleObservable(m, a, o), exec: (a, o) => procs.exec(a, o), - execModule: (m, a, o) => procs.execModule(m, a, o) + execModule: (m, a, o) => procs.execModule(m, a, o), + execForLinter: (m, a, o) => procs.execForLinter(m, a, o), }; } diff --git a/src/client/common/process/pythonProcess.ts b/src/client/common/process/pythonProcess.ts index b6ce351ee318..f4d1de8883ba 100644 --- a/src/client/common/process/pythonProcess.ts +++ b/src/client/common/process/pythonProcess.ts @@ -5,7 +5,7 @@ import { PythonExecInfo } from '../../pythonEnvironments/exec'; import { ErrorUtils } from '../errors/errorUtils'; import { ModuleNotInstalledError } from '../errors/moduleNotInstalledError'; import * as internalPython from './internal/python'; -import { ExecutionResult, IProcessService, ObservableExecutionResult, SpawnOptions } from './types'; +import { ExecutionResult, IProcessService, IPythonEnvironment, ObservableExecutionResult, SpawnOptions } from './types'; class PythonProcessService { constructor( @@ -18,7 +18,7 @@ class PythonProcessService { // from ProcessService: exec(file: string, args: string[], options: SpawnOptions): Promise>; execObservable(file: string, args: string[], options: SpawnOptions): ObservableExecutionResult; - } + }, ) {} public execObservable(args: string[], options: SpawnOptions): ObservableExecutionResult { @@ -30,7 +30,7 @@ class PythonProcessService { public execModuleObservable( moduleName: string, moduleArgs: string[], - options: SpawnOptions + options: SpawnOptions, ): ObservableExecutionResult { const args = internalPython.execModule(moduleName, moduleArgs); const opts: SpawnOptions = { ...options }; @@ -47,7 +47,7 @@ class PythonProcessService { public async execModule( moduleName: string, moduleArgs: string[], - options: SpawnOptions + options: SpawnOptions, ): Promise> { const args = internalPython.execModule(moduleName, moduleArgs); const opts: SpawnOptions = { ...options }; @@ -64,16 +64,32 @@ class PythonProcessService { return result; } + + public async execForLinter( + moduleName: string, + args: string[], + options: SpawnOptions, + ): Promise> { + const opts: SpawnOptions = { ...options }; + const executable = this.deps.getExecutionInfo(args); + const result = await this.deps.exec(executable.command, executable.args, opts); + + // If a module is not installed we'll have something in stderr. + if (moduleName && ErrorUtils.outputHasModuleNotInstalledError(moduleName, result.stderr)) { + const isInstalled = await this.deps.isModuleInstalled(moduleName); + if (!isInstalled) { + throw new ModuleNotInstalledError(moduleName); + } + } + + return result; + } } export function createPythonProcessService( procs: IProcessService, // from PythonEnvironment: - env: { - getExecutionInfo(pythonArgs?: string[]): PythonExecInfo; - getExecutionObservableInfo(pythonArgs?: string[]): PythonExecInfo; - isModuleInstalled(moduleName: string): Promise; - } + env: IPythonEnvironment, ) { const deps = { // from PythonService: @@ -82,7 +98,7 @@ export function createPythonProcessService( getExecutionObservableInfo: (a?: string[]) => env.getExecutionObservableInfo(a), // from ProcessService: exec: async (f: string, a: string[], o: SpawnOptions) => procs.exec(f, a, o), - execObservable: (f: string, a: string[], o: SpawnOptions) => procs.execObservable(f, a, o) + execObservable: (f: string, a: string[], o: SpawnOptions) => procs.execObservable(f, a, o), }; return new PythonProcessService(deps); } diff --git a/src/client/common/process/pythonToolService.ts b/src/client/common/process/pythonToolService.ts index cb1068651c82..136ab56fe0c4 100644 --- a/src/client/common/process/pythonToolService.ts +++ b/src/client/common/process/pythonToolService.ts @@ -11,7 +11,7 @@ import { IPythonExecutionFactory, IPythonToolExecutionService, ObservableExecutionResult, - SpawnOptions + SpawnOptions, } from './types'; @injectable() @@ -20,7 +20,7 @@ export class PythonToolExecutionService implements IPythonToolExecutionService { public async execObservable( executionInfo: ExecutionInfo, options: SpawnOptions, - resource: Uri + resource: Uri, ): Promise> { if (options.env) { throw new Error('Environment variables are not supported'); @@ -40,7 +40,7 @@ export class PythonToolExecutionService implements IPythonToolExecutionService { public async exec( executionInfo: ExecutionInfo, options: SpawnOptions, - resource: Uri + resource: Uri, ): Promise> { if (options.env) { throw new Error('Environment variables are not supported'); @@ -49,7 +49,7 @@ export class PythonToolExecutionService implements IPythonToolExecutionService { const pythonExecutionService = await this.serviceContainer .get(IPythonExecutionFactory) .create({ resource }); - return pythonExecutionService.execModule(executionInfo.moduleName!, executionInfo.args, options); + return pythonExecutionService.execModule(executionInfo.moduleName, executionInfo.args, options); } else { const processService = await this.serviceContainer .get(IProcessServiceFactory) @@ -57,4 +57,23 @@ export class PythonToolExecutionService implements IPythonToolExecutionService { return processService.exec(executionInfo.execPath!, executionInfo.args, { ...options }); } } + + public async execForLinter( + executionInfo: ExecutionInfo, + options: SpawnOptions, + resource: Uri, + ): Promise> { + if (options.env) { + throw new Error('Environment variables are not supported'); + } + const pythonExecutionService = await this.serviceContainer + .get(IPythonExecutionFactory) + .create({ resource }); + + if (executionInfo.execPath) { + return pythonExecutionService.exec(executionInfo.args, options); + } + + return pythonExecutionService.execForLinter(executionInfo.moduleName!, executionInfo.args, options); + } } diff --git a/src/client/common/process/rawProcessApis.ts b/src/client/common/process/rawProcessApis.ts new file mode 100644 index 000000000000..864191851c91 --- /dev/null +++ b/src/client/common/process/rawProcessApis.ts @@ -0,0 +1,322 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { exec, execSync, spawn } from 'child_process'; +import { Readable } from 'stream'; +import { Observable } from 'rxjs/Observable'; +import { IDisposable } from '../types'; +import { createDeferred } from '../utils/async'; +import { EnvironmentVariables } from '../variables/types'; +import { DEFAULT_ENCODING } from './constants'; +import { ExecutionResult, ObservableExecutionResult, Output, ShellOptions, SpawnOptions, StdErrError } from './types'; +import { noop } from '../utils/misc'; +import { decodeBuffer } from './decoder'; +import { traceVerbose } from '../../logging'; +import { WorkspaceService } from '../application/workspace'; +import { ProcessLogger } from './logger'; + +const PS_ERROR_SCREEN_BOGUS = /your [0-9]+x[0-9]+ screen size is bogus\. expect trouble/; + +function getDefaultOptions(options: T, defaultEnv?: EnvironmentVariables): T { + const defaultOptions = { ...options }; + const execOptions = defaultOptions as SpawnOptions; + if (execOptions) { + execOptions.encoding = + typeof execOptions.encoding === 'string' && execOptions.encoding.length > 0 + ? execOptions.encoding + : DEFAULT_ENCODING; + const { encoding } = execOptions; + delete execOptions.encoding; + execOptions.encoding = encoding; + } + if (!defaultOptions.env || Object.keys(defaultOptions.env).length === 0) { + const env = defaultEnv || process.env; + defaultOptions.env = { ...env }; + } else { + defaultOptions.env = { ...defaultOptions.env }; + } + + if (execOptions && execOptions.extraVariables) { + defaultOptions.env = { ...defaultOptions.env, ...execOptions.extraVariables }; + } + + // Always ensure we have unbuffered output. + defaultOptions.env.PYTHONUNBUFFERED = '1'; + if (!defaultOptions.env.PYTHONIOENCODING) { + defaultOptions.env.PYTHONIOENCODING = 'utf-8'; + } + + return defaultOptions; +} + +export function shellExec( + command: string, + options: ShellOptions & { doNotLog?: boolean } = {}, + defaultEnv?: EnvironmentVariables, + disposables?: Set, +): Promise> { + const shellOptions = getDefaultOptions(options, defaultEnv); + if (!options.doNotLog) { + const processLogger = new ProcessLogger(new WorkspaceService()); + const loggingOptions = { ...shellOptions, encoding: shellOptions.encoding ?? undefined }; + processLogger.logProcess(command, undefined, loggingOptions); + } + return new Promise((resolve, reject) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const callback = (e: any, stdout: any, stderr: any) => { + if (e && e !== null) { + reject(e); + } else if (shellOptions.throwOnStdErr && stderr && stderr.length) { + reject(new Error(stderr)); + } else { + stdout = filterOutputUsingCondaRunMarkers(stdout); + // Make sure stderr is undefined if we actually had none. This is checked + // elsewhere because that's how exec behaves. + resolve({ stderr: stderr && stderr.length > 0 ? stderr : undefined, stdout }); + } + }; + let procExited = false; + const proc = exec(command, shellOptions, callback); // NOSONAR + proc.once('close', () => { + procExited = true; + }); + proc.once('exit', () => { + procExited = true; + }); + proc.once('error', () => { + procExited = true; + }); + const disposable: IDisposable = { + dispose: () => { + // If process has not exited nor killed, force kill it. + if (!procExited && !proc.killed) { + if (proc.pid) { + killPid(proc.pid); + } else { + proc.kill(); + } + } + }, + }; + if (disposables) { + disposables.add(disposable); + } + }); +} + +export function plainExec( + file: string, + args: string[], + options: SpawnOptions & { doNotLog?: boolean } = {}, + defaultEnv?: EnvironmentVariables, + disposables?: Set, +): Promise> { + const spawnOptions = getDefaultOptions(options, defaultEnv); + const encoding = spawnOptions.encoding ? spawnOptions.encoding : 'utf8'; + if (!options.doNotLog) { + const processLogger = new ProcessLogger(new WorkspaceService()); + processLogger.logProcess(file, args, options); + } + const proc = spawn(file, args, spawnOptions); + // Listen to these errors (unhandled errors in streams tears down the process). + // Errors will be bubbled up to the `error` event in `proc`, hence no need to log. + proc.stdout?.on('error', noop); + proc.stderr?.on('error', noop); + const deferred = createDeferred>(); + const disposable: IDisposable = { + dispose: () => { + // If process has not exited nor killed, force kill it. + if (!proc.killed && !deferred.completed) { + if (proc.pid) { + killPid(proc.pid); + } else { + proc.kill(); + } + } + }, + }; + disposables?.add(disposable); + const internalDisposables: IDisposable[] = []; + + // eslint-disable-next-line @typescript-eslint/ban-types + const on = (ee: Readable | null, name: string, fn: Function) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ee?.on(name, fn as any); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + internalDisposables.push({ dispose: () => ee?.removeListener(name, fn as any) as any }); + }; + + if (options.token) { + internalDisposables.push(options.token.onCancellationRequested(disposable.dispose)); + } + + const stdoutBuffers: Buffer[] = []; + on(proc.stdout, 'data', (data: Buffer) => { + stdoutBuffers.push(data); + options.outputChannel?.append(data.toString()); + }); + const stderrBuffers: Buffer[] = []; + on(proc.stderr, 'data', (data: Buffer) => { + if (options.mergeStdOutErr) { + stdoutBuffers.push(data); + stderrBuffers.push(data); + } else { + stderrBuffers.push(data); + } + options.outputChannel?.append(data.toString()); + }); + + proc.once('close', () => { + if (deferred.completed) { + return; + } + const stderr: string | undefined = + stderrBuffers.length === 0 ? undefined : decodeBuffer(stderrBuffers, encoding); + if ( + stderr && + stderr.length > 0 && + options.throwOnStdErr && + // ignore this specific error silently; see this issue for context: https://github.com/microsoft/vscode/issues/75932 + !(PS_ERROR_SCREEN_BOGUS.test(stderr) && stderr.replace(PS_ERROR_SCREEN_BOGUS, '').trim().length === 0) + ) { + deferred.reject(new StdErrError(stderr)); + } else { + let stdout = decodeBuffer(stdoutBuffers, encoding); + stdout = filterOutputUsingCondaRunMarkers(stdout); + deferred.resolve({ stdout, stderr }); + } + internalDisposables.forEach((d) => d.dispose()); + disposable.dispose(); + }); + proc.once('error', (ex) => { + deferred.reject(ex); + internalDisposables.forEach((d) => d.dispose()); + disposable.dispose(); + }); + + return deferred.promise; +} + +function filterOutputUsingCondaRunMarkers(stdout: string) { + // These markers are added if conda run is used or `interpreterInfo.py` is + // run, see `get_output_via_markers.py`. + const regex = />>>PYTHON-EXEC-OUTPUT([\s\S]*)<<= 2 ? match[1].trim() : undefined; + return filteredOut !== undefined ? filteredOut : stdout; +} + +function removeCondaRunMarkers(out: string) { + out = out.replace('>>>PYTHON-EXEC-OUTPUT\r\n', '').replace('>>>PYTHON-EXEC-OUTPUT\n', ''); + return out.replace('<<, +): ObservableExecutionResult { + const spawnOptions = getDefaultOptions(options, defaultEnv); + const encoding = spawnOptions.encoding ? spawnOptions.encoding : 'utf8'; + if (!options.doNotLog) { + const processLogger = new ProcessLogger(new WorkspaceService()); + processLogger.logProcess(file, args, options); + } + const proc = spawn(file, args, spawnOptions); + let procExited = false; + const disposable: IDisposable = { + dispose() { + if (proc && proc.pid && !proc.killed && !procExited) { + killPid(proc.pid); + } + if (proc) { + proc.unref(); + } + }, + }; + disposables?.add(disposable); + + const output = new Observable>((subscriber) => { + const internalDisposables: IDisposable[] = []; + + // eslint-disable-next-line @typescript-eslint/ban-types + const on = (ee: Readable | null, name: string, fn: Function) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ee?.on(name, fn as any); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + internalDisposables.push({ dispose: () => ee?.removeListener(name, fn as any) as any }); + }; + + if (options.token) { + internalDisposables.push( + options.token.onCancellationRequested(() => { + if (!procExited && !proc.killed) { + if (proc.pid) { + killPid(proc.pid); + } else { + proc.kill(); + } + procExited = true; + } + }), + ); + } + + const sendOutput = (source: 'stdout' | 'stderr', data: Buffer) => { + let out = decodeBuffer([data], encoding); + if (source === 'stderr' && options.throwOnStdErr) { + subscriber.error(new StdErrError(out)); + } else { + // Because all of output is not retrieved at once, filtering out the + // actual output using markers is not possible. Hence simply remove + // the markers and return original output. + out = removeCondaRunMarkers(out); + subscriber.next({ source, out }); + } + }; + + on(proc.stdout, 'data', (data: Buffer) => sendOutput('stdout', data)); + on(proc.stderr, 'data', (data: Buffer) => sendOutput('stderr', data)); + + proc.once('close', () => { + procExited = true; + subscriber.complete(); + internalDisposables.forEach((d) => d.dispose()); + }); + proc.once('exit', () => { + procExited = true; + subscriber.complete(); + internalDisposables.forEach((d) => d.dispose()); + }); + proc.once('error', (ex) => { + procExited = true; + subscriber.error(ex); + internalDisposables.forEach((d) => d.dispose()); + }); + if (options.stdinStr !== undefined) { + proc.stdin?.write(options.stdinStr); + proc.stdin?.end(); + } + }); + + return { + proc, + out: output, + dispose: disposable.dispose, + }; +} + +export function killPid(pid: number): void { + try { + if (process.platform === 'win32') { + // Windows doesn't support SIGTERM, so execute taskkill to kill the process + execSync(`taskkill /pid ${pid} /T /F`); // NOSONAR + } else { + process.kill(pid); + } + } catch { + traceVerbose('Unable to kill process with pid', pid); + } +} diff --git a/src/client/common/process/serviceRegistry.ts b/src/client/common/process/serviceRegistry.ts index 27684a20cc32..0ea57231148a 100644 --- a/src/client/common/process/serviceRegistry.ts +++ b/src/client/common/process/serviceRegistry.ts @@ -2,14 +2,12 @@ // Licensed under the MIT License. import { IServiceManager } from '../../ioc/types'; -import { BufferDecoder } from './decoder'; import { ProcessServiceFactory } from './processFactory'; import { PythonExecutionFactory } from './pythonExecutionFactory'; import { PythonToolExecutionService } from './pythonToolService'; -import { IBufferDecoder, IProcessServiceFactory, IPythonExecutionFactory, IPythonToolExecutionService } from './types'; +import { IProcessServiceFactory, IPythonExecutionFactory, IPythonToolExecutionService } from './types'; export function registerTypes(serviceManager: IServiceManager) { - serviceManager.addSingleton(IBufferDecoder, BufferDecoder); serviceManager.addSingleton(IProcessServiceFactory, ProcessServiceFactory); serviceManager.addSingleton(IPythonExecutionFactory, PythonExecutionFactory); serviceManager.addSingleton(IPythonToolExecutionService, PythonToolExecutionService); diff --git a/src/client/common/process/types.ts b/src/client/common/process/types.ts index df1a030d48c3..9263e69cbe21 100644 --- a/src/client/common/process/types.ts +++ b/src/client/common/process/types.ts @@ -3,18 +3,10 @@ import { ChildProcess, ExecOptions, SpawnOptions as ChildProcessSpawnOptions } from 'child_process'; import { Observable } from 'rxjs/Observable'; -import { CancellationToken, Uri } from 'vscode'; - -import { Newable } from '../../ioc/types'; +import { CancellationToken, OutputChannel, Uri } from 'vscode'; import { PythonExecInfo } from '../../pythonEnvironments/exec'; import { InterpreterInformation, PythonEnvironment } from '../../pythonEnvironments/info'; import { ExecutionInfo, IDisposable } from '../types'; -import { EnvironmentVariables } from '../variables/types'; - -export const IBufferDecoder = Symbol('IBufferDecoder'); -export interface IBufferDecoder { - decode(buffers: Buffer[], encoding: string): string; -} export type Output = { source: 'stdout' | 'stderr'; @@ -26,17 +18,18 @@ export type ObservableExecutionResult = { dispose(): void; }; -// tslint:disable-next-line:interface-name export type SpawnOptions = ChildProcessSpawnOptions & { encoding?: string; token?: CancellationToken; mergeStdOutErr?: boolean; throwOnStdErr?: boolean; extraVariables?: NodeJS.ProcessEnv; + outputChannel?: OutputChannel; + stdinStr?: string; + useWorker?: boolean; }; -// tslint:disable-next-line:interface-name -export type ShellOptions = ExecOptions & { throwOnStdErr?: boolean }; +export type ShellOptions = ExecOptions & { throwOnStdErr?: boolean; useWorker?: boolean }; export type ExecutionResult = { stdout: T; @@ -45,7 +38,12 @@ export type ExecutionResult = { export const IProcessLogger = Symbol('IProcessLogger'); export interface IProcessLogger { - logProcess(file: string, ars: string[], options?: SpawnOptions): void; + /** + * Pass `args` as `undefined` if first argument is supposed to be a shell command. + * Note it is assumed that command args are always quoted and respect + * `String.prototype.toCommandArgument()` prototype. + */ + logProcess(fileOrCommand: string, args?: string[], options?: SpawnOptions): void; } export interface IProcessService extends IDisposable { @@ -58,7 +56,7 @@ export interface IProcessService extends IDisposable { export const IProcessServiceFactory = Symbol('IProcessServiceFactory'); export interface IProcessServiceFactory { - create(resource?: Uri): Promise; + create(resource?: Uri, options?: { doNotUseCustomEnvs: boolean }): Promise; } export const IPythonExecutionFactory = Symbol('IPythonExecutionFactory'); @@ -66,68 +64,6 @@ export type ExecutionFactoryCreationOptions = { resource?: Uri; pythonPath?: string; }; -export function isDaemonPoolCreationOption( - options: PooledDaemonExecutionFactoryCreationOptions | DedicatedDaemonExecutionFactoryCreationOptions -): options is PooledDaemonExecutionFactoryCreationOptions { - if ('dedicated' in options && options.dedicated === true) { - return false; - } else { - return true; - } -} - -// This daemon will belong to a daemon pool (i.e it goes back into a pool for re-use). -export type PooledDaemonExecutionFactoryCreationOptions = ExecutionFactoryCreationOptions & { - /** - * Python file that implements the daemon. - * - * @type {string} - */ - daemonModule?: string; - /** - * Typescript Daemon class (client) that maps to the Python daemon. - * Defaults to `PythonDaemonExecutionService`. - * Any other class provided must extend `PythonDaemonExecutionService`. - * - * @type {Newable} - */ - daemonClass?: Newable; - /** - * Number of daemons to be created for standard synchronous operations such as - * checking if a module is installed, running a module, running a python file, etc. - * Defaults to `2`. - * - */ - daemonCount?: number; - /** - * Number of daemons to be created for operations such as execObservable, execModuleObservale. - * These operations are considered to be long running compared to checking if a module is installed. - * Hence a separate daemon will be created for this. - * Defaults to `1`. - * - */ - observableDaemonCount?: number; -}; -// This daemon will not belong to a daemon pool (i.e its a dedicated daemon and cannot be re-used). -export type DedicatedDaemonExecutionFactoryCreationOptions = ExecutionFactoryCreationOptions & { - /** - * Python file that implements the daemon. - */ - daemonModule?: string; - /** - * Typescript Daemon class (client) that maps to the Python daemon. - * Defaults to `PythonDaemonExecutionService`. - * Any other class provided must extend `PythonDaemonExecutionService`. - */ - daemonClass?: Newable; - /** - * This flag indicates it is a dedicated daemon. - */ - dedicated: true; -}; -export type DaemonExecutionFactoryCreationOptions = - | PooledDaemonExecutionFactoryCreationOptions - | DedicatedDaemonExecutionFactoryCreationOptions; export type ExecutionFactoryCreateWithEnvironmentOptions = { resource?: Uri; interpreter?: PythonEnvironment; @@ -138,36 +74,22 @@ export type ExecutionFactoryCreateWithEnvironmentOptions = { * * @type {boolean} */ - bypassCondaExecution?: boolean; }; export interface IPythonExecutionFactory { create(options: ExecutionFactoryCreationOptions): Promise; - /** - * Creates a daemon Python Process. - * On windows it's cheaper to create a daemon and use that than spin up Python Processes everytime. - * If something cannot be executed within the daemon, it will resort to using the standard IPythonExecutionService. - * Note: The returned execution service is always using an activated environment. - * - * @param {ExecutionFactoryCreationOptions} options - * @returns {(Promise)} - * @memberof IPythonExecutionFactory - */ - createDaemon( - options: DaemonExecutionFactoryCreationOptions - ): Promise; createActivatedEnvironment(options: ExecutionFactoryCreateWithEnvironmentOptions): Promise; createCondaExecutionService( pythonPath: string, - processService?: IProcessService, - resource?: Uri + processService: IProcessService, ): Promise; } export const IPythonExecutionService = Symbol('IPythonExecutionService'); export interface IPythonExecutionService { getInterpreterInformation(): Promise; - getExecutablePath(): Promise; + getExecutablePath(): Promise; isModuleInstalled(moduleName: string): Promise; + getModuleVersion(moduleName: string): Promise; getExecutionInfo(pythonArgs?: string[]): PythonExecInfo; execObservable(args: string[], options: SpawnOptions): ObservableExecutionResult; @@ -175,18 +97,19 @@ export interface IPythonExecutionService { exec(args: string[], options: SpawnOptions): Promise>; execModule(moduleName: string, args: string[], options: SpawnOptions): Promise>; + execForLinter(moduleName: string, args: string[], options: SpawnOptions): Promise>; } -/** - * Identical to the PythonExecutionService, but with a `dispose` method. - * This is a daemon process that lives on until it is disposed, hence the `IDisposable`. - * - * @export - * @interface IPythonDaemonExecutionService - * @extends {IPythonExecutionService} - * @extends {IDisposable} - */ -export interface IPythonDaemonExecutionService extends IPythonExecutionService, IDisposable {} +export interface IPythonEnvironment { + getInterpreterInformation(): Promise; + getExecutionObservableInfo(pythonArgs?: string[], pythonExecutable?: string): PythonExecInfo; + getExecutablePath(): Promise; + isModuleInstalled(moduleName: string): Promise; + getModuleVersion(moduleName: string): Promise; + getExecutionInfo(pythonArgs?: string[], pythonExecutable?: string): PythonExecInfo; +} + +export type ShellExecFunc = (command: string, options?: ShellOptions | undefined) => Promise>; export class StdErrError extends Error { constructor(message: string) { @@ -194,17 +117,14 @@ export class StdErrError extends Error { } } -export interface IExecutionEnvironmentVariablesService { - getEnvironmentVariables(resource?: Uri): Promise; -} - export const IPythonToolExecutionService = Symbol('IPythonToolRunnerService'); export interface IPythonToolExecutionService { execObservable( executionInfo: ExecutionInfo, options: SpawnOptions, - resource: Uri + resource: Uri, ): Promise>; exec(executionInfo: ExecutionInfo, options: SpawnOptions, resource: Uri): Promise>; + execForLinter(executionInfo: ExecutionInfo, options: SpawnOptions, resource: Uri): Promise>; } diff --git a/src/client/common/process/worker/main.ts b/src/client/common/process/worker/main.ts new file mode 100644 index 000000000000..324673618942 --- /dev/null +++ b/src/client/common/process/worker/main.ts @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { Worker } from 'worker_threads'; +import * as path from 'path'; +import { traceVerbose, traceError } from '../../../logging/index'; + +/** + * Executes a worker file. Make sure to declare the worker file as a entry in the webpack config. + * @param workerFileName Filename of the worker file to execute, it has to end with ".worker.js" for webpack to bundle it. + * @param workerData Arguments to the worker file. + * @returns + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types +export async function executeWorkerFile(workerFileName: string, workerData: any): Promise { + if (!workerFileName.endsWith('.worker.js')) { + throw new Error('Worker file must end with ".worker.js" for webpack to bundle webworkers'); + } + return new Promise((resolve, reject) => { + const worker = new Worker(workerFileName, { workerData }); + const id = worker.threadId; + traceVerbose( + `Worker id ${id} for file ${path.basename(workerFileName)} with data ${JSON.stringify(workerData)}`, + ); + worker.on('message', (msg: { err: Error; res: unknown }) => { + if (msg.err) { + reject(msg.err); + } + resolve(msg.res); + }); + worker.on('error', (ex: Error) => { + traceError(`Error in worker ${workerFileName}`, ex); + reject(ex); + }); + worker.on('exit', (code) => { + traceVerbose(`Worker id ${id} exited with code ${code}`); + if (code !== 0) { + reject(new Error(`Worker ${workerFileName} stopped with exit code ${code}`)); + } + }); + }); +} diff --git a/src/client/common/process/worker/plainExec.worker.ts b/src/client/common/process/worker/plainExec.worker.ts new file mode 100644 index 000000000000..f44ea15f9653 --- /dev/null +++ b/src/client/common/process/worker/plainExec.worker.ts @@ -0,0 +1,16 @@ +import { parentPort, workerData } from 'worker_threads'; +import { _workerPlainExecImpl } from './workerRawProcessApis'; + +_workerPlainExecImpl(workerData.file, workerData.args, workerData.options) + .then((res) => { + if (!parentPort) { + throw new Error('Not in a worker thread'); + } + parentPort.postMessage({ res }); + }) + .catch((err) => { + if (!parentPort) { + throw new Error('Not in a worker thread'); + } + parentPort.postMessage({ err }); + }); diff --git a/src/client/common/process/worker/rawProcessApiWrapper.ts b/src/client/common/process/worker/rawProcessApiWrapper.ts new file mode 100644 index 000000000000..e6476df5d8fa --- /dev/null +++ b/src/client/common/process/worker/rawProcessApiWrapper.ts @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { SpawnOptions } from 'child_process'; +import * as path from 'path'; +import { executeWorkerFile } from './main'; +import { ExecutionResult, ShellOptions } from './types'; + +export function workerShellExec(command: string, options: ShellOptions): Promise> { + return executeWorkerFile(path.join(__dirname, 'shellExec.worker.js'), { + command, + options, + }); +} + +export function workerPlainExec( + file: string, + args: string[], + options: SpawnOptions = {}, +): Promise> { + return executeWorkerFile(path.join(__dirname, 'plainExec.worker.js'), { + file, + args, + options, + }); +} diff --git a/src/client/common/process/worker/shellExec.worker.ts b/src/client/common/process/worker/shellExec.worker.ts new file mode 100644 index 000000000000..f4e9809a29a5 --- /dev/null +++ b/src/client/common/process/worker/shellExec.worker.ts @@ -0,0 +1,16 @@ +import { parentPort, workerData } from 'worker_threads'; +import { _workerShellExecImpl } from './workerRawProcessApis'; + +_workerShellExecImpl(workerData.command, workerData.options, workerData.defaultEnv) + .then((res) => { + if (!parentPort) { + throw new Error('Not in a worker thread'); + } + parentPort.postMessage({ res }); + }) + .catch((ex) => { + if (!parentPort) { + throw new Error('Not in a worker thread'); + } + parentPort.postMessage({ ex }); + }); diff --git a/src/client/common/process/worker/types.ts b/src/client/common/process/worker/types.ts new file mode 100644 index 000000000000..5c58aec10214 --- /dev/null +++ b/src/client/common/process/worker/types.ts @@ -0,0 +1,38 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +import { ExecOptions, SpawnOptions as ChildProcessSpawnOptions } from 'child_process'; + +export function noop() {} +export interface IDisposable { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + dispose(): void | undefined | Promise; +} +export type EnvironmentVariables = Record; +export class StdErrError extends Error { + // eslint-disable-next-line @typescript-eslint/no-useless-constructor + constructor(message: string) { + super(message); + } +} + +export type SpawnOptions = ChildProcessSpawnOptions & { + encoding?: string; + // /** + // * Can't use `CancellationToken` here as it comes from vscode which is not available in worker threads. + // */ + // token?: CancellationToken; + mergeStdOutErr?: boolean; + throwOnStdErr?: boolean; + extraVariables?: NodeJS.ProcessEnv; + // /** + // * Can't use `OutputChannel` here as it comes from vscode which is not available in worker threads. + // */ + // outputChannel?: OutputChannel; + stdinStr?: string; +}; +export type ShellOptions = ExecOptions & { throwOnStdErr?: boolean }; + +export type ExecutionResult = { + stdout: T; + stderr?: T; +}; diff --git a/src/client/common/process/worker/workerRawProcessApis.ts b/src/client/common/process/worker/workerRawProcessApis.ts new file mode 100644 index 000000000000..cfae9b1e6471 --- /dev/null +++ b/src/client/common/process/worker/workerRawProcessApis.ts @@ -0,0 +1,214 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// !!!! IMPORTANT: DO NOT IMPORT FROM VSCODE MODULE AS IT IS NOT AVAILABLE INSIDE WORKER THREADS !!!! + +import { exec, execSync, spawn } from 'child_process'; +import { Readable } from 'stream'; +import { createDeferred } from '../../utils/async'; +import { DEFAULT_ENCODING } from '../constants'; +import { decodeBuffer } from '../decoder'; +import { + ShellOptions, + SpawnOptions, + EnvironmentVariables, + IDisposable, + noop, + StdErrError, + ExecutionResult, +} from './types'; +import { traceWarn } from '../../../logging'; + +const PS_ERROR_SCREEN_BOGUS = /your [0-9]+x[0-9]+ screen size is bogus\. expect trouble/; + +function getDefaultOptions(options: T, defaultEnv?: EnvironmentVariables): T { + const defaultOptions = { ...options }; + const execOptions = defaultOptions as SpawnOptions; + if (execOptions) { + execOptions.encoding = + typeof execOptions.encoding === 'string' && execOptions.encoding.length > 0 + ? execOptions.encoding + : DEFAULT_ENCODING; + const { encoding } = execOptions; + delete execOptions.encoding; + execOptions.encoding = encoding; + } + if (!defaultOptions.env || Object.keys(defaultOptions.env).length === 0) { + const env = defaultEnv || process.env; + defaultOptions.env = { ...env }; + } else { + defaultOptions.env = { ...defaultOptions.env }; + } + + if (execOptions && execOptions.extraVariables) { + defaultOptions.env = { ...defaultOptions.env, ...execOptions.extraVariables }; + } + + // Always ensure we have unbuffered output. + defaultOptions.env.PYTHONUNBUFFERED = '1'; + if (!defaultOptions.env.PYTHONIOENCODING) { + defaultOptions.env.PYTHONIOENCODING = 'utf-8'; + } + + return defaultOptions; +} + +export function _workerShellExecImpl( + command: string, + options: ShellOptions, + defaultEnv?: EnvironmentVariables, + disposables?: Set, +): Promise> { + const shellOptions = getDefaultOptions(options, defaultEnv); + return new Promise((resolve, reject) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const callback = (e: any, stdout: any, stderr: any) => { + if (e && e !== null) { + reject(e); + } else if (shellOptions.throwOnStdErr && stderr && stderr.length) { + reject(new Error(stderr)); + } else { + stdout = filterOutputUsingCondaRunMarkers(stdout); + // Make sure stderr is undefined if we actually had none. This is checked + // elsewhere because that's how exec behaves. + resolve({ stderr: stderr && stderr.length > 0 ? stderr : undefined, stdout }); + } + }; + let procExited = false; + const proc = exec(command, shellOptions, callback); // NOSONAR + proc.once('close', () => { + procExited = true; + }); + proc.once('exit', () => { + procExited = true; + }); + proc.once('error', () => { + procExited = true; + }); + const disposable: IDisposable = { + dispose: () => { + // If process has not exited nor killed, force kill it. + if (!procExited && !proc.killed) { + if (proc.pid) { + killPid(proc.pid); + } else { + proc.kill(); + } + } + }, + }; + if (disposables) { + disposables.add(disposable); + } + }); +} + +export function _workerPlainExecImpl( + file: string, + args: string[], + options: SpawnOptions & { doNotLog?: boolean } = {}, + defaultEnv?: EnvironmentVariables, + disposables?: Set, +): Promise> { + const spawnOptions = getDefaultOptions(options, defaultEnv); + const encoding = spawnOptions.encoding ? spawnOptions.encoding : 'utf8'; + const proc = spawn(file, args, spawnOptions); + // Listen to these errors (unhandled errors in streams tears down the process). + // Errors will be bubbled up to the `error` event in `proc`, hence no need to log. + proc.stdout?.on('error', noop); + proc.stderr?.on('error', noop); + const deferred = createDeferred>(); + const disposable: IDisposable = { + dispose: () => { + // If process has not exited nor killed, force kill it. + if (!proc.killed && !deferred.completed) { + if (proc.pid) { + killPid(proc.pid); + } else { + proc.kill(); + } + } + }, + }; + disposables?.add(disposable); + const internalDisposables: IDisposable[] = []; + + // eslint-disable-next-line @typescript-eslint/ban-types + const on = (ee: Readable | null, name: string, fn: Function) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ee?.on(name, fn as any); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + internalDisposables.push({ dispose: () => ee?.removeListener(name, fn as any) as any }); + }; + + // Tokens not supported yet as they come from vscode module which is not available. + // if (options.token) { + // internalDisposables.push(options.token.onCancellationRequested(disposable.dispose)); + // } + + const stdoutBuffers: Buffer[] = []; + on(proc.stdout, 'data', (data: Buffer) => { + stdoutBuffers.push(data); + }); + const stderrBuffers: Buffer[] = []; + on(proc.stderr, 'data', (data: Buffer) => { + if (options.mergeStdOutErr) { + stdoutBuffers.push(data); + stderrBuffers.push(data); + } else { + stderrBuffers.push(data); + } + }); + + proc.once('close', () => { + if (deferred.completed) { + return; + } + const stderr: string | undefined = + stderrBuffers.length === 0 ? undefined : decodeBuffer(stderrBuffers, encoding); + if ( + stderr && + stderr.length > 0 && + options.throwOnStdErr && + // ignore this specific error silently; see this issue for context: https://github.com/microsoft/vscode/issues/75932 + !(PS_ERROR_SCREEN_BOGUS.test(stderr) && stderr.replace(PS_ERROR_SCREEN_BOGUS, '').trim().length === 0) + ) { + deferred.reject(new StdErrError(stderr)); + } else { + let stdout = decodeBuffer(stdoutBuffers, encoding); + stdout = filterOutputUsingCondaRunMarkers(stdout); + deferred.resolve({ stdout, stderr }); + } + internalDisposables.forEach((d) => d.dispose()); + disposable.dispose(); + }); + proc.once('error', (ex) => { + deferred.reject(ex); + internalDisposables.forEach((d) => d.dispose()); + disposable.dispose(); + }); + + return deferred.promise; +} + +function filterOutputUsingCondaRunMarkers(stdout: string) { + // These markers are added if conda run is used or `interpreterInfo.py` is + // run, see `get_output_via_markers.py`. + const regex = />>>PYTHON-EXEC-OUTPUT([\s\S]*)<<= 2 ? match[1].trim() : undefined; + return filteredOut !== undefined ? filteredOut : stdout; +} + +function killPid(pid: number): void { + try { + if (process.platform === 'win32') { + // Windows doesn't support SIGTERM, so execute taskkill to kill the process + execSync(`taskkill /pid ${pid} /T /F`); // NOSONAR + } else { + process.kill(pid); + } + } catch { + traceWarn('Unable to kill process with pid', pid); + } +} diff --git a/src/client/common/refBool.ts b/src/client/common/refBool.ts deleted file mode 100644 index c0ef43a20777..000000000000 --- a/src/client/common/refBool.ts +++ /dev/null @@ -1,11 +0,0 @@ -export class RefBool { - constructor(private val: boolean) {} - - public get value(): boolean { - return this.val; - } - - public update(newVal: boolean) { - this.val = newVal; - } -} diff --git a/src/client/common/serviceRegistry.ts b/src/client/common/serviceRegistry.ts index 444b666c70e3..abd2b220e400 100644 --- a/src/client/common/serviceRegistry.ts +++ b/src/client/common/serviceRegistry.ts @@ -1,10 +1,23 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. import { IExtensionSingleActivationService } from '../activation/types'; -import { IExperimentService, IFileDownloader, IHttpClient, IInterpreterPathService } from '../common/types'; -import { LiveShareApi } from '../datascience/liveshare/liveshare'; -import { INotebookExecutionLogger } from '../datascience/types'; +import { + IBrowserService, + IConfigurationService, + ICurrentProcess, + IExperimentService, + IExtensions, + IInstaller, + IInterpreterPathService, + IPathUtils, + IPersistentStateFactory, + IRandom, + IToolExecutionPath, + IsWindows, + ToolExecutionPath, +} from './types'; import { IServiceManager } from '../ioc/types'; +import { JupyterExtensionDependencyManager } from '../jupyter/jupyterExtensionDependencyManager'; import { ImportTracker } from '../telemetry/importTracker'; import { IImportTracker } from '../telemetry/types'; import { ActiveResourceService } from './application/activeResource'; @@ -13,13 +26,11 @@ import { ApplicationShell } from './application/applicationShell'; import { ClipboardService } from './application/clipboard'; import { CommandManager } from './application/commandManager'; import { ReloadVSCodeCommandHandler } from './application/commands/reloadCommand'; -import { CustomEditorService } from './application/customEditorService'; +import { ReportIssueCommandHandler } from './application/commands/reportIssueCommand'; import { DebugService } from './application/debugService'; -import { DebugSessionTelemetry } from './application/debugSessionTelemetry'; import { DocumentManager } from './application/documentManager'; import { Extensions } from './application/extensions'; import { LanguageService } from './application/languageService'; -import { VSCodeNotebook } from './application/notebook'; import { TerminalManager } from './application/terminalManager'; import { IActiveResourceService, @@ -27,46 +38,22 @@ import { IApplicationShell, IClipboard, ICommandManager, - ICustomEditorService, + IContextKeyManager, IDebugService, IDocumentManager, + IJupyterExtensionDependencyManager, ILanguageService, - ILiveShareApi, ITerminalManager, - IVSCodeNotebook, - IWorkspaceService + IWorkspaceService, } from './application/types'; import { WorkspaceService } from './application/workspace'; -import { AsyncDisposableRegistry } from './asyncDisposableRegistry'; import { ConfigurationService } from './configuration/service'; -import { CryptoUtils } from './crypto'; -import { EditorUtils } from './editor'; -import { ExperimentsManager } from './experiments/manager'; +import { PipEnvExecutionPath } from './configuration/executionSettings/pipEnvExecution'; import { ExperimentService } from './experiments/service'; -import { FeatureDeprecationManager } from './featureDeprecationManager'; -import { - ExtensionInsidersDailyChannelRule, - ExtensionInsidersOffChannelRule, - ExtensionInsidersWeeklyChannelRule -} from './insidersBuild/downloadChannelRules'; -import { ExtensionChannelService } from './insidersBuild/downloadChannelService'; -import { InsidersExtensionPrompt } from './insidersBuild/insidersExtensionPrompt'; -import { InsidersExtensionService } from './insidersBuild/insidersExtensionService'; -import { - ExtensionChannel, - IExtensionChannelRule, - IExtensionChannelService, - IInsiderExtensionPrompt -} from './insidersBuild/types'; import { ProductInstaller } from './installer/productInstaller'; import { InterpreterPathService } from './interpreterPathService'; import { BrowserService } from './net/browser'; -import { FileDownloader } from './net/fileDownloader'; -import { HttpClient } from './net/httpClient'; -import { NugetService } from './nuget/nugetService'; -import { INugetService } from './nuget/types'; import { PersistentStateFactory } from './persistentState'; -import { IS_WINDOWS } from './platform/constants'; import { PathUtils } from './platform/pathUtils'; import { CurrentProcess } from './process/currentProcess'; import { ProcessLogger } from './process/logger'; @@ -74,6 +61,7 @@ import { IProcessLogger } from './process/types'; import { TerminalActivator } from './terminal/activator'; import { PowershellTerminalActivationFailedHandler } from './terminal/activator/powershellFailedHandler'; import { Bash } from './terminal/environmentActivationProviders/bash'; +import { Nushell } from './terminal/environmentActivationProviders/nushell'; import { CommandPromptAndPowerShell } from './terminal/environmentActivationProviders/commandPrompt'; import { CondaActivationCommandProvider } from './terminal/environmentActivationProviders/condaActivationProvider'; import { PipEnvActivationCommandProvider } from './terminal/environmentActivationProviders/pipEnvActivationProvider'; @@ -91,44 +79,46 @@ import { ITerminalActivator, ITerminalHelper, ITerminalServiceFactory, - TerminalActivationProviders + TerminalActivationProviders, } from './terminal/types'; -import { - IAsyncDisposableRegistry, - IBrowserService, - IConfigurationService, - ICryptoUtils, - ICurrentProcess, - IEditorUtils, - IExperimentsManager, - IExtensions, - IFeatureDeprecationManager, - IInstaller, - IPathUtils, - IPersistentStateFactory, - IRandom, - IsWindows -} from './types'; + import { IMultiStepInputFactory, MultiStepInputFactory } from './utils/multiStepInput'; import { Random } from './utils/random'; +import { ContextKeyManager } from './application/contextKeyManager'; +import { CreatePythonFileCommandHandler } from './application/commands/createPythonFile'; +import { RequireJupyterPrompt } from '../jupyter/requireJupyterPrompt'; +import { isWindows } from './utils/platform'; +import { PixiActivationCommandProvider } from './terminal/environmentActivationProviders/pixiActivationProvider'; -// tslint:disable-next-line: max-func-body-length -export function registerTypes(serviceManager: IServiceManager) { - serviceManager.addSingletonInstance(IsWindows, IS_WINDOWS); +export function registerTypes(serviceManager: IServiceManager): void { + serviceManager.addSingletonInstance(IsWindows, isWindows()); serviceManager.addSingleton(IActiveResourceService, ActiveResourceService); serviceManager.addSingleton(IInterpreterPathService, InterpreterPathService); serviceManager.addSingleton(IExtensions, Extensions); serviceManager.addSingleton(IRandom, Random); serviceManager.addSingleton(IPersistentStateFactory, PersistentStateFactory); + serviceManager.addBinding(IPersistentStateFactory, IExtensionSingleActivationService); serviceManager.addSingleton(ITerminalServiceFactory, TerminalServiceFactory); serviceManager.addSingleton(IPathUtils, PathUtils); serviceManager.addSingleton(IApplicationShell, ApplicationShell); - serviceManager.addSingleton(IVSCodeNotebook, VSCodeNotebook); serviceManager.addSingleton(IClipboard, ClipboardService); serviceManager.addSingleton(ICurrentProcess, CurrentProcess); serviceManager.addSingleton(IInstaller, ProductInstaller); + serviceManager.addSingleton( + IJupyterExtensionDependencyManager, + JupyterExtensionDependencyManager, + ); + serviceManager.addSingleton( + IExtensionSingleActivationService, + RequireJupyterPrompt, + ); + serviceManager.addSingleton( + IExtensionSingleActivationService, + CreatePythonFileCommandHandler, + ); serviceManager.addSingleton(ICommandManager, CommandManager); + serviceManager.addSingleton(IContextKeyManager, ContextKeyManager); serviceManager.addSingleton(IConfigurationService, ConfigurationService); serviceManager.addSingleton(IWorkspaceService, WorkspaceService); serviceManager.addSingleton(IProcessLogger, ProcessLogger); @@ -138,85 +128,64 @@ export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IApplicationEnvironment, ApplicationEnvironment); serviceManager.addSingleton(ILanguageService, LanguageService); serviceManager.addSingleton(IBrowserService, BrowserService); - serviceManager.addSingleton(IHttpClient, HttpClient); - serviceManager.addSingleton(IFileDownloader, FileDownloader); - serviceManager.addSingleton(IEditorUtils, EditorUtils); - serviceManager.addSingleton(INugetService, NugetService); serviceManager.addSingleton(ITerminalActivator, TerminalActivator); serviceManager.addSingleton( ITerminalActivationHandler, - PowershellTerminalActivationFailedHandler + PowershellTerminalActivationFailedHandler, ); - serviceManager.addSingleton(ILiveShareApi, LiveShareApi); - serviceManager.addSingleton(ICryptoUtils, CryptoUtils); - serviceManager.addSingleton(IExperimentsManager, ExperimentsManager); serviceManager.addSingleton(IExperimentService, ExperimentService); serviceManager.addSingleton(ITerminalHelper, TerminalHelper); serviceManager.addSingleton( ITerminalActivationCommandProvider, Bash, - TerminalActivationProviders.bashCShellFish + TerminalActivationProviders.bashCShellFish, ); serviceManager.addSingleton( ITerminalActivationCommandProvider, CommandPromptAndPowerShell, - TerminalActivationProviders.commandPromptAndPowerShell + TerminalActivationProviders.commandPromptAndPowerShell, + ); + serviceManager.addSingleton( + ITerminalActivationCommandProvider, + Nushell, + TerminalActivationProviders.nushell, ); serviceManager.addSingleton( ITerminalActivationCommandProvider, PyEnvActivationCommandProvider, - TerminalActivationProviders.pyenv + TerminalActivationProviders.pyenv, ); serviceManager.addSingleton( ITerminalActivationCommandProvider, CondaActivationCommandProvider, - TerminalActivationProviders.conda + TerminalActivationProviders.conda, + ); + serviceManager.addSingleton( + ITerminalActivationCommandProvider, + PixiActivationCommandProvider, + TerminalActivationProviders.pixi, ); serviceManager.addSingleton( ITerminalActivationCommandProvider, PipEnvActivationCommandProvider, - TerminalActivationProviders.pipenv + TerminalActivationProviders.pipenv, ); - serviceManager.addSingleton(IFeatureDeprecationManager, FeatureDeprecationManager); + serviceManager.addSingleton(IToolExecutionPath, PipEnvExecutionPath, ToolExecutionPath.pipenv); - serviceManager.addSingleton(IAsyncDisposableRegistry, AsyncDisposableRegistry); serviceManager.addSingleton(IMultiStepInputFactory, MultiStepInputFactory); serviceManager.addSingleton(IImportTracker, ImportTracker); serviceManager.addBinding(IImportTracker, IExtensionSingleActivationService); - serviceManager.addBinding(IImportTracker, INotebookExecutionLogger); serviceManager.addSingleton(IShellDetector, TerminalNameShellDetector); serviceManager.addSingleton(IShellDetector, SettingsShellDetector); serviceManager.addSingleton(IShellDetector, UserEnvironmentShellDetector); serviceManager.addSingleton(IShellDetector, VSCEnvironmentShellDetector); - serviceManager.addSingleton(IInsiderExtensionPrompt, InsidersExtensionPrompt); - serviceManager.addSingleton( - IExtensionSingleActivationService, - InsidersExtensionService - ); serviceManager.addSingleton( IExtensionSingleActivationService, - ReloadVSCodeCommandHandler - ); - serviceManager.addSingleton(IExtensionChannelService, ExtensionChannelService); - serviceManager.addSingleton( - IExtensionChannelRule, - ExtensionInsidersOffChannelRule, - ExtensionChannel.off - ); - serviceManager.addSingleton( - IExtensionChannelRule, - ExtensionInsidersDailyChannelRule, - ExtensionChannel.daily - ); - serviceManager.addSingleton( - IExtensionChannelRule, - ExtensionInsidersWeeklyChannelRule, - ExtensionChannel.weekly + ReloadVSCodeCommandHandler, ); serviceManager.addSingleton( IExtensionSingleActivationService, - DebugSessionTelemetry + ReportIssueCommandHandler, ); - serviceManager.addSingleton(ICustomEditorService, CustomEditorService); } diff --git a/src/client/common/startPage/startPage.ts b/src/client/common/startPage/startPage.ts deleted file mode 100644 index 71e26040dc48..000000000000 --- a/src/client/common/startPage/startPage.ts +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { ConfigurationTarget, EventEmitter, Uri, ViewColumn } from 'vscode'; -import { IExtensionSingleActivationService } from '../../activation/types'; -import { EXTENSION_ROOT_DIR } from '../../constants'; -import { Commands, Telemetry } from '../../datascience/constants'; -import { ICodeCssGenerator, INotebookEditorProvider, IThemeFinder } from '../../datascience/types'; -import { WebviewPanelHost } from '../../datascience/webviews/webviewPanelHost'; -import { sendTelemetryEvent } from '../../telemetry'; -import { - IApplicationEnvironment, - IApplicationShell, - ICommandManager, - IDocumentManager, - IWebviewPanelProvider, - IWorkspaceService -} from '../application/types'; -import { IFileSystem } from '../platform/types'; -import { IConfigurationService, IExtensionContext, Resource } from '../types'; -import * as localize from '../utils/localize'; -import { StopWatch } from '../utils/stopWatch'; -import { StartPageMessageListener } from './startPageMessageListener'; -import { IStartPage, IStartPageMapping, StartPageMessages } from './types'; - -const startPageDir = path.join(EXTENSION_ROOT_DIR, 'out', 'datascience-ui', 'viewers'); - -// Class that opens, disposes and handles messages and actions for the Python Extension Start Page. -// It also runs when the extension activates. -@injectable() -export class StartPage extends WebviewPanelHost - implements IStartPage, IExtensionSingleActivationService { - protected closedEvent: EventEmitter = new EventEmitter(); - private timer: StopWatch; - private actionTaken = false; - private actionTakenOnFirstTime = false; - private firstTime = false; - private webviewDidLoad = false; - constructor( - @inject(IWebviewPanelProvider) provider: IWebviewPanelProvider, - @inject(ICodeCssGenerator) cssGenerator: ICodeCssGenerator, - @inject(IThemeFinder) themeFinder: IThemeFinder, - @inject(IConfigurationService) protected configuration: IConfigurationService, - @inject(IWorkspaceService) workspaceService: IWorkspaceService, - @inject(IFileSystem) private file: IFileSystem, - @inject(INotebookEditorProvider) private notebookEditorProvider: INotebookEditorProvider, - @inject(ICommandManager) private readonly commandManager: ICommandManager, - @inject(IDocumentManager) private readonly documentManager: IDocumentManager, - @inject(IApplicationShell) private appShell: IApplicationShell, - @inject(IExtensionContext) private readonly context: IExtensionContext, - @inject(IApplicationEnvironment) private appEnvironment: IApplicationEnvironment - ) { - super( - configuration, - provider, - cssGenerator, - themeFinder, - workspaceService, - (c, v, d) => new StartPageMessageListener(c, v, d), - startPageDir, - [path.join(startPageDir, 'commons.initial.bundle.js'), path.join(startPageDir, 'startPage.js')], - localize.StartPage.getStarted(), - ViewColumn.One, - false, - false - ); - this.timer = new StopWatch(); - } - - public async activate(): Promise { - this.activateBackground().ignoreErrors(); - } - - public dispose(): Promise { - if (!this.isDisposed) { - super.dispose(); - } - return this.close(); - } - - public async open(): Promise { - sendTelemetryEvent(Telemetry.StartPageViewed); - setTimeout(async () => { - await this.loadWebPanel(process.cwd()); - // open webview - await super.show(true); - - setTimeout(() => { - if (!this.webviewDidLoad) { - sendTelemetryEvent(Telemetry.StartPageWebViewError); - } - }, 5000); - }, 3000); - } - - public get owningResource(): Resource { - return undefined; - } - - public async close(): Promise { - if (!this.actionTaken) { - sendTelemetryEvent(Telemetry.StartPageClosedWithoutAction); - } - if (this.actionTakenOnFirstTime) { - sendTelemetryEvent(Telemetry.StartPageUsedAnActionOnFirstTime); - } - sendTelemetryEvent(Telemetry.StartPageTime, this.timer.elapsedTime); - // Fire our event - this.closedEvent.fire(this); - } - - // tslint:disable-next-line: no-any - public async onMessage(message: string, payload: any) { - switch (message) { - case StartPageMessages.Started: - this.webviewDidLoad = true; - break; - case StartPageMessages.RequestShowAgainSetting: - const settings = this.configuration.getSettings(); - await this.postMessage(StartPageMessages.SendSetting, { - showAgainSetting: settings.showStartPage - }); - break; - case StartPageMessages.OpenBlankNotebook: - sendTelemetryEvent(Telemetry.StartPageOpenBlankNotebook); - this.setTelemetryFlags(); - - const savedVersion: string | undefined = this.context.globalState.get('extensionVersion'); - - if (savedVersion) { - await this.notebookEditorProvider.createNew(); - } else { - this.openSampleNotebook().ignoreErrors(); - } - break; - case StartPageMessages.OpenBlankPythonFile: - sendTelemetryEvent(Telemetry.StartPageOpenBlankPythonFile); - this.setTelemetryFlags(); - - const doc = await this.documentManager.openTextDocument({ - language: 'python', - content: `print("${localize.StartPage.helloWorld()}")` - }); - await this.documentManager.showTextDocument(doc, 1, true); - break; - case StartPageMessages.OpenInteractiveWindow: - sendTelemetryEvent(Telemetry.StartPageOpenInteractiveWindow); - this.setTelemetryFlags(); - - const doc2 = await this.documentManager.openTextDocument({ - language: 'python', - content: `#%%\nprint("${localize.StartPage.helloWorld()}")` - }); - await this.documentManager.showTextDocument(doc2, 1, true); - await this.commandManager.executeCommand(Commands.RunAllCells, Uri.parse('')); - break; - case StartPageMessages.OpenCommandPalette: - sendTelemetryEvent(Telemetry.StartPageOpenCommandPalette); - this.setTelemetryFlags(); - - await this.commandManager.executeCommand('workbench.action.showCommands'); - break; - case StartPageMessages.OpenCommandPaletteWithOpenNBSelected: - sendTelemetryEvent(Telemetry.StartPageOpenCommandPaletteWithOpenNBSelected); - this.setTelemetryFlags(); - - await this.commandManager.executeCommand( - 'workbench.action.quickOpen', - '>Create New Blank Jupyter Notebook' - ); - break; - case StartPageMessages.OpenSampleNotebook: - sendTelemetryEvent(Telemetry.StartPageOpenSampleNotebook); - this.setTelemetryFlags(); - - this.openSampleNotebook().ignoreErrors(); - break; - case StartPageMessages.OpenFileBrowser: - sendTelemetryEvent(Telemetry.StartPageOpenFileBrowser); - this.setTelemetryFlags(); - - const uri = await this.appShell.showOpenDialog({ - filters: { - Python: ['py', 'ipynb'] - }, - canSelectMany: false - }); - if (uri) { - const doc3 = await this.documentManager.openTextDocument(uri[0]); - await this.documentManager.showTextDocument(doc3); - } - break; - case StartPageMessages.OpenFolder: - sendTelemetryEvent(Telemetry.StartPageOpenFolder); - this.setTelemetryFlags(); - this.commandManager.executeCommand('workbench.action.files.openFolder'); - break; - case StartPageMessages.OpenWorkspace: - sendTelemetryEvent(Telemetry.StartPageOpenWorkspace); - this.setTelemetryFlags(); - this.commandManager.executeCommand('workbench.action.openWorkspace'); - break; - case StartPageMessages.UpdateSettings: - if (payload === false) { - sendTelemetryEvent(Telemetry.StartPageClickedDontShowAgain); - } - await this.configuration.updateSetting('showStartPage', payload, undefined, ConfigurationTarget.Global); - break; - default: - break; - } - - super.onMessage(message, payload); - } - - // Public for testing - public async extensionVersionChanged(): Promise { - const savedVersion: string | undefined = this.context.globalState.get('extensionVersion'); - const version: string = this.appEnvironment.packageJson.version; - let shouldShowStartPage: boolean; - - if (savedVersion) { - if (savedVersion === version || this.savedVersionisOlder(savedVersion, version)) { - // There has not been an update - shouldShowStartPage = false; - } else { - sendTelemetryEvent(Telemetry.StartPageOpenedFromNewUpdate); - shouldShowStartPage = true; - } - } else { - sendTelemetryEvent(Telemetry.StartPageOpenedFromNewInstall); - shouldShowStartPage = true; - } - - // savedVersion being undefined means this is the first time the user activates the extension. - // if savedVersion != version, there was an update - await this.context.globalState.update('extensionVersion', version); - return shouldShowStartPage; - } - - private async activateBackground(): Promise { - const settings = this.configuration.getSettings(); - - if (settings.showStartPage && this.appEnvironment.extensionChannel === 'stable') { - // extesionVersionChanged() reads CHANGELOG.md - // So we use separate if's to try and avoid reading a file every time - const firstTimeOrUpdate = await this.extensionVersionChanged(); - - if (firstTimeOrUpdate) { - this.firstTime = true; - this.open().ignoreErrors(); - } - } - } - - private savedVersionisOlder(savedVersion: string, actualVersion: string): boolean { - const saved = savedVersion.split('.'); - const actual = actualVersion.split('.'); - - switch (true) { - case Number(actual[0]) > Number(saved[0]): - return false; - case Number(actual[0]) < Number(saved[0]): - return true; - case Number(actual[1]) > Number(saved[1]): - return false; - case Number(actual[1]) < Number(saved[1]): - return true; - case Number(actual[2][0]) > Number(saved[2][0]): - return false; - case Number(actual[2][0]) < Number(saved[2][0]): - return true; - default: - return false; - } - } - - private async openSampleNotebook(): Promise { - const ipynb = '.ipynb'; - const localizedFilePath = path.join( - EXTENSION_ROOT_DIR, - 'pythonFiles', - localize.StartPage.sampleNotebook() + ipynb - ); - let sampleNotebookPath: string; - - if (await this.file.fileExists(localizedFilePath)) { - sampleNotebookPath = localizedFilePath; - } else { - sampleNotebookPath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'Notebooks intro.ipynb'); - } - - const content = await this.file.readFile(sampleNotebookPath); - await this.notebookEditorProvider.createNew(content, localize.StartPage.sampleNotebook()); - } - - private setTelemetryFlags() { - if (this.firstTime) { - this.actionTakenOnFirstTime = true; - } - this.actionTaken = true; - } -} diff --git a/src/client/common/startPage/startPageMessageListener.ts b/src/client/common/startPage/startPageMessageListener.ts deleted file mode 100644 index 642bfb609674..000000000000 --- a/src/client/common/startPage/startPageMessageListener.ts +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { IWebviewPanel, IWebviewPanelMessageListener } from '../application/types'; -import '../extensions'; - -// tslint:disable:no-any -// This class listens to messages that come from the local Python Interactive window -export class StartPageMessageListener implements IWebviewPanelMessageListener { - private disposedCallback: () => void; - private callback: (message: string, payload: any) => void; - private viewChanged: (panel: IWebviewPanel) => void; - - constructor( - callback: (message: string, payload: any) => void, - viewChanged: (panel: IWebviewPanel) => void, - disposed: () => void - ) { - // Save our dispose callback so we remove our interactive window - this.disposedCallback = disposed; - - // Save our local callback so we can handle the non broadcast case(s) - this.callback = callback; - - // Save view changed so we can forward view change events. - this.viewChanged = viewChanged; - } - - public async dispose() { - this.disposedCallback(); - } - - public onMessage(message: string, payload: any) { - // Send to just our local callback. - this.callback(message, payload); - } - - public onChangeViewState(panel: IWebviewPanel) { - // Forward this onto our callback - this.viewChanged(panel); - } -} diff --git a/src/client/common/startPage/types.ts b/src/client/common/startPage/types.ts deleted file mode 100644 index a147085fe426..000000000000 --- a/src/client/common/startPage/types.ts +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import { SharedMessages } from '../../datascience/messages'; - -export const IStartPage = Symbol('IStartPage'); -export interface IStartPage { - open(): Promise; - extensionVersionChanged(): Promise; -} - -export interface ISettingPackage { - showAgainSetting: boolean; -} - -export namespace StartPageMessages { - export const Started = SharedMessages.Started; - export const UpdateSettings = SharedMessages.UpdateSettings; - export const RequestShowAgainSetting = 'RequestShowAgainSetting'; - export const SendSetting = 'SendSetting'; - export const OpenBlankNotebook = 'OpenBlankNotebook'; - export const OpenBlankPythonFile = 'OpenBlankPythonFile'; - export const OpenInteractiveWindow = 'OpenInteractiveWindow'; - export const OpenCommandPalette = 'OpenCommandPalette'; - export const OpenCommandPaletteWithOpenNBSelected = 'OpenCommandPaletteWithOpenNBSelected'; - export const OpenSampleNotebook = 'OpenSampleNotebook'; - export const OpenFileBrowser = 'OpenFileBrowser'; - export const OpenFolder = 'OpenFolder'; - export const OpenWorkspace = 'OpenWorkspace'; -} - -export class IStartPageMapping { - public [StartPageMessages.RequestShowAgainSetting]: ISettingPackage; - public [StartPageMessages.SendSetting]: ISettingPackage; - public [StartPageMessages.Started]: never | undefined; - public [StartPageMessages.UpdateSettings]: boolean; - public [StartPageMessages.OpenBlankNotebook]: never | undefined; - public [StartPageMessages.OpenBlankPythonFile]: never | undefined; - public [StartPageMessages.OpenInteractiveWindow]: never | undefined; - public [StartPageMessages.OpenCommandPalette]: never | undefined; - public [StartPageMessages.OpenCommandPaletteWithOpenNBSelected]: never | undefined; - public [StartPageMessages.OpenSampleNotebook]: never | undefined; - public [StartPageMessages.OpenFileBrowser]: never | undefined; - public [StartPageMessages.OpenFolder]: never | undefined; - public [StartPageMessages.OpenWorkspace]: never | undefined; -} diff --git a/src/client/common/stringUtils.ts b/src/client/common/stringUtils.ts new file mode 100644 index 000000000000..02ca51082ea8 --- /dev/null +++ b/src/client/common/stringUtils.ts @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +export interface SplitLinesOptions { + trim?: boolean; + removeEmptyEntries?: boolean; +} + +/** + * Split a string using the cr and lf characters and return them as an array. + * By default lines are trimmed and empty lines are removed. + * @param {SplitLinesOptions=} splitOptions - Options used for splitting the string. + */ +export function splitLines( + source: string, + splitOptions: SplitLinesOptions = { removeEmptyEntries: true, trim: true }, +): string[] { + let lines = source.split(/\r?\n/g); + if (splitOptions?.trim) { + lines = lines.map((line) => line.trim()); + } + if (splitOptions?.removeEmptyEntries) { + lines = lines.filter((line) => line.length > 0); + } + return lines; +} + +/** + * Replaces all instances of a substring with a new substring. + */ +export function replaceAll(source: string, substr: string, newSubstr: string): string { + if (!source) { + return source; + } + + /** Escaping function from the MDN web docs site + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + * Escapes all the following special characters in a string . * + ? ^ $ { } ( ) | \ \\ + */ + + function escapeRegExp(unescapedStr: string): string { + return unescapedStr.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string + } + + return source.replace(new RegExp(escapeRegExp(substr), 'g'), newSubstr); +} diff --git a/src/client/common/terminal/activator/base.ts b/src/client/common/terminal/activator/base.ts index 27ad3d6ad5d5..b4d2f888d5d2 100644 --- a/src/client/common/terminal/activator/base.ts +++ b/src/client/common/terminal/activator/base.ts @@ -4,6 +4,7 @@ 'use strict'; import { Terminal } from 'vscode'; +import { traceVerbose } from '../../../logging'; import { createDeferred, sleep } from '../../utils/async'; import { ITerminalActivator, ITerminalHelper, TerminalActivationOptions, TerminalShellType } from '../types'; @@ -12,7 +13,7 @@ export class BaseTerminalActivator implements ITerminalActivator { constructor(private readonly helper: ITerminalHelper) {} public async activateEnvironmentInTerminal( terminal: Terminal, - options?: TerminalActivationOptions + options?: TerminalActivationOptions, ): Promise { if (this.activatedTerminals.has(terminal)) { return this.activatedTerminals.get(terminal)!; @@ -24,12 +25,13 @@ export class BaseTerminalActivator implements ITerminalActivator { const activationCommands = await this.helper.getEnvironmentActivationCommands( terminalShellType, options?.resource, - options?.interpreter + options?.interpreter, ); let activated = false; if (activationCommands) { - for (const command of activationCommands!) { + for (const command of activationCommands) { terminal.show(options?.preserveFocus); + traceVerbose(`Command sent to terminal: ${command}`); terminal.sendText(command); await this.waitForCommandToProcess(terminalShellType); activated = true; diff --git a/src/client/common/terminal/activator/index.ts b/src/client/common/terminal/activator/index.ts index b7b8beb147a6..cde04bdbf10d 100644 --- a/src/client/common/terminal/activator/index.ts +++ b/src/client/common/terminal/activator/index.ts @@ -5,27 +5,49 @@ import { inject, injectable, multiInject } from 'inversify'; import { Terminal } from 'vscode'; -import { IConfigurationService } from '../../types'; +import { IConfigurationService, IExperimentService } from '../../types'; import { ITerminalActivationHandler, ITerminalActivator, ITerminalHelper, TerminalActivationOptions } from '../types'; import { BaseTerminalActivator } from './base'; +import { inTerminalEnvVarExperiment } from '../../experiments/helpers'; +import { shouldEnvExtHandleActivation } from '../../../envExt/api.internal'; +import { EventName } from '../../../telemetry/constants'; +import { sendTelemetryEvent } from '../../../telemetry'; @injectable() export class TerminalActivator implements ITerminalActivator { protected baseActivator!: ITerminalActivator; + private pendingActivations = new WeakMap>(); constructor( @inject(ITerminalHelper) readonly helper: ITerminalHelper, @multiInject(ITerminalActivationHandler) private readonly handlers: ITerminalActivationHandler[], - @inject(IConfigurationService) private readonly configurationService: IConfigurationService + @inject(IConfigurationService) private readonly configurationService: IConfigurationService, + @inject(IExperimentService) private readonly experimentService: IExperimentService, ) { this.initialize(); } public async activateEnvironmentInTerminal( terminal: Terminal, - options?: TerminalActivationOptions + options?: TerminalActivationOptions, + ): Promise { + let promise = this.pendingActivations.get(terminal); + if (promise) { + return promise; + } + promise = this.activateEnvironmentInTerminalImpl(terminal, options); + this.pendingActivations.set(terminal, promise); + return promise; + } + private async activateEnvironmentInTerminalImpl( + terminal: Terminal, + options?: TerminalActivationOptions, ): Promise { const settings = this.configurationService.getSettings(options?.resource); - const activateEnvironment = settings.terminal.activateEnvironment; - if (!activateEnvironment || options?.hideFromUser) { + const activateEnvironment = + settings.terminal.activateEnvironment && !inTerminalEnvVarExperiment(this.experimentService); + if (!activateEnvironment || options?.hideFromUser || shouldEnvExtHandleActivation()) { + if (shouldEnvExtHandleActivation()) { + sendTelemetryEvent(EventName.PYTHON_INTERPRETER_ACTIVATION_FOR_TERMINAL); + } return false; } @@ -33,7 +55,7 @@ export class TerminalActivator implements ITerminalActivator { this.handlers.forEach((handler) => handler .handleActivation(terminal, options?.resource, options?.preserveFocus === true, activated) - .ignoreErrors() + .ignoreErrors(), ); return activated; } diff --git a/src/client/common/terminal/activator/powershellFailedHandler.ts b/src/client/common/terminal/activator/powershellFailedHandler.ts index cdb758ebea54..d580ed4d38bf 100644 --- a/src/client/common/terminal/activator/powershellFailedHandler.ts +++ b/src/client/common/terminal/activator/powershellFailedHandler.ts @@ -7,7 +7,7 @@ import { inject, injectable, named } from 'inversify'; import { Terminal } from 'vscode'; import { PowerShellActivationHackDiagnosticsServiceId, - PowershellActivationNotAvailableDiagnostic + PowershellActivationNotAvailableDiagnostic, } from '../../../application/diagnostics/checks/powerShellActivation'; import { IDiagnosticsService } from '../../../application/diagnostics/types'; import { IPlatformService } from '../../platform/types'; @@ -21,7 +21,7 @@ export class PowershellTerminalActivationFailedHandler implements ITerminalActiv @inject(IPlatformService) private readonly platformService: IPlatformService, @inject(IDiagnosticsService) @named(PowerShellActivationHackDiagnosticsServiceId) - private readonly diagnosticService: IDiagnosticsService + private readonly diagnosticService: IDiagnosticsService, ) {} public async handleActivation(terminal: Terminal, resource: Resource, _preserveFocus: boolean, activated: boolean) { if (activated || !this.platformService.isWindows) { @@ -34,7 +34,7 @@ export class PowershellTerminalActivationFailedHandler implements ITerminalActiv // Check if we can activate in Command Prompt. const activationCommands = await this.helper.getEnvironmentActivationCommands( TerminalShellType.commandPrompt, - resource + resource, ); if (!activationCommands || !Array.isArray(activationCommands) || activationCommands.length === 0) { return; diff --git a/src/client/common/terminal/commandPrompt.ts b/src/client/common/terminal/commandPrompt.ts index 3d83e854e116..4a44557c52a7 100644 --- a/src/client/common/terminal/commandPrompt.ts +++ b/src/client/common/terminal/commandPrompt.ts @@ -19,7 +19,7 @@ export function getCommandPromptLocation(currentProcess: ICurrentProcess) { } export async function useCommandPromptAsDefaultShell( currentProcess: ICurrentProcess, - configService: IConfigurationService + configService: IConfigurationService, ) { const cmdPromptLocation = getCommandPromptLocation(currentProcess); await configService.updateSectionSetting( @@ -27,6 +27,6 @@ export async function useCommandPromptAsDefaultShell( 'integrated.shell.windows', cmdPromptLocation, undefined, - ConfigurationTarget.Global + ConfigurationTarget.Global, ); } diff --git a/src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts b/src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts index 215e114f00eb..abc2ff89df63 100644 --- a/src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts +++ b/src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts @@ -1,35 +1,75 @@ +/* eslint-disable max-classes-per-file */ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. import { inject, injectable } from 'inversify'; import * as path from 'path'; import { Uri } from 'vscode'; +import { IInterpreterService } from '../../../interpreter/contracts'; import { IServiceContainer } from '../../../ioc/types'; -import { getVenvExecutableFinder } from '../../../pythonEnvironments/discovery/subenv'; import { IFileSystem } from '../../platform/types'; -import { IConfigurationService } from '../../types'; import { ITerminalActivationCommandProvider, TerminalShellType } from '../types'; +type ExecutableFinderFunc = (python: string) => Promise; + +/** + * Build an "executable finder" function that identifies venv environments. + * + * @param basename - the venv name or names to look for + * @param pathDirname - typically `path.dirname` + * @param pathJoin - typically `path.join` + * @param fileExists - typically `fs.exists` + */ + +function getVenvExecutableFinder( + basename: string | string[], + // + pathDirname: (filename: string) => string, + pathJoin: (...parts: string[]) => string, + // + fileExists: (n: string) => Promise, +): ExecutableFinderFunc { + const basenames = typeof basename === 'string' ? [basename] : basename; + return async (python: string) => { + // Generated scripts are found in the same directory as the interpreter. + const binDir = pathDirname(python); + for (const name of basenames) { + const filename = pathJoin(binDir, name); + if (await fileExists(filename)) { + return filename; + } + } + // No matches so return undefined. + return undefined; + }; +} + @injectable() -export abstract class BaseActivationCommandProvider implements ITerminalActivationCommandProvider { +abstract class BaseActivationCommandProvider implements ITerminalActivationCommandProvider { constructor(@inject(IServiceContainer) protected readonly serviceContainer: IServiceContainer) {} public abstract isShellSupported(targetShell: TerminalShellType): boolean; - public getActivationCommands( + + public async getActivationCommands( resource: Uri | undefined, - targetShell: TerminalShellType + targetShell: TerminalShellType, ): Promise { - const pythonPath = this.serviceContainer.get(IConfigurationService).getSettings(resource) - .pythonPath; - return this.getActivationCommandsForInterpreter(pythonPath, targetShell); + const interpreter = await this.serviceContainer + .get(IInterpreterService) + .getActiveInterpreter(resource); + if (!interpreter) { + return undefined; + } + return this.getActivationCommandsForInterpreter(interpreter.path, targetShell); } + public abstract getActivationCommandsForInterpreter( pythonPath: string, - targetShell: TerminalShellType + targetShell: TerminalShellType, ): Promise; } -export type ActivationScripts = Record; +export type ActivationScripts = Partial>; export abstract class VenvBaseActivationCommandProvider extends BaseActivationCommandProvider { public isShellSupported(targetShell: TerminalShellType): boolean { @@ -49,7 +89,7 @@ export abstract class VenvBaseActivationCommandProvider extends BaseActivationCo path.dirname, path.join, // Bind "this"! - (n: string) => fs.fileExists(n) + (n: string) => fs.fileExists(n), ); return findScript(pythonPath); } diff --git a/src/client/common/terminal/environmentActivationProviders/bash.ts b/src/client/common/terminal/environmentActivationProviders/bash.ts index b0379ae97c7e..00c4d3da114c 100644 --- a/src/client/common/terminal/environmentActivationProviders/bash.ts +++ b/src/client/common/terminal/environmentActivationProviders/bash.ts @@ -7,7 +7,7 @@ import { TerminalShellType } from '../types'; import { ActivationScripts, VenvBaseActivationCommandProvider } from './baseActivationProvider'; // For a given shell the scripts are in order of precedence. -const SCRIPTS: ActivationScripts = ({ +const SCRIPTS: ActivationScripts = { // Group 1 [TerminalShellType.wsl]: ['activate.sh', 'activate'], [TerminalShellType.ksh]: ['activate.sh', 'activate'], @@ -18,14 +18,13 @@ const SCRIPTS: ActivationScripts = ({ [TerminalShellType.tcshell]: ['activate.csh'], [TerminalShellType.cshell]: ['activate.csh'], // Group 3 - [TerminalShellType.fish]: ['activate.fish'] -} as unknown) as ActivationScripts; + [TerminalShellType.fish]: ['activate.fish'], +}; export function getAllScripts(): string[] { const scripts: string[] = []; - for (const key of Object.keys(SCRIPTS)) { - const shell = key as TerminalShellType; - for (const name of SCRIPTS[shell]) { + for (const names of Object.values(SCRIPTS)) { + for (const name of names) { if (!scripts.includes(name)) { scripts.push(name); } @@ -40,12 +39,12 @@ export class Bash extends VenvBaseActivationCommandProvider { public async getActivationCommandsForInterpreter( pythonPath: string, - targetShell: TerminalShellType + targetShell: TerminalShellType, ): Promise { const scriptFile = await this.findScriptFile(pythonPath, targetShell); if (!scriptFile) { - return; + return undefined; } - return [`source ${scriptFile.fileToCommandArgument()}`]; + return [`source ${scriptFile.fileToCommandArgumentForPythonExt()}`]; } } diff --git a/src/client/common/terminal/environmentActivationProviders/commandPrompt.ts b/src/client/common/terminal/environmentActivationProviders/commandPrompt.ts index dc8f51ba8aa6..6d40e2c390a0 100644 --- a/src/client/common/terminal/environmentActivationProviders/commandPrompt.ts +++ b/src/client/common/terminal/environmentActivationProviders/commandPrompt.ts @@ -9,25 +9,24 @@ import { TerminalShellType } from '../types'; import { ActivationScripts, VenvBaseActivationCommandProvider } from './baseActivationProvider'; // For a given shell the scripts are in order of precedence. -const SCRIPTS: ActivationScripts = ({ +const SCRIPTS: ActivationScripts = { // Group 1 [TerminalShellType.commandPrompt]: ['activate.bat', 'Activate.ps1'], // Group 2 [TerminalShellType.powershell]: ['Activate.ps1', 'activate.bat'], - [TerminalShellType.powershellCore]: ['Activate.ps1', 'activate.bat'] -} as unknown) as ActivationScripts; + [TerminalShellType.powershellCore]: ['Activate.ps1', 'activate.bat'], +}; export function getAllScripts(pathJoin: (...p: string[]) => string): string[] { const scripts: string[] = []; - for (const key of Object.keys(SCRIPTS)) { - const shell = key as TerminalShellType; - for (const name of SCRIPTS[shell]) { + for (const names of Object.values(SCRIPTS)) { + for (const name of names) { if (!scripts.includes(name)) { scripts.push( name, // We also add scripts in subdirs. pathJoin('Scripts', name), - pathJoin('scripts', name) + pathJoin('scripts', name), ); } } @@ -38,18 +37,19 @@ export function getAllScripts(pathJoin: (...p: string[]) => string): string[] { @injectable() export class CommandPromptAndPowerShell extends VenvBaseActivationCommandProvider { protected readonly scripts: ActivationScripts; + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super(serviceContainer); - this.scripts = ({} as unknown) as ActivationScripts; - for (const key of Object.keys(SCRIPTS)) { + this.scripts = {}; + for (const [key, names] of Object.entries(SCRIPTS)) { const shell = key as TerminalShellType; const scripts: string[] = []; - for (const name of SCRIPTS[shell]) { + for (const name of names) { scripts.push( name, // We also add scripts in subdirs. path.join('Scripts', name), - path.join('scripts', name) + path.join('scripts', name), ); } this.scripts[shell] = scripts; @@ -58,25 +58,27 @@ export class CommandPromptAndPowerShell extends VenvBaseActivationCommandProvide public async getActivationCommandsForInterpreter( pythonPath: string, - targetShell: TerminalShellType + targetShell: TerminalShellType, ): Promise { const scriptFile = await this.findScriptFile(pythonPath, targetShell); if (!scriptFile) { - return; + return undefined; } if (targetShell === TerminalShellType.commandPrompt && scriptFile.endsWith('activate.bat')) { - return [scriptFile.fileToCommandArgument()]; - } else if ( + return [scriptFile.fileToCommandArgumentForPythonExt()]; + } + if ( (targetShell === TerminalShellType.powershell || targetShell === TerminalShellType.powershellCore) && scriptFile.endsWith('Activate.ps1') ) { - return [`& ${scriptFile.fileToCommandArgument()}`]; - } else if (targetShell === TerminalShellType.commandPrompt && scriptFile.endsWith('Activate.ps1')) { + return [`& ${scriptFile.fileToCommandArgumentForPythonExt()}`]; + } + if (targetShell === TerminalShellType.commandPrompt && scriptFile.endsWith('Activate.ps1')) { // lets not try to run the powershell file from command prompt (user may not have powershell) return []; - } else { - return; } + + return undefined; } } diff --git a/src/client/common/terminal/environmentActivationProviders/condaActivationProvider.ts b/src/client/common/terminal/environmentActivationProviders/condaActivationProvider.ts index 3f4b5cfaa809..42bb8f38fc9e 100644 --- a/src/client/common/terminal/environmentActivationProviders/condaActivationProvider.ts +++ b/src/client/common/terminal/environmentActivationProviders/condaActivationProvider.ts @@ -1,22 +1,20 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + 'use strict'; + import '../../extensions'; import { inject, injectable } from 'inversify'; import * as path from 'path'; import { Uri } from 'vscode'; +import { traceInfo, traceVerbose, traceWarn } from '../../../logging'; -import { ICondaService } from '../../../interpreter/contracts'; +import { IComponentAdapter, ICondaService } from '../../../interpreter/contracts'; import { IPlatformService } from '../../platform/types'; import { IConfigurationService } from '../../types'; import { ITerminalActivationCommandProvider, TerminalShellType } from '../types'; -// Version number of conda that requires we call activate with 'conda activate' instead of just 'activate' -const CondaRequiredMajor = 4; -const CondaRequiredMinor = 4; -const CondaRequiredMinorForPowerShell = 6; - /** * Support conda env activation (in the terminal). */ @@ -25,13 +23,15 @@ export class CondaActivationCommandProvider implements ITerminalActivationComman constructor( @inject(ICondaService) private readonly condaService: ICondaService, @inject(IPlatformService) private platform: IPlatformService, - @inject(IConfigurationService) private configService: IConfigurationService + @inject(IConfigurationService) private configService: IConfigurationService, + @inject(IComponentAdapter) private pyenvs: IComponentAdapter, ) {} /** * Is the given shell supported for activating a conda env? */ - public isShellSupported(_targetShell: TerminalShellType): boolean { + // eslint-disable-next-line class-methods-use-this + public isShellSupported(): boolean { return true; } @@ -40,9 +40,9 @@ export class CondaActivationCommandProvider implements ITerminalActivationComman */ public getActivationCommands( resource: Uri | undefined, - targetShell: TerminalShellType + targetShell: TerminalShellType, ): Promise { - const pythonPath = this.configService.getSettings(resource).pythonPath; + const { pythonPath } = this.configService.getSettings(resource); return this.getActivationCommandsForInterpreter(pythonPath, targetShell); } @@ -52,60 +52,85 @@ export class CondaActivationCommandProvider implements ITerminalActivationComman */ public async getActivationCommandsForInterpreter( pythonPath: string, - targetShell: TerminalShellType + targetShell: TerminalShellType, ): Promise { - const envInfo = await this.condaService.getCondaEnvironment(pythonPath); + traceVerbose(`Getting conda activation commands for interpreter ${pythonPath} with shell ${targetShell}`); + const envInfo = await this.pyenvs.getCondaEnvironment(pythonPath); if (!envInfo) { - return; + traceWarn(`No conda environment found for interpreter ${pythonPath}`); + return undefined; } + traceVerbose(`Found conda environment: ${JSON.stringify(envInfo)}`); const condaEnv = envInfo.name.length > 0 ? envInfo.name : envInfo.path; - // Algorithm differs based on version - // Old version, just call activate directly. - // New version, call activate from the same path as our python path, then call it again to activate our environment. - // -- note that the 'default' conda location won't allow activate to work for the environment sometimes. - const versionInfo = await this.condaService.getCondaVersion(); - if (versionInfo && versionInfo.major >= CondaRequiredMajor) { - // Conda added support for powershell in 4.6. + // New version. + const interpreterPath = await this.condaService.getInterpreterPathForEnvironment(envInfo); + traceInfo(`Using interpreter path: ${interpreterPath}`); + const activatePath = await this.condaService.getActivationScriptFromInterpreter(interpreterPath, envInfo.name); + traceVerbose(`Got activation script: ${activatePath?.path}} with type: ${activatePath?.type}`); + // eslint-disable-next-line camelcase + if (activatePath?.path) { if ( - versionInfo.minor >= CondaRequiredMinorForPowerShell && - (targetShell === TerminalShellType.powershell || targetShell === TerminalShellType.powershellCore) + this.platform.isWindows && + targetShell !== TerminalShellType.bash && + targetShell !== TerminalShellType.gitbash ) { - return this.getPowershellCommands(condaEnv); + const commands = [activatePath.path, `conda activate ${condaEnv.toCommandArgumentForPythonExt()}`]; + traceInfo(`Using Windows-specific commands: ${commands.join(', ')}`); + return commands; } - if (versionInfo.minor >= CondaRequiredMinor) { - // New version. - const interpreterPath = await this.condaService.getCondaFileFromInterpreter(pythonPath, envInfo.name); - if (interpreterPath) { - const activatePath = path.join(path.dirname(interpreterPath), 'activate').fileToCommandArgument(); - const firstActivate = this.platform.isWindows ? activatePath : `source ${activatePath}`; - return [firstActivate, `conda activate ${condaEnv.toCommandArgument()}`]; + + const condaInfo = await this.condaService.getCondaInfo(); + + traceVerbose(`Conda shell level: ${condaInfo?.conda_shlvl}`); + if ( + activatePath.type !== 'global' || + // eslint-disable-next-line camelcase + condaInfo?.conda_shlvl === undefined || + condaInfo.conda_shlvl === -1 + ) { + // activatePath is not the global activate path, or we don't have a shlvl, or it's -1(conda never sourced). + // and we need to source the activate path. + if (activatePath.path === 'activate') { + const commands = [ + `source ${activatePath.path}`, + `conda activate ${condaEnv.toCommandArgumentForPythonExt()}`, + ]; + traceInfo(`Using source activate commands: ${commands.join(', ')}`); + return commands; } + const command = [`source ${activatePath.path} ${condaEnv.toCommandArgumentForPythonExt()}`]; + traceInfo(`Using single source command: ${command}`); + return command; } + const command = [`conda activate ${condaEnv.toCommandArgumentForPythonExt()}`]; + traceInfo(`Using direct conda activate command: ${command}`); + return command; } switch (targetShell) { case TerminalShellType.powershell: case TerminalShellType.powershellCore: - return this.getPowershellCommands(condaEnv); + traceVerbose('Using PowerShell-specific activation'); + return _getPowershellCommands(condaEnv); - // tslint:disable-next-line:no-suspicious-comment // TODO: Do we really special-case fish on Windows? case TerminalShellType.fish: - return this.getFishCommands(condaEnv, await this.condaService.getCondaFile()); + traceVerbose('Using Fish shell-specific activation'); + return getFishCommands(condaEnv, await this.condaService.getCondaFile()); default: if (this.platform.isWindows) { + traceVerbose('Using Windows shell-specific activation fallback option.'); return this.getWindowsCommands(condaEnv); - } else { - return this.getUnixCommands(condaEnv, await this.condaService.getCondaFile()); } + return getUnixCommands(condaEnv, await this.condaService.getCondaFile()); } } public async getWindowsActivateCommand(): Promise { - let activateCmd: string = 'activate'; + let activateCmd = 'activate'; const condaExePath = await this.condaService.getCondaFile(); @@ -113,7 +138,7 @@ export class CondaActivationCommandProvider implements ITerminalActivationComman const condaScriptsPath: string = path.dirname(condaExePath); // prefix the cmd with the found path, and ensure it's quoted properly activateCmd = path.join(condaScriptsPath, activateCmd); - activateCmd = activateCmd.toCommandArgument(); + activateCmd = activateCmd.toCommandArgumentForPythonExt(); } return activateCmd; @@ -121,30 +146,27 @@ export class CondaActivationCommandProvider implements ITerminalActivationComman public async getWindowsCommands(condaEnv: string): Promise { const activate = await this.getWindowsActivateCommand(); - return [`${activate} ${condaEnv.toCommandArgument()}`]; - } - /** - * The expectation is for the user to configure Powershell for Conda. - * Hence we just send the command `conda activate ...`. - * This configuration is documented on Conda. - * Extension will not attempt to work around issues by trying to setup shell for user. - * - * @param {string} condaEnv - * @returns {(Promise)} - * @memberof CondaActivationCommandProvider - */ - public async getPowershellCommands(condaEnv: string): Promise { - return [`conda activate ${condaEnv.toCommandArgument()}`]; + return [`${activate} ${condaEnv.toCommandArgumentForPythonExt()}`]; } +} - public async getFishCommands(condaEnv: string, condaFile: string): Promise { - // https://github.com/conda/conda/blob/be8c08c083f4d5e05b06bd2689d2cd0d410c2ffe/shell/etc/fish/conf.d/conda.fish#L18-L28 - return [`${condaFile.fileToCommandArgument()} activate ${condaEnv.toCommandArgument()}`]; - } +/** + * The expectation is for the user to configure Powershell for Conda. + * Hence we just send the command `conda activate ...`. + * This configuration is documented on Conda. + * Extension will not attempt to work around issues by trying to setup shell for user. + */ +export async function _getPowershellCommands(condaEnv: string): Promise { + return [`conda activate ${condaEnv.toCommandArgumentForPythonExt()}`]; +} - public async getUnixCommands(condaEnv: string, condaFile: string): Promise { - const condaDir = path.dirname(condaFile); - const activateFile = path.join(condaDir, 'activate'); - return [`source ${activateFile.fileToCommandArgument()} ${condaEnv.toCommandArgument()}`]; - } +async function getFishCommands(condaEnv: string, condaFile: string): Promise { + // https://github.com/conda/conda/blob/be8c08c083f4d5e05b06bd2689d2cd0d410c2ffe/shell/etc/fish/conf.d/conda.fish#L18-L28 + return [`${condaFile.fileToCommandArgumentForPythonExt()} activate ${condaEnv.toCommandArgumentForPythonExt()}`]; +} + +async function getUnixCommands(condaEnv: string, condaFile: string): Promise { + const condaDir = path.dirname(condaFile); + const activateFile = path.join(condaDir, 'activate'); + return [`source ${activateFile.fileToCommandArgumentForPythonExt()} ${condaEnv.toCommandArgumentForPythonExt()}`]; } diff --git a/src/client/common/terminal/environmentActivationProviders/nushell.ts b/src/client/common/terminal/environmentActivationProviders/nushell.ts new file mode 100644 index 000000000000..333fd5167770 --- /dev/null +++ b/src/client/common/terminal/environmentActivationProviders/nushell.ts @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { injectable } from 'inversify'; +import '../../extensions'; +import { TerminalShellType } from '../types'; +import { ActivationScripts, VenvBaseActivationCommandProvider } from './baseActivationProvider'; + +// For a given shell the scripts are in order of precedence. +const SCRIPTS: ActivationScripts = { + [TerminalShellType.nushell]: ['activate.nu'], +}; + +export function getAllScripts(): string[] { + const scripts: string[] = []; + for (const names of Object.values(SCRIPTS)) { + for (const name of names) { + if (!scripts.includes(name)) { + scripts.push(name); + } + } + } + return scripts; +} + +@injectable() +export class Nushell extends VenvBaseActivationCommandProvider { + protected readonly scripts = SCRIPTS; + + public async getActivationCommandsForInterpreter( + pythonPath: string, + targetShell: TerminalShellType, + ): Promise { + const scriptFile = await this.findScriptFile(pythonPath, targetShell); + if (!scriptFile) { + return undefined; + } + return [`overlay use ${scriptFile.fileToCommandArgumentForPythonExt()}`]; + } +} diff --git a/src/client/common/terminal/environmentActivationProviders/pipEnvActivationProvider.ts b/src/client/common/terminal/environmentActivationProviders/pipEnvActivationProvider.ts index 3e7a90ce7603..d097c759ec40 100644 --- a/src/client/common/terminal/environmentActivationProviders/pipEnvActivationProvider.ts +++ b/src/client/common/terminal/environmentActivationProviders/pipEnvActivationProvider.ts @@ -5,61 +5,52 @@ import { inject, injectable, named } from 'inversify'; import { Uri } from 'vscode'; -import '../../../common/extensions'; -import { - IInterpreterLocatorService, - IInterpreterService, - IPipEnvService, - PIPENV_SERVICE -} from '../../../interpreter/contracts'; +import '../../extensions'; +import { IInterpreterService } from '../../../interpreter/contracts'; +import { isPipenvEnvironmentRelatedToFolder } from '../../../pythonEnvironments/common/environmentManagers/pipenv'; import { EnvironmentType } from '../../../pythonEnvironments/info'; import { IWorkspaceService } from '../../application/types'; -import { IFileSystem } from '../../platform/types'; -import { ITerminalActivationCommandProvider, TerminalShellType } from '../types'; +import { IToolExecutionPath, ToolExecutionPath } from '../../types'; +import { ITerminalActivationCommandProvider } from '../types'; @injectable() export class PipEnvActivationCommandProvider implements ITerminalActivationCommandProvider { constructor( @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, - @inject(IInterpreterLocatorService) - @named(PIPENV_SERVICE) - private readonly pipenvService: IPipEnvService, + @inject(IToolExecutionPath) + @named(ToolExecutionPath.pipenv) + private readonly pipEnvExecution: IToolExecutionPath, @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, - @inject(IFileSystem) private readonly fs: IFileSystem ) {} - public isShellSupported(_targetShell: TerminalShellType): boolean { + // eslint-disable-next-line class-methods-use-this + public isShellSupported(): boolean { return false; } - public async getActivationCommands(resource: Uri | undefined, _: TerminalShellType): Promise { + public async getActivationCommands(resource: Uri | undefined): Promise { const interpreter = await this.interpreterService.getActiveInterpreter(resource); if (!interpreter || interpreter.envType !== EnvironmentType.Pipenv) { - return; + return undefined; } // Activate using `pipenv shell` only if the current folder relates pipenv environment. const workspaceFolder = resource ? this.workspaceService.getWorkspaceFolder(resource) : undefined; - if ( - workspaceFolder && - interpreter.pipEnvWorkspaceFolder && - !this.fs.arePathsSame(workspaceFolder.uri.fsPath, interpreter.pipEnvWorkspaceFolder) - ) { - return; + if (workspaceFolder) { + if (!(await isPipenvEnvironmentRelatedToFolder(interpreter.path, workspaceFolder?.uri.fsPath))) { + return undefined; + } } - const execName = this.pipenvService.executable; - return [`${execName.fileToCommandArgument()} shell`]; + const execName = this.pipEnvExecution.executable; + return [`${execName.fileToCommandArgumentForPythonExt()} shell`]; } - public async getActivationCommandsForInterpreter( - pythonPath: string, - _targetShell: TerminalShellType - ): Promise { + public async getActivationCommandsForInterpreter(pythonPath: string): Promise { const interpreter = await this.interpreterService.getInterpreterDetails(pythonPath); if (!interpreter || interpreter.envType !== EnvironmentType.Pipenv) { - return; + return undefined; } - const execName = this.pipenvService.executable; - return [`${execName.fileToCommandArgument()} shell`]; + const execName = this.pipEnvExecution.executable; + return [`${execName.fileToCommandArgumentForPythonExt()} shell`]; } } diff --git a/src/client/common/terminal/environmentActivationProviders/pixiActivationProvider.ts b/src/client/common/terminal/environmentActivationProviders/pixiActivationProvider.ts new file mode 100644 index 000000000000..1deaa56dd8ae --- /dev/null +++ b/src/client/common/terminal/environmentActivationProviders/pixiActivationProvider.ts @@ -0,0 +1,77 @@ +/* eslint-disable class-methods-use-this */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { Uri } from 'vscode'; +import { IInterpreterService } from '../../../interpreter/contracts'; +import { ITerminalActivationCommandProvider, TerminalShellType } from '../types'; +import { getPixiActivationCommands } from '../../../pythonEnvironments/common/environmentManagers/pixi'; + +@injectable() +export class PixiActivationCommandProvider implements ITerminalActivationCommandProvider { + constructor(@inject(IInterpreterService) private readonly interpreterService: IInterpreterService) {} + + // eslint-disable-next-line class-methods-use-this + public isShellSupported(targetShell: TerminalShellType): boolean { + return shellTypeToPixiShell(targetShell) !== undefined; + } + + public async getActivationCommands( + resource: Uri | undefined, + targetShell: TerminalShellType, + ): Promise { + const interpreter = await this.interpreterService.getActiveInterpreter(resource); + if (!interpreter) { + return undefined; + } + + return this.getActivationCommandsForInterpreter(interpreter.path, targetShell); + } + + public getActivationCommandsForInterpreter( + pythonPath: string, + targetShell: TerminalShellType, + ): Promise { + return getPixiActivationCommands(pythonPath, targetShell); + } +} + +/** + * Returns the name of a terminal shell type within Pixi. + */ +function shellTypeToPixiShell(targetShell: TerminalShellType): string | undefined { + switch (targetShell) { + case TerminalShellType.powershell: + case TerminalShellType.powershellCore: + return 'powershell'; + case TerminalShellType.commandPrompt: + return 'cmd'; + + case TerminalShellType.zsh: + return 'zsh'; + + case TerminalShellType.fish: + return 'fish'; + + case TerminalShellType.nushell: + return 'nushell'; + + case TerminalShellType.xonsh: + return 'xonsh'; + + case TerminalShellType.cshell: + // Explicitly unsupported + return undefined; + + case TerminalShellType.gitbash: + case TerminalShellType.bash: + case TerminalShellType.wsl: + case TerminalShellType.tcshell: + case TerminalShellType.other: + default: + return 'bash'; + } +} diff --git a/src/client/common/terminal/environmentActivationProviders/pyenvActivationProvider.ts b/src/client/common/terminal/environmentActivationProviders/pyenvActivationProvider.ts index d458a10938b6..6b5ced048672 100644 --- a/src/client/common/terminal/environmentActivationProviders/pyenvActivationProvider.ts +++ b/src/client/common/terminal/environmentActivationProviders/pyenvActivationProvider.ts @@ -14,6 +14,7 @@ import { ITerminalActivationCommandProvider, TerminalShellType } from '../types' export class PyEnvActivationCommandProvider implements ITerminalActivationCommandProvider { constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) {} + // eslint-disable-next-line class-methods-use-this public isShellSupported(_targetShell: TerminalShellType): boolean { return true; } @@ -23,23 +24,23 @@ export class PyEnvActivationCommandProvider implements ITerminalActivationComman .get(IInterpreterService) .getActiveInterpreter(resource); if (!interpreter || interpreter.envType !== EnvironmentType.Pyenv || !interpreter.envName) { - return; + return undefined; } - return [`pyenv shell ${interpreter.envName.toCommandArgument()}`]; + return [`pyenv shell ${interpreter.envName.toCommandArgumentForPythonExt()}`]; } public async getActivationCommandsForInterpreter( pythonPath: string, - _targetShell: TerminalShellType + _targetShell: TerminalShellType, ): Promise { const interpreter = await this.serviceContainer .get(IInterpreterService) .getInterpreterDetails(pythonPath); if (!interpreter || interpreter.envType !== EnvironmentType.Pyenv || !interpreter.envName) { - return; + return undefined; } - return [`pyenv shell ${interpreter.envName.toCommandArgument()}`]; + return [`pyenv shell ${interpreter.envName.toCommandArgumentForPythonExt()}`]; } } diff --git a/src/client/common/terminal/factory.ts b/src/client/common/terminal/factory.ts index 2a9e74f624ab..39cc88c4b024 100644 --- a/src/client/common/terminal/factory.ts +++ b/src/client/common/terminal/factory.ts @@ -3,12 +3,12 @@ import { inject, injectable } from 'inversify'; import { Uri } from 'vscode'; +import * as path from 'path'; import { IInterpreterService } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; import { PythonEnvironment } from '../../pythonEnvironments/info'; import { IWorkspaceService } from '../application/types'; import { IFileSystem } from '../platform/types'; -import { isUri } from '../utils/misc'; import { TerminalService } from './service'; import { SynchronousTerminalService } from './syncTerminalService'; import { ITerminalService, ITerminalServiceFactory, TerminalCreationOptions } from './types'; @@ -20,29 +20,21 @@ export class TerminalServiceFactory implements ITerminalServiceFactory { constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer, @inject(IFileSystem) private fs: IFileSystem, - @inject(IInterpreterService) private interpreterService: IInterpreterService + @inject(IInterpreterService) private interpreterService: IInterpreterService, ) { this.terminalServices = new Map(); } - public getTerminalService(options?: TerminalCreationOptions): ITerminalService; - public getTerminalService(resource?: Uri, title?: string): ITerminalService; - public getTerminalService(arg1?: Uri | TerminalCreationOptions, arg2?: string): ITerminalService { - const resource = isUri(arg1) ? arg1 : undefined; - const title = isUri(arg1) ? undefined : arg1?.title || arg2; - const terminalTitle = typeof title === 'string' && title.trim().length > 0 ? title.trim() : 'Python'; - const interpreter = isUri(arg1) ? undefined : arg1?.interpreter; - const hideFromUser = isUri(arg1) ? false : arg1?.hideFromUser === true; - const env = isUri(arg1) ? undefined : arg1?.env; - - const options: TerminalCreationOptions = { - env, - hideFromUser, - interpreter, - resource, - title - }; - const id = this.getTerminalId(terminalTitle, resource, interpreter); + public getTerminalService(options: TerminalCreationOptions & { newTerminalPerFile?: boolean }): ITerminalService { + const resource = options?.resource; + const title = options?.title; + let terminalTitle = typeof title === 'string' && title.trim().length > 0 ? title.trim() : 'Python'; + const interpreter = options?.interpreter; + const id = this.getTerminalId(terminalTitle, resource, interpreter, options.newTerminalPerFile); if (!this.terminalServices.has(id)) { + if (resource && options.newTerminalPerFile) { + terminalTitle = `${terminalTitle}: ${path.basename(resource.fsPath).replace('.py', '')}`; + } + options.title = terminalTitle; const terminalService = new TerminalService(this.serviceContainer, options); this.terminalServices.set(id, terminalService); } @@ -52,20 +44,26 @@ export class TerminalServiceFactory implements ITerminalServiceFactory { this.fs, this.interpreterService, this.terminalServices.get(id)!, - interpreter + interpreter, ); } public createTerminalService(resource?: Uri, title?: string): ITerminalService { title = typeof title === 'string' && title.trim().length > 0 ? title.trim() : 'Python'; return new TerminalService(this.serviceContainer, { resource, title }); } - private getTerminalId(title: string, resource?: Uri, interpreter?: PythonEnvironment): string { + private getTerminalId( + title: string, + resource?: Uri, + interpreter?: PythonEnvironment, + newTerminalPerFile?: boolean, + ): string { if (!resource && !interpreter) { return title; } const workspaceFolder = this.serviceContainer .get(IWorkspaceService) .getWorkspaceFolder(resource || undefined); - return `${title}:${workspaceFolder?.uri.fsPath || ''}:${interpreter?.path}`; + const fileId = resource && newTerminalPerFile ? resource.fsPath : ''; + return `${title}:${workspaceFolder?.uri.fsPath || ''}:${interpreter?.path}:${fileId}`; } } diff --git a/src/client/common/terminal/helper.ts b/src/client/common/terminal/helper.ts index 7d22bf75b8a5..d2b3bb7879af 100644 --- a/src/client/common/terminal/helper.ts +++ b/src/client/common/terminal/helper.ts @@ -3,13 +3,14 @@ import { inject, injectable, multiInject, named } from 'inversify'; import { Terminal, Uri } from 'vscode'; -import { ICondaService, IInterpreterService } from '../../interpreter/contracts'; +import { IComponentAdapter, IInterpreterService } from '../../interpreter/contracts'; +import { IServiceContainer } from '../../ioc/types'; +import { traceDecoratorError, traceError } from '../../logging'; import { EnvironmentType, PythonEnvironment } from '../../pythonEnvironments/info'; import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; import { ITerminalManager } from '../application/types'; import '../extensions'; -import { traceDecorators, traceError } from '../logger'; import { IPlatformService } from '../platform/types'; import { IConfigurationService, Resource } from '../types'; import { OSType } from '../utils/platform'; @@ -19,8 +20,9 @@ import { ITerminalActivationCommandProvider, ITerminalHelper, TerminalActivationProviders, - TerminalShellType + TerminalShellType, } from './types'; +import { isPixiEnvironment } from '../../pythonEnvironments/common/environmentManagers/pixi'; @injectable() export class TerminalHelper implements ITerminalHelper { @@ -28,7 +30,7 @@ export class TerminalHelper implements ITerminalHelper { constructor( @inject(IPlatformService) private readonly platform: IPlatformService, @inject(ITerminalManager) private readonly terminalManager: ITerminalManager, - @inject(ICondaService) private readonly condaService: ICondaService, + @inject(IServiceContainer) private readonly serviceContainer: IServiceContainer, @inject(IInterpreterService) readonly interpreterService: IInterpreterService, @inject(IConfigurationService) private readonly configurationService: IConfigurationService, @inject(ITerminalActivationCommandProvider) @@ -41,12 +43,18 @@ export class TerminalHelper implements ITerminalHelper { @named(TerminalActivationProviders.commandPromptAndPowerShell) private readonly commandPromptAndPowerShell: ITerminalActivationCommandProvider, @inject(ITerminalActivationCommandProvider) + @named(TerminalActivationProviders.nushell) + private readonly nushell: ITerminalActivationCommandProvider, + @inject(ITerminalActivationCommandProvider) @named(TerminalActivationProviders.pyenv) private readonly pyenv: ITerminalActivationCommandProvider, @inject(ITerminalActivationCommandProvider) @named(TerminalActivationProviders.pipenv) private readonly pipenv: ITerminalActivationCommandProvider, - @multiInject(IShellDetector) shellDetectors: IShellDetector[] + @inject(ITerminalActivationCommandProvider) + @named(TerminalActivationProviders.pixi) + private readonly pixi: ITerminalActivationCommandProvider, + @multiInject(IShellDetector) shellDetectors: IShellDetector[], ) { this.shellDetector = new ShellDetector(this.platform, shellDetectors); } @@ -62,49 +70,56 @@ export class TerminalHelper implements ITerminalHelper { terminalShellType === TerminalShellType.powershell || terminalShellType === TerminalShellType.powershellCore; const commandPrefix = isPowershell ? '& ' : ''; - const formattedArgs = args.map((a) => a.toCommandArgument()); + const formattedArgs = args.map((a) => a.toCommandArgumentForPythonExt()); - return `${commandPrefix}${command.fileToCommandArgument()} ${formattedArgs.join(' ')}`.trim(); + return `${commandPrefix}${command.fileToCommandArgumentForPythonExt()} ${formattedArgs.join(' ')}`.trim(); } public async getEnvironmentActivationCommands( terminalShellType: TerminalShellType, resource?: Uri, - interpreter?: PythonEnvironment + interpreter?: PythonEnvironment, ): Promise { - const providers = [this.pipenv, this.pyenv, this.bashCShellFish, this.commandPromptAndPowerShell]; + const providers = [ + this.pixi, + this.pipenv, + this.pyenv, + this.bashCShellFish, + this.commandPromptAndPowerShell, + this.nushell, + ]; const promise = this.getActivationCommands(resource || undefined, interpreter, terminalShellType, providers); this.sendTelemetry( terminalShellType, EventName.PYTHON_INTERPRETER_ACTIVATION_FOR_TERMINAL, interpreter, - promise + promise, ).ignoreErrors(); return promise; } public async getEnvironmentActivationShellCommands( resource: Resource, shell: TerminalShellType, - interpreter?: PythonEnvironment + interpreter?: PythonEnvironment, ): Promise { if (this.platform.osType === OSType.Unknown) { return; } - const providers = [this.bashCShellFish, this.commandPromptAndPowerShell]; + const providers = [this.pixi, this.bashCShellFish, this.commandPromptAndPowerShell, this.nushell]; const promise = this.getActivationCommands(resource, interpreter, shell, providers); this.sendTelemetry( shell, EventName.PYTHON_INTERPRETER_ACTIVATION_FOR_RUNNING_CODE, interpreter, - promise + promise, ).ignoreErrors(); return promise; } - @traceDecorators.error('Failed to capture telemetry') + @traceDecoratorError('Failed to capture telemetry') protected async sendTelemetry( terminalShellType: TerminalShellType, eventName: EventName, interpreter: PythonEnvironment | undefined, - promise: Promise + promise: Promise, ): Promise { let hasCommands = false; let failed = false; @@ -125,14 +140,28 @@ export class TerminalHelper implements ITerminalHelper { resource: Resource, interpreter: PythonEnvironment | undefined, terminalShellType: TerminalShellType, - providers: ITerminalActivationCommandProvider[] + providers: ITerminalActivationCommandProvider[], ): Promise { const settings = this.configurationService.getSettings(resource); + const isPixiEnv = interpreter + ? interpreter.envType === EnvironmentType.Pixi + : await isPixiEnvironment(settings.pythonPath); + if (isPixiEnv) { + const activationCommands = interpreter + ? await this.pixi.getActivationCommandsForInterpreter(interpreter.path, terminalShellType) + : await this.pixi.getActivationCommands(resource, terminalShellType); + + if (Array.isArray(activationCommands)) { + return activationCommands; + } + } + + const condaService = this.serviceContainer.get(IComponentAdapter); // If we have a conda environment, then use that. const isCondaEnvironment = interpreter ? interpreter.envType === EnvironmentType.Conda - : await this.condaService.isCondaEnvironment(settings.pythonPath); + : await condaService.isCondaEnvironment(settings.pythonPath); if (isCondaEnvironment) { const activationCommands = interpreter ? await this.conda.getActivationCommandsForInterpreter(interpreter.path, terminalShellType) diff --git a/src/client/common/terminal/service.ts b/src/client/common/terminal/service.ts index dd9bb04074e3..0dffd5615ae1 100644 --- a/src/client/common/terminal/service.ts +++ b/src/client/common/terminal/service.ts @@ -2,21 +2,27 @@ // Licensed under the MIT License. import { inject, injectable } from 'inversify'; -import { CancellationToken, Disposable, Event, EventEmitter, Terminal } from 'vscode'; +import { CancellationToken, Disposable, Event, EventEmitter, Terminal, TerminalShellExecution } from 'vscode'; import '../../common/extensions'; import { IInterpreterService } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; import { captureTelemetry } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; -import { ITerminalManager } from '../application/types'; +import { ITerminalAutoActivation } from '../../terminals/types'; +import { IApplicationShell, ITerminalManager } from '../application/types'; +import { _SCRIPTS_DIR } from '../process/internal/scripts/constants'; import { IConfigurationService, IDisposableRegistry } from '../types'; import { ITerminalActivator, ITerminalHelper, ITerminalService, TerminalCreationOptions, - TerminalShellType + TerminalShellType, } from './types'; +import { traceVerbose } from '../../logging'; +import { sleep } from '../utils/async'; +import { useEnvExtension } from '../../envExt/api.internal'; +import { ensureTerminalLegacy } from '../../envExt/api.legacy'; @injectable() export class TerminalService implements ITerminalService, Disposable { @@ -26,23 +32,39 @@ export class TerminalService implements ITerminalService, Disposable { private terminalManager: ITerminalManager; private terminalHelper: ITerminalHelper; private terminalActivator: ITerminalActivator; + private terminalAutoActivator: ITerminalAutoActivation; + private applicationShell: IApplicationShell; + private readonly executeCommandListeners: Set = new Set(); + private _terminalFirstLaunched: boolean = true; + private pythonReplCommandQueue: string[] = []; + private isReplReady: boolean = false; + private replPromptListener?: Disposable; + private replShellTypeListener?: Disposable; public get onDidCloseTerminal(): Event { return this.terminalClosed.event.bind(this.terminalClosed); } + constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer, - private readonly options?: TerminalCreationOptions + private readonly options?: TerminalCreationOptions, ) { const disposableRegistry = this.serviceContainer.get(IDisposableRegistry); disposableRegistry.push(this); this.terminalHelper = this.serviceContainer.get(ITerminalHelper); this.terminalManager = this.serviceContainer.get(ITerminalManager); + this.terminalAutoActivator = this.serviceContainer.get(ITerminalAutoActivation); + this.applicationShell = this.serviceContainer.get(IApplicationShell); this.terminalManager.onDidCloseTerminal(this.terminalCloseHandler, this, disposableRegistry); this.terminalActivator = this.serviceContainer.get(ITerminalActivator); } public dispose() { - if (this.terminal) { - this.terminal.dispose(); + this.terminal?.dispose(); + this.disposeReplListener(); + + if (this.executeCommandListeners && this.executeCommandListeners.size > 0) { + this.executeCommandListeners.forEach((d) => { + d?.dispose(); + }); } } public async sendCommand(command: string, args: string[], _?: CancellationToken): Promise { @@ -51,8 +73,10 @@ export class TerminalService implements ITerminalService, Disposable { if (!this.options?.hideFromUser) { this.terminal!.show(true); } - this.terminal!.sendText(text, true); + + await this.executeCommand(text, false); } + /** @deprecated */ public async sendText(text: string): Promise { await this.ensureTerminal(); if (!this.options?.hideFromUser) { @@ -60,43 +84,175 @@ export class TerminalService implements ITerminalService, Disposable { } this.terminal!.sendText(text); } + public async executeCommand( + commandLine: string, + isPythonShell: boolean, + ): Promise { + if (isPythonShell) { + if (this.isReplReady) { + this.terminal?.sendText(commandLine); + traceVerbose(`Python REPL sendText: ${commandLine}`); + } else { + // Queue command to run once REPL is ready. + this.pythonReplCommandQueue.push(commandLine); + traceVerbose(`Python REPL queued command: ${commandLine}`); + this.startReplListener(); + } + return undefined; + } + + // Non-REPL code execution + return this.executeCommandInternal(commandLine); + } + + private startReplListener(): void { + if (this.replPromptListener || this.replShellTypeListener) { + return; + } + + this.replShellTypeListener = this.terminalManager.onDidChangeTerminalState((terminal) => { + if (this.terminal && terminal === this.terminal) { + if (terminal.state.shell == 'python') { + traceVerbose('Python REPL ready from terminal shell api'); + this.onReplReady(); + } + } + }); + + let terminalData = ''; + this.replPromptListener = this.applicationShell.onDidWriteTerminalData((e) => { + if (this.terminal && e.terminal === this.terminal) { + terminalData += e.data; + if (/>>>\s*$/.test(terminalData)) { + traceVerbose('Python REPL ready, from >>> prompt detection'); + this.onReplReady(); + } + } + }); + } + + private onReplReady(): void { + if (this.isReplReady) { + return; + } + this.isReplReady = true; + this.flushReplQueue(); + this.disposeReplListener(); + } + + private disposeReplListener(): void { + if (this.replPromptListener) { + this.replPromptListener.dispose(); + this.replPromptListener = undefined; + } + if (this.replShellTypeListener) { + this.replShellTypeListener.dispose(); + this.replShellTypeListener = undefined; + } + } + + private flushReplQueue(): void { + while (this.pythonReplCommandQueue.length > 0) { + const commandLine = this.pythonReplCommandQueue.shift(); + if (commandLine) { + traceVerbose(`Executing queued REPL command: ${commandLine}`); + this.terminal?.sendText(commandLine); + } + } + } + + private async executeCommandInternal(commandLine: string): Promise { + const terminal = this.terminal; + if (!terminal) { + traceVerbose('Terminal not available, cannot execute command'); + return undefined; + } + + if (!this.options?.hideFromUser) { + terminal.show(true); + } + + // If terminal was just launched, wait some time for shell integration to onDidChangeShellIntegration. + if (!terminal.shellIntegration && this._terminalFirstLaunched) { + this._terminalFirstLaunched = false; + const promise = new Promise((resolve) => { + const disposable = this.terminalManager.onDidChangeTerminalShellIntegration(() => { + // eslint-disable-next-line @typescript-eslint/no-use-before-define + clearTimeout(timer); + disposable.dispose(); + resolve(true); + }); + const TIMEOUT_DURATION = 500; + const timer = setTimeout(() => { + disposable.dispose(); + resolve(true); + }, TIMEOUT_DURATION); + }); + await promise; + } + + if (terminal.shellIntegration) { + const execution = terminal.shellIntegration.executeCommand(commandLine); + traceVerbose(`Shell Integration is enabled, executeCommand: ${commandLine}`); + return execution; + } else { + terminal.sendText(commandLine); + traceVerbose(`Shell Integration is disabled, sendText: ${commandLine}`); + } + + return undefined; + } + public async show(preserveFocus: boolean = true): Promise { await this.ensureTerminal(preserveFocus); if (!this.options?.hideFromUser) { this.terminal!.show(preserveFocus); } } - private async ensureTerminal(preserveFocus: boolean = true): Promise { + // TODO: Debt switch to Promise ---> breaks 20 tests + public async ensureTerminal(preserveFocus: boolean = true): Promise { if (this.terminal) { return; } - this.terminalShellType = this.terminalHelper.identifyTerminalShell(this.terminal); - this.terminal = this.terminalManager.createTerminal({ - name: this.options?.title || 'Python', - env: this.options?.env, - hideFromUser: this.options?.hideFromUser - }); - // Sometimes the terminal takes some time to start up before it can start accepting input. - await new Promise((resolve) => setTimeout(resolve, 100)); + if (useEnvExtension()) { + this.terminal = await ensureTerminalLegacy(this.options?.resource, { + name: this.options?.title || 'Python', + hideFromUser: this.options?.hideFromUser, + }); + return; + } else { + this.terminalShellType = this.terminalHelper.identifyTerminalShell(this.terminal); + this.terminal = this.terminalManager.createTerminal({ + name: this.options?.title || 'Python', + hideFromUser: this.options?.hideFromUser, + }); + this.terminalAutoActivator.disableAutoActivation(this.terminal); - await this.terminalActivator.activateEnvironmentInTerminal(this.terminal!, { - resource: this.options?.resource, - preserveFocus, - interpreter: this.options?.interpreter, - hideFromUser: this.options?.hideFromUser - }); + await sleep(100); + + await this.terminalActivator.activateEnvironmentInTerminal(this.terminal, { + resource: this.options?.resource, + preserveFocus, + interpreter: this.options?.interpreter, + hideFromUser: this.options?.hideFromUser, + }); + } if (!this.options?.hideFromUser) { - this.terminal!.show(preserveFocus); + this.terminal.show(preserveFocus); } this.sendTelemetry().ignoreErrors(); + return; } private terminalCloseHandler(terminal: Terminal) { if (terminal === this.terminal) { this.terminalClosed.fire(); this.terminal = undefined; + this.isReplReady = false; + this.disposeReplListener(); + this.pythonReplCommandQueue = []; } } @@ -114,7 +270,7 @@ export class TerminalService implements ITerminalService, Disposable { captureTelemetry(EventName.TERMINAL_CREATE, { terminal: this.terminalShellType, pythonVersion, - interpreterType + interpreterType, }); } } diff --git a/src/client/common/terminal/shellDetector.ts b/src/client/common/terminal/shellDetector.ts index 9a911a26bc0b..bf183f20a279 100644 --- a/src/client/common/terminal/shellDetector.ts +++ b/src/client/common/terminal/shellDetector.ts @@ -4,11 +4,11 @@ 'use strict'; import { inject, injectable, multiInject } from 'inversify'; -import { Terminal } from 'vscode'; +import { Terminal, env } from 'vscode'; +import { traceError, traceVerbose } from '../../logging'; import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; import '../extensions'; -import { traceVerbose } from '../logger'; import { IPlatformService } from '../platform/types'; import { OSType } from '../utils/platform'; import { IShellDetector, ShellIdentificationTelemetry, TerminalShellType } from './types'; @@ -17,26 +17,22 @@ const defaultOSShells = { [OSType.Linux]: TerminalShellType.bash, [OSType.OSX]: TerminalShellType.bash, [OSType.Windows]: TerminalShellType.commandPrompt, - [OSType.Unknown]: TerminalShellType.other + [OSType.Unknown]: TerminalShellType.other, }; @injectable() export class ShellDetector { constructor( @inject(IPlatformService) private readonly platform: IPlatformService, - @multiInject(IShellDetector) private readonly shellDetectors: IShellDetector[] + @multiInject(IShellDetector) private readonly shellDetectors: IShellDetector[], ) {} /** * Logic is as follows: * 1. Try to identify the type of the shell based on the name of the terminal. - * 2. Try to identify the type of the shell based on the usettigs in VSC. + * 2. Try to identify the type of the shell based on the settings in VSC. * 3. Try to identify the type of the shell based on the user environment (OS). * 4. If all else fail, use defaults hardcoded (cmd for windows, bash for linux & mac). - * More information here See solution here https://github.com/microsoft/vscode/issues/74233#issuecomment-497527337 - * - * @param {Terminal} [terminal] - * @returns {TerminalShellType} - * @memberof TerminalHelper + * More information here: https://github.com/microsoft/vscode/issues/74233#issuecomment-497527337 */ public identifyTerminalShell(terminal?: Terminal): TerminalShellType { let shell: TerminalShellType | undefined; @@ -45,7 +41,7 @@ export class ShellDetector { shellIdentificationSource: 'default', terminalProvided: !!terminal, hasCustomShell: undefined, - hasShellInEnv: undefined + hasShellInEnv: undefined, }; // Sort in order of priority and then identify the shell. @@ -53,10 +49,8 @@ export class ShellDetector { for (const detector of shellDetectors) { shell = detector.identify(telemetryProperties, terminal); - traceVerbose( - `${detector}. Shell identified as ${shell} ${terminal ? `(Terminal name is ${terminal.name})` : ''}` - ); if (shell && shell !== TerminalShellType.other) { + telemetryProperties.failed = false; break; } } @@ -65,10 +59,11 @@ export class ShellDetector { // This impacts executing code in terminals and activation of environments in terminal. // So, the better this works, the better it is for the user. sendTelemetryEvent(EventName.TERMINAL_SHELL_IDENTIFICATION, undefined, telemetryProperties); - traceVerbose(`Shell identified as '${shell}'`); + traceVerbose(`Shell identified as ${shell} ${terminal ? `(Terminal name is ${terminal.name})` : ''}`); // If we could not identify the shell, use the defaults. if (shell === undefined || shell === TerminalShellType.other) { + traceError('Unable to identify shell', env.shell, ' for OS ', this.platform.osType); traceVerbose('Using default OS shell'); shell = defaultOSShells[this.platform.osType]; } diff --git a/src/client/common/terminal/shellDetectors/baseShellDetector.ts b/src/client/common/terminal/shellDetectors/baseShellDetector.ts index 04c534dedf06..4262bdf80364 100644 --- a/src/client/common/terminal/shellDetectors/baseShellDetector.ts +++ b/src/client/common/terminal/shellDetectors/baseShellDetector.ts @@ -5,11 +5,8 @@ import { injectable, unmanaged } from 'inversify'; import { Terminal } from 'vscode'; -import { traceVerbose } from '../../logger'; import { IShellDetector, ShellIdentificationTelemetry, TerminalShellType } from '../types'; -// tslint:disable: max-classes-per-file - /* When identifying the shell use the following algorithm: * 1. Identify shell based on the name of the terminal (if there is one already opened and used). @@ -21,17 +18,18 @@ When identifying the shell use the following algorithm: // Types of shells can be found here: // 1. https://wiki.ubuntu.com/ChangingShells -const IS_GITBASH = /(gitbash.exe$)/i; -const IS_BASH = /(bash.exe$|bash$)/i; -const IS_WSL = /(wsl.exe$)/i; +const IS_GITBASH = /(gitbash$)/i; +const IS_BASH = /(bash$)/i; +const IS_WSL = /(wsl$)/i; const IS_ZSH = /(zsh$)/i; const IS_KSH = /(ksh$)/i; -const IS_COMMAND = /(cmd.exe$|cmd$)/i; -const IS_POWERSHELL = /(powershell.exe$|powershell$)/i; -const IS_POWERSHELL_CORE = /(pwsh.exe$|pwsh$)/i; +const IS_COMMAND = /(cmd$)/i; +const IS_POWERSHELL = /(powershell$)/i; +const IS_POWERSHELL_CORE = /(pwsh$)/i; const IS_FISH = /(fish$)/i; const IS_CSHELL = /(csh$)/i; const IS_TCSHELL = /(tcsh$)/i; +const IS_NUSHELL = /(nu$)/i; const IS_XONSH = /(xonsh$)/i; const detectableShells = new Map(); @@ -45,6 +43,7 @@ detectableShells.set(TerminalShellType.commandPrompt, IS_COMMAND); detectableShells.set(TerminalShellType.fish, IS_FISH); detectableShells.set(TerminalShellType.tcshell, IS_TCSHELL); detectableShells.set(TerminalShellType.cshell, IS_CSHELL); +detectableShells.set(TerminalShellType.nushell, IS_NUSHELL); detectableShells.set(TerminalShellType.powershellCore, IS_POWERSHELL_CORE); detectableShells.set(TerminalShellType.xonsh, IS_XONSH); @@ -53,21 +52,27 @@ export abstract class BaseShellDetector implements IShellDetector { constructor(@unmanaged() public readonly priority: number) {} public abstract identify( telemetryProperties: ShellIdentificationTelemetry, - terminal?: Terminal + terminal?: Terminal, ): TerminalShellType | undefined; public identifyShellFromShellPath(shellPath: string): TerminalShellType { - const shell = Array.from(detectableShells.keys()).reduce((matchedShell, shellToDetect) => { - if (matchedShell === TerminalShellType.other) { - const pat = detectableShells.get(shellToDetect); - if (pat && pat.test(shellPath)) { - return shellToDetect; - } + return identifyShellFromShellPath(shellPath); + } +} + +export function identifyShellFromShellPath(shellPath: string): TerminalShellType { + // Remove .exe extension so shells can be more consistently detected + // on Windows (including Cygwin). + const basePath = shellPath.replace(/\.exe$/i, ''); + + const shell = Array.from(detectableShells.keys()).reduce((matchedShell, shellToDetect) => { + if (matchedShell === TerminalShellType.other) { + const pat = detectableShells.get(shellToDetect); + if (pat && pat.test(basePath)) { + return shellToDetect; } - return matchedShell; - }, TerminalShellType.other); + } + return matchedShell; + }, TerminalShellType.other); - traceVerbose(`Shell path '${shellPath}'`); - traceVerbose(`Shell path identified as shell '${shell}'`); - return shell; - } + return shell; } diff --git a/src/client/common/terminal/shellDetectors/settingsShellDetector.ts b/src/client/common/terminal/shellDetectors/settingsShellDetector.ts index 5fb315cc1c2c..6288675ec3f8 100644 --- a/src/client/common/terminal/shellDetectors/settingsShellDetector.ts +++ b/src/client/common/terminal/shellDetectors/settingsShellDetector.ts @@ -6,7 +6,6 @@ import { inject, injectable } from 'inversify'; import { Terminal } from 'vscode'; import { IWorkspaceService } from '../../application/types'; -import { traceVerbose } from '../../logger'; import { IPlatformService } from '../../platform/types'; import { OSType } from '../../utils/platform'; import { ShellIdentificationTelemetry, TerminalShellType } from '../types'; @@ -14,16 +13,12 @@ import { BaseShellDetector } from './baseShellDetector'; /** * Identifies the shell based on the user settings. - * - * @export - * @class SettingsShellDetector - * @extends {BaseShellDetector} */ @injectable() export class SettingsShellDetector extends BaseShellDetector { constructor( @inject(IWorkspaceService) private readonly workspace: IWorkspaceService, - @inject(IPlatformService) private readonly platform: IPlatformService + @inject(IPlatformService) private readonly platform: IPlatformService, ) { super(2); } @@ -51,7 +46,7 @@ export class SettingsShellDetector extends BaseShellDetector { } public identify( telemetryProperties: ShellIdentificationTelemetry, - _terminal?: Terminal + _terminal?: Terminal, ): TerminalShellType | undefined { const shellPath = this.getTerminalShellPath(); telemetryProperties.hasCustomShell = !!shellPath; @@ -59,9 +54,9 @@ export class SettingsShellDetector extends BaseShellDetector { if (shell !== TerminalShellType.other) { telemetryProperties.shellIdentificationSource = 'environment'; + } else { + telemetryProperties.shellIdentificationSource = 'settings'; } - telemetryProperties.shellIdentificationSource = 'settings'; - traceVerbose(`Shell path from user settings '${shellPath}'`); return shell; } } diff --git a/src/client/common/terminal/shellDetectors/terminalNameShellDetector.ts b/src/client/common/terminal/shellDetectors/terminalNameShellDetector.ts index 34ca634d3936..0f14adbe9d36 100644 --- a/src/client/common/terminal/shellDetectors/terminalNameShellDetector.ts +++ b/src/client/common/terminal/shellDetectors/terminalNameShellDetector.ts @@ -5,16 +5,12 @@ import { injectable } from 'inversify'; import { Terminal } from 'vscode'; -import { traceVerbose } from '../../logger'; +import { traceVerbose } from '../../../logging'; import { ShellIdentificationTelemetry, TerminalShellType } from '../types'; import { BaseShellDetector } from './baseShellDetector'; /** * Identifies the shell, based on the display name of the terminal. - * - * @export - * @class TerminalNameShellDetector - * @extends {BaseShellDetector} */ @injectable() export class TerminalNameShellDetector extends BaseShellDetector { @@ -23,7 +19,7 @@ export class TerminalNameShellDetector extends BaseShellDetector { } public identify( telemetryProperties: ShellIdentificationTelemetry, - terminal?: Terminal + terminal?: Terminal, ): TerminalShellType | undefined { if (!terminal) { return; diff --git a/src/client/common/terminal/shellDetectors/userEnvironmentShellDetector.ts b/src/client/common/terminal/shellDetectors/userEnvironmentShellDetector.ts index 3f655da212ca..da84eef4d46f 100644 --- a/src/client/common/terminal/shellDetectors/userEnvironmentShellDetector.ts +++ b/src/client/common/terminal/shellDetectors/userEnvironmentShellDetector.ts @@ -5,7 +5,6 @@ import { inject, injectable } from 'inversify'; import { Terminal } from 'vscode'; -import { traceVerbose } from '../../logger'; import { IPlatformService } from '../../platform/types'; import { ICurrentProcess } from '../../types'; import { OSType } from '../../utils/platform'; @@ -14,16 +13,12 @@ import { BaseShellDetector } from './baseShellDetector'; /** * Identifies the shell based on the users environment (env variables). - * - * @export - * @class UserEnvironmentShellDetector - * @extends {BaseShellDetector} */ @injectable() export class UserEnvironmentShellDetector extends BaseShellDetector { constructor( @inject(ICurrentProcess) private readonly currentProcess: ICurrentProcess, - @inject(IPlatformService) private readonly platform: IPlatformService + @inject(IPlatformService) private readonly platform: IPlatformService, ) { super(1); } @@ -32,7 +27,7 @@ export class UserEnvironmentShellDetector extends BaseShellDetector { } public identify( telemetryProperties: ShellIdentificationTelemetry, - _terminal?: Terminal + _terminal?: Terminal, ): TerminalShellType | undefined { const shellPath = this.getDefaultPlatformShell(); telemetryProperties.hasShellInEnv = !!shellPath; @@ -41,7 +36,6 @@ export class UserEnvironmentShellDetector extends BaseShellDetector { if (shell !== TerminalShellType.other) { telemetryProperties.shellIdentificationSource = 'environment'; } - traceVerbose(`Shell path from user env '${shellPath}'`); return shell; } } diff --git a/src/client/common/terminal/shellDetectors/vscEnvironmentShellDetector.ts b/src/client/common/terminal/shellDetectors/vscEnvironmentShellDetector.ts index e39197fbea7b..9ca1b8c4ec22 100644 --- a/src/client/common/terminal/shellDetectors/vscEnvironmentShellDetector.ts +++ b/src/client/common/terminal/shellDetectors/vscEnvironmentShellDetector.ts @@ -5,17 +5,13 @@ import { inject } from 'inversify'; import { Terminal } from 'vscode'; +import { traceVerbose } from '../../../logging'; import { IApplicationEnvironment } from '../../application/types'; -import { traceVerbose } from '../../logger'; import { ShellIdentificationTelemetry, TerminalShellType } from '../types'; import { BaseShellDetector } from './baseShellDetector'; /** * Identifies the shell, based on the VSC Environment API. - * - * @export - * @class VSCEnvironmentShellDetector - * @extends {BaseShellDetector} */ export class VSCEnvironmentShellDetector extends BaseShellDetector { constructor(@inject(IApplicationEnvironment) private readonly appEnv: IApplicationEnvironment) { @@ -23,15 +19,20 @@ export class VSCEnvironmentShellDetector extends BaseShellDetector { } public identify( telemetryProperties: ShellIdentificationTelemetry, - _terminal?: Terminal + terminal?: Terminal, ): TerminalShellType | undefined { - if (!this.appEnv.shell) { + const shellPath = + terminal?.creationOptions && 'shellPath' in terminal.creationOptions && terminal.creationOptions.shellPath + ? terminal.creationOptions.shellPath + : this.appEnv.shell; + if (!shellPath) { return; } - const shell = this.identifyShellFromShellPath(this.appEnv.shell); - traceVerbose(`Terminal shell path '${this.appEnv.shell}' identified as shell '${shell}'`); + const shell = this.identifyShellFromShellPath(shellPath); + traceVerbose(`Terminal shell path '${shellPath}' identified as shell '${shell}'`); telemetryProperties.shellIdentificationSource = shell === TerminalShellType.other ? telemetryProperties.shellIdentificationSource : 'vscode'; + telemetryProperties.failed = shell === TerminalShellType.other ? false : true; return shell; } } diff --git a/src/client/common/terminal/syncTerminalService.ts b/src/client/common/terminal/syncTerminalService.ts index 53e6231be95d..0b46a86ee51e 100644 --- a/src/client/common/terminal/syncTerminalService.ts +++ b/src/client/common/terminal/syncTerminalService.ts @@ -4,11 +4,11 @@ 'use strict'; import { inject } from 'inversify'; -import { CancellationToken, Disposable, Event } from 'vscode'; +import { CancellationToken, Disposable, Event, TerminalShellExecution } from 'vscode'; import { IInterpreterService } from '../../interpreter/contracts'; +import { traceVerbose } from '../../logging'; import { PythonEnvironment } from '../../pythonEnvironments/info'; import { Cancellation } from '../cancellation'; -import { traceVerbose } from '../logger'; import { IFileSystem, TemporaryFile } from '../platform/types'; import * as internalScripts from '../process/internal/scripts'; import { createDeferred, Deferred } from '../utils/async'; @@ -20,7 +20,7 @@ enum State { notStarted = 0, started = 1, completed = 2, - errored = 4 + errored = 4, } class ExecutionState implements Disposable { @@ -30,7 +30,7 @@ class ExecutionState implements Disposable { constructor( public readonly lockFile: string, private readonly fs: IFileSystem, - private readonly command: string[] + private readonly command: string[], ) { this.registerStateUpdate(); this._completed.promise.finally(() => this.dispose()).ignoreErrors(); @@ -56,9 +56,9 @@ class ExecutionState implements Disposable { this._completed.reject( new Error( `Command failed with errors, check the terminal for details. Command: ${this.command.join( - ' ' - )}\n${errorContents}` - ) + ' ', + )}\n${errorContents}`, + ), ); } else if (state & State.completed) { this._completed.resolve(); @@ -66,8 +66,7 @@ class ExecutionState implements Disposable { }, 100); this.disposable = { - // tslint:disable-next-line: no-any - dispose: () => clearInterval(timeout as any) + dispose: () => clearInterval(timeout as any), }; } private async getLockFileState(file: string): Promise { @@ -93,11 +92,6 @@ class ExecutionState implements Disposable { * - Send text to a terminal that executes our python file, passing in the original text as args * - The pthon file will execute the commands as a subprocess * - At the end of the execution a file is created to singal completion. - * - * @export - * @class SynchronousTerminalService - * @implements {ITerminalService} - * @implements {Disposable} */ export class SynchronousTerminalService implements ITerminalService, Disposable { private readonly disposables: Disposable[] = []; @@ -108,7 +102,7 @@ export class SynchronousTerminalService implements ITerminalService, Disposable @inject(IFileSystem) private readonly fs: IFileSystem, @inject(IInterpreterService) private readonly interpreter: IInterpreterService, public readonly terminalService: TerminalService, - private readonly pythonInterpreter?: PythonEnvironment + private readonly pythonInterpreter?: PythonEnvironment, ) {} public dispose() { this.terminalService.dispose(); @@ -129,7 +123,7 @@ export class SynchronousTerminalService implements ITerminalService, Disposable command: string, args: string[], cancel?: CancellationToken, - swallowExceptions: boolean = true + swallowExceptions: boolean = true, ): Promise { if (!cancel) { return this.terminalService.sendCommand(command, args); @@ -147,9 +141,13 @@ export class SynchronousTerminalService implements ITerminalService, Disposable lockFile.dispose(); } } + /** @deprecated */ public sendText(text: string): Promise { return this.terminalService.sendText(text); } + public executeCommand(commandLine: string, isPythonShell: boolean): Promise { + return this.terminalService.executeCommand(commandLine, isPythonShell); + } public show(preserveFocus?: boolean | undefined): Promise { return this.terminalService.show(preserveFocus); } diff --git a/src/client/common/terminal/types.ts b/src/client/common/terminal/types.ts index 487827ffb4db..3e54458a57fd 100644 --- a/src/client/common/terminal/types.ts +++ b/src/client/common/terminal/types.ts @@ -3,7 +3,7 @@ 'use strict'; -import { CancellationToken, Event, Terminal, Uri } from 'vscode'; +import { CancellationToken, Event, Terminal, Uri, TerminalShellExecution } from 'vscode'; import { PythonEnvironment } from '../../pythonEnvironments/info'; import { IEventNamePropertyMapping } from '../../telemetry/index'; import { IDisposable, Resource } from '../types'; @@ -11,9 +11,11 @@ import { IDisposable, Resource } from '../types'; export enum TerminalActivationProviders { bashCShellFish = 'bashCShellFish', commandPromptAndPowerShell = 'commandPromptAndPowerShell', + nushell = 'nushell', pyenv = 'pyenv', conda = 'conda', - pipenv = 'pipenv' + pipenv = 'pipenv', + pixi = 'pixi', } export enum TerminalShellType { powershell = 'powershell', @@ -26,9 +28,10 @@ export enum TerminalShellType { fish = 'fish', cshell = 'cshell', tcshell = 'tshell', + nushell = 'nushell', wsl = 'wsl', xonsh = 'xonsh', - other = 'other' + other = 'other', } export interface ITerminalService extends IDisposable { @@ -47,9 +50,11 @@ export interface ITerminalService extends IDisposable { command: string, args: string[], cancel?: CancellationToken, - swallowExceptions?: boolean + swallowExceptions?: boolean, ): Promise; + /** @deprecated */ sendText(text: string): Promise; + executeCommand(commandLine: string, isPythonShell: boolean): Promise; show(preserveFocus?: boolean): Promise; } @@ -87,24 +92,11 @@ export type TerminalCreationOptions = { }; export interface ITerminalServiceFactory { - /** - * Gets a terminal service with a specific title. - * If one exists, its returned else a new one is created. - * @param {Uri} resource - * @param {string} title - * @returns {ITerminalService} - * @memberof ITerminalServiceFactory - */ - getTerminalService(resource?: Uri, title?: string): ITerminalService; /** * Gets a terminal service. * If one exists with the same information, that is returned else a new one is created. - * - * @param {TerminalCreationOptions} [options] - * @returns {ITerminalService} - * @memberof ITerminalServiceFactory */ - getTerminalService(options?: TerminalCreationOptions): ITerminalService; + getTerminalService(options: TerminalCreationOptions & { newTerminalPerFile?: boolean }): ITerminalService; createTerminalService(resource?: Uri, title?: string): ITerminalService; } @@ -117,12 +109,12 @@ export interface ITerminalHelper { getEnvironmentActivationCommands( terminalShellType: TerminalShellType, resource?: Uri, - interpreter?: PythonEnvironment + interpreter?: PythonEnvironment, ): Promise; getEnvironmentActivationShellCommands( resource: Resource, shell: TerminalShellType, - interpreter?: PythonEnvironment + interpreter?: PythonEnvironment, ): Promise; } @@ -131,11 +123,7 @@ export type TerminalActivationOptions = { resource?: Resource; preserveFocus?: boolean; interpreter?: PythonEnvironment; - /** - * When sending commands to the terminal, do not display the terminal. - * - * @type {boolean} - */ + // When sending commands to the terminal, do not display the terminal. hideFromUser?: boolean; }; export interface ITerminalActivator { @@ -149,7 +137,7 @@ export interface ITerminalActivationCommandProvider { getActivationCommands(resource: Uri | undefined, targetShell: TerminalShellType): Promise; getActivationCommandsForInterpreter( pythonPath: string, - targetShell: TerminalShellType + targetShell: TerminalShellType, ): Promise; } @@ -159,7 +147,7 @@ export interface ITerminalActivationHandler { terminal: Terminal, resource: Uri | undefined, preserveFocus: boolean, - activated: boolean + activated: boolean, ): Promise; } @@ -169,16 +157,10 @@ export const IShellDetector = Symbol('IShellDetector'); /** * Used to identify a shell. * Each implemenetion will provide a unique way of identifying the shell. - * - * @export - * @interface IShellDetector */ export interface IShellDetector { /** * Classes with higher priorities will be used first when identifying the shell. - * - * @type {number} - * @memberof IShellDetector */ readonly priority: number; identify(telemetryProperties: ShellIdentificationTelemetry, terminal?: Terminal): TerminalShellType | undefined; diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 67800d1caaca..c30ad704b6c1 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -1,52 +1,52 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + 'use strict'; import { Socket } from 'net'; -import { Request as RequestResult } from 'request'; import { CancellationToken, + ConfigurationChangeEvent, ConfigurationTarget, - DiagnosticSeverity, Disposable, DocumentSymbolProvider, Event, Extension, ExtensionContext, - OutputChannel, + Memento, + LogOutputChannel, Uri, - WorkspaceEdit } from 'vscode'; import { LanguageServerType } from '../activation/types'; -import { LogLevel } from '../logging/levels'; -import { CommandsWithoutArgs } from './application/commands'; -import { ExtensionChannels } from './insidersBuild/types'; -import { InterpreterUri } from './installer/types'; +import type { InstallOptions, InterpreterUri, ModuleInstallFlags } from './installer/types'; import { EnvironmentVariables } from './variables/types'; -export const IOutputChannel = Symbol('IOutputChannel'); -export interface IOutputChannel extends OutputChannel {} +import { ITestingSettings } from '../testing/configuration/types'; + +export interface IDisposable { + dispose(): void | undefined | Promise; +} + +export const ILogOutputChannel = Symbol('ILogOutputChannel'); +export interface ILogOutputChannel extends LogOutputChannel {} export const IDocumentSymbolProvider = Symbol('IDocumentSymbolProvider'); export interface IDocumentSymbolProvider extends DocumentSymbolProvider {} export const IsWindows = Symbol('IS_WINDOWS'); export const IDisposableRegistry = Symbol('IDisposableRegistry'); -export type IDisposableRegistry = Disposable[]; +export type IDisposableRegistry = IDisposable[]; export const IMemento = Symbol('IGlobalMemento'); export const GLOBAL_MEMENTO = Symbol('IGlobalMemento'); export const WORKSPACE_MEMENTO = Symbol('IWorkspaceMemento'); export type Resource = Uri | undefined; export interface IPersistentState { + /** + * Storage is exposed in this type to make sure folks always use persistent state + * factory to access any type of storage as all storages are tracked there. + */ + readonly storage: Memento; readonly value: T; updateValue(value: T): Promise; } -export type Version = { - raw: string; - major: number; - minor: number; - patch: number; - build: string[]; - prerelease: string[]; -}; export type ReadWrite = { -readonly [P in keyof T]: T[P]; @@ -64,52 +64,36 @@ export type ExecutionInfo = { moduleName?: string; args: string[]; product?: Product; + useShell?: boolean; }; export enum InstallerResponse { Installed, Disabled, - Ignore + Ignore, +} + +export enum ProductInstallStatus { + Installed, + NotInstalled, + NeedsUpgrade, } export enum ProductType { - Linter = 'Linter', - Formatter = 'Formatter', TestFramework = 'TestFramework', - RefactoringLibrary = 'RefactoringLibrary', - WorkspaceSymbols = 'WorkspaceSymbols', - DataScience = 'DataScience' + DataScience = 'DataScience', + Python = 'Python', } export enum Product { pytest = 1, - nosetest = 2, - pylint = 3, - flake8 = 4, - pycodestyle = 5, - pylama = 6, - prospector = 7, - pydocstyle = 8, - yapf = 9, - autopep8 = 10, - mypy = 11, unittest = 12, - ctags = 13, - rope = 14, - isort = 15, - black = 16, - bandit = 17, - jupyter = 18, - ipykernel = 19, - notebook = 20, - kernelspec = 21, - nbconvert = 22, - pandas = 23 -} - -export enum ModuleNamePurpose { - install = 1, - run = 2 + tensorboard = 24, + torchProfilerInstallName = 25, + torchProfilerImportName = 26, + pip = 27, + ensurepip = 28, + python = 29, } export const IInstaller = Symbol('IInstaller'); @@ -118,14 +102,25 @@ export interface IInstaller { promptToInstall( product: Product, resource?: InterpreterUri, - cancel?: CancellationToken + cancel?: CancellationToken, + flags?: ModuleInstallFlags, + ): Promise; + install( + product: Product, + resource?: InterpreterUri, + cancel?: CancellationToken, + flags?: ModuleInstallFlags, + options?: InstallOptions, ): Promise; - install(product: Product, resource?: InterpreterUri, cancel?: CancellationToken): Promise; - isInstalled(product: Product, resource?: InterpreterUri): Promise; - translateProductToModuleName(product: Product, purpose: ModuleNamePurpose): string; + isInstalled(product: Product, resource?: InterpreterUri): Promise; + isProductVersionCompatible( + product: Product, + semVerRequirement: string, + resource?: InterpreterUri, + ): Promise; + translateProductToModuleName(product: Product): string; } -// tslint:disable-next-line:no-suspicious-comment // TODO: Drop IPathUtils in favor of IFileSystemPathUtils. // See https://github.com/microsoft/vscode-python/issues/8542. export const IPathUtils = Symbol('IPathUtils'); @@ -155,150 +150,52 @@ export interface ICurrentProcess { readonly stdout: NodeJS.WriteStream; readonly stdin: NodeJS.ReadStream; readonly execPath: string; + // eslint-disable-next-line @typescript-eslint/ban-types on(event: string | symbol, listener: Function): this; } export interface IPythonSettings { + readonly interpreter: IInterpreterSettings; readonly pythonPath: string; readonly venvPath: string; readonly venvFolders: string[]; + readonly activeStateToolPath: string; readonly condaPath: string; readonly pipenvPath: string; readonly poetryPath: string; - readonly insidersChannel: ExtensionChannels; - readonly downloadLanguageServer: boolean; - readonly showStartPage: boolean; - readonly jediPath: string; - readonly jediMemoryLimit: number; + readonly pixiToolPath: string; readonly devOptions: string[]; - readonly linting: ILintingSettings; - readonly formatting: IFormattingSettings; readonly testing: ITestingSettings; readonly autoComplete: IAutoCompleteSettings; readonly terminal: ITerminalSettings; - readonly sortImports: ISortImportSettings; - readonly workspaceSymbols: IWorkspaceSymbolSettings; readonly envFile: string; - readonly disableInstallationChecks: boolean; readonly globalModuleInstallation: boolean; - readonly analysis: IAnalysisSettings; - readonly autoUpdateLanguageServer: boolean; - readonly datascience: IDataScienceSettings; - readonly onDidChange: Event; readonly experiments: IExperiments; readonly languageServer: LanguageServerType; + readonly languageServerIsDefault: boolean; readonly defaultInterpreterPath: string; - readonly logging: ILoggingSettings; -} -export interface ISortImportSettings { - readonly path: string; - readonly args: string[]; + readonly REPL: IREPLSettings; + register(): void; } -export interface ITestingSettings { - readonly promptToConfigure: boolean; - readonly debugPort: number; - readonly nosetestsEnabled: boolean; - nosetestPath: string; - nosetestArgs: string[]; - readonly pytestEnabled: boolean; - pytestPath: string; - pytestArgs: string[]; - readonly unittestEnabled: boolean; - unittestArgs: string[]; - cwd?: string; - readonly autoTestDiscoverOnSaveEnabled: boolean; -} -export interface IPylintCategorySeverity { - readonly convention: DiagnosticSeverity; - readonly refactor: DiagnosticSeverity; - readonly warning: DiagnosticSeverity; - readonly error: DiagnosticSeverity; - readonly fatal: DiagnosticSeverity; -} -export interface IPycodestyleCategorySeverity { - readonly W: DiagnosticSeverity; - readonly E: DiagnosticSeverity; -} -// tslint:disable-next-line:interface-name -export interface Flake8CategorySeverity { - readonly F: DiagnosticSeverity; - readonly E: DiagnosticSeverity; - readonly W: DiagnosticSeverity; +export interface IInterpreterSettings { + infoVisibility: 'never' | 'onPythonRelated' | 'always'; } -export interface IMypyCategorySeverity { - readonly error: DiagnosticSeverity; - readonly note: DiagnosticSeverity; -} - -export type LoggingLevelSettingType = 'off' | 'error' | 'warn' | 'info' | 'debug'; -export interface ILoggingSettings { - readonly level: LogLevel | 'off'; -} -export interface ILintingSettings { - readonly enabled: boolean; - readonly ignorePatterns: string[]; - readonly prospectorEnabled: boolean; - readonly prospectorArgs: string[]; - readonly pylintEnabled: boolean; - readonly pylintArgs: string[]; - readonly pycodestyleEnabled: boolean; - readonly pycodestyleArgs: string[]; - readonly pylamaEnabled: boolean; - readonly pylamaArgs: string[]; - readonly flake8Enabled: boolean; - readonly flake8Args: string[]; - readonly pydocstyleEnabled: boolean; - readonly pydocstyleArgs: string[]; - readonly lintOnSave: boolean; - readonly maxNumberOfProblems: number; - readonly pylintCategorySeverity: IPylintCategorySeverity; - readonly pycodestyleCategorySeverity: IPycodestyleCategorySeverity; - readonly flake8CategorySeverity: Flake8CategorySeverity; - readonly mypyCategorySeverity: IMypyCategorySeverity; - prospectorPath: string; - pylintPath: string; - pycodestylePath: string; - pylamaPath: string; - flake8Path: string; - pydocstylePath: string; - mypyEnabled: boolean; - mypyArgs: string[]; - mypyPath: string; - banditEnabled: boolean; - banditArgs: string[]; - banditPath: string; - readonly pylintUseMinimalCheckers: boolean; -} -export interface IFormattingSettings { - readonly provider: string; - autopep8Path: string; - readonly autopep8Args: string[]; - blackPath: string; - readonly blackArgs: string[]; - yapfPath: string; - readonly yapfArgs: string[]; -} -export interface IAutoCompleteSettings { - readonly addBrackets: boolean; - readonly extraPaths: string[]; - readonly showAdvancedMembers: boolean; - readonly typeshedPaths: string[]; -} -export interface IWorkspaceSymbolSettings { - readonly enabled: boolean; - tagFilePath: string; - readonly rebuildOnStart: boolean; - readonly rebuildOnFileSave: boolean; - readonly ctagsPath: string; - readonly exclusionPatterns: string[]; -} export interface ITerminalSettings { readonly executeInFileDir: boolean; + readonly focusAfterLaunch: boolean; readonly launchArgs: string[]; readonly activateEnvironment: boolean; readonly activateEnvInCurrentTerminal: boolean; + readonly shellIntegration: { + enabled: boolean; + }; +} + +export interface IREPLSettings { + readonly enableREPLSmartSend: boolean; + readonly sendToNativeREPL: boolean; } export interface IExperiments { @@ -316,109 +213,39 @@ export interface IExperiments { readonly optOutFrom: string[]; } -export enum AnalysisSettingsLogLevel { - Information = 'Information', - Error = 'Error', - Warning = 'Warning' -} - -export type LanguageServerDownloadChannels = 'stable' | 'beta' | 'daily'; -export interface IAnalysisSettings { - readonly downloadChannel?: LanguageServerDownloadChannels; - readonly typeshedPaths: string[]; - readonly cacheFolderPath: string | null; - readonly errors: string[]; - readonly warnings: string[]; - readonly information: string[]; - readonly disabled: string[]; - readonly traceLogging: boolean; - readonly logLevel: AnalysisSettingsLogLevel; -} - -export interface IVariableQuery { - language: string; - query: string; - parseExpr: string; -} - -export interface IDataScienceSettings { - allowImportFromNotebook: boolean; - alwaysTrustNotebooks: boolean; - enabled: boolean; - jupyterInterruptTimeout: number; - jupyterLaunchTimeout: number; - jupyterLaunchRetries: number; - jupyterServerURI: string; - notebookFileRoot: string; - changeDirOnImportExport: boolean; - useDefaultConfigForJupyter: boolean; - searchForJupyter: boolean; - allowInput: boolean; - showCellInputCode: boolean; - collapseCellInputCodeByDefault: boolean; - maxOutputSize: number; - enableScrollingForCellOutputs: boolean; - gatherToScript?: boolean; - gatherSpecPath?: string; - sendSelectionToInteractiveWindow: boolean; - markdownRegularExpression: string; - codeRegularExpression: string; - allowLiveShare?: boolean; - errorBackgroundColor: string; - ignoreVscodeTheme?: boolean; - variableExplorerExclude?: string; - liveShareConnectionTimeout?: number; - decorateCells?: boolean; - enableCellCodeLens?: boolean; - askForLargeDataFrames?: boolean; - enableAutoMoveToNextCell?: boolean; - allowUnauthorizedRemoteConnection?: boolean; - askForKernelRestart?: boolean; - enablePlotViewer?: boolean; - codeLenses?: string; - debugCodeLenses?: string; - debugpyDistPath?: string; - stopOnFirstLineWhileDebugging?: boolean; - textOutputLimit?: number; - magicCommandsAsComments?: boolean; - stopOnError?: boolean; - remoteDebuggerPort?: number; - colorizeInputBox?: boolean; - addGotoCodeLenses?: boolean; - useNotebookEditor?: boolean; - runMagicCommands?: string; - runStartupCommands: string | string[]; - debugJustMyCode: boolean; - defaultCellMarker?: string; - verboseLogging?: boolean; - themeMatplotlibPlots?: boolean; - useWebViewServer?: boolean; - variableQueries: IVariableQuery[]; - disableJupyterAutoStart?: boolean; - jupyterCommandLineArguments: string[]; - widgetScriptSources: WidgetCDNs[]; - alwaysScrollOnNewCell?: boolean; - interactiveWindowMode: InteractiveWindowMode; +export interface IAutoCompleteSettings { + readonly extraPaths: string[]; } -export type InteractiveWindowMode = 'perFile' | 'single' | 'multiple'; - -export type WidgetCDNs = 'unpkg.com' | 'jsdelivr.com'; - export const IConfigurationService = Symbol('IConfigurationService'); export interface IConfigurationService { + readonly onDidChange: Event; getSettings(resource?: Uri): IPythonSettings; isTestExecution(): boolean; - updateSetting(setting: string, value?: {}, resource?: Uri, configTarget?: ConfigurationTarget): Promise; + updateSetting(setting: string, value?: unknown, resource?: Uri, configTarget?: ConfigurationTarget): Promise; updateSectionSetting( section: string, setting: string, - value?: {}, + value?: unknown, resource?: Uri, - configTarget?: ConfigurationTarget + configTarget?: ConfigurationTarget, ): Promise; } +/** + * Carries various tool execution path settings. For eg. pipenvPath, condaPath, pytestPath etc. These can be + * potentially used in discovery, autoselection, activation, installers, execution etc. And so should be a + * common interface to all the components. + */ +export const IToolExecutionPath = Symbol('IToolExecutionPath'); +export interface IToolExecutionPath { + readonly executable: string; +} +export enum ToolExecutionPath { + pipenv = 'pipenv', + // Gradually populate this list with tools as they come up. +} + export const ISocketServer = Symbol('ISocketServer'); export interface ISocketServer extends Disposable { readonly client: Promise; @@ -432,12 +259,6 @@ export type DownloadOptions = { * @type {('Downloading ... ' | string)} */ progressMessagePrefix: 'Downloading ... ' | string; - /** - * Output panel into which progress information is written. - * - * @type {IOutputChannel} - */ - outputChannel?: IOutputChannel; /** * Extension of file that'll be created when downloading the file. * @@ -446,41 +267,6 @@ export type DownloadOptions = { extension: 'tmp' | string; }; -export const IFileDownloader = Symbol('IFileDownloader'); -/** - * File downloader, that'll display progress in the status bar. - * - * @export - * @interface IFileDownloader - */ -export interface IFileDownloader { - /** - * Download file and display progress in statusbar. - * Optionnally display progress in the provided output channel. - * - * @param {string} uri - * @param {DownloadOptions} options - * @returns {Promise} - * @memberof IFileDownloader - */ - downloadFile(uri: string, options: DownloadOptions): Promise; -} - -export const IHttpClient = Symbol('IHttpClient'); -export interface IHttpClient { - downloadFile(uri: string): Promise; - /** - * Downloads file from uri as string and parses them into JSON objects - * @param uri The uri to download the JSON from - * @param strict Set `false` to allow trailing comma and comments in the JSON, defaults to `true` - */ - getJSON(uri: string, strict?: boolean): Promise; - /** - * Returns the url is valid (i.e. return status code of 200). - */ - exists(uri: string): Promise; -} - export const IExtensionContext = Symbol('ExtensionContext'); export interface IExtensionContext extends ExtensionContext {} @@ -489,8 +275,8 @@ export interface IExtensions { /** * All extensions currently known to the system. */ - // tslint:disable-next-line:no-any - readonly all: readonly Extension[]; + + readonly all: readonly Extension[]; /** * An event which fires when `extensions.all` changes. This can happen when extensions are @@ -504,8 +290,8 @@ export interface IExtensions { * @param extensionId An extension identifier. * @return An extension or `undefined`. */ - // tslint:disable-next-line:no-any - getExtension(extensionId: string): Extension | undefined; + + getExtension(extensionId: string): Extension | undefined; /** * Get an extension its full identifier in the form of: `publisher.name`. @@ -514,6 +300,11 @@ export interface IExtensions { * @return An extension or `undefined`. */ getExtension(extensionId: string): Extension | undefined; + + /** + * Determines which extension called into our extension code based on call stacks. + */ + determineExtensionFromCallStack(): Promise<{ extensionId: string; displayName: string }>; } export const IBrowserService = Symbol('IBrowserService'); @@ -521,47 +312,6 @@ export interface IBrowserService { launch(url: string): void; } -export const IPythonExtensionBanner = Symbol('IPythonExtensionBanner'); -export interface IPythonExtensionBanner { - readonly enabled: boolean; - showBanner(): Promise; -} -export const BANNER_NAME_PROPOSE_LS: string = 'ProposePylance'; -export const BANNER_NAME_DS_SURVEY: string = 'DSSurveyBanner'; -export const BANNER_NAME_INTERACTIVE_SHIFTENTER: string = 'InteractiveShiftEnterBanner'; - -export type DeprecatedSettingAndValue = { - setting: string; - values?: {}[]; -}; - -export type DeprecatedFeatureInfo = { - doNotDisplayPromptStateKey: string; - message: string; - moreInfoUrl: string; - commands?: CommandsWithoutArgs[]; - setting?: DeprecatedSettingAndValue; -}; - -export const IFeatureDeprecationManager = Symbol('IFeatureDeprecationManager'); - -export interface IFeatureDeprecationManager extends Disposable { - initialize(): void; - registerDeprecation(deprecatedInfo: DeprecatedFeatureInfo): void; -} - -export const IEditorUtils = Symbol('IEditorUtils'); -export interface IEditorUtils { - getWorkspaceEditsFromPatch(originalContents: string, patch: string, uri: Uri): WorkspaceEdit; -} - -export interface IDisposable { - dispose(): void | undefined; -} -export interface IAsyncDisposable { - dispose(): Promise; -} - /** * Stores hash formats */ @@ -570,71 +320,14 @@ export interface IHashFormat { string: string; // If hash format is a string } -/** - * Interface used to implement cryptography tools - */ -export const ICryptoUtils = Symbol('ICryptoUtils'); -export interface ICryptoUtils { - /** - * Creates hash using the data and encoding specified - * @returns hash as number, or string - * @param data The string to hash - * @param hashFormat Return format of the hash, number or string - * @param [algorithm] - */ - createHash( - data: string, - hashFormat: E, - algorithm?: 'SHA512' | 'SHA256' | 'FNV' - ): IHashFormat[E]; -} - -export const IAsyncDisposableRegistry = Symbol('IAsyncDisposableRegistry'); -export interface IAsyncDisposableRegistry extends IAsyncDisposable { - push(disposable: IDisposable | IAsyncDisposable): void; -} - -/* ABExperiments field carries the identity, and the range of the experiment, - where the experiment is valid for users falling between the number 'min' and 'max' - More details: https://en.wikipedia.org/wiki/A/B_testing -*/ -export type ABExperiments = { - name: string; // Name of the experiment - salt: string; // Salt string for the experiment - min: number; // Lower limit for the experiment - max: number; // Upper limit for the experiment -}[]; - -/** - * Interface used to implement AB testing - */ -export const IExperimentsManager = Symbol('IExperimentsManager'); -export interface IExperimentsManager { - /** - * Checks if experiments are enabled, sets required environment to be used for the experiments, logs experiment groups - */ - activate(): Promise; - - /** - * Checks if user is in experiment or not - * @param experimentName Name of the experiment - * @returns `true` if user is in experiment, `false` if user is not in experiment - */ - inExperiment(experimentName: string): boolean; - - /** - * Sends experiment telemetry if user is in experiment - * @param experimentName Name of the experiment - */ - sendTelemetryIfInExperiment(experimentName: string): void; -} - /** * Experiment service leveraging VS Code's experiment framework. */ export const IExperimentService = Symbol('IExperimentService'); export interface IExperimentService { + activate(): Promise; inExperiment(experimentName: string): Promise; + inExperimentSync(experimentName: string): boolean; getExperimentValue(experimentName: string): Promise; } @@ -654,5 +347,20 @@ export interface IInterpreterPathService { get(resource: Resource): string; inspect(resource: Resource): InspectInterpreterSettingType; update(resource: Resource, configTarget: ConfigurationTarget, value: string | undefined): Promise; - copyOldInterpreterStorageValuesToNew(resource: Uri | undefined): Promise; + copyOldInterpreterStorageValuesToNew(resource: Resource): Promise; +} + +export type DefaultLSType = LanguageServerType.Jedi | LanguageServerType.Node; + +/** + * Interface used to retrieve the default language server. + * + * Note: This is added to get around a problem that the config service is not `async`. + * Adding experiment check there would mean touching the entire extension. For simplicity + * this is a solution. + */ +export const IDefaultLanguageServer = Symbol('IDefaultLanguageServer'); + +export interface IDefaultLanguageServer { + readonly defaultLSType: DefaultLSType; } diff --git a/src/client/common/utils/arrayUtils.ts b/src/client/common/utils/arrayUtils.ts new file mode 100644 index 000000000000..5ec671118297 --- /dev/null +++ b/src/client/common/utils/arrayUtils.ts @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/** + * Returns the elements of an array that meet the condition specified in an async callback function. + * @param asyncPredicate The filter method calls the async predicate function one time for each element in the array. + */ +export async function asyncFilter(arr: T[], asyncPredicate: (value: T) => Promise): Promise { + const results = await Promise.all(arr.map(asyncPredicate)); + return arr.filter((_v, index) => results[index]); +} + +export async function asyncForEach(arr: T[], asyncFunc: (value: T) => Promise): Promise { + await Promise.all(arr.map(asyncFunc)); +} diff --git a/src/client/common/utils/async.ts b/src/client/common/utils/async.ts index 00566150b2c0..a44425f8f1a3 100644 --- a/src/client/common/utils/async.ts +++ b/src/client/common/utils/async.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-use-before-define */ +/* eslint-disable no-async-promise-executor */ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -9,98 +11,90 @@ export async function sleep(timeout: number): Promise { }); } -export async function waitForPromise(promise: Promise, timeout: number): Promise { - // Set a timer that will resolve with null - return new Promise((resolve, reject) => { - const timer = setTimeout(() => resolve(null), timeout); - promise - .then((result) => { - // When the promise resolves, make sure to clear the timer or - // the timer may stick around causing tests to wait - clearTimeout(timer); - resolve(result); - }) - .catch((e) => { - clearTimeout(timer); - reject(e); - }); - }); -} - -// tslint:disable-next-line: no-any +// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types export function isThenable(v: any): v is Thenable { return typeof v?.then === 'function'; } -// tslint:disable-next-line: no-any +// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types export function isPromise(v: any): v is Promise { return typeof v?.then === 'function' && typeof v?.catch === 'function'; } -//====================== // Deferred -// tslint:disable-next-line:interface-name export interface Deferred { readonly promise: Promise; readonly resolved: boolean; readonly rejected: boolean; readonly completed: boolean; resolve(value?: T | PromiseLike): void; - // tslint:disable-next-line:no-any - reject(reason?: any): void; + reject(reason?: string | Error | Record | unknown): void; } class DeferredImpl implements Deferred { - private _resolve!: (value?: T | PromiseLike) => void; - // tslint:disable-next-line:no-any - private _reject!: (reason?: any) => void; - private _resolved: boolean = false; - private _rejected: boolean = false; + private _resolve!: (value: T | PromiseLike) => void; + + private _reject!: (reason?: string | Error | Record) => void; + + private _resolved = false; + + private _rejected = false; + private _promise: Promise; - // tslint:disable-next-line:no-any + + // eslint-disable-next-line @typescript-eslint/no-explicit-any constructor(private scope: any = null) { - // tslint:disable-next-line:promise-must-complete this._promise = new Promise((res, rej) => { this._resolve = res; this._reject = rej; }); } - public resolve(_value?: T | PromiseLike) { - // tslint:disable-next-line:no-any - this._resolve.apply(this.scope ? this.scope : this, arguments as any); + + public resolve(_value: T | PromiseLike) { + if (this.completed) { + return; + } + this._resolve.apply(this.scope ? this.scope : this, [_value]); this._resolved = true; } - // tslint:disable-next-line:no-any - public reject(_reason?: any) { - // tslint:disable-next-line:no-any - this._reject.apply(this.scope ? this.scope : this, arguments as any); + + public reject(_reason?: string | Error | Record) { + if (this.completed) { + return; + } + this._reject.apply(this.scope ? this.scope : this, [_reason]); this._rejected = true; } + get promise(): Promise { return this._promise; } + get resolved(): boolean { return this._resolved; } + get rejected(): boolean { return this._rejected; } + get completed(): boolean { return this._rejected || this._resolved; } } -// tslint:disable-next-line:no-any -export function createDeferred(scope: any = null): Deferred { + +// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types +export function createDeferred(scope: any = null): Deferred { return new DeferredImpl(scope); } export function createDeferredFrom(...promises: Promise[]): Deferred { const deferred = createDeferred(); Promise.all(promises) - // tslint:disable-next-line:no-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any .then(deferred.resolve.bind(deferred) as any) - // tslint:disable-next-line:no-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any .catch(deferred.reject.bind(deferred) as any); return deferred; @@ -111,15 +105,19 @@ export function createDeferredFromPromise(promise: Promise): Deferred { return deferred; } -//================================ // iterators +interface IAsyncIterator extends AsyncIterator {} + +export interface IAsyncIterableIterator extends IAsyncIterator, AsyncIterable {} + /** * An iterator that yields nothing. */ -export function iterEmpty(): AsyncIterator { - // tslint:disable-next-line:no-empty - return ((async function* () {})() as unknown) as AsyncIterator; +export function iterEmpty(): IAsyncIterableIterator { + return ((async function* () { + /** No body. */ + })() as unknown) as IAsyncIterableIterator; } type NextResult = { index: number } & ( @@ -132,12 +130,13 @@ async function getNext(it: AsyncIterator, indexMaybe?: number): const result = await it.next(); return { index, result, err: null }; } catch (err) { - return { index, err, result: null }; + return { index, err: err as Error, result: null }; } } -// tslint:disable-next-line:promise-must-complete no-empty -export const NEVER: Promise = new Promise(() => {}); +const NEVER: Promise = new Promise(() => { + /** No body. */ +}); /** * Yield everything produced by the given iterators as soon as each is ready. @@ -151,13 +150,17 @@ export const NEVER: Promise = new Promise(() => {}); */ export async function* chain( iterators: AsyncIterator[], - onError?: (err: Error, index: number) => Promise + onError?: (err: Error, index: number) => Promise, // Ultimately we may also want to support cancellation. -): AsyncIterator { +): IAsyncIterableIterator { const promises = iterators.map(getNext); let numRunning = iterators.length; + while (numRunning > 0) { + // Promise.race will not fail, because each promise calls getNext, + // Which handles failures by wrapping each iterator in a try/catch block. const { index, result, err } = await Promise.race(promises); + if (err !== null) { promises[index] = NEVER as Promise>; numRunning -= 1; @@ -192,8 +195,8 @@ export async function* chain( export async function* mapToIterator( items: T[], func: (item: T) => Promise, - race = true -): AsyncIterator { + race = true, +): IAsyncIterableIterator { if (race) { const iterators = items.map((item) => { async function* generator() { @@ -210,8 +213,8 @@ export async function* mapToIterator( /** * Convert an iterator into an iterable, if it isn't one already. */ -export function iterable(iterator: AsyncIterator): AsyncIterableIterator { - const it = iterator as AsyncIterableIterator; +export function iterable(iterator: IAsyncIterator): IAsyncIterableIterator { + const it = iterator as IAsyncIterableIterator; if (it[Symbol.asyncIterator] === undefined) { it[Symbol.asyncIterator] = () => it; } @@ -221,14 +224,70 @@ export function iterable(iterator: AsyncIterator): AsyncIterableIter /** * Get everything yielded by the iterator. */ -export async function flattenIterator(iterator: AsyncIterator): Promise { +export async function flattenIterator(iterator: IAsyncIterator): Promise { + const results: T[] = []; + for await (const item of iterable(iterator)) { + results.push(item); + } + return results; +} + +/** + * Get everything yielded by the iterable. + */ +export async function flattenIterable(iterableItem: AsyncIterable): Promise { const results: T[] = []; - // We are dealing with an iterator, not an iterable, so we have - // to iterate manually rather than with a for-await loop. - let result = await iterator.next(); - while (!result.done) { - results.push(result.value); - result = await iterator.next(); + for await (const item of iterableItem) { + results.push(item); } return results; } + +/** + * Wait for a condition to be fulfilled within a timeout. + */ +export async function waitForCondition( + condition: () => Promise, + timeoutMs: number, + errorMessage: string, +): Promise { + return new Promise(async (resolve, reject) => { + const timeout = setTimeout(() => { + clearTimeout(timeout); + + clearTimeout(timer); + reject(new Error(errorMessage)); + }, timeoutMs); + const timer = setInterval(async () => { + if (!(await condition().catch(() => false))) { + return; + } + clearTimeout(timeout); + clearTimeout(timer); + resolve(); + }, 10); + }); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function isPromiseLike(v: any): v is PromiseLike { + return typeof v?.then === 'function'; +} + +export function raceTimeout(timeout: number, ...promises: Promise[]): Promise; +export function raceTimeout(timeout: number, defaultValue: T, ...promises: Promise[]): Promise; +export function raceTimeout(timeout: number, defaultValue: T, ...promises: Promise[]): Promise { + const resolveValue = isPromiseLike(defaultValue) ? undefined : defaultValue; + if (isPromiseLike(defaultValue)) { + promises.push((defaultValue as unknown) as Promise); + } + + let promiseResolve: ((value: T) => void) | undefined = undefined; + + const timer = setTimeout(() => promiseResolve?.((resolveValue as unknown) as T), timeout); + + return Promise.race([ + Promise.race(promises).finally(() => clearTimeout(timer)), + new Promise((resolve) => (promiseResolve = resolve)), + ]); +} diff --git a/src/client/common/utils/cacheUtils.ts b/src/client/common/utils/cacheUtils.ts index 2f6895b4e5a8..6101b3ef928f 100644 --- a/src/client/common/utils/cacheUtils.ts +++ b/src/client/common/utils/cacheUtils.ts @@ -3,97 +3,9 @@ 'use strict'; -// tslint:disable:no-any no-require-imports - -import { Uri } from 'vscode'; -import '../../common/extensions'; -import { IServiceContainer } from '../../ioc/types'; -import { DEFAULT_INTERPRETER_SETTING } from '../constants'; -import { DeprecatePythonPath } from '../experiments/groups'; -import { IExperimentsManager, IInterpreterPathService, Resource } from '../types'; - -type VSCodeType = typeof import('vscode'); -type CacheData = { - value: unknown; - expiry: number; -}; -const resourceSpecificCacheStores = new Map>(); - -/** - * Get a cache key specific to a resource (i.e. workspace) - * This key will be used to cache interpreter related data, hence the Python Path - * used in a workspace will affect the cache key. - * @param {Resource} resource - * @param {VSCodeType} [vscode=require('vscode')] - * @param serviceContainer - * @returns - */ -function getCacheKey( - resource: Resource, - vscode: VSCodeType = require('vscode'), - serviceContainer: IServiceContainer | undefined -) { - const section = vscode.workspace.getConfiguration('python', vscode.Uri.file(__filename)); - if (!section) { - return 'python'; - } - let interpreterPathService: IInterpreterPathService | undefined; - let inExperiment: boolean | undefined; - if (serviceContainer) { - interpreterPathService = serviceContainer.get(IInterpreterPathService); - const abExperiments = serviceContainer.get(IExperimentsManager); - inExperiment = abExperiments.inExperiment(DeprecatePythonPath.experiment); - abExperiments.sendTelemetryIfInExperiment(DeprecatePythonPath.control); - } - const globalPythonPath = - inExperiment && interpreterPathService - ? interpreterPathService.inspect(vscode.Uri.file(__filename)).globalValue || DEFAULT_INTERPRETER_SETTING - : section.inspect('pythonPath')!.globalValue || DEFAULT_INTERPRETER_SETTING; - // Get the workspace related to this resource. - if ( - !resource || - !Array.isArray(vscode.workspace.workspaceFolders) || - vscode.workspace.workspaceFolders.length === 0 - ) { - return globalPythonPath; - } - const folder = resource ? vscode.workspace.getWorkspaceFolder(resource) : vscode.workspace.workspaceFolders[0]; - if (!folder) { - return globalPythonPath; - } - const workspacePythonPath = - inExperiment && interpreterPathService - ? interpreterPathService.get(resource) - : vscode.workspace.getConfiguration('python', resource).get('pythonPath') || - DEFAULT_INTERPRETER_SETTING; - return `${folder.uri.fsPath}-${workspacePythonPath}`; -} -/** - * Gets the cache store for a resource that's specific to the interpreter. - * @param {Resource} resource - * @param {VSCodeType} [vscode=require('vscode')] - * @param serviceContainer - * @returns - */ -function getCacheStore( - resource: Resource, - vscode: VSCodeType = require('vscode'), - serviceContainer: IServiceContainer | undefined -) { - const key = getCacheKey(resource, vscode, serviceContainer); - if (!resourceSpecificCacheStores.has(key)) { - resourceSpecificCacheStores.set(key, new Map()); - } - return resourceSpecificCacheStores.get(key)!; -} - const globalCacheStore = new Map(); -/** - * Gets a cache store to be used to store return values of methods or any other. - * - * @returns - */ +// Gets a cache store to be used to store return values of methods or any other. export function getGlobalCacheStore() { return globalCacheStore; } @@ -105,18 +17,23 @@ export function getCacheKeyFromFunctionArgs(keyPrefix: string, fnArgs: any[]): s export function clearCache() { globalCacheStore.clear(); - resourceSpecificCacheStores.clear(); } +type CacheData = { + value: T; + expiry: number; +}; + +/** + * InMemoryCache caches a single value up until its expiry. + */ export class InMemoryCache { - private readonly _store = new Map(); - protected get store(): Map { - return this._store; - } - constructor(protected readonly expiryDurationMs: number, protected readonly cacheKey: string = '') {} + private cacheData?: CacheData; + + constructor(protected readonly expiryDurationMs: number) {} public get hasData() { - if (!this.store.get(this.cacheKey) || this.hasExpired(this.store.get(this.cacheKey)!.expiry)) { - this.store.delete(this.cacheKey); + if (!this.cacheData || this.hasExpired(this.cacheData.expiry)) { + this.cacheData = undefined; return false; } return true; @@ -130,19 +47,23 @@ export class InMemoryCache { * @memberof InMemoryCache */ public get data(): T | undefined { - if (!this.hasData || !this.store.has(this.cacheKey)) { + if (!this.hasData) { return; } - return this.store.get(this.cacheKey)?.value as T; + return this.cacheData?.value; } public set data(value: T | undefined) { - this.store.set(this.cacheKey, { - expiry: this.calculateExpiry(), - value - }); + if (value !== undefined) { + this.cacheData = { + expiry: this.calculateExpiry(), + value, + }; + } else { + this.cacheData = undefined; + } } public clear() { - this.store.clear(); + this.cacheData = undefined; } /** @@ -166,20 +87,3 @@ export class InMemoryCache { return Date.now() + this.expiryDurationMs; } } - -export class InMemoryInterpreterSpecificCache extends InMemoryCache { - private readonly resource: Resource; - protected get store() { - return getCacheStore(this.resource, this.vscode, this.serviceContainer); - } - constructor( - keyPrefix: string, - expiryDurationMs: number, - args: [Uri | undefined, ...any[]], - private readonly serviceContainer: IServiceContainer | undefined, - private readonly vscode: VSCodeType = require('vscode') - ) { - super(expiryDurationMs, getCacheKeyFromFunctionArgs(keyPrefix, args.slice(1))); - this.resource = args[0]; - } -} diff --git a/src/client/common/utils/charCode.ts b/src/client/common/utils/charCode.ts new file mode 100644 index 000000000000..ba76626bfcbb --- /dev/null +++ b/src/client/common/utils/charCode.ts @@ -0,0 +1,453 @@ +//!!! DO NOT modify, this file was COPIED from 'microsoft/vscode' + +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// Names from https://blog.codinghorror.com/ascii-pronunciation-rules-for-programmers/ + +/** + * An inlined enum containing useful character codes (to be used with String.charCodeAt). + * Please leave the const keyword such that it gets inlined when compiled to JavaScript! + */ +export const enum CharCode { + Null = 0, + /** + * The `\b` character. + */ + Backspace = 8, + /** + * The `\t` character. + */ + Tab = 9, + /** + * The `\n` character. + */ + LineFeed = 10, + /** + * The `\r` character. + */ + CarriageReturn = 13, + Space = 32, + /** + * The `!` character. + */ + ExclamationMark = 33, + /** + * The `"` character. + */ + DoubleQuote = 34, + /** + * The `#` character. + */ + Hash = 35, + /** + * The `$` character. + */ + DollarSign = 36, + /** + * The `%` character. + */ + PercentSign = 37, + /** + * The `&` character. + */ + Ampersand = 38, + /** + * The `'` character. + */ + SingleQuote = 39, + /** + * The `(` character. + */ + OpenParen = 40, + /** + * The `)` character. + */ + CloseParen = 41, + /** + * The `*` character. + */ + Asterisk = 42, + /** + * The `+` character. + */ + Plus = 43, + /** + * The `,` character. + */ + Comma = 44, + /** + * The `-` character. + */ + Dash = 45, + /** + * The `.` character. + */ + Period = 46, + /** + * The `/` character. + */ + Slash = 47, + + Digit0 = 48, + Digit1 = 49, + Digit2 = 50, + Digit3 = 51, + Digit4 = 52, + Digit5 = 53, + Digit6 = 54, + Digit7 = 55, + Digit8 = 56, + Digit9 = 57, + + /** + * The `:` character. + */ + Colon = 58, + /** + * The `;` character. + */ + Semicolon = 59, + /** + * The `<` character. + */ + LessThan = 60, + /** + * The `=` character. + */ + Equals = 61, + /** + * The `>` character. + */ + GreaterThan = 62, + /** + * The `?` character. + */ + QuestionMark = 63, + /** + * The `@` character. + */ + AtSign = 64, + + A = 65, + B = 66, + C = 67, + D = 68, + E = 69, + F = 70, + G = 71, + H = 72, + I = 73, + J = 74, + K = 75, + L = 76, + M = 77, + N = 78, + O = 79, + P = 80, + Q = 81, + R = 82, + S = 83, + T = 84, + U = 85, + V = 86, + W = 87, + X = 88, + Y = 89, + Z = 90, + + /** + * The `[` character. + */ + OpenSquareBracket = 91, + /** + * The `\` character. + */ + Backslash = 92, + /** + * The `]` character. + */ + CloseSquareBracket = 93, + /** + * The `^` character. + */ + Caret = 94, + /** + * The `_` character. + */ + Underline = 95, + /** + * The ``(`)`` character. + */ + BackTick = 96, + + a = 97, + b = 98, + c = 99, + d = 100, + e = 101, + f = 102, + g = 103, + h = 104, + i = 105, + j = 106, + k = 107, + l = 108, + m = 109, + n = 110, + o = 111, + p = 112, + q = 113, + r = 114, + s = 115, + t = 116, + u = 117, + v = 118, + w = 119, + x = 120, + y = 121, + z = 122, + + /** + * The `{` character. + */ + OpenCurlyBrace = 123, + /** + * The `|` character. + */ + Pipe = 124, + /** + * The `}` character. + */ + CloseCurlyBrace = 125, + /** + * The `~` character. + */ + Tilde = 126, + + /** + * The   (no-break space) character. + * Unicode Character 'NO-BREAK SPACE' (U+00A0) + */ + NoBreakSpace = 160, + + U_Combining_Grave_Accent = 0x0300, // U+0300 Combining Grave Accent + U_Combining_Acute_Accent = 0x0301, // U+0301 Combining Acute Accent + U_Combining_Circumflex_Accent = 0x0302, // U+0302 Combining Circumflex Accent + U_Combining_Tilde = 0x0303, // U+0303 Combining Tilde + U_Combining_Macron = 0x0304, // U+0304 Combining Macron + U_Combining_Overline = 0x0305, // U+0305 Combining Overline + U_Combining_Breve = 0x0306, // U+0306 Combining Breve + U_Combining_Dot_Above = 0x0307, // U+0307 Combining Dot Above + U_Combining_Diaeresis = 0x0308, // U+0308 Combining Diaeresis + U_Combining_Hook_Above = 0x0309, // U+0309 Combining Hook Above + U_Combining_Ring_Above = 0x030a, // U+030A Combining Ring Above + U_Combining_Double_Acute_Accent = 0x030b, // U+030B Combining Double Acute Accent + U_Combining_Caron = 0x030c, // U+030C Combining Caron + U_Combining_Vertical_Line_Above = 0x030d, // U+030D Combining Vertical Line Above + U_Combining_Double_Vertical_Line_Above = 0x030e, // U+030E Combining Double Vertical Line Above + U_Combining_Double_Grave_Accent = 0x030f, // U+030F Combining Double Grave Accent + U_Combining_Candrabindu = 0x0310, // U+0310 Combining Candrabindu + U_Combining_Inverted_Breve = 0x0311, // U+0311 Combining Inverted Breve + U_Combining_Turned_Comma_Above = 0x0312, // U+0312 Combining Turned Comma Above + U_Combining_Comma_Above = 0x0313, // U+0313 Combining Comma Above + U_Combining_Reversed_Comma_Above = 0x0314, // U+0314 Combining Reversed Comma Above + U_Combining_Comma_Above_Right = 0x0315, // U+0315 Combining Comma Above Right + U_Combining_Grave_Accent_Below = 0x0316, // U+0316 Combining Grave Accent Below + U_Combining_Acute_Accent_Below = 0x0317, // U+0317 Combining Acute Accent Below + U_Combining_Left_Tack_Below = 0x0318, // U+0318 Combining Left Tack Below + U_Combining_Right_Tack_Below = 0x0319, // U+0319 Combining Right Tack Below + U_Combining_Left_Angle_Above = 0x031a, // U+031A Combining Left Angle Above + U_Combining_Horn = 0x031b, // U+031B Combining Horn + U_Combining_Left_Half_Ring_Below = 0x031c, // U+031C Combining Left Half Ring Below + U_Combining_Up_Tack_Below = 0x031d, // U+031D Combining Up Tack Below + U_Combining_Down_Tack_Below = 0x031e, // U+031E Combining Down Tack Below + U_Combining_Plus_Sign_Below = 0x031f, // U+031F Combining Plus Sign Below + U_Combining_Minus_Sign_Below = 0x0320, // U+0320 Combining Minus Sign Below + U_Combining_Palatalized_Hook_Below = 0x0321, // U+0321 Combining Palatalized Hook Below + U_Combining_Retroflex_Hook_Below = 0x0322, // U+0322 Combining Retroflex Hook Below + U_Combining_Dot_Below = 0x0323, // U+0323 Combining Dot Below + U_Combining_Diaeresis_Below = 0x0324, // U+0324 Combining Diaeresis Below + U_Combining_Ring_Below = 0x0325, // U+0325 Combining Ring Below + U_Combining_Comma_Below = 0x0326, // U+0326 Combining Comma Below + U_Combining_Cedilla = 0x0327, // U+0327 Combining Cedilla + U_Combining_Ogonek = 0x0328, // U+0328 Combining Ogonek + U_Combining_Vertical_Line_Below = 0x0329, // U+0329 Combining Vertical Line Below + U_Combining_Bridge_Below = 0x032a, // U+032A Combining Bridge Below + U_Combining_Inverted_Double_Arch_Below = 0x032b, // U+032B Combining Inverted Double Arch Below + U_Combining_Caron_Below = 0x032c, // U+032C Combining Caron Below + U_Combining_Circumflex_Accent_Below = 0x032d, // U+032D Combining Circumflex Accent Below + U_Combining_Breve_Below = 0x032e, // U+032E Combining Breve Below + U_Combining_Inverted_Breve_Below = 0x032f, // U+032F Combining Inverted Breve Below + U_Combining_Tilde_Below = 0x0330, // U+0330 Combining Tilde Below + U_Combining_Macron_Below = 0x0331, // U+0331 Combining Macron Below + U_Combining_Low_Line = 0x0332, // U+0332 Combining Low Line + U_Combining_Double_Low_Line = 0x0333, // U+0333 Combining Double Low Line + U_Combining_Tilde_Overlay = 0x0334, // U+0334 Combining Tilde Overlay + U_Combining_Short_Stroke_Overlay = 0x0335, // U+0335 Combining Short Stroke Overlay + U_Combining_Long_Stroke_Overlay = 0x0336, // U+0336 Combining Long Stroke Overlay + U_Combining_Short_Solidus_Overlay = 0x0337, // U+0337 Combining Short Solidus Overlay + U_Combining_Long_Solidus_Overlay = 0x0338, // U+0338 Combining Long Solidus Overlay + U_Combining_Right_Half_Ring_Below = 0x0339, // U+0339 Combining Right Half Ring Below + U_Combining_Inverted_Bridge_Below = 0x033a, // U+033A Combining Inverted Bridge Below + U_Combining_Square_Below = 0x033b, // U+033B Combining Square Below + U_Combining_Seagull_Below = 0x033c, // U+033C Combining Seagull Below + U_Combining_X_Above = 0x033d, // U+033D Combining X Above + U_Combining_Vertical_Tilde = 0x033e, // U+033E Combining Vertical Tilde + U_Combining_Double_Overline = 0x033f, // U+033F Combining Double Overline + U_Combining_Grave_Tone_Mark = 0x0340, // U+0340 Combining Grave Tone Mark + U_Combining_Acute_Tone_Mark = 0x0341, // U+0341 Combining Acute Tone Mark + U_Combining_Greek_Perispomeni = 0x0342, // U+0342 Combining Greek Perispomeni + U_Combining_Greek_Koronis = 0x0343, // U+0343 Combining Greek Koronis + U_Combining_Greek_Dialytika_Tonos = 0x0344, // U+0344 Combining Greek Dialytika Tonos + U_Combining_Greek_Ypogegrammeni = 0x0345, // U+0345 Combining Greek Ypogegrammeni + U_Combining_Bridge_Above = 0x0346, // U+0346 Combining Bridge Above + U_Combining_Equals_Sign_Below = 0x0347, // U+0347 Combining Equals Sign Below + U_Combining_Double_Vertical_Line_Below = 0x0348, // U+0348 Combining Double Vertical Line Below + U_Combining_Left_Angle_Below = 0x0349, // U+0349 Combining Left Angle Below + U_Combining_Not_Tilde_Above = 0x034a, // U+034A Combining Not Tilde Above + U_Combining_Homothetic_Above = 0x034b, // U+034B Combining Homothetic Above + U_Combining_Almost_Equal_To_Above = 0x034c, // U+034C Combining Almost Equal To Above + U_Combining_Left_Right_Arrow_Below = 0x034d, // U+034D Combining Left Right Arrow Below + U_Combining_Upwards_Arrow_Below = 0x034e, // U+034E Combining Upwards Arrow Below + U_Combining_Grapheme_Joiner = 0x034f, // U+034F Combining Grapheme Joiner + U_Combining_Right_Arrowhead_Above = 0x0350, // U+0350 Combining Right Arrowhead Above + U_Combining_Left_Half_Ring_Above = 0x0351, // U+0351 Combining Left Half Ring Above + U_Combining_Fermata = 0x0352, // U+0352 Combining Fermata + U_Combining_X_Below = 0x0353, // U+0353 Combining X Below + U_Combining_Left_Arrowhead_Below = 0x0354, // U+0354 Combining Left Arrowhead Below + U_Combining_Right_Arrowhead_Below = 0x0355, // U+0355 Combining Right Arrowhead Below + U_Combining_Right_Arrowhead_And_Up_Arrowhead_Below = 0x0356, // U+0356 Combining Right Arrowhead And Up Arrowhead Below + U_Combining_Right_Half_Ring_Above = 0x0357, // U+0357 Combining Right Half Ring Above + U_Combining_Dot_Above_Right = 0x0358, // U+0358 Combining Dot Above Right + U_Combining_Asterisk_Below = 0x0359, // U+0359 Combining Asterisk Below + U_Combining_Double_Ring_Below = 0x035a, // U+035A Combining Double Ring Below + U_Combining_Zigzag_Above = 0x035b, // U+035B Combining Zigzag Above + U_Combining_Double_Breve_Below = 0x035c, // U+035C Combining Double Breve Below + U_Combining_Double_Breve = 0x035d, // U+035D Combining Double Breve + U_Combining_Double_Macron = 0x035e, // U+035E Combining Double Macron + U_Combining_Double_Macron_Below = 0x035f, // U+035F Combining Double Macron Below + U_Combining_Double_Tilde = 0x0360, // U+0360 Combining Double Tilde + U_Combining_Double_Inverted_Breve = 0x0361, // U+0361 Combining Double Inverted Breve + U_Combining_Double_Rightwards_Arrow_Below = 0x0362, // U+0362 Combining Double Rightwards Arrow Below + U_Combining_Latin_Small_Letter_A = 0x0363, // U+0363 Combining Latin Small Letter A + U_Combining_Latin_Small_Letter_E = 0x0364, // U+0364 Combining Latin Small Letter E + U_Combining_Latin_Small_Letter_I = 0x0365, // U+0365 Combining Latin Small Letter I + U_Combining_Latin_Small_Letter_O = 0x0366, // U+0366 Combining Latin Small Letter O + U_Combining_Latin_Small_Letter_U = 0x0367, // U+0367 Combining Latin Small Letter U + U_Combining_Latin_Small_Letter_C = 0x0368, // U+0368 Combining Latin Small Letter C + U_Combining_Latin_Small_Letter_D = 0x0369, // U+0369 Combining Latin Small Letter D + U_Combining_Latin_Small_Letter_H = 0x036a, // U+036A Combining Latin Small Letter H + U_Combining_Latin_Small_Letter_M = 0x036b, // U+036B Combining Latin Small Letter M + U_Combining_Latin_Small_Letter_R = 0x036c, // U+036C Combining Latin Small Letter R + U_Combining_Latin_Small_Letter_T = 0x036d, // U+036D Combining Latin Small Letter T + U_Combining_Latin_Small_Letter_V = 0x036e, // U+036E Combining Latin Small Letter V + U_Combining_Latin_Small_Letter_X = 0x036f, // U+036F Combining Latin Small Letter X + + /** + * Unicode Character 'LINE SEPARATOR' (U+2028) + * http://www.fileformat.info/info/unicode/char/2028/index.htm + */ + LINE_SEPARATOR = 0x2028, + /** + * Unicode Character 'PARAGRAPH SEPARATOR' (U+2029) + * http://www.fileformat.info/info/unicode/char/2029/index.htm + */ + PARAGRAPH_SEPARATOR = 0x2029, + /** + * Unicode Character 'NEXT LINE' (U+0085) + * http://www.fileformat.info/info/unicode/char/0085/index.htm + */ + NEXT_LINE = 0x0085, + + // http://www.fileformat.info/info/unicode/category/Sk/list.htm + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + U_CIRCUMFLEX = 0x005e, // U+005E CIRCUMFLEX + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + U_GRAVE_ACCENT = 0x0060, // U+0060 GRAVE ACCENT + U_DIAERESIS = 0x00a8, // U+00A8 DIAERESIS + U_MACRON = 0x00af, // U+00AF MACRON + U_ACUTE_ACCENT = 0x00b4, // U+00B4 ACUTE ACCENT + U_CEDILLA = 0x00b8, // U+00B8 CEDILLA + U_MODIFIER_LETTER_LEFT_ARROWHEAD = 0x02c2, // U+02C2 MODIFIER LETTER LEFT ARROWHEAD + U_MODIFIER_LETTER_RIGHT_ARROWHEAD = 0x02c3, // U+02C3 MODIFIER LETTER RIGHT ARROWHEAD + U_MODIFIER_LETTER_UP_ARROWHEAD = 0x02c4, // U+02C4 MODIFIER LETTER UP ARROWHEAD + U_MODIFIER_LETTER_DOWN_ARROWHEAD = 0x02c5, // U+02C5 MODIFIER LETTER DOWN ARROWHEAD + U_MODIFIER_LETTER_CENTRED_RIGHT_HALF_RING = 0x02d2, // U+02D2 MODIFIER LETTER CENTRED RIGHT HALF RING + U_MODIFIER_LETTER_CENTRED_LEFT_HALF_RING = 0x02d3, // U+02D3 MODIFIER LETTER CENTRED LEFT HALF RING + U_MODIFIER_LETTER_UP_TACK = 0x02d4, // U+02D4 MODIFIER LETTER UP TACK + U_MODIFIER_LETTER_DOWN_TACK = 0x02d5, // U+02D5 MODIFIER LETTER DOWN TACK + U_MODIFIER_LETTER_PLUS_SIGN = 0x02d6, // U+02D6 MODIFIER LETTER PLUS SIGN + U_MODIFIER_LETTER_MINUS_SIGN = 0x02d7, // U+02D7 MODIFIER LETTER MINUS SIGN + U_BREVE = 0x02d8, // U+02D8 BREVE + U_DOT_ABOVE = 0x02d9, // U+02D9 DOT ABOVE + U_RING_ABOVE = 0x02da, // U+02DA RING ABOVE + U_OGONEK = 0x02db, // U+02DB OGONEK + U_SMALL_TILDE = 0x02dc, // U+02DC SMALL TILDE + U_DOUBLE_ACUTE_ACCENT = 0x02dd, // U+02DD DOUBLE ACUTE ACCENT + U_MODIFIER_LETTER_RHOTIC_HOOK = 0x02de, // U+02DE MODIFIER LETTER RHOTIC HOOK + U_MODIFIER_LETTER_CROSS_ACCENT = 0x02df, // U+02DF MODIFIER LETTER CROSS ACCENT + U_MODIFIER_LETTER_EXTRA_HIGH_TONE_BAR = 0x02e5, // U+02E5 MODIFIER LETTER EXTRA-HIGH TONE BAR + U_MODIFIER_LETTER_HIGH_TONE_BAR = 0x02e6, // U+02E6 MODIFIER LETTER HIGH TONE BAR + U_MODIFIER_LETTER_MID_TONE_BAR = 0x02e7, // U+02E7 MODIFIER LETTER MID TONE BAR + U_MODIFIER_LETTER_LOW_TONE_BAR = 0x02e8, // U+02E8 MODIFIER LETTER LOW TONE BAR + U_MODIFIER_LETTER_EXTRA_LOW_TONE_BAR = 0x02e9, // U+02E9 MODIFIER LETTER EXTRA-LOW TONE BAR + U_MODIFIER_LETTER_YIN_DEPARTING_TONE_MARK = 0x02ea, // U+02EA MODIFIER LETTER YIN DEPARTING TONE MARK + U_MODIFIER_LETTER_YANG_DEPARTING_TONE_MARK = 0x02eb, // U+02EB MODIFIER LETTER YANG DEPARTING TONE MARK + U_MODIFIER_LETTER_UNASPIRATED = 0x02ed, // U+02ED MODIFIER LETTER UNASPIRATED + U_MODIFIER_LETTER_LOW_DOWN_ARROWHEAD = 0x02ef, // U+02EF MODIFIER LETTER LOW DOWN ARROWHEAD + U_MODIFIER_LETTER_LOW_UP_ARROWHEAD = 0x02f0, // U+02F0 MODIFIER LETTER LOW UP ARROWHEAD + U_MODIFIER_LETTER_LOW_LEFT_ARROWHEAD = 0x02f1, // U+02F1 MODIFIER LETTER LOW LEFT ARROWHEAD + U_MODIFIER_LETTER_LOW_RIGHT_ARROWHEAD = 0x02f2, // U+02F2 MODIFIER LETTER LOW RIGHT ARROWHEAD + U_MODIFIER_LETTER_LOW_RING = 0x02f3, // U+02F3 MODIFIER LETTER LOW RING + U_MODIFIER_LETTER_MIDDLE_GRAVE_ACCENT = 0x02f4, // U+02F4 MODIFIER LETTER MIDDLE GRAVE ACCENT + U_MODIFIER_LETTER_MIDDLE_DOUBLE_GRAVE_ACCENT = 0x02f5, // U+02F5 MODIFIER LETTER MIDDLE DOUBLE GRAVE ACCENT + U_MODIFIER_LETTER_MIDDLE_DOUBLE_ACUTE_ACCENT = 0x02f6, // U+02F6 MODIFIER LETTER MIDDLE DOUBLE ACUTE ACCENT + U_MODIFIER_LETTER_LOW_TILDE = 0x02f7, // U+02F7 MODIFIER LETTER LOW TILDE + U_MODIFIER_LETTER_RAISED_COLON = 0x02f8, // U+02F8 MODIFIER LETTER RAISED COLON + U_MODIFIER_LETTER_BEGIN_HIGH_TONE = 0x02f9, // U+02F9 MODIFIER LETTER BEGIN HIGH TONE + U_MODIFIER_LETTER_END_HIGH_TONE = 0x02fa, // U+02FA MODIFIER LETTER END HIGH TONE + U_MODIFIER_LETTER_BEGIN_LOW_TONE = 0x02fb, // U+02FB MODIFIER LETTER BEGIN LOW TONE + U_MODIFIER_LETTER_END_LOW_TONE = 0x02fc, // U+02FC MODIFIER LETTER END LOW TONE + U_MODIFIER_LETTER_SHELF = 0x02fd, // U+02FD MODIFIER LETTER SHELF + U_MODIFIER_LETTER_OPEN_SHELF = 0x02fe, // U+02FE MODIFIER LETTER OPEN SHELF + U_MODIFIER_LETTER_LOW_LEFT_ARROW = 0x02ff, // U+02FF MODIFIER LETTER LOW LEFT ARROW + U_GREEK_LOWER_NUMERAL_SIGN = 0x0375, // U+0375 GREEK LOWER NUMERAL SIGN + U_GREEK_TONOS = 0x0384, // U+0384 GREEK TONOS + U_GREEK_DIALYTIKA_TONOS = 0x0385, // U+0385 GREEK DIALYTIKA TONOS + U_GREEK_KORONIS = 0x1fbd, // U+1FBD GREEK KORONIS + U_GREEK_PSILI = 0x1fbf, // U+1FBF GREEK PSILI + U_GREEK_PERISPOMENI = 0x1fc0, // U+1FC0 GREEK PERISPOMENI + U_GREEK_DIALYTIKA_AND_PERISPOMENI = 0x1fc1, // U+1FC1 GREEK DIALYTIKA AND PERISPOMENI + U_GREEK_PSILI_AND_VARIA = 0x1fcd, // U+1FCD GREEK PSILI AND VARIA + U_GREEK_PSILI_AND_OXIA = 0x1fce, // U+1FCE GREEK PSILI AND OXIA + U_GREEK_PSILI_AND_PERISPOMENI = 0x1fcf, // U+1FCF GREEK PSILI AND PERISPOMENI + U_GREEK_DASIA_AND_VARIA = 0x1fdd, // U+1FDD GREEK DASIA AND VARIA + U_GREEK_DASIA_AND_OXIA = 0x1fde, // U+1FDE GREEK DASIA AND OXIA + U_GREEK_DASIA_AND_PERISPOMENI = 0x1fdf, // U+1FDF GREEK DASIA AND PERISPOMENI + U_GREEK_DIALYTIKA_AND_VARIA = 0x1fed, // U+1FED GREEK DIALYTIKA AND VARIA + U_GREEK_DIALYTIKA_AND_OXIA = 0x1fee, // U+1FEE GREEK DIALYTIKA AND OXIA + U_GREEK_VARIA = 0x1fef, // U+1FEF GREEK VARIA + U_GREEK_OXIA = 0x1ffd, // U+1FFD GREEK OXIA + U_GREEK_DASIA = 0x1ffe, // U+1FFE GREEK DASIA + + U_IDEOGRAPHIC_FULL_STOP = 0x3002, // U+3002 IDEOGRAPHIC FULL STOP + U_LEFT_CORNER_BRACKET = 0x300c, // U+300C LEFT CORNER BRACKET + U_RIGHT_CORNER_BRACKET = 0x300d, // U+300D RIGHT CORNER BRACKET + U_LEFT_BLACK_LENTICULAR_BRACKET = 0x3010, // U+3010 LEFT BLACK LENTICULAR BRACKET + U_RIGHT_BLACK_LENTICULAR_BRACKET = 0x3011, // U+3011 RIGHT BLACK LENTICULAR BRACKET + + U_OVERLINE = 0x203e, // Unicode Character 'OVERLINE' + + /** + * UTF-8 BOM + * Unicode Character 'ZERO WIDTH NO-BREAK SPACE' (U+FEFF) + * http://www.fileformat.info/info/unicode/char/feff/index.htm + */ + UTF8_BOM = 65279, + + U_FULLWIDTH_SEMICOLON = 0xff1b, // U+FF1B FULLWIDTH SEMICOLON + U_FULLWIDTH_COMMA = 0xff0c, // U+FF0C FULLWIDTH COMMA +} diff --git a/src/client/common/utils/decorators.ts b/src/client/common/utils/decorators.ts index 2c9362d329f7..44a82ee13760 100644 --- a/src/client/common/utils/decorators.ts +++ b/src/client/common/utils/decorators.ts @@ -1,16 +1,10 @@ -// tslint:disable:no-any no-require-imports no-function-expression no-invalid-this - -import { ProgressLocation, ProgressOptions, window } from 'vscode'; import '../../common/extensions'; -import { IServiceContainer } from '../../ioc/types'; +import { traceError } from '../../logging'; import { isTestExecution } from '../constants'; -import { traceError, traceVerbose } from '../logger'; -import { Resource } from '../types'; import { createDeferred, Deferred } from './async'; -import { getCacheKeyFromFunctionArgs, getGlobalCacheStore, InMemoryInterpreterSpecificCache } from './cacheUtils'; -import { TraceInfo, tracing } from './misc'; +import { getCacheKeyFromFunctionArgs, getGlobalCacheStore } from './cacheUtils'; +import { StopWatch } from './stopWatch'; -// tslint:disable-next-line:no-require-imports no-var-requires const _debounce = require('lodash/debounce') as typeof import('lodash/debounce'); type VoidFunction = () => any; @@ -57,7 +51,6 @@ export function debounceAsync(wait?: number) { } export function makeDebounceDecorator(wait?: number) { - // tslint:disable-next-line:no-any no-function-expression return function (_target: any, _propertyName: string, descriptor: TypedPropertyDescriptor) { // We could also make use of _debounce() options. For instance, // the following causes the original method to be called @@ -77,14 +70,13 @@ export function makeDebounceDecorator(wait?: number) { return originalMethod.apply(this, arguments as any); }, wait, - options + options, ); (descriptor as any).value = debounced; }; } export function makeDebounceAsyncDecorator(wait?: number) { - // tslint:disable-next-line:no-any no-function-expression return function (_target: any, _propertyName: string, descriptor: TypedPropertyDescriptor) { type StateInformation = { started: boolean; @@ -127,25 +119,31 @@ export function makeDebounceAsyncDecorator(wait?: number) { }; } -type VSCodeType = typeof import('vscode'); - -export function clearCachedResourceSpecificIngterpreterData( - key: string, - resource: Resource, - serviceContainer: IServiceContainer, - vscode: VSCodeType = require('vscode') -) { - const cacheStore = new InMemoryInterpreterSpecificCache(key, 0, [resource], serviceContainer, vscode); - cacheStore.clear(); -} - type PromiseFunctionWithAnyArgs = (...any: any) => Promise; const cacheStoreForMethods = getGlobalCacheStore(); -export function cache(expiryDurationMs: number) { + +/** + * Extension start up time is considered the duration until extension is likely to keep running commands in background. + * It is observed on CI it can take upto 3 minutes, so this is an intelligent guess. + */ +const extensionStartUpTime = 200_000; +/** + * Tracks the time since the module was loaded. For caching purposes, we consider this time to approximately signify + * how long extension has been active. + */ +const moduleLoadWatch = new StopWatch(); +/** + * Caches function value until a specific duration. + * @param expiryDurationMs Duration to cache the result for. If set as '-1', the cache will never expire for the session. + * @param cachePromise If true, cache the promise instead of the promise result. + * @param expiryDurationAfterStartUpMs If specified, this is the duration to cache the result for after extension startup (until extension is likely to + * keep running commands in background) + */ +export function cache(expiryDurationMs: number, cachePromise = false, expiryDurationAfterStartUpMs?: number) { return function ( target: Object, propertyName: string, - descriptor: TypedPropertyDescriptor + descriptor: TypedPropertyDescriptor, ) { const originalMethod = descriptor.value!; const className = 'constructor' in target && target.constructor.name ? target.constructor.name : ''; @@ -154,18 +152,29 @@ export function cache(expiryDurationMs: number) { if (isTestExecution()) { return originalMethod.apply(this, args) as Promise; } - const key = getCacheKeyFromFunctionArgs(keyPrefix, args); + let key: string; + try { + key = getCacheKeyFromFunctionArgs(keyPrefix, args); + } catch (ex) { + traceError('Error while creating key for keyPrefix:', keyPrefix, ex); + return originalMethod.apply(this, args) as Promise; + } const cachedItem = cacheStoreForMethods.get(key); - if (cachedItem && cachedItem.expiry > Date.now()) { - traceVerbose(`Cached data exists ${key}`); + if (cachedItem && (cachedItem.expiry > Date.now() || expiryDurationMs === -1)) { return Promise.resolve(cachedItem.data); } + const expiryMs = + expiryDurationAfterStartUpMs && moduleLoadWatch.elapsedTime > extensionStartUpTime + ? expiryDurationAfterStartUpMs + : expiryDurationMs; const promise = originalMethod.apply(this, args) as Promise; - promise - .then((result) => - cacheStoreForMethods.set(key, { data: result, expiry: Date.now() + expiryDurationMs }) - ) - .ignoreErrors(); + if (cachePromise) { + cacheStoreForMethods.set(key, { data: promise, expiry: Date.now() + expiryMs }); + } else { + promise + .then((result) => cacheStoreForMethods.set(key, { data: result, expiry: Date.now() + expiryMs })) + .ignoreErrors(); + } return promise; }; }; @@ -179,14 +188,12 @@ export function cache(expiryDurationMs: number) { * @returns void */ 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 || propertyName}, method:${propertyName}):`; - // tslint:disable-next-line:no-any no-function-expression + descriptor.value = function (...args: any[]) { try { - // tslint:disable-next-line:no-invalid-this no-use-before-declare no-unsafe-any const result = originalMethod.apply(this, args); // If method being wrapped returns a promise then wait and swallow errors. @@ -207,56 +214,3 @@ export function swallowExceptions(scopeName?: string) { }; }; } - -// tslint:disable-next-line:no-any -type PromiseFunction = (...any: any[]) => Promise; - -export function displayProgress(title: string, location = ProgressLocation.Window) { - return function (_target: Object, _propertyName: string, descriptor: TypedPropertyDescriptor) { - const originalMethod = descriptor.value!; - // tslint:disable-next-line:no-any no-function-expression - descriptor.value = async function (...args: any[]) { - const progressOptions: ProgressOptions = { location, title }; - // tslint:disable-next-line:no-invalid-this - const promise = originalMethod.apply(this, args); - if (!isTestExecution()) { - window.withProgress(progressOptions, () => promise); - } - return promise; - }; - }; -} - -// Information about a function/method call. -export type CallInfo = { - kind: string; // "Class", etc. - name: string; - // tslint:disable-next-line:no-any - args: any[]; -}; - -// Return a decorator that traces the decorated function. -export function trace(log: (c: CallInfo, t: TraceInfo) => void) { - // tslint:disable-next-line:no-function-expression no-any - return function (_: Object, __: string, descriptor: TypedPropertyDescriptor) { - const originalMethod = descriptor.value; - // tslint:disable-next-line:no-function-expression no-any - descriptor.value = function (...args: any[]) { - const call = { - kind: 'Class', - name: _ && _.constructor ? _.constructor.name : '', - args - }; - // tslint:disable-next-line:no-this-assignment no-invalid-this - const scope = this; - return tracing( - // "log()" - (t) => log(call, t), - // "run()" - () => originalMethod.apply(scope, args) - ); - }; - - return descriptor; - }; -} diff --git a/src/client/common/utils/delayTrigger.ts b/src/client/common/utils/delayTrigger.ts new file mode 100644 index 000000000000..d110e005fc48 --- /dev/null +++ b/src/client/common/utils/delayTrigger.ts @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { clearTimeout, setTimeout } from 'timers'; +import { Disposable } from 'vscode'; +import { traceVerbose } from '../../logging'; + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export interface IDelayedTrigger { + trigger(...args: any[]): void; +} + +/** + * DelayedTrigger can be used to prevent some action being called too + * often within a given duration. This was added to support file watching + * for tests. Suppose we are watching for *.py files. If the user installs + * a new package or runs a formatter on the entire workspace. This could + * trigger too many discover test calls which are expensive. We could + * debounce, but the limitation with debounce is that it might run before + * the package has finished installing. With delayed trigger approach + * we delay running until @param ms amount of time has passed. + */ +export class DelayedTrigger implements IDelayedTrigger, Disposable { + private timerId: NodeJS.Timeout | undefined; + + private triggeredCounter = 0; + + private calledCounter = 0; + + /** + * Delay calling the function in callback for a predefined amount of time. + * @param callback : Callback that should be called after some time has passed. + * @param ms : Amount of time after the last trigger that the call to callback + * should be delayed. + * @param name : A name for the callback action. This will be used in logs. + */ + constructor( + private readonly callback: (...args: any[]) => void, + private readonly ms: number, + private readonly name: string, + ) {} + + public trigger(...args: unknown[]): void { + this.triggeredCounter += 1; + if (this.timerId) { + clearTimeout(this.timerId); + } + + this.timerId = setTimeout(() => { + this.calledCounter += 1; + traceVerbose( + `Delay Trigger[${this.name}]: triggered=${this.triggeredCounter}, called=${this.calledCounter}`, + ); + this.callback(...args); + }, this.ms); + } + + public dispose(): void { + if (this.timerId) { + clearTimeout(this.timerId); + } + } +} diff --git a/src/client/common/utils/enum.ts b/src/client/common/utils/enum.ts index 2d6a53fdb505..78104b48846f 100644 --- a/src/client/common/utils/enum.ts +++ b/src/client/common/utils/enum.ts @@ -3,13 +3,11 @@ 'use strict'; -// tslint:disable:no-any - export function getNamesAndValues(e: any): { name: string; value: T }[] { return getNames(e).map((n) => ({ name: n, value: e[n] })); } -export function getNames(e: any) { +function getNames(e: any) { return getObjValues(e).filter((v) => typeof v === 'string') as string[]; } diff --git a/src/client/common/utils/exec.ts b/src/client/common/utils/exec.ts new file mode 100644 index 000000000000..181934617eac --- /dev/null +++ b/src/client/common/utils/exec.ts @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as fsapi from 'fs'; +import * as path from 'path'; +import { getEnvironmentVariable, getOSType, OSType } from './platform'; + +/** + * Determine the env var to use for the executable search path. + */ +export function getSearchPathEnvVarNames(ostype = getOSType()): ('Path' | 'PATH')[] { + if (ostype === OSType.Windows) { + // On Windows both are supported now. + return ['Path', 'PATH']; + } + return ['PATH']; +} + +/** + * Get the OS executable lookup "path" from the appropriate env var. + */ +export function getSearchPathEntries(): string[] { + const envVars = getSearchPathEnvVarNames(); + for (const envVar of envVars) { + const value = getEnvironmentVariable(envVar); + if (value !== undefined) { + return parseSearchPathEntries(value); + } + } + // No env var was set. + return []; +} + +function parseSearchPathEntries(envVarValue: string): string[] { + return envVarValue + .split(path.delimiter) + .map((entry: string) => entry.trim()) + .filter((entry) => entry.length > 0); +} + +/** + * Determine if the given file is executable by the current user. + * + * If the file does not exist or has any other problem when accessed + * then `false` is returned. The caller is responsible to determine + * whether or not the file exists. + * + * If it could not be determined if the file is executable (e.g. on + * Windows) then `undefined` is returned. This allows the caller + * to decide what to do. + */ +export async function isValidAndExecutable(filename: string): Promise { + // There are three options when it comes to checking if a file + // is executable: `fs.stat()`, `fs.access()`, and + // `child_process.exec()`. `stat()` requires non-trivial logic + // to deal with user/group/everyone permissions. `exec()` requires + // that we make an attempt to actually execute the file, which is + // beyond the scope of this function (due to potential security + // risks). That leaves `access()`, which is what we use. + try { + // We do not need to check if the file exists. `fs.access()` + // takes care of that for us. + await fsapi.promises.access(filename, fsapi.constants.X_OK); + } catch (err) { + return false; + } + if (getOSType() === OSType.Windows) { + // On Windows a file is determined to be executable through + // its ACLs. However, the FS-related functionality provided + // by node does not check them (currently). This includes both + // `fs.stat()` and `fs.access()` (which we used above). One + // option is to use the "acl" NPM package (or something similar) + // to make the relevant checks. However, we want to avoid + // adding a dependency needlessly. Another option is to fall + // back to checking the filename's suffix (e.g. ".exe"). The + // problem there is that such a check is a bit *too* naive. + // Finally, we could still go the `exec()` route. We'd + // rather not given the concern identified above. Instead, + // it is good enough to return `undefined` and let the + // caller decide what to do about it. That is better + // than returning `true` when we aren't sure. + // + // Note that we still call `fs.access()` on Windows first, + // in case node makes it smarter later. + return undefined; + } + return true; +} diff --git a/src/client/common/utils/filesystem.ts b/src/client/common/utils/filesystem.ts new file mode 100644 index 000000000000..f2708e523bfb --- /dev/null +++ b/src/client/common/utils/filesystem.ts @@ -0,0 +1,143 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as fs from 'fs'; +import * as vscode from 'vscode'; +import { traceError } from '../../logging'; + +export import FileType = vscode.FileType; + +export type DirEntry = { + filename: string; + filetype: FileType; +}; + +interface IKnowsFileType { + isFile(): boolean; + isDirectory(): boolean; + isSymbolicLink(): boolean; +} + +// This helper function determines the file type of the given stats +// object. The type follows the convention of node's fs module, where +// a file has exactly one type. Symlinks are not resolved. +export function convertFileType(info: IKnowsFileType): FileType { + if (info.isFile()) { + return FileType.File; + } + if (info.isDirectory()) { + return FileType.Directory; + } + if (info.isSymbolicLink()) { + // The caller is responsible for combining this ("logical or") + // with File or Directory as necessary. + return FileType.SymbolicLink; + } + return FileType.Unknown; +} + +/** + * Identify the file type for the given file. + */ +export async function getFileType( + filename: string, + opts: { + ignoreErrors: boolean; + } = { ignoreErrors: true }, +): Promise { + let stat: fs.Stats; + try { + stat = await fs.promises.lstat(filename); + } catch (err) { + const error = err as NodeJS.ErrnoException; + if (error.code === 'ENOENT') { + return undefined; + } + if (opts.ignoreErrors) { + traceError(`lstat() failed for "${filename}" (${err})`); + return FileType.Unknown; + } + throw err; // re-throw + } + return convertFileType(stat); +} + +function normalizeFileTypes(filetypes: FileType | FileType[] | undefined): FileType[] | undefined { + if (filetypes === undefined) { + return undefined; + } + if (Array.isArray(filetypes)) { + if (filetypes.length === 0) { + return undefined; + } + return filetypes; + } + return [filetypes]; +} + +async function resolveFile( + file: string | DirEntry, + opts: { + ensure?: boolean; + onMissing?: FileType; + } = {}, +): Promise { + let filename: string; + if (typeof file !== 'string') { + if (!opts.ensure) { + if (opts.onMissing === undefined) { + return file; + } + // At least make sure it exists. + if ((await getFileType(file.filename)) !== undefined) { + return file; + } + } + filename = file.filename; + } else { + filename = file; + } + + const filetype = (await getFileType(filename)) || opts.onMissing; + if (filetype === undefined) { + return undefined; + } + return { filename, filetype }; +} + +type FileFilterFunc = (file: string | DirEntry) => Promise; + +export function getFileFilter( + opts: { + ignoreMissing?: boolean; + ignoreFileType?: FileType | FileType[]; + ensureEntry?: boolean; + } = { + ignoreMissing: true, + }, +): FileFilterFunc | undefined { + const ignoreFileType = normalizeFileTypes(opts.ignoreFileType); + + if (!opts.ignoreMissing && !ignoreFileType) { + // Do not filter. + return undefined; + } + + async function filterFile(file: string | DirEntry): Promise { + let entry = await resolveFile(file, { ensure: opts.ensureEntry }); + if (!entry) { + if (opts.ignoreMissing) { + return false; + } + const filename = typeof file === 'string' ? file : file.filename; + entry = { filename, filetype: FileType.Unknown }; + } + if (ignoreFileType) { + if (ignoreFileType.includes(entry!.filetype)) { + return false; + } + } + return true; + } + return filterFile; +} diff --git a/src/client/common/utils/icons.ts b/src/client/common/utils/icons.ts index 3d312818e058..71f71898ae9f 100644 --- a/src/client/common/utils/icons.ts +++ b/src/client/common/utils/icons.ts @@ -10,9 +10,9 @@ import { EXTENSION_ROOT_DIR } from '../../constants'; const darkIconsPath = path.join(EXTENSION_ROOT_DIR, 'resources', 'dark'); const lightIconsPath = path.join(EXTENSION_ROOT_DIR, 'resources', 'light'); -export function getIcon(fileName: string): { light: string | Uri; dark: string | Uri } { +export function getIcon(fileName: string): { light: Uri; dark: Uri } { return { - dark: path.join(darkIconsPath, fileName), - light: path.join(lightIconsPath, fileName) + dark: Uri.file(path.join(darkIconsPath, fileName)), + light: Uri.file(path.join(lightIconsPath, fileName)), }; } diff --git a/src/client/common/utils/iterable.ts b/src/client/common/utils/iterable.ts new file mode 100644 index 000000000000..5e04aaa430ea --- /dev/null +++ b/src/client/common/utils/iterable.ts @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/* eslint-disable @typescript-eslint/no-explicit-any */ +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace Iterable { + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + export function is(thing: any): thing is Iterable { + return thing && typeof thing === 'object' && typeof thing[Symbol.iterator] === 'function'; + } +} diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index 089cb95d873e..7b7560c74e05 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -3,1396 +3,552 @@ 'use strict'; -import * as path from 'path'; -import { EXTENSION_ROOT_DIR } from '../../constants'; -import { FileSystem } from '../platform/fileSystem'; +import { l10n } from 'vscode'; +import { Commands } from '../constants'; + +/* eslint-disable @typescript-eslint/no-namespace, no-shadow */ // External callers of localize use these tables to retrieve localized values. export namespace Diagnostics { - export const warnSourceMaps = localize( - 'diagnostics.warnSourceMaps', - 'Source map support is enabled in the Python Extension, this will adversely impact performance of the extension.' - ); - export const disableSourceMaps = localize('diagnostics.disableSourceMaps', 'Disable Source Map Support'); - export const warnBeforeEnablingSourceMaps = localize( - 'diagnostics.warnBeforeEnablingSourceMaps', - 'Enabling source map support in the Python Extension will adversely impact performance of the extension.' - ); - export const enableSourceMapsAndReloadVSC = localize( - 'diagnostics.enableSourceMapsAndReloadVSC', - 'Enable and reload Window.' - ); - export const lsNotSupported = localize( - 'diagnostics.lsNotSupported', - 'Your operating system does not meet the minimum requirements of the Python Language Server. Reverting to the alternative autocompletion provider, Jedi.' - ); - export const upgradeCodeRunner = localize( - 'diagnostics.upgradeCodeRunner', - 'Please update the Code Runner extension for it to be compatible with the Python extension.' + export const lsNotSupported = l10n.t( + 'Your operating system does not meet the minimum requirements of the Python Language Server. Reverting to the alternative autocompletion provider, Jedi.', ); - export const removedPythonPathFromSettings = localize( - 'diagnostics.removedPythonPathFromSettings', - 'We removed the "python.pythonPath" setting from your settings.json file as the setting is no longer used by the Python extension. You can get the path of your selected interpreter in the Python output channel. [Learn more](https://aka.ms/AA7jfor).' + export const invalidPythonPathInDebuggerSettings = l10n.t( + 'You need to select a Python interpreter before you start debugging.\n\nTip: click on "Select Interpreter" in the status bar.', ); - export const invalidPythonPathInDebuggerSettings = localize( - 'diagnostics.invalidPythonPathInDebuggerSettings', - 'You need to select a Python interpreter before you start debugging.\n\nTip: click on "Select Python Interpreter" in the status bar.' + export const invalidPythonPathInDebuggerLaunch = l10n.t('The Python path in your debug configuration is invalid.'); + export const invalidDebuggerTypeDiagnostic = l10n.t( + 'Your launch.json file needs to be updated to change the "pythonExperimental" debug configurations to use the "python" debugger type, otherwise Python debugging may not work. Would you like to automatically update your launch.json file now?', ); - export const invalidPythonPathInDebuggerLaunch = localize( - 'diagnostics.invalidPythonPathInDebuggerLaunch', - 'The Python path in your debug configuration is invalid.' + export const consoleTypeDiagnostic = l10n.t( + 'Your launch.json file needs to be updated to change the console type string from "none" to "internalConsole", otherwise Python debugging may not work. Would you like to automatically update your launch.json file now?', ); - export const invalidDebuggerTypeDiagnostic = localize( - 'diagnostics.invalidDebuggerTypeDiagnostic', - 'Your launch.json file needs to be updated to change the "pythonExperimental" debug configurations to use the "python" debugger type, otherwise Python debugging may not work. Would you like to automatically update your launch.json file now?' + export const justMyCodeDiagnostic = l10n.t( + 'Configuration "debugStdLib" in launch.json is no longer supported. It\'s recommended to replace it with "justMyCode", which is the exact opposite of using "debugStdLib". Would you like to automatically update your launch.json file to do that?', ); - export const consoleTypeDiagnostic = localize( - 'diagnostics.consoleTypeDiagnostic', - 'Your launch.json file needs to be updated to change the console type string from "none" to "internalConsole", otherwise Python debugging may not work. Would you like to automatically update your launch.json file now?' + export const yesUpdateLaunch = l10n.t('Yes, update launch.json'); + export const invalidTestSettings = l10n.t( + 'Your settings needs to be updated to change the setting "python.unitTest." to "python.testing.", otherwise testing Python code using the extension may not work. Would you like to automatically update your settings now?', ); - export const justMyCodeDiagnostic = localize( - 'diagnostics.justMyCodeDiagnostic', - 'Configuration "debugStdLib" in launch.json is no longer supported. It\'s recommended to replace it with "justMyCode", which is the exact opposite of using "debugStdLib". Would you like to automatically update your launch.json file to do that?' + export const updateSettings = l10n.t('Yes, update settings'); + export const pylanceDefaultMessage = l10n.t( + "The Python extension now includes Pylance to improve completions, code navigation, overall performance and much more! You can learn more about the update and learn how to change your language server [here](https://aka.ms/new-python-bundle).\n\nRead Pylance's license [here](https://marketplace.visualstudio.com/items/ms-python.vscode-pylance/license).", ); - export const yesUpdateLaunch = localize('diagnostics.yesUpdateLaunch', 'Yes, update launch.json'); - export const invalidTestSettings = localize( - 'diagnostics.invalidTestSettings', - 'Your settings needs to be updated to change the setting "python.unitTest." to "python.testing.", otherwise testing Python code using the extension may not work. Would you like to automatically update your settings now?' - ); - export const updateSettings = localize('diagnostics.updateSettings', 'Yes, update settings'); - export const checkIsort5UpgradeGuide = localize( - 'diagnostics.checkIsort5UpgradeGuide', - 'We found outdated configuration for sorting imports in this workspace. Check the [isort upgrade guide](https://aka.ms/AA9j5x4) to update your settings.' + export const invalidSmartSendMessage = l10n.t( + `Python is unable to parse the code provided. Please + turn off Smart Send if you wish to always run line by line or explicitly select code + to force run. See [logs](command:{0}) for more details`, + Commands.ViewOutput, ); } export namespace Common { - export const bannerLabelYes = localize('Common.bannerLabelYes', 'Yes'); - export const bannerLabelNo = localize('Common.bannerLabelNo', 'No'); - export const yesPlease = localize('Common.yesPlease', 'Yes, please'); - export const canceled = localize('Common.canceled', 'Canceled'); - export const cancel = localize('Common.cancel', 'Cancel'); - export const ok = localize('Common.ok', 'Ok'); - export const gotIt = localize('Common.gotIt', 'Got it!'); - export const install = localize('Common.install', 'Install'); - export const loadingExtension = localize('Common.loadingPythonExtension', 'Python extension loading...'); - export const openOutputPanel = localize('Common.openOutputPanel', 'Show output'); - export const noIWillDoItLater = localize('Common.noIWillDoItLater', 'No, I will do it later'); - export const notNow = localize('Common.notNow', 'Not now'); - export const doNotShowAgain = localize('Common.doNotShowAgain', 'Do not show again'); - export const reload = localize('Common.reload', 'Reload'); - export const moreInfo = localize('Common.moreInfo', 'More Info'); - export const learnMore = localize('Common.learnMore', 'Learn more'); - export const and = localize('Common.and', 'and'); - export const reportThisIssue = localize('Common.reportThisIssue', 'Report this issue'); + export const allow = l10n.t('Allow'); + export const seeInstructions = l10n.t('See Instructions'); + export const close = l10n.t('Close'); + export const bannerLabelYes = l10n.t('Yes'); + export const bannerLabelNo = l10n.t('No'); + export const canceled = l10n.t('Canceled'); + export const cancel = l10n.t('Cancel'); + export const ok = l10n.t('Ok'); + export const error = l10n.t('Error'); + export const gotIt = l10n.t('Got it!'); + export const install = l10n.t('Install'); + export const loadingExtension = l10n.t('Python extension loading...'); + export const openOutputPanel = l10n.t('Show output'); + export const noIWillDoItLater = l10n.t('No, I will do it later'); + export const notNow = l10n.t('Not now'); + export const doNotShowAgain = l10n.t("Don't show again"); + export const editSomething = l10n.t('Edit {0}'); + export const reload = l10n.t('Reload'); + export const moreInfo = l10n.t('More Info'); + export const learnMore = l10n.t('Learn more'); + export const and = l10n.t('and'); + export const reportThisIssue = l10n.t('Report this issue'); + export const recommended = l10n.t('Recommended'); + export const clearAll = l10n.t('Clear all'); + export const alwaysIgnore = l10n.t('Always Ignore'); + export const ignore = l10n.t('Ignore'); + export const selectPythonInterpreter = l10n.t('Select Python Interpreter'); + export const openLaunch = l10n.t('Open launch.json'); + export const useCommandPrompt = l10n.t('Use Command Prompt'); + export const download = l10n.t('Download'); + export const showLogs = l10n.t('Show logs'); + export const openFolder = l10n.t('Open Folder...'); } export namespace CommonSurvey { - export const remindMeLaterLabel = localize('CommonSurvey.remindMeLaterLabel', 'Remind me later'); - export const yesLabel = localize('CommonSurvey.yesLabel', 'Yes, take survey now'); - export const noLabel = localize('CommonSurvey.noLabel', 'No, thanks'); + export const remindMeLaterLabel = l10n.t('Remind me later'); + export const yesLabel = l10n.t('Yes, take survey now'); + export const noLabel = l10n.t('No, thanks'); } export namespace AttachProcess { - export const unsupportedOS = localize('AttachProcess.unsupportedOS', "Operating system '{0}' not supported."); - export const attachTitle = localize('AttachProcess.attachTitle', 'Attach to process'); - export const selectProcessPlaceholder = localize( - 'AttachProcess.selectProcessPlaceholder', - 'Select the process to attach to' - ); - export const noProcessSelected = localize('AttachProcess.noProcessSelected', 'No process selected'); - export const refreshList = localize('AttachProcess.refreshList', 'Refresh process list'); + export const attachTitle = l10n.t('Attach to process'); + export const selectProcessPlaceholder = l10n.t('Select the process to attach to'); + export const noProcessSelected = l10n.t('No process selected'); + export const refreshList = l10n.t('Refresh process list'); } -export namespace Pylance { - export const proposePylanceMessage = localize( - 'Pylance.proposePylanceMessage', - 'Try out a new faster, feature-rich language server for Python by Microsoft, Pylance! Install the extension now.' - ); - export const tryItNow = localize('Pylance.tryItNow', 'Try it now'); - export const remindMeLater = localize('Pylance.remindMeLater', 'Remind me later'); - - export const installPylanceMessage = localize( - 'Pylance.installPylanceMessage', - 'Pylance extension is not installed. Click Yes to open Pylance installation page.' - ); - export const pylanceNotInstalledMessage = localize( - 'Pylance.pylanceNotInstalledMessage', - 'Pylance extension is not installed.' - ); - export const pylanceInstalledReloadPromptMessage = localize( - 'Pylance.pylanceInstalledReloadPromptMessage', - 'Pylance extension is now installed. Reload window to activate?' - ); +export namespace Repl { + export const disableSmartSend = l10n.t('Disable Smart Send'); + export const launchNativeRepl = l10n.t('Launch VS Code Native REPL'); } +export namespace Pylance { + export const remindMeLater = l10n.t('Remind me later'); -export namespace LanguageService { - export const startingJedi = localize('LanguageService.startingJedi', 'Starting Jedi Python language engine.'); - export const startingMicrosoft = localize( - 'LanguageService.startingMicrosoft', - 'Starting Microsoft Python language server.' - ); - export const startingPylance = localize('LanguageService.startingPylance', 'Starting Pylance language server.'); - export const startingNone = localize( - 'LanguageService.startingNone', - 'Editor support is inactive since language server is set to None.' + export const pylanceNotInstalledMessage = l10n.t('Pylance extension is not installed.'); + export const pylanceInstalledReloadPromptMessage = l10n.t( + 'Pylance extension is now installed. Reload window to activate?', ); - export const reloadAfterLanguageServerChange = localize( - 'LanguageService.reloadAfterLanguageServerChange', - 'Please reload the window switching between language servers.' + export const pylanceRevertToJediPrompt = l10n.t( + 'The Pylance extension is not installed but the python.languageServer value is set to "Pylance". Would you like to install the Pylance extension to use Pylance, or revert back to Jedi?', ); + export const pylanceInstallPylance = l10n.t('Install Pylance'); + export const pylanceRevertToJedi = l10n.t('Revert to Jedi'); +} - export const lsFailedToStart = localize( - 'LanguageService.lsFailedToStart', - 'We encountered an issue starting the language server. Reverting to Jedi language engine. Check the Python output panel for details.' +export namespace TensorBoard { + export const enterRemoteUrl = l10n.t('Enter remote URL'); + export const enterRemoteUrlDetail = l10n.t( + 'Enter a URL pointing to a remote directory containing your TensorBoard log files', ); - export const lsFailedToDownload = localize( - 'LanguageService.lsFailedToDownload', - 'We encountered an issue downloading the language server. Reverting to Jedi language engine. Check the Python output panel for details.' + export const useCurrentWorkingDirectoryDetail = l10n.t( + 'TensorBoard will search for tfevent files in all subdirectories of the current working directory', ); - export const lsFailedToExtract = localize( - 'LanguageService.lsFailedToExtract', - 'We encountered an issue extracting the language server. Reverting to Jedi language engine. Check the Python output panel for details.' + export const useCurrentWorkingDirectory = l10n.t('Use current working directory'); + export const logDirectoryPrompt = l10n.t('Select a log directory to start TensorBoard with'); + export const progressMessage = l10n.t('Starting TensorBoard session...'); + export const nativeTensorBoardPrompt = l10n.t( + 'VS Code now has integrated TensorBoard support. Would you like to launch TensorBoard? (Tip: Launch TensorBoard anytime by opening the command palette and searching for "Launch TensorBoard".)', ); - export const downloadFailedOutputMessage = localize( - 'LanguageService.downloadFailedOutputMessage', - 'Language server download failed.' + export const selectAFolder = l10n.t('Select a folder'); + export const selectAFolderDetail = l10n.t('Select a log directory containing tfevent files'); + export const selectAnotherFolder = l10n.t('Select another folder'); + export const selectAnotherFolderDetail = l10n.t('Use the file explorer to select another folder'); + export const installPrompt = l10n.t( + 'The package TensorBoard is required to launch a TensorBoard session. Would you like to install it?', ); - export const extractionFailedOutputMessage = localize( - 'LanguageService.extractionFailedOutputMessage', - 'Language server extraction failed.' + export const installTensorBoardAndProfilerPluginPrompt = l10n.t( + 'TensorBoard >= 2.4.1 and the PyTorch Profiler TensorBoard plugin >= 0.2.0 are required. Would you like to install these packages?', ); - export const extractionCompletedOutputMessage = localize( - 'LanguageService.extractionCompletedOutputMessage', - 'Language server download complete.' + export const installProfilerPluginPrompt = l10n.t( + 'We recommend installing version >= 0.2.0 of the PyTorch Profiler TensorBoard plugin. Would you like to install the package?', ); - export const extractionDoneOutputMessage = localize('LanguageService.extractionDoneOutputMessage', 'done.'); - export const reloadVSCodeIfSeachPathHasChanged = localize( - 'LanguageService.reloadVSCodeIfSeachPathHasChanged', - 'Search paths have changed for this Python interpreter. Please reload the extension to ensure that the IntelliSense works correctly.' + export const upgradePrompt = l10n.t( + 'Integrated TensorBoard support is only available for TensorBoard >= 2.4.1. Would you like to upgrade your copy of TensorBoard?', ); -} - -export namespace Http { - export const downloadingFile = localize('downloading.file', 'Downloading {0}...'); - export const downloadingFileProgress = localize('downloading.file.progress', '{0}{1} of {2} KB ({3}%)'); -} -export namespace Experiments { - export const inGroup = localize('Experiments.inGroup', "User belongs to experiment group '{0}'"); -} -export namespace Interpreters { - export const loading = localize('Interpreters.LoadingInterpreters', 'Loading Python Interpreters'); - export const refreshing = localize('Interpreters.RefreshingInterpreters', 'Refreshing Python Interpreters'); - export const condaInheritEnvMessage = localize( - 'Interpreters.condaInheritEnvMessage', - 'We noticed you\'re using a conda environment. If you are experiencing issues with this environment in the integrated terminal, we recommend that you let the Python extension change "terminal.integrated.inheritEnv" to false in your user settings.' + export const missingSourceFile = l10n.t( + 'The Python extension could not locate the requested source file on disk. Please manually specify the file.', ); - export const unsafeInterpreterMessage = localize( - 'Interpreters.unsafeInterpreterMessage', - 'We found a Python environment in this workspace. Do you want to select it to start up the features in the Python extension? Only accept if you trust this environment.' + export const selectMissingSourceFile = l10n.t('Choose File'); + export const selectMissingSourceFileDescription = l10n.t( + "The source file's contents may not match the original contents in the trace.", ); - export const environmentPromptMessage = localize( - 'Interpreters.environmentPromptMessage', - 'We noticed a new virtual environment has been created. Do you want to select it for the workspace folder?' - ); - export const entireWorkspace = localize('Interpreters.entireWorkspace', 'Entire workspace'); - export const selectInterpreterTip = localize( - 'Interpreters.selectInterpreterTip', - 'Tip: you can change the Python interpreter used by the Python extension by clicking on the Python version in the status bar' - ); - export const pythonInterpreterPath = localize('Interpreters.pythonInterpreterPath', 'Python interpreter path: {0}'); } -export namespace InterpreterQuickPickList { - export const quickPickListPlaceholder = localize( - 'InterpreterQuickPickList.quickPickListPlaceholder', - 'Current: {0}' - ); - export const enterPath = { - detail: localize('InterpreterQuickPickList.enterPath.detail', 'Enter path or find an existing interpreter'), - label: localize('InterpreterQuickPickList.enterPath.label', 'Enter interpreter path...'), - placeholder: localize('InterpreterQuickPickList.enterPath.placeholder', 'Enter path to a Python interpreter.') +export namespace LanguageService { + export const virtualWorkspaceStatusItem = { + detail: l10n.t('Limited IntelliSense supported by Jedi and Pylance'), }; - export const browsePath = { - label: localize('InterpreterQuickPickList.browsePath.label', 'Find...'), - detail: localize( - 'InterpreterQuickPickList.browsePath.detail', - 'Browse your file system to find a Python interpreter.' - ), - openButtonLabel: localize('python.command.python.setInterpreter.title', 'Select Interpreter'), - title: localize('InterpreterQuickPickList.browsePath.title', 'Select Python interpreter') + export const statusItem = { + name: l10n.t('Python IntelliSense Status'), + text: l10n.t('Partial Mode'), + detail: l10n.t('Limited IntelliSense provided by Pylance'), }; -} -export namespace ExtensionChannels { - export const yesWeekly = localize('ExtensionChannels.yesWeekly', 'Yes, weekly'); - export const yesDaily = localize('ExtensionChannels.yesDaily', 'Yes, daily'); - export const promptMessage = localize( - 'ExtensionChannels.promptMessage', - 'We noticed you are using Visual Studio Code Insiders. Would you like to use the Insiders build of the Python extension?' - ); - export const reloadToUseInsidersMessage = localize( - 'ExtensionChannels.reloadToUseInsidersMessage', - 'Please reload Visual Studio Code to use the insiders build of the Python extension.' - ); - export const downloadCompletedOutputMessage = localize( - 'ExtensionChannels.downloadCompletedOutputMessage', - 'Insiders build download complete.' - ); - export const startingDownloadOutputMessage = localize( - 'ExtensionChannels.startingDownloadOutputMessage', - 'Starting download for Insiders build.' - ); - export const downloadingInsidersMessage = localize( - 'ExtensionChannels.downloadingInsidersMessage', - 'Downloading Insiders Extension... ' + export const startingPylance = l10n.t('Starting Pylance language server.'); + export const startingNone = l10n.t('Editor support is inactive since language server is set to None.'); + export const untrustedWorkspaceMessage = l10n.t( + 'Only Pylance is supported in untrusted workspaces, setting language server to None.', ); - export const installingInsidersMessage = localize( - 'ExtensionChannels.installingInsidersMessage', - 'Installing Insiders build of extension... ' - ); - export const installingStableMessage = localize( - 'ExtensionChannels.installingStableMessage', - 'Installing Stable build of extension... ' - ); - export const installationCompleteMessage = localize('ExtensionChannels.installationCompleteMessage', 'complete.'); -} -export namespace OutputChannelNames { - export const languageServer = localize('OutputChannelNames.languageServer', 'Python Language Server'); - export const python = localize('OutputChannelNames.python', 'Python'); - export const pythonTest = localize('OutputChannelNames.pythonTest', 'Python Test Log'); - export const jupyter = localize('OutputChannelNames.jupyter', 'Jupyter'); -} -export namespace Logging { - export const currentWorkingDirectory = localize('Logging.CurrentWorkingDirectory', 'cwd:'); -} - -export namespace Linters { - export const enableLinter = localize('Linter.enableLinter', 'Enable {0}'); - export const enablePylint = localize( - 'Linter.enablePylint', - 'You have a pylintrc file in your workspace. Do you want to enable pylint?' - ); - export const replaceWithSelectedLinter = localize( - 'Linter.replaceWithSelectedLinter', - "Multiple linters are enabled in settings. Replace with '{0}'?" + export const reloadAfterLanguageServerChange = l10n.t( + 'Reload the window after switching between language servers.', ); -} - -export namespace InteractiveShiftEnterBanner { - export const bannerMessage = localize( - 'InteractiveShiftEnterBanner.bannerMessage', - 'Would you like shift-enter to send code to the new Interactive Window experience?' - ); -} -export namespace DataScienceSurveyBanner { - export const bannerMessage = localize( - 'DataScienceSurveyBanner.bannerMessage', - 'Can you please take 2 minutes to tell us how the Python Data Science features are working for you?' + export const lsFailedToStart = l10n.t( + 'We encountered an issue starting the language server. Reverting to Jedi language engine. Check the Python output panel for details.', ); - export const bannerLabelYes = localize('DataScienceSurveyBanner.bannerLabelYes', 'Yes, take survey now'); - export const bannerLabelNo = localize('DataScienceSurveyBanner.bannerLabelNo', 'No, thanks'); -} -export namespace DataScienceRendererExtension { - export const installingExtension = localize( - 'DataScienceRendererExtension.installingExtension', - 'Installing Notebook Renderers extension... ' - ); - export const installationCompleteMessage = localize( - 'DataScienceRendererExtension.installationCompleteMessage', - 'complete.' + export const lsFailedToDownload = l10n.t( + 'We encountered an issue downloading the language server. Reverting to Jedi language engine. Check the Python output panel for details.', ); - export const startingDownloadOutputMessage = localize( - 'DataScienceRendererExtension.startingDownloadOutputMessage', - 'Starting download of Notebook Renderers extension.' + export const lsFailedToExtract = l10n.t( + 'We encountered an issue extracting the language server. Reverting to Jedi language engine. Check the Python output panel for details.', ); - export const downloadingMessage = localize( - 'DataScienceRendererExtension.downloadingMessage', - 'Downloading Notebook Renderers Extension... ' - ); - export const downloadCompletedOutputMessage = localize( - 'DataScienceRendererExtension.downloadCompletedOutputMessage', - 'Notebook Renderers extension download complete.' + export const downloadFailedOutputMessage = l10n.t('Language server download failed.'); + export const extractionFailedOutputMessage = l10n.t('Language server extraction failed.'); + export const extractionCompletedOutputMessage = l10n.t('Language server download complete.'); + export const extractionDoneOutputMessage = l10n.t('done.'); + export const reloadVSCodeIfSeachPathHasChanged = l10n.t( + 'Search paths have changed for this Python interpreter. Reload the extension to ensure that the IntelliSense works correctly.', ); } -export namespace DataScienceNotebookSurveyBanner { - export const bannerMessage = localize( - 'DataScienceNotebookSurveyBanner.bannerMessage', - 'Can you please take 2 minutes to tell us how the Preview Notebook Editor is working for you?' - ); -} - -export namespace Installer { - export const noCondaOrPipInstaller = localize( - 'Installer.noCondaOrPipInstaller', - 'There is no Conda or Pip installer available in the selected environment.' - ); - export const noPipInstaller = localize( - 'Installer.noPipInstaller', - 'There is no Pip installer available in the selected environment.' - ); - export const searchForHelp = localize('Installer.searchForHelp', 'Search for help'); -} - -export namespace ExtensionSurveyBanner { - export const bannerMessage = localize( - 'ExtensionSurveyBanner.bannerMessage', - 'Can you please take 2 minutes to tell us how the Python extension is working for you?' - ); - export const bannerLabelYes = localize('ExtensionSurveyBanner.bannerLabelYes', 'Yes, take survey now'); - export const bannerLabelNo = localize('ExtensionSurveyBanner.bannerLabelNo', 'No, thanks'); - export const maybeLater = localize('ExtensionSurveyBanner.maybeLater', 'Maybe later'); -} - -export namespace Products { - export const installingModule = localize('products.installingModule', 'Installing {0}'); -} - -export namespace DataScience { - export const unknownServerUri = localize( - 'DataScience.unknownServerUri', - 'Server URI cannot be used. Did you uninstall an extension that provided a Jupyter server connection?' - ); - export const uriProviderDescriptionFormat = localize( - 'DataScience.uriProviderDescriptionFormat', - '{0} (From {1} extension)' - ); - export const unknownPackage = localize('DataScience.unknownPackage', 'unknown'); - export const interactiveWindowTitle = localize('DataScience.interactiveWindowTitle', 'Python Interactive'); - export const interactiveWindowTitleFormat = localize( - 'DataScience.interactiveWindowTitleFormat', - 'Python Interactive - {0}' - ); - - export const interactiveWindowModeBannerTitle = localize( - 'DataScience.interactiveWindowModeBannerTitle', - 'Do you want to open a new Python Interactive window for this file? [More Information](command:workbench.action.openSettings?%5B%22python.dataScience.interactiveWindowMode%22%5D).' - ); - - export const interactiveWindowModeBannerSwitchYes = localize( - 'DataScience.interactiveWindowModeBannerSwitchYes', - 'Yes' - ); - export const interactiveWindowModeBannerSwitchAlways = localize( - 'DataScience.interactiveWindowModeBannerSwitchAlways', - 'Always' - ); - export const interactiveWindowModeBannerSwitchNo = localize( - 'DataScience.interactiveWindowModeBannerSwitchNo', - 'No' - ); - - export const dataExplorerTitle = localize('DataScience.dataExplorerTitle', 'Data Viewer'); - export const badWebPanelFormatString = localize( - 'DataScience.badWebPanelFormatString', - '

{0} is not a valid file name

' - ); - export const checkingIfImportIsSupported = localize( - 'DataScience.checkingIfImportIsSupported', - 'Checking if import is supported' - ); - export const installingMissingDependencies = localize( - 'DataScience.installingMissingDependencies', - 'Installing missing dependencies' - ); - export const performingExport = localize('DataScience.performingExport', 'Performing Export'); - export const convertingToPDF = localize('DataScience.convertingToPDF', 'Converting to PDF'); - export const exportNotebookToPython = localize( - 'DataScience.exportNotebookToPython', - 'Exporting Notebook to Python' - ); - export const sessionDisposed = localize( - 'DataScience.sessionDisposed', - 'Cannot execute code, session has been disposed.' - ); - export const passwordFailure = localize( - 'DataScience.passwordFailure', - 'Failed to connect to password protected server. Check that password is correct.' - ); - export const rawKernelProcessNotStarted = localize( - 'DataScience.rawKernelProcessNotStarted', - 'Raw kernel process was not able to start.' - ); - export const rawKernelProcessExitBeforeConnect = localize( - 'DataScience.rawKernelProcessExitBeforeConnect', - 'Raw kernel process exited before connecting.' - ); - export const unknownMimeTypeFormat = localize( - 'DataScience.unknownMimeTypeFormat', - 'Mime type {0} is not currently supported' - ); - export const exportDialogTitle = localize('DataScience.exportDialogTitle', 'Export to Jupyter Notebook'); - export const exportDialogFilter = localize('DataScience.exportDialogFilter', 'Jupyter Notebooks'); - export const exportDialogComplete = localize('DataScience.exportDialogComplete', 'Notebook written to {0}'); - export const exportDialogFailed = localize('DataScience.exportDialogFailed', 'Failed to export notebook. {0}'); - export const exportOpenQuestion = localize('DataScience.exportOpenQuestion', 'Open in browser'); - export const exportOpenQuestion1 = localize('DataScience.exportOpenQuestion1', 'Open in editor'); - export const runCellLensCommandTitle = localize('python.command.python.datascience.runcell.title', 'Run cell'); - export const importDialogTitle = localize('DataScience.importDialogTitle', 'Import Jupyter Notebook'); - export const importDialogFilter = localize('DataScience.importDialogFilter', 'Jupyter Notebooks'); - export const notebookCheckForImportTitle = localize( - 'DataScience.notebookCheckForImportTitle', - 'Do you want to import the Jupyter Notebook into Python code?' - ); - export const notebookCheckForImportYes = localize('DataScience.notebookCheckForImportYes', 'Import'); - export const notebookCheckForImportNo = localize('DataScience.notebookCheckForImportNo', 'Later'); - export const notebookCheckForImportDontAskAgain = localize( - 'DataScience.notebookCheckForImportDontAskAgain', - "Don't Ask Again" - ); - export const libraryNotInstalled = localize( - 'DataScience.libraryNotInstalled', - 'Data Science library {0} is not installed. Install?' - ); - export const couldNotInstallLibrary = localize( - 'DataScience.couldNotInstallLibrary', - 'Could not install {0}. If pip is not available, please use the package manager of your choice to manually install this library into your Python environment.' - ); - export const libraryRequiredToLaunchJupyterNotInstalled = localize( - 'DataScience.libraryRequiredToLaunchJupyterNotInstalled', - 'Data Science library {0} is not installed.' - ); - export const librariesRequiredToLaunchJupyterNotInstalled = localize( - 'DataScience.librariesRequiredToLaunchJupyterNotInstalled', - 'Data Science libraries {0} are not installed.' - ); - export const libraryRequiredToLaunchJupyterNotInstalledInterpreter = localize( - 'DataScience.libraryRequiredToLaunchJupyterNotInstalledInterpreter', - '{0} requires {1} to be installed.' - ); - export const libraryRequiredToLaunchJupyterKernelNotInstalledInterpreter = localize( - 'DataScience.libraryRequiredToLaunchJupyterKernelNotInstalledInterpreter', - '{0} requires {1} to be installed.' - ); - export const librariesRequiredToLaunchJupyterNotInstalledInterpreter = localize( - 'DataScience.librariesRequiredToLaunchJupyterNotInstalledInterpreter', - '{0} requires {1} to be installed.' - ); - export const selectJupyterInterpreter = localize( - 'DataScience.selectJupyterInterpreter', - 'Select an Interpreter to start Jupyter' - ); - export const jupyterInstall = localize('DataScience.jupyterInstall', 'Install'); - export const currentlySelectedJupyterInterpreterForPlaceholder = localize( - 'Datascience.currentlySelectedJupyterInterpreterForPlaceholder', - 'current: {0}' - ); - export const jupyterNotSupported = localize( - 'DataScience.jupyterNotSupported', - 'Jupyter cannot be started. Error attempting to locate jupyter: {0}' - ); - export const jupyterNotSupportedBecauseOfEnvironment = localize( - 'DataScience.jupyterNotSupportedBecauseOfEnvironment', - 'Activating {0} to run Jupyter failed with {1}' - ); - export const jupyterNbConvertNotSupported = localize( - 'DataScience.jupyterNbConvertNotSupported', - 'Jupyter nbconvert is not installed' - ); - export const jupyterLaunchTimedOut = localize( - 'DataScience.jupyterLaunchTimedOut', - 'The Jupyter notebook server failed to launch in time' - ); - export const jupyterLaunchNoURL = localize( - 'DataScience.jupyterLaunchNoURL', - 'Failed to find the URL of the launched Jupyter notebook server' - ); - export const jupyterSelfCertFail = localize( - 'DataScience.jupyterSelfCertFail', - 'The security certificate used by server {0} was not issued by a trusted certificate authority.\r\nThis may indicate an attempt to steal your information.\r\nDo you want to enable the Allow Unauthorized Remote Connection setting for this workspace to allow you to connect?' - ); - export const jupyterSelfCertEnable = localize('DataScience.jupyterSelfCertEnable', 'Yes, connect anyways'); - export const jupyterSelfCertClose = localize('DataScience.jupyterSelfCertClose', 'No, close the connection'); - export const pythonInteractiveHelpLink = localize( - 'DataScience.pythonInteractiveHelpLink', - 'See [https://aka.ms/pyaiinstall] for help on installing jupyter.' - ); - export const markdownHelpInstallingMissingDependencies = localize( - 'DataScience.markdownHelpInstallingMissingDependencies', - 'See [https://aka.ms/pyaiinstall](https://aka.ms/pyaiinstall) for help on installing Jupyter and related dependencies.' - ); - export const importingFormat = localize('DataScience.importingFormat', 'Importing {0}'); - export const startingJupyter = localize('DataScience.startingJupyter', 'Starting Jupyter server'); - export const connectingIPyKernel = localize('DataScience.connectingToIPyKernel', 'Connecting to IPython kernel'); - export const connectedToIPyKernel = localize('DataScience.connectedToIPyKernel', 'Connected.'); - export const connectingToJupyter = localize('DataScience.connectingToJupyter', 'Connecting to Jupyter server'); - export const exportingFormat = localize('DataScience.exportingFormat', 'Exporting {0}'); - export const runAllCellsLensCommandTitle = localize( - 'python.command.python.datascience.runallcells.title', - 'Run all cells' - ); - export const runAllCellsAboveLensCommandTitle = localize( - 'python.command.python.datascience.runallcellsabove.title', - 'Run above' - ); - export const runCellAndAllBelowLensCommandTitle = localize( - 'python.command.python.datascience.runcellandallbelow.title', - 'Run Below' - ); - export const importChangeDirectoryComment = localize( - 'DataScience.importChangeDirectoryComment', - '{0} Change working directory from the workspace root to the ipynb file location. Turn this addition off with the DataScience.changeDirOnImportExport setting' - ); - export const exportChangeDirectoryComment = localize( - 'DataScience.exportChangeDirectoryComment', - '# Change directory to VSCode workspace root so that relative path loads work correctly. Turn this addition off with the DataScience.changeDirOnImportExport setting' - ); - - export const restartKernelMessage = localize( - 'DataScience.restartKernelMessage', - 'Do you want to restart the Jupter kernel? All variables will be lost.' - ); - export const restartKernelMessageYes = localize('DataScience.restartKernelMessageYes', 'Restart'); - export const restartKernelMessageDontAskAgain = localize( - 'DataScience.restartKernelMessageDontAskAgain', - "Don't Ask Again" - ); - export const restartKernelMessageNo = localize('DataScience.restartKernelMessageNo', 'Cancel'); - export const restartingKernelStatus = localize('DataScience.restartingKernelStatus', 'Restarting IPython Kernel'); - export const restartingKernelFailed = localize( - 'DataScience.restartingKernelFailed', - 'Kernel restart failed. Jupyter server is hung. Please reload VS code.' - ); - export const interruptingKernelFailed = localize( - 'DataScience.interruptingKernelFailed', - 'Kernel interrupt failed. Jupyter server is hung. Please reload VS code.' - ); - export const sessionStartFailedWithKernel = localize( - 'DataScience.sessionStartFailedWithKernel', - "Failed to start a session for the Kernel '{0}'. \nView Jupyter [log](command:{1}) for further details." - ); - export const executingCode = localize('DataScience.executingCode', 'Executing Cell'); - export const collapseAll = localize('DataScience.collapseAll', 'Collapse all cell inputs'); - export const expandAll = localize('DataScience.expandAll', 'Expand all cell inputs'); - export const collapseSingle = localize('DataScience.collapseSingle', 'Collapse'); - export const expandSingle = localize('DataScience.expandSingle', 'Expand'); - export const exportKey = localize('DataScience.export', 'Export as Jupyter notebook'); - export const restartServer = localize('DataScience.restartServer', 'Restart IPython Kernel'); - export const undo = localize('DataScience.undo', 'Undo'); - export const redo = localize('DataScience.redo', 'Redo'); - export const save = localize('DataScience.save', 'Save file'); - export const clearAll = localize('DataScience.clearAll', 'Remove all cells'); - export const reloadRequired = localize( - 'DataScience.reloadRequired', - 'Please reload the window for new settings to take effect.' - ); - export const pythonVersionHeader = localize('DataScience.pythonVersionHeader', 'Python Version:'); - export const pythonRestartHeader = localize('DataScience.pythonRestartHeader', 'Restarted Kernel:'); - export const pythonNewHeader = localize('DataScience.pythonNewHeader', 'Started new kernel:'); - export const pythonConnectHeader = localize('DataScience.pythonConnectHeader', 'Connected to kernel:'); - - export const jupyterSelectURIPrompt = localize( - 'DataScience.jupyterSelectURIPrompt', - 'Enter the URI of the running Jupyter server' - ); - export const jupyterSelectURIQuickPickTitle = localize( - 'DataScience.jupyterSelectURIQuickPickTitle', - 'Pick how to connect to Jupyter' - ); - export const jupyterSelectURIQuickPickPlaceholder = localize( - 'DataScience.jupyterSelectURIQuickPickPlaceholder', - 'Choose an option' - ); - export const jupyterSelectURILocalLabel = localize('DataScience.jupyterSelectURILocalLabel', 'Default'); - export const jupyterSelectURILocalDetail = localize( - 'DataScience.jupyterSelectURILocalDetail', - 'VS Code will automatically start a server for you on the localhost' - ); - export const jupyterSelectURIMRUDetail = localize('DataScience.jupyterSelectURIMRUDetail', 'Last Connection: {0}'); - export const jupyterSelectURINewLabel = localize('DataScience.jupyterSelectURINewLabel', 'Existing'); - export const jupyterSelectURINewDetail = localize( - 'DataScience.jupyterSelectURINewDetail', - 'Specify the URI of an existing server' - ); - export const jupyterSelectURIInvalidURI = localize( - 'DataScience.jupyterSelectURIInvalidURI', - 'Invalid URI specified' - ); - export const jupyterSelectURIRunningDetailFormat = localize( - 'DataScience.jupyterSelectURIRunningDetailFormat', - 'Last activity {0}. {1} existing connections.' - ); - export const jupyterSelectURINotRunningDetail = localize( - 'DataScience.jupyterSelectURINotRunningDetail', - 'Cannot connect at this time. Status unknown.' - ); - export const jupyterSelectUserAndPasswordTitle = localize( - 'DataScience.jupyterSelectUserAndPasswordTitle', - 'Enter your user name and password to connect to Jupyter Hub' - ); - export const jupyterSelectUserPrompt = localize('DataScience.jupyterSelectUserPrompt', 'Enter your user name'); - export const jupyterSelectPasswordPrompt = localize( - 'DataScience.jupyterSelectPasswordPrompt', - 'Enter your password' - ); - export const jupyterNotebookFailure = localize( - 'DataScience.jupyterNotebookFailure', - 'Jupyter notebook failed to launch. \r\n{0}' - ); - export const jupyterNotebookConnectFailed = localize( - 'DataScience.jupyterNotebookConnectFailed', - 'Failed to connect to Jupyter notebook. \r\n{0}\r\n{1}' - ); - export const reloadAfterChangingJupyterServerConnection = localize( - 'DataScience.reloadAfterChangingJupyterServerConnection', - 'Please reload VS Code when changing the Jupyter Server connection.' - ); - export const jupyterNotebookRemoteConnectFailed = localize( - 'DataScience.jupyterNotebookRemoteConnectFailed', - 'Failed to connect to remote Jupyter notebook.\r\nCheck that the Jupyter Server URI setting has a valid running server specified.\r\n{0}\r\n{1}' - ); - export const jupyterNotebookRemoteConnectSelfCertsFailed = localize( - 'DataScience.jupyterNotebookRemoteConnectSelfCertsFailed', - 'Failed to connect to remote Jupyter notebook.\r\nSpecified server is using self signed certs. Enable Allow Unauthorized Remote Connection setting to connect anyways\r\n{0}\r\n{1}' - ); - export const rawConnectionDisplayName = localize( - 'DataScience.rawConnectionDisplayName', - 'Direct kernel connection' - ); - export const rawConnectionBrokenError = localize( - 'DataScience.rawConnectionBrokenError', - 'Direct kernel connection broken' - ); - export const jupyterServerCrashed = localize( - 'DataScience.jupyterServerCrashed', - 'Jupyter server crashed. Unable to connect. \r\nError code from jupyter: {0}' - ); - export const notebookVersionFormat = localize('DataScience.notebookVersionFormat', 'Jupyter Notebook Version: {0}'); - export const jupyterKernelSpecNotFound = localize( - 'DataScience.jupyterKernelSpecNotFound', - 'Cannot create a IPython kernel spec and none are available for use' - ); - export const jupyterKernelSpecModuleNotFound = localize( - 'DataScience.jupyterKernelSpecModuleNotFound', - "'Kernelspec' module not installed in the selected interpreter ({0}).\n Please re-install or update 'jupyter'." - ); - export const interruptKernel = localize('DataScience.interruptKernel', 'Interrupt IPython Kernel'); - export const clearAllOutput = localize('DataScience.clearAllOutput', 'Clear All Output'); - export const interruptKernelStatus = localize('DataScience.interruptKernelStatus', 'Interrupting IPython Kernel'); - export const exportCancel = localize('DataScience.exportCancel', 'Cancel'); - export const exportPythonQuickPickLabel = localize('DataScience.exportPythonQuickPickLabel', 'Python Script'); - export const exportHTMLQuickPickLabel = localize('DataScience.exportHTMLQuickPickLabel', 'HTML'); - export const exportPDFQuickPickLabel = localize('DataScience.exportPDFQuickPickLabel', 'PDF'); - export const restartKernelAfterInterruptMessage = localize( - 'DataScience.restartKernelAfterInterruptMessage', - 'Interrupting the kernel timed out. Do you want to restart the kernel instead? All variables will be lost.' - ); - export const pythonInterruptFailedHeader = localize( - 'DataScience.pythonInterruptFailedHeader', - 'Keyboard interrupt crashed the kernel. Kernel restarted.' - ); - export const sysInfoURILabel = localize('DataScience.sysInfoURILabel', 'Jupyter Server URI: '); - export const executingCodeFailure = localize('DataScience.executingCodeFailure', 'Executing code failed : {0}'); - export const inputWatermark = localize('DataScience.inputWatermark', 'Type code here and press shift-enter to run'); - export const liveShareConnectFailure = localize( - 'DataScience.liveShareConnectFailure', - 'Cannot connect to host jupyter session. URI not found.' - ); - export const liveShareCannotSpawnNotebooks = localize( - 'DataScience.liveShareCannotSpawnNotebooks', - 'Spawning jupyter notebooks is not supported over a live share connection' - ); - export const liveShareCannotImportNotebooks = localize( - 'DataScience.liveShareCannotImportNotebooks', - 'Importing notebooks is not currently supported over a live share connection' - ); - export const liveShareHostFormat = localize('DataScience.liveShareHostFormat', '{0} Jupyter Server'); - export const liveShareSyncFailure = localize( - 'DataScience.liveShareSyncFailure', - 'Synchronization failure during live share startup.' - ); - export const liveShareServiceFailure = localize( - 'DataScience.liveShareServiceFailure', - "Failure starting '{0}' service during live share connection." - ); - export const documentMismatch = localize( - 'DataScience.documentMismatch', - 'Cannot run cells, duplicate documents for {0} found.' - ); - export const jupyterGetVariablesBadResults = localize( - 'DataScience.jupyterGetVariablesBadResults', - 'Failed to fetch variable info from the Jupyter server.' - ); - export const dataExplorerInvalidVariableFormat = localize( - 'DataScience.dataExplorerInvalidVariableFormat', - "'{0}' is not an active variable." - ); - export const pythonInteractiveCreateFailed = localize( - 'DataScience.pythonInteractiveCreateFailed', - "Failure to create a 'Python Interactive' window. Try reinstalling the Python extension." - ); - export const jupyterGetVariablesExecutionError = localize( - 'DataScience.jupyterGetVariablesExecutionError', - 'Failure during variable extraction: \r\n{0}' - ); - export const loadingMessage = localize('DataScience.loadingMessage', 'loading ...'); - export const fetchingDataViewer = localize('DataScience.fetchingDataViewer', 'Fetching data ...'); - export const noRowsInDataViewer = localize('DataScience.noRowsInDataViewer', 'No rows match current filter'); - export const jupyterServer = localize('DataScience.jupyterServer', 'Jupyter Server'); - export const notebookIsTrusted = localize('DataScience.notebookIsTrusted', 'Trusted'); - export const notebookIsNotTrusted = localize('DataScience.notebookIsNotTrusted', 'Not Trusted'); - export const noKernel = localize('DataScience.noKernel', 'No Kernel'); - export const serverNotStarted = localize('DataScience.serverNotStarted', 'Not Started'); - export const selectKernel = localize('DataScience.selectKernel', 'Select a Kernel'); - export const selectDifferentKernel = localize('DataScience.selectDifferentKernel', 'Select a different Kernel'); - export const selectDifferentJupyterInterpreter = localize( - 'DataScience.selectDifferentJupyterInterpreter', - 'Select a different Interpreter' - ); - export const localJupyterServer = localize('DataScience.localJupyterServer', 'local'); - export const pandasTooOldForViewingFormat = localize( - 'DataScience.pandasTooOldForViewingFormat', - "Python package 'pandas' is version {0}. Version 0.20 or greater is required for viewing data." - ); - export const pandasRequiredForViewing = localize( - 'DataScience.pandasRequiredForViewing', - "Python package 'pandas' is required for viewing data." - ); - export const valuesColumn = localize('DataScience.valuesColumn', 'values'); - export const liveShareInvalid = localize( - 'DataScience.liveShareInvalid', - 'One or more guests in the session do not have the Python Extension installed. Live share session cannot continue.' - ); - export const tooManyColumnsMessage = localize( - 'DataScience.tooManyColumnsMessage', - 'Variables with over a 1000 columns may take a long time to display. Are you sure you wish to continue?' - ); - export const tooManyColumnsYes = localize('DataScience.tooManyColumnsYes', 'Yes'); - export const tooManyColumnsNo = localize('DataScience.tooManyColumnsNo', 'No'); - export const tooManyColumnsDontAskAgain = localize('DataScience.tooManyColumnsDontAskAgain', "Don't Ask Again"); - export const filterRowsButton = localize('DataScience.filterRowsButton', 'Filter Rows'); - export const filterRowsTooltip = localize( - 'DataScience.filterRowsTooltip', - 'Allows filtering multiple rows. Use =, >, or < signs to filter numeric values.' - ); - export const previewHeader = localize('DataScience.previewHeader', '--- Begin preview of {0} ---'); - export const previewFooter = localize('DataScience.previewFooter', '--- End preview of {0} ---'); - export const previewStatusMessage = localize('DataScience.previewStatusMessage', 'Generating preview of {0}'); - export const plotViewerTitle = localize('DataScience.plotViewerTitle', 'Plots'); - export const exportPlotTitle = localize('DataScience.exportPlotTitle', 'Save plot image'); - export const pdfFilter = localize('DataScience.pdfFilter', 'PDF'); - export const pngFilter = localize('DataScience.pngFilter', 'PNG'); - export const svgFilter = localize('DataScience.svgFilter', 'SVG'); - export const previousPlot = localize('DataScience.previousPlot', 'Previous'); - export const nextPlot = localize('DataScience.nextPlot', 'Next'); - export const panPlot = localize('DataScience.panPlot', 'Pan'); - export const zoomInPlot = localize('DataScience.zoomInPlot', 'Zoom in'); - export const zoomOutPlot = localize('DataScience.zoomOutPlot', 'Zoom out'); - export const exportPlot = localize('DataScience.exportPlot', 'Export to different formats'); - export const deletePlot = localize('DataScience.deletePlot', 'Remove'); - export const editSection = localize('DataScience.editSection', 'Input new cells here.'); - export const selectedImageListLabel = localize('DataScience.selectedImageListLabel', 'Selected Image'); - export const imageListLabel = localize('DataScience.imageListLabel', 'Image'); - export const exportImageFailed = localize('DataScience.exportImageFailed', 'Error exporting image: {0}'); - export const jupyterDataRateExceeded = localize( - 'DataScience.jupyterDataRateExceeded', - 'Cannot view variable because data rate exceeded. Please restart your server with a higher data rate limit. For example, --NotebookApp.iopub_data_rate_limit=10000000000.0' - ); - export const addCellBelowCommandTitle = localize('DataScience.addCellBelowCommandTitle', 'Add cell'); - export const debugCellCommandTitle = localize('DataScience.debugCellCommandTitle', 'Debug Cell'); - export const debugStepOverCommandTitle = localize('DataScience.debugStepOverCommandTitle', 'Step over'); - export const debugContinueCommandTitle = localize('DataScience.debugContinueCommandTitle', 'Continue'); - export const debugStopCommandTitle = localize('DataScience.debugStopCommandTitle', 'Stop'); - export const runCurrentCellAndAddBelow = localize( - 'DataScience.runCurrentCellAndAddBelow', - 'Run current and add cell below' - ); - export const variableExplorerDisabledDuringDebugging = localize( - 'DataScience.variableExplorerDisabledDuringDebugging', - "Please see the Debug Side Bar's VARIABLES section." - ); - export const jupyterDebuggerNotInstalledError = localize( - 'DataScience.jupyterDebuggerNotInstalledError', - 'Pip module {0} is required for debugging cells. You will need to install it to debug cells.' - ); - export const jupyterDebuggerOutputParseError = localize( - 'DataScience.jupyterDebuggerOutputParseError', - 'Unable to parse {0} output, please log an issue with https://github.com/microsoft/vscode-python' - ); - export const jupyterDebuggerPortNotAvailableError = localize( - 'DataScience.jupyterDebuggerPortNotAvailableError', - 'Port {0} cannot be opened for debugging. Please specify a different port in the remoteDebuggerPort setting.' - ); - export const jupyterDebuggerPortBlockedError = localize( - 'DataScience.jupyterDebuggerPortBlockedError', - 'Port {0} cannot be connected to for debugging. Please let port {0} through your firewall.' - ); - export const jupyterDebuggerPortNotAvailableSearchError = localize( - 'DataScience.jupyterDebuggerPortNotAvailableSearchError', - 'Ports in the range {0}-{1} cannot be found for debugging. Please specify a port in the remoteDebuggerPort setting.' - ); - export const jupyterDebuggerPortBlockedSearchError = localize( - 'DataScience.jupyterDebuggerPortBlockedSearchError', - 'A port cannot be connected to for debugging. Please let ports {0}-{1} through your firewall.' - ); - export const jupyterDebuggerInstallNew = localize( - 'DataScience.jupyterDebuggerInstallNew', - 'Pip module {0} is required for debugging cells. Install {0} and continue to debug cell?' - ); - export const jupyterDebuggerInstallNewRunByLine = localize( - 'DataScience.jupyterDebuggerInstallNewRunByLine', - 'Pip module {0} is required for running by line. Install {0} and continue to run by line?' - ); - export const jupyterDebuggerInstallUpdate = localize( - 'DataScience.jupyterDebuggerInstallUpdate', - 'The version of {0} installed does not support debugging cells. Update {0} to newest version and continue to debug cell?' - ); - export const jupyterDebuggerInstallUpdateRunByLine = localize( - 'DataScience.jupyterDebuggerInstallUpdateRunByLine', - 'The version of {0} installed does not support running by line. Update {0} to newest version and continue to run by line?' - ); - export const jupyterDebuggerInstallYes = localize('DataScience.jupyterDebuggerInstallYes', 'Yes'); - export const jupyterDebuggerInstallNo = localize('DataScience.jupyterDebuggerInstallNo', 'No'); - export const cellStopOnErrorFormatMessage = localize( - 'DataScience.cellStopOnErrorFormatMessage', - '{0} cells were canceled due to an error in the previous cell.' - ); - export const scrollToCellTitleFormatMessage = localize('DataScience.scrollToCellTitleFormatMessage', 'Go to [{0}]'); - export const instructionComments = localize( - 'DataScience.instructionComments', - '# To add a new cell, type "{0}"\n# To add a new markdown cell, type "{0} [markdown]"\n' - ); - export const invalidNotebookFileError = localize( - 'DataScience.invalidNotebookFileError', - 'Notebook is not in the correct format. Check the file for correct json.' - ); - export const invalidNotebookFileErrorFormat = localize( - 'DataScience.invalidNotebookFileError', - '{0} is not a valid notebook file. Check the file for correct json.' - ); - export const nativeEditorTitle = localize('DataScience.nativeEditorTitle', 'Notebook Editor'); - export const untitledNotebookFileName = localize('DataScience.untitledNotebookFileName', 'Untitled'); - export const dirtyNotebookMessage1 = localize( - 'DataScience.dirtyNotebookMessage1', - 'Do you want to save the changes you made to {0}?' - ); - export const dirtyNotebookMessage2 = localize( - 'DataScience.dirtyNotebookMessage2', - "Your changes will be lost if you don't save them." - ); - export const dirtyNotebookYes = localize('DataScience.dirtyNotebookYes', 'Save'); - export const dirtyNotebookNo = localize('DataScience.dirtyNotebookNo', "Don't Save"); - export const dirtyNotebookCancel = localize('DataScience.dirtyNotebookCancel', 'Cancel'); - export const dirtyNotebookDialogTitle = localize('DataScience.dirtyNotebookDialogTitle', 'Save'); - export const dirtyNotebookDialogFilter = localize('DataScience.dirtyNotebookDialogFilter', 'Jupyter Notebooks'); - export const remoteDebuggerNotSupported = localize( - 'DataScience.remoteDebuggerNotSupported', - 'Debugging while attached to a remote server is not currently supported.' - ); - export const notebookExportAs = localize('DataScience.notebookExportAs', 'Export As'); - export const exportAsPythonFileTitle = localize('DataScience.exportAsPythonFileTitle', 'Save As Python File'); - export const exportAsQuickPickPlaceholder = localize('DataScience.exportAsQuickPickPlaceholder', 'Export As...'); - export const openExportedFileMessage = localize( - 'DataScience.openExportedFileMessage', - 'Would you like to open the exported file?' - ); - export const openExportFileYes = localize('DataScience.openExportFileYes', 'Yes'); - export const openExportFileNo = localize('DataScience.openExportFileNo', 'No'); - export const exportFailedGeneralMessage = localize( - 'DataScience.exportFailedGeneralMessage', - `Please check the 'Python' [output](command:python.viewOutput) panel for further details.` - ); - export const exportToPDFDependencyMessage = localize( - 'DataScience.exportToPDFDependencyMessage', - 'If you have not installed xelatex (TeX) you will need to do so before you can export to PDF, for further instructions please look https://nbconvert.readthedocs.io/en/latest/install.html#installing-tex. \r\nTo avoid installing xelatex (TeX) you might want to try exporting to HTML and using your browsers "Print to PDF" feature.' - ); - export const failedExportMessage = localize('DataScience.failedExportMessage', 'Export failed.'); - export const runCell = localize('DataScience.runCell', 'Run cell'); - export const deleteCell = localize('DataScience.deleteCell', 'Delete cell'); - export const moveCellUp = localize('DataScience.moveCellUp', 'Move cell up'); - export const moveCellDown = localize('DataScience.moveCellDown', 'Move cell down'); - export const moveSelectedCellUp = localize('DataScience.moveSelectedCellUp', 'Move selected cell up'); - export const moveSelectedCellDown = localize('DataScience.deleteCell', 'Move selected cell down'); - export const insertBelow = localize('DataScience.insertBelow', 'Insert cell below'); - export const insertAbove = localize('DataScience.insertAbove', 'Insert cell above'); - export const addCell = localize('DataScience.addCell', 'Add cell'); - export const runAll = localize('DataScience.runAll', 'Insert cell'); - export const convertingToPythonFile = localize( - 'DataScience.convertingToPythonFile', - 'Converting ipynb to python file' - ); - export const noInterpreter = localize('DataScience.noInterpreter', 'No python selected'); - export const notebookNotFound = localize( - 'DataScience.notebookNotFound', - 'python -m jupyter notebook --version is not running' - ); - export const findJupyterCommandProgress = localize( - 'DataScience.findJupyterCommandProgress', - 'Active interpreter does not support {0}. Searching for the best available interpreter.' - ); - export const findJupyterCommandProgressCheckInterpreter = localize( - 'DataScience.findJupyterCommandProgressCheckInterpreter', - 'Checking {0}.' - ); - export const findJupyterCommandProgressSearchCurrentPath = localize( - 'DataScience.findJupyterCommandProgressSearchCurrentPath', - 'Searching current path.' - ); - export const gatherError = localize('DataScience.gatherError', 'Gather internal error'); - export const gatheredScriptDescription = localize( - 'DataScience.gatheredScriptDescription', - '# This file was generated by the Gather Extension.\n# It requires version 2020.7.94776 (or newer) of the Python Extension.\n#\n# The intent is that it contains only the code required to produce\n# the same results as the cell originally selected for gathering.\n# Please note that the Python analysis is quite conservative, so if\n# it is unsure whether a line of code is necessary for execution, it\n# will err on the side of including it.\n#\n# Please let us know if you are satisfied with what was gathered here:\n# https://aka.ms/gatherfeedback\n\n' - ); - export const gatheredNotebookDescriptionInMarkdown = localize( - 'DataScience.gatheredNotebookDescriptionInMarkdown', - '# Gathered Notebook\nGathered from ```{0}```\n\n| | |\n|---|---|\n|   |This notebook was generated by the Gather Extension. It requires version 2020.7.94776 (or newer) of the Python Extension, please update [here](https://command:python.datascience.latestExtension). The intent is that it contains only the code and cells required to produce the same results as the cell originally selected for gathering. Please note that the Python analysis is quite conservative, so if it is unsure whether a line of code is necessary for execution, it will err on the side of including it.|\n\n**Are you satisfied with the code that was gathered?**\n\n[Yes](https://command:python.datascience.gatherquality?yes) [No](https://command:python.datascience.gatherquality?no)' - ); - export const savePngTitle = localize('DataScience.savePngTitle', 'Save Image'); - export const fallbackToUseActiveInterpreterAsKernel = localize( - 'DataScience.fallbackToUseActiveInterpeterAsKernel', - "Couldn't find kernel '{0}' that the notebook was created with. Using the current interpreter." - ); - export const fallBackToRegisterAndUseActiveInterpeterAsKernel = localize( - 'DataScience.fallBackToRegisterAndUseActiveInterpeterAsKernel', - "Couldn't find kernel '{0}' that the notebook was created with. Registering a new kernel using the current interpreter." - ); - export const fallBackToPromptToUseActiveInterpreterOrSelectAKernel = localize( - 'DataScience.fallBackToPromptToUseActiveInterpreterOrSelectAKernel', - "Couldn't find kernel '{0}' that the notebook was created with." - ); - export const startingJupyterLogMessage = localize( - 'DataScience.startingJupyterLogMessage', - 'Starting Jupyter from {0}' - ); - export const jupyterStartTimedout = localize( - 'DataScience.jupyterStartTimedout', - "Starting Jupyter has timedout. Please check the 'Jupyter' output panel for further details." - ); - export const switchingKernelProgress = localize('DataScience.switchingKernelProgress', "Switching Kernel to '{0}'"); - export const waitingForJupyterSessionToBeIdle = localize( - 'DataScience.waitingForJupyterSessionToBeIdle', - 'Waiting for Jupyter Session to be idle' - ); - export const gettingListOfKernelsForLocalConnection = localize( - 'DataScience.gettingListOfKernelsForLocalConnection', - 'Fetching Kernels' - ); - export const gettingListOfKernelsForRemoteConnection = localize( - 'DataScience.gettingListOfKernelsForRemoteConnection', - 'Fetching Kernels' - ); - export const gettingListOfKernelSpecs = localize('DataScience.gettingListOfKernelSpecs', 'Fetching Kernel specs'); - export const startingJupyterNotebook = localize('DataScience.startingJupyterNotebook', 'Starting Jupyter Notebook'); - export const registeringKernel = localize('DataScience.registeringKernel', 'Registering Kernel'); - export const trimmedOutput = localize( - 'DataScience.trimmedOutput', - 'Output was trimmed for performance reasons.\nTo see the full output set the setting "python.dataScience.textOutputLimit" to 0.' +export namespace Interpreters { + export const requireJupyter = l10n.t( + 'Running in Interactive window requires Jupyter Extension. Would you like to install it? [Learn more](https://aka.ms/pythonJupyterSupport).', ); - export const jupyterCommandLineDefaultLabel = localize('DataScience.jupyterCommandLineDefaultLabel', 'Default'); - export const jupyterCommandLineDefaultDetail = localize( - 'DataScience.jupyterCommandLineDefaultDetail', - 'The Python extension will determine the appropriate command line for Jupyter' + export const installingPython = l10n.t('Installing Python into Environment...'); + export const discovering = l10n.t('Discovering Python Interpreters'); + export const refreshing = l10n.t('Refreshing Python Interpreters'); + export const envExtDiscoveryAttribution = l10n.t( + 'Environment discovery is managed by the Python Environments extension (ms-python.vscode-python-envs). Check the "Python Environments" output channel for environment-specific logs.', ); - export const jupyterCommandLineCustomLabel = localize('DataScience.jupyterCommandLineCustomLabel', 'Custom'); - export const jupyterCommandLineCustomDetail = localize( - 'DataScience.jupyterCommandLineCustomDetail', - 'Customize the command line passed to Jupyter on startup' + export const envExtDiscoveryFailed = l10n.t( + 'Environment discovery failed. Check the "Python Environments" output channel for details. The Python Environments extension (ms-python.vscode-python-envs) manages environment discovery.', ); - export const jupyterCommandLineReloadQuestion = localize( - 'DataScience.jupyterCommandLineReloadQuestion', - 'Please reload the window when changing the Jupyter command line.' + export const envExtDiscoverySlow = l10n.t( + 'Environment discovery is taking longer than expected. Check the "Python Environments" output channel for progress. The Python Environments extension (ms-python.vscode-python-envs) manages environment discovery.', ); - export const jupyterCommandLineReloadAnswer = localize('DataScience.jupyterCommandLineReloadAnswer', 'Reload'); - export const jupyterCommandLineQuickPickPlaceholder = localize( - 'DataScience.jupyterCommandLineQuickPickPlaceholder', - 'Choose an option' + export const envExtActivationFailed = l10n.t( + 'Failed to activate the Python Environments extension (ms-python.vscode-python-envs), which is required for environment discovery. Please ensure it is installed and enabled.', ); - export const jupyterCommandLineQuickPickTitle = localize( - 'DataScience.jupyterCommandLineQuickPickTitle', - 'Pick command line for Jupyter' + export const envExtDiscoveryNoEnvironments = l10n.t( + 'Environment discovery completed but no Python environments were found. Check the "Python Environments" output channel for details.', ); - export const jupyterCommandLinePrompt = localize( - 'DataScience.jupyterCommandLinePrompt', - 'Enter your custom command line for Jupyter' + export const envExtNoActiveEnvironment = l10n.t( + 'No Python environment is set for this resource. Check the "Python Environments" output channel for details, or select an interpreter.', ); - - export const connectingToJupyterUri = localize( - 'DataScience.connectingToJupyterUri', - 'Connecting to Jupyter server at {0}' + export const condaInheritEnvMessage = l10n.t( + 'We noticed you\'re using a conda environment. If you are experiencing issues with this environment in the integrated terminal, we recommend that you let the Python extension change "terminal.integrated.inheritEnv" to false in your user settings. [Learn more](https://aka.ms/AA66i8f).', ); - export const createdNewNotebook = localize('DataScience.createdNewNotebook', '{0}: Creating new notebook '); - - export const createdNewKernel = localize('DataScience.createdNewKernel', '{0}: Kernel started: {1}'); - export const kernelInvalid = localize( - 'DataScience.kernelInvalid', - 'Kernel {0} is not usable. Check the Jupyter output tab for more information.' + export const activatingTerminals = l10n.t('Reactivating terminals...'); + export const activateTerminalDescription = l10n.t('Activated environment for'); + export const terminalEnvVarCollectionPrompt = l10n.t( + '{0} environment was successfully activated, even though {1} indicator may not be present in the terminal prompt. [Learn more](https://aka.ms/vscodePythonTerminalActivation).', ); - - export const nativeDependencyFail = localize( - 'DataScience.nativeDependencyFail', - '{0}. We cannot launch a jupyter server for you because your OS is not supported. Select an already running server if you wish to continue.' + export const shellIntegrationEnvVarCollectionDescription = l10n.t( + 'Enables `python.terminal.shellIntegration.enabled` by modifying `PYTHONSTARTUP` and `PYTHON_BASIC_REPL`', ); - - export const selectNewServer = localize('DataScience.selectNewServer', 'Pick Running Server'); - export const jupyterSelectURIRemoteLabel = localize('DataScience.jupyterSelectURIRemoteLabel', 'Existing'); - export const jupyterSelectURIQuickPickTitleRemoteOnly = localize( - 'DataScience.jupyterSelectURIQuickPickTitleRemoteOnly', - 'Pick an already running jupyter server' + export const shellIntegrationDisabledEnvVarCollectionDescription = l10n.t( + 'Disables `python.terminal.shellIntegration.enabled` by unsetting `PYTHONSTARTUP` and `PYTHON_BASIC_REPL`', ); - export const jupyterSelectURIRemoteDetail = localize( - 'DataScience.jupyterSelectURIRemoteDetail', - 'Specify the URI of an existing server' + export const terminalDeactivateProgress = l10n.t('Editing {0}...'); + export const restartingTerminal = l10n.t('Restarting terminal and deactivating...'); + export const terminalDeactivatePrompt = l10n.t( + 'Deactivating virtual environments may not work by default. To make it work, edit your "{0}" and then restart your shell. [Learn more](https://aka.ms/AAmx2ft).', ); - - export const loadClassFailedWithNoInternet = localize( - 'DataScience.loadClassFailedWithNoInternet', - 'Error loading {0}:{1}. Internet connection required for loading 3rd party widgets.' + export const activatedCondaEnvLaunch = l10n.t( + 'We noticed VS Code was launched from an activated conda environment, would you like to select it?', ); - export const loadThirdPartyWidgetScriptsPostEnabled = localize( - 'DataScience.loadThirdPartyWidgetScriptsPostEnabled', - "Please restart the Kernel when changing the setting 'python.dataScience.widgetScriptSources'." + export const environmentPromptMessage = l10n.t( + 'We noticed a new environment has been created. Do you want to select it for the workspace folder?', ); - export const useCDNForWidgets = localize( - 'DataScience.useCDNForWidgets', - 'Widgets require us to download supporting files from a 3rd party website. Click [here](https://aka.ms/PVSCIPyWidgets) for more information.' + export const entireWorkspace = l10n.t('Select at workspace level'); + export const clearAtWorkspace = l10n.t('Clear at workspace level'); + export const selectInterpreterTip = l10n.t( + 'Tip: you can change the Python interpreter used by the Python extension by clicking on the Python version in the status bar', ); - export const enableCDNForWidgetsSetting = localize( - 'DataScience.enableCDNForWidgetsSetting', - "Widgets require us to download supporting files from a 3rd party website. Click
here to enable this or click here for more information. (Error loading {0}:{1})." + export const installPythonTerminalMessageLinux = l10n.t( + '💡 Try installing the Python package using your package manager. Alternatively you can also download it from https://www.python.org/downloads', ); - export const unhandledMessage = localize( - 'DataScience.unhandledMessage', - 'Unhandled kernel message from a widget: {0} : {1}' + export const installPythonTerminalMacMessage = l10n.t( + '💡 Brew does not seem to be available. You can download Python from https://www.python.org/downloads. Alternatively, you can install the Python package using some other available package manager.', ); + export const changePythonInterpreter = l10n.t('Change Python Interpreter'); + export const selectedPythonInterpreter = l10n.t('Selected Python Interpreter'); +} - export const widgetScriptNotFoundOnCDNWidgetMightNotWork = localize( - 'DataScience.widgetScriptNotFoundOnCDNWidgetMightNotWork', - "Unable to load a compatible version of the widget '{0}'. Expected behavior may be affected." - ); - export const qgridWidgetScriptVersionCompatibilityWarning = localize( - 'DataScience.qgridWidgetScriptVersionCompatibilityWarning', - "Unable to load a compatible version of the widget 'qgrid'. Consider downgrading to version 1.1.1." +export namespace InterpreterQuickPickList { + export const condaEnvWithoutPythonTooltip = l10n.t( + 'Python is not available in this environment, it will automatically be installed upon selecting it', ); + export const noPythonInstalled = l10n.t('Python is not installed'); + export const clickForInstructions = l10n.t('Click for instructions...'); + export const globalGroupName = l10n.t('Global'); + export const workspaceGroupName = l10n.t('Workspace'); + export const enterPath = { + label: l10n.t('Enter interpreter path...'), + placeholder: l10n.t('Enter path to a Python interpreter.'), + }; + export const defaultInterpreterPath = { + label: l10n.t('Use Python from `python.defaultInterpreterPath` setting'), + }; + export const browsePath = { + label: l10n.t('Find...'), + detail: l10n.t('Browse your file system to find a Python interpreter.'), + openButtonLabel: l10n.t('Select Interpreter'), + title: l10n.t('Select Python interpreter'), + }; + export const refreshInterpreterList = l10n.t('Refresh Interpreter list'); + export const refreshingInterpreterList = l10n.t('Refreshing Interpreter list...'); + export const create = { + label: l10n.t('Create Virtual Environment...'), + }; +} - export const kernelStarted = localize('DataScience.kernelStarted', 'Started kernel {0}.'); - export const runByLine = localize('DataScience.runByLine', 'Run by line (F10)'); - export const step = localize('DataScience.step', 'Run next line (F10)'); - export const stopRunByLine = localize('DataScience.stopRunByLine', 'Stop'); - export const rawKernelSessionFailed = localize( - 'DataScience.rawKernelSessionFailed', - 'Unable to start session for kernel {0}. Select another kernel to launch with.' - ); - export const rawKernelConnectingSession = localize( - 'DataScience.rawKernelConnectingSession', - 'Connecting to kernel.' - ); +export namespace OutputChannelNames { + export const languageServer = l10n.t('Python Language Server'); + export const python = l10n.t('Python'); +} - export const reloadCustomEditor = localize( - 'DataScience.reloadCustomEditor', - 'Please reload VS Code to use the custom editor API' - ); - export const reloadVSCodeNotebookEditor = localize( - 'DataScience.reloadVSCodeNotebookEditor', - 'Please reload VS Code to use the Notebook Editor' - ); - export const usingPreviewNotebookWithOtherNotebookWarning = localize( - 'DataScience.usingPreviewNotebookWithOtherNotebookWarning', - 'Opening the same file in the Preview Notebook Editor and stable Notebook Editor is not recommended. Doing so could result in data loss or corruption of notebooks.' - ); - export const launchNotebookTrustPrompt = localize( - 'DataScience.launchNotebookTrustPrompt', - 'A notebook could execute harmful code when opened. Some outputs have been hidden. Do you trust this notebook? [Learn more.](https://aka.ms/trusted-notebooks)' - ); - export const trustNotebook = localize('DataScience.launchNotebookTrustPrompt.yes', 'Trust'); - export const doNotTrustNotebook = localize('DataScience.launchNotebookTrustPrompt.no', 'Do not trust'); - export const trustAllNotebooks = localize( - 'DataScience.launchNotebookTrustPrompt.trustAllNotebooks', - 'Trust all notebooks' - ); - export const insecureSessionMessage = localize( - 'DataScience.insecureSessionMessage', - 'Connecting over HTTP without a token may be an insecure connection. Do you want to connect to a possibly insecure server?' - ); - export const insecureSessionDenied = localize( - 'DataScience.insecureSessionDenied', - 'Denied connection to insecure server.' - ); - export const previewNotebookOnlySupportedInVSCInsiders = localize( - 'DataScience.previewNotebookOnlySupportedInVSCInsiders', - 'The Preview Notebook Editor is supported only in the Insiders version of Visual Studio Code.' - ); - export const connected = localize('DataScience.connected', 'Connected'); - export const disconnected = localize('DataScience.disconnected', 'Disconnected'); - export const ipykernelNotInstalled = localize( - 'DataScience.ipykernelNotInstalled', - 'IPyKernel not installed into interpreter {0}' - ); +export namespace Linters { + export const selectLinter = l10n.t('Select Linter'); } -export namespace StartPage { - export const getStarted = localize('StartPage.getStarted', 'Python - Get Started'); - export const pythonExtensionTitle = localize('StartPage.pythonExtensionTitle', 'Python Extension'); - export const createJupyterNotebook = localize('StartPage.createJupyterNotebook', 'Create a Jupyter Notebook'); - export const notebookDescription = localize( - 'StartPage.notebookDescription', - '- Run "" in the Command Palette (
Shift + Command + P
)
- Explore our to learn about notebook features' - ); - export const createAPythonFile = localize('StartPage.createAPythonFile', 'Create a Python File'); - export const pythonFileDescription = localize( - 'StartPage.pythonFileDescription', - '- Create a with a .py extension' - ); - export const openInteractiveWindow = localize( - 'StartPage.openInteractiveWindow', - 'Use the Interactive Window to develop Python Scripts' - ); - export const interactiveWindowDesc = localize( - 'StartPage.interactiveWindowDesc', - '- You can create cells on a Python file by typing "#%%"
- Use "
Shift + Enter
" to run a cell, the output will be shown in the interactive window' +export namespace Installer { + export const noCondaOrPipInstaller = l10n.t( + 'There is no Conda or Pip installer available in the selected environment.', ); + export const noPipInstaller = l10n.t('There is no Pip installer available in the selected environment.'); + export const searchForHelp = l10n.t('Search for help'); +} - export const releaseNotes = localize( - 'StartPage.releaseNotes', - 'Take a look at our Release Notes to learn more about the latest features.' - ); - export const tutorialAndDoc = localize( - 'StartPage.tutorialAndDoc', - 'Explore more features in our Tutorials or check Documentation for tips and troubleshooting.' - ); - export const dontShowAgain = localize('StartPage.dontShowAgain', "Don't show this page again"); - export const helloWorld = localize('StartPage.helloWorld', 'Hello world'); - // When localizing sampleNotebook, the translated notebook must also be included in - // pythonFiles\* - export const sampleNotebook = localize('StartPage.sampleNotebook', 'Notebooks intro'); - export const openFolder = localize('StartPage.openFolder', 'Open a Folder or Workspace'); - export const folderDesc = localize( - 'StartPage.folderDesc', - '- Open a
- Open a ' +export namespace ExtensionSurveyBanner { + export const bannerMessage = l10n.t( + 'Can you take 2 minutes to tell us how the Python extension is working for you?', ); + export const bannerLabelYes = l10n.t('Yes, take survey now'); + export const bannerLabelNo = l10n.t('No, thanks'); + export const maybeLater = l10n.t('Maybe later'); } - export namespace DebugConfigStrings { export const selectConfiguration = { - title: localize('debug.selectConfigurationTitle'), - placeholder: localize('debug.selectConfigurationPlaceholder') + title: l10n.t('Select a debug configuration'), + placeholder: l10n.t('Debug Configuration'), }; export const launchJsonCompletions = { - label: localize('debug.launchJsonConfigurationsCompletionLabel'), - description: localize('debug.launchJsonConfigurationsCompletionDescription') + label: l10n.t('Python'), + description: l10n.t('Select a Python debug configuration'), }; export namespace file { export const snippet = { - name: localize('python.snippet.launch.standard.label') + name: l10n.t('Python: Current File'), }; - // tslint:disable-next-line:no-shadowed-variable + export const selectConfiguration = { - label: localize('debug.debugFileConfigurationLabel'), - description: localize('debug.debugFileConfigurationDescription') + label: l10n.t('Python File'), + description: l10n.t('Debug the currently active Python file'), }; } export namespace module { export const snippet = { - name: localize('python.snippet.launch.module.label'), - default: localize('python.snippet.launch.module.default') + name: l10n.t('Python: Module'), + default: l10n.t('enter-your-module-name'), }; - // tslint:disable-next-line:no-shadowed-variable + export const selectConfiguration = { - label: localize('debug.debugModuleConfigurationLabel'), - description: localize('debug.debugModuleConfigurationDescription') + label: l10n.t('Module'), + description: l10n.t("Debug a Python module by invoking it with '-m'"), }; export const enterModule = { - title: localize('debug.moduleEnterModuleTitle'), - prompt: localize('debug.moduleEnterModulePrompt'), - default: localize('debug.moduleEnterModuleDefault'), - invalid: localize('debug.moduleEnterModuleInvalidNameError') + title: l10n.t('Debug Module'), + prompt: l10n.t('Enter a Python module/package name'), + default: l10n.t('enter-your-module-name'), + invalid: l10n.t('Enter a valid module name'), }; } export namespace attach { export const snippet = { - name: localize('python.snippet.launch.attach.label') + name: l10n.t('Python: Remote Attach'), }; - // tslint:disable-next-line:no-shadowed-variable + export const selectConfiguration = { - label: localize('debug.remoteAttachConfigurationLabel'), - description: localize('debug.remoteAttachConfigurationDescription') + label: l10n.t('Remote Attach'), + description: l10n.t('Attach to a remote debug server'), }; export const enterRemoteHost = { - title: localize('debug.attachRemoteHostTitle'), - prompt: localize('debug.attachRemoteHostPrompt'), - invalid: localize('debug.attachRemoteHostValidationError') + title: l10n.t('Remote Debugging'), + prompt: l10n.t('Enter a valid host name or IP address'), + invalid: l10n.t('Enter a valid host name or IP address'), }; export const enterRemotePort = { - title: localize('debug.attachRemotePortTitle'), - prompt: localize('debug.attachRemotePortPrompt'), - invalid: localize('debug.attachRemotePortValidationError') + title: l10n.t('Remote Debugging'), + prompt: l10n.t('Enter the port number that the debug server is listening on'), + invalid: l10n.t('Enter a valid port number'), }; } export namespace attachPid { export const snippet = { - name: localize('python.snippet.launch.attachpid.label') + name: l10n.t('Python: Attach using Process Id'), }; - // tslint:disable-next-line:no-shadowed-variable + export const selectConfiguration = { - label: localize('debug.attachPidConfigurationLabel'), - description: localize('debug.attachPidConfigurationDescription') + label: l10n.t('Attach using Process ID'), + description: l10n.t('Attach to a local process'), }; } export namespace django { export const snippet = { - name: localize('python.snippet.launch.django.label') + name: l10n.t('Python: Django'), }; - // tslint:disable-next-line:no-shadowed-variable + export const selectConfiguration = { - label: localize('debug.debugDjangoConfigurationLabel'), - description: localize('debug.debugDjangoConfigurationDescription') + label: l10n.t('Django'), + description: l10n.t('Launch and debug a Django web application'), }; export const enterManagePyPath = { - title: localize('debug.djangoEnterManagePyPathTitle'), - prompt: localize('debug.djangoEnterManagePyPathPrompt'), - invalid: localize('debug.djangoEnterManagePyPathInvalidFilePathError') + title: l10n.t('Debug Django'), + prompt: l10n.t( + "Enter the path to manage.py ('${workspaceFolder}' points to the root of the current workspace folder)", + ), + invalid: l10n.t('Enter a valid Python file path'), + }; + } + export namespace fastapi { + export const snippet = { + name: l10n.t('Python: FastAPI'), + }; + + export const selectConfiguration = { + label: l10n.t('FastAPI'), + description: l10n.t('Launch and debug a FastAPI web application'), + }; + export const enterAppPathOrNamePath = { + title: l10n.t('Debug FastAPI'), + prompt: l10n.t("Enter the path to the application, e.g. 'main.py' or 'main'"), + invalid: l10n.t('Enter a valid name'), }; } export namespace flask { export const snippet = { - name: localize('python.snippet.launch.flask.label') + name: l10n.t('Python: Flask'), }; - // tslint:disable-next-line:no-shadowed-variable + export const selectConfiguration = { - label: localize('debug.debugFlaskConfigurationLabel'), - description: localize('debug.debugFlaskConfigurationDescription') + label: l10n.t('Flask'), + description: l10n.t('Launch and debug a Flask web application'), }; export const enterAppPathOrNamePath = { - title: localize('debug.flaskEnterAppPathOrNamePathTitle'), - prompt: localize('debug.flaskEnterAppPathOrNamePathPrompt'), - invalid: localize('debug.flaskEnterAppPathOrNamePathInvalidNameError') + title: l10n.t('Debug Flask'), + prompt: l10n.t('Python: Flask'), + invalid: l10n.t('Enter a valid name'), }; } export namespace pyramid { export const snippet = { - name: localize('python.snippet.launch.pyramid.label') + name: l10n.t('Python: Pyramid Application'), }; - // tslint:disable-next-line:no-shadowed-variable + export const selectConfiguration = { - label: localize('debug.debugPyramidConfigurationLabel'), - description: localize('debug.debugPyramidConfigurationDescription') + label: l10n.t('Pyramid'), + description: l10n.t('Launch and debug a Pyramid web application'), }; export const enterDevelopmentIniPath = { - title: localize('debug.pyramidEnterDevelopmentIniPathTitle'), - prompt: localize('debug.pyramidEnterDevelopmentIniPathPrompt'), - invalid: localize('debug.pyramidEnterDevelopmentIniPathInvalidFilePathError') + title: l10n.t('Debug Pyramid'), + invalid: l10n.t('Enter a valid file path'), }; } } export namespace Testing { - export const testErrorDiagnosticMessage = localize('Testing.testErrorDiagnosticMessage', 'Error'); - export const testFailDiagnosticMessage = localize('Testing.testFailDiagnosticMessage', 'Fail'); - export const testSkippedDiagnosticMessage = localize('Testing.testSkippedDiagnosticMessage', 'Skipped'); - export const configureTests = localize('Testing.configureTests', 'Configure Test Framework'); - export const disableTests = localize('Testing.disableTests', 'Disable Tests'); + export const configureTests = l10n.t('Configure Test Framework'); + export const cancelUnittestDiscovery = l10n.t('Canceled unittest test discovery'); + export const errorUnittestDiscovery = l10n.t('Unittest test discovery error'); + export const cancelPytestDiscovery = l10n.t('Canceled pytest test discovery'); + export const errorPytestDiscovery = l10n.t('pytest test discovery error'); + export const seePythonOutput = l10n.t('(see Output > Python)'); + export const cancelUnittestExecution = l10n.t('Canceled unittest test execution'); + export const errorUnittestExecution = l10n.t('Unittest test execution error'); + export const cancelPytestExecution = l10n.t('Canceled pytest test execution'); + export const errorPytestExecution = l10n.t('pytest test execution error'); + export const copilotSetupMessage = l10n.t('Confirm your Python testing framework to enable test discovery.'); } export namespace OutdatedDebugger { - export const outdatedDebuggerMessage = localize( - 'OutdatedDebugger.updateDebuggerMessage', - 'We noticed you are attaching to ptvsd (Python debugger), which was deprecated on May 1st, 2020. Please switch to [debugpy](https://aka.ms/migrateToDebugpy).' + export const outdatedDebuggerMessage = l10n.t( + 'We noticed you are attaching to ptvsd (Python debugger), which was deprecated on May 1st, 2020. Use [debugpy](https://aka.ms/migrateToDebugpy) instead.', ); } -// Skip using vscode-nls and instead just compute our strings based on key values. Key values -// can be loaded out of the nls..json files -let loadedCollection: Record | undefined; -let defaultCollection: Record | undefined; -let askedForCollection: Record = {}; -let loadedLocale: string; - -// This is exported only for testing purposes. -export function _resetCollections() { - loadedLocale = ''; - loadedCollection = undefined; - askedForCollection = {}; +export namespace Python27Support { + export const jediMessage = l10n.t( + 'IntelliSense with Jedi for Python 2.7 is no longer supported. [Learn more](https://aka.ms/python-27-support).', + ); } -// This is exported only for testing purposes. -export function _getAskedForCollection() { - return askedForCollection; +export namespace SwitchToDefaultLS { + export const bannerMessage = l10n.t( + "The Microsoft Python Language Server has reached end of life. Your language server has been set to the default for Python in VS Code, Pylance.\n\nIf you'd like to change your language server, you can learn about how to do so [here](https://devblogs.microsoft.com/python/python-in-visual-studio-code-may-2021-release/#configuring-your-language-server).\n\nRead Pylance's license [here](https://marketplace.visualstudio.com/items/ms-python.vscode-pylance/license).", + ); } -// Return the effective set of all localization strings, by key. -// -// This should not be used for direct lookup. -export function getCollectionJSON(): string { - // Load the current collection - if (!loadedCollection || parseLocale() !== loadedLocale) { - load(); - } +export namespace CreateEnv { + export const informEnvCreation = l10n.t('The following environment is selected:'); + export const statusTitle = l10n.t('Creating environment'); + export const statusStarting = l10n.t('Starting...'); - // Combine the default and loaded collections - return JSON.stringify({ ...defaultCollection, ...loadedCollection }); -} - -// tslint:disable-next-line:no-suspicious-comment -export function localize(key: string, defValue?: string) { - // Return a pointer to function so that we refetch it on each call. - return () => { - return getString(key, defValue); - }; -} + export const hasVirtualEnv = l10n.t('Workspace folder contains a virtual environment'); -function parseLocale(): string { - // Attempt to load from the vscode locale. If not there, use english - const vscodeConfigString = process.env.VSCODE_NLS_CONFIG; - return vscodeConfigString ? JSON.parse(vscodeConfigString).locale : 'en-us'; -} + export const noWorkspace = l10n.t('A workspace is required when creating an environment using venv.'); -function getString(key: string, defValue?: string) { - // Load the current collection - if (!loadedCollection || parseLocale() !== loadedLocale) { - load(); - } + export const pickWorkspacePlaceholder = l10n.t('Select a workspace to create environment'); - // The default collection (package.nls.json) is the fallback. - // Note that we are guaranteed the following (during shipping) - // 1. defaultCollection was initialized by the load() call above - // 2. defaultCollection has the key (see the "keys exist" test) - let collection = defaultCollection!; + export const providersQuickPickPlaceholder = l10n.t('Select an environment type'); - // Use the current locale if the key is defined there. - if (loadedCollection && loadedCollection.hasOwnProperty(key)) { - collection = loadedCollection; + export namespace Venv { + export const creating = l10n.t('Creating venv...'); + export const creatingMicrovenv = l10n.t('Creating microvenv...'); + export const created = l10n.t('Environment created...'); + export const existing = l10n.t('Using existing environment...'); + export const downloadingPip = l10n.t('Downloading pip...'); + export const installingPip = l10n.t('Installing pip...'); + export const upgradingPip = l10n.t('Upgrading pip...'); + export const installingPackages = l10n.t('Installing packages...'); + export const errorCreatingEnvironment = l10n.t('Error while creating virtual environment.'); + export const selectPythonPlaceHolder = l10n.t('Select a Python installation to create the virtual environment'); + export const providerDescription = l10n.t('Creates a `.venv` virtual environment in the current workspace'); + export const error = l10n.t('Creating virtual environment failed with error.'); + export const tomlExtrasQuickPickTitle = l10n.t('Select optional dependencies to install from pyproject.toml'); + export const requirementsQuickPickTitle = l10n.t('Select dependencies to install'); + export const recreate = l10n.t('Delete and Recreate'); + export const recreateDescription = l10n.t( + 'Delete existing ".venv" directory and create a new ".venv" environment', + ); + export const useExisting = l10n.t('Use Existing'); + export const useExistingDescription = l10n.t('Use existing ".venv" environment with no changes to it'); + export const existingVenvQuickPickPlaceholder = l10n.t( + 'Choose an option to handle the existing ".venv" environment', + ); + export const deletingEnvironmentProgress = l10n.t('Deleting existing ".venv" environment...'); + export const errorDeletingEnvironment = l10n.t('Error while deleting existing ".venv" environment.'); + export const openRequirementsFile = l10n.t('Open requirements file'); } - let result = collection[key]; - if (!result && defValue) { - // This can happen during development if you haven't fixed up the nls file yet or - // if for some reason somebody broke the functional test. - result = defValue; - } - askedForCollection[key] = result; - - return result; -} - -function load() { - const fs = new FileSystem(); - // Figure out our current locale. - loadedLocale = parseLocale(); - - // Find the nls file that matches (if there is one) - const nlsFile = path.join(EXTENSION_ROOT_DIR, `package.nls.${loadedLocale}.json`); - if (fs.fileExistsSync(nlsFile)) { - const contents = fs.readFileSync(nlsFile); - loadedCollection = JSON.parse(contents); - } else { - // If there isn't one, at least remember that we looked so we don't try to load a second time - loadedCollection = {}; + export namespace Conda { + export const condaMissing = l10n.t('Install `conda` to create conda environments.'); + export const created = l10n.t('Environment created...'); + export const installingPackages = l10n.t('Installing packages...'); + export const errorCreatingEnvironment = l10n.t('Error while creating conda environment.'); + export const selectPythonQuickPickPlaceholder = l10n.t( + 'Select the version of Python to install in the environment', + ); + export const creating = l10n.t('Creating conda environment...'); + export const providerDescription = l10n.t('Creates a `.conda` Conda environment in the current workspace'); + + export const recreate = l10n.t('Delete and Recreate'); + export const recreateDescription = l10n.t('Delete existing ".conda" environment and create a new one'); + export const useExisting = l10n.t('Use Existing'); + export const useExistingDescription = l10n.t('Use existing ".conda" environment with no changes to it'); + export const existingCondaQuickPickPlaceholder = l10n.t( + 'Choose an option to handle the existing ".conda" environment', + ); + export const deletingEnvironmentProgress = l10n.t('Deleting existing ".conda" environment...'); + export const errorDeletingEnvironment = l10n.t('Error while deleting existing ".conda" environment.'); } - // Get the default collection if necessary. Strings may be in the default or the locale json - if (!defaultCollection) { - const defaultNlsFile = path.join(EXTENSION_ROOT_DIR, 'package.nls.json'); - if (fs.fileExistsSync(defaultNlsFile)) { - const contents = fs.readFileSync(defaultNlsFile); - defaultCollection = JSON.parse(contents); - } else { - defaultCollection = {}; - } + export namespace Trigger { + export const workspaceTriggerMessage = l10n.t( + 'A virtual environment is not currently selected for your Python interpreter. Would you like to create a virtual environment?', + ); + export const createEnvironment = l10n.t('Create'); + + export const globalPipInstallTriggerMessage = l10n.t( + 'You may have installed Python packages into your global environment, which can cause conflicts between package versions. Would you like to create a virtual environment with these packages to isolate your dependencies?', + ); } } -// Default to loading the current locale -load(); +export namespace PythonLocator { + export const startupFailedNotification = l10n.t( + 'Python Locator failed to start. Python environment discovery may not work correctly.', + ); + export const windowsRuntimeMissing = l10n.t( + 'Missing Windows runtime dependencies detected. The Python Locator requires the Microsoft Visual C++ Redistributable. This is often missing on clean Windows installations.', + ); + export const windowsStartupFailed = l10n.t( + 'Python Locator failed to start on Windows. This might be due to missing system dependencies such as the Microsoft Visual C++ Redistributable.', + ); +} diff --git a/src/client/common/utils/logging.ts b/src/client/common/utils/logging.ts deleted file mode 100644 index c9c2f756c094..000000000000 --- a/src/client/common/utils/logging.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -export function formatErrorForLogging(error: Error | string): string { - let message: string = ''; - if (typeof error === 'string') { - message = error; - } else { - if (error.message) { - message = `Error Message: ${error.message}`; - } - if (error.name && error.message.indexOf(error.name) === -1) { - message += `, (${error.name})`; - } - // tslint:disable-next-line:no-any - const innerException = (error as any).innerException; - if (innerException && (innerException.message || innerException.name)) { - if (innerException.message) { - message += `, Inner Error Message: ${innerException.message}`; - } - if (innerException.name && innerException.message.indexOf(innerException.name) === -1) { - message += `, (${innerException.name})`; - } - } - } - return message; -} diff --git a/src/client/common/utils/misc.ts b/src/client/common/utils/misc.ts index 0a1186b6da0a..a461d25d9d30 100644 --- a/src/client/common/utils/misc.ts +++ b/src/client/common/utils/misc.ts @@ -2,52 +2,20 @@ // Licensed under the MIT License. 'use strict'; import type { TextDocument, Uri } from 'vscode'; -import { NotebookCellScheme } from '../constants'; +import { InteractiveInputScheme, NotebookCellScheme } from '../constants'; import { InterpreterUri } from '../installer/types'; -import { IAsyncDisposable, IDisposable, Resource } from '../types'; -import { isPromise } from './async'; -import { StopWatch } from './stopWatch'; +import { isParentPath } from '../platform/fs-paths'; +import { Resource } from '../types'; -// tslint:disable-next-line:no-empty export function noop() {} -/** - * Execute a block of code ignoring any exceptions. - */ -export function swallowExceptions(cb: Function) { - try { - cb(); - } catch { - // Ignore errors. - } -} - -export function using(disposable: T, func: (obj: T) => void) { - try { - func(disposable); - } finally { - disposable.dispose(); - } -} - -export async function usingAsync( - disposable: T, - func: (obj: T) => Promise -): Promise { - try { - return await func(disposable); - } finally { - await disposable.dispose(); - } -} - /** * Like `Readonly<>`, but recursive. * * See https://github.com/Microsoft/TypeScript/pull/21316. */ -// tslint:disable-next-line:no-any -export type DeepReadonly = T extends any[] ? IDeepReadonlyArray : DeepReadonlyNonArray; + +type DeepReadonly = T extends any[] ? IDeepReadonlyArray : DeepReadonlyNonArray; type DeepReadonlyNonArray = T extends object ? DeepReadonlyObject : T; interface IDeepReadonlyArray extends ReadonlyArray> {} type DeepReadonlyObject = { @@ -55,54 +23,10 @@ type DeepReadonlyObject = { }; type NonFunctionPropertyNames = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T]; -// Information about a traced function/method call. -export type TraceInfo = { - elapsed: number; // milliseconds - // Either returnValue or err will be set. - // tslint:disable-next-line:no-any - returnValue?: any; - err?: Error; -}; - -// Call run(), call log() with the trace info, and return the result. -export function tracing(log: (t: TraceInfo) => void, run: () => T): T { - const timer = new StopWatch(); - try { - // tslint:disable-next-line:no-invalid-this no-use-before-declare no-unsafe-any - const result = run(); - - // If method being wrapped returns a promise then wait for it. - if (isPromise(result)) { - // tslint:disable-next-line:prefer-type-cast - (result as Promise) - .then((data) => { - log({ elapsed: timer.elapsedTime, returnValue: data }); - return data; - }) - .catch((ex) => { - log({ elapsed: timer.elapsedTime, err: ex }); - // tslint:disable-next-line:no-suspicious-comment - // TODO(GH-11645) Re-throw the error like we do - // in the non-Promise case. - }); - } else { - log({ elapsed: timer.elapsedTime, returnValue: result }); - } - return result; - } catch (ex) { - log({ elapsed: timer.elapsedTime, err: ex }); - throw ex; - } -} - /** * Checking whether something is a Resource (Uri/undefined). * Using `instanceof Uri` doesn't always work as the object is not an instance of Uri (at least not in tests). * That's why VSC too has a helper method `URI.isUri` (though not public). - * - * @export - * @param {InterpreterUri} [resource] - * @returns {resource is Resource} */ export function isResource(resource?: InterpreterUri): resource is Resource { if (!resource) { @@ -116,13 +40,9 @@ export function isResource(resource?: InterpreterUri): resource is Resource { * Checking whether something is a Uri. * Using `instanceof Uri` doesn't always work as the object is not an instance of Uri (at least not in tests). * That's why VSC too has a helper method `URI.isUri` (though not public). - * - * @export - * @param {InterpreterUri} [resource] - * @returns {resource is Uri} */ -// tslint:disable-next-line: no-any -export function isUri(resource?: Uri | any): resource is Uri { + +function isUri(resource?: Uri | any): resource is Uri { if (!resource) { return false; } @@ -133,42 +53,38 @@ export function isUri(resource?: Uri | any): resource is Uri { /** * Create a filter func that determine if the given URI and candidate match. * - * The scheme must match, as well as path. + * Only compares path. * * @param checkParent - if `true`, match if the candidate is rooted under `uri` + * or if the candidate matches `uri` exactly. * @param checkChild - if `true`, match if `uri` is rooted under the candidate - * @param checkExact - if `true`, match if the candidate matches `uri` exactly + * or if the candidate matches `uri` exactly. */ export function getURIFilter( uri: Uri, opts: { checkParent?: boolean; checkChild?: boolean; - checkExact?: boolean; - } = { checkExact: true } + } = { checkParent: true }, ): (u: Uri) => boolean { let uriPath = uri.path; - while (uri.path.endsWith('/')) { + while (uriPath.endsWith('/')) { uriPath = uriPath.slice(0, -1); } const uriRoot = `${uriPath}/`; function filter(candidate: Uri): boolean { - if (candidate.scheme !== uri.scheme) { - return false; - } + // Do not compare schemes as it is sometimes not available, in + // which case file is assumed as scheme. let candidatePath = candidate.path; - while (candidate.path.endsWith('/')) { + while (candidatePath.endsWith('/')) { candidatePath = candidatePath.slice(0, -1); } - if (opts.checkExact && candidatePath === uriPath) { - return true; - } - if (opts.checkParent && candidatePath.startsWith(uriRoot)) { + if (opts.checkParent && isParentPath(candidatePath, uriRoot)) { return true; } if (opts.checkChild) { - const candidateRoot = `{candidatePath}/`; - if (uriPath.startsWith(candidateRoot)) { + const candidateRoot = `${candidatePath}/`; + if (isParentPath(uriPath, candidateRoot)) { return true; } } @@ -179,9 +95,5 @@ export function getURIFilter( export function isNotebookCell(documentOrUri: TextDocument | Uri): boolean { const uri = isUri(documentOrUri) ? documentOrUri : documentOrUri.uri; - return uri.scheme.includes(NotebookCellScheme); -} - -export function isUntitledFile(file?: Uri) { - return file?.scheme === 'untitled'; + return uri.scheme.includes(NotebookCellScheme) || uri.scheme.includes(InteractiveInputScheme); } diff --git a/src/client/common/utils/multiStepInput.ts b/src/client/common/utils/multiStepInput.ts index 03e68155e86a..2de1684a4d2e 100644 --- a/src/client/common/utils/multiStepInput.ts +++ b/src/client/common/utils/multiStepInput.ts @@ -1,43 +1,71 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -'use strict'; +/* eslint-disable max-classes-per-file */ -// tslint:disable:max-func-body-length no-any no-unnecessary-class +'use strict'; import { inject, injectable } from 'inversify'; -import { Disposable, QuickInput, QuickInputButton, QuickInputButtons, QuickPickItem } from 'vscode'; +import { Disposable, QuickInput, QuickInputButton, QuickInputButtons, QuickPick, QuickPickItem, Event } from 'vscode'; import { IApplicationShell } from '../application/types'; +import { createDeferred } from './async'; // Borrowed from https://github.com/Microsoft/vscode-extension-samples/blob/master/quickinput-sample/src/multiStepInput.ts // Why re-invent the wheel :) export class InputFlowAction { public static back = new InputFlowAction(); + public static cancel = new InputFlowAction(); + public static resume = new InputFlowAction(); - private constructor() {} + + private constructor() { + /** No body. */ + } } -export type InputStep = (input: MultiStepInput, state: T) => Promise | void>; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type InputStep = (input: MultiStepInput, state: T) => Promise | void>; + +type buttonCallbackType = (quickPick: QuickPick) => void; -export interface IQuickPickParameters { +export type QuickInputButtonSetup = { + /** + * Button for an action in a QuickPick. + */ + button: QuickInputButton; + /** + * Callback to be invoked when button is clicked. + */ + callback: buttonCallbackType; +}; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export interface IQuickPickParameters { title?: string; step?: number; totalSteps?: number; canGoBack?: boolean; items: T[]; - activeItem?: T; - placeholder: string; - buttons?: QuickInputButton[]; + activeItem?: T | ((quickPick: QuickPick) => Promise); + placeholder: string | undefined; + customButtonSetups?: QuickInputButtonSetup[]; matchOnDescription?: boolean; matchOnDetail?: boolean; + keepScrollPosition?: boolean; + sortByLabel?: boolean; acceptFilterBoxTextAsSelection?: boolean; - shouldResume?(): Promise; + /** + * A method called only after quickpick has been created and all handlers are registered. + */ + initialize?: (quickPick: QuickPick) => void; + onChangeItem?: { + callback: (event: E, quickPick: QuickPick) => void; + event: Event; + }; } -// tslint:disable-next-line: interface-name -export interface InputBoxParameters { +interface InputBoxParameters { title: string; password?: boolean; step?: number; @@ -46,11 +74,10 @@ export interface InputBoxParameters { prompt: string; buttons?: QuickInputButton[]; validate(value: string): Promise; - shouldResume?(): Promise; } -type MultiStepInputQuickPicResponseType = T | (P extends { buttons: (infer I)[] } ? I : never); -type MultiStepInputInputBoxResponseType

= string | (P extends { buttons: (infer I)[] } ? I : never); +type MultiStepInputQuickPickResponseType = T | (P extends { buttons: (infer I)[] } ? I : never) | undefined; +type MultiStepInputInputBoxResponseType

= string | (P extends { buttons: (infer I)[] } ? I : never) | undefined; export interface IMultiStepInput { run(start: InputStep, state: S): Promise; showQuickPick>({ @@ -60,9 +87,8 @@ export interface IMultiStepInput { items, activeItem, placeholder, - buttons, - shouldResume - }: P): Promise>; + customButtonSetups, + }: P): Promise>; showInputBox

({ title, step, @@ -71,15 +97,17 @@ export interface IMultiStepInput { prompt, validate, buttons, - shouldResume }: P): Promise>; } export class MultiStepInput implements IMultiStepInput { private current?: QuickInput; + private steps: InputStep[] = []; + constructor(private readonly shell: IApplicationShell) {} - public run(start: InputStep, state: S) { + + public run(start: InputStep, state: S): Promise { return this.stepThrough(start, state); } @@ -90,60 +118,94 @@ export class MultiStepInput implements IMultiStepInput { items, activeItem, placeholder, - buttons, - shouldResume, + customButtonSetups, matchOnDescription, matchOnDetail, - acceptFilterBoxTextAsSelection - }: P): Promise> { + acceptFilterBoxTextAsSelection, + onChangeItem, + keepScrollPosition, + sortByLabel, + initialize, + }: P): Promise> { const disposables: Disposable[] = []; - try { - return await new Promise>((resolve, reject) => { - const input = this.shell.createQuickPick(); - input.title = title; - input.step = step; - input.totalSteps = totalSteps; - input.placeholder = placeholder; - input.ignoreFocusOut = true; - input.items = items; - input.matchOnDescription = matchOnDescription || false; - input.matchOnDetail = matchOnDetail || false; - if (activeItem) { - input.activeItems = [activeItem]; - } else { - input.activeItems = []; + const input = this.shell.createQuickPick(); + input.title = title; + input.step = step; + input.sortByLabel = sortByLabel || false; + input.totalSteps = totalSteps; + input.placeholder = placeholder; + input.ignoreFocusOut = true; + input.items = items; + input.matchOnDescription = matchOnDescription || false; + input.matchOnDetail = matchOnDetail || false; + input.buttons = this.steps.length > 1 ? [QuickInputButtons.Back] : []; + if (customButtonSetups) { + for (const customButtonSetup of customButtonSetups) { + input.buttons = [...input.buttons, customButtonSetup.button]; + } + } + if (this.current) { + this.current.dispose(); + } + this.current = input; + if (onChangeItem) { + disposables.push(onChangeItem.event((e) => onChangeItem.callback(e, input))); + } + // Quickpick should be initialized synchronously and on changed item handlers are registered synchronously. + if (initialize) { + initialize(input); + } + if (activeItem) { + if (typeof activeItem === 'function') { + activeItem(input).then((item) => { + if (input.activeItems.length === 0) { + input.activeItems = [item]; + } + }); + } + } else { + input.activeItems = []; + } + this.current.show(); + // Keep scroll position is only meant to keep scroll position when updating items, + // so do it after initialization. This ensures quickpick starts with the active + // item in focus when this is true, instead of having scroll position at top. + input.keepScrollPosition = keepScrollPosition; + + const deferred = createDeferred(); + + disposables.push( + input.onDidTriggerButton(async (item) => { + if (item === QuickInputButtons.Back) { + deferred.reject(InputFlowAction.back); + input.hide(); } - input.buttons = [...(this.steps.length > 1 ? [QuickInputButtons.Back] : []), ...(buttons || [])]; - disposables.push( - input.onDidTriggerButton((item) => { - if (item === QuickInputButtons.Back) { - reject(InputFlowAction.back); - } else { - resolve(item); + if (customButtonSetups) { + for (const customButtonSetup of customButtonSetups) { + if (JSON.stringify(item) === JSON.stringify(customButtonSetup?.button)) { + await customButtonSetup?.callback(input); } - }), - input.onDidChangeSelection((selectedItems) => resolve(selectedItems[0])), - input.onDidHide(() => { - (async () => { - reject( - shouldResume && (await shouldResume()) ? InputFlowAction.resume : InputFlowAction.cancel - ); - })().catch(reject); - }) - ); - if (acceptFilterBoxTextAsSelection) { - disposables.push( - input.onDidAccept(() => { - resolve(input.value); - }) - ); + } } - if (this.current) { - this.current.dispose(); + }), + input.onDidChangeSelection((selectedItems) => deferred.resolve(selectedItems[0])), + input.onDidHide(() => { + if (!deferred.completed) { + deferred.resolve(undefined); } - this.current = input; - this.current.show(); - }); + }), + ); + if (acceptFilterBoxTextAsSelection) { + disposables.push( + input.onDidAccept(() => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + deferred.resolve(input.value as any); + }), + ); + } + + try { + return await deferred.promise; } finally { disposables.forEach((d) => d.dispose()); } @@ -158,7 +220,6 @@ export class MultiStepInput implements IMultiStepInput { validate, password, buttons, - shouldResume }: P): Promise> { const disposables: Disposable[] = []; try { @@ -167,7 +228,7 @@ export class MultiStepInput implements IMultiStepInput { input.title = title; input.step = step; input.totalSteps = totalSteps; - input.password = password ? true : false; + input.password = !!password; input.value = value || ''; input.prompt = prompt; input.ignoreFocusOut = true; @@ -178,7 +239,8 @@ export class MultiStepInput implements IMultiStepInput { if (item === QuickInputButtons.Back) { reject(InputFlowAction.back); } else { - resolve(item); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + resolve(item as any); } }), input.onDidAccept(async () => { @@ -200,12 +262,8 @@ export class MultiStepInput implements IMultiStepInput { } }), input.onDidHide(() => { - (async () => { - reject( - shouldResume && (await shouldResume()) ? InputFlowAction.resume : InputFlowAction.cancel - ); - })().catch(reject); - }) + resolve(undefined); + }), ); if (this.current) { this.current.dispose(); @@ -232,6 +290,9 @@ export class MultiStepInput implements IMultiStepInput { if (err === InputFlowAction.back) { this.steps.pop(); step = this.steps.pop(); + if (step === undefined) { + throw err; + } } else if (err === InputFlowAction.resume) { step = this.steps.pop(); } else if (err === InputFlowAction.cancel) { @@ -253,6 +314,7 @@ export interface IMultiStepInputFactory { @injectable() export class MultiStepInputFactory { constructor(@inject(IApplicationShell) private readonly shell: IApplicationShell) {} + public create(): IMultiStepInput { return new MultiStepInput(this.shell); } diff --git a/src/client/common/utils/platform.ts b/src/client/common/utils/platform.ts index 20476b5944d4..a1a49ba3c427 100644 --- a/src/client/common/utils/platform.ts +++ b/src/client/common/utils/platform.ts @@ -8,13 +8,13 @@ import { EnvironmentVariables } from '../variables/types'; export enum Architecture { Unknown = 1, x86 = 2, - x64 = 3 + x64 = 3, } export enum OSType { Unknown = 'Unknown', Windows = 'Windows', OSX = 'OSX', - Linux = 'Linux' + Linux = 'Linux', } // Return the OS type for the given platform string. @@ -30,18 +30,52 @@ export function getOSType(platform: string = process.platform): OSType { } } -export function getEnvironmentVariable(key: string): string | undefined { - // tslint:disable-next-line: no-any - return ((process.env as any) as EnvironmentVariables)[key]; +const architectures: Record = { + x86: Architecture.x86, // 32-bit + x64: Architecture.x64, // 64-bit + '': Architecture.Unknown, +}; + +/** + * Identify the host's native architecture/bitness. + */ +export function getArchitecture(): Architecture { + const fromProc = architectures[process.arch]; + if (fromProc !== undefined) { + return fromProc; + } + + const arch = require('arch'); + return architectures[arch()] || Architecture.Unknown; } -export function getPathEnvironmentVariable(): string | undefined { - return getEnvironmentVariable('Path') || getEnvironmentVariable('PATH'); +/** + * Look up the requested env var value (or undefined` if not set). + */ +export function getEnvironmentVariable(key: string): string | undefined { + return ((process.env as any) as EnvironmentVariables)[key]; } +/** + * Get the current user's home directory. + * + * The lookup is limited to environment variables. + */ export function getUserHomeDir(): string | undefined { if (getOSType() === OSType.Windows) { return getEnvironmentVariable('USERPROFILE'); } return getEnvironmentVariable('HOME') || getEnvironmentVariable('HOMEPATH'); } + +export function isWindows(): boolean { + return getOSType() === OSType.Windows; +} + +export function getPathEnvVariable(): string[] { + const value = getEnvironmentVariable('PATH') || getEnvironmentVariable('Path'); + if (value) { + return value.split(isWindows() ? ';' : ':'); + } + return []; +} diff --git a/src/client/common/utils/random.ts b/src/client/common/utils/random.ts index 872766274ff2..a766df771116 100644 --- a/src/client/common/utils/random.ts +++ b/src/client/common/utils/random.ts @@ -17,7 +17,7 @@ function getRandom(): number { return num / maxValue; } -export function getRandomBetween(min: number = 0, max: number = 10): number { +function getRandomBetween(min: number = 0, max: number = 10): number { const randomVal: number = getRandom(); return min + randomVal * (max - min); } diff --git a/src/client/common/utils/resourceLifecycle.ts b/src/client/common/utils/resourceLifecycle.ts new file mode 100644 index 000000000000..b5d1a9a1c83a --- /dev/null +++ b/src/client/common/utils/resourceLifecycle.ts @@ -0,0 +1,199 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// eslint-disable-next-line max-classes-per-file +import { traceWarn } from '../../logging'; +import { IDisposable } from '../types'; +import { Iterable } from './iterable'; + +interface IDisposables extends IDisposable { + push(...disposable: IDisposable[]): void; +} + +export const EmptyDisposable = { + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + dispose: () => { + /** */ + }, +}; + +/** + * Disposes of the value(s) passed in. + */ +export function dispose(disposable: T): T; +export function dispose(disposable: T | undefined): T | undefined; +export function dispose = Iterable>(disposables: A): A; +export function dispose(disposables: Array): Array; +export function dispose(disposables: ReadonlyArray): ReadonlyArray; +// eslint-disable-next-line @typescript-eslint/no-explicit-any, consistent-return +export function dispose(arg: T | Iterable | undefined): any { + if (Iterable.is(arg)) { + for (const d of arg) { + if (d) { + try { + d.dispose(); + } catch (e) { + traceWarn(`dispose() failed for ${d}`, e); + } + } + } + + return Array.isArray(arg) ? [] : arg; + } + if (arg) { + arg.dispose(); + return arg; + } +} + +/** + * Safely dispose each of the disposables. + */ +export async function disposeAll(disposables: IDisposable[]): Promise { + await Promise.all( + disposables.map(async (d) => { + try { + return Promise.resolve(d.dispose()); + } catch (err) { + // do nothing + } + return Promise.resolve(); + }), + ); +} + +/** + * A list of disposables. + */ +export class Disposables implements IDisposables { + private disposables: IDisposable[] = []; + + constructor(...disposables: IDisposable[]) { + this.disposables.push(...disposables); + } + + public push(...disposables: IDisposable[]): void { + this.disposables.push(...disposables); + } + + public async dispose(): Promise { + const { disposables } = this; + this.disposables = []; + await disposeAll(disposables); + } +} + +/** + * Manages a collection of disposable values. + * + * This is the preferred way to manage multiple disposables. A `DisposableStore` is safer to work with than an + * `IDisposable[]` as it considers edge cases, such as registering the same value multiple times or adding an item to a + * store that has already been disposed of. + */ +export class DisposableStore implements IDisposable { + static DISABLE_DISPOSED_WARNING = false; + + private readonly _toDispose = new Set(); + + private _isDisposed = false; + + constructor(...disposables: IDisposable[]) { + disposables.forEach((disposable) => this.add(disposable)); + } + + /** + * Dispose of all registered disposables and mark this object as disposed. + * + * Any future disposables added to this object will be disposed of on `add`. + */ + public dispose(): void { + if (this._isDisposed) { + return; + } + + this._isDisposed = true; + this.clear(); + } + + /** + * @return `true` if this object has been disposed of. + */ + public get isDisposed(): boolean { + return this._isDisposed; + } + + /** + * Dispose of all registered disposables but do not mark this object as disposed. + */ + public clear(): void { + if (this._toDispose.size === 0) { + return; + } + + try { + dispose(this._toDispose); + } finally { + this._toDispose.clear(); + } + } + + /** + * Add a new {@link IDisposable disposable} to the collection. + */ + public add(o: T): T { + if (!o) { + return o; + } + if (((o as unknown) as DisposableStore) === this) { + throw new Error('Cannot register a disposable on itself!'); + } + + if (this._isDisposed) { + if (!DisposableStore.DISABLE_DISPOSED_WARNING) { + traceWarn( + new Error( + 'Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!', + ).stack, + ); + } + } else { + this._toDispose.add(o); + } + + return o; + } +} + +/** + * Abstract class for a {@link IDisposable disposable} object. + * + * Subclasses can {@linkcode _register} disposables that will be automatically cleaned up when this object is disposed of. + */ +export abstract class DisposableBase implements IDisposable { + protected readonly _store = new DisposableStore(); + + private _isDisposed = false; + + public get isDisposed(): boolean { + return this._isDisposed; + } + + constructor(...disposables: IDisposable[]) { + disposables.forEach((disposable) => this._store.add(disposable)); + } + + public dispose(): void { + this._store.dispose(); + this._isDisposed = true; + } + + /** + * Adds `o` to the collection of disposables managed by this object. + */ + public _register(o: T): T { + if (((o as unknown) as DisposableBase) === this) { + throw new Error('Cannot register a disposable on itself!'); + } + return this._store.add(o); + } +} diff --git a/src/client/common/utils/runAfterActivation.ts b/src/client/common/utils/runAfterActivation.ts new file mode 100644 index 000000000000..9a5297ea00f7 --- /dev/null +++ b/src/client/common/utils/runAfterActivation.ts @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const itemsToRun: (() => void)[] = []; +let activationCompleted = false; + +/** + * Add items to be run after extension activation. This will add item + * to the end of the list. This function will immediately run the item + * if extension is already activated. + */ +export function addItemsToRunAfterActivation(run: () => void): void { + if (activationCompleted) { + run(); + } else { + itemsToRun.push(run); + } +} + +/** + * This should be called after extension activation is complete. + */ +export function runAfterActivation(): void { + activationCompleted = true; + while (itemsToRun.length > 0) { + const run = itemsToRun.shift(); + if (run) { + run(); + } + } +} diff --git a/src/client/common/utils/serializers.ts b/src/client/common/utils/serializers.ts deleted file mode 100644 index 7626c0780264..000000000000 --- a/src/client/common/utils/serializers.ts +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -/** - * Serialize ArraBuffer and ArrayBufferView into a fomat such that they are json serializable. - * - * @export - * @param {(undefined | (ArrayBuffer | ArrayBufferView)[])} buffers - * @returns - */ -export function serializeDataViews(buffers: undefined | (ArrayBuffer | ArrayBufferView)[]) { - if (!buffers || !Array.isArray(buffers) || buffers.length === 0) { - return; - } - // tslint:disable-next-line: no-any - const newBufferView: any[] = []; - // tslint:disable-next-line: prefer-for-of - for (let i = 0; i < buffers.length; i += 1) { - const item = buffers[i]; - if ('buffer' in item && 'byteOffset' in item) { - // It is an ArrayBufferView - // tslint:disable-next-line: no-any - const buffer = Array.apply(null, new Uint8Array(item.buffer as any) as any); - newBufferView.push({ - ...item, - byteLength: item.byteLength, - byteOffset: item.byteOffset, - buffer - // tslint:disable-next-line: no-any - } as any); - } else { - // Do not use `Array.apply`, it will not work for large arrays. - // Nodejs will throw `stackoverflow` exceptions. - // Else following ipynb fails https://github.com/K3D-tools/K3D-jupyter/blob/821a59ed88579afaafababd6291e8692d70eb088/examples/camera_manipulation.ipynb - // Yet another case where 99% can work, but 1% can fail when testing. - // tslint:disable-next-line: no-any - newBufferView.push([...new Uint8Array(item as any)]); - } - } - - // tslint:disable-next-line: no-any - return newBufferView; -} - -/** - * Deserializes ArrayBuffer and ArrayBufferView from a format that was json serializable into actual ArrayBuffer and ArrayBufferViews. - * - * @export - * @param {(undefined | (ArrayBuffer | ArrayBufferView)[])} buffers - * @returns - */ -export function deserializeDataViews(buffers: undefined | (ArrayBuffer | ArrayBufferView)[]) { - if (!Array.isArray(buffers) || buffers.length === 0) { - return buffers; - } - const newBufferView: (ArrayBuffer | ArrayBufferView)[] = []; - // tslint:disable-next-line: prefer-for-of - for (let i = 0; i < buffers.length; i += 1) { - const item = buffers[i]; - if ('buffer' in item && 'byteOffset' in item) { - const buffer = new Uint8Array(item.buffer).buffer; - // It is an ArrayBufferView - // tslint:disable-next-line: no-any - const bufferView = new DataView(buffer, item.byteOffset, item.byteLength); - newBufferView.push(bufferView); - } else { - const buffer = new Uint8Array(item).buffer; - // tslint:disable-next-line: no-any - newBufferView.push(buffer); - } - } - return newBufferView; -} diff --git a/src/client/common/utils/stopWatch.ts b/src/client/common/utils/stopWatch.ts index c78c763f7d2c..9c9a73d8279e 100644 --- a/src/client/common/utils/stopWatch.ts +++ b/src/client/common/utils/stopWatch.ts @@ -3,7 +3,7 @@ 'use strict'; -export class StopWatch { +export class StopWatch implements IStopWatch { private started = new Date().getTime(); public get elapsedTime() { return new Date().getTime() - this.started; @@ -12,3 +12,7 @@ export class StopWatch { this.started = new Date().getTime(); } } + +export interface IStopWatch { + elapsedTime: number; +} diff --git a/src/client/common/utils/sysTypes.ts b/src/client/common/utils/sysTypes.ts index ce0bce0af963..e56f12e34fff 100644 --- a/src/client/common/utils/sysTypes.ts +++ b/src/client/common/utils/sysTypes.ts @@ -5,14 +5,12 @@ 'use strict'; -// tslint:disable:rule1 no-any no-unnecessary-callback-wrapper jsdoc-format no-for-in prefer-const no-increment-decrement - const _typeof = { number: 'number', string: 'string', undefined: 'undefined', object: 'object', - function: 'function' + function: 'function', }; /** @@ -41,13 +39,6 @@ export function isString(str: any): str is string { return false; } -/** - * @returns whether the provided parameter is a JavaScript Array and each element in the array is a string. - */ -export function isStringArray(value: any): value is string[] { - return isArray(value) && (value).every((elem) => isString(elem)); -} - /** * * @returns whether the provided parameter is of type `object` but **not** @@ -74,57 +65,3 @@ export function isNumber(obj: any): obj is number { return false; } - -/** - * @returns whether the provided parameter is a JavaScript Boolean or not. - */ -export function isBoolean(obj: any): obj is boolean { - return obj === true || obj === false; -} - -/** - * @returns whether the provided parameter is undefined. - */ -export function isUndefined(obj: any): boolean { - return typeof obj === _typeof.undefined; -} - -/** - * @returns whether the provided parameter is undefined or null. - */ -export function isUndefinedOrNull(obj: any): boolean { - return isUndefined(obj) || obj === null; -} - -const hasOwnProperty = Object.prototype.hasOwnProperty; - -/** - * @returns whether the provided parameter is an empty JavaScript Object or not. - */ -export function isEmptyObject(obj: any): obj is any { - if (!isObject(obj)) { - return false; - } - - for (let key in obj) { - if (hasOwnProperty.call(obj, key)) { - return false; - } - } - - return true; -} - -/** - * @returns whether the provided parameter is a JavaScript Function or not. - */ -export function isFunction(obj: any): obj is Function { - return typeof obj === _typeof.function; -} - -/** - * @returns whether the provided parameters is are JavaScript Function or not. - */ -export function areFunctions(...objects: any[]): boolean { - return objects && objects.length > 0 && objects.every(isFunction); -} diff --git a/src/client/common/utils/text.ts b/src/client/common/utils/text.ts index 59359966db47..ee61cae5bb1e 100644 --- a/src/client/common/utils/text.ts +++ b/src/client/common/utils/text.ts @@ -6,8 +6,8 @@ import { Position, Range, TextDocument } from 'vscode'; import { isNumber } from './sysTypes'; -export function getWindowsLineEndingCount(document: TextDocument, offset: Number) { - //const eolPattern = new RegExp('\r\n', 'g'); +export function getWindowsLineEndingCount(document: TextDocument, offset: number): number { + // const eolPattern = new RegExp('\r\n', 'g'); const eolPattern = /\r\n/g; const readBlock = 1024; let count = 0; @@ -111,3 +111,141 @@ export function parsePosition(raw: string | number): Position { } return new Position(line, col); } + +/** + * Return the indentation part of the given line. + */ +export function getIndent(line: string): string { + const found = line.match(/^ */); + return found![0]; +} + +/** + * Return the dedented lines in the given text. + * + * This is used to represent text concisely and readably, which is + * particularly useful for declarative definitions (e.g. in tests). + * + * (inspired by Python's `textwrap.dedent()`) + */ +export function getDedentedLines(text: string): string[] { + const linesep = text.includes('\r') ? '\r\n' : '\n'; + const lines = text.split(linesep); + if (!lines) { + return [text]; + } + + if (lines[0] !== '') { + throw Error('expected actual first line to be blank'); + } + lines.shift(); + if (lines.length === 0) { + return []; + } + + if (lines[0] === '') { + throw Error('expected "first" line to not be blank'); + } + const leading = getIndent(lines[0]).length; + + for (let i = 0; i < lines.length; i += 1) { + const line = lines[i]; + if (getIndent(line).length < leading) { + throw Error(`line ${i} has less indent than the "first" line`); + } + lines[i] = line.substring(leading); + } + + return lines; +} + +/** + * Extract a tree based on the given text. + * + * The tree is derived from the indent level of each line. The caller + * is responsible for applying any meaning to the text of each node + * in the tree. + * + * Blank lines and comments (with a leading `#`) are ignored. Also, + * the full text is automatically dedented until at least one line + * has no indent (i.e. is treated as a root). + * + * @returns - the list of nodes in the tree (pairs of text & parent index) + * (note that the parent index of roots is `-1`) + * + * Example: + * + * parseTree(` + * # This comment and the following blank line are ignored. + * + * this is a root + * the first branch + * a sub-branch # This comment is ignored. + * this is the first leaf node! + * another leaf node... + * middle + * + * the second main branch + * # indents do not have to be consistent across the full text. + * # ...and the indent of comments is not relevant. + * node 1 + * node 2 + * + * the last leaf node! + * + * another root + * nothing to see here! + * + * # this comment is ignored + * `.trim()) + * + * would produce the following: + * + * [ + * ['this is a root', -1], + * ['the first branch', 0], + * ['a sub-branch', 1], + * ['this is the first leaf node!', 2], + * ['another leaf node...', 1], + * ['middle', 1], + * ['the second main branch', 0], + * ['node 1', 6], + * ['node 2', 6], + * ['the last leaf node!', 0], + * ['another root', -1], + * ['nothing to see here!', 10], + * ] + */ +export function parseTree(text: string): [string, number][] { + const parsed: [string, number][] = []; + const parents: [string, number][] = []; + + const lines = getDedentedLines(text) + .map((l) => l.split(' #')[0].split(' //')[0].trimEnd()) + .filter((l) => l.trim() !== ''); + lines.forEach((line) => { + const indent = getIndent(line); + const entry = line.trim(); + + let parentIndex: number; + if (indent === '') { + parentIndex = -1; + parents.push([indent, parsed.length]); + } else if (parsed.length === 0) { + throw Error(`expected non-indented line, got ${line}`); + } else { + let parentIndent: string; + [parentIndent, parentIndex] = parents[parents.length - 1]; + while (indent.length <= parentIndent.length) { + parents.pop(); + [parentIndent, parentIndex] = parents[parents.length - 1]; + } + if (parentIndent.length < indent.length) { + parents.push([indent, parsed.length]); + } + } + parsed.push([entry, parentIndex!]); + }); + + return parsed; +} diff --git a/src/client/common/utils/version.ts b/src/client/common/utils/version.ts index 830ebc668f78..b3d9ed3d2f46 100644 --- a/src/client/common/utils/version.ts +++ b/src/client/common/utils/version.ts @@ -3,12 +3,9 @@ 'use strict'; -// tslint:disable:no-multiline-string - import * as semver from 'semver'; import { verboseRegExp } from './regexp'; -//=========================== // basic version info /** @@ -36,7 +33,7 @@ type ErrorMsg = string; function normalizeVersionPart(part: unknown): [number, ErrorMsg] { // Any -1 values where the original is not a number are handled in validation. if (typeof part === 'number') { - if (isNaN(part)) { + if (Number.isNaN(part)) { return [-1, 'missing']; } if (part < 0) { @@ -47,7 +44,7 @@ function normalizeVersionPart(part: unknown): [number, ErrorMsg] { } if (typeof part === 'string') { const parsed = parseInt(part, 10); - if (isNaN(parsed)) { + if (Number.isNaN(parsed)) { return [-1, 'string not numeric']; } if (parsed < 0) { @@ -73,33 +70,47 @@ export const EMPTY_VERSION: RawBasicVersionInfo = { major: -1, minor: -1, micro: -1, - unnormalized: { - major: undefined, - minor: undefined, - micro: undefined - } }; +Object.freeze(EMPTY_VERSION); + +function copyStrict(info: T): RawBasicVersionInfo { + const copied: RawBasicVersionInfo = { + major: info.major, + minor: info.minor, + micro: info.micro, + }; + + const { unnormalized } = (info as unknown) as RawBasicVersionInfo; + if (unnormalized !== undefined) { + copied.unnormalized = { + major: unnormalized.major, + minor: unnormalized.minor, + micro: unnormalized.micro, + }; + } + + return copied; +} /** * Make a copy and set all the properties properly. * - * Only the "basic" version info will be normalized. The caller - * is responsible for any other properties beyond that. + * Only the "basic" version info will be set (and normalized). + * The caller is responsible for any other properties beyond that. */ -export function normalizeBasicVersionInfo(info: T | undefined): T { +function normalizeBasicVersionInfo(info: T | undefined): T { if (!info) { return EMPTY_VERSION as T; } - const norm: T = { ...info }; - const raw = (norm as unknown) as RawBasicVersionInfo; + const norm = copyStrict(info); // Do not normalize if it has already been normalized. - if (raw.unnormalized === undefined) { - raw.unnormalized = {}; - [norm.major, raw.unnormalized.major] = normalizeVersionPart(norm.major); - [norm.minor, raw.unnormalized.minor] = normalizeVersionPart(norm.minor); - [norm.micro, raw.unnormalized.micro] = normalizeVersionPart(norm.micro); + if (norm.unnormalized === undefined) { + norm.unnormalized = {}; + [norm.major, norm.unnormalized.major] = normalizeVersionPart(norm.major); + [norm.minor, norm.unnormalized.minor] = normalizeVersionPart(norm.minor); + [norm.micro, norm.unnormalized.micro] = normalizeVersionPart(norm.micro); } - return norm; + return norm as T; } function validateVersionPart(prop: string, part: number, unnormalized?: ErrorMsg) { @@ -122,7 +133,7 @@ function validateVersionPart(prop: string, part: number, unnormalized?: ErrorMsg * Only the "basic" version info will be validated. The caller * is responsible for any other properties beyond that. */ -export function validateBasicVersionInfo(info: T) { +function validateBasicVersionInfo(info: T): void { const raw = (info as unknown) as RawBasicVersionInfo; validateVersionPart('major', info.major, raw.unnormalized?.major); validateVersionPart('minor', info.minor, raw.unnormalized?.minor); @@ -147,9 +158,11 @@ export function validateBasicVersionInfo(info: T) { export function getVersionString(info: T): string { if (info.major < 0) { return ''; - } else if (info.minor < 0) { + } + if (info.minor < 0) { return `${info.major}`; - } else if (info.micro < 0) { + } + if (info.micro < 0) { return `${info.major}.${info.minor}`; } return `${info.major}.${info.minor}.${info.micro}`; @@ -213,7 +226,7 @@ export function parseBasicVersionInfo(verStr: string // This is effectively normalized. version: ({ major, minor, micro } as unknown) as T, before: before || '', - after: after || '' + after: after || '', }; } @@ -232,7 +245,60 @@ export function isVersionInfoEmpty(info: T): boolean return info.major < 0 && info.minor < 0 && info.micro < 0; } -//=========================== +/** + * Decide if two versions are the same or if one is "less". + * + * Note that a less-complete object that otherwise matches + * is considered "less". + * + * Additional checks for an otherwise "identical" version may be made + * through `compareExtra()`. + * + * @returns - the customary comparison indicator (e.g. -1 means left is "more") + * @returns - a string that indicates the property where they differ (if any) + */ +export function compareVersions( + // the versions to compare: + left: T, + right: V, + compareExtra?: (v1: T, v2: V) => [number, string], +): [number, string] { + if (left.major < right.major) { + return [1, 'major']; + } + if (left.major > right.major) { + return [-1, 'major']; + } + if (left.major === -1) { + // Don't bother checking minor or micro. + return [0, '']; + } + + if (left.minor < right.minor) { + return [1, 'minor']; + } + if (left.minor > right.minor) { + return [-1, 'minor']; + } + if (left.minor === -1) { + // Don't bother checking micro. + return [0, '']; + } + + if (left.micro < right.micro) { + return [1, 'micro']; + } + if (left.micro > right.micro) { + return [-1, 'micro']; + } + + if (compareExtra !== undefined) { + return compareExtra(left, right); + } + + return [0, '']; +} + // base version info /** @@ -248,15 +314,12 @@ export type VersionInfo = BasicVersionInfo & { * Make a copy and set all the properties properly. */ export function normalizeVersionInfo(info: T): T { - const basic = normalizeBasicVersionInfo(info); - if (!info) { - basic.raw = ''; - return basic; - } - const norm = { ...info, ...basic }; + const norm = normalizeBasicVersionInfo(info); + norm.raw = info.raw; if (!norm.raw) { norm.raw = ''; } + // Any string value of "raw" is considered normalized. return norm; } @@ -267,7 +330,7 @@ export function normalizeVersionInfo(info: T): T { * * This assumes that the info has already been normalized. */ -export function validateVersionInfo(info: T) { +export function validateVersionInfo(info: T): void { validateBasicVersionInfo(info); // `info.raw` can be anything. } @@ -287,14 +350,56 @@ export function parseVersionInfo(verStr: string): ParseRe return result; } -//=========================== +/** + * Checks if major, minor, and micro match. + * + * Additional checks may be made through `compareExtra()`. + */ +export function areIdenticalVersion( + // the versions to compare: + left: T, + right: V, + compareExtra?: (v1: T, v2: V) => [number, string], +): boolean { + const [result] = compareVersions(left, right, compareExtra); + return result === 0; +} + +/** + * Checks if the versions are identical or one is more complete than other (and otherwise the same). + * + * At the least the major version must be set (non-negative). + */ +export function areSimilarVersions( + // the versions to compare: + left: T, + right: V, + compareExtra?: (v1: T, v2: V) => [number, string], +): boolean { + const [result, prop] = compareVersions(left, right, compareExtra); + if (result === 0) { + return true; + } + + if (prop === 'major') { + // An empty version is never similar (except to another empty version). + return false; + } + + if (result < 0) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return ((right as unknown) as any)[prop] === -1; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return ((left as unknown) as any)[prop] === -1; +} + // semver -export function parseVersion(raw: string): semver.SemVer { +export function parseSemVerSafe(raw: string): semver.SemVer { raw = raw.replace(/\.00*(?=[1-9]|0\.)/, '.'); const ver = semver.coerce(raw); if (ver === null || !semver.valid(ver)) { - // tslint:disable-next-line: no-suspicious-comment // TODO: Raise an exception instead? return new semver.SemVer('0.0.0'); } diff --git a/src/client/common/utils/workerPool.ts b/src/client/common/utils/workerPool.ts index 12bc2d322045..a241c416f3bd 100644 --- a/src/client/common/utils/workerPool.ts +++ b/src/client/common/utils/workerPool.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { traceError } from '../logger'; +import { traceError } from '../../logging'; import { createDeferred, Deferred } from './async'; interface IWorker { @@ -27,7 +27,7 @@ interface IWorkItem { export enum QueuePosition { Back, - Front + Front, } export interface IWorkerPool extends IWorker { @@ -47,7 +47,7 @@ class Worker implements IWorker { private readonly next: NextFunc, private readonly workFunc: WorkFunc, private readonly postResult: PostResult, - private readonly name: string + private readonly name: string, ) {} public stop() { this.stopProcessing = true; @@ -61,7 +61,7 @@ class Worker implements IWorker { const result = await this.workFunc(workItem); this.postResult(workItem, result); } catch (ex) { - this.postResult(workItem, undefined, ex); + this.postResult(workItem, undefined, ex as Error); } } catch (ex) { // Next got rejected. Likely worker pool is shutting down. @@ -139,7 +139,7 @@ class WorkerPool implements IWorkerPool { public constructor( private readonly workerFunc: WorkFunc, private readonly numWorkers: number = 2, - private readonly name: string = 'Worker' + private readonly name: string = 'Worker', ) {} public addToQueue(item: T, position?: QueuePosition): Promise { @@ -179,8 +179,8 @@ class WorkerPool implements IWorkerPool { (workItem: IWorkItem) => this.workerFunc(workItem.item), (workItem: IWorkItem, result?: R, error?: Error) => this.queue.completed(workItem, result, error), - `${this.name} ${num}` - ) + `${this.name} ${num}`, + ), ); num = num - 1; } @@ -222,7 +222,7 @@ class WorkerPool implements IWorkerPool { // new items are added to the queue. return new Promise>((resolve, reject) => { this.waitingWorkersUnblockQueue.push({ - unblock: (workItem?: IWorkItem) => { + unblock: (workItem: IWorkItem) => { // This will be called to unblock any worker waiting for items. if (this.stopProcessing) { // We should reject here since the processing should be stopped. @@ -233,17 +233,17 @@ class WorkerPool implements IWorkerPool { }, stop: () => { reject(); - } + }, }); }); } } -export function createWorkerPool( +export function createRunningWorkerPool( workerFunc: WorkFunc, - numWorkers: number = 2, - name: string = 'Worker' -): IWorkerPool { + numWorkers?: number, + name?: string, +): WorkerPool { const pool = new WorkerPool(workerFunc, numWorkers, name); pool.start(); return pool; diff --git a/src/client/common/variables/environment.ts b/src/client/common/variables/environment.ts index 63f7896d5423..9f0abd9b0ee7 100644 --- a/src/client/common/variables/environment.ts +++ b/src/client/common/variables/environment.ts @@ -1,13 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { pathExistsSync, readFileSync } from '../platform/fs-paths'; import { inject, injectable } from 'inversify'; import * as path from 'path'; +import { traceError } from '../../logging'; import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; import { IFileSystem } from '../platform/types'; import { IPathUtils } from '../types'; import { EnvironmentVariables, IEnvironmentVariablesService } from './types'; +import { normCase } from '../platform/fs-paths'; @injectable() export class EnvironmentVariablesService implements IEnvironmentVariablesService { @@ -15,32 +18,64 @@ export class EnvironmentVariablesService implements IEnvironmentVariablesService constructor( // We only use a small portion of either of these interfaces. @inject(IPathUtils) private readonly pathUtils: IPathUtils, - @inject(IFileSystem) private readonly fs: IFileSystem + @inject(IFileSystem) private readonly fs: IFileSystem, ) {} public async parseFile( filePath?: string, - baseVars?: EnvironmentVariables + baseVars?: EnvironmentVariables, ): Promise { - if (!filePath || !(await this.fs.fileExists(filePath))) { + if (!filePath || !(await this.fs.pathExists(filePath))) { return; } - return parseEnvFile(await this.fs.readFile(filePath), baseVars); + const contents = await this.fs.readFile(filePath).catch((ex) => { + traceError('Custom .env is likely not pointing to a valid file', ex); + return undefined; + }); + if (!contents) { + return; + } + return parseEnvFile(contents, baseVars); } - public mergeVariables(source: EnvironmentVariables, target: EnvironmentVariables) { + public parseFileSync(filePath?: string, baseVars?: EnvironmentVariables): EnvironmentVariables | undefined { + if (!filePath || !pathExistsSync(filePath)) { + return; + } + let contents: string | undefined; + try { + contents = readFileSync(filePath, { encoding: 'utf8' }); + } catch (ex) { + traceError('Custom .env is likely not pointing to a valid file', ex); + } + if (!contents) { + return; + } + return parseEnvFile(contents, baseVars); + } + + public mergeVariables( + source: EnvironmentVariables, + target: EnvironmentVariables, + options?: { overwrite?: boolean; mergeAll?: boolean }, + ) { if (!target) { return; } + const reference = target; + target = normCaseKeys(target); + source = normCaseKeys(source); const settingsNotToMerge = ['PYTHONPATH', this.pathVariable]; Object.keys(source).forEach((setting) => { - if (settingsNotToMerge.indexOf(setting) >= 0) { + if (!options?.mergeAll && settingsNotToMerge.indexOf(setting) >= 0) { return; } - if (target[setting] === undefined) { + if (target[setting] === undefined || options?.overwrite) { target[setting] = source[setting]; } }); + restoreKeys(target); + matchTarget(reference, target); } public appendPythonPath(vars: EnvironmentVariables, ...pythonPaths: string[]) { @@ -51,18 +86,24 @@ export class EnvironmentVariablesService implements IEnvironmentVariablesService return this.appendPaths(vars, this.pathVariable, ...paths); } - private get pathVariable(): 'Path' | 'PATH' { + private get pathVariable(): string { if (!this._pathVariable) { this._pathVariable = this.pathUtils.getPathVariableName(); } - return this._pathVariable!; + return normCase(this._pathVariable)!; } - private appendPaths( - vars: EnvironmentVariables, - variableName: 'PATH' | 'Path' | 'PYTHONPATH', - ...pathsToAppend: string[] - ) { + private appendPaths(vars: EnvironmentVariables, variableName: string, ...pathsToAppend: string[]) { + const reference = vars; + vars = normCaseKeys(vars); + variableName = normCase(variableName); + vars = this._appendPaths(vars, variableName, ...pathsToAppend); + restoreKeys(vars); + matchTarget(reference, vars); + return vars; + } + + private _appendPaths(vars: EnvironmentVariables, variableName: string, ...pathsToAppend: string[]) { const valueToAppend = pathsToAppend .filter((item) => typeof item === 'string' && item.trim().length > 0) .map((item) => item.trim()) @@ -102,7 +143,7 @@ function parseEnvLine(line: string): [string, string] { // https://github.com/motdotla/dotenv/blob/master/lib/main.js#L32 // We don't use dotenv here because it loses ordering, which is // significant for substitution. - const match = line.match(/^\s*([a-zA-Z]\w*)\s*=\s*(.*?)?\s*$/); + const match = line.match(/^\s*(_*[a-zA-Z]\w*)\s*=\s*(.*?)?\s*$/); if (!match) { return ['', '']; } @@ -130,7 +171,7 @@ function substituteEnvVars( value: string, localVars: EnvironmentVariables, globalVars: EnvironmentVariables, - missing = '' + missing = '', ): string { // Substitution here is inspired a little by dotenv-expand: // https://github.com/motdotla/dotenv-expand/blob/master/lib/main.js @@ -154,3 +195,40 @@ function substituteEnvVars( return value.replace(/\\\$/g, '$'); } + +export function normCaseKeys(env: EnvironmentVariables): EnvironmentVariables { + const normalizedEnv: EnvironmentVariables = {}; + Object.keys(env).forEach((key) => { + const normalizedKey = normCase(key); + normalizedEnv[normalizedKey] = env[key]; + }); + return normalizedEnv; +} + +export function restoreKeys(env: EnvironmentVariables) { + const processEnvKeys = Object.keys(process.env); + processEnvKeys.forEach((processEnvKey) => { + const originalKey = normCase(processEnvKey); + if (originalKey !== processEnvKey && env[originalKey] !== undefined) { + env[processEnvKey] = env[originalKey]; + delete env[originalKey]; + } + }); +} + +export function matchTarget(reference: EnvironmentVariables, target: EnvironmentVariables): void { + Object.keys(reference).forEach((key) => { + if (target.hasOwnProperty(key)) { + reference[key] = target[key]; + } else { + delete reference[key]; + } + }); + + // Add any new keys from target to reference + Object.keys(target).forEach((key) => { + if (!reference.hasOwnProperty(key)) { + reference[key] = target[key]; + } + }); +} diff --git a/src/client/common/variables/environmentVariablesProvider.ts b/src/client/common/variables/environmentVariablesProvider.ts index fd083b10e88f..14573d2204aa 100644 --- a/src/client/common/variables/environmentVariablesProvider.ts +++ b/src/client/common/variables/environmentVariablesProvider.ts @@ -1,34 +1,39 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { inject, injectable, optional } from 'inversify'; +import { inject, injectable } from 'inversify'; +import * as path from 'path'; import { ConfigurationChangeEvent, Disposable, Event, EventEmitter, FileSystemWatcher, Uri } from 'vscode'; -import { IServiceContainer } from '../../ioc/types'; +import { traceError, traceVerbose } from '../../logging'; import { sendFileCreationTelemetry } from '../../telemetry/envFileTelemetry'; import { IWorkspaceService } from '../application/types'; -import { traceVerbose } from '../logger'; +import { PythonSettings } from '../configSettings'; import { IPlatformService } from '../platform/types'; -import { IConfigurationService, ICurrentProcess, IDisposableRegistry } from '../types'; -import { InMemoryInterpreterSpecificCache } from '../utils/cacheUtils'; -import { clearCachedResourceSpecificIngterpreterData } from '../utils/decorators'; +import { ICurrentProcess, IDisposableRegistry } from '../types'; +import { InMemoryCache } from '../utils/cacheUtils'; +import { SystemVariables } from './systemVariables'; import { EnvironmentVariables, IEnvironmentVariablesProvider, IEnvironmentVariablesService } from './types'; const CACHE_DURATION = 60 * 60 * 1000; @injectable() export class EnvironmentVariablesProvider implements IEnvironmentVariablesProvider, Disposable { public trackedWorkspaceFolders = new Set(); + private fileWatchers = new Map(); + private disposables: Disposable[] = []; + private changeEventEmitter: EventEmitter; + + private readonly envVarCaches = new Map>(); + constructor( @inject(IEnvironmentVariablesService) private envVarsService: IEnvironmentVariablesService, @inject(IDisposableRegistry) disposableRegistry: Disposable[], @inject(IPlatformService) private platformService: IPlatformService, @inject(IWorkspaceService) private workspaceService: IWorkspaceService, - @inject(IConfigurationService) private readonly configurationService: IConfigurationService, @inject(ICurrentProcess) private process: ICurrentProcess, - @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @optional() private cacheDuration: number = CACHE_DURATION + private cacheDuration: number = CACHE_DURATION, ) { disposableRegistry.push(this); this.changeEventEmitter = new EventEmitter(); @@ -40,7 +45,7 @@ export class EnvironmentVariablesProvider implements IEnvironmentVariablesProvid return this.changeEventEmitter.event; } - public dispose() { + public dispose(): void { this.changeEventEmitter.dispose(); this.fileWatchers.forEach((watcher) => { if (watcher) { @@ -50,23 +55,56 @@ export class EnvironmentVariablesProvider implements IEnvironmentVariablesProvid } public async getEnvironmentVariables(resource?: Uri): Promise { - // Cache resource specific interpreter data - const cacheStore = new InMemoryInterpreterSpecificCache( - 'getEnvironmentVariables', - this.cacheDuration, - [resource], - this.serviceContainer - ); - if (cacheStore.hasData) { - traceVerbose(`Cached data exists getEnvironmentVariables, ${resource ? resource.fsPath : ''}`); - return Promise.resolve(cacheStore.data) as Promise; + const cached = this.getCachedEnvironmentVariables(resource); + if (cached) { + return cached; + } + const vars = await this._getEnvironmentVariables(resource); + this.setCachedEnvironmentVariables(resource, vars); + traceVerbose('Dump environment variables', JSON.stringify(vars, null, 4)); + return vars; + } + + public getEnvironmentVariablesSync(resource?: Uri): EnvironmentVariables { + const cached = this.getCachedEnvironmentVariables(resource); + if (cached) { + return cached; } - const promise = this._getEnvironmentVariables(resource); - promise.then((result) => (cacheStore.data = result)).ignoreErrors(); - return promise; + const vars = this._getEnvironmentVariablesSync(resource); + this.setCachedEnvironmentVariables(resource, vars); + return vars; } + + private getCachedEnvironmentVariables(resource?: Uri): EnvironmentVariables | undefined { + const cacheKey = this.getWorkspaceFolderUri(resource)?.fsPath ?? ''; + const cache = this.envVarCaches.get(cacheKey); + if (cache) { + const cachedData = cache.data; + if (cachedData) { + return { ...cachedData }; + } + } + return undefined; + } + + private setCachedEnvironmentVariables(resource: Uri | undefined, vars: EnvironmentVariables): void { + const cacheKey = this.getWorkspaceFolderUri(resource)?.fsPath ?? ''; + const cache = new InMemoryCache(this.cacheDuration); + this.envVarCaches.set(cacheKey, cache); + cache.data = { ...vars }; + } + public async _getEnvironmentVariables(resource?: Uri): Promise { - let mergedVars = await this.getCustomEnvironmentVariables(resource); + const customVars = await this.getCustomEnvironmentVariables(resource); + return this.getMergedEnvironmentVariables(customVars); + } + + public _getEnvironmentVariablesSync(resource?: Uri): EnvironmentVariables { + const customVars = this.getCustomEnvironmentVariablesSync(resource); + return this.getMergedEnvironmentVariables(customVars); + } + + private getMergedEnvironmentVariables(mergedVars?: EnvironmentVariables): EnvironmentVariables { if (!mergedVars) { mergedVars = {}; } @@ -81,14 +119,34 @@ export class EnvironmentVariablesProvider implements IEnvironmentVariablesProvid } return mergedVars; } + public async getCustomEnvironmentVariables(resource?: Uri): Promise { - const settings = this.configurationService.getSettings(resource); + return this.envVarsService.parseFile(this.getEnvFile(resource), this.process.env); + } + + private getCustomEnvironmentVariablesSync(resource?: Uri): EnvironmentVariables | undefined { + return this.envVarsService.parseFileSync(this.getEnvFile(resource), this.process.env); + } + + private getEnvFile(resource?: Uri): string { + const systemVariables: SystemVariables = new SystemVariables( + undefined, + PythonSettings.getSettingsUriAndTarget(resource, this.workspaceService).uri?.fsPath, + this.workspaceService, + ); const workspaceFolderUri = this.getWorkspaceFolderUri(resource); + const envFileSetting = this.workspaceService.getConfiguration('python', resource).get('envFile'); + const envFile = systemVariables.resolveAny(envFileSetting); + if (envFile === undefined) { + traceError('Unable to read `python.envFile` setting for resource', JSON.stringify(resource)); + return workspaceFolderUri?.fsPath ? path.join(workspaceFolderUri?.fsPath, '.env') : ''; + } this.trackedWorkspaceFolders.add(workspaceFolderUri ? workspaceFolderUri.fsPath : ''); - this.createFileWatcher(settings.envFile, workspaceFolderUri); - return this.envVarsService.parseFile(settings.envFile, this.process.env); + this.createFileWatcher(envFile, workspaceFolderUri); + return envFile; } - public configurationChanged(e: ConfigurationChangeEvent) { + + public configurationChanged(e: ConfigurationChangeEvent): void { this.trackedWorkspaceFolders.forEach((item) => { const uri = item && item.length > 0 ? Uri.file(item) : undefined; if (e.affectsConfiguration('python.envFile', uri)) { @@ -96,7 +154,8 @@ export class EnvironmentVariablesProvider implements IEnvironmentVariablesProvid } }); } - public createFileWatcher(envFile: string, workspaceFolderUri?: Uri) { + + public createFileWatcher(envFile: string, workspaceFolderUri?: Uri): void { if (this.fileWatchers.has(envFile)) { return; } @@ -108,9 +167,10 @@ export class EnvironmentVariablesProvider implements IEnvironmentVariablesProvid this.disposables.push(envFileWatcher.onDidDelete(() => this.onEnvironmentFileChanged(workspaceFolderUri))); } } + private getWorkspaceFolderUri(resource?: Uri): Uri | undefined { if (!resource) { - return; + return undefined; } const workspaceFolder = this.workspaceService.getWorkspaceFolder(resource!); return workspaceFolder ? workspaceFolder.uri : undefined; @@ -122,16 +182,8 @@ export class EnvironmentVariablesProvider implements IEnvironmentVariablesProvid } private onEnvironmentFileChanged(workspaceFolderUri?: Uri) { - clearCachedResourceSpecificIngterpreterData( - 'getEnvironmentVariables', - workspaceFolderUri, - this.serviceContainer - ); - clearCachedResourceSpecificIngterpreterData( - 'CustomEnvironmentVariables', - workspaceFolderUri, - this.serviceContainer - ); + // An environment file changing can affect multiple workspaces; clear everything and reparse later. + this.envVarCaches.clear(); this.changeEventEmitter.fire(workspaceFolderUri); } } diff --git a/src/client/common/variables/serviceRegistry.ts b/src/client/common/variables/serviceRegistry.ts index 3a98c3cbfea4..db4f620ab6a7 100644 --- a/src/client/common/variables/serviceRegistry.ts +++ b/src/client/common/variables/serviceRegistry.ts @@ -9,10 +9,10 @@ import { IEnvironmentVariablesProvider, IEnvironmentVariablesService } from './t export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton( IEnvironmentVariablesService, - EnvironmentVariablesService + EnvironmentVariablesService, ); serviceManager.addSingleton( IEnvironmentVariablesProvider, - EnvironmentVariablesProvider + EnvironmentVariablesProvider, ); } diff --git a/src/client/common/variables/sysTypes.ts b/src/client/common/variables/sysTypes.ts deleted file mode 100644 index 10bd2b776b17..000000000000 --- a/src/client/common/variables/sysTypes.ts +++ /dev/null @@ -1,39 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -// tslint:disable:no-any no-increment-decrement - -import { isFunction, isString } from '../utils/sysTypes'; - -export type TypeConstraint = string | Function; - -export function validateConstraints(args: any[], constraints: TypeConstraint[]): void { - const len = Math.min(args.length, constraints.length); - for (let i = 0; i < len; i++) { - validateConstraint(args[i], constraints[i]); - } -} - -export function validateConstraint(arg: any, constraint: TypeConstraint): void { - if (isString(constraint)) { - if (typeof arg !== constraint) { - throw new Error(`argument does not match constraint: typeof ${constraint}`); - } - } else if (isFunction(constraint)) { - if (arg instanceof constraint) { - return; - } - if (arg && arg.constructor === constraint) { - return; - } - if (constraint.length === 1 && constraint.call(undefined, arg) === true) { - return; - } - throw new Error( - 'argument does not match one of these constraints: arg instanceof constraint, arg.constructor === constraint, nor constraint(arg) === true' - ); - } -} diff --git a/src/client/common/variables/systemVariables.ts b/src/client/common/variables/systemVariables.ts index 74b307dafb78..05e5d9d6f584 100644 --- a/src/client/common/variables/systemVariables.ts +++ b/src/client/common/variables/systemVariables.ts @@ -7,18 +7,17 @@ import * as Path from 'path'; import { Range, Uri } from 'vscode'; import { IDocumentManager, IWorkspaceService } from '../application/types'; +import { WorkspaceService } from '../application/workspace'; import * as Types from '../utils/sysTypes'; import { IStringDictionary, ISystemVariables } from './types'; -/* tslint:disable:rule1 no-any no-unnecessary-callback-wrapper jsdoc-format no-for-in prefer-const no-increment-decrement */ - abstract class AbstractSystemVariables implements ISystemVariables { public resolve(value: string): string; public resolve(value: string[]): string[]; public resolve(value: IStringDictionary): IStringDictionary; public resolve(value: IStringDictionary): IStringDictionary; public resolve(value: IStringDictionary>): IStringDictionary>; - // tslint:disable-next-line:no-any + public resolve(value: any): any { if (Types.isString(value)) { return this.__resolveString(value); @@ -32,7 +31,7 @@ abstract class AbstractSystemVariables implements ISystemVariables { } public resolveAny(value: T): T; - // tslint:disable-next-line:no-any + public resolveAny(value: any): any { if (Types.isString(value)) { return this.__resolveString(value); @@ -48,7 +47,6 @@ abstract class AbstractSystemVariables implements ISystemVariables { private __resolveString(value: string): string { const regexp = /\$\{(.*?)\}/g; return value.replace(regexp, (match: string, name: string) => { - // tslint:disable-next-line:no-any const newValue = (this)[name]; if (Types.isString(newValue)) { return newValue; @@ -59,24 +57,24 @@ abstract class AbstractSystemVariables implements ISystemVariables { } private __resolveLiteral( - values: IStringDictionary | string[]> + values: IStringDictionary | string[]>, ): IStringDictionary | string[]> { const result: IStringDictionary | string[]> = Object.create(null); Object.keys(values).forEach((key) => { const value = values[key]; - // tslint:disable-next-line:no-any + result[key] = this.resolve(value); }); return result; } private __resolveAnyLiteral(values: T): T; - // tslint:disable-next-line:no-any + private __resolveAnyLiteral(values: any): any { const result: IStringDictionary | string[]> = Object.create(null); Object.keys(values).forEach((key) => { const value = values[key]; - // tslint:disable-next-line:no-any + result[key] = this.resolveAny(value); }); return result; @@ -87,7 +85,7 @@ abstract class AbstractSystemVariables implements ISystemVariables { } private __resolveAnyArray(value: T[]): T[]; - // tslint:disable-next-line:no-any + private __resolveAnyArray(value: any[]): any[] { return value.map((s) => this.resolveAny(s)); } @@ -105,7 +103,7 @@ export class SystemVariables extends AbstractSystemVariables { file: Uri | undefined, rootFolder: string | undefined, workspace?: IWorkspaceService, - documentManager?: IDocumentManager + documentManager?: IDocumentManager, ) { super(); const workspaceFolder = workspace && file ? workspace.getWorkspaceFolder(file) : undefined; @@ -117,8 +115,8 @@ export class SystemVariables extends AbstractSystemVariables { this._selectedText = documentManager.activeTextEditor.document.getText( new Range( documentManager.activeTextEditor.selection.start, - documentManager.activeTextEditor.selection.end - ) + documentManager.activeTextEditor.selection.end, + ), ); } this._execPath = process.execPath; @@ -128,6 +126,18 @@ export class SystemVariables extends AbstractSystemVariables { string | undefined >)[`env.${key}`] = process.env[key]; }); + workspace = workspace ?? new WorkspaceService(); + try { + workspace.workspaceFolders?.forEach((folder) => { + const basename = Path.basename(folder.uri.fsPath); + ((this as any) as Record)[`workspaceFolder:${basename}`] = + folder.uri.fsPath; + ((this as any) as Record)[`workspaceFolder:${folder.name}`] = + folder.uri.fsPath; + }); + } catch { + // This try...catch block is here to support pre-existing tests, ignore error. + } } public get cwd(): string { diff --git a/src/client/common/variables/types.ts b/src/client/common/variables/types.ts index c1b844451021..252a0d48038f 100644 --- a/src/client/common/variables/types.ts +++ b/src/client/common/variables/types.ts @@ -9,7 +9,12 @@ export const IEnvironmentVariablesService = Symbol('IEnvironmentVariablesService export interface IEnvironmentVariablesService { parseFile(filePath?: string, baseVars?: EnvironmentVariables): Promise; - mergeVariables(source: EnvironmentVariables, target: EnvironmentVariables): void; + parseFileSync(filePath?: string, baseVars?: EnvironmentVariables): EnvironmentVariables | undefined; + mergeVariables( + source: EnvironmentVariables, + target: EnvironmentVariables, + options?: { overwrite?: boolean; mergeAll?: boolean }, + ): void; appendPythonPath(vars: EnvironmentVariables, ...pythonPaths: string[]): void; appendPath(vars: EnvironmentVariables, ...paths: string[]): void; } @@ -29,7 +34,7 @@ export interface ISystemVariables { resolve(value: IStringDictionary): IStringDictionary; resolve(value: IStringDictionary>): IStringDictionary>; resolveAny(value: T): T; - // tslint:disable-next-line:no-any + [key: string]: any; } @@ -38,5 +43,5 @@ export const IEnvironmentVariablesProvider = Symbol('IEnvironmentVariablesProvid export interface IEnvironmentVariablesProvider { onDidEnvironmentVariablesChange: Event; getEnvironmentVariables(resource?: Uri): Promise; - getCustomEnvironmentVariables(resource?: Uri): Promise; + getEnvironmentVariablesSync(resource?: Uri): EnvironmentVariables; } diff --git a/src/client/common/vscodeApis/browserApis.ts b/src/client/common/vscodeApis/browserApis.ts new file mode 100644 index 000000000000..ccf51bd07ec8 --- /dev/null +++ b/src/client/common/vscodeApis/browserApis.ts @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { env, Uri } from 'vscode'; + +export function launch(url: string): void { + env.openExternal(Uri.parse(url)); +} diff --git a/src/client/common/vscodeApis/commandApis.ts b/src/client/common/vscodeApis/commandApis.ts new file mode 100644 index 000000000000..908cb761c538 --- /dev/null +++ b/src/client/common/vscodeApis/commandApis.ts @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { commands, Disposable } from 'vscode'; + +/** + * Wrapper for vscode.commands.executeCommand to make it easier to mock in tests + */ +export function executeCommand(command: string, ...rest: any[]): Thenable { + return commands.executeCommand(command, ...rest); +} + +/** + * Wrapper for vscode.commands.registerCommand to make it easier to mock in tests + */ +export function registerCommand(command: string, callback: (...args: any[]) => any, thisArg?: any): Disposable { + return commands.registerCommand(command, callback, thisArg); +} diff --git a/src/client/common/vscodeApis/extensionsApi.ts b/src/client/common/vscodeApis/extensionsApi.ts new file mode 100644 index 000000000000..f099d6f636b0 --- /dev/null +++ b/src/client/common/vscodeApis/extensionsApi.ts @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import * as vscode from 'vscode'; +import * as fs from '../platform/fs-paths'; +import { PVSC_EXTENSION_ID } from '../constants'; + +export function getExtension(extensionId: string): vscode.Extension | undefined { + return vscode.extensions.getExtension(extensionId); +} + +export function isExtensionEnabled(extensionId: string): boolean { + return vscode.extensions.getExtension(extensionId) !== undefined; +} + +export function isExtensionDisabled(extensionId: string): boolean { + // We need an enabled extension to find the extensions dir. + const pythonExt = getExtension(PVSC_EXTENSION_ID); + if (pythonExt) { + let found = false; + fs.readdirSync(path.dirname(pythonExt.extensionPath), { withFileTypes: false }).forEach((s) => { + if (s.toString().startsWith(extensionId)) { + found = true; + } + }); + return found; + } + return false; +} + +export function isInsider(): boolean { + return vscode.env.appName.includes('Insider'); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function getExtensions(): readonly vscode.Extension[] { + return vscode.extensions.all; +} diff --git a/src/client/common/vscodeApis/languageApis.ts b/src/client/common/vscodeApis/languageApis.ts new file mode 100644 index 000000000000..87681507693d --- /dev/null +++ b/src/client/common/vscodeApis/languageApis.ts @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License + +import { DiagnosticChangeEvent, DiagnosticCollection, Disposable, languages } from 'vscode'; + +export function createDiagnosticCollection(name: string): DiagnosticCollection { + return languages.createDiagnosticCollection(name); +} + +export function onDidChangeDiagnostics(handler: (e: DiagnosticChangeEvent) => void): Disposable { + return languages.onDidChangeDiagnostics(handler); +} diff --git a/src/client/common/vscodeApis/windowApis.ts b/src/client/common/vscodeApis/windowApis.ts new file mode 100644 index 000000000000..90a06e7ed75a --- /dev/null +++ b/src/client/common/vscodeApis/windowApis.ts @@ -0,0 +1,280 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable max-classes-per-file */ + +import { + CancellationToken, + MessageItem, + MessageOptions, + Progress, + ProgressOptions, + QuickPick, + QuickInputButtons, + QuickPickItem, + QuickPickOptions, + TextEditor, + window, + Disposable, + QuickPickItemButtonEvent, + Uri, + TerminalShellExecutionStartEvent, + LogOutputChannel, + OutputChannel, + TerminalLinkProvider, + NotebookDocument, + NotebookEditor, + NotebookDocumentShowOptions, + Terminal, +} from 'vscode'; +import { createDeferred, Deferred } from '../utils/async'; +import { Resource } from '../types'; +import { getWorkspaceFolders } from './workspaceApis'; + +export function showTextDocument(uri: Uri): Thenable { + return window.showTextDocument(uri); +} + +export function showNotebookDocument( + document: NotebookDocument, + options?: NotebookDocumentShowOptions, +): Thenable { + return window.showNotebookDocument(document, options); +} + +export function showQuickPick( + items: readonly T[] | Thenable, + options?: QuickPickOptions, + token?: CancellationToken, +): Thenable { + return window.showQuickPick(items, options, token); +} + +export function createQuickPick(): QuickPick { + return window.createQuickPick(); +} + +export function showErrorMessage(message: string, ...items: T[]): Thenable; +export function showErrorMessage( + message: string, + options: MessageOptions, + ...items: T[] +): Thenable; +export function showErrorMessage(message: string, ...items: T[]): Thenable; +export function showErrorMessage( + message: string, + options: MessageOptions, + ...items: T[] +): Thenable; + +export function showErrorMessage(message: string, ...items: any[]): Thenable { + return window.showErrorMessage(message, ...items); +} + +export function showWarningMessage(message: string, ...items: T[]): Thenable; +export function showWarningMessage( + message: string, + options: MessageOptions, + ...items: T[] +): Thenable; +export function showWarningMessage(message: string, ...items: T[]): Thenable; +export function showWarningMessage( + message: string, + options: MessageOptions, + ...items: T[] +): Thenable; + +export function showWarningMessage(message: string, ...items: any[]): Thenable { + return window.showWarningMessage(message, ...items); +} + +export function showInformationMessage(message: string, ...items: T[]): Thenable; +export function showInformationMessage( + message: string, + options: MessageOptions, + ...items: T[] +): Thenable; +export function showInformationMessage(message: string, ...items: T[]): Thenable; +export function showInformationMessage( + message: string, + options: MessageOptions, + ...items: T[] +): Thenable; + +export function showInformationMessage(message: string, ...items: any[]): Thenable { + return window.showInformationMessage(message, ...items); +} + +export function withProgress( + options: ProgressOptions, + task: (progress: Progress<{ message?: string; increment?: number }>, token: CancellationToken) => Thenable, +): Thenable { + return window.withProgress(options, task); +} + +export function getActiveTextEditor(): TextEditor | undefined { + const { activeTextEditor } = window; + return activeTextEditor; +} + +export function onDidChangeActiveTextEditor(handler: (e: TextEditor | undefined) => void): Disposable { + return window.onDidChangeActiveTextEditor(handler); +} + +export function onDidStartTerminalShellExecution(handler: (e: TerminalShellExecutionStartEvent) => void): Disposable { + return window.onDidStartTerminalShellExecution(handler); +} + +export function onDidChangeTerminalState(handler: (e: Terminal) => void): Disposable { + return window.onDidChangeTerminalState(handler); +} + +export enum MultiStepAction { + Back = 'Back', + Cancel = 'Cancel', + Continue = 'Continue', +} + +export async function showQuickPickWithBack( + items: readonly T[], + options?: QuickPickOptions, + token?: CancellationToken, + itemButtonHandler?: (e: QuickPickItemButtonEvent) => void, +): Promise { + const quickPick: QuickPick = window.createQuickPick(); + const disposables: Disposable[] = [quickPick]; + + quickPick.items = items; + quickPick.buttons = [QuickInputButtons.Back]; + quickPick.canSelectMany = options?.canPickMany ?? false; + quickPick.ignoreFocusOut = options?.ignoreFocusOut ?? false; + quickPick.matchOnDescription = options?.matchOnDescription ?? false; + quickPick.matchOnDetail = options?.matchOnDetail ?? false; + quickPick.placeholder = options?.placeHolder; + quickPick.title = options?.title; + + const deferred = createDeferred(); + + disposables.push( + quickPick, + quickPick.onDidTriggerButton((item) => { + if (item === QuickInputButtons.Back) { + deferred.reject(MultiStepAction.Back); + quickPick.hide(); + } + }), + quickPick.onDidAccept(() => { + if (!deferred.completed) { + if (quickPick.canSelectMany) { + deferred.resolve(quickPick.selectedItems.map((item) => item)); + } else { + deferred.resolve(quickPick.selectedItems[0]); + } + + quickPick.hide(); + } + }), + quickPick.onDidHide(() => { + if (!deferred.completed) { + deferred.resolve(undefined); + } + }), + quickPick.onDidTriggerItemButton((e) => { + if (itemButtonHandler) { + itemButtonHandler(e); + } + }), + ); + if (token) { + disposables.push( + token.onCancellationRequested(() => { + quickPick.hide(); + }), + ); + } + quickPick.show(); + + try { + return await deferred.promise; + } finally { + disposables.forEach((d) => d.dispose()); + } +} + +export class MultiStepNode { + constructor( + public previous: MultiStepNode | undefined, + public readonly current: (context?: MultiStepAction) => Promise, + public next: MultiStepNode | undefined, + ) {} + + public static async run(step: MultiStepNode, context?: MultiStepAction): Promise { + let nextStep: MultiStepNode | undefined = step; + let flowAction = await nextStep.current(context); + while (nextStep !== undefined) { + if (flowAction === MultiStepAction.Cancel) { + return flowAction; + } + if (flowAction === MultiStepAction.Back) { + nextStep = nextStep?.previous; + } + if (flowAction === MultiStepAction.Continue) { + nextStep = nextStep?.next; + } + + if (nextStep) { + flowAction = await nextStep?.current(flowAction); + } + } + + return flowAction; + } +} + +export function createStepBackEndNode(deferred?: Deferred): MultiStepNode { + return new MultiStepNode( + undefined, + async () => { + if (deferred) { + // This is to ensure we don't leave behind any pending promises. + deferred.reject(MultiStepAction.Back); + } + return Promise.resolve(MultiStepAction.Back); + }, + undefined, + ); +} + +export function createStepForwardEndNode(deferred?: Deferred, result?: T): MultiStepNode { + return new MultiStepNode( + undefined, + async () => { + if (deferred) { + // This is to ensure we don't leave behind any pending promises. + deferred.resolve(result); + } + return Promise.resolve(MultiStepAction.Back); + }, + undefined, + ); +} + +export function getActiveResource(): Resource { + const editor = window.activeTextEditor; + if (editor && !editor.document.isUntitled) { + return editor.document.uri; + } + const workspaces = getWorkspaceFolders(); + return Array.isArray(workspaces) && workspaces.length > 0 ? workspaces[0].uri : undefined; +} + +export function createOutputChannel(name: string, languageId?: string): OutputChannel { + return window.createOutputChannel(name, languageId); +} +export function createLogOutputChannel(name: string, options: { log: true }): LogOutputChannel { + return window.createOutputChannel(name, options); +} + +export function registerTerminalLinkProvider(provider: TerminalLinkProvider): Disposable { + return window.registerTerminalLinkProvider(provider); +} diff --git a/src/client/common/vscodeApis/workspaceApis.ts b/src/client/common/vscodeApis/workspaceApis.ts new file mode 100644 index 000000000000..cd45f655702d --- /dev/null +++ b/src/client/common/vscodeApis/workspaceApis.ts @@ -0,0 +1,116 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as vscode from 'vscode'; +import { Resource } from '../types'; + +export function getWorkspaceFolders(): readonly vscode.WorkspaceFolder[] | undefined { + return vscode.workspace.workspaceFolders; +} + +export function getWorkspaceFolder(uri: Resource): vscode.WorkspaceFolder | undefined { + return uri ? vscode.workspace.getWorkspaceFolder(uri) : undefined; +} + +export function getWorkspaceFolderPaths(): string[] { + return vscode.workspace.workspaceFolders?.map((w) => w.uri.fsPath) ?? []; +} + +export function getConfiguration( + section?: string, + scope?: vscode.ConfigurationScope | null, +): vscode.WorkspaceConfiguration { + return vscode.workspace.getConfiguration(section, scope); +} + +export function applyEdit(edit: vscode.WorkspaceEdit): Thenable { + return vscode.workspace.applyEdit(edit); +} + +export function findFiles( + include: vscode.GlobPattern, + exclude?: vscode.GlobPattern | null, + maxResults?: number, + token?: vscode.CancellationToken, +): Thenable { + return vscode.workspace.findFiles(include, exclude, maxResults, token); +} + +export function onDidCloseTextDocument(handler: (e: vscode.TextDocument) => void): vscode.Disposable { + return vscode.workspace.onDidCloseTextDocument(handler); +} + +export function onDidSaveTextDocument(handler: (e: vscode.TextDocument) => void): vscode.Disposable { + return vscode.workspace.onDidSaveTextDocument(handler); +} + +export function getOpenTextDocuments(): readonly vscode.TextDocument[] { + return vscode.workspace.textDocuments; +} + +export function onDidOpenTextDocument(handler: (doc: vscode.TextDocument) => void): vscode.Disposable { + return vscode.workspace.onDidOpenTextDocument(handler); +} + +export function onDidChangeTextDocument(handler: (e: vscode.TextDocumentChangeEvent) => void): vscode.Disposable { + return vscode.workspace.onDidChangeTextDocument(handler); +} + +export function onDidChangeConfiguration(handler: (e: vscode.ConfigurationChangeEvent) => void): vscode.Disposable { + return vscode.workspace.onDidChangeConfiguration(handler); +} + +export function onDidCloseNotebookDocument(handler: (e: vscode.NotebookDocument) => void): vscode.Disposable { + return vscode.workspace.onDidCloseNotebookDocument(handler); +} + +export function createFileSystemWatcher( + globPattern: vscode.GlobPattern, + ignoreCreateEvents?: boolean, + ignoreChangeEvents?: boolean, + ignoreDeleteEvents?: boolean, +): vscode.FileSystemWatcher { + return vscode.workspace.createFileSystemWatcher( + globPattern, + ignoreCreateEvents, + ignoreChangeEvents, + ignoreDeleteEvents, + ); +} + +export function onDidChangeWorkspaceFolders( + handler: (e: vscode.WorkspaceFoldersChangeEvent) => void, +): vscode.Disposable { + return vscode.workspace.onDidChangeWorkspaceFolders(handler); +} + +export function isVirtualWorkspace(): boolean { + const isVirtualWorkspace = + vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.every((f) => f.uri.scheme !== 'file'); + return !!isVirtualWorkspace; +} + +export function isTrusted(): boolean { + return vscode.workspace.isTrusted; +} + +export function onDidGrantWorkspaceTrust(handler: () => void): vscode.Disposable { + return vscode.workspace.onDidGrantWorkspaceTrust(handler); +} + +export function createDirectory(uri: vscode.Uri): Thenable { + return vscode.workspace.fs.createDirectory(uri); +} + +export function openNotebookDocument(uri: vscode.Uri): Thenable; +export function openNotebookDocument( + notebookType: string, + content?: vscode.NotebookData, +): Thenable; +export function openNotebookDocument(notebook: any, content?: vscode.NotebookData): Thenable { + return vscode.workspace.openNotebookDocument(notebook, content); +} + +export function copy(source: vscode.Uri, dest: vscode.Uri, options?: { overwrite?: boolean }): Thenable { + return vscode.workspace.fs.copy(source, dest, options); +} diff --git a/src/client/components.ts b/src/client/components.ts new file mode 100644 index 000000000000..f06f69eaac35 --- /dev/null +++ b/src/client/components.ts @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { IDisposableRegistry, IExtensionContext } from './common/types'; +import { IServiceContainer, IServiceManager } from './ioc/types'; + +/** + * The global extension state needed by components. + * + */ +export type ExtensionState = { + context: IExtensionContext; + disposables: IDisposableRegistry; + // For now we include the objects dealing with inversify (IOC) + // registration. These will be removed later. + legacyIOC: { + serviceManager: IServiceManager; + serviceContainer: IServiceContainer; + }; +}; + +/** + * The result of activating a component of the extension. + * + * Getting this value means the component has reached a state where it + * may be used by the rest of the extension. + * + * If the component started any non-critical activation-related + * operations during activation then the "fullyReady" property will only + * resolve once all those operations complete. + * + * The component may have also started long-running background helpers. + * Those are not exposed here. + */ +export type ActivationResult = { + fullyReady: Promise; +}; diff --git a/src/client/constants.ts b/src/client/constants.ts index 55e22eb0e4e3..48c5f55e5ce4 100644 --- a/src/client/constants.ts +++ b/src/client/constants.ts @@ -11,7 +11,4 @@ const folderName = path.basename(__dirname); export const EXTENSION_ROOT_DIR = folderName === 'client' ? path.join(__dirname, '..', '..') : path.join(__dirname, '..', '..', '..', '..'); -export const HiddenFileFormatString = '_HiddenFile_{0}.py'; export const HiddenFilePrefix = '_HiddenFile_'; - -export const MillisecondsInADay = 24 * 60 * 60 * 1_000; diff --git a/src/client/datascience/activation.ts b/src/client/datascience/activation.ts deleted file mode 100644 index ccf319aa9b5c..000000000000 --- a/src/client/datascience/activation.ts +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IExtensionSingleActivationService } from '../activation/types'; -import '../common/extensions'; -import { IPythonDaemonExecutionService, IPythonExecutionFactory } from '../common/process/types'; -import { IDisposableRegistry } from '../common/types'; -import { debounceAsync, swallowExceptions } from '../common/utils/decorators'; -import { sendTelemetryEvent, setSharedProperty } from '../telemetry'; -import { JupyterDaemonModule, Telemetry } from './constants'; -import { ActiveEditorContextService } from './context/activeEditorContext'; -import { JupyterInterpreterService } from './jupyter/interpreter/jupyterInterpreterService'; -import { KernelDaemonPreWarmer } from './kernel-launcher/kernelDaemonPreWarmer'; -import { INotebookAndInteractiveWindowUsageTracker, INotebookEditor, INotebookEditorProvider } from './types'; - -@injectable() -export class Activation implements IExtensionSingleActivationService { - private notebookOpened = false; - constructor( - @inject(INotebookEditorProvider) private readonly notebookEditorProvider: INotebookEditorProvider, - @inject(JupyterInterpreterService) private readonly jupyterInterpreterService: JupyterInterpreterService, - @inject(IPythonExecutionFactory) private readonly factory: IPythonExecutionFactory, - @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry, - @inject(ActiveEditorContextService) private readonly contextService: ActiveEditorContextService, - @inject(KernelDaemonPreWarmer) private readonly daemonPoolPrewarmer: KernelDaemonPreWarmer, - @inject(INotebookAndInteractiveWindowUsageTracker) - private readonly tracker: INotebookAndInteractiveWindowUsageTracker - ) {} - public async activate(): Promise { - this.disposables.push(this.notebookEditorProvider.onDidOpenNotebookEditor(this.onDidOpenNotebookEditor, this)); - this.disposables.push(this.jupyterInterpreterService.onDidChangeInterpreter(this.onDidChangeInterpreter, this)); - this.contextService.activate().ignoreErrors(); - this.daemonPoolPrewarmer.activate(undefined).ignoreErrors(); - this.tracker.startTracking(); - } - - private onDidOpenNotebookEditor(e: INotebookEditor) { - this.notebookOpened = true; - this.PreWarmDaemonPool().ignoreErrors(); - setSharedProperty('ds_notebookeditor', e.type); - sendTelemetryEvent(Telemetry.OpenNotebookAll); - // Warm up our selected interpreter for the extension - this.jupyterInterpreterService.setInitialInterpreter().ignoreErrors(); - } - - private onDidChangeInterpreter() { - if (this.notebookOpened) { - // Warm up our selected interpreter for the extension - this.jupyterInterpreterService.setInitialInterpreter().ignoreErrors(); - this.PreWarmDaemonPool().ignoreErrors(); - } - } - - @debounceAsync(500) - @swallowExceptions('Failed to pre-warm daemon pool') - private async PreWarmDaemonPool() { - const interpreter = await this.jupyterInterpreterService.getSelectedInterpreter(); - if (!interpreter) { - return; - } - await this.factory.createDaemon({ - daemonModule: JupyterDaemonModule, - pythonPath: interpreter.path - }); - } -} diff --git a/src/client/datascience/api/jupyterIntegration.ts b/src/client/datascience/api/jupyterIntegration.ts deleted file mode 100644 index 98c154fe1706..000000000000 --- a/src/client/datascience/api/jupyterIntegration.ts +++ /dev/null @@ -1,142 +0,0 @@ -// tslint:disable-next-line: no-single-line-block-comment -/* eslint-disable comma-dangle */ -// tslint:disable-next-line: no-single-line-block-comment -/* eslint-disable implicit-arrow-linebreak */ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -import { dirname } from 'path'; -import { CancellationToken, Disposable, Event, Uri } from 'vscode'; -import * as lsp from 'vscode-languageserver-protocol'; -import { ILanguageServerCache, ILanguageServerConnection } from '../../activation/types'; -import { InterpreterUri } from '../../common/installer/types'; -import { IExtensions, IInstaller, InstallerResponse, Product, Resource } from '../../common/types'; -import { isResource } from '../../common/utils/misc'; -import { getDebugpyPackagePath } from '../../debugger/extension/adapter/remoteLaunchers'; -import { IEnvironmentActivationService } from '../../interpreter/activation/types'; -import { IInterpreterQuickPickItem, IInterpreterSelector } from '../../interpreter/configuration/types'; -import { IInterpreterService } from '../../interpreter/contracts'; -import { IWindowsStoreInterpreter } from '../../interpreter/locators/types'; -import { WindowsStoreInterpreter } from '../../pythonEnvironments/discovery/locators/services/windowsStoreInterpreter'; -import { PythonEnvironment } from '../../pythonEnvironments/info'; - -export interface ILanguageServer extends Disposable { - readonly connection: ILanguageServerConnection; - readonly capabilities: lsp.ServerCapabilities; -} - -type PythonApiForJupyterExtension = { - /** - * IInterpreterService - */ - onDidChangeInterpreter: Event; - /** - * IInterpreterService - */ - getInterpreters(resource?: Uri): Promise; - /** - * IInterpreterService - */ - getActiveInterpreter(resource?: Uri): Promise; - /** - * IInterpreterService - */ - getInterpreterDetails(pythonPath: string, resource?: Uri): Promise; - - /** - * IEnvironmentActivationService - */ - getActivatedEnvironmentVariables( - resource: Resource, - interpreter?: PythonEnvironment, - allowExceptions?: boolean - ): Promise; - isWindowsStoreInterpreter(pythonPath: string): Promise; - /** - * IWindowsStoreInterpreter - */ - getSuggestions(resource: Resource): Promise; - /** - * IInstaller - */ - install(product: Product, resource?: InterpreterUri, cancel?: CancellationToken): Promise; - /** - * Returns path to where `debugpy` is. In python extension this is `/pythonFiles/lib/python`. - */ - getDebuggerPath(): Promise; - /** - * Returns a ILanguageServer that can be used for communicating with a language server process. - * @param resource file that determines which connection to return - */ - getLanguageServer(resource?: InterpreterUri): Promise; -}; - -export type JupyterExtensionApi = { - registerPythonApi(interpreterService: PythonApiForJupyterExtension): void; -}; - -@injectable() -export class JupyterExtensionIntegration { - constructor( - @inject(IExtensions) private readonly extensions: IExtensions, - @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, - @inject(IInterpreterSelector) private readonly interpreterSelector: IInterpreterSelector, - @inject(WindowsStoreInterpreter) private readonly windowsStoreInterpreter: IWindowsStoreInterpreter, - @inject(IInstaller) private readonly installer: IInstaller, - @inject(IEnvironmentActivationService) private readonly envActivation: IEnvironmentActivationService, - @inject(ILanguageServerCache) private readonly languageServerCache: ILanguageServerCache - ) {} - - public registerApi(jupyterExtensionApi: JupyterExtensionApi) { - jupyterExtensionApi.registerPythonApi({ - onDidChangeInterpreter: this.interpreterService.onDidChangeInterpreter, - getActiveInterpreter: async (resource?: Uri) => this.interpreterService.getActiveInterpreter(resource), - getInterpreterDetails: async (pythonPath: string) => - this.interpreterService.getInterpreterDetails(pythonPath), - getInterpreters: async (resource: Uri | undefined) => this.interpreterService.getInterpreters(resource), - getActivatedEnvironmentVariables: async ( - resource: Resource, - interpreter?: PythonEnvironment, - allowExceptions?: boolean - ) => this.envActivation.getActivatedEnvironmentVariables(resource, interpreter, allowExceptions), - isWindowsStoreInterpreter: async (pythonPath: string): Promise => - this.windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath), - getSuggestions: async (resource: Resource): Promise => - this.interpreterSelector.getSuggestions(resource), - install: async ( - product: Product, - resource?: InterpreterUri, - cancel?: CancellationToken - ): Promise => this.installer.install(product, resource, cancel), - getDebuggerPath: async () => dirname(getDebugpyPackagePath()), - getLanguageServer: async (r) => { - const resource = isResource(r) ? r : undefined; - const interpreter = !isResource(r) ? r : undefined; - const client = await this.languageServerCache.get(resource, interpreter); - - // Some langauge servers don't support the connection yet. (like Jedi until we switch to LSP) - if (client && client.connection && client.capabilities) { - return { - connection: client.connection, - capabilities: client.capabilities, - dispose: client.dispose - }; - } - return undefined; - } - }); - } - - public async integrateWithJupyterExtension(): Promise { - const jupyterExtension = this.extensions.getExtension('ms-ai-tools.jupyter'); - if (!jupyterExtension) { - return; - } - await jupyterExtension.activate(); - if (!jupyterExtension.isActive) { - return; - } - this.registerApi(jupyterExtension.exports); - } -} diff --git a/src/client/datascience/baseJupyterSession.ts b/src/client/datascience/baseJupyterSession.ts deleted file mode 100644 index 8c938fbf8db1..000000000000 --- a/src/client/datascience/baseJupyterSession.ts +++ /dev/null @@ -1,525 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import type { Kernel, KernelMessage, Session } from '@jupyterlab/services'; -import type { JSONObject } from '@phosphor/coreutils'; -import type { Slot } from '@phosphor/signaling'; -import { Observable } from 'rxjs/Observable'; -import { ReplaySubject } from 'rxjs/ReplaySubject'; -import { Event, EventEmitter } from 'vscode'; -import { CancellationToken } from 'vscode-jsonrpc'; -import { ServerStatus } from '../../datascience-ui/interactive-common/mainState'; -import { traceError, traceInfo, traceWarning } from '../common/logger'; -import { sleep, waitForPromise } from '../common/utils/async'; -import * as localize from '../common/utils/localize'; -import { noop } from '../common/utils/misc'; -import { sendTelemetryEvent } from '../telemetry'; -import { Identifiers, Telemetry } from './constants'; -import { JupyterInvalidKernelError } from './jupyter/jupyterInvalidKernelError'; -import { JupyterWaitForIdleError } from './jupyter/jupyterWaitForIdleError'; -import { kernelConnectionMetadataHasKernelSpec } from './jupyter/kernels/helpers'; -import { JupyterKernelPromiseFailedError } from './jupyter/kernels/jupyterKernelPromiseFailedError'; -import { KernelConnectionMetadata } from './jupyter/kernels/types'; -import { suppressShutdownErrors } from './raw-kernel/rawKernel'; -import { IJupyterSession, ISessionWithSocket, KernelSocketInformation } from './types'; - -/** - * Exception raised when starting a Jupyter Session fails. - * - * @export - * @class JupyterSessionStartError - * @extends {Error} - */ -export class JupyterSessionStartError extends Error { - constructor(originalException: Error) { - super(originalException.message); - this.stack = originalException.stack; - sendTelemetryEvent(Telemetry.StartSessionFailedJupyter); - } -} - -export abstract class BaseJupyterSession implements IJupyterSession { - protected get session(): ISessionWithSocket | undefined { - return this._session; - } - protected kernelConnectionMetadata?: KernelConnectionMetadata; - public get kernelSocket(): Observable { - return this._kernelSocket; - } - private get jupyterLab(): undefined | typeof import('@jupyterlab/services') { - if (!this._jupyterLab) { - // tslint:disable-next-line:no-require-imports - this._jupyterLab = require('@jupyterlab/services') as typeof import('@jupyterlab/services'); // NOSONAR - } - return this._jupyterLab; - } - - public get onSessionStatusChanged(): Event { - if (!this.onStatusChangedEvent) { - this.onStatusChangedEvent = new EventEmitter(); - } - return this.onStatusChangedEvent.event; - } - - public get status(): ServerStatus { - return this.getServerStatus(); - } - - public get isConnected(): boolean { - return this.connected; - } - protected onStatusChangedEvent: EventEmitter = new EventEmitter(); - protected statusHandler: Slot; - protected connected: boolean = false; - protected restartSessionPromise: Promise | undefined; - private _session: ISessionWithSocket | undefined; - private _kernelSocket = new ReplaySubject(); - private _jupyterLab?: typeof import('@jupyterlab/services'); - private ioPubEventEmitter = new EventEmitter(); - private ioPubHandler: Slot; - - constructor(private restartSessionUsed: (id: Kernel.IKernelConnection) => void, public workingDirectory: string) { - this.statusHandler = this.onStatusChanged.bind(this); - this.ioPubHandler = (_s, m) => this.ioPubEventEmitter.fire(m); - } - public dispose(): Promise { - return this.shutdown(); - } - // Abstracts for each Session type to implement - public abstract async waitForIdle(timeout: number): Promise; - - public async shutdown(): Promise { - if (this.session) { - try { - traceInfo('Shutdown session - current session'); - await this.shutdownSession(this.session, this.statusHandler); - traceInfo('Shutdown session - get restart session'); - if (this.restartSessionPromise) { - const restartSession = await this.restartSessionPromise; - traceInfo('Shutdown session - shutdown restart session'); - await this.shutdownSession(restartSession, undefined); - } - } catch { - noop(); - } - this.setSession(undefined); - this.restartSessionPromise = undefined; - } - if (this.onStatusChangedEvent) { - this.onStatusChangedEvent.dispose(); - } - traceInfo('Shutdown session -- complete'); - } - public async interrupt(timeout: number): Promise { - if (this.session && this.session.kernel) { - // Listen for session status changes - this.session.statusChanged.connect(this.statusHandler); - - await this.waitForKernelPromise( - this.session.kernel.interrupt(), - timeout, - localize.DataScience.interruptingKernelFailed() - ); - } - } - public async requestKernelInfo(): Promise { - if (!this.session) { - throw new Error('Cannot request KernelInfo, Session not initialized.'); - } - return this.session.kernel.requestKernelInfo(); - } - public async changeKernel(kernelConnection: KernelConnectionMetadata, timeoutMS: number): Promise { - let newSession: ISessionWithSocket | undefined; - - // If we are already using this kernel in an active session just return back - const currentKernelSpec = - this.kernelConnectionMetadata && kernelConnectionMetadataHasKernelSpec(this.kernelConnectionMetadata) - ? this.kernelConnectionMetadata.kernelSpec - : undefined; - const kernelSpecToUse = kernelConnectionMetadataHasKernelSpec(kernelConnection) - ? kernelConnection.kernelSpec - : undefined; - if (this.session && currentKernelSpec && kernelSpecToUse) { - // Name and id have to match (id is only for active sessions) - if (currentKernelSpec.name === kernelSpecToUse.name && currentKernelSpec.id === kernelSpecToUse.id) { - return; - } - } - - newSession = await this.createNewKernelSession(kernelConnection, timeoutMS); - - // This is just like doing a restart, kill the old session (and the old restart session), and start new ones - if (this.session) { - this.shutdownSession(this.session, this.statusHandler).ignoreErrors(); - this.restartSessionPromise?.then((r) => this.shutdownSession(r, undefined)).ignoreErrors(); // NOSONAR - } - - // Update our kernel connection metadata. - this.kernelConnectionMetadata = kernelConnection; - - // Save the new session - this.setSession(newSession); - - // Listen for session status changes - this.session?.statusChanged.connect(this.statusHandler); // NOSONAR - - // Start the restart session promise too. - this.restartSessionPromise = this.createRestartSession(kernelConnection, newSession); - } - - public async restart(_timeout: number): Promise { - if (this.session?.isRemoteSession) { - await this.session.kernel.restart(); - return; - } - - // Start the restart session now in case it wasn't started - if (!this.restartSessionPromise) { - this.startRestartSession(); - } - - // Just kill the current session and switch to the other - if (this.restartSessionPromise && this.session) { - traceInfo(`Restarting ${this.session.kernel.id}`); - - // Save old state for shutdown - const oldSession = this.session; - const oldStatusHandler = this.statusHandler; - - // Just switch to the other session. It should already be ready - this.setSession(await this.restartSessionPromise); - if (!this.session) { - throw new Error(localize.DataScience.sessionDisposed()); - } - this.restartSessionUsed(this.session.kernel); - traceInfo(`Got new session ${this.session.kernel.id}`); - - // Rewire our status changed event. - this.session.statusChanged.connect(this.statusHandler); - - // After switching, start another in case we restart again. - this.restartSessionPromise = this.createRestartSession(this.kernelConnectionMetadata, oldSession); - traceInfo('Started new restart session'); - if (oldStatusHandler) { - oldSession.statusChanged.disconnect(oldStatusHandler); - } - this.shutdownSession(oldSession, undefined).ignoreErrors(); - } else { - throw new Error(localize.DataScience.sessionDisposed()); - } - } - - public requestExecute( - content: KernelMessage.IExecuteRequestMsg['content'], - disposeOnDone?: boolean, - metadata?: JSONObject - ): Kernel.IShellFuture | undefined { - const promise = - this.session && this.session.kernel - ? this.session.kernel.requestExecute(content, disposeOnDone, metadata) - : undefined; - - // It has been observed that starting the restart session slows down first time to execute a cell. - // Solution is to start the restart session after the first execution of user code. - if (promise) { - promise.done.finally(() => this.startRestartSession()).catch(noop); - } - return promise; - } - - public requestInspect( - content: KernelMessage.IInspectRequestMsg['content'] - ): Promise { - return this.session && this.session.kernel - ? this.session.kernel.requestInspect(content) - : Promise.resolve(undefined); - } - - public requestComplete( - content: KernelMessage.ICompleteRequestMsg['content'] - ): Promise { - return this.session && this.session.kernel - ? this.session.kernel.requestComplete(content) - : Promise.resolve(undefined); - } - - public sendInputReply(content: string) { - if (this.session && this.session.kernel) { - // tslint:disable-next-line: no-any - this.session.kernel.sendInputReply({ value: content, status: 'ok' }); - } - } - - public registerCommTarget( - targetName: string, - callback: (comm: Kernel.IComm, msg: KernelMessage.ICommOpenMsg) => void | PromiseLike - ) { - if (this.session && this.session.kernel) { - this.session.kernel.registerCommTarget(targetName, callback); - } else { - throw new Error(localize.DataScience.sessionDisposed()); - } - } - - public sendCommMessage( - buffers: (ArrayBuffer | ArrayBufferView)[], - content: { comm_id: string; data: JSONObject; target_name: string | undefined }, - // tslint:disable-next-line: no-any - metadata: any, - // tslint:disable-next-line: no-any - msgId: any - ): Kernel.IShellFuture< - KernelMessage.IShellMessage<'comm_msg'>, - KernelMessage.IShellMessage - > { - if (this.session && this.session.kernel && this.jupyterLab) { - const shellMessage = this.jupyterLab.KernelMessage.createMessage>({ - // tslint:disable-next-line: no-any - msgType: 'comm_msg', - channel: 'shell', - buffers, - content, - metadata, - msgId, - session: this.session.kernel.clientId, - username: this.session.kernel.username - }); - - return this.session.kernel.sendShellMessage(shellMessage, false, true); - } else { - throw new Error(localize.DataScience.sessionDisposed()); - } - } - - public requestCommInfo( - content: KernelMessage.ICommInfoRequestMsg['content'] - ): Promise { - if (this.session?.kernel) { - return this.session.kernel.requestCommInfo(content); - } else { - throw new Error(localize.DataScience.sessionDisposed()); - } - } - public registerMessageHook( - msgId: string, - hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike - ): void { - if (this.session?.kernel) { - return this.session.kernel.registerMessageHook(msgId, hook); - } else { - throw new Error(localize.DataScience.sessionDisposed()); - } - } - public removeMessageHook( - msgId: string, - hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike - ): void { - if (this.session?.kernel) { - return this.session.kernel.removeMessageHook(msgId, hook); - } else { - throw new Error(localize.DataScience.sessionDisposed()); - } - } - - // Sub classes need to implement their own restarting specific code - protected abstract startRestartSession(): void; - protected abstract async createRestartSession( - kernelConnection: KernelConnectionMetadata | undefined, - session: ISessionWithSocket, - cancelToken?: CancellationToken - ): Promise; - - // Sub classes need to implement their own kernel change specific code - protected abstract createNewKernelSession( - kernelConnection: KernelConnectionMetadata, - timeoutMS: number - ): Promise; - - protected async waitForIdleOnSession(session: ISessionWithSocket | undefined, timeout: number): Promise { - if (session && session.kernel) { - traceInfo(`Waiting for idle on (kernel): ${session.kernel.id} -> ${session.kernel.status}`); - // tslint:disable-next-line: no-any - const statusHandler = (resolve: () => void, reject: (exc: any) => void, e: Kernel.Status | undefined) => { - if (e === 'idle') { - resolve(); - } else if (e === 'dead') { - traceError('Kernel died while waiting for idle'); - // If we throw an exception, make sure to shutdown the session as it's not usable anymore - this.shutdownSession(session, this.statusHandler).ignoreErrors(); - const kernelModel = { - ...session.kernel, - lastActivityTime: new Date(), - numberOfConnections: 0, - session: session.model - }; - reject( - new JupyterInvalidKernelError({ - kernelModel, - kind: 'connectToLiveKernel' - }) - ); - } - }; - - let statusChangeHandler: Slot | undefined; - const kernelStatusChangedPromise = new Promise((resolve, reject) => { - statusChangeHandler = (_: ISessionWithSocket, e: Kernel.Status) => statusHandler(resolve, reject, e); - session.statusChanged.connect(statusChangeHandler); - }); - let kernelChangedHandler: Slot | undefined; - const statusChangedPromise = new Promise((resolve, reject) => { - kernelChangedHandler = (_: ISessionWithSocket, e: Session.IKernelChangedArgs) => - statusHandler(resolve, reject, e.newValue?.status); - session.kernelChanged.connect(kernelChangedHandler); - }); - const checkStatusPromise = new Promise(async (resolve) => { - // This function seems to cause CI builds to timeout randomly on - // different tests. Waiting for status to go idle doesn't seem to work and - // in the past, waiting on the ready promise doesn't work either. Check status with a maximum of 5 seconds - const startTime = Date.now(); - while ( - session && - session.kernel && - session.kernel.status !== 'idle' && - Date.now() - startTime < timeout - ) { - await sleep(100); - } - resolve(); - }); - await Promise.race([kernelStatusChangedPromise, statusChangedPromise, checkStatusPromise]); - traceInfo(`Finished waiting for idle on (kernel): ${session.kernel.id} -> ${session.kernel.status}`); - - if (statusChangeHandler && session && session.statusChanged) { - session.statusChanged.disconnect(statusChangeHandler); - } - if (kernelChangedHandler && session && session.kernelChanged) { - session.kernelChanged.disconnect(kernelChangedHandler); - } - - // If we didn't make it out in ten seconds, indicate an error - if (session.kernel && session.kernel.status === 'idle') { - // So that we don't have problems with ipywidgets, always register the default ipywidgets comm target. - // Restart sessions and retries might make this hard to do correctly otherwise. - session.kernel.registerCommTarget(Identifiers.DefaultCommTarget, noop); - - return; - } - - // If we throw an exception, make sure to shutdown the session as it's not usable anymore - this.shutdownSession(session, this.statusHandler).ignoreErrors(); - throw new JupyterWaitForIdleError(localize.DataScience.jupyterLaunchTimedOut()); - } - } - - // Changes the current session. - protected setSession(session: ISessionWithSocket | undefined) { - const oldSession = this._session; - if (this.ioPubHandler && oldSession) { - oldSession.iopubMessage.disconnect(this.ioPubHandler); - } - this._session = session; - if (session) { - session.iopubMessage.connect(this.ioPubHandler); - } - - // If we have a new session, then emit the new kernel connection information. - if (session && oldSession !== session) { - if (!session.kernelSocketInformation) { - traceError(`Unable to find WebSocket connection associated with kernel ${session.kernel.id}`); - this._kernelSocket.next(undefined); - } else { - this._kernelSocket.next({ - options: { - clientId: session.kernel.clientId, - id: session.kernel.id, - model: { ...session.kernel.model }, - userName: session.kernel.username - }, - socket: session.kernelSocketInformation.socket - }); - } - } - } - protected async shutdownSession( - session: ISessionWithSocket | undefined, - statusHandler: Slot | undefined - ): Promise { - if (session && session.kernel) { - const kernelId = session.kernel.id; - traceInfo(`shutdownSession ${kernelId} - start`); - try { - if (statusHandler) { - session.statusChanged.disconnect(statusHandler); - } - // Do not shutdown remote sessions. - if (session.isRemoteSession) { - session.dispose(); - return; - } - try { - suppressShutdownErrors(session.kernel); - // Shutdown may fail if the process has been killed - if (!session.isDisposed) { - await waitForPromise(session.shutdown(), 1000); - } - } catch { - noop(); - } - if (session && !session.isDisposed) { - session.dispose(); - } - } catch (e) { - // Ignore, just trace. - traceWarning(e); - } - traceInfo(`shutdownSession ${kernelId} - shutdown complete`); - } - } - private getServerStatus(): ServerStatus { - if (this.session) { - switch (this.session.kernel.status) { - case 'busy': - return ServerStatus.Busy; - case 'dead': - return ServerStatus.Dead; - case 'idle': - case 'connected': - return ServerStatus.Idle; - case 'restarting': - case 'autorestarting': - case 'reconnecting': - return ServerStatus.Restarting; - case 'starting': - return ServerStatus.Starting; - default: - return ServerStatus.NotStarted; - } - } - - return ServerStatus.NotStarted; - } - - private async waitForKernelPromise( - kernelPromise: Promise, - timeout: number, - errorMessage: string - ): Promise { - // Wait for this kernel promise to happen - try { - await waitForPromise(kernelPromise, timeout); - } catch (e) { - if (!e) { - // We timed out. Throw a specific exception - throw new JupyterKernelPromiseFailedError(errorMessage); - } - throw e; - } - } - - private onStatusChanged(_s: Session.ISession) { - if (this.onStatusChangedEvent) { - this.onStatusChangedEvent.fire(this.getServerStatus()); - } - } -} diff --git a/src/client/datascience/cellFactory.ts b/src/client/datascience/cellFactory.ts deleted file mode 100644 index b8df01e41317..000000000000 --- a/src/client/datascience/cellFactory.ts +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../common/extensions'; - -import * as uuid from 'uuid/v4'; -import { Range, TextDocument, Uri } from 'vscode'; - -import { parseForComments } from '../../datascience-ui/common'; -import { createCodeCell, createMarkdownCell } from '../../datascience-ui/common/cellFactory'; -import { IDataScienceSettings, Resource } from '../common/types'; -import { noop } from '../common/utils/misc'; -import { CellMatcher } from './cellMatcher'; -import { Identifiers } from './constants'; -import { CellState, ICell, ICellRange } from './types'; - -function generateCodeCell( - code: string[], - file: string, - line: number, - id: string, - magicCommandsAsComments: boolean -): ICell { - // Code cells start out with just source and no outputs. - return { - data: createCodeCell(code, magicCommandsAsComments), - id: id, - file: file, - line: line, - state: CellState.init - }; -} - -function generateMarkdownCell(code: string[], file: string, line: number, id: string): ICell { - return { - id: id, - file: file, - line: line, - state: CellState.finished, - data: createMarkdownCell(code) - }; -} - -export function getCellResource(cell: ICell): Resource { - if (cell.file !== Identifiers.EmptyFileName) { - return Uri.file(cell.file); - } - return undefined; -} - -export function generateCells( - settings: IDataScienceSettings | undefined, - code: string, - file: string, - line: number, - splitMarkdown: boolean, - id: string -): ICell[] { - // Determine if we have a markdown cell/ markdown and code cell combined/ or just a code cell - const split = code.splitLines({ trim: false }); - const firstLine = split[0]; - const matcher = new CellMatcher(settings); - const { magicCommandsAsComments = false } = settings || {}; - if (matcher.isMarkdown(firstLine)) { - // We have at least one markdown. We might have to split it if there any lines that don't begin - // with # or are inside a multiline comment - let firstNonMarkdown = -1; - parseForComments( - split, - (_s, _i) => noop(), - (s, i) => { - // Make sure there's actually some code. - if (s && s.length > 0 && firstNonMarkdown === -1) { - firstNonMarkdown = splitMarkdown ? i : -1; - } - } - ); - if (firstNonMarkdown >= 0) { - // Make sure if we split, the second cell has a new id. It's a new submission. - return [ - generateMarkdownCell(split.slice(0, firstNonMarkdown), file, line, id), - generateCodeCell( - split.slice(firstNonMarkdown), - file, - line + firstNonMarkdown, - uuid(), - magicCommandsAsComments - ) - ]; - } else { - // Just a single markdown cell - return [generateMarkdownCell(split, file, line, id)]; - } - } else { - // Just code - return [generateCodeCell(split, file, line, id, magicCommandsAsComments)]; - } -} - -export function hasCells(document: TextDocument, settings?: IDataScienceSettings): boolean { - const matcher = new CellMatcher(settings); - for (let index = 0; index < document.lineCount; index += 1) { - const line = document.lineAt(index); - if (matcher.isCell(line.text)) { - return true; - } - } - - return false; -} - -export function generateCellsFromString(source: string, settings?: IDataScienceSettings): ICell[] { - const lines: string[] = source.splitLines({ trim: false, removeEmptyEntries: false }); - - // Find all the lines that start a cell - const matcher = new CellMatcher(settings); - const starts: { startLine: number; title: string; code: string; cell_type: string }[] = []; - let currentCode: string | undefined; - for (let index = 0; index < lines.length; index += 1) { - const line = lines[index]; - if (matcher.isCell(line)) { - if (starts.length > 0 && currentCode) { - const previousCell = starts[starts.length - 1]; - previousCell.code = currentCode; - } - const results = matcher.exec(line); - if (results !== undefined) { - starts.push({ - startLine: index + 1, - title: results, - cell_type: matcher.getCellType(line), - code: '' - }); - } - currentCode = undefined; - } - currentCode = currentCode ? `${currentCode}\n${line}` : line; - } - - if (starts.length >= 1 && currentCode) { - const previousCell = starts[starts.length - 1]; - previousCell.code = currentCode; - } - - // For each one, get its text and turn it into a cell - return Array.prototype.concat( - ...starts.map((s) => { - return generateCells(settings, s.code, '', s.startLine, false, uuid()); - }) - ); -} - -export function generateCellRangesFromDocument(document: TextDocument, settings?: IDataScienceSettings): ICellRange[] { - // Implmentation of getCells here based on Don's Jupyter extension work - const matcher = new CellMatcher(settings); - const cells: ICellRange[] = []; - for (let index = 0; index < document.lineCount; index += 1) { - const line = document.lineAt(index); - if (matcher.isCell(line.text)) { - if (cells.length > 0) { - const previousCell = cells[cells.length - 1]; - previousCell.range = new Range(previousCell.range.start, document.lineAt(index - 1).range.end); - } - - const results = matcher.exec(line.text); - if (results !== undefined) { - cells.push({ - range: line.range, - title: results, - cell_type: matcher.getCellType(line.text) - }); - } - } - } - - if (cells.length >= 1) { - const line = document.lineAt(document.lineCount - 1); - const previousCell = cells[cells.length - 1]; - previousCell.range = new Range(previousCell.range.start, line.range.end); - } - - return cells; -} - -export function generateCellsFromDocument(document: TextDocument, settings?: IDataScienceSettings): ICell[] { - const ranges = generateCellRangesFromDocument(document, settings); - - // For each one, get its text and turn it into a cell - return Array.prototype.concat( - ...ranges.map((cr) => { - const code = document.getText(cr.range); - return generateCells(settings, code, '', cr.range.start.line, false, uuid()); - }) - ); -} diff --git a/src/client/datascience/cellMatcher.ts b/src/client/datascience/cellMatcher.ts deleted file mode 100644 index 1a6a61848983..000000000000 --- a/src/client/datascience/cellMatcher.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../common/extensions'; - -import { IDataScienceSettings } from '../common/types'; -import { noop } from '../common/utils/misc'; -import { RegExpValues } from './constants'; - -export class CellMatcher { - public codeExecRegEx: RegExp; - public markdownExecRegEx: RegExp; - - private codeMatchRegEx: RegExp; - private markdownMatchRegEx: RegExp; - private defaultCellMarker: string; - private defaultCellMarkerExec: RegExp; - - constructor(settings?: IDataScienceSettings) { - this.codeMatchRegEx = this.createRegExp( - settings ? settings.codeRegularExpression : undefined, - RegExpValues.PythonCellMarker - ); - this.markdownMatchRegEx = this.createRegExp( - settings ? settings.markdownRegularExpression : undefined, - RegExpValues.PythonMarkdownCellMarker - ); - this.codeExecRegEx = new RegExp(`${this.codeMatchRegEx.source}(.*)`); - this.markdownExecRegEx = new RegExp(`${this.markdownMatchRegEx.source}(.*)`); - this.defaultCellMarker = settings?.defaultCellMarker ? settings.defaultCellMarker : '# %%'; - this.defaultCellMarkerExec = this.createRegExp(`${this.defaultCellMarker}(.*)`, /# %%(.*)/); - } - - public isCell(code: string): boolean { - return this.isCode(code) || this.isMarkdown(code); - } - - public isMarkdown(code: string): boolean { - return this.markdownMatchRegEx.test(code); - } - - public isCode(code: string): boolean { - return this.codeMatchRegEx.test(code) || code.trim() === this.defaultCellMarker; - } - - public getCellType(code: string): string { - return this.isMarkdown(code) ? 'markdown' : 'code'; - } - - public stripFirstMarker(code: string): string { - const lines = code.splitLines({ trim: false, removeEmptyEntries: false }); - - // Only strip this off the first line. Otherwise we want the markers in the code. - if (lines.length > 0 && (this.isCode(lines[0]) || this.isMarkdown(lines[0]))) { - return lines.slice(1).join('\n'); - } - return code; - } - - public exec(code: string): string | undefined { - let result: RegExpExecArray | null = null; - if (this.defaultCellMarkerExec.test(code)) { - this.defaultCellMarkerExec.lastIndex = -1; - result = this.defaultCellMarkerExec.exec(code); - } else if (this.codeMatchRegEx.test(code)) { - this.codeExecRegEx.lastIndex = -1; - result = this.codeExecRegEx.exec(code); - } else if (this.markdownMatchRegEx.test(code)) { - this.markdownExecRegEx.lastIndex = -1; - result = this.markdownExecRegEx.exec(code); - } - if (result) { - return result.length > 1 ? result[result.length - 1].trim() : ''; - } - return undefined; - } - - private createRegExp(potential: string | undefined, backup: RegExp): RegExp { - try { - if (potential) { - return new RegExp(potential); - } - } catch { - noop(); - } - - return backup; - } -} diff --git a/src/client/datascience/codeCssGenerator.ts b/src/client/datascience/codeCssGenerator.ts deleted file mode 100644 index c92380695dbb..000000000000 --- a/src/client/datascience/codeCssGenerator.ts +++ /dev/null @@ -1,518 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import type { JSONArray, JSONObject } from '@phosphor/coreutils'; -import { inject, injectable } from 'inversify'; -import { parse } from 'jsonc-parser'; -import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; -import * as path from 'path'; -import { IWorkspaceService } from '../common/application/types'; -import { traceError, traceInfo, traceWarning } from '../common/logger'; - -import { IConfigurationService, Resource } from '../common/types'; -import { DefaultTheme } from './constants'; -import { ICodeCssGenerator, IDataScienceFileSystem, IThemeFinder } from './types'; - -// tslint:disable:no-any -const DarkTheme = 'dark'; -const LightTheme = 'light'; - -const MonacoColorRegEx = /^#?([0-9A-Fa-f]{6})([0-9A-Fa-f]{2})?$/; -const ThreeColorRegEx = /^#?([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])$/; - -// These are based on the colors generated by 'Default Light+' and are only set when we -// are ignoring themes. -//tslint:disable:no-multiline-string object-literal-key-quotes -const DefaultCssVars: { [key: string]: string } = { - light: ` - :root { - --override-widget-background: #f3f3f3; - --override-foreground: #000000; - --override-background: #FFFFFF; - --override-selection-background: #add6ff; - --override-watermark-color: rgba(66, 66, 66, 0.75); - --override-tabs-background: #f3f3f3; - --override-progress-background: #0066bf; - --override-badge-background: #c4c4c4; - --override-lineHighlightBorder: #eeeeee; - --override-peek-background: #f2f8fc; - } -`, - dark: ` - :root { - --override-widget-background: #1e1e1e; - --override-foreground: #d4d4d4; - --override-background: #1e1e1e; - --override-selection-background: #264f78; - --override-watermark-color: rgba(231, 231, 231, 0.6); - --override-tabs-background: #252526; - --override-progress-background: #0066bf; - --override-badge-background: #4d4d4d; - --override-lineHighlightBorder: #282828; - --override-peek-background: #001f33; - } -` -}; - -// These colors below should match colors that come from either the Default Light+ theme or the Default Dark+ theme. -// They are used when we can't find a theme json file. -const DefaultColors: { [key: string]: string } = { - 'light.comment': '#008000', - 'light.constant.numeric': '#09885a', - 'light.string': '#a31515', - 'light.keyword.control': '#AF00DB', - 'light.keyword.operator': '#000000', - 'light.variable': '#001080', - 'light.entity.name.type': '#267f99', - 'light.support.function': '#795E26', - 'light.punctuation': '#000000', - 'dark.comment': '#6A9955', - 'dark.constant.numeric': '#b5cea8', - 'dark.string': '#ce9178', - 'dark.keyword.control': '#C586C0', - 'dark.keyword.operator': '#d4d4d4', - 'dark.variable': '#9CDCFE', - 'dark.entity.name.type': '#4EC9B0', - 'dark.support.function': '#DCDCAA', - 'dark.punctuation': '#1e1e1e' -}; - -interface IApplyThemeArgs { - tokenColors?: JSONArray | null; - baseColors?: JSONObject | null; - fontFamily: string; - fontSize: number; - isDark: boolean; - defaultStyle: string | undefined; -} - -// This class generates css using the current theme in order to colorize code. -// -// NOTE: This is all a big hack. It's relying on the theme json files to have a certain format -// in order for this to work. -// See this vscode issue for the real way we think this should happen: -// https://github.com/Microsoft/vscode/issues/32813 -@injectable() -export class CodeCssGenerator implements ICodeCssGenerator { - constructor( - @inject(IWorkspaceService) private workspaceService: IWorkspaceService, - @inject(IThemeFinder) private themeFinder: IThemeFinder, - @inject(IConfigurationService) private configService: IConfigurationService, - @inject(IDataScienceFileSystem) private fs: IDataScienceFileSystem - ) {} - - public generateThemeCss(resource: Resource, isDark: boolean, theme: string): Promise { - return this.applyThemeData(resource, isDark, theme, '', this.generateCss.bind(this)); - } - - public generateMonacoTheme(resource: Resource, isDark: boolean, theme: string): Promise { - return this.applyThemeData(resource, isDark, theme, {} as any, this.generateMonacoThemeObject.bind(this)); - } - - private async applyThemeData( - resource: Resource, - isDark: boolean, - theme: string, - defaultT: T, - applier: (args: IApplyThemeArgs) => T - ): Promise { - let result = defaultT; - try { - // First compute our current theme. - const ignoreTheme = this.configService.getSettings(resource).datascience.ignoreVscodeTheme ? true : false; - theme = ignoreTheme ? DefaultTheme : theme; - const editor = this.workspaceService.getConfiguration('editor', undefined); - const fontFamily = editor - ? editor.get('fontFamily', "Consolas, 'Courier New', monospace") - : "Consolas, 'Courier New', monospace"; - const fontSize = editor ? editor.get('fontSize', 14) : 14; - const isDarkUpdated = ignoreTheme ? false : isDark; - - // Then we have to find where the theme resources are loaded from - if (theme) { - traceInfo('Searching for token colors ...'); - const tokenColors = await this.findTokenColors(theme); - const baseColors = await this.findBaseColors(theme); - - // The tokens object then contains the necessary data to generate our css - if (tokenColors && fontFamily && fontSize) { - traceInfo('Using colors to generate CSS ...'); - result = applier({ - tokenColors, - baseColors, - fontFamily, - fontSize, - isDark: isDarkUpdated, - defaultStyle: ignoreTheme ? LightTheme : undefined - }); - } else if (tokenColors === null && fontFamily && fontSize) { - // No colors found. See if we can figure out what type of theme we have - const style = isDark ? DarkTheme : LightTheme; - result = applier({ fontFamily, fontSize, isDark: isDarkUpdated, defaultStyle: style }); - } - } - } catch (err) { - // On error don't fail, just log - traceError(err); - } - - return result; - } - - private getScopes(entry: any): JSONArray { - if (entry && entry.scope) { - return Array.isArray(entry.scope) ? (entry.scope as JSONArray) : entry.scope.toString().split(','); - } - return []; - } - - private matchTokenColor(tokenColors: JSONArray, scope: string): number { - return tokenColors.findIndex((entry: any) => { - const scopeArray = this.getScopes(entry); - if (scopeArray.find((v) => v !== null && v !== undefined && v.toString().trim() === scope)) { - return true; - } - return false; - }); - } - - private getScopeStyle = ( - tokenColors: JSONArray | null | undefined, - scope: string, - secondary: string, - defaultStyle: string | undefined - ): { color: string; fontStyle: string } => { - // Search through the scopes on the json object - if (tokenColors) { - let match = this.matchTokenColor(tokenColors, scope); - if (match < 0 && secondary) { - match = this.matchTokenColor(tokenColors, secondary); - } - const found = match >= 0 ? (tokenColors[match] as any) : null; - if (found !== null) { - const settings = found.settings; - if (settings && settings !== null) { - const fontStyle = settings.fontStyle ? settings.fontStyle : 'normal'; - const foreground = settings.foreground ? settings.foreground : 'var(--vscode-editor-foreground)'; - - return { fontStyle, color: foreground }; - } - } - } - - // Default to editor foreground - return { color: this.getDefaultColor(defaultStyle, scope), fontStyle: 'normal' }; - }; - - private getDefaultColor(style: string | undefined, scope: string): string { - return style - ? DefaultColors[`${style}.${scope}`] - : 'var(--override-foreground, var(--vscode-editor-foreground))'; - } - - // tslint:disable-next-line:max-func-body-length - private generateCss(args: IApplyThemeArgs): string { - // There's a set of values that need to be found - const commentStyle = this.getScopeStyle(args.tokenColors, 'comment', 'comment', args.defaultStyle); - const numericStyle = this.getScopeStyle(args.tokenColors, 'constant.numeric', 'constant', args.defaultStyle); - const stringStyle = this.getScopeStyle(args.tokenColors, 'string', 'string', args.defaultStyle); - const variableStyle = this.getScopeStyle(args.tokenColors, 'variable', 'variable', args.defaultStyle); - const entityTypeStyle = this.getScopeStyle( - args.tokenColors, - 'entity.name.type', - 'entity.name.type', - args.defaultStyle - ); - - // Use these values to fill in our format string - return ` -:root { - --code-comment-color: ${commentStyle.color}; - --code-numeric-color: ${numericStyle.color}; - --code-string-color: ${stringStyle.color}; - --code-variable-color: ${variableStyle.color}; - --code-type-color: ${entityTypeStyle.color}; - --code-font-family: ${args.fontFamily}; - --code-font-size: ${args.fontSize}px; -} - -${args.defaultStyle ? DefaultCssVars[args.defaultStyle] : ''} -`; - } - - // Based on this data here: - // https://github.com/Microsoft/vscode/blob/master/src/vs/editor/standalone/common/themes.ts#L13 - // tslint:disable: max-func-body-length - private generateMonacoThemeObject(args: IApplyThemeArgs): monacoEditor.editor.IStandaloneThemeData { - const result: monacoEditor.editor.IStandaloneThemeData = { - base: args.isDark ? 'vs-dark' : 'vs', - inherit: false, - rules: [], - colors: {} - }; - // If we have token colors enumerate them and add them into the rules - if (args.tokenColors && args.tokenColors.length) { - const tokenSet = new Set(); - args.tokenColors.forEach((t: any) => { - const scopes = this.getScopes(t); - const settings = t && t.settings ? t.settings : undefined; - if (scopes && settings) { - scopes.forEach((s) => { - const token = s ? s.toString() : ''; - if (!tokenSet.has(token)) { - tokenSet.add(token); - - if (settings.foreground) { - // Make sure matches the monaco requirements of having 6 values - if (!MonacoColorRegEx.test(settings.foreground)) { - const match = ThreeColorRegEx.exec(settings.foreground); - if (match && match.length > 3) { - settings.foreground = `#${match[1]}${match[1]}${match[2]}${match[2]}${match[3]}${match[3]}`; - } else { - settings.foreground = undefined; - } - } - } - - if (settings.foreground) { - result.rules.push({ - token, - foreground: settings.foreground, - background: settings.background, - fontStyle: settings.fontStyle - }); - } else { - result.rules.push({ - token, - background: settings.background, - fontStyle: settings.fontStyle - }); - } - - // Special case some items. punctuation.definition.comment doesn't seem to - // be listed anywhere. Add it manually when we find a 'comment' - // tslint:disable-next-line: possible-timing-attack - if (token === 'comment') { - result.rules.push({ - token: 'punctuation.definition.comment', - foreground: settings.foreground, - background: settings.background, - fontStyle: settings.fontStyle - }); - } - - // Same for string - // tslint:disable-next-line: possible-timing-attack - if (token === 'string') { - result.rules.push({ - token: 'punctuation.definition.string', - foreground: settings.foreground, - background: settings.background, - fontStyle: settings.fontStyle - }); - } - } - }); - } - }); - - result.rules = result.rules.sort( - (a: monacoEditor.editor.ITokenThemeRule, b: monacoEditor.editor.ITokenThemeRule) => { - return a.token.localeCompare(b.token); - } - ); - } else { - // Otherwise use our default values. - result.base = args.defaultStyle === DarkTheme ? 'vs-dark' : 'vs'; - result.inherit = true; - - if (args.defaultStyle) { - // Special case. We need rules for the comment beginning and the string beginning - result.rules.push({ - token: 'punctuation.definition.comment', - foreground: DefaultColors[`${args.defaultStyle}.comment`] - }); - result.rules.push({ - token: 'punctuation.definition.string', - foreground: DefaultColors[`${args.defaultStyle}.string`] - }); - } - } - // If we have base colors enumerate them and add them to the colors - if (args.baseColors) { - const keys = Object.keys(args.baseColors); - keys.forEach((k) => { - const color = args.baseColors && args.baseColors[k] ? args.baseColors[k] : '#000000'; - result.colors[k] = color ? color.toString() : '#000000'; - }); - } // The else case here should end up inheriting. - return result; - } - - private mergeColors = (colors1: JSONArray, colors2: JSONArray): JSONArray => { - return [...colors1, ...colors2]; - }; - - private mergeBaseColors = (colors1: JSONObject, colors2: JSONObject): JSONObject => { - return { ...colors1, ...colors2 }; - }; - - private readTokenColors = async (themeFile: string): Promise => { - try { - const tokenContent = await this.fs.readLocalFile(themeFile); - const theme = parse(tokenContent); - let tokenColors: JSONArray = []; - - if (typeof theme.tokenColors === 'string') { - const style = await this.fs.readLocalFile(theme.tokenColors); - tokenColors = JSON.parse(style); - } else { - tokenColors = theme.tokenColors as JSONArray; - } - - if (tokenColors && tokenColors.length > 0) { - // This theme may include others. If so we need to combine the two together - const include = theme ? theme.include : undefined; - if (include) { - const includePath = path.join(path.dirname(themeFile), include.toString()); - const includedColors = await this.readTokenColors(includePath); - return this.mergeColors(tokenColors, includedColors); - } - - // Theme is a root, don't need to include others - return tokenColors; - } - - // Might also have a 'settings' object that equates to token colors - const settings = theme.settings as JSONArray; - if (settings && settings.length > 0) { - return settings; - } - - return []; - } catch (e) { - traceError('Python Extension: Error reading custom theme', e); - return []; - } - }; - - private readBaseColors = async (themeFile: string): Promise => { - const tokenContent = await this.fs.readLocalFile(themeFile); - const theme = parse(tokenContent); - const colors = theme.colors as JSONObject; - - // This theme may include others. If so we need to combine the two together - const include = theme ? theme.include : undefined; - if (include) { - const includePath = path.join(path.dirname(themeFile), include.toString()); - const includedColors = await this.readBaseColors(includePath); - return this.mergeBaseColors(colors, includedColors); - } - - // Theme is a root, don't need to include others - return colors; - }; - - private findTokenColors = async (theme: string): Promise => { - try { - traceInfo('Attempting search for colors ...'); - const themeRoot = await this.themeFinder.findThemeRootJson(theme); - - // Use the first result if we have one - if (themeRoot) { - traceInfo(`Loading colors from ${themeRoot} ...`); - - // This should be the path to the file. Load it as a json object - const contents = await this.fs.readLocalFile(themeRoot); - const json = parse(contents); - - // There should be a theme colors section - const contributes = json.contributes as JSONObject; - - // If no contributes section, see if we have a tokenColors section. This means - // this is a direct token colors file - if (!contributes) { - const tokenColors = json.tokenColors as JSONObject; - if (tokenColors) { - return await this.readTokenColors(themeRoot); - } - } - - // This should have a themes section - const themes = contributes.themes as JSONArray; - - // One of these (it's an array), should have our matching theme entry - const index = themes.findIndex((e: any) => { - return e !== null && (e.id === theme || e.name === theme); - }); - - const found = index >= 0 ? (themes[index] as any) : null; - if (found !== null) { - // Then the path entry should contain a relative path to the json file with - // the tokens in it - const themeFile = path.join(path.dirname(themeRoot), found.path); - traceInfo(`Reading colors from ${themeFile}`); - return await this.readTokenColors(themeFile); - } - } else { - traceWarning(`Color theme ${theme} not found. Using default colors.`); - } - } catch (err) { - // Swallow any exceptions with searching or parsing - traceError(err); - } - - // Force the colors to the defaults - return null; - }; - - private findBaseColors = async (theme: string): Promise => { - try { - traceInfo('Attempting search for colors ...'); - const themeRoot = await this.themeFinder.findThemeRootJson(theme); - - // Use the first result if we have one - if (themeRoot) { - traceInfo(`Loading base colors from ${themeRoot} ...`); - - // This should be the path to the file. Load it as a json object - const contents = await this.fs.readLocalFile(themeRoot); - const json = parse(contents); - - // There should be a theme colors section - const contributes = json.contributes as JSONObject; - - // If no contributes section, see if we have a tokenColors section. This means - // this is a direct token colors file - if (!contributes) { - return await this.readBaseColors(themeRoot); - } - - // This should have a themes section - const themes = contributes.themes as JSONArray; - - // One of these (it's an array), should have our matching theme entry - const index = themes.findIndex((e: any) => { - return e !== null && (e.id === theme || e.name === theme); - }); - - const found = index >= 0 ? (themes[index] as any) : null; - if (found !== null) { - // Then the path entry should contain a relative path to the json file with - // the tokens in it - const themeFile = path.join(path.dirname(themeRoot), found.path); - traceInfo(`Reading base colors from ${themeFile}`); - return await this.readBaseColors(themeFile); - } - } else { - traceWarning(`Color theme ${theme} not found. Using default colors.`); - } - } catch (err) { - // Swallow any exceptions with searching or parsing - traceError(err); - } - - // Force the colors to the defaults - return null; - }; -} diff --git a/src/client/datascience/commands/commandLineSelector.ts b/src/client/datascience/commands/commandLineSelector.ts deleted file mode 100644 index 0ea635d5c83d..000000000000 --- a/src/client/datascience/commands/commandLineSelector.ts +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { ICommandManager } from '../../common/application/types'; -import { IDisposable } from '../../common/types'; -import { Commands } from '../constants'; -import { JupyterCommandLineSelector } from '../jupyter/commandLineSelector'; - -@injectable() -export class JupyterCommandLineSelectorCommand implements IDisposable { - private readonly disposables: IDisposable[] = []; - constructor( - @inject(ICommandManager) private readonly commandManager: ICommandManager, - @inject(JupyterCommandLineSelector) private readonly commandSelector: JupyterCommandLineSelector - ) {} - public register() { - this.disposables.push( - this.commandManager.registerCommand( - Commands.SelectJupyterCommandLine, - this.commandSelector.selectJupyterCommandLine, - this.commandSelector - ) - ); - } - public dispose() { - this.disposables.forEach((d) => d.dispose()); - } -} diff --git a/src/client/datascience/commands/commandRegistry.ts b/src/client/datascience/commands/commandRegistry.ts deleted file mode 100644 index ff9c64025dd8..000000000000 --- a/src/client/datascience/commands/commandRegistry.ts +++ /dev/null @@ -1,469 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable, multiInject, named, optional } from 'inversify'; -import { CodeLens, ConfigurationTarget, env, Range, Uri } from 'vscode'; -import { ICommandNameArgumentTypeMapping } from '../../common/application/commands'; -import { IApplicationShell, ICommandManager, IDebugService, IDocumentManager } from '../../common/application/types'; -import { Commands as coreCommands } from '../../common/constants'; - -import { IStartPage } from '../../common/startPage/types'; -import { IConfigurationService, IDisposable, IOutputChannel } from '../../common/types'; -import { DataScience } from '../../common/utils/localize'; -import { noop } from '../../common/utils/misc'; -import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; -import { Commands, JUPYTER_OUTPUT_CHANNEL, Telemetry } from '../constants'; -import { - ICodeWatcher, - IDataScienceCodeLensProvider, - IDataScienceCommandListener, - IDataScienceFileSystem, - INotebookEditorProvider -} from '../types'; -import { JupyterCommandLineSelectorCommand } from './commandLineSelector'; -import { ExportCommands } from './exportCommands'; -import { NotebookCommands } from './notebookCommands'; -import { JupyterServerSelectorCommand } from './serverSelector'; - -@injectable() -export class CommandRegistry implements IDisposable { - private readonly disposables: IDisposable[] = []; - constructor( - @inject(IDocumentManager) private documentManager: IDocumentManager, - @inject(IDataScienceCodeLensProvider) private dataScienceCodeLensProvider: IDataScienceCodeLensProvider, - @multiInject(IDataScienceCommandListener) - @optional() - private commandListeners: IDataScienceCommandListener[] | undefined, - @inject(ICommandManager) private readonly commandManager: ICommandManager, - @inject(JupyterServerSelectorCommand) private readonly serverSelectedCommand: JupyterServerSelectorCommand, - @inject(NotebookCommands) private readonly notebookCommands: NotebookCommands, - @inject(JupyterCommandLineSelectorCommand) - private readonly commandLineCommand: JupyterCommandLineSelectorCommand, - @inject(INotebookEditorProvider) private notebookEditorProvider: INotebookEditorProvider, - @inject(IDebugService) private debugService: IDebugService, - @inject(IConfigurationService) private configService: IConfigurationService, - @inject(IApplicationShell) private appShell: IApplicationShell, - @inject(IOutputChannel) @named(JUPYTER_OUTPUT_CHANNEL) private jupyterOutput: IOutputChannel, - @inject(IStartPage) private startPage: IStartPage, - @inject(ExportCommands) private readonly exportCommand: ExportCommands, - @inject(IDataScienceFileSystem) private readonly fs: IDataScienceFileSystem - ) { - this.disposables.push(this.serverSelectedCommand); - this.disposables.push(this.notebookCommands); - } - public register() { - this.commandLineCommand.register(); - this.serverSelectedCommand.register(); - this.notebookCommands.register(); - this.exportCommand.register(); - this.registerCommand(Commands.RunAllCells, this.runAllCells); - this.registerCommand(Commands.RunCell, this.runCell); - this.registerCommand(Commands.RunCurrentCell, this.runCurrentCell); - this.registerCommand(Commands.RunCurrentCellAdvance, this.runCurrentCellAndAdvance); - this.registerCommand(Commands.ExecSelectionInInteractiveWindow, this.runSelectionOrLine); - this.registerCommand(Commands.RunAllCellsAbove, this.runAllCellsAbove); - this.registerCommand(Commands.RunCellAndAllBelow, this.runCellAndAllBelow); - this.registerCommand(Commands.InsertCellBelowPosition, this.insertCellBelowPosition); - this.registerCommand(Commands.InsertCellBelow, this.insertCellBelow); - this.registerCommand(Commands.InsertCellAbove, this.insertCellAbove); - this.registerCommand(Commands.DeleteCells, this.deleteCells); - this.registerCommand(Commands.SelectCell, this.selectCell); - this.registerCommand(Commands.SelectCellContents, this.selectCellContents); - this.registerCommand(Commands.ExtendSelectionByCellAbove, this.extendSelectionByCellAbove); - this.registerCommand(Commands.ExtendSelectionByCellBelow, this.extendSelectionByCellBelow); - this.registerCommand(Commands.MoveCellsUp, this.moveCellsUp); - this.registerCommand(Commands.MoveCellsDown, this.moveCellsDown); - this.registerCommand(Commands.ChangeCellToMarkdown, this.changeCellToMarkdown); - this.registerCommand(Commands.ChangeCellToCode, this.changeCellToCode); - this.registerCommand(Commands.GotoNextCellInFile, this.gotoNextCellInFile); - this.registerCommand(Commands.GotoPrevCellInFile, this.gotoPrevCellInFile); - this.registerCommand(Commands.RunAllCellsAbovePalette, this.runAllCellsAboveFromCursor); - this.registerCommand(Commands.RunCellAndAllBelowPalette, this.runCellAndAllBelowFromCursor); - this.registerCommand(Commands.RunToLine, this.runToLine); - this.registerCommand(Commands.RunFromLine, this.runFromLine); - this.registerCommand(Commands.RunFileInInteractiveWindows, this.runFileInteractive); - this.registerCommand(Commands.DebugFileInInteractiveWindows, this.debugFileInteractive); - this.registerCommand(Commands.AddCellBelow, this.addCellBelow); - this.registerCommand(Commands.RunCurrentCellAndAddBelow, this.runCurrentCellAndAddBelow); - this.registerCommand(Commands.DebugCell, this.debugCell); - this.registerCommand(Commands.DebugStepOver, this.debugStepOver); - this.registerCommand(Commands.DebugContinue, this.debugContinue); - this.registerCommand(Commands.DebugStop, this.debugStop); - this.registerCommand(Commands.DebugCurrentCellPalette, this.debugCurrentCellFromCursor); - this.registerCommand(Commands.CreateNewNotebook, this.createNewNotebook); - this.registerCommand(Commands.ViewJupyterOutput, this.viewJupyterOutput); - this.registerCommand(Commands.GatherQuality, this.reportGatherQuality); - this.registerCommand(Commands.LatestExtension, this.openPythonExtensionPage); - this.registerCommand( - Commands.EnableLoadingWidgetsFrom3rdPartySource, - this.enableLoadingWidgetScriptsFromThirdParty - ); - this.registerCommand(coreCommands.OpenStartPage, this.openStartPage); - if (this.commandListeners) { - this.commandListeners.forEach((listener: IDataScienceCommandListener) => { - listener.register(this.commandManager); - }); - } - } - public dispose() { - this.disposables.forEach((d) => d.dispose()); - } - private registerCommand< - E extends keyof ICommandNameArgumentTypeMapping, - U extends ICommandNameArgumentTypeMapping[E] - // tslint:disable-next-line: no-any - >(command: E, callback: (...args: U) => any) { - const disposable = this.commandManager.registerCommand(command, callback, this); - this.disposables.push(disposable); - } - - private getCodeWatcher(file: Uri | undefined): ICodeWatcher | undefined { - if (file) { - const possibleDocuments = this.documentManager.textDocuments.filter((d) => - this.fs.arePathsSame(d.uri, file) - ); - if (possibleDocuments && possibleDocuments.length === 1) { - return this.dataScienceCodeLensProvider.getCodeWatcher(possibleDocuments[0]); - } else if (possibleDocuments && possibleDocuments.length > 1) { - throw new Error(DataScience.documentMismatch().format(file.fsPath)); - } - } - - return undefined; - } - - private enableLoadingWidgetScriptsFromThirdParty(): void { - if (this.configService.getSettings(undefined).datascience.widgetScriptSources.length > 0) { - return; - } - // Update the setting and once updated, notify user to restart kernel. - this.configService - .updateSetting( - 'dataScience.widgetScriptSources', - ['jsdelivr.com', 'unpkg.com'], - undefined, - ConfigurationTarget.Global - ) - .then(() => { - // Let user know they'll need to restart the kernel. - this.appShell - .showInformationMessage(DataScience.loadThirdPartyWidgetScriptsPostEnabled()) - .then(noop, noop); - }) - .catch(noop); - } - - private async runAllCells(file: Uri | undefined): Promise { - let codeWatcher = this.getCodeWatcher(file); - if (!codeWatcher) { - codeWatcher = this.getCurrentCodeWatcher(); - } - if (codeWatcher) { - return codeWatcher.runAllCells(); - } else { - return; - } - } - - private async runFileInteractive(file: Uri): Promise { - let codeWatcher = this.getCodeWatcher(file); - if (!codeWatcher) { - codeWatcher = this.getCurrentCodeWatcher(); - } - if (codeWatcher) { - return codeWatcher.runFileInteractive(); - } else { - return; - } - } - - private async debugFileInteractive(file: Uri): Promise { - let codeWatcher = this.getCodeWatcher(file); - if (!codeWatcher) { - codeWatcher = this.getCurrentCodeWatcher(); - } - if (codeWatcher) { - return codeWatcher.debugFileInteractive(); - } else { - return; - } - } - - // Note: see codewatcher.ts where the runcell command args are attached. The reason we don't have any - // objects for parameters is because they can't be recreated when passing them through the LiveShare API - private async runCell( - file: Uri, - startLine: number, - startChar: number, - endLine: number, - endChar: number - ): Promise { - const codeWatcher = this.getCodeWatcher(file); - if (codeWatcher) { - return codeWatcher.runCell(new Range(startLine, startChar, endLine, endChar)); - } - } - - private async runAllCellsAbove(file: Uri, stopLine: number, stopCharacter: number): Promise { - if (file) { - const codeWatcher = this.getCodeWatcher(file); - - if (codeWatcher) { - return codeWatcher.runAllCellsAbove(stopLine, stopCharacter); - } - } - } - - private async runCellAndAllBelow(file: Uri | undefined, startLine: number, startCharacter: number): Promise { - if (file) { - const codeWatcher = this.getCodeWatcher(file); - - if (codeWatcher) { - return codeWatcher.runCellAndAllBelow(startLine, startCharacter); - } - } - } - - private async runToLine(): Promise { - const activeCodeWatcher = this.getCurrentCodeWatcher(); - const textEditor = this.documentManager.activeTextEditor; - - if (activeCodeWatcher && textEditor && textEditor.selection) { - return activeCodeWatcher.runToLine(textEditor.selection.start.line); - } - } - - private async runFromLine(): Promise { - const activeCodeWatcher = this.getCurrentCodeWatcher(); - const textEditor = this.documentManager.activeTextEditor; - - if (activeCodeWatcher && textEditor && textEditor.selection) { - return activeCodeWatcher.runFromLine(textEditor.selection.start.line); - } - } - - private async runCurrentCell(): Promise { - const activeCodeWatcher = this.getCurrentCodeWatcher(); - if (activeCodeWatcher) { - return activeCodeWatcher.runCurrentCell(); - } else { - return; - } - } - - private async runCurrentCellAndAdvance(): Promise { - const activeCodeWatcher = this.getCurrentCodeWatcher(); - if (activeCodeWatcher) { - return activeCodeWatcher.runCurrentCellAndAdvance(); - } else { - return; - } - } - - private async runSelectionOrLine(): Promise { - const activeCodeWatcher = this.getCurrentCodeWatcher(); - if (activeCodeWatcher) { - return activeCodeWatcher.runSelectionOrLine(this.documentManager.activeTextEditor); - } else { - return; - } - } - - private async debugCell( - file: Uri, - startLine: number, - startChar: number, - endLine: number, - endChar: number - ): Promise { - if (file) { - const codeWatcher = this.getCodeWatcher(file); - - if (codeWatcher) { - return codeWatcher.debugCell(new Range(startLine, startChar, endLine, endChar)); - } - } - } - - @captureTelemetry(Telemetry.DebugStepOver) - private async debugStepOver(): Promise { - // Make sure that we are in debug mode - if (this.debugService.activeDebugSession) { - this.commandManager.executeCommand('workbench.action.debug.stepOver'); - } - } - - @captureTelemetry(Telemetry.DebugStop) - private async debugStop(): Promise { - // Make sure that we are in debug mode - if (this.debugService.activeDebugSession) { - this.commandManager.executeCommand('workbench.action.debug.stop'); - } - } - - @captureTelemetry(Telemetry.DebugContinue) - private async debugContinue(): Promise { - // Make sure that we are in debug mode - if (this.debugService.activeDebugSession) { - this.commandManager.executeCommand('workbench.action.debug.continue'); - } - } - - @captureTelemetry(Telemetry.AddCellBelow) - private async addCellBelow(): Promise { - await this.getCurrentCodeWatcher()?.addEmptyCellToBottom(); - } - - private async runCurrentCellAndAddBelow(): Promise { - this.getCurrentCodeWatcher()?.runCurrentCellAndAddBelow(); - } - - private async insertCellBelowPosition(): Promise { - this.getCurrentCodeWatcher()?.insertCellBelowPosition(); - } - - private async insertCellBelow(): Promise { - this.getCurrentCodeWatcher()?.insertCellBelow(); - } - - private async insertCellAbove(): Promise { - this.getCurrentCodeWatcher()?.insertCellAbove(); - } - - private async deleteCells(): Promise { - this.getCurrentCodeWatcher()?.deleteCells(); - } - - private async selectCell(): Promise { - this.getCurrentCodeWatcher()?.selectCell(); - } - - private async selectCellContents(): Promise { - this.getCurrentCodeWatcher()?.selectCellContents(); - } - - private async extendSelectionByCellAbove(): Promise { - this.getCurrentCodeWatcher()?.extendSelectionByCellAbove(); - } - - private async extendSelectionByCellBelow(): Promise { - this.getCurrentCodeWatcher()?.extendSelectionByCellBelow(); - } - - private async moveCellsUp(): Promise { - this.getCurrentCodeWatcher()?.moveCellsUp(); - } - - private async moveCellsDown(): Promise { - this.getCurrentCodeWatcher()?.moveCellsDown(); - } - - private async changeCellToMarkdown(): Promise { - this.getCurrentCodeWatcher()?.changeCellToMarkdown(); - } - - private async changeCellToCode(): Promise { - this.getCurrentCodeWatcher()?.changeCellToCode(); - } - - private async gotoNextCellInFile(): Promise { - this.getCurrentCodeWatcher()?.gotoNextCell(); - } - - private async gotoPrevCellInFile(): Promise { - this.getCurrentCodeWatcher()?.gotoPreviousCell(); - } - - private async runAllCellsAboveFromCursor(): Promise { - const currentCodeLens = this.getCurrentCodeLens(); - if (currentCodeLens) { - const activeCodeWatcher = this.getCurrentCodeWatcher(); - if (activeCodeWatcher) { - return activeCodeWatcher.runAllCellsAbove( - currentCodeLens.range.start.line, - currentCodeLens.range.start.character - ); - } - } else { - return; - } - } - - private async runCellAndAllBelowFromCursor(): Promise { - const currentCodeLens = this.getCurrentCodeLens(); - if (currentCodeLens) { - const activeCodeWatcher = this.getCurrentCodeWatcher(); - if (activeCodeWatcher) { - return activeCodeWatcher.runCellAndAllBelow( - currentCodeLens.range.start.line, - currentCodeLens.range.start.character - ); - } - } else { - return; - } - } - - private async debugCurrentCellFromCursor(): Promise { - const currentCodeLens = this.getCurrentCodeLens(); - if (currentCodeLens) { - const activeCodeWatcher = this.getCurrentCodeWatcher(); - if (activeCodeWatcher) { - return activeCodeWatcher.debugCurrentCell(); - } - } else { - return; - } - } - - private async createNewNotebook(): Promise { - await this.notebookEditorProvider.createNew(); - } - - private async openStartPage(): Promise { - sendTelemetryEvent(Telemetry.StartPageOpenedFromCommandPalette); - return this.startPage.open(); - } - - private viewJupyterOutput() { - this.jupyterOutput.show(true); - } - - private getCurrentCodeLens(): CodeLens | undefined { - const activeEditor = this.documentManager.activeTextEditor; - const activeCodeWatcher = this.getCurrentCodeWatcher(); - if (activeEditor && activeCodeWatcher) { - // Find the cell that matches - return activeCodeWatcher.getCodeLenses().find((c: CodeLens) => { - if ( - c.range.end.line >= activeEditor.selection.anchor.line && - c.range.start.line <= activeEditor.selection.anchor.line - ) { - return true; - } - return false; - }); - } - } - // Get our matching code watcher for the active document - private getCurrentCodeWatcher(): ICodeWatcher | undefined { - const activeEditor = this.documentManager.activeTextEditor; - if (!activeEditor || !activeEditor.document) { - return undefined; - } - - // Ask our code lens provider to find the matching code watcher for the current document - return this.dataScienceCodeLensProvider.getCodeWatcher(activeEditor.document); - } - - private reportGatherQuality(val: string) { - sendTelemetryEvent(Telemetry.GatherQualityReport, undefined, { result: val[0] === 'no' ? 'no' : 'yes' }); - env.openExternal(Uri.parse(`https://aka.ms/gatherfeedback?succeed=${val[0]}`)); - } - - private openPythonExtensionPage() { - env.openExternal(Uri.parse(`https://marketplace.visualstudio.com/items?itemName=ms-python.python`)); - } -} diff --git a/src/client/datascience/commands/exportCommands.ts b/src/client/datascience/commands/exportCommands.ts deleted file mode 100644 index b8be186195e0..000000000000 --- a/src/client/datascience/commands/exportCommands.ts +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { QuickPickItem, QuickPickOptions, Uri } from 'vscode'; -import { getLocString } from '../../../datascience-ui/react-common/locReactSide'; -import { ICommandNameArgumentTypeMapping } from '../../common/application/commands'; -import { IApplicationShell, ICommandManager } from '../../common/application/types'; - -import { IDisposable } from '../../common/types'; -import { DataScience } from '../../common/utils/localize'; -import { isUri } from '../../common/utils/misc'; -import { sendTelemetryEvent } from '../../telemetry'; -import { Commands, Telemetry } from '../constants'; -import { ExportManager } from '../export/exportManager'; -import { ExportFormat, IExportManager } from '../export/types'; -import { IDataScienceFileSystem, INotebookEditorProvider, INotebookModel } from '../types'; - -interface IExportQuickPickItem extends QuickPickItem { - handler(): void; -} - -@injectable() -export class ExportCommands implements IDisposable { - private readonly disposables: IDisposable[] = []; - constructor( - @inject(ICommandManager) private readonly commandManager: ICommandManager, - @inject(IExportManager) private exportManager: ExportManager, - @inject(IApplicationShell) private readonly applicationShell: IApplicationShell, - @inject(INotebookEditorProvider) private readonly notebookProvider: INotebookEditorProvider, - @inject(IDataScienceFileSystem) private readonly fs: IDataScienceFileSystem - ) {} - public register() { - this.registerCommand(Commands.ExportAsPythonScript, (model) => this.export(model, ExportFormat.python)); - this.registerCommand(Commands.ExportToHTML, (model, defaultFileName?) => - this.export(model, ExportFormat.html, defaultFileName) - ); - this.registerCommand(Commands.ExportToPDF, (model, defaultFileName?) => - this.export(model, ExportFormat.pdf, defaultFileName) - ); - this.registerCommand(Commands.Export, (model, defaultFileName?) => - this.export(model, undefined, defaultFileName) - ); - } - - public dispose() { - this.disposables.forEach((d) => d.dispose()); - } - - private registerCommand< - E extends keyof ICommandNameArgumentTypeMapping, - U extends ICommandNameArgumentTypeMapping[E] - // tslint:disable-next-line: no-any - >(command: E, callback: (...args: U) => any) { - const disposable = this.commandManager.registerCommand(command, callback, this); - this.disposables.push(disposable); - } - - private async export(modelOrUri: Uri | INotebookModel, exportMethod?: ExportFormat, defaultFileName?: string) { - defaultFileName = typeof defaultFileName === 'string' ? defaultFileName : undefined; - let model: INotebookModel | undefined; - if (modelOrUri && isUri(modelOrUri)) { - const uri = modelOrUri; - const editor = this.notebookProvider.editors.find((item) => this.fs.arePathsSame(item.file, uri)); - if (editor && editor.model) { - model = editor.model; - } - } else { - model = modelOrUri; - } - if (!model) { - // if no model was passed then this was called from the command palette, - // so we need to get the active editor - const activeEditor = this.notebookProvider.activeEditor; - if (!activeEditor || !activeEditor.model) { - return; - } - model = activeEditor.model; - - if (exportMethod) { - sendTelemetryEvent(Telemetry.ExportNotebookAsCommand, undefined, { format: exportMethod }); - } - } - - if (exportMethod) { - await this.exportManager.export(exportMethod, model, defaultFileName); - } else { - // if we don't have an export method we need to ask for one and display the - // quickpick menu - const pickedItem = await this.showExportQuickPickMenu(model, defaultFileName).then((item) => item); - if (pickedItem !== undefined) { - pickedItem.handler(); - } else { - sendTelemetryEvent(Telemetry.ClickedExportNotebookAsQuickPick); - } - } - } - - private getExportQuickPickItems(model: INotebookModel, defaultFileName?: string): IExportQuickPickItem[] { - return [ - { - label: DataScience.exportPythonQuickPickLabel(), - picked: true, - handler: () => { - sendTelemetryEvent(Telemetry.ClickedExportNotebookAsQuickPick, undefined, { - format: ExportFormat.python - }); - this.commandManager.executeCommand(Commands.ExportAsPythonScript, model); - } - }, - { - label: DataScience.exportHTMLQuickPickLabel(), - picked: false, - handler: () => { - sendTelemetryEvent(Telemetry.ClickedExportNotebookAsQuickPick, undefined, { - format: ExportFormat.html - }); - this.commandManager.executeCommand(Commands.ExportToHTML, model, defaultFileName); - } - }, - { - label: DataScience.exportPDFQuickPickLabel(), - picked: false, - handler: () => { - sendTelemetryEvent(Telemetry.ClickedExportNotebookAsQuickPick, undefined, { - format: ExportFormat.pdf - }); - this.commandManager.executeCommand(Commands.ExportToPDF, model, defaultFileName); - } - } - ]; - } - - private async showExportQuickPickMenu( - model: INotebookModel, - defaultFileName?: string - ): Promise { - const items = this.getExportQuickPickItems(model, defaultFileName); - - const options: QuickPickOptions = { - ignoreFocusOut: false, - matchOnDescription: true, - matchOnDetail: true, - placeHolder: getLocString('DataScience.exportAsQuickPickPlaceholder', 'Export As...') - }; - - return this.applicationShell.showQuickPick(items, options); - } -} diff --git a/src/client/datascience/commands/notebookCommands.ts b/src/client/datascience/commands/notebookCommands.ts deleted file mode 100644 index 7c96bd7590a7..000000000000 --- a/src/client/datascience/commands/notebookCommands.ts +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { Uri } from 'vscode'; -import { ICommandManager } from '../../common/application/types'; -import { IDisposable } from '../../common/types'; -import { Commands } from '../constants'; -import { - getDisplayNameOrNameOfKernelConnection, - kernelConnectionMetadataHasKernelModel -} from '../jupyter/kernels/helpers'; -import { KernelSelector } from '../jupyter/kernels/kernelSelector'; -import { KernelSwitcher } from '../jupyter/kernels/kernelSwitcher'; -import { KernelConnectionMetadata } from '../jupyter/kernels/types'; -import { IInteractiveWindowProvider, INotebookEditorProvider, INotebookProvider, ISwitchKernelOptions } from '../types'; - -@injectable() -export class NotebookCommands implements IDisposable { - private readonly disposables: IDisposable[] = []; - constructor( - @inject(ICommandManager) private readonly commandManager: ICommandManager, - @inject(INotebookEditorProvider) private notebookEditorProvider: INotebookEditorProvider, - @inject(IInteractiveWindowProvider) private interactiveWindowProvider: IInteractiveWindowProvider, - @inject(INotebookProvider) private readonly notebookProvider: INotebookProvider, - @inject(KernelSelector) private readonly kernelSelector: KernelSelector, - @inject(KernelSwitcher) private readonly kernelSwitcher: KernelSwitcher - ) {} - public register() { - this.disposables.push( - this.commandManager.registerCommand(Commands.SwitchJupyterKernel, this.switchKernel, this), - this.commandManager.registerCommand(Commands.SetJupyterKernel, this.setKernel, this), - this.commandManager.registerCommand(Commands.NotebookEditorCollapseAllCells, this.collapseAll, this), - this.commandManager.registerCommand(Commands.NotebookEditorExpandAllCells, this.expandAll, this) - ); - } - public dispose() { - this.disposables.forEach((d) => d.dispose()); - } - - private collapseAll() { - if (this.notebookEditorProvider.activeEditor) { - this.notebookEditorProvider.activeEditor.collapseAllCells(); - } - } - - private expandAll() { - if (this.notebookEditorProvider.activeEditor) { - this.notebookEditorProvider.activeEditor.expandAllCells(); - } - } - - private async switchKernel(options: ISwitchKernelOptions | undefined) { - // If no identity, spec, or resource, look at the active editor or interactive window. - // Only one is possible to be active at any point in time - if (!options) { - options = this.notebookEditorProvider.activeEditor - ? { - identity: this.notebookEditorProvider.activeEditor.file, - resource: this.notebookEditorProvider.activeEditor.file, - currentKernelDisplayName: - this.notebookEditorProvider.activeEditor.model.metadata?.kernelspec?.display_name || - this.notebookEditorProvider.activeEditor.model.metadata?.kernelspec?.name - } - : { - identity: this.interactiveWindowProvider.activeWindow?.identity, - resource: this.interactiveWindowProvider.activeWindow?.owner, - currentKernelDisplayName: getDisplayNameOrNameOfKernelConnection( - this.interactiveWindowProvider.activeWindow?.notebook?.getKernelConnection() - ) - }; - } - if (options.identity) { - // Make sure we have a connection or we can't get remote kernels. - const connection = await this.notebookProvider.connect({ getOnly: false, disableUI: false }); - - // Select a new kernel using the connection information - const kernel = await this.kernelSelector.selectJupyterKernel( - options.identity, - connection, - connection?.type || this.notebookProvider.type, - options.currentKernelDisplayName - ); - if (kernel) { - await this.setKernel(kernel, options.identity, options.resource); - } - } - } - - private async setKernel(kernel: KernelConnectionMetadata, identity: Uri, resource: Uri | undefined) { - const specOrModel = kernelConnectionMetadataHasKernelModel(kernel) ? kernel.kernelModel : kernel.kernelSpec; - if (specOrModel) { - const notebook = await this.notebookProvider.getOrCreateNotebook({ - resource, - identity, - getOnly: true - }); - - // If we have a notebook, change its kernel now - if (notebook) { - return this.kernelSwitcher.switchKernelWithRetry(notebook, kernel); - } else { - this.notebookProvider.firePotentialKernelChanged(identity, kernel); - } - } - } -} diff --git a/src/client/datascience/commands/serverSelector.ts b/src/client/datascience/commands/serverSelector.ts deleted file mode 100644 index ea2956f0d331..000000000000 --- a/src/client/datascience/commands/serverSelector.ts +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { ICommandManager } from '../../common/application/types'; -import { IDisposable } from '../../common/types'; -import { Commands } from '../constants'; -import { JupyterServerSelector } from '../jupyter/serverSelector'; - -@injectable() -export class JupyterServerSelectorCommand implements IDisposable { - private readonly disposables: IDisposable[] = []; - constructor( - @inject(ICommandManager) private readonly commandManager: ICommandManager, - @inject(JupyterServerSelector) private readonly serverSelector: JupyterServerSelector - ) {} - public register() { - this.disposables.push( - this.commandManager.registerCommand( - Commands.SelectJupyterURI, - () => this.serverSelector.selectJupyterURI(true), - this.serverSelector - ) - ); - } - public dispose() { - this.disposables.forEach((d) => d.dispose()); - } -} diff --git a/src/client/datascience/common.ts b/src/client/datascience/common.ts deleted file mode 100644 index a1ab3a6935eb..000000000000 --- a/src/client/datascience/common.ts +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import type { nbformat } from '@jupyterlab/coreutils'; -import * as os from 'os'; -import { parse, SemVer } from 'semver'; -import { Memento, Uri } from 'vscode'; -import { splitMultilineString } from '../../datascience-ui/common'; -import { traceError, traceInfo } from '../common/logger'; -import { IPythonExecutionFactory } from '../common/process/types'; -import { DataScience } from '../common/utils/localize'; -import { noop } from '../common/utils/misc'; -import { Settings } from './constants'; -import { ICell, IDataScienceFileSystem } from './types'; - -// Can't figure out a better way to do this. Enumerate -// the allowed keys of different output formats. -const dummyStreamObj: nbformat.IStream = { - output_type: 'stream', - name: 'stdout', - text: '' -}; -const dummyErrorObj: nbformat.IError = { - output_type: 'error', - ename: '', - evalue: '', - traceback: [''] -}; -const dummyDisplayObj: nbformat.IDisplayData = { - output_type: 'display_data', - data: {}, - metadata: {} -}; -const dummyExecuteResultObj: nbformat.IExecuteResult = { - output_type: 'execute_result', - name: '', - execution_count: 0, - data: {}, - metadata: {} -}; -export const AllowedCellOutputKeys = { - ['stream']: new Set(Object.keys(dummyStreamObj)), - ['error']: new Set(Object.keys(dummyErrorObj)), - ['display_data']: new Set(Object.keys(dummyDisplayObj)), - ['execute_result']: new Set(Object.keys(dummyExecuteResultObj)) -}; - -export function getSavedUriList(globalState: Memento): { uri: string; time: number; displayName?: string }[] { - const uriList = globalState.get<{ uri: string; time: number; displayName?: string }[]>( - Settings.JupyterServerUriList - ); - return uriList - ? uriList.sort((a, b) => { - return b.time - a.time; - }) - : []; -} -export function addToUriList(globalState: Memento, uri: string, time: number, displayName: string) { - const uriList = getSavedUriList(globalState); - - const editList = uriList.filter((f, i) => { - return f.uri !== uri && i < Settings.JupyterServerUriListMax - 1; - }); - editList.splice(0, 0, { uri, time, displayName }); - - globalState.update(Settings.JupyterServerUriList, editList).then(noop, noop); -} - -function fixupOutput(output: nbformat.IOutput): nbformat.IOutput { - let allowedKeys: Set; - switch (output.output_type) { - case 'stream': - case 'error': - case 'execute_result': - case 'display_data': - allowedKeys = AllowedCellOutputKeys[output.output_type]; - break; - default: - return output; - } - const result = { ...output }; - for (const k of Object.keys(output)) { - if (!allowedKeys.has(k)) { - delete result[k]; - } - } - return result; -} - -export function pruneCell(cell: nbformat.ICell): nbformat.ICell { - // Source is usually a single string on input. Convert back to an array - const result = ({ - ...cell, - source: splitMultilineString(cell.source) - // tslint:disable-next-line: no-any - } as any) as nbformat.ICell; // nyc (code coverage) barfs on this so just trick it. - - // Remove outputs and execution_count from non code cells - if (result.cell_type !== 'code') { - // Map to any so nyc will build. - // tslint:disable-next-line: no-any - delete (result).outputs; - // tslint:disable-next-line: no-any - delete (result).execution_count; - } else { - // Clean outputs from code cells - result.outputs = result.outputs ? (result.outputs as nbformat.IOutput[]).map(fixupOutput) : []; - } - - return result; -} - -export function traceCellResults(prefix: string, results: ICell[]) { - if (results.length > 0 && results[0].data.cell_type === 'code') { - const cell = results[0].data as nbformat.ICodeCell; - const error = cell.outputs && cell.outputs[0] ? cell.outputs[0].evalue : undefined; - if (error) { - traceError(`${prefix} Error : ${error}`); - } else if (cell.outputs && cell.outputs[0]) { - if (cell.outputs[0].output_type.includes('image')) { - traceInfo(`${prefix} Output: image`); - } else { - const data = cell.outputs[0].data; - const text = cell.outputs[0].text; - traceInfo(`${prefix} Output: ${text || JSON.stringify(data)}`); - } - } - } else { - traceInfo(`${prefix} no output.`); - } -} - -export function translateKernelLanguageToMonaco(kernelLanguage: string): string { - // The only known translation is C# to csharp at the moment - if (kernelLanguage === 'C#' || kernelLanguage === 'c#') { - return 'csharp'; - } - return kernelLanguage.toLowerCase(); -} - -export function generateNewNotebookUri( - counter: number, - rootFolder: string | undefined, - title?: string, - forVSCodeNotebooks?: boolean -): Uri { - // However if there are files already on disk, we should be able to overwrite them because - // they will only ever be used by 'open' editors. So just use the current counter for our untitled count. - const fileName = title ? `${title}-${counter}.ipynb` : `${DataScience.untitledNotebookFileName()}-${counter}.ipynb`; - // Turn this back into an untitled - if (forVSCodeNotebooks) { - return Uri.file(fileName).with({ scheme: 'untitled', path: fileName }); - } else { - return Uri.joinPath(rootFolder ? Uri.file(rootFolder) : Uri.file(os.tmpdir()), fileName).with({ - scheme: 'untitled' - }); - } -} - -export async function getRealPath( - fs: IDataScienceFileSystem, - execFactory: IPythonExecutionFactory, - pythonPath: string, - expectedPath: string -): Promise { - if (await fs.localDirectoryExists(expectedPath)) { - return expectedPath; - } - if (await fs.localFileExists(expectedPath)) { - return expectedPath; - } - - // If can't find the path, try turning it into a real path. - const pythonRunner = await execFactory.create({ pythonPath }); - const result = await pythonRunner.exec( - ['-c', `import os;print(os.path.realpath("${expectedPath.replace(/\\/g, '\\\\')}"))`], - { - throwOnStdErr: false, - encoding: 'utf-8' - } - ); - if (result && result.stdout) { - const trimmed = result.stdout.trim(); - if (await fs.localDirectoryExists(trimmed)) { - return trimmed; - } - if (await fs.localFileExists(trimmed)) { - return trimmed; - } - } -} - -// For the given string parse it out to a SemVer or return undefined -export function parseSemVer(versionString: string): SemVer | undefined { - const versionMatch = /^\s*(\d+)\.(\d+)\.(.+)\s*$/.exec(versionString); - if (versionMatch && versionMatch.length > 2) { - const major = parseInt(versionMatch[1], 10); - const minor = parseInt(versionMatch[2], 10); - const build = parseInt(versionMatch[3], 10); - return parse(`${major}.${minor}.${build}`, true) ?? undefined; - } -} diff --git a/src/client/datascience/constants.ts b/src/client/datascience/constants.ts deleted file mode 100644 index 0ff4f8f4f688..000000000000 --- a/src/client/datascience/constants.ts +++ /dev/null @@ -1,595 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import * as path from 'path'; -import { EXTENSION_ROOT_DIR, PYTHON_LANGUAGE } from '../common/constants'; -import { IS_WINDOWS } from '../common/platform/constants'; -import { IVariableQuery } from '../common/types'; - -export const DefaultTheme = 'Default Light+'; -// Identifier for the output panel that will display the output from the Jupyter Server. -export const JUPYTER_OUTPUT_CHANNEL = 'JUPYTER_OUTPUT_CHANNEL'; - -// Python Module to be used when instantiating the Python Daemon. -export const JupyterDaemonModule = 'vscode_datascience_helpers.jupyter_daemon'; -export const KernelLauncherDaemonModule = 'vscode_datascience_helpers.kernel_launcher_daemon'; - -export const GatherExtension = 'ms-python.gather'; - -// List of 'language' names that we know about. All should be lower case as that's how we compare. -export const KnownNotebookLanguages: string[] = [ - 'python', - 'r', - 'julia', - 'c++', - 'c#', - 'f#', - 'scala', - 'haskell', - 'bash', - 'cling', - 'sas' -]; - -export namespace Commands { - export const RunAllCells = 'python.datascience.runallcells'; - export const RunAllCellsAbove = 'python.datascience.runallcellsabove'; - export const RunCellAndAllBelow = 'python.datascience.runcellandallbelow'; - export const SetJupyterKernel = 'python.datascience.setKernel'; - export const SwitchJupyterKernel = 'python.datascience.switchKernel'; - export const RunAllCellsAbovePalette = 'python.datascience.runallcellsabove.palette'; - export const RunCellAndAllBelowPalette = 'python.datascience.runcurrentcellandallbelow.palette'; - export const RunToLine = 'python.datascience.runtoline'; - export const RunFromLine = 'python.datascience.runfromline'; - export const RunCell = 'python.datascience.runcell'; - export const RunCurrentCell = 'python.datascience.runcurrentcell'; - export const RunCurrentCellAdvance = 'python.datascience.runcurrentcelladvance'; - export const CreateNewInteractive = 'python.datascience.createnewinteractive'; - export const ImportNotebook = 'python.datascience.importnotebook'; - export const ImportNotebookFile = 'python.datascience.importnotebookfile'; - export const OpenNotebook = 'python.datascience.opennotebook'; - export const OpenNotebookInPreviewEditor = 'python.datascience.opennotebookInPreviewEditor'; - export const SelectJupyterURI = 'python.datascience.selectjupyteruri'; - export const SelectJupyterCommandLine = 'python.datascience.selectjupytercommandline'; - export const ExportFileAsNotebook = 'python.datascience.exportfileasnotebook'; - export const ExportFileAndOutputAsNotebook = 'python.datascience.exportfileandoutputasnotebook'; - export const UndoCells = 'python.datascience.undocells'; - export const RedoCells = 'python.datascience.redocells'; - export const RemoveAllCells = 'python.datascience.removeallcells'; - export const InterruptKernel = 'python.datascience.interruptkernel'; - export const RestartKernel = 'python.datascience.restartkernel'; - export const NotebookEditorUndoCells = 'python.datascience.notebookeditor.undocells'; - export const NotebookEditorRedoCells = 'python.datascience.notebookeditor.redocells'; - export const NotebookEditorRemoveAllCells = 'python.datascience.notebookeditor.removeallcells'; - export const NotebookEditorInterruptKernel = 'python.datascience.notebookeditor.interruptkernel'; - export const NotebookEditorRestartKernel = 'python.datascience.notebookeditor.restartkernel'; - export const NotebookEditorRunAllCells = 'python.datascience.notebookeditor.runallcells'; - export const NotebookEditorRunSelectedCell = 'python.datascience.notebookeditor.runselectedcell'; - export const NotebookEditorAddCellBelow = 'python.datascience.notebookeditor.addcellbelow'; - export const ExpandAllCells = 'python.datascience.expandallcells'; - export const CollapseAllCells = 'python.datascience.collapseallcells'; - export const ExportOutputAsNotebook = 'python.datascience.exportoutputasnotebook'; - export const ExecSelectionInInteractiveWindow = 'python.datascience.execSelectionInteractive'; - export const RunFileInInteractiveWindows = 'python.datascience.runFileInteractive'; - export const DebugFileInInteractiveWindows = 'python.datascience.debugFileInteractive'; - export const AddCellBelow = 'python.datascience.addcellbelow'; - export const DebugCurrentCellPalette = 'python.datascience.debugcurrentcell.palette'; - export const DebugCell = 'python.datascience.debugcell'; - export const DebugStepOver = 'python.datascience.debugstepover'; - export const DebugContinue = 'python.datascience.debugcontinue'; - export const DebugStop = 'python.datascience.debugstop'; - export const RunCurrentCellAndAddBelow = 'python.datascience.runcurrentcellandaddbelow'; - export const InsertCellBelowPosition = 'python.datascience.insertCellBelowPosition'; - export const InsertCellBelow = 'python.datascience.insertCellBelow'; - export const InsertCellAbove = 'python.datascience.insertCellAbove'; - export const DeleteCells = 'python.datascience.deleteCells'; - export const SelectCell = 'python.datascience.selectCell'; - export const SelectCellContents = 'python.datascience.selectCellContents'; - export const ExtendSelectionByCellAbove = 'python.datascience.extendSelectionByCellAbove'; - export const ExtendSelectionByCellBelow = 'python.datascience.extendSelectionByCellBelow'; - export const MoveCellsUp = 'python.datascience.moveCellsUp'; - export const MoveCellsDown = 'python.datascience.moveCellsDown'; - export const ChangeCellToMarkdown = 'python.datascience.changeCellToMarkdown'; - export const ChangeCellToCode = 'python.datascience.changeCellToCode'; - export const GotoNextCellInFile = 'python.datascience.gotoNextCellInFile'; - export const GotoPrevCellInFile = 'python.datascience.gotoPrevCellInFile'; - export const ScrollToCell = 'python.datascience.scrolltocell'; - export const CreateNewNotebook = 'python.datascience.createnewnotebook'; - export const ViewJupyterOutput = 'python.datascience.viewJupyterOutput'; - export const ExportAsPythonScript = 'python.datascience.exportAsPythonScript'; - export const ExportToHTML = 'python.datascience.exportToHTML'; - export const ExportToPDF = 'python.datascience.exportToPDF'; - export const Export = 'python.datascience.export'; - export const SaveNotebookNonCustomEditor = 'python.datascience.notebookeditor.save'; - export const SaveAsNotebookNonCustomEditor = 'python.datascience.notebookeditor.saveAs'; - export const OpenNotebookNonCustomEditor = 'python.datascience.notebookeditor.open'; - export const GatherQuality = 'python.datascience.gatherquality'; - export const LatestExtension = 'python.datascience.latestExtension'; - export const TrustNotebook = 'python.datascience.notebookeditor.trust'; - export const EnableLoadingWidgetsFrom3rdPartySource = - 'python.datascience.enableLoadingWidgetScriptsFromThirdPartySource'; - export const NotebookEditorExpandAllCells = 'python.datascience.notebookeditor.expandallcells'; - export const NotebookEditorCollapseAllCells = 'python.datascience.notebookeditor.collapseallcells'; -} - -export namespace CodeLensCommands { - // If not specified in the options this is the default set of commands in our design time code lenses - export const DefaultDesignLenses = [Commands.RunCurrentCell, Commands.RunAllCellsAbove, Commands.DebugCell]; - // If not specified in the options this is the default set of commands in our debug time code lenses - export const DefaultDebuggingLenses = [Commands.DebugContinue, Commands.DebugStop, Commands.DebugStepOver]; - // These are the commands that are allowed at debug time - export const DebuggerCommands = [Commands.DebugContinue, Commands.DebugStop, Commands.DebugStepOver]; -} - -export namespace EditorContexts { - export const HasCodeCells = 'python.datascience.hascodecells'; - export const DataScienceEnabled = 'python.datascience.featureenabled'; - export const HaveInteractiveCells = 'python.datascience.haveinteractivecells'; - export const HaveRedoableCells = 'python.datascience.haveredoablecells'; - export const HaveInteractive = 'python.datascience.haveinteractive'; - export const IsInteractiveActive = 'python.datascience.isinteractiveactive'; - export const OwnsSelection = 'python.datascience.ownsSelection'; - export const HaveNativeCells = 'python.datascience.havenativecells'; - export const HaveNativeRedoableCells = 'python.datascience.havenativeredoablecells'; - export const HaveNative = 'python.datascience.havenative'; - export const IsNativeActive = 'python.datascience.isnativeactive'; - export const IsInteractiveOrNativeActive = 'python.datascience.isinteractiveornativeactive'; - export const IsPythonOrNativeActive = 'python.datascience.ispythonornativeactive'; - export const IsPythonOrInteractiveActive = 'python.datascience.ispythonorinteractiveeactive'; - export const IsPythonOrInteractiveOrNativeActive = 'python.datascience.ispythonorinteractiveornativeeactive'; - export const HaveCellSelected = 'python.datascience.havecellselected'; - export const IsNotebookTrusted = 'python.datascience.isnotebooktrusted'; - export const CanRestartNotebookKernel = 'python.datascience.notebookeditor.canrestartNotebookkernel'; -} - -export namespace RegExpValues { - export const PythonCellMarker = /^(#\s*%%|#\s*\|#\s*In\[\d*?\]|#\s*In\[ \])/; - export const PythonMarkdownCellMarker = /^(#\s*%%\s*\[markdown\]|#\s*\)/; - export const CheckJupyterRegEx = IS_WINDOWS ? /^jupyter?\.exe$/ : /^jupyter?$/; - export const PyKernelOutputRegEx = /.*\s+(.+)$/m; - export const KernelSpecOutputRegEx = /^\s*(\S+)\s+(\S+)$/; - // This next one has to be a string because uglifyJS isn't handling the groups. We use named-js-regexp to parse it - // instead. - export const UrlPatternRegEx = - '(?https?:\\/\\/)((\\(.+\\s+or\\s+(?.+)\\))|(?[^\\s]+))(?:.+)'; - export interface IUrlPatternGroupType { - LOCAL: string | undefined; - PREFIX: string | undefined; - REST: string | undefined; - IP: string | undefined; - } - export const HttpPattern = /https?:\/\//; - export const ExtractPortRegex = /https?:\/\/[^\s]+:(\d+)[^\s]+/; - export const ConvertToRemoteUri = /(https?:\/\/)([^\s])+(:\d+[^\s]*)/; - export const ParamsExractorRegEx = /\S+\((.*)\)\s*{/; - export const ArgsSplitterRegEx = /([^\s,]+)/; - export const ShapeSplitterRegEx = /.*,\s*(\d+).*/; - export const SvgHeightRegex = /(\/m; -} - -export enum Telemetry { - ImportNotebook = 'DATASCIENCE.IMPORT_NOTEBOOK', - RunCell = 'DATASCIENCE.RUN_CELL', - RunCurrentCell = 'DATASCIENCE.RUN_CURRENT_CELL', - RunCurrentCellAndAdvance = 'DATASCIENCE.RUN_CURRENT_CELL_AND_ADVANCE', - RunAllCells = 'DATASCIENCE.RUN_ALL_CELLS', - RunAllCellsAbove = 'DATASCIENCE.RUN_ALL_CELLS_ABOVE', - RunCellAndAllBelow = 'DATASCIENCE.RUN_CELL_AND_ALL_BELOW', - AddEmptyCellToBottom = 'DATASCIENCE.RUN_ADD_EMPTY_CELL_TO_BOTTOM', - RunCurrentCellAndAddBelow = 'DATASCIENCE.RUN_CURRENT_CELL_AND_ADD_BELOW', - InsertCellBelowPosition = 'DATASCIENCE.RUN_INSERT_CELL_BELOW_POSITION', - InsertCellBelow = 'DATASCIENCE.RUN_INSERT_CELL_BELOW', - InsertCellAbove = 'DATASCIENCE.RUN_INSERT_CELL_ABOVE', - DeleteCells = 'DATASCIENCE.RUN_DELETE_CELLS', - SelectCell = 'DATASCIENCE.RUN_SELECT_CELL', - SelectCellContents = 'DATASCIENCE.RUN_SELECT_CELL_CONTENTS', - ExtendSelectionByCellAbove = 'DATASCIENCE.RUN_EXTEND_SELECTION_BY_CELL_ABOVE', - ExtendSelectionByCellBelow = 'DATASCIENCE.RUN_EXTEND_SELECTION_BY_CELL_BELOW', - MoveCellsUp = 'DATASCIENCE.RUN_MOVE_CELLS_UP', - MoveCellsDown = 'DATASCIENCE.RUN_MOVE_CELLS_DOWN', - ChangeCellToMarkdown = 'DATASCIENCE.RUN_CHANGE_CELL_TO_MARKDOWN', - ChangeCellToCode = 'DATASCIENCE.RUN_CHANGE_CELL_TO_CODE', - GotoNextCellInFile = 'DATASCIENCE.GOTO_NEXT_CELL_IN_FILE', - GotoPrevCellInFile = 'DATASCIENCE.GOTO_PREV_CELL_IN_FILE', - RunSelectionOrLine = 'DATASCIENCE.RUN_SELECTION_OR_LINE', - RunToLine = 'DATASCIENCE.RUN_TO_LINE', - RunFromLine = 'DATASCIENCE.RUN_FROM_LINE', - DeleteAllCells = 'DATASCIENCE.DELETE_ALL_CELLS', - DeleteCell = 'DATASCIENCE.DELETE_CELL', - GotoSourceCode = 'DATASCIENCE.GOTO_SOURCE', - CopySourceCode = 'DATASCIENCE.COPY_SOURCE', - RestartKernel = 'DS_INTERNAL.RESTART_KERNEL', - RestartKernelCommand = 'DATASCIENCE.RESTART_KERNEL_COMMAND', - ExportNotebookInteractive = 'DATASCIENCE.EXPORT_NOTEBOOK', - Undo = 'DATASCIENCE.UNDO', - Redo = 'DATASCIENCE.REDO', - /** - * Saving a notebook - */ - Save = 'DATASCIENCE.SAVE', - CellCount = 'DS_INTERNAL.CELL_COUNT', - /** - * Whether auto save feature in VS Code is enabled or not. - */ - CreateNewInteractive = 'DATASCIENCE.CREATE_NEW_INTERACTIVE', - ExpandAll = 'DATASCIENCE.EXPAND_ALL', - CollapseAll = 'DATASCIENCE.COLLAPSE_ALL', - SelectJupyterURI = 'DATASCIENCE.SELECT_JUPYTER_URI', - SelectLocalJupyterKernel = 'DATASCIENCE.SELECT_LOCAL_JUPYTER_KERNEL', - SelectRemoteJupyterKernel = 'DATASCIENCE.SELECT_REMOTE_JUPYTER_KERNEL', - SetJupyterURIToLocal = 'DATASCIENCE.SET_JUPYTER_URI_LOCAL', - SetJupyterURIToUserSpecified = 'DATASCIENCE.SET_JUPYTER_URI_USER_SPECIFIED', - Interrupt = 'DATASCIENCE.INTERRUPT', - /** - * Exporting from the interactive window - */ - ExportPythonFileInteractive = 'DATASCIENCE.EXPORT_PYTHON_FILE', - ExportPythonFileAndOutputInteractive = 'DATASCIENCE.EXPORT_PYTHON_FILE_AND_OUTPUT', - /** - * User clicked export as quick pick button - */ - ClickedExportNotebookAsQuickPick = 'DATASCIENCE.CLICKED_EXPORT_NOTEBOOK_AS_QUICK_PICK', - /** - * exported a notebook - */ - ExportNotebookAs = 'DATASCIENCE.EXPORT_NOTEBOOK_AS', - /** - * User invokes export as format from command pallet - */ - ExportNotebookAsCommand = 'DATASCIENCE.EXPORT_NOTEBOOK_AS_COMMAND', - /** - * An export to a specific format failed - */ - ExportNotebookAsFailed = 'DATASCIENCE.EXPORT_NOTEBOOK_AS_FAILED', - - StartJupyter = 'DS_INTERNAL.JUPYTERSTARTUPCOST', - SubmitCellThroughInput = 'DATASCIENCE.SUBMITCELLFROMREPL', - ConnectLocalJupyter = 'DS_INTERNAL.CONNECTLOCALJUPYTER', - ConnectRemoteJupyter = 'DS_INTERNAL.CONNECTREMOTEJUPYTER', - ConnectRemoteJupyterViaLocalHost = 'DS_INTERNAL.CONNECTREMOTEJUPYTER_VIA_LOCALHOST', - ConnectFailedJupyter = 'DS_INTERNAL.CONNECTFAILEDJUPYTER', - ConnectRemoteFailedJupyter = 'DS_INTERNAL.CONNECTREMOTEFAILEDJUPYTER', - StartSessionFailedJupyter = 'DS_INTERNAL.START_SESSION_FAILED_JUPYTER', - ConnectRemoteSelfCertFailedJupyter = 'DS_INTERNAL.CONNECTREMOTESELFCERTFAILEDJUPYTER', - RegisterAndUseInterpreterAsKernel = 'DS_INTERNAL.REGISTER_AND_USE_INTERPRETER_AS_KERNEL', - UseInterpreterAsKernel = 'DS_INTERNAL.USE_INTERPRETER_AS_KERNEL', - UseExistingKernel = 'DS_INTERNAL.USE_EXISTING_KERNEL', - SwitchToInterpreterAsKernel = 'DS_INTERNAL.SWITCH_TO_INTERPRETER_AS_KERNEL', - SwitchToExistingKernel = 'DS_INTERNAL.SWITCH_TO_EXISTING_KERNEL', - SelfCertsMessageEnabled = 'DATASCIENCE.SELFCERTSMESSAGEENABLED', - SelfCertsMessageClose = 'DATASCIENCE.SELFCERTSMESSAGECLOSE', - RemoteAddCode = 'DATASCIENCE.LIVESHARE.ADDCODE', - RemoteReexecuteCode = 'DATASCIENCE.LIVESHARE.REEXECUTECODE', - ShiftEnterBannerShown = 'DS_INTERNAL.SHIFTENTER_BANNER_SHOWN', - EnableInteractiveShiftEnter = 'DATASCIENCE.ENABLE_INTERACTIVE_SHIFT_ENTER', - DisableInteractiveShiftEnter = 'DATASCIENCE.DISABLE_INTERACTIVE_SHIFT_ENTER', - ShowDataViewer = 'DATASCIENCE.SHOW_DATA_EXPLORER', - RunFileInteractive = 'DATASCIENCE.RUN_FILE_INTERACTIVE', - DebugFileInteractive = 'DATASCIENCE.DEBUG_FILE_INTERACTIVE', - PandasNotInstalled = 'DS_INTERNAL.SHOW_DATA_NO_PANDAS', - PandasTooOld = 'DS_INTERNAL.SHOW_DATA_PANDAS_TOO_OLD', - DataScienceSettings = 'DS_INTERNAL.SETTINGS', - VariableExplorerToggled = 'DATASCIENCE.VARIABLE_EXPLORER_TOGGLE', - VariableExplorerVariableCount = 'DS_INTERNAL.VARIABLE_EXPLORER_VARIABLE_COUNT', - AddCellBelow = 'DATASCIENCE.ADD_CELL_BELOW', - GetPasswordAttempt = 'DATASCIENCE.GET_PASSWORD_ATTEMPT', - GetPasswordFailure = 'DS_INTERNAL.GET_PASSWORD_FAILURE', - GetPasswordSuccess = 'DS_INTERNAL.GET_PASSWORD_SUCCESS', - OpenPlotViewer = 'DATASCIENCE.OPEN_PLOT_VIEWER', - DebugCurrentCell = 'DATASCIENCE.DEBUG_CURRENT_CELL', - CodeLensAverageAcquisitionTime = 'DS_INTERNAL.CODE_LENS_ACQ_TIME', - FindJupyterCommand = 'DS_INTERNAL.FIND_JUPYTER_COMMAND', - /** - * Telemetry sent when user selects an interpreter to be used for starting of Jupyter server. - */ - SelectJupyterInterpreter = 'DS_INTERNAL.SELECT_JUPYTER_INTERPRETER', - /** - * User used command to select an intrepreter for the jupyter server. - */ - SelectJupyterInterpreterCommand = 'DATASCIENCE.SELECT_JUPYTER_INTERPRETER_Command', - StartJupyterProcess = 'DS_INTERNAL.START_JUPYTER_PROCESS', - WaitForIdleJupyter = 'DS_INTERNAL.WAIT_FOR_IDLE_JUPYTER', - HiddenCellTime = 'DS_INTERNAL.HIDDEN_EXECUTION_TIME', - RestartJupyterTime = 'DS_INTERNAL.RESTART_JUPYTER_TIME', - InterruptJupyterTime = 'DS_INTERNAL.INTERRUPT_JUPYTER_TIME', - ExecuteCell = 'DATASCIENCE.EXECUTE_CELL_TIME', - ExecuteCellPerceivedCold = 'DS_INTERNAL.EXECUTE_CELL_PERCEIVED_COLD', - ExecuteCellPerceivedWarm = 'DS_INTERNAL.EXECUTE_CELL_PERCEIVED_WARM', - PerceivedJupyterStartupNotebook = 'DS_INTERNAL.PERCEIVED_JUPYTER_STARTUP_NOTEBOOK', - StartExecuteNotebookCellPerceivedCold = 'DS_INTERNAL.START_EXECUTE_NOTEBOOK_CELL_PERCEIVED_COLD', - WebviewStartup = 'DS_INTERNAL.WEBVIEW_STARTUP', - VariableExplorerFetchTime = 'DS_INTERNAL.VARIABLE_EXPLORER_FETCH_TIME', - WebviewStyleUpdate = 'DS_INTERNAL.WEBVIEW_STYLE_UPDATE', - WebviewMonacoStyleUpdate = 'DS_INTERNAL.WEBVIEW_MONACO_STYLE_UPDATE', - FindJupyterKernelSpec = 'DS_INTERNAL.FIND_JUPYTER_KERNEL_SPEC', - HashedCellOutputMimeType = 'DS_INTERNAL.HASHED_OUTPUT_MIME_TYPE', - HashedCellOutputMimeTypePerf = 'DS_INTERNAL.HASHED_OUTPUT_MIME_TYPE_PERF', - HashedNotebookCellOutputMimeTypePerf = 'DS_INTERNAL.HASHED_NOTEBOOK_OUTPUT_MIME_TYPE_PERF', - JupyterInstalledButNotKernelSpecModule = 'DS_INTERNAL.JUPYTER_INTALLED_BUT_NO_KERNELSPEC_MODULE', - DebugpyPromptToInstall = 'DATASCIENCE.DEBUGPY_PROMPT_TO_INSTALL', - DebugpySuccessfullyInstalled = 'DATASCIENCE.DEBUGPY_SUCCESSFULLY_INSTALLED', - DebugpyInstallFailed = 'DATASCIENCE.DEBUGPY_INSTALL_FAILED', - DebugpyInstallCancelled = 'DATASCIENCE.DEBUGPY_INSTALL_CANCELLED', - ScrolledToCell = 'DATASCIENCE.SCROLLED_TO_CELL', - ExecuteNativeCell = 'DATASCIENCE.NATIVE.EXECUTE_NATIVE_CELL', - CreateNewNotebook = 'DATASCIENCE.NATIVE.CREATE_NEW_NOTEBOOK', - DebugStepOver = 'DATASCIENCE.DEBUG_STEP_OVER', - DebugContinue = 'DATASCIENCE.DEBUG_CONTINUE', - DebugStop = 'DATASCIENCE.DEBUG_STOP', - OpenNotebook = 'DATASCIENCE.NATIVE.OPEN_NOTEBOOK', - OpenNotebookAll = 'DATASCIENCE.NATIVE.OPEN_NOTEBOOK_ALL', - ConvertToPythonFile = 'DATASCIENCE.NATIVE.CONVERT_NOTEBOOK_TO_PYTHON', - NotebookWorkspaceCount = 'DS_INTERNAL.NATIVE.WORKSPACE_NOTEBOOK_COUNT', - NotebookRunCount = 'DS_INTERNAL.NATIVE.NOTEBOOK_RUN_COUNT', - NotebookOpenCount = 'DS_INTERNAL.NATIVE.NOTEBOOK_OPEN_COUNT', - NotebookOpenTime = 'DS_INTERNAL.NATIVE.NOTEBOOK_OPEN_TIME', - SessionIdleTimeout = 'DS_INTERNAL.JUPYTER_IDLE_TIMEOUT', - JupyterStartTimeout = 'DS_INTERNAL.JUPYTER_START_TIMEOUT', - JupyterNotInstalledErrorShown = 'DATASCIENCE.JUPYTER_NOT_INSTALLED_ERROR_SHOWN', - JupyterCommandSearch = 'DATASCIENCE.JUPYTER_COMMAND_SEARCH', - RegisterInterpreterAsKernel = 'DS_INTERNAL.JUPYTER_REGISTER_INTERPRETER_AS_KERNEL', - UserInstalledJupyter = 'DATASCIENCE.USER_INSTALLED_JUPYTER', - UserInstalledPandas = 'DATASCIENCE.USER_INSTALLED_PANDAS', - UserDidNotInstallJupyter = 'DATASCIENCE.USER_DID_NOT_INSTALL_JUPYTER', - UserDidNotInstallPandas = 'DATASCIENCE.USER_DID_NOT_INSTALL_PANDAS', - OpenedInteractiveWindow = 'DATASCIENCE.OPENED_INTERACTIVE', - OpenNotebookFailure = 'DS_INTERNAL.NATIVE.OPEN_NOTEBOOK_FAILURE', - FindKernelForLocalConnection = 'DS_INTERNAL.FIND_KERNEL_FOR_LOCAL_CONNECTION', - CompletionTimeFromLS = 'DS_INTERNAL.COMPLETION_TIME_FROM_LS', - CompletionTimeFromJupyter = 'DS_INTERNAL.COMPLETION_TIME_FROM_JUPYTER', - NotebookLanguage = 'DATASCIENCE.NOTEBOOK_LANGUAGE', - KernelSpecNotFound = 'DS_INTERNAL.KERNEL_SPEC_NOT_FOUND', - KernelRegisterFailed = 'DS_INTERNAL.KERNEL_REGISTER_FAILED', - KernelEnumeration = 'DS_INTERNAL.KERNEL_ENUMERATION', - KernelLauncherPerf = 'DS_INTERNAL.KERNEL_LAUNCHER_PERF', - KernelFinderPerf = 'DS_INTERNAL.KERNEL_FINDER_PERF', - JupyterInstallFailed = 'DS_INTERNAL.JUPYTER_INSTALL_FAILED', - UserInstalledModule = 'DATASCIENCE.USER_INSTALLED_MODULE', - JupyterCommandLineNonDefault = 'DS_INTERNAL.JUPYTER_CUSTOM_COMMAND_LINE', - NewFileForInteractiveWindow = 'DS_INTERNAL.NEW_FILE_USED_IN_INTERACTIVE', - KernelInvalid = 'DS_INTERNAL.INVALID_KERNEL_USED', - GatherIsInstalled = 'DS_INTERNAL.GATHER_IS_INSTALLED', - GatherCompleted = 'DATASCIENCE.GATHER_COMPLETED', - GatherStats = 'DS_INTERNAL.GATHER_STATS', - GatherException = 'DS_INTERNAL.GATHER_EXCEPTION', - GatheredNotebookSaved = 'DATASCIENCE.GATHERED_NOTEBOOK_SAVED', - GatherQualityReport = 'DS_INTERNAL.GATHER_QUALITY_REPORT', - ZMQSupported = 'DS_INTERNAL.ZMQ_NATIVE_BINARIES_LOADING', - ZMQNotSupported = 'DS_INTERNAL.ZMQ_NATIVE_BINARIES_NOT_LOADING', - IPyWidgetLoadSuccess = 'DS_INTERNAL.IPYWIDGET_LOAD_SUCCESS', - IPyWidgetLoadFailure = 'DS_INTERNAL.IPYWIDGET_LOAD_FAILURE', - IPyWidgetWidgetVersionNotSupportedLoadFailure = 'DS_INTERNAL.IPYWIDGET_WIDGET_VERSION_NOT_SUPPORTED_LOAD_FAILURE', - IPyWidgetLoadDisabled = 'DS_INTERNAL.IPYWIDGET_LOAD_DISABLED', - HashedIPyWidgetNameUsed = 'DS_INTERNAL.IPYWIDGET_USED_BY_USER', - VSCNotebookCellTranslationFailed = 'DS_INTERNAL.VSCNOTEBOOK_CELL_TRANSLATION_FAILED', - HashedIPyWidgetNameDiscovered = 'DS_INTERNAL.IPYWIDGET_DISCOVERED', - HashedIPyWidgetScriptDiscoveryError = 'DS_INTERNAL.IPYWIDGET_DISCOVERY_ERRORED', - DiscoverIPyWidgetNamesLocalPerf = 'DS_INTERNAL.IPYWIDGET_TEST_AVAILABILITY_ON_LOCAL', - DiscoverIPyWidgetNamesCDNPerf = 'DS_INTERNAL.IPYWIDGET_TEST_AVAILABILITY_ON_CDN', - IPyWidgetPromptToUseCDN = 'DS_INTERNAL.IPYWIDGET_PROMPT_TO_USE_CDN', - IPyWidgetPromptToUseCDNSelection = 'DS_INTERNAL.IPYWIDGET_PROMPT_TO_USE_CDN_SELECTION', - IPyWidgetOverhead = 'DS_INTERNAL.IPYWIDGET_OVERHEAD', - IPyWidgetRenderFailure = 'DS_INTERNAL.IPYWIDGET_RENDER_FAILURE', - IPyWidgetUnhandledMessage = 'DS_INTERNAL.IPYWIDGET_UNHANDLED_MESSAGE', - RawKernelCreatingNotebook = 'DS_INTERNAL.RAWKERNEL_CREATING_NOTEBOOK', - JupyterCreatingNotebook = 'DS_INTERNAL.JUPYTER_CREATING_NOTEBOOK', - RawKernelSessionConnect = 'DS_INTERNAL.RAWKERNEL_SESSION_CONNECT', - RawKernelStartRawSession = 'DS_INTERNAL.RAWKERNEL_START_RAW_SESSION', - RawKernelSessionStartSuccess = 'DS_INTERNAL.RAWKERNEL_SESSION_START_SUCCESS', - RawKernelSessionStartUserCancel = 'DS_INTERNAL.RAWKERNEL_SESSION_START_USER_CANCEL', - RawKernelSessionStartTimeout = 'DS_INTERNAL.RAWKERNEL_SESSION_START_TIMEOUT', - RawKernelSessionStartException = 'DS_INTERNAL.RAWKERNEL_SESSION_START_EXCEPTION', - RawKernelProcessLaunch = 'DS_INTERNAL.RAWKERNEL_PROCESS_LAUNCH', - StartPageViewed = 'DS_INTERNAL.STARTPAGE_VIEWED', - StartPageOpenedFromCommandPalette = 'DS_INTERNAL.STARTPAGE_OPENED_FROM_COMMAND_PALETTE', - StartPageOpenedFromNewInstall = 'DS_INTERNAL.STARTPAGE_OPENED_FROM_NEW_INSTALL', - StartPageOpenedFromNewUpdate = 'DS_INTERNAL.STARTPAGE_OPENED_FROM_NEW_UPDATE', - StartPageWebViewError = 'DS_INTERNAL.STARTPAGE_WEBVIEWERROR', - StartPageTime = 'DS_INTERNAL.STARTPAGE_TIME', - StartPageClickedDontShowAgain = 'DATASCIENCE.STARTPAGE_DONT_SHOW_AGAIN', - StartPageClosedWithoutAction = 'DATASCIENCE.STARTPAGE_CLOSED_WITHOUT_ACTION', - StartPageUsedAnActionOnFirstTime = 'DATASCIENCE.STARTPAGE_USED_ACTION_ON_FIRST_TIME', - StartPageOpenBlankNotebook = 'DATASCIENCE.STARTPAGE_OPEN_BLANK_NOTEBOOK', - StartPageOpenBlankPythonFile = 'DATASCIENCE.STARTPAGE_OPEN_BLANK_PYTHON_FILE', - StartPageOpenInteractiveWindow = 'DATASCIENCE.STARTPAGE_OPEN_INTERACTIVE_WINDOW', - StartPageOpenCommandPalette = 'DATASCIENCE.STARTPAGE_OPEN_COMMAND_PALETTE', - StartPageOpenCommandPaletteWithOpenNBSelected = 'DATASCIENCE.STARTPAGE_OPEN_COMMAND_PALETTE_WITH_OPENNBSELECTED', - StartPageOpenSampleNotebook = 'DATASCIENCE.STARTPAGE_OPEN_SAMPLE_NOTEBOOK', - StartPageOpenFileBrowser = 'DATASCIENCE.STARTPAGE_OPEN_FILE_BROWSER', - StartPageOpenFolder = 'DATASCIENCE.STARTPAGE_OPEN_FOLDER', - StartPageOpenWorkspace = 'DATASCIENCE.STARTPAGE_OPEN_WORKSPACE', - RunByLineStart = 'DATASCIENCE.RUN_BY_LINE', - RunByLineStep = 'DATASCIENCE.RUN_BY_LINE_STEP', - RunByLineStop = 'DATASCIENCE.RUN_BY_LINE_STOP', - RunByLineVariableHover = 'DATASCIENCE.RUN_BY_LINE_VARIABLE_HOVER', - TrustAllNotebooks = 'DATASCIENCE.TRUST_ALL_NOTEBOOKS', - TrustNotebook = 'DATASCIENCE.TRUST_NOTEBOOK', - DoNotTrustNotebook = 'DATASCIENCE.DO_NOT_TRUST_NOTEBOOK', - NotebookTrustPromptShown = 'DATASCIENCE.NOTEBOOK_TRUST_PROMPT_SHOWN' -} - -export enum NativeKeyboardCommandTelemetry { - ArrowDown = 'DATASCIENCE.NATIVE.KEYBOARD.ARROW_DOWN', - ArrowUp = 'DATASCIENCE.NATIVE.KEYBOARD.ARROW_UP', - ChangeToCode = 'DATASCIENCE.NATIVE.KEYBOARD.CHANGE_TO_CODE', - ChangeToMarkdown = 'DATASCIENCE.NATIVE.KEYBOARD.CHANGE_TO_MARKDOWN', - DeleteCell = 'DATASCIENCE.NATIVE.KEYBOARD.DELETE_CELL', - InsertAbove = 'DATASCIENCE.NATIVE.KEYBOARD.INSERT_ABOVE', - InsertBelow = 'DATASCIENCE.NATIVE.KEYBOARD.INSERT_BELOW', - Redo = 'DATASCIENCE.NATIVE.KEYBOARD.REDO', - Run = 'DATASCIENCE.NATIVE.KEYBOARD.RUN', - Save = 'DATASCIENCE.NATIVE.KEYBOARD.SAVE', - RunAndAdd = 'DATASCIENCE.NATIVE.KEYBOARD.RUN_AND_ADD', - RunAndMove = 'DATASCIENCE.NATIVE.KEYBOARD.RUN_AND_MOVE', - ToggleLineNumbers = 'DATASCIENCE.NATIVE.KEYBOARD.TOGGLE_LINE_NUMBERS', - ToggleOutput = 'DATASCIENCE.NATIVE.KEYBOARD.TOGGLE_OUTPUT', - Undo = 'DATASCIENCE.NATIVE.KEYBOARD.UNDO', - Unfocus = 'DATASCIENCE.NATIVE.KEYBOARD.UNFOCUS' -} - -export enum NativeMouseCommandTelemetry { - AddToEnd = 'DATASCIENCE.NATIVE.MOUSE.ADD_TO_END', - ChangeToCode = 'DATASCIENCE.NATIVE.MOUSE.CHANGE_TO_CODE', - ChangeToMarkdown = 'DATASCIENCE.NATIVE.MOUSE.CHANGE_TO_MARKDOWN', - DeleteCell = 'DATASCIENCE.NATIVE.MOUSE.DELETE_CELL', - InsertBelow = 'DATASCIENCE.NATIVE.MOUSE.INSERT_BELOW', - MoveCellDown = 'DATASCIENCE.NATIVE.MOUSE.MOVE_CELL_DOWN', - MoveCellUp = 'DATASCIENCE.NATIVE.MOUSE.MOVE_CELL_UP', - Run = 'DATASCIENCE.NATIVE.MOUSE.RUN', - RunAbove = 'DATASCIENCE.NATIVE.MOUSE.RUN_ABOVE', - RunAll = 'DATASCIENCE.NATIVE.MOUSE.RUN_ALL', - RunBelow = 'DATASCIENCE.NATIVE.MOUSE.RUN_BELOW', - SelectKernel = 'DATASCIENCE.NATIVE.MOUSE.SELECT_KERNEL', - SelectServer = 'DATASCIENCE.NATIVE.MOUSE.SELECT_SERVER', - Save = 'DATASCIENCE.NATIVE.MOUSE.SAVE', - ToggleVariableExplorer = 'DATASCIENCE.NATIVE.MOUSE.TOGGLE_VARIABLE_EXPLORER' -} - -/** - * Notebook editing in VS Code Notebooks is handled by VSC. - * There's no way for us to know whether user added a cell using keyboard or not. - * Similarly a cell could have been added as part of an undo operation. - * All we know is previously user had n # of cells and now they have m # of cells. - */ -export enum VSCodeNativeTelemetry { - AddCell = 'DATASCIENCE.VSCODE_NATIVE.INSERT_CELL', - RunAllCells = 'DATASCIENCE.VSCODE_NATIVE.RUN_ALL', - DeleteCell = 'DATASCIENCE.VSCODE_NATIVE.DELETE_CELL', - MoveCell = 'DATASCIENCE.VSCODE_NATIVE.MOVE_CELL', - ChangeToCode = 'DATASCIENCE.VSCODE_NATIVE.CHANGE_TO_CODE', // Not guaranteed to work see, https://github.com/microsoft/vscode/issues/100042 - ChangeToMarkdown = 'DATASCIENCE.VSCODE_NATIVE.CHANGE_TO_MARKDOWN' // Not guaranteed to work see, https://github.com/microsoft/vscode/issues/100042 -} - -export namespace HelpLinks { - export const PythonInteractiveHelpLink = 'https://aka.ms/pyaiinstall'; - export const JupyterDataRateHelpLink = 'https://aka.ms/AA5ggm0'; // This redirects here: https://jupyter-notebook.readthedocs.io/en/stable/config.html -} - -export namespace Settings { - export const JupyterServerLocalLaunch = 'local'; - export const JupyterServerUriList = 'python.dataScience.jupyterServer.uriList'; - export const JupyterServerUriListMax = 10; - // If this timeout expires, ignore the completion request sent to Jupyter. - export const IntellisenseTimeout = 500; - // If this timeout expires, ignore the completions requests. (don't wait for it to complete). - export const MaxIntellisenseTimeout = 30_000; - export const RemoteDebuggerPortBegin = 8889; - export const RemoteDebuggerPortEnd = 9000; - export const DefaultVariableQuery: IVariableQuery = { - language: PYTHON_LANGUAGE, - query: '_rwho_ls = %who_ls\nprint(_rwho_ls)', - parseExpr: "'(\\w+)'" - }; -} - -export namespace DataFrameLoading { - export const SysPath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'vscode_datascience_helpers', 'dataframes'); - export const DataFrameSysImport = `import sys\nsys.path.append("${SysPath.replace(/\\/g, '\\\\')}")`; - export const DataFrameInfoImportName = '_VSCODE_InfoImport'; - export const DataFrameInfoImport = `import vscodeGetDataFrameInfo as ${DataFrameInfoImportName}`; - export const DataFrameInfoFunc = `${DataFrameInfoImportName}._VSCODE_getDataFrameInfo`; - export const DataFrameRowImportName = '_VSCODE_RowImport'; - export const DataFrameRowImport = `import vscodeGetDataFrameRows as ${DataFrameRowImportName}`; - export const DataFrameRowFunc = `${DataFrameRowImportName}._VSCODE_getDataFrameRows`; - export const VariableInfoImportName = '_VSCODE_VariableImport'; - export const VariableInfoImport = `import vscodeGetVariableInfo as ${VariableInfoImportName}`; - export const VariableInfoFunc = `${VariableInfoImportName}._VSCODE_getVariableInfo`; -} - -export namespace Identifiers { - export const EmptyFileName = '2DB9B899-6519-4E1B-88B0-FA728A274115'; - export const GeneratedThemeName = 'ipython-theme'; // This needs to be all lower class and a valid class name. - export const HistoryPurpose = 'history'; - export const RawPurpose = 'raw'; - export const PingPurpose = 'ping'; - export const MatplotLibDefaultParams = '_VSCode_defaultMatplotlib_Params'; - export const EditCellId = '3D3AB152-ADC1-4501-B813-4B83B49B0C10'; - export const SvgSizeTag = 'sizeTag={{0}, {1}}'; - export const InteractiveWindowIdentityScheme = 'history'; - export const DefaultCodeCellMarker = '# %%'; - export const DefaultCommTarget = 'jupyter.widget'; - export const ALL_VARIABLES = 'ALL_VARIABLES'; - export const OLD_VARIABLES = 'OLD_VARIABLES'; - export const KERNEL_VARIABLES = 'KERNEL_VARIABLES'; - export const DEBUGGER_VARIABLES = 'DEBUGGER_VARIABLES'; - export const MULTIPLEXING_DEBUGSERVICE = 'MULTIPLEXING_DEBUGSERVICE'; - export const RUN_BY_LINE_DEBUGSERVICE = 'RUN_BY_LINE_DEBUGSERVICE'; - export const REMOTE_URI = 'https://remote/'; - export const REMOTE_URI_ID_PARAM = 'id'; - export const REMOTE_URI_HANDLE_PARAM = 'uriHandle'; -} - -export namespace CodeSnippets { - export const ChangeDirectory = [ - '{0}', - '{1}', - 'import os', - 'try:', - "\tos.chdir(os.path.join(os.getcwd(), '{2}'))", - '\tprint(os.getcwd())', - 'except:', - '\tpass', - '' - ]; - export const ChangeDirectoryCommentIdentifier = '# ms-python.python added'; // Not translated so can compare. - export const ImportIPython = '{0}\nfrom IPython import get_ipython\n\n{1}'; - export const MatplotLibInitSvg = `import matplotlib\n%matplotlib inline\n${Identifiers.MatplotLibDefaultParams} = dict(matplotlib.rcParams)\n%config InlineBackend.figure_formats = {'svg', 'png'}`; - export const MatplotLibInitPng = `import matplotlib\n%matplotlib inline\n${Identifiers.MatplotLibDefaultParams} = dict(matplotlib.rcParams)\n%config InlineBackend.figure_formats = {'png'}`; - export const ConfigSvg = `%config InlineBackend.figure_formats = {'svg', 'png'}`; - export const ConfigPng = `%config InlineBackend.figure_formats = {'png'}`; - export const UpdateCWDAndPath = - 'import os\nimport sys\n%cd "{0}"\nif os.getcwd() not in sys.path:\n sys.path.insert(0, os.getcwd())'; - export const disableJedi = '%config Completer.use_jedi = False'; -} - -export enum JupyterCommands { - NotebookCommand = 'notebook', - ConvertCommand = 'nbconvert', - KernelSpecCommand = 'kernelspec' -} - -export namespace LiveShare { - export const JupyterExecutionService = 'jupyterExecutionService'; - export const JupyterServerSharedService = 'jupyterServerSharedService'; - export const JupyterNotebookSharedService = 'jupyterNotebookSharedService'; - export const CommandBrokerService = 'commmandBrokerService'; - export const WebPanelMessageService = 'webPanelMessageService'; - export const InteractiveWindowProviderService = 'interactiveWindowProviderService'; - export const GuestCheckerService = 'guestCheckerService'; - export const LiveShareBroadcastRequest = 'broadcastRequest'; - export const RawNotebookProviderService = 'rawNotebookProviderSharedService'; - export const ResponseLifetime = 15000; - export const ResponseRange = 1000; // Range of time alloted to check if a response matches or not - export const InterruptDefaultTimeout = 10000; -} - -export namespace LiveShareCommands { - export const isNotebookSupported = 'isNotebookSupported'; - export const getImportPackageVersion = 'getImportPackageVersion'; - export const connectToNotebookServer = 'connectToNotebookServer'; - export const getUsableJupyterPython = 'getUsableJupyterPython'; - export const executeObservable = 'executeObservable'; - export const getSysInfo = 'getSysInfo'; - export const requestKernelInfo = 'requestKernelInfo'; - export const serverResponse = 'serverResponse'; - export const catchupRequest = 'catchupRequest'; - export const syncRequest = 'synchRequest'; - export const restart = 'restart'; - export const interrupt = 'interrupt'; - export const interactiveWindowCreate = 'interactiveWindowCreate'; - export const interactiveWindowCreateSync = 'interactiveWindowCreateSync'; - export const disposeServer = 'disposeServer'; - export const guestCheck = 'guestCheck'; - export const createNotebook = 'createNotebook'; - export const inspect = 'inspect'; - export const rawKernelSupported = 'rawKernelSupported'; - export const createRawNotebook = 'createRawNotebook'; -} - -export const VSCodeNotebookProvider = 'VSCodeNotebookProvider'; -export const OurNotebookProvider = 'OurNotebookProvider'; -export const DataScienceStartupTime = Symbol('DataScienceStartupTime'); diff --git a/src/client/datascience/context/activeEditorContext.ts b/src/client/datascience/context/activeEditorContext.ts deleted file mode 100644 index 103021f29481..000000000000 --- a/src/client/datascience/context/activeEditorContext.ts +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { TextEditor } from 'vscode'; -import { ServerStatus } from '../../../datascience-ui/interactive-common/mainState'; -import { IExtensionSingleActivationService } from '../../activation/types'; -import { ICommandManager, IDocumentManager } from '../../common/application/types'; -import { PYTHON_LANGUAGE } from '../../common/constants'; -import { ContextKey } from '../../common/contextKey'; -import { NotebookEditorSupport } from '../../common/experiments/groups'; -import { traceError } from '../../common/logger'; -import { IDisposable, IDisposableRegistry, IExperimentsManager } from '../../common/types'; -import { setSharedProperty } from '../../telemetry'; -import { EditorContexts } from '../constants'; -import { - IInteractiveWindow, - IInteractiveWindowProvider, - INotebook, - INotebookEditor, - INotebookEditorProvider, - INotebookProvider, - ITrustService -} from '../types'; - -@injectable() -export class ActiveEditorContextService implements IExtensionSingleActivationService, IDisposable { - private readonly disposables: IDisposable[] = []; - private nativeContext: ContextKey; - private interactiveContext: ContextKey; - private interactiveOrNativeContext: ContextKey; - private pythonOrInteractiveContext: ContextKey; - private pythonOrNativeContext: ContextKey; - private pythonOrInteractiveOrNativeContext: ContextKey; - private canRestartNotebookKernelContext: ContextKey; - private hasNativeNotebookCells: ContextKey; - private isNotebookTrusted: ContextKey; - private isPythonFileActive: boolean = false; - constructor( - @inject(IInteractiveWindowProvider) private readonly interactiveProvider: IInteractiveWindowProvider, - @inject(INotebookEditorProvider) private readonly notebookEditorProvider: INotebookEditorProvider, - @inject(IDocumentManager) private readonly docManager: IDocumentManager, - @inject(ICommandManager) private readonly commandManager: ICommandManager, - @inject(IDisposableRegistry) disposables: IDisposableRegistry, - @inject(IExperimentsManager) private readonly experiments: IExperimentsManager, - @inject(INotebookProvider) private readonly notebookProvider: INotebookProvider, - @inject(ITrustService) private readonly trustService: ITrustService - ) { - disposables.push(this); - this.nativeContext = new ContextKey(EditorContexts.IsNativeActive, this.commandManager); - this.canRestartNotebookKernelContext = new ContextKey( - EditorContexts.CanRestartNotebookKernel, - this.commandManager - ); - this.interactiveContext = new ContextKey(EditorContexts.IsInteractiveActive, this.commandManager); - this.interactiveOrNativeContext = new ContextKey( - EditorContexts.IsInteractiveOrNativeActive, - this.commandManager - ); - this.pythonOrNativeContext = new ContextKey(EditorContexts.IsPythonOrNativeActive, this.commandManager); - this.pythonOrInteractiveContext = new ContextKey( - EditorContexts.IsPythonOrInteractiveActive, - this.commandManager - ); - this.pythonOrInteractiveOrNativeContext = new ContextKey( - EditorContexts.IsPythonOrInteractiveOrNativeActive, - this.commandManager - ); - this.hasNativeNotebookCells = new ContextKey(EditorContexts.HaveNativeCells, this.commandManager); - this.isNotebookTrusted = new ContextKey(EditorContexts.IsNotebookTrusted, this.commandManager); - } - public dispose() { - this.disposables.forEach((item) => item.dispose()); - } - public async activate(): Promise { - this.docManager.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor, this, this.disposables); - this.notebookProvider.onSessionStatusChanged(this.onDidKernelStatusChange, this, this.disposables); - this.interactiveProvider.onDidChangeActiveInteractiveWindow( - this.onDidChangeActiveInteractiveWindow, - this, - this.disposables - ); - this.notebookEditorProvider.onDidChangeActiveNotebookEditor( - this.onDidChangeActiveNotebookEditor, - this, - this.disposables - ); - this.trustService.onDidSetNotebookTrust(this.onDidSetNotebookTrust, this, this.disposables); - - // Do we already have python file opened. - if (this.docManager.activeTextEditor?.document.languageId === PYTHON_LANGUAGE) { - this.onDidChangeActiveTextEditor(this.docManager.activeTextEditor); - } - } - - private updateNativeNotebookCellContext() { - if (!this.experiments.inExperiment(NotebookEditorSupport.nativeNotebookExperiment)) { - return; - } - this.hasNativeNotebookCells - .set((this.notebookEditorProvider.activeEditor?.model?.cells?.length || 0) > 0) - .ignoreErrors(); - } - private onDidChangeActiveInteractiveWindow(e?: IInteractiveWindow) { - this.interactiveContext.set(!!e).ignoreErrors(); - this.updateMergedContexts(); - } - private onDidChangeActiveNotebookEditor(e?: INotebookEditor) { - // This will ensure all subsequent telemetry will get the context of whether it is a custom/native/old notebook editor. - // This is temporary, and once we ship native editor this needs to be removed. - setSharedProperty('ds_notebookeditor', e?.type); - this.nativeContext.set(!!e).ignoreErrors(); - this.isNotebookTrusted.set(e?.model?.isTrusted === true).ignoreErrors(); - this.updateMergedContexts(); - this.updateContextOfActiveNotebookKernel(e); - } - private updateContextOfActiveNotebookKernel(activeEditor?: INotebookEditor) { - if (activeEditor) { - this.notebookProvider - .getOrCreateNotebook({ identity: activeEditor.file, getOnly: true }) - .then((nb) => { - if (activeEditor === this.notebookEditorProvider.activeEditor) { - const canStart = nb && nb.status !== ServerStatus.NotStarted && activeEditor.model?.isTrusted; - this.canRestartNotebookKernelContext.set(!!canStart).ignoreErrors(); - } - }) - .catch( - traceError.bind(undefined, 'Failed to determine if a notebook is active for the current editor') - ); - } else { - this.canRestartNotebookKernelContext.set(false).ignoreErrors(); - } - } - private onDidKernelStatusChange({ notebook }: { status: ServerStatus; notebook: INotebook }) { - // Ok, kernel status has changed. - const activeEditor = this.notebookEditorProvider.activeEditor; - if (!activeEditor) { - return; - } - if (activeEditor.file.toString() !== notebook.identity.toString()) { - // Status of a notebook thats not related to active editor has changed. - // We can ignore that. - return; - } - this.updateContextOfActiveNotebookKernel(activeEditor); - } - private onDidChangeActiveTextEditor(e?: TextEditor) { - this.isPythonFileActive = - e?.document.languageId === PYTHON_LANGUAGE && !this.notebookEditorProvider.activeEditor; - this.updateNativeNotebookCellContext(); - this.updateMergedContexts(); - } - // When trust service says trust has changed, update context with whether the currently active notebook is trusted - private onDidSetNotebookTrust() { - if (this.notebookEditorProvider.activeEditor?.model !== undefined) { - this.isNotebookTrusted.set(this.notebookEditorProvider.activeEditor?.model?.isTrusted).ignoreErrors(); - } - } - private updateMergedContexts() { - this.interactiveOrNativeContext - .set(this.nativeContext.value === true && this.interactiveContext.value === true) - .ignoreErrors(); - this.pythonOrNativeContext - .set(this.nativeContext.value === true || this.isPythonFileActive === true) - .ignoreErrors(); - this.pythonOrInteractiveContext - .set(this.interactiveContext.value === true || this.isPythonFileActive === true) - .ignoreErrors(); - this.pythonOrInteractiveOrNativeContext - .set( - this.nativeContext.value === true || - (this.interactiveContext.value === true && this.isPythonFileActive === true) - ) - .ignoreErrors(); - } -} diff --git a/src/client/datascience/data-viewing/dataViewer.ts b/src/client/datascience/data-viewing/dataViewer.ts deleted file mode 100644 index 5073196fd1c4..000000000000 --- a/src/client/datascience/data-viewing/dataViewer.ts +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { ViewColumn } from 'vscode'; - -import { IApplicationShell, IWebviewPanelProvider, IWorkspaceService } from '../../common/application/types'; -import { EXTENSION_ROOT_DIR, UseCustomEditorApi } from '../../common/constants'; -import { traceError } from '../../common/logger'; -import { IConfigurationService, IDisposable, Resource } from '../../common/types'; -import * as localize from '../../common/utils/localize'; -import { noop } from '../../common/utils/misc'; -import { StopWatch } from '../../common/utils/stopWatch'; -import { sendTelemetryEvent } from '../../telemetry'; -import { HelpLinks, Telemetry } from '../constants'; -import { JupyterDataRateLimitError } from '../jupyter/jupyterDataRateLimitError'; -import { ICodeCssGenerator, IThemeFinder } from '../types'; -import { WebviewPanelHost } from '../webviews/webviewPanelHost'; -import { DataViewerMessageListener } from './dataViewerMessageListener'; -import { - DataViewerMessages, - IDataFrameInfo, - IDataViewer, - IDataViewerDataProvider, - IDataViewerMapping, - IGetRowsRequest -} from './types'; - -const dataExplorereDir = path.join(EXTENSION_ROOT_DIR, 'out', 'datascience-ui', 'viewers'); -@injectable() -export class DataViewer extends WebviewPanelHost implements IDataViewer, IDisposable { - private dataProvider: IDataViewerDataProvider | undefined; - private rowsTimer: StopWatch | undefined; - private pendingRowsCount: number = 0; - private dataFrameInfoPromise: Promise | undefined; - - constructor( - @inject(IWebviewPanelProvider) provider: IWebviewPanelProvider, - @inject(IConfigurationService) configuration: IConfigurationService, - @inject(ICodeCssGenerator) cssGenerator: ICodeCssGenerator, - @inject(IThemeFinder) themeFinder: IThemeFinder, - @inject(IWorkspaceService) workspaceService: IWorkspaceService, - @inject(IApplicationShell) private applicationShell: IApplicationShell, - @inject(UseCustomEditorApi) useCustomEditorApi: boolean - ) { - super( - configuration, - provider, - cssGenerator, - themeFinder, - workspaceService, - (c, v, d) => new DataViewerMessageListener(c, v, d), - dataExplorereDir, - [path.join(dataExplorereDir, 'commons.initial.bundle.js'), path.join(dataExplorereDir, 'dataExplorer.js')], - localize.DataScience.dataExplorerTitle(), - ViewColumn.One, - useCustomEditorApi, - false - ); - } - - public async showData(dataProvider: IDataViewerDataProvider, title: string): Promise { - if (!this.isDisposed) { - // Save the data provider - this.dataProvider = dataProvider; - - // Load the web panel using our current directory as we don't expect to load any other files - await super.loadWebPanel(process.cwd()).catch(traceError); - - super.setTitle(title); - - // Then show our web panel. Eventually we need to consume the data - await super.show(true); - - const dataFrameInfo = await this.prepDataFrameInfo(); - - // Send a message with our data - this.postMessage(DataViewerMessages.InitializeData, dataFrameInfo).ignoreErrors(); - } - } - - public dispose(): void { - super.dispose(); - - if (this.dataProvider) { - // Call dispose on the data provider - this.dataProvider.dispose(); - this.dataProvider = undefined; - } - } - - protected get owningResource(): Resource { - return undefined; - } - - //tslint:disable-next-line:no-any - protected onMessage(message: string, payload: any) { - switch (message) { - case DataViewerMessages.GetAllRowsRequest: - this.getAllRows().ignoreErrors(); - break; - - case DataViewerMessages.GetRowsRequest: - this.getRowChunk(payload as IGetRowsRequest).ignoreErrors(); - break; - - default: - break; - } - - super.onMessage(message, payload); - } - - private getDataFrameInfo(): Promise { - if (!this.dataFrameInfoPromise) { - this.dataFrameInfoPromise = this.dataProvider ? this.dataProvider.getDataFrameInfo() : Promise.resolve({}); - } - return this.dataFrameInfoPromise; - } - - private async prepDataFrameInfo(): Promise { - this.rowsTimer = new StopWatch(); - const output = await this.getDataFrameInfo(); - - // Log telemetry about number of rows - try { - sendTelemetryEvent(Telemetry.ShowDataViewer, 0, { - rows: output.rowCount ? output.rowCount : 0, - columns: output.columns ? output.columns.length : 0 - }); - - // Count number of rows to fetch so can send telemetry on how long it took. - this.pendingRowsCount = output.rowCount ? output.rowCount : 0; - } catch { - noop(); - } - - return output; - } - - private async getAllRows() { - return this.wrapRequest(async () => { - if (this.dataProvider) { - const allRows = await this.dataProvider.getAllRows(); - this.pendingRowsCount = 0; - return this.postMessage(DataViewerMessages.GetAllRowsResponse, allRows); - } - }); - } - - private getRowChunk(request: IGetRowsRequest) { - return this.wrapRequest(async () => { - if (this.dataProvider) { - const dataFrameInfo = await this.getDataFrameInfo(); - const rows = await this.dataProvider.getRows( - request.start, - Math.min(request.end, dataFrameInfo.rowCount ? dataFrameInfo.rowCount : 0) - ); - return this.postMessage(DataViewerMessages.GetRowsResponse, { - rows, - start: request.start, - end: request.end - }); - } - }); - } - - private async wrapRequest(func: () => Promise) { - try { - return await func(); - } catch (e) { - if (e instanceof JupyterDataRateLimitError) { - traceError(e); - const actionTitle = localize.DataScience.pythonInteractiveHelpLink(); - this.applicationShell.showErrorMessage(e.toString(), actionTitle).then((v) => { - // User clicked on the link, open it. - if (v === actionTitle) { - this.applicationShell.openUrl(HelpLinks.JupyterDataRateHelpLink); - } - }); - this.dispose(); - } - traceError(e); - this.applicationShell.showErrorMessage(e); - } finally { - this.sendElapsedTimeTelemetry(); - } - } - - private sendElapsedTimeTelemetry() { - if (this.rowsTimer && this.pendingRowsCount === 0) { - sendTelemetryEvent(Telemetry.ShowDataViewer, this.rowsTimer.elapsedTime); - } - } -} diff --git a/src/client/datascience/data-viewing/dataViewerDependencyService.ts b/src/client/datascience/data-viewing/dataViewerDependencyService.ts deleted file mode 100644 index 67e2dc1d1224..000000000000 --- a/src/client/datascience/data-viewing/dataViewerDependencyService.ts +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { SemVer } from 'semver'; -import { CancellationToken } from 'vscode'; -import { IApplicationShell } from '../../common/application/types'; -import { Cancellation, createPromiseFromCancellation, wrapCancellationTokens } from '../../common/cancellation'; -import { traceWarning } from '../../common/logger'; -import { IPythonExecutionFactory } from '../../common/process/types'; -import { IInstaller, InstallerResponse, Product } from '../../common/types'; -import { Common, DataScience } from '../../common/utils/localize'; -import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { sendTelemetryEvent } from '../../telemetry'; -import { parseSemVer } from '../common'; -import { Telemetry } from '../constants'; - -const minimumSupportedPandaVersion = '0.20.0'; - -function isVersionOfPandasSupported(version: SemVer) { - return version.compare(minimumSupportedPandaVersion) > 0; -} - -/** - * Responsible for managing dependencies of a Data Viewer. - */ -@injectable() -export class DataViewerDependencyService { - constructor( - @inject(IApplicationShell) private readonly applicationShell: IApplicationShell, - @inject(IInstaller) private readonly installer: IInstaller, - @inject(IPythonExecutionFactory) private pythonFactory: IPythonExecutionFactory - ) {} - - public async checkAndInstallMissingDependencies( - interpreter?: PythonEnvironment, - token?: CancellationToken - ): Promise { - const pandasVersion = await this.getVersionOfPandas(interpreter, token); - if (Cancellation.isCanceled(token)) { - return; - } - - if (pandasVersion) { - if (isVersionOfPandasSupported(pandasVersion)) { - return; - } - sendTelemetryEvent(Telemetry.PandasTooOld); - // Warn user that we cannot start because pandas is too old. - const versionStr = `${pandasVersion.major}.${pandasVersion.minor}.${pandasVersion.build}`; - throw new Error(DataScience.pandasTooOldForViewingFormat().format(versionStr)); - } - - sendTelemetryEvent(Telemetry.PandasNotInstalled); - await this.installMissingDependencies(interpreter, token); - } - - private async installMissingDependencies( - interpreter?: PythonEnvironment, - token?: CancellationToken - ): Promise { - const selection = await this.applicationShell.showErrorMessage( - DataScience.pandasRequiredForViewing(), - Common.install() - ); - - if (Cancellation.isCanceled(token)) { - return; - } - - if (selection === Common.install()) { - const cancellatonPromise = createPromiseFromCancellation({ - cancelAction: 'resolve', - defaultValue: InstallerResponse.Ignore, - token - }); - // Always pass a cancellation token to `install`, to ensure it waits until the module is installed. - const response = await Promise.race([ - this.installer.install(Product.pandas, interpreter, wrapCancellationTokens(token)), - cancellatonPromise - ]); - if (response === InstallerResponse.Installed) { - sendTelemetryEvent(Telemetry.UserInstalledPandas); - } - } else { - sendTelemetryEvent(Telemetry.UserDidNotInstallPandas); - throw new Error(DataScience.pandasRequiredForViewing()); - } - } - - private async getVersionOfPandas( - interpreter?: PythonEnvironment, - token?: CancellationToken - ): Promise { - const launcher = await this.pythonFactory.createActivatedEnvironment({ - resource: undefined, - interpreter, - allowEnvironmentFetchExceptions: true, - bypassCondaExecution: true - }); - try { - const result = await launcher.exec(['-c', 'import pandas;print(pandas.__version__)'], { - throwOnStdErr: true, - token - }); - - return parseSemVer(result.stdout); - } catch (ex) { - traceWarning('Failed to get version of Pandas to use Data Viewer', ex); - return; - } - } -} diff --git a/src/client/datascience/data-viewing/dataViewerFactory.ts b/src/client/datascience/data-viewing/dataViewerFactory.ts deleted file mode 100644 index ce64481e6c41..000000000000 --- a/src/client/datascience/data-viewing/dataViewerFactory.ts +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; - -import { inject, injectable } from 'inversify'; - -import { IAsyncDisposable, IAsyncDisposableRegistry } from '../../common/types'; -import { IServiceContainer } from '../../ioc/types'; -import { IDataViewer, IDataViewerDataProvider, IDataViewerFactory } from './types'; - -@injectable() -export class DataViewerFactory implements IDataViewerFactory, IAsyncDisposable { - private activeExplorers: IDataViewer[] = []; - constructor( - @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(IAsyncDisposableRegistry) asyncRegistry: IAsyncDisposableRegistry - ) { - asyncRegistry.push(this); - } - - public async dispose() { - await Promise.all(this.activeExplorers.map((d) => d.dispose())); - } - - public async create(dataProvider: IDataViewerDataProvider, title: string): Promise { - let result: IDataViewer | undefined; - - // Create the data explorer - const dataExplorer = this.serviceContainer.get(IDataViewer); - try { - // Then load the data. - this.activeExplorers.push(dataExplorer); - - // Show the window and the data - await dataExplorer.showData(dataProvider, title); - result = dataExplorer; - } finally { - if (!result) { - // If throw any errors, close the window we opened. - dataExplorer.dispose(); - } - } - return result; - } -} diff --git a/src/client/datascience/data-viewing/dataViewerMessageListener.ts b/src/client/datascience/data-viewing/dataViewerMessageListener.ts deleted file mode 100644 index 7d9cb03526a9..000000000000 --- a/src/client/datascience/data-viewing/dataViewerMessageListener.ts +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; - -import { IWebviewPanel, IWebviewPanelMessageListener } from '../../common/application/types'; - -// tslint:disable:no-any - -// This class listens to messages that come from the local Data Explorer window -export class DataViewerMessageListener implements IWebviewPanelMessageListener { - private disposedCallback: () => void; - private callback: (message: string, payload: any) => void; - private viewChanged: (panel: IWebviewPanel) => void; - - constructor( - callback: (message: string, payload: any) => void, - viewChanged: (panel: IWebviewPanel) => void, - disposed: () => void - ) { - // Save our dispose callback so we remove our interactive window - this.disposedCallback = disposed; - - // Save our local callback so we can handle the non broadcast case(s) - this.callback = callback; - - // Save view changed so we can forward view change events. - this.viewChanged = viewChanged; - } - - public async dispose() { - this.disposedCallback(); - } - - public onMessage(message: string, payload: any) { - // Send to just our local callback. - this.callback(message, payload); - } - - public onChangeViewState(panel: IWebviewPanel) { - // Forward this onto our callback - this.viewChanged(panel); - } -} diff --git a/src/client/datascience/data-viewing/jupyterVariableDataProvider.ts b/src/client/datascience/data-viewing/jupyterVariableDataProvider.ts deleted file mode 100644 index 58a4982edea9..000000000000 --- a/src/client/datascience/data-viewing/jupyterVariableDataProvider.ts +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; - -import { inject, injectable, named } from 'inversify'; - -import { Identifiers } from '../constants'; -import { IJupyterVariable, IJupyterVariableDataProvider, IJupyterVariables, INotebook } from '../types'; -import { DataViewerDependencyService } from './dataViewerDependencyService'; -import { ColumnType, IDataFrameInfo, IRowsResponse } from './types'; - -@injectable() -export class JupyterVariableDataProvider implements IJupyterVariableDataProvider { - private initialized: boolean = false; - private notebook: INotebook | undefined; - private variable: IJupyterVariable | undefined; - - constructor( - @inject(IJupyterVariables) @named(Identifiers.ALL_VARIABLES) private variableManager: IJupyterVariables, - @inject(DataViewerDependencyService) private dependencyService: DataViewerDependencyService - ) {} - - /** - * Normalizes column types to the types the UI component understands. - * Defaults to 'string'. - * @param columns - * @returns Array of columns with normalized type - */ - private static getNormalizedColumns(columns: { key: string; type: string }[]): { key: string; type: ColumnType }[] { - return columns.map((column: { key: string; type: string }) => { - let normalizedType: ColumnType; - switch (column.type) { - case 'bool': - normalizedType = ColumnType.Bool; - break; - case 'integer': - case 'int32': - case 'int64': - case 'float': - case 'float32': - case 'float64': - case 'number': - normalizedType = ColumnType.Number; - break; - default: - normalizedType = ColumnType.String; - } - return { - key: column.key, - type: normalizedType - }; - }); - } - - public dispose(): void { - return; - } - - public setDependencies(variable: IJupyterVariable, notebook: INotebook): void { - this.notebook = notebook; - this.variable = variable; - } - - public async getDataFrameInfo(): Promise { - let dataFrameInfo: IDataFrameInfo = {}; - await this.ensureInitialized(); - if (this.variable && this.notebook) { - dataFrameInfo = { - columns: this.variable.columns - ? JupyterVariableDataProvider.getNormalizedColumns(this.variable.columns) - : this.variable.columns, - indexColumn: this.variable.indexColumn, - rowCount: this.variable.rowCount - }; - } - return dataFrameInfo; - } - - public async getAllRows() { - let allRows: IRowsResponse = []; - await this.ensureInitialized(); - if (this.variable && this.variable.rowCount && this.notebook) { - const dataFrameRows = await this.variableManager.getDataFrameRows( - this.variable, - this.notebook, - 0, - this.variable.rowCount - ); - allRows = dataFrameRows && dataFrameRows.data ? (dataFrameRows.data as IRowsResponse) : []; - } - return allRows; - } - - public async getRows(start: number, end: number) { - let rows: IRowsResponse = []; - await this.ensureInitialized(); - if (this.variable && this.variable.rowCount && this.notebook) { - const dataFrameRows = await this.variableManager.getDataFrameRows(this.variable, this.notebook, start, end); - rows = dataFrameRows && dataFrameRows.data ? (dataFrameRows.data as IRowsResponse) : []; - } - return rows; - } - - private async ensureInitialized(): Promise { - // Postpone pre-req and variable initialization until data is requested. - if (!this.initialized && this.variable && this.notebook) { - this.initialized = true; - await this.dependencyService.checkAndInstallMissingDependencies(this.notebook.getMatchingInterpreter()); - this.variable = await this.variableManager.getDataFrameInfo(this.variable, this.notebook); - } - } -} diff --git a/src/client/datascience/data-viewing/jupyterVariableDataProviderFactory.ts b/src/client/datascience/data-viewing/jupyterVariableDataProviderFactory.ts deleted file mode 100644 index b8b48b45a4b3..000000000000 --- a/src/client/datascience/data-viewing/jupyterVariableDataProviderFactory.ts +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; - -import { inject, injectable } from 'inversify'; - -import { IServiceContainer } from '../../ioc/types'; -import { - IJupyterVariable, - IJupyterVariableDataProvider, - IJupyterVariableDataProviderFactory, - INotebook -} from '../types'; - -@injectable() -export class JupyterVariableDataProviderFactory implements IJupyterVariableDataProviderFactory { - constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {} - - public async create(variable: IJupyterVariable, notebook: INotebook): Promise { - const jupyterVariableDataProvider = this.serviceContainer.get( - IJupyterVariableDataProvider - ); - jupyterVariableDataProvider.setDependencies(variable, notebook); - return jupyterVariableDataProvider; - } -} diff --git a/src/client/datascience/data-viewing/types.ts b/src/client/datascience/data-viewing/types.ts deleted file mode 100644 index df3c97bc5cd5..000000000000 --- a/src/client/datascience/data-viewing/types.ts +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import { IDisposable } from '../../common/types'; -import { SharedMessages } from '../messages'; - -export const CellFetchAllLimit = 100000; -export const CellFetchSizeFirst = 100000; -export const CellFetchSizeSubsequent = 1000000; -export const MaxStringCompare = 200; -export const ColumnWarningSize = 1000; // Anything over this takes too long to load - -export namespace DataViewerRowStates { - export const Fetching = 'fetching'; - export const Skipped = 'skipped'; -} - -export namespace DataViewerMessages { - export const Started = SharedMessages.Started; - export const UpdateSettings = SharedMessages.UpdateSettings; - export const InitializeData = 'init'; - export const GetAllRowsRequest = 'get_all_rows_request'; - export const GetAllRowsResponse = 'get_all_rows_response'; - export const GetRowsRequest = 'get_rows_request'; - export const GetRowsResponse = 'get_rows_response'; - export const CompletedData = 'complete'; -} - -export interface IGetRowsRequest { - start: number; - end: number; -} - -export interface IGetRowsResponse { - rows: IRowsResponse; - start: number; - end: number; -} - -// Map all messages to specific payloads -export type IDataViewerMapping = { - [DataViewerMessages.Started]: never | undefined; - [DataViewerMessages.UpdateSettings]: string; - [DataViewerMessages.InitializeData]: IDataFrameInfo; - [DataViewerMessages.GetAllRowsRequest]: never | undefined; - [DataViewerMessages.GetAllRowsResponse]: IRowsResponse; - [DataViewerMessages.GetRowsRequest]: IGetRowsRequest; - [DataViewerMessages.GetRowsResponse]: IGetRowsResponse; - [DataViewerMessages.CompletedData]: never | undefined; -}; - -export interface IDataFrameInfo { - columns?: { key: string; type: ColumnType }[]; - indexColumn?: string; - rowCount?: number; -} - -export interface IDataViewerDataProvider { - dispose(): void; - getDataFrameInfo(): Promise; - getAllRows(): Promise; - getRows(start: number, end: number): Promise; -} - -export enum ColumnType { - String = 'string', - Number = 'number', - Bool = 'bool' -} - -// tslint:disable-next-line: no-any -export type IRowsResponse = any[]; - -export const IDataViewerFactory = Symbol('IDataViewerFactory'); -export interface IDataViewerFactory { - create(dataProvider: IDataViewerDataProvider, title: string): Promise; -} - -export const IDataViewer = Symbol('IDataViewer'); -export interface IDataViewer extends IDisposable { - showData(dataProvider: IDataViewerDataProvider, title: string): Promise; -} diff --git a/src/client/datascience/dataScienceFileSystem.ts b/src/client/datascience/dataScienceFileSystem.ts deleted file mode 100644 index 47eb20fb5104..000000000000 --- a/src/client/datascience/dataScienceFileSystem.ts +++ /dev/null @@ -1,218 +0,0 @@ -import * as fs from 'fs-extra'; -import * as glob from 'glob'; -import { injectable } from 'inversify'; -import * as tmp from 'tmp'; -import { promisify } from 'util'; -import { FileStat, FileSystem, Uri, workspace } from 'vscode'; -import { traceError } from '../common/logger'; -import { createDirNotEmptyError, isFileNotFoundError } from '../common/platform/errors'; -import { convertFileType, convertStat, getHashString } from '../common/platform/fileSystem'; -import { FileSystemPathUtils } from '../common/platform/fs-paths'; -import { FileType, IFileSystemPathUtils, TemporaryFile } from '../common/platform/types'; -import { IDataScienceFileSystem } from './types'; - -const ENCODING = 'utf8'; - -/** - * File system abstraction which wraps the VS Code API. - */ -@injectable() -export class DataScienceFileSystem implements IDataScienceFileSystem { - protected vscfs: FileSystem; - private globFiles: (pat: string, options?: { cwd: string; dot?: boolean }) => Promise; - private fsPathUtils: IFileSystemPathUtils; - constructor() { - this.globFiles = promisify(glob); - this.fsPathUtils = FileSystemPathUtils.withDefaults(); - this.vscfs = workspace.fs; - } - - public async appendLocalFile(path: string, text: string): Promise { - return fs.appendFile(path, text); - } - - public areLocalPathsSame(path1: string, path2: string): boolean { - return this.fsPathUtils.arePathsSame(path1, path2); - } - - public async createLocalDirectory(path: string): Promise { - await this.createDirectory(Uri.file(path)); - } - - public createLocalWriteStream(path: string): fs.WriteStream { - return fs.createWriteStream(path); - } - - public async copyLocal(source: string, destination: string): Promise { - const srcUri = Uri.file(source); - const dstUri = Uri.file(destination); - await this.vscfs.copy(srcUri, dstUri, { overwrite: true }); - } - - public async createTemporaryLocalFile(suffix: string, mode?: number): Promise { - const opts = { - postfix: suffix, - mode - }; - return new Promise((resolve, reject) => { - tmp.file(opts, (err, filename, _fd, cleanUp) => { - if (err) { - return reject(err); - } - resolve({ - filePath: filename, - dispose: cleanUp - }); - }); - }); - } - - public async deleteLocalDirectory(dirname: string) { - const uri = Uri.file(dirname); - // The "recursive" option disallows directories, even if they - // are empty. So we have to deal with this ourselves. - const files = await this.vscfs.readDirectory(uri); - if (files && files.length > 0) { - throw createDirNotEmptyError(dirname); - } - return this.vscfs.delete(uri, { - recursive: true, - useTrash: false - }); - } - - public async deleteLocalFile(path: string): Promise { - const uri = Uri.file(path); - return this.vscfs.delete(uri, { - recursive: false, - useTrash: false - }); - } - - public getDisplayName(filename: string, cwd?: string): string { - return this.fsPathUtils.getDisplayName(filename, cwd); - } - - public async getFileHash(filename: string): Promise { - // The reason for lstat rather than stat is not clear... - const stat = await this.lstat(filename); - const data = `${stat.ctime}-${stat.mtime}`; - return getHashString(data); - } - - public async localDirectoryExists(dirname: string): Promise { - return this.localPathExists(dirname, FileType.Directory); - } - - public async localFileExists(filename: string): Promise { - return this.localPathExists(filename, FileType.File); - } - - public async readLocalData(filename: string): Promise { - const uri = Uri.file(filename); - const data = await this.vscfs.readFile(uri); - return Buffer.from(data); - } - - public async readLocalFile(filename: string): Promise { - const uri = Uri.file(filename); - return this.readFile(uri); - } - - public async searchLocal(globPattern: string, cwd?: string, dot?: boolean): Promise { - // tslint:disable-next-line: no-any - let options: any; - if (cwd) { - options = { ...options, cwd }; - } - if (dot) { - options = { ...options, dot }; - } - - const found = await this.globFiles(globPattern, options); - return Array.isArray(found) ? found : []; - } - - public async writeLocalFile(filename: string, text: string | Buffer): Promise { - const uri = Uri.file(filename); - return this.writeFile(uri, text); - } - - // URI-based filesystem functions for interacting with files provided by VS Code - public arePathsSame(path1: Uri, path2: Uri): boolean { - if (path1.scheme === 'file' && path1.scheme === path2.scheme) { - return this.areLocalPathsSame(path1.fsPath, path2.fsPath); - } else { - return path1.toString() === path2.toString(); - } - } - - public async copy(source: Uri, destination: Uri): Promise { - await this.vscfs.copy(source, destination); - } - - public async createDirectory(uri: Uri): Promise { - await this.vscfs.createDirectory(uri); - } - - public async delete(uri: Uri): Promise { - await this.vscfs.delete(uri); - } - - public async readFile(uri: Uri): Promise { - const result = await this.vscfs.readFile(uri); - const data = Buffer.from(result); - return data.toString(ENCODING); - } - - public async stat(uri: Uri): Promise { - return this.vscfs.stat(uri); - } - - public async writeFile(uri: Uri, text: string | Buffer): Promise { - const data = typeof text === 'string' ? Buffer.from(text) : text; - return this.vscfs.writeFile(uri, data); - } - - private async lstat(filename: string): Promise { - // tslint:disable-next-line: no-suspicious-comment - // TODO https://github.com/microsoft/vscode/issues/71204 (84514)): - // This functionality has been requested for the VS Code API. - const stat = await fs.lstat(filename); - // Note that, unlike stat(), lstat() does not include the type - // of the symlink's target. - const fileType = convertFileType(stat); - return convertStat(stat, fileType); - } - - private async localPathExists( - // the "file" to look for - filename: string, - // the file type to expect; if not provided then any file type - // matches; otherwise a mismatch results in a "false" value - fileType?: FileType - ): Promise { - let stat: FileStat; - try { - // Note that we are using stat() rather than lstat(). This - // means that any symlinks are getting resolved. - const uri = Uri.file(filename); - stat = await this.stat(uri); - } catch (err) { - if (isFileNotFoundError(err)) { - return false; - } - traceError(`stat() failed for "${filename}"`, err); - return false; - } - - if (fileType === undefined) { - return true; - } - if (fileType === FileType.Unknown) { - // FileType.Unknown == 0, hence do not use bitwise operations. - return stat.type === FileType.Unknown; - } - return (stat.type & fileType) === fileType; - } -} diff --git a/src/client/datascience/dataScienceSurveyBanner.ts b/src/client/datascience/dataScienceSurveyBanner.ts deleted file mode 100644 index cd63823c1778..000000000000 --- a/src/client/datascience/dataScienceSurveyBanner.ts +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable, named } from 'inversify'; -import { Event, EventEmitter } from 'vscode'; -import { IApplicationShell } from '../common/application/types'; -import '../common/extensions'; -import { - BANNER_NAME_DS_SURVEY, - IBrowserService, - IPersistentStateFactory, - IPythonExtensionBanner -} from '../common/types'; -import * as localize from '../common/utils/localize'; -import { noop } from '../common/utils/misc'; -import { InteractiveWindowMessages, IReExecuteCells } from './interactive-common/interactiveWindowTypes'; -import { IInteractiveWindowListener, INotebookEditorProvider } from './types'; - -export enum DSSurveyStateKeys { - ShowBanner = 'ShowDSSurveyBanner', - OpenNotebookCount = 'DS_OpenNotebookCount', - ExecutionCount = 'DS_ExecutionCount' -} - -enum DSSurveyLabelIndex { - Yes, - No -} - -const NotebookOpenThreshold = 5; -const NotebookExecutionThreshold = 100; - -@injectable() -export class DataScienceSurveyBannerLogger implements IInteractiveWindowListener { - // tslint:disable-next-line: no-any - private postEmitter = new EventEmitter<{ message: string; payload: any }>(); - constructor( - @inject(IPersistentStateFactory) private persistentState: IPersistentStateFactory, - @inject(IPythonExtensionBanner) - @named(BANNER_NAME_DS_SURVEY) - private readonly dataScienceSurveyBanner: IPythonExtensionBanner - ) {} - // tslint:disable-next-line: no-any - public get postMessage(): Event<{ message: string; payload: any }> { - return this.postEmitter.event; - } - // tslint:disable-next-line: no-any - public onMessage(message: string, payload?: any): void { - if (message === InteractiveWindowMessages.ReExecuteCells) { - const args = payload as IReExecuteCells; - if (args && args.cellIds.length) { - const state = this.persistentState.createGlobalPersistentState( - DSSurveyStateKeys.ExecutionCount, - 0 - ); - state - .updateValue(state.value + args.cellIds.length) - .then(() => { - // On every update try to show the banner. - return this.dataScienceSurveyBanner.showBanner(); - }) - .ignoreErrors(); - } - } - } - public dispose(): void | undefined { - noop(); - } -} - -@injectable() -export class DataScienceSurveyBanner implements IPythonExtensionBanner { - private disabledInCurrentSession: boolean = false; - private isInitialized: boolean = false; - private bannerMessage: string = localize.DataScienceSurveyBanner.bannerMessage(); - private bannerLabels: string[] = [ - localize.DataScienceSurveyBanner.bannerLabelYes(), - localize.DataScienceSurveyBanner.bannerLabelNo() - ]; - private readonly surveyLink: string; - - constructor( - @inject(IApplicationShell) private appShell: IApplicationShell, - @inject(IPersistentStateFactory) private persistentState: IPersistentStateFactory, - @inject(IBrowserService) private browserService: IBrowserService, - @inject(INotebookEditorProvider) editorProvider: INotebookEditorProvider, - surveyLink: string = 'https://aka.ms/pyaisurvey' - ) { - this.surveyLink = surveyLink; - this.initialize(); - editorProvider.onDidOpenNotebookEditor(this.openedNotebook.bind(this)); - } - - public initialize(): void { - if (this.isInitialized) { - return; - } - this.isInitialized = true; - } - public get enabled(): boolean { - return this.persistentState.createGlobalPersistentState(DSSurveyStateKeys.ShowBanner, true).value; - } - - public async showBanner(): Promise { - if (!this.enabled || this.disabledInCurrentSession) { - return; - } - - const executionCount: number = this.getExecutionCount(); - const notebookCount: number = this.getOpenNotebookCount(); - const show = await this.shouldShowBanner(executionCount, notebookCount); - if (!show) { - return; - } - - const response = await this.appShell.showInformationMessage(this.bannerMessage, ...this.bannerLabels); - switch (response) { - case this.bannerLabels[DSSurveyLabelIndex.Yes]: { - await this.launchSurvey(); - await this.disable(); - break; - } - case this.bannerLabels[DSSurveyLabelIndex.No]: { - await this.disable(); - break; - } - default: { - // Disable for the current session. - this.disabledInCurrentSession = true; - } - } - } - - public async shouldShowBanner(executionCount: number, notebookOpenCount: number): Promise { - if (!this.enabled || this.disabledInCurrentSession) { - return false; - } - - return executionCount >= NotebookExecutionThreshold || notebookOpenCount > NotebookOpenThreshold; - } - - public async disable(): Promise { - await this.persistentState - .createGlobalPersistentState(DSSurveyStateKeys.ShowBanner, false) - .updateValue(false); - } - - public async launchSurvey(): Promise { - this.browserService.launch(this.surveyLink); - } - - private getOpenNotebookCount(): number { - const state = this.persistentState.createGlobalPersistentState(DSSurveyStateKeys.OpenNotebookCount, 0); - return state.value; - } - - private getExecutionCount(): number { - const state = this.persistentState.createGlobalPersistentState(DSSurveyStateKeys.ExecutionCount, 0); - return state.value; - } - - private async openedNotebook() { - const state = this.persistentState.createGlobalPersistentState(DSSurveyStateKeys.OpenNotebookCount, 0); - await state.updateValue(state.value + 1); - return this.showBanner(); - } -} diff --git a/src/client/datascience/datascience.ts b/src/client/datascience/datascience.ts deleted file mode 100644 index 9109ca732d94..000000000000 --- a/src/client/datascience/datascience.ts +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import type { JSONObject } from '@phosphor/coreutils'; -import { inject, injectable } from 'inversify'; -import * as vscode from 'vscode'; -import { ICommandManager, IDocumentManager, IWorkspaceService } from '../common/application/types'; -import { PYTHON_ALLFILES, PYTHON_LANGUAGE } from '../common/constants'; -import { ContextKey } from '../common/contextKey'; -import '../common/extensions'; -import { IConfigurationService, IDisposable, IDisposableRegistry, IExtensionContext } from '../common/types'; -import { debounceAsync, swallowExceptions } from '../common/utils/decorators'; -import { sendTelemetryEvent } from '../telemetry'; -import { hasCells } from './cellFactory'; -import { CommandRegistry } from './commands/commandRegistry'; -import { EditorContexts, Telemetry } from './constants'; -import { IDataScience, IDataScienceCodeLensProvider } from './types'; - -@injectable() -export class DataScience implements IDataScience { - public isDisposed: boolean = false; - private changeHandler: IDisposable | undefined; - private startTime: number = Date.now(); - constructor( - @inject(ICommandManager) private commandManager: ICommandManager, - @inject(IDisposableRegistry) private disposableRegistry: IDisposableRegistry, - @inject(IExtensionContext) private extensionContext: IExtensionContext, - @inject(IDataScienceCodeLensProvider) private dataScienceCodeLensProvider: IDataScienceCodeLensProvider, - @inject(IConfigurationService) private configuration: IConfigurationService, - @inject(IDocumentManager) private documentManager: IDocumentManager, - @inject(IWorkspaceService) private workspace: IWorkspaceService, - @inject(CommandRegistry) private commandRegistry: CommandRegistry - ) { - this.disposableRegistry.push(this.commandRegistry); - } - - public get activationStartTime(): number { - return this.startTime; - } - - public async activate(): Promise { - this.commandRegistry.register(); - - this.extensionContext.subscriptions.push( - vscode.languages.registerCodeLensProvider(PYTHON_ALLFILES, this.dataScienceCodeLensProvider) - ); - - // Set our initial settings and sign up for changes - this.onSettingsChanged(); - this.changeHandler = this.configuration.getSettings(undefined).onDidChange(this.onSettingsChanged.bind(this)); - this.disposableRegistry.push(this); - - // Listen for active editor changes so we can detect have code cells or not - this.disposableRegistry.push( - this.documentManager.onDidChangeActiveTextEditor(() => this.onChangedActiveTextEditor()) - ); - this.onChangedActiveTextEditor(); - - // Send telemetry for all of our settings - this.sendSettingsTelemetry().ignoreErrors(); - } - - public async dispose() { - if (this.changeHandler) { - this.changeHandler.dispose(); - this.changeHandler = undefined; - } - } - - private onSettingsChanged = () => { - const settings = this.configuration.getSettings(undefined); - const enabled = settings.datascience.enabled; - let editorContext = new ContextKey(EditorContexts.DataScienceEnabled, this.commandManager); - editorContext.set(enabled).catch(); - const ownsSelection = settings.datascience.sendSelectionToInteractiveWindow; - editorContext = new ContextKey(EditorContexts.OwnsSelection, this.commandManager); - editorContext.set(ownsSelection && enabled).catch(); - }; - - private onChangedActiveTextEditor() { - // Setup the editor context for the cells - const editorContext = new ContextKey(EditorContexts.HasCodeCells, this.commandManager); - const activeEditor = this.documentManager.activeTextEditor; - - if (activeEditor && activeEditor.document.languageId === PYTHON_LANGUAGE) { - // Inform the editor context that we have cells, fire and forget is ok on the promise here - // as we don't care to wait for this context to be set and we can't do anything if it fails - editorContext.set(hasCells(activeEditor.document, this.configuration.getSettings().datascience)).catch(); - } else { - editorContext.set(false).catch(); - } - } - - @debounceAsync(1) - @swallowExceptions('Sending DataScience Settings Telemetry failed') - private async sendSettingsTelemetry(): Promise { - // Get our current settings. This is what we want to send. - // tslint:disable-next-line:no-any - const settings = this.configuration.getSettings().datascience as any; - - // Translate all of the 'string' based settings into known values or not. - const pythonConfig = this.workspace.getConfiguration('python'); - if (pythonConfig) { - const keys = Object.keys(settings); - const resultSettings: JSONObject = {}; - for (const k of keys) { - const currentValue = settings[k]; - if (typeof currentValue === 'string' && k !== 'interactiveWindowMode') { - const inspectResult = pythonConfig.inspect(`dataScience.${k}`); - if (inspectResult && inspectResult.defaultValue !== currentValue) { - resultSettings[k] = 'non-default'; - } else { - resultSettings[k] = 'default'; - } - } else { - resultSettings[k] = currentValue; - } - } - sendTelemetryEvent(Telemetry.DataScienceSettings, 0, resultSettings); - } - } -} diff --git a/src/client/datascience/debugLocationTracker.ts b/src/client/datascience/debugLocationTracker.ts deleted file mode 100644 index 6642b3f44652..000000000000 --- a/src/client/datascience/debugLocationTracker.ts +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { DebugAdapterTracker, Event, EventEmitter } from 'vscode'; -import { DebugProtocol } from 'vscode-debugprotocol'; - -import { IDebugLocation } from './types'; - -// When a python debugging session is active keep track of the current debug location -export class DebugLocationTracker implements DebugAdapterTracker { - private waitingForStackTrace: boolean = false; - private _debugLocation: IDebugLocation | undefined; - private debugLocationUpdatedEvent: EventEmitter = new EventEmitter(); - private sessionEndedEmitter: EventEmitter = new EventEmitter(); - - constructor(private _sessionId: string) { - this.DebugLocation = undefined; - } - - public get sessionId() { - return this._sessionId; - } - - public get sessionEnded(): Event { - return this.sessionEndedEmitter.event; - } - - public get debugLocationUpdated(): Event { - return this.debugLocationUpdatedEvent.event; - } - - public get debugLocation(): IDebugLocation | undefined { - return this._debugLocation; - } - - // tslint:disable-next-line:no-any - public onDidSendMessage(message: DebugProtocol.ProtocolMessage) { - if (this.isStopEvent(message)) { - // Some type of stop, wait to see our next stack trace to find our location - this.waitingForStackTrace = true; - } - - if (this.isContinueEvent(message)) { - // Running, clear the location - this.DebugLocation = undefined; - this.waitingForStackTrace = false; - } - - if (this.waitingForStackTrace) { - // If we are waiting for a stack track, check our messages for one - const debugLoc = this.getStackTrace(message); - if (debugLoc) { - this.DebugLocation = debugLoc; - this.waitingForStackTrace = false; - } - } - } - - public onWillStopSession() { - this.sessionEndedEmitter.fire(this); - } - - // Set our new location and fire our debug event - private set DebugLocation(newLocation: IDebugLocation | undefined) { - const oldLocation = this._debugLocation; - this._debugLocation = newLocation; - - if (this._debugLocation !== oldLocation) { - this.debugLocationUpdatedEvent.fire(); - } - } - - // tslint:disable-next-line:no-any - private isStopEvent(message: DebugProtocol.ProtocolMessage) { - if (message.type === 'event') { - const eventMessage = message as DebugProtocol.Event; - if (eventMessage.event === 'stopped') { - return true; - } - } - - return false; - } - - // tslint:disable-next-line:no-any - private getStackTrace(message: DebugProtocol.ProtocolMessage): IDebugLocation | undefined { - if (message.type === 'response') { - const responseMessage = message as DebugProtocol.Response; - if (responseMessage.command === 'stackTrace') { - const messageBody = responseMessage.body; - if (messageBody.stackFrames.length > 0) { - const lineNumber = messageBody.stackFrames[0].line; - const fileName = this.normalizeFilePath(messageBody.stackFrames[0].source.path); - const column = messageBody.stackFrames[0].column; - return { lineNumber, fileName, column }; - } - } - } - - return undefined; - } - - private normalizeFilePath(path: string): string { - // Make the path match the os. Debugger seems to return - // invalid path chars on linux/darwin - if (process.platform !== 'win32') { - return path.replace(/\\/g, '/'); - } - return path; - } - - // tslint:disable-next-line:no-any - private isContinueEvent(message: DebugProtocol.ProtocolMessage): boolean { - if (message.type === 'event') { - const eventMessage = message as DebugProtocol.Event; - if (eventMessage.event === 'continue') { - return true; - } - } else if (message.type === 'response') { - const responseMessage = message as DebugProtocol.Response; - if (responseMessage.command === 'continue') { - return true; - } - } - - return false; - } -} diff --git a/src/client/datascience/debugLocationTrackerFactory.ts b/src/client/datascience/debugLocationTrackerFactory.ts deleted file mode 100644 index d31287410c1a..000000000000 --- a/src/client/datascience/debugLocationTrackerFactory.ts +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { inject, injectable } from 'inversify'; -import { - DebugAdapterTracker, - DebugAdapterTrackerFactory, - DebugSession, - Event, - EventEmitter, - ProviderResult -} from 'vscode'; - -import { IDebugService } from '../common/application/types'; -import { IDisposableRegistry } from '../common/types'; -import { DebugLocationTracker } from './debugLocationTracker'; -import { IDebugLocationTracker } from './types'; - -// Hook up our IDebugLocationTracker to python debugging sessions -@injectable() -export class DebugLocationTrackerFactory implements IDebugLocationTracker, DebugAdapterTrackerFactory { - private activeTrackers: Map = new Map(); - private updatedEmitter: EventEmitter = new EventEmitter(); - - constructor( - @inject(IDebugService) debugService: IDebugService, - @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry - ) { - disposableRegistry.push(debugService.registerDebugAdapterTrackerFactory('python', this)); - } - - public createDebugAdapterTracker(session: DebugSession): ProviderResult { - const result = new DebugLocationTracker(session.id); - this.activeTrackers.set(session.id, result); - result.sessionEnded(this.onSessionEnd.bind(this)); - result.debugLocationUpdated(this.onLocationUpdated.bind(this)); - this.onLocationUpdated(); - return result; - } - - public get updated(): Event { - return this.updatedEmitter.event; - } - - public getLocation(session: DebugSession) { - const tracker = this.activeTrackers.get(session.id); - if (tracker) { - return tracker.debugLocation; - } - } - - private onSessionEnd(locationTracker: DebugLocationTracker) { - this.activeTrackers.delete(locationTracker.sessionId); - } - - private onLocationUpdated() { - this.updatedEmitter.fire(); - } -} diff --git a/src/client/datascience/editor-integration/cellhashprovider.ts b/src/client/datascience/editor-integration/cellhashprovider.ts deleted file mode 100644 index afc123713c84..000000000000 --- a/src/client/datascience/editor-integration/cellhashprovider.ts +++ /dev/null @@ -1,447 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import type { KernelMessage } from '@jupyterlab/services'; -import * as hashjs from 'hash.js'; -import { inject, injectable, multiInject, optional } from 'inversify'; -import stripAnsi from 'strip-ansi'; -import { Event, EventEmitter, Position, Range, TextDocumentChangeEvent, TextDocumentContentChangeEvent } from 'vscode'; - -import { splitMultilineString } from '../../../datascience-ui/common'; -import { IDebugService, IDocumentManager } from '../../common/application/types'; -import { traceError, traceInfo } from '../../common/logger'; - -import { IConfigurationService } from '../../common/types'; -import { noop } from '../../common/utils/misc'; -import { getCellResource } from '../cellFactory'; -import { CellMatcher } from '../cellMatcher'; -import { Identifiers } from '../constants'; -import { - ICell, - ICellHash, - ICellHashListener, - ICellHashProvider, - IDataScienceFileSystem, - IFileHashes, - INotebook, - INotebookExecutionLogger -} from '../types'; - -// tslint:disable-next-line:no-require-imports no-var-requires -const _escapeRegExp = require('lodash/escapeRegExp') as typeof import('lodash/escapeRegExp'); // NOSONAR -// tslint:disable-next-line: no-require-imports no-var-requires -const _escape = require('lodash/escape') as typeof import('lodash/escape'); // NOSONAR -const LineNumberMatchRegex = /(;32m[ ->]*?)(\d+)(.*)/g; - -interface IRangedCellHash extends ICellHash { - code: string; - startOffset: number; - endOffset: number; - deleted: boolean; - realCode: string; - trimmedRightCode: string; - firstNonBlankLineIndex: number; // zero based. First non blank line of the real code. -} - -// This class provides hashes for debugging jupyter cells. Call getHashes just before starting debugging to compute all of the -// hashes for cells. -@injectable() -export class CellHashProvider implements ICellHashProvider, INotebookExecutionLogger { - // tslint:disable-next-line: no-any - private postEmitter: EventEmitter<{ message: string; payload: any }> = new EventEmitter<{ - message: string; - // tslint:disable-next-line: no-any - payload: any; - }>(); - // Map of file to Map of start line to actual hash - private executionCount: number = 0; - private hashes: Map = new Map(); - private updateEventEmitter: EventEmitter = new EventEmitter(); - private traceBackRegexes = new Map(); - - constructor( - @inject(IDocumentManager) private documentManager: IDocumentManager, - @inject(IConfigurationService) private configService: IConfigurationService, - @inject(IDebugService) private debugService: IDebugService, - @inject(IDataScienceFileSystem) private fs: IDataScienceFileSystem, - @multiInject(ICellHashListener) @optional() private listeners: ICellHashListener[] | undefined - ) { - // Watch document changes so we can update our hashes - this.documentManager.onDidChangeTextDocument(this.onChangedDocument.bind(this)); - } - - public dispose() { - this.hashes.clear(); - this.traceBackRegexes.clear(); - } - - public get updated(): Event { - return this.updateEventEmitter.event; - } - - // tslint:disable-next-line: no-any - public get postMessage(): Event<{ message: string; payload: any }> { - return this.postEmitter.event; - } - - public getHashes(): IFileHashes[] { - return [...this.hashes.entries()] - .map((e) => { - return { - file: e[0], - hashes: e[1].filter((h) => !h.deleted) - }; - }) - .filter((e) => e.hashes.length > 0); - } - - public onKernelRestarted() { - this.hashes.clear(); - this.traceBackRegexes.clear(); - this.executionCount = 0; - this.updateEventEmitter.fire(); - } - - public async preExecute(cell: ICell, silent: boolean): Promise { - try { - if (!silent) { - // Don't log empty cells - const stripped = this.extractExecutableLines(cell); - if (stripped.length > 0 && stripped.find((s) => s.trim().length > 0)) { - // When the user adds new code, we know the execution count is increasing - this.executionCount += 1; - - // Skip hash on unknown file though - if (cell.file !== Identifiers.EmptyFileName) { - await this.addCellHash(cell, this.executionCount); - } - } - } - } catch (exc) { - // Don't let exceptions in a preExecute mess up normal operation - traceError(exc); - } - } - - public async postExecute(_cell: ICell, _silent: boolean): Promise { - noop(); - } - - public preHandleIOPub(msg: KernelMessage.IIOPubMessage): KernelMessage.IIOPubMessage { - // When an error message comes, rewrite the traceback so we can jump back to the correct - // cell. For now this only works with the interactive window - if (msg.header.msg_type === 'error') { - return { - ...msg, - content: { - ...msg.content, - transient: this.modifyTraceback(msg as KernelMessage.IErrorMsg) // NOSONAR - } - }; - } - return msg; - } - - public extractExecutableLines(cell: ICell): string[] { - const cellMatcher = new CellMatcher(this.configService.getSettings(getCellResource(cell)).datascience); - const lines = splitMultilineString(cell.data.source); - // Only strip this off the first line. Otherwise we want the markers in the code. - if (lines.length > 0 && (cellMatcher.isCode(lines[0]) || cellMatcher.isMarkdown(lines[0]))) { - return lines.slice(1); - } - return lines; - } - - public generateHashFileName(cell: ICell, expectedCount: number): string { - // First get the true lines from the cell - const { stripped } = this.extractStrippedLines(cell); - - // Then use that to make a hash value - const hashedCode = stripped.join(''); - const hash = hashjs.sha1().update(hashedCode).digest('hex').substr(0, 12); - return ``; - } - - // tslint:disable-next-line: cyclomatic-complexity - public async addCellHash(cell: ICell, expectedCount: number): Promise { - // Find the text document that matches. We need more information than - // the add code gives us - const doc = this.documentManager.textDocuments.find((d) => this.fs.areLocalPathsSame(d.fileName, cell.file)); - if (doc) { - // Compute the code that will really be sent to jupyter - const { stripped, trueStartLine } = this.extractStrippedLines(cell); - - const line = doc.lineAt(trueStartLine); - const endLine = doc.lineAt(Math.min(trueStartLine + stripped.length - 1, doc.lineCount - 1)); - - // Find the first non blank line - let firstNonBlankIndex = 0; - while (firstNonBlankIndex < stripped.length && stripped[firstNonBlankIndex].trim().length === 0) { - firstNonBlankIndex += 1; - } - - // Use the original values however to track edits. This is what we need - // to move around - const startOffset = doc.offsetAt(new Position(cell.line, 0)); - const endOffset = doc.offsetAt(endLine.rangeIncludingLineBreak.end); - - // Compute the runtime line and adjust our cell/stripped source for debugging - const runtimeLine = this.adjustRuntimeForDebugging(cell, stripped, startOffset, endOffset); - const hashedCode = stripped.join(''); - const realCode = doc.getText(new Range(new Position(cell.line, 0), endLine.rangeIncludingLineBreak.end)); - - const hash: IRangedCellHash = { - hash: hashjs.sha1().update(hashedCode).digest('hex').substr(0, 12), - line: line ? line.lineNumber + 1 : 1, - endLine: endLine ? endLine.lineNumber + 1 : 1, - firstNonBlankLineIndex: firstNonBlankIndex + trueStartLine, - executionCount: expectedCount, - startOffset, - endOffset, - deleted: false, - code: hashedCode, - trimmedRightCode: stripped.map((s) => s.replace(/[ \t\r]+\n$/g, '\n')).join(''), - realCode, - runtimeLine, - id: cell.id, - timestamp: Date.now() - }; - - traceInfo(`Adding hash for ${expectedCount} = ${hash.hash} with ${stripped.length} lines`); - - let list = this.hashes.get(cell.file); - if (!list) { - list = []; - } - - // Figure out where to put the item in the list - let inserted = false; - for (let i = 0; i < list.length && !inserted; i += 1) { - const pos = list[i]; - if (hash.line >= pos.line && hash.line <= pos.endLine) { - // Stick right here. This is either the same cell or a cell that overwrote where - // we were. - list.splice(i, 1, hash); - inserted = true; - } else if (pos.line > hash.line) { - // This item comes just after the cell we're inserting. - list.splice(i, 0, hash); - inserted = true; - } - } - if (!inserted) { - list.push(hash); - } - this.hashes.set(cell.file, list); - - // Save a regex to find this file later when looking for - // exceptions in output - if (!this.traceBackRegexes.has(cell.file)) { - const fileDisplayName = this.fs.getDisplayName(cell.file); - const escaped = _escapeRegExp(fileDisplayName); - const fileMatchRegex = new RegExp(`\\[.*?;32m${escaped}`); - this.traceBackRegexes.set(cell.file, fileMatchRegex); - } - - // Tell listeners we have new hashes. - if (this.listeners) { - const hashes = this.getHashes(); - await Promise.all(this.listeners.map((l) => l.hashesUpdated(hashes))); - - // Then fire our event - this.updateEventEmitter.fire(); - } - } - } - - public getExecutionCount(): number { - return this.executionCount; - } - - public incExecutionCount(): void { - this.executionCount += 1; - } - - private onChangedDocument(e: TextDocumentChangeEvent) { - // See if the document is in our list of docs to watch - const perFile = this.hashes.get(e.document.fileName); - if (perFile) { - // Apply the content changes to the file's cells. - const docText = e.document.getText(); - e.contentChanges.forEach((c) => { - this.handleContentChange(docText, c, perFile); - }); - } - } - - private extractStrippedLines(cell: ICell): { stripped: string[]; trueStartLine: number } { - // Compute the code that will really be sent to jupyter - const lines = splitMultilineString(cell.data.source); - const stripped = this.extractExecutableLines(cell); - - // Figure out our true 'start' line. This is what we need to tell the debugger is - // actually the start of the code as that's what Jupyter will be getting. - let trueStartLine = cell.line; - for (let i = 0; i < stripped.length; i += 1) { - if (stripped[i] !== lines[i]) { - trueStartLine += i + 1; - break; - } - } - - // Find the first non blank line - let firstNonBlankIndex = 0; - while (firstNonBlankIndex < stripped.length && stripped[firstNonBlankIndex].trim().length === 0) { - firstNonBlankIndex += 1; - } - - // Jupyter also removes blank lines at the end. Make sure only one - let lastLinePos = stripped.length - 1; - let nextToLastLinePos = stripped.length - 2; - while (nextToLastLinePos > 0) { - const lastLine = stripped[lastLinePos]; - const nextToLastLine = stripped[nextToLastLinePos]; - if ( - (lastLine.length === 0 || lastLine === '\n') && - (nextToLastLine.length === 0 || nextToLastLine === '\n') - ) { - stripped.splice(lastLinePos, 1); - lastLinePos -= 1; - nextToLastLinePos -= 1; - } else { - break; - } - } - // Make sure the last line with actual content ends with a linefeed - if (!stripped[lastLinePos].endsWith('\n') && stripped[lastLinePos].length > 0) { - stripped[lastLinePos] = `${stripped[lastLinePos]}\n`; - } - - return { stripped, trueStartLine }; - } - - private handleContentChange(docText: string, c: TextDocumentContentChangeEvent, hashes: IRangedCellHash[]) { - // First compute the number of lines that changed - const lineDiff = c.range.start.line - c.range.end.line + c.text.split('\n').length - 1; - const offsetDiff = c.text.length - c.rangeLength; - - // Compute the inclusive offset that is changed by the cell. - const endChangedOffset = c.rangeLength <= 0 ? c.rangeOffset : c.rangeOffset + c.rangeLength - 1; - - hashes.forEach((h) => { - // See how this existing cell compares to the change - if (h.endOffset < c.rangeOffset) { - // No change. This cell is entirely before the change - } else if (h.startOffset > endChangedOffset) { - // This cell is after the text that got replaced. Adjust its start/end lines - h.line += lineDiff; - h.endLine += lineDiff; - h.startOffset += offsetDiff; - h.endOffset += offsetDiff; - } else if (h.startOffset === endChangedOffset) { - // Cell intersects but exactly, might be a replacement or an insertion - if (h.deleted || c.rangeLength > 0 || lineDiff === 0) { - // Replacement - h.deleted = docText.substr(h.startOffset, h.endOffset - h.startOffset) !== h.realCode; - } else { - // Insertion - h.line += lineDiff; - h.endLine += lineDiff; - h.startOffset += offsetDiff; - h.endOffset += offsetDiff; - } - } else { - // Intersection, delete if necessary - h.deleted = docText.substr(h.startOffset, h.endOffset - h.startOffset) !== h.realCode; - } - }); - } - - private adjustRuntimeForDebugging( - cell: ICell, - source: string[], - _cellStartOffset: number, - _cellEndOffset: number - ): number { - if ( - this.debugService.activeDebugSession && - this.configService.getSettings(getCellResource(cell)).datascience.stopOnFirstLineWhileDebugging - ) { - // Inject the breakpoint line - source.splice(0, 0, 'breakpoint()\n'); - cell.data.source = source; - cell.extraLines = [0]; - - // Start on the second line - return 2; - } - // No breakpoint necessary, start on the first line - return 1; - } - - // This function will modify a traceback from an error message. - // Tracebacks take a form like so: - // "---------------------------------------------------------------------------" - // "ZeroDivisionError Traceback (most recent call last)" - // "d:\Training\SnakePython\foo.py in \n 1 print('some more')\n ----> 2 cause_error()\n " - // "d:\Training\SnakePython\foo.py in cause_error()\n 3 print('error')\n  4 print('now')\n ----> 5 print( 1 / 0)\n " - // "ZeroDivisionError: division by zero" - // Each item in the array being a stack frame. - private modifyTraceback(msg: KernelMessage.IErrorMsg): string[] { - // Do one frame at a time. - return msg.content.traceback ? msg.content.traceback.map(this.modifyTracebackFrame.bind(this)) : []; - } - - private findCellOffset(hashes: IRangedCellHash[] | undefined, codeLines: string): number | undefined { - if (hashes) { - // Go through all cell code looking for these code lines exactly - // (although with right side trimmed as that's what a stack trace does) - for (const hash of hashes) { - const index = hash.trimmedRightCode.indexOf(codeLines); - if (index >= 0) { - // Jupyter isn't counting blank lines at the top so use our - // first non blank line - return hash.firstNonBlankLineIndex; - } - } - } - // No hash found - return undefined; - } - - private modifyTracebackFrame(traceFrame: string): string { - // See if this item matches any of our cell files - const regexes = [...this.traceBackRegexes.entries()]; - const match = regexes.find((e) => e[1].test(traceFrame)); - if (match) { - // We have a match, pull out the source lines - let sourceLines = ''; - const regex = /(;32m[ ->]*?)(\d+)(.*)/g; - for (let l = regex.exec(traceFrame); l && l.length > 3; l = regex.exec(traceFrame)) { - const newLine = stripAnsi(l[3]).substr(1); // Seem to have a space on the front - sourceLines = `${sourceLines}${newLine}\n`; - } - - // Now attempt to find a cell that matches these source lines - const offset = this.findCellOffset(this.hashes.get(match[0]), sourceLines); - if (offset !== undefined) { - return traceFrame.replace(LineNumberMatchRegex, (_s, prefix, num, suffix) => { - const n = parseInt(num, 10); - const newLine = offset + n - 1; - return `${_escape(prefix)}${newLine + 1}${_escape( - suffix - )}`; - }); - } - } - return _escape(traceFrame); - } -} - -export function getCellHashProvider(notebook: INotebook): ICellHashProvider | undefined { - const logger = notebook.getLoggers().find((f) => f instanceof CellHashProvider); - if (logger) { - // tslint:disable-next-line: no-any - return (logger as any) as ICellHashProvider; - } -} diff --git a/src/client/datascience/editor-integration/codeLensFactory.ts b/src/client/datascience/editor-integration/codeLensFactory.ts deleted file mode 100644 index a3ced5f49440..000000000000 --- a/src/client/datascience/editor-integration/codeLensFactory.ts +++ /dev/null @@ -1,497 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { inject, injectable } from 'inversify'; -import { CodeLens, Command, Event, EventEmitter, Range, TextDocument, Uri } from 'vscode'; - -import { IDocumentManager } from '../../common/application/types'; -import { traceWarning } from '../../common/logger'; - -import { IConfigurationService, Resource } from '../../common/types'; -import * as localize from '../../common/utils/localize'; -import { noop } from '../../common/utils/misc'; -import { generateCellRangesFromDocument } from '../cellFactory'; -import { CodeLensCommands, Commands, Identifiers } from '../constants'; -import { InteractiveWindowMessages, SysInfoReason } from '../interactive-common/interactiveWindowTypes'; -import { - ICell, - ICellHashProvider, - ICellRange, - ICodeLensFactory, - IDataScienceFileSystem, - IFileHashes, - IInteractiveWindowListener, - INotebook, - INotebookProvider -} from '../types'; -import { getCellHashProvider } from './cellhashprovider'; - -type CodeLensCacheData = { - cachedDocumentVersion: number | undefined; - cachedExecutionCounts: Set; - documentLenses: CodeLens[]; - cellRanges: ICellRange[]; - gotoCellLens: CodeLens[]; -}; - -type PerNotebookData = { - cellExecutionCounts: Map; - documentExecutionCounts: Map; - hashProvider: ICellHashProvider | undefined; -}; - -/** - * This class is a singleton that generates code lenses for any document the user opens. It listens - * to cells being execute so it can add 'goto' lenses on cells that have already been run. - */ -@injectable() -export class CodeLensFactory implements ICodeLensFactory, IInteractiveWindowListener { - private updateEvent: EventEmitter = new EventEmitter(); - // tslint:disable-next-line: no-any - private postEmitter: EventEmitter<{ message: string; payload: any }> = new EventEmitter<{ - message: string; - // tslint:disable-next-line: no-any - payload: any; - }>(); - private notebookData = new Map(); - private codeLensCache = new Map(); - constructor( - @inject(IConfigurationService) private configService: IConfigurationService, - @inject(INotebookProvider) private notebookProvider: INotebookProvider, - @inject(IDataScienceFileSystem) private fs: IDataScienceFileSystem, - @inject(IDocumentManager) private documentManager: IDocumentManager - ) { - this.documentManager.onDidCloseTextDocument(this.onClosedDocument.bind(this)); - this.configService.getSettings(undefined).onDidChange(this.onChangedSettings.bind(this)); - this.notebookProvider.onNotebookCreated(this.onNotebookCreated.bind(this)); - } - - public dispose(): void { - noop(); - } - - // tslint:disable-next-line: no-any - public get postMessage(): Event<{ message: string; payload: any }> { - return this.postEmitter.event; - } - - // tslint:disable-next-line: no-any - public onMessage(message: string, payload?: any) { - switch (message) { - case InteractiveWindowMessages.NotebookIdentity: - if (payload.type === 'interactive') { - this.trackNotebook(payload.resource); - } - break; - - case InteractiveWindowMessages.NotebookClose: - if (payload.type === 'interactive') { - this.untrackNotebook(payload.resource); - } - break; - - case InteractiveWindowMessages.AddedSysInfo: - if (payload && payload.type) { - const reason = payload.type as SysInfoReason; - if (reason !== SysInfoReason.Interrupt) { - this.clearExecutionCounts(payload.notebookIdentity); - } - } - break; - - case InteractiveWindowMessages.FinishCell: - const cell = payload.cell as ICell; - if (cell && cell.data && cell.data.execution_count) { - if (cell.file && cell.file !== Identifiers.EmptyFileName) { - this.updateExecutionCounts(payload.notebookIdentity, cell); - } - } - break; - - default: - break; - } - } - - public get updateRequired(): Event { - return this.updateEvent.event; - } - - public createCodeLenses(document: TextDocument): CodeLens[] { - const cache = this.getCodeLensCacheData(document); - return [...cache.documentLenses, ...cache.gotoCellLens]; - } - - public getCellRanges(document: TextDocument): ICellRange[] { - const cache = this.getCodeLensCacheData(document); - return cache.cellRanges; - } - - private getCodeLensCacheData(document: TextDocument): CodeLensCacheData { - // See if we have a cached version of the code lenses for this document - const key = document.fileName.toLocaleLowerCase(); - let cache = this.codeLensCache.get(key); - let needUpdate = false; - - // If we don't have one, generate one - if (!cache) { - cache = { - cachedDocumentVersion: undefined, - cachedExecutionCounts: new Set(), - documentLenses: [], - cellRanges: [], - gotoCellLens: [] - }; - needUpdate = true; - this.codeLensCache.set(key, cache); - } - - // If the document version doesn't match, our cell ranges are out of date - if (cache.cachedDocumentVersion !== document.version) { - cache.cellRanges = generateCellRangesFromDocument( - document, - this.configService.getSettings(document.uri).datascience - ); - - // Because we have all new ranges, we need to recompute ALL of our code lenses. - cache.documentLenses = []; - cache.gotoCellLens = []; - cache.cachedDocumentVersion = document.version; - needUpdate = true; - } - - // If the document execution count doesn't match, then our goto cell lenses are out of date - const documentCounts = this.getDocumentExecutionCounts(key); - if ( - documentCounts.length !== cache.cachedExecutionCounts.size || - documentCounts.find((n) => !cache?.cachedExecutionCounts.has(n)) - ) { - cache.gotoCellLens = []; - cache.cachedExecutionCounts = new Set(documentCounts); - needUpdate = true; - } - - // Generate our code lenses if necessary - if (cache.documentLenses.length === 0 && needUpdate && cache.cellRanges.length) { - // Enumerate the possible commands for the document based code lenses - const commands = needUpdate ? this.enumerateCommands(document.uri) : []; - - // Then iterate over all of the cell ranges and generate code lenses for each possible - // commands - let firstCell = true; - cache.cellRanges.forEach((r) => { - commands.forEach((c) => { - const codeLens = this.createCodeLens(document, r, c, firstCell); - if (codeLens) { - cache?.documentLenses.push(codeLens); // NOSONAR - } - }); - firstCell = false; - }); - } - - // Generate the goto cell lenses if necessary - if ( - needUpdate && - cache.gotoCellLens.length === 0 && - cache.cellRanges.length && - this.configService.getSettings(document.uri).datascience.addGotoCodeLenses - ) { - const hashes = this.getHashes(); - if (hashes && hashes.length) { - cache.cellRanges.forEach((r) => { - const codeLens = this.createExecutionLens(document, r.range, hashes); - if (codeLens) { - cache?.gotoCellLens.push(codeLens); // NOSONAR - } - }); - } - } - return cache; - } - - private trackNotebook(identity: Uri) { - // Setup our per notebook data if not already tracked. - if (!this.notebookData.has(identity.toString())) { - this.notebookData.set(identity.toString(), this.createNotebookData()); - } - } - - private createNotebookData(): PerNotebookData { - return { - cellExecutionCounts: new Map(), - documentExecutionCounts: new Map(), - hashProvider: undefined - }; - } - - private untrackNotebook(identity: Uri) { - this.notebookData.delete(identity.toString()); - this.updateEvent.fire(); - } - - private clearExecutionCounts(identity: Uri) { - const data = this.notebookData.get(identity.toString()); - if (data) { - data.cellExecutionCounts.clear(); - data.documentExecutionCounts.clear(); - this.updateEvent.fire(); - } - } - - private getDocumentExecutionCounts(key: string): number[] { - return [...this.notebookData.values()] - .map((d) => d.documentExecutionCounts.get(key)) - .filter((n) => n !== undefined) as number[]; - } - - private updateExecutionCounts(identity: Uri, cell: ICell) { - let data = this.notebookData.get(identity.toString()); - if (!data) { - data = this.createNotebookData(); - } - if (data && cell.data.execution_count) { - data.cellExecutionCounts.set(cell.id, cell.data.execution_count?.toString()); - data.documentExecutionCounts.set( - cell.file.toLowerCase(), - parseInt(cell.data.execution_count.toString(), 10) - ); - this.updateEvent.fire(); - } - } - - private onNotebookCreated(args: { identity: Uri; notebook: INotebook }) { - const key = args.identity.toString(); - let data = this.notebookData.get(key); - if (!data) { - data = this.createNotebookData(); - this.notebookData.set(key, data); - } - if (data) { - data.hashProvider = getCellHashProvider(args.notebook); - } - args.notebook.onDisposed(() => { - this.notebookData.delete(key); - }); - } - - private getHashProviders(): ICellHashProvider[] { - return [...this.notebookData.values()].filter((v) => v.hashProvider).map((v) => v.hashProvider!); - } - - private getHashes(): IFileHashes[] { - // Get all of the hash providers and get all of their hashes - const providers = this.getHashProviders(); - - // Combine them together into one big array - return providers && providers.length ? providers.map((p) => p!.getHashes()).reduce((p, c) => [...p, ...c]) : []; - } - - private onClosedDocument(doc: TextDocument) { - this.codeLensCache.delete(doc.fileName.toLocaleLowerCase()); - - // Don't delete the document execution count, we need to keep track - // of it past the closing of a doc if the notebook or interactive window is still open. - } - - private onChangedSettings() { - // When config settings change, refresh our code lenses. - this.codeLensCache.clear(); - - // Force an update so that code lenses are recomputed now and not during execution. - this.updateEvent.fire(); - } - - private enumerateCommands(resource: Resource): string[] { - let fullCommandList: string[]; - // Add our non-debug commands - const commands = this.configService.getSettings(resource).datascience.codeLenses; - if (commands) { - fullCommandList = commands.split(',').map((s) => s.trim()); - } else { - fullCommandList = CodeLensCommands.DefaultDesignLenses; - } - - // Add our debug commands - const debugCommands = this.configService.getSettings(resource).datascience.debugCodeLenses; - if (debugCommands) { - fullCommandList = fullCommandList.concat(debugCommands.split(',').map((s) => s.trim())); - } else { - fullCommandList = fullCommandList.concat(CodeLensCommands.DefaultDebuggingLenses); - } - - return fullCommandList; - } - - // tslint:disable-next-line: max-func-body-length - private createCodeLens( - document: TextDocument, - cellRange: { range: Range; cell_type: string }, - commandName: string, - isFirst: boolean - ): CodeLens | undefined { - // We only support specific commands - // Be careful here. These arguments will be serialized during liveshare sessions - // and so shouldn't reference local objects. - const { range, cell_type } = cellRange; - switch (commandName) { - case Commands.RunCurrentCellAndAddBelow: - return this.generateCodeLens( - range, - Commands.RunCurrentCellAndAddBelow, - localize.DataScience.runCurrentCellAndAddBelow() - ); - case Commands.AddCellBelow: - return this.generateCodeLens( - range, - Commands.AddCellBelow, - localize.DataScience.addCellBelowCommandTitle(), - [document.uri, range.start.line] - ); - case Commands.DebugCurrentCellPalette: - return this.generateCodeLens( - range, - Commands.DebugCurrentCellPalette, - localize.DataScience.debugCellCommandTitle() - ); - - case Commands.DebugCell: - // If it's not a code cell (e.g. markdown), don't add the "Debug cell" action. - if (cell_type !== 'code') { - break; - } - return this.generateCodeLens(range, Commands.DebugCell, localize.DataScience.debugCellCommandTitle(), [ - document.uri, - range.start.line, - range.start.character, - range.end.line, - range.end.character - ]); - - case Commands.DebugStepOver: - // Only code cells get debug actions - if (cell_type !== 'code') { - break; - } - return this.generateCodeLens( - range, - Commands.DebugStepOver, - localize.DataScience.debugStepOverCommandTitle() - ); - - case Commands.DebugContinue: - // Only code cells get debug actions - if (cell_type !== 'code') { - break; - } - return this.generateCodeLens( - range, - Commands.DebugContinue, - localize.DataScience.debugContinueCommandTitle() - ); - - case Commands.DebugStop: - // Only code cells get debug actions - if (cell_type !== 'code') { - break; - } - return this.generateCodeLens(range, Commands.DebugStop, localize.DataScience.debugStopCommandTitle()); - - case Commands.RunCurrentCell: - case Commands.RunCell: - return this.generateCodeLens(range, Commands.RunCell, localize.DataScience.runCellLensCommandTitle(), [ - document.uri, - range.start.line, - range.start.character, - range.end.line, - range.end.character - ]); - - case Commands.RunAllCells: - return this.generateCodeLens( - range, - Commands.RunAllCells, - localize.DataScience.runAllCellsLensCommandTitle(), - [document.uri, range.start.line, range.start.character] - ); - - case Commands.RunAllCellsAbovePalette: - case Commands.RunAllCellsAbove: - if (!isFirst) { - return this.generateCodeLens( - range, - Commands.RunAllCellsAbove, - localize.DataScience.runAllCellsAboveLensCommandTitle(), - [document.uri, range.start.line, range.start.character] - ); - } else { - return this.generateCodeLens( - range, - Commands.RunCellAndAllBelow, - localize.DataScience.runCellAndAllBelowLensCommandTitle(), - [document.uri, range.start.line, range.start.character] - ); - } - break; - case Commands.RunCellAndAllBelowPalette: - case Commands.RunCellAndAllBelow: - return this.generateCodeLens( - range, - Commands.RunCellAndAllBelow, - localize.DataScience.runCellAndAllBelowLensCommandTitle(), - [document.uri, range.start.line, range.start.character] - ); - - default: - traceWarning(`Invalid command for code lens ${commandName}`); - break; - } - - return undefined; - } - - private findMatchingCellExecutionCount(cellId: string) { - // Cell ids on interactive window are generated on the fly so there shouldn't be dupes - const data = [...this.notebookData.values()].find((d) => d.cellExecutionCounts.get(cellId)); - return data?.cellExecutionCounts.get(cellId); - } - - private createExecutionLens(document: TextDocument, range: Range, hashes: IFileHashes[]) { - const list = hashes - .filter((h) => this.fs.areLocalPathsSame(h.file, document.fileName)) - .map((f) => f.hashes) - .flat(); - if (list) { - // Match just the start of the range. Should be - 2 (1 for 1 based numbers and 1 for skipping the comment at the top) - const rangeMatches = list - .filter((h) => h.line - 2 === range.start.line) - .sort((a, b) => a.timestamp - b.timestamp); - if (rangeMatches && rangeMatches.length) { - const rangeMatch = rangeMatches[rangeMatches.length - 1]; - const matchingExecutionCount = this.findMatchingCellExecutionCount(rangeMatch.id); - if (matchingExecutionCount !== undefined) { - return this.generateCodeLens( - range, - Commands.ScrollToCell, - localize.DataScience.scrollToCellTitleFormatMessage().format(matchingExecutionCount), - [document.uri, rangeMatch.id] - ); - } - } - } - } - - // tslint:disable-next-line: no-any - private generateCodeLens(range: Range, commandName: string, title: string, args?: any[]): CodeLens { - return new CodeLens(range, this.generateCommand(commandName, title, args)); - } - - // tslint:disable-next-line: no-any - private generateCommand(commandName: string, title: string, args?: any[]): Command { - return { - arguments: args, - title, - command: commandName - }; - } -} diff --git a/src/client/datascience/editor-integration/codelensprovider.ts b/src/client/datascience/editor-integration/codelensprovider.ts deleted file mode 100644 index fcf233ca91e6..000000000000 --- a/src/client/datascience/editor-integration/codelensprovider.ts +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { inject, injectable } from 'inversify'; -import * as vscode from 'vscode'; - -import { ICommandManager, IDebugService, IDocumentManager, IVSCodeNotebook } from '../../common/application/types'; -import { ContextKey } from '../../common/contextKey'; - -import { IConfigurationService, IDataScienceSettings, IDisposable, IDisposableRegistry } from '../../common/types'; -import { StopWatch } from '../../common/utils/stopWatch'; -import { IServiceContainer } from '../../ioc/types'; -import { sendTelemetryEvent } from '../../telemetry'; -import { CodeLensCommands, EditorContexts, Telemetry } from '../constants'; -import { ICodeWatcher, IDataScienceCodeLensProvider, IDataScienceFileSystem, IDebugLocationTracker } from '../types'; - -@injectable() -export class DataScienceCodeLensProvider implements IDataScienceCodeLensProvider, IDisposable { - private totalExecutionTimeInMs: number = 0; - private totalGetCodeLensCalls: number = 0; - private activeCodeWatchers: ICodeWatcher[] = []; - private didChangeCodeLenses: vscode.EventEmitter = new vscode.EventEmitter(); - constructor( - @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(IDebugLocationTracker) private debugLocationTracker: IDebugLocationTracker, - @inject(IDocumentManager) private documentManager: IDocumentManager, - @inject(IConfigurationService) private configuration: IConfigurationService, - @inject(ICommandManager) private commandManager: ICommandManager, - @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, - @inject(IDebugService) private debugService: IDebugService, - @inject(IDataScienceFileSystem) private fs: IDataScienceFileSystem, - @inject(IVSCodeNotebook) private readonly vsCodeNotebook: IVSCodeNotebook - ) { - disposableRegistry.push(this); - disposableRegistry.push(this.debugService.onDidChangeActiveDebugSession(this.onChangeDebugSession.bind(this))); - disposableRegistry.push(this.documentManager.onDidCloseTextDocument(this.onDidCloseTextDocument.bind(this))); - disposableRegistry.push(this.debugLocationTracker.updated(this.onDebugLocationUpdated.bind(this))); - } - - public dispose() { - // On shutdown send how long on average we spent parsing code lens - if (this.totalGetCodeLensCalls > 0) { - sendTelemetryEvent( - Telemetry.CodeLensAverageAcquisitionTime, - this.totalExecutionTimeInMs / this.totalGetCodeLensCalls - ); - } - } - - public get onDidChangeCodeLenses(): vscode.Event { - return this.didChangeCodeLenses.event; - } - - // CodeLensProvider interface - // Some implementation based on DonJayamanne's jupyter extension work - public provideCodeLenses(document: vscode.TextDocument, _token: vscode.CancellationToken): vscode.CodeLens[] { - if (this.vsCodeNotebook.activeNotebookEditor) { - return []; - } - // Get the list of code lens for this document. - return this.getCodeLensTimed(document); - } - - // IDataScienceCodeLensProvider interface - public getCodeWatcher(document: vscode.TextDocument): ICodeWatcher | undefined { - return this.matchWatcher( - document.fileName, - document.version, - this.configuration.getSettings(document.uri).datascience - ); - } - - private onDebugLocationUpdated() { - this.didChangeCodeLenses.fire(); - } - - private onChangeDebugSession(_e: vscode.DebugSession | undefined) { - this.didChangeCodeLenses.fire(); - } - - private onDidCloseTextDocument(e: vscode.TextDocument) { - const index = this.activeCodeWatchers.findIndex( - (item) => item.uri && this.fs.areLocalPathsSame(item.uri.fsPath, e.fileName) - ); - if (index >= 0) { - this.activeCodeWatchers.splice(index, 1); - } - } - - private getCodeLensTimed(document: vscode.TextDocument): vscode.CodeLens[] { - const stopWatch = new StopWatch(); - const result = this.getCodeLens(document); - this.totalExecutionTimeInMs += stopWatch.elapsedTime; - this.totalGetCodeLensCalls += 1; - - // Update the hasCodeCells context at the same time we are asked for codelens as VS code will - // ask whenever a change occurs. Do this regardless of if we have code lens turned on or not as - // shift+enter relies on this code context. - const editorContext = new ContextKey(EditorContexts.HasCodeCells, this.commandManager); - editorContext.set(result && result.length > 0).catch(); - - // Don't provide any code lenses if we have not enabled data science - const settings = this.configuration.getSettings(document.uri); - if (!settings.datascience.enabled || !settings.datascience.enableCellCodeLens) { - // Clear out any existing code watchers, providecodelenses is called on settings change - // so we don't need to watch the settings change specifically here - if (this.activeCodeWatchers.length > 0) { - this.activeCodeWatchers = []; - } - return []; - } - - return this.adjustDebuggingLenses(document, result); - } - - // Adjust what code lenses are visible or not given debug mode and debug context location - private adjustDebuggingLenses(document: vscode.TextDocument, lenses: vscode.CodeLens[]): vscode.CodeLens[] { - const debugCellList = CodeLensCommands.DebuggerCommands; - - if (this.debugService.activeDebugSession) { - const debugLocation = this.debugLocationTracker.getLocation(this.debugService.activeDebugSession); - - if (debugLocation && this.fs.areLocalPathsSame(debugLocation.fileName, document.uri.fsPath)) { - // We are in the given debug file, so only return the code lens that contains the given line - const activeLenses = lenses.filter((lens) => { - // -1 for difference between file system one based and debugger zero based - const pos = new vscode.Position(debugLocation.lineNumber - 1, debugLocation.column - 1); - return lens.range.contains(pos); - }); - - return activeLenses.filter((lens) => { - if (lens.command) { - return debugCellList.includes(lens.command.command); - } - return false; - }); - } - } else { - return lenses.filter((lens) => { - if (lens.command) { - return !debugCellList.includes(lens.command.command); - } - return false; - }); - } - - // Fall through case to return nothing - return []; - } - - private getCodeLens(document: vscode.TextDocument): vscode.CodeLens[] { - // See if we already have a watcher for this file and version - const codeWatcher: ICodeWatcher | undefined = this.matchWatcher( - document.fileName, - document.version, - this.configuration.getSettings(document.uri).datascience - ); - if (codeWatcher) { - return codeWatcher.getCodeLenses(); - } - - // Create a new watcher for this file - const newCodeWatcher = this.createNewCodeWatcher(document); - return newCodeWatcher.getCodeLenses(); - } - - private matchWatcher(fileName: string, version: number, settings: IDataScienceSettings): ICodeWatcher | undefined { - const index = this.activeCodeWatchers.findIndex( - (item) => item.uri && this.fs.areLocalPathsSame(item.uri.fsPath, fileName) - ); - if (index >= 0) { - const item = this.activeCodeWatchers[index]; - if (item.getVersion() === version) { - // Also make sure the cached settings are the same. Otherwise these code lenses - // were created with old settings - const settingsStr = JSON.stringify(settings); - const itemSettings = JSON.stringify(item.getCachedSettings()); - if (settingsStr === itemSettings) { - return item; - } - } - // If we have an old version remove it from the active list - this.activeCodeWatchers.splice(index, 1); - } - - // Create a new watcher for this file if we can find a matching document - const possibleDocuments = this.documentManager.textDocuments.filter((d) => d.fileName === fileName); - if (possibleDocuments && possibleDocuments.length > 0) { - return this.createNewCodeWatcher(possibleDocuments[0]); - } - - return undefined; - } - - private createNewCodeWatcher(document: vscode.TextDocument): ICodeWatcher { - const newCodeWatcher = this.serviceContainer.get(ICodeWatcher); - newCodeWatcher.setDocument(document); - newCodeWatcher.codeLensUpdated(this.onWatcherUpdated.bind(this)); - this.activeCodeWatchers.push(newCodeWatcher); - return newCodeWatcher; - } - - private onWatcherUpdated(): void { - this.didChangeCodeLenses.fire(); - } -} diff --git a/src/client/datascience/editor-integration/codewatcher.ts b/src/client/datascience/editor-integration/codewatcher.ts deleted file mode 100644 index ce00d51c830b..000000000000 --- a/src/client/datascience/editor-integration/codewatcher.ts +++ /dev/null @@ -1,1128 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import type { nbformat } from '@jupyterlab/coreutils'; -import { inject, injectable } from 'inversify'; -import { - CodeLens, - commands, - Event, - EventEmitter, - Position, - Range, - Selection, - TextDocument, - TextEditor, - TextEditorRevealType, - Uri -} from 'vscode'; - -import { IDocumentManager } from '../../common/application/types'; - -import { IConfigurationService, IDataScienceSettings, IDisposable, Resource } from '../../common/types'; -import * as localize from '../../common/utils/localize'; -import { StopWatch } from '../../common/utils/stopWatch'; -import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; -import { ICodeExecutionHelper } from '../../terminals/types'; -import { CellMatcher } from '../cellMatcher'; -import { Commands, Identifiers, Telemetry } from '../constants'; -import { - ICellRange, - ICodeLensFactory, - ICodeWatcher, - IDataScienceErrorHandler, - IDataScienceFileSystem, - IInteractiveWindowProvider -} from '../types'; - -function getIndex(index: number, length: number): number { - // return index within the length range with negative indexing - if (length <= 0) { - throw new RangeError(`Length must be > 0 not ${length}`); - } - // negative index count back from length - if (index < 0) { - index += length; - } - // bounded index - if (index < 0) { - return 0; - } else if (index >= length) { - return length - 1; - } else { - return index; - } -} - -@injectable() -export class CodeWatcher implements ICodeWatcher { - private static sentExecuteCellTelemetry: boolean = false; - private document?: TextDocument; - private version: number = -1; - private codeLenses: CodeLens[] = []; - private cells: ICellRange[] = []; - private cachedSettings: IDataScienceSettings | undefined; - private codeLensUpdatedEvent: EventEmitter = new EventEmitter(); - private updateRequiredDisposable: IDisposable | undefined; - private closeDocumentDisposable: IDisposable | undefined; - - constructor( - @inject(IInteractiveWindowProvider) private interactiveWindowProvider: IInteractiveWindowProvider, - @inject(IDataScienceFileSystem) private fs: IDataScienceFileSystem, - @inject(IConfigurationService) private configService: IConfigurationService, - @inject(IDocumentManager) private documentManager: IDocumentManager, - @inject(ICodeExecutionHelper) private executionHelper: ICodeExecutionHelper, - @inject(IDataScienceErrorHandler) protected dataScienceErrorHandler: IDataScienceErrorHandler, - @inject(ICodeLensFactory) private codeLensFactory: ICodeLensFactory - ) {} - - public setDocument(document: TextDocument) { - this.document = document; - - // Cache the version, we don't want to pull an old version if the document is updated - this.version = document.version; - - // Get document cells here. Make a copy of our settings. - this.cachedSettings = JSON.parse(JSON.stringify(this.configService.getSettings(document.uri).datascience)); - - // Use the factory to generate our new code lenses. - this.codeLenses = this.codeLensFactory.createCodeLenses(document); - this.cells = this.codeLensFactory.getCellRanges(document); - - // Listen for changes - this.updateRequiredDisposable = this.codeLensFactory.updateRequired(this.onCodeLensFactoryUpdated.bind(this)); - - // Make sure to stop listening for changes when this document closes. - this.closeDocumentDisposable = this.documentManager.onDidCloseTextDocument(this.onDocumentClosed.bind(this)); - } - - public get codeLensUpdated(): Event { - return this.codeLensUpdatedEvent.event; - } - - public get uri() { - return this.document?.uri; - } - - public getVersion() { - return this.version; - } - - public getCachedSettings(): IDataScienceSettings | undefined { - return this.cachedSettings; - } - - public getCodeLenses() { - return this.codeLenses; - } - - @captureTelemetry(Telemetry.DebugCurrentCell) - public async debugCurrentCell() { - if (!this.documentManager.activeTextEditor || !this.documentManager.activeTextEditor.document) { - return; - } - - // Run the cell that matches the current cursor position. - return this.runMatchingCell(this.documentManager.activeTextEditor.selection, false, true); - } - - @captureTelemetry(Telemetry.RunAllCells) - public async runAllCells() { - const runCellCommands = this.codeLenses.filter( - (c) => - c.command && - c.command.command === Commands.RunCell && - c.command.arguments && - c.command.arguments.length >= 5 - ); - let leftCount = runCellCommands.length; - - // Run all of our code lenses, they should always be ordered in the file so we can just - // run them one by one - for (const lens of runCellCommands) { - // Make sure that we have the correct command (RunCell) lenses - let range: Range = new Range( - lens.command!.arguments![1], - lens.command!.arguments![2], - lens.command!.arguments![3], - lens.command!.arguments![4] - ); - if (this.document) { - // Special case, if this is the first, expand our range to always include the top. - if (leftCount === runCellCommands.length) { - range = new Range(new Position(0, 0), range.end); - } - - const code = this.document.getText(range); - leftCount -= 1; - - // Note: We do a get or create active before all addCode commands to make sure that we either have a history up already - // or if we do not we need to start it up as these commands are all expected to start a new history if needed - const success = await this.addCode(code, this.document.uri, range.start.line); - if (!success) { - await this.addErrorMessage(this.document.uri, leftCount); - break; - } - } - } - - // If there are no codelenses, just run all of the code as a single cell - if (runCellCommands.length === 0) { - return this.runFileInteractiveInternal(false); - } - } - - @captureTelemetry(Telemetry.RunFileInteractive) - public async runFileInteractive() { - return this.runFileInteractiveInternal(false); - } - - @captureTelemetry(Telemetry.DebugFileInteractive) - public async debugFileInteractive() { - return this.runFileInteractiveInternal(true); - } - - // Run all cells up to the cell containing this start line and character - @captureTelemetry(Telemetry.RunAllCellsAbove) - public async runAllCellsAbove(stopLine: number, stopCharacter: number) { - const runCellCommands = this.codeLenses.filter((c) => c.command && c.command.command === Commands.RunCell); - let leftCount = runCellCommands.findIndex( - (c) => c.range.start.line >= stopLine && c.range.start.character >= stopCharacter - ); - if (leftCount < 0) { - leftCount = runCellCommands.length; - } - const startCount = leftCount; - - // Run our code lenses up to this point, lenses are created in order on document load - // so we can rely on them being in linear order for this - for (const lens of runCellCommands) { - // Make sure we are dealing with run cell based code lenses in case more types are added later - if (leftCount > 0 && this.document) { - let range: Range = new Range(lens.range.start, lens.range.end); - - // If this is the first, make sure it extends to the top - if (leftCount === startCount) { - range = new Range(new Position(0, 0), range.end); - } - - // We have a cell and we are not past or at the stop point - leftCount -= 1; - const code = this.document.getText(range); - const success = await this.addCode(code, this.document.uri, lens.range.start.line); - if (!success) { - await this.addErrorMessage(this.document.uri, leftCount); - break; - } - } else { - // If we get a cell past or at the stop point stop - break; - } - } - } - - @captureTelemetry(Telemetry.RunCellAndAllBelow) - public async runCellAndAllBelow(startLine: number, startCharacter: number) { - const runCellCommands = this.codeLenses.filter((c) => c.command && c.command.command === Commands.RunCell); - const index = runCellCommands.findIndex( - (c) => c.range.start.line >= startLine && c.range.start.character >= startCharacter - ); - let leftCount = index > 0 ? runCellCommands.length - index : runCellCommands.length; - - // Run our code lenses from this point to the end, lenses are created in order on document load - // so we can rely on them being in linear order for this - for (let pos = index; pos >= 0 && pos < runCellCommands.length; pos += 1) { - if (leftCount > 0 && this.document) { - const lens = runCellCommands[pos]; - // We have a cell and we are not past or at the stop point - leftCount -= 1; - const code = this.document.getText(lens.range); - const success = await this.addCode(code, this.document.uri, lens.range.start.line); - if (!success) { - await this.addErrorMessage(this.document.uri, leftCount); - break; - } - } - } - } - - @captureTelemetry(Telemetry.RunSelectionOrLine) - public async runSelectionOrLine(activeEditor: TextEditor | undefined) { - if (this.document && activeEditor && this.fs.arePathsSame(activeEditor.document.uri, this.document.uri)) { - // Get just the text of the selection or the current line if none - const codeToExecute = await this.executionHelper.getSelectedTextToExecute(activeEditor); - if (!codeToExecute) { - return; - } - const normalizedCode = await this.executionHelper.normalizeLines(codeToExecute!); - if (!normalizedCode || normalizedCode.trim().length === 0) { - return; - } - await this.addCode(normalizedCode, this.document.uri, activeEditor.selection.start.line, activeEditor); - } - } - - @captureTelemetry(Telemetry.RunToLine) - public async runToLine(targetLine: number) { - if (this.document && targetLine > 0) { - const previousLine = this.document.lineAt(targetLine - 1); - const code = this.document.getText( - new Range(0, 0, previousLine.range.end.line, previousLine.range.end.character) - ); - - if (code && code.trim().length) { - await this.addCode(code, this.document.uri, 0); - } - } - } - - @captureTelemetry(Telemetry.RunFromLine) - public async runFromLine(targetLine: number) { - if (this.document && targetLine < this.document.lineCount) { - const lastLine = this.document.lineAt(this.document.lineCount - 1); - const code = this.document.getText( - new Range(targetLine, 0, lastLine.range.end.line, lastLine.range.end.character) - ); - - if (code && code.trim().length) { - await this.addCode(code, this.document.uri, targetLine); - } - } - } - - @captureTelemetry(Telemetry.RunCell) - public async runCell(range: Range): Promise { - if (!this.documentManager.activeTextEditor || !this.documentManager.activeTextEditor.document) { - return; - } - - // Run the cell clicked. Advance if the cursor is inside this cell and we're allowed to - const advance = - range.contains(this.documentManager.activeTextEditor.selection.start) && - this.configService.getSettings(this.documentManager.activeTextEditor.document.uri).datascience - .enableAutoMoveToNextCell; - return this.runMatchingCell(range, advance); - } - - @captureTelemetry(Telemetry.DebugCurrentCell) - public async debugCell(range: Range): Promise { - if (!this.documentManager.activeTextEditor || !this.documentManager.activeTextEditor.document) { - return; - } - - // Debug the cell clicked. - return this.runMatchingCell(range, false, true); - } - - @captureTelemetry(Telemetry.RunCurrentCell) - public async runCurrentCell(): Promise { - if (!this.documentManager.activeTextEditor || !this.documentManager.activeTextEditor.document) { - return; - } - - // Run the cell that matches the current cursor position. - return this.runMatchingCell(this.documentManager.activeTextEditor.selection, false); - } - - @captureTelemetry(Telemetry.RunCurrentCellAndAdvance) - public async runCurrentCellAndAdvance() { - if (!this.documentManager.activeTextEditor || !this.documentManager.activeTextEditor.document) { - return; - } - - // Run the cell that matches the current cursor position. Always advance - return this.runMatchingCell(this.documentManager.activeTextEditor.selection, true); - } - - // telemetry captured on CommandRegistry - public async addEmptyCellToBottom(): Promise { - const editor = this.documentManager.activeTextEditor; - if (editor) { - this.insertCell(editor, editor.document.lineCount + 1); - } - } - - @captureTelemetry(Telemetry.RunCurrentCellAndAddBelow) - public async runCurrentCellAndAddBelow(): Promise { - if (!this.documentManager.activeTextEditor || !this.documentManager.activeTextEditor.document) { - return; - } - - const editor = this.documentManager.activeTextEditor; - const cellMatcher = new CellMatcher(); - let index = 0; - const cellDelineator = this.getDefaultCellMarker(editor.document.uri); - - if (editor) { - editor.edit((editBuilder) => { - let lastCell = true; - - for (let i = editor.selection.end.line + 1; i < editor.document.lineCount; i += 1) { - if (cellMatcher.isCell(editor.document.lineAt(i).text)) { - lastCell = false; - index = i; - editBuilder.insert(new Position(i, 0), `${cellDelineator}\n\n`); - break; - } - } - - if (lastCell) { - index = editor.document.lineCount; - editBuilder.insert(new Position(editor.document.lineCount, 0), `\n${cellDelineator}\n`); - } - }); - } - - // Run the cell that matches the current cursor position, and then advance to the new cell - const newPosition = new Position(index + 1, 0); - return this.runMatchingCell(editor.selection, false).then(() => - this.advanceToRange(new Range(newPosition, newPosition)) - ); - } - - @captureTelemetry(Telemetry.InsertCellBelowPosition) - public insertCellBelowPosition() { - const editor = this.documentManager.activeTextEditor; - if (editor && editor.selection) { - this.insertCell(editor, editor.selection.end.line + 1); - } - } - - @captureTelemetry(Telemetry.InsertCellBelow) - public insertCellBelow() { - const editor = this.documentManager.activeTextEditor; - if (editor && editor.selection) { - const cell = this.getCellFromPosition(editor.selection.end); - if (cell) { - this.insertCell(editor, cell.range.end.line + 1); - } else { - this.insertCell(editor, editor.selection.end.line + 1); - } - } - } - - @captureTelemetry(Telemetry.InsertCellAbove) - public insertCellAbove() { - const editor = this.documentManager.activeTextEditor; - if (editor && editor.selection) { - const cell = this.getCellFromPosition(editor.selection.start); - if (cell) { - this.insertCell(editor, cell.range.start.line); - } else { - this.insertCell(editor, editor.selection.start.line); - } - } - } - - @captureTelemetry(Telemetry.DeleteCells) - public deleteCells() { - const editor = this.documentManager.activeTextEditor; - if (!editor || !editor.selection) { - return; - } - - const firstLastCells = this.getStartEndCells(editor.selection); - if (!firstLastCells) { - return; - } - const startCell = firstLastCells[0]; - const endCell = firstLastCells[1]; - - // Start of the document should start at position 0, 0 and end one line ahead. - let startLineNumber = 0; - let startCharacterNumber = 0; - let endLineNumber = endCell.range.end.line + 1; - let endCharacterNumber = 0; - // Anywhere else in the document should start at the end of line before the - // cell and end at the last character of the cell. - if (startCell.range.start.line > 0) { - startLineNumber = startCell.range.start.line - 1; - startCharacterNumber = editor.document.lineAt(startLineNumber).range.end.character; - endLineNumber = endCell.range.end.line; - endCharacterNumber = endCell.range.end.character; - } - const cellExtendedRange = new Range( - new Position(startLineNumber, startCharacterNumber), - new Position(endLineNumber, endCharacterNumber) - ); - editor.edit((editBuilder) => { - editBuilder.replace(cellExtendedRange, ''); - this.codeLensUpdatedEvent.fire(); - }); - } - - @captureTelemetry(Telemetry.SelectCell) - public selectCell() { - const editor = this.documentManager.activeTextEditor; - if (editor && editor.selection) { - const startEndCells = this.getStartEndCells(editor.selection); - if (startEndCells) { - const startCell = startEndCells[0]; - const endCell = startEndCells[1]; - if (editor.selection.anchor.isBeforeOrEqual(editor.selection.active)) { - editor.selection = new Selection(startCell.range.start, endCell.range.end); - } else { - editor.selection = new Selection(endCell.range.end, startCell.range.start); - } - } - } - } - - @captureTelemetry(Telemetry.SelectCellContents) - public selectCellContents() { - const editor = this.documentManager.activeTextEditor; - if (!editor || !editor.selection) { - return; - } - const startEndCellIndex = this.getStartEndCellIndex(editor.selection); - if (!startEndCellIndex) { - return; - } - const startCellIndex = startEndCellIndex[0]; - const endCellIndex = startEndCellIndex[1]; - const isAnchorLessEqualActive = editor.selection.anchor.isBeforeOrEqual(editor.selection.active); - - const cells = this.cells; - const selections: Selection[] = []; - for (let i = startCellIndex; i <= endCellIndex; i += 1) { - const cell = cells[i]; - let anchorLine = cell.range.start.line + 1; - let achorCharacter = 0; - let activeLine = cell.range.end.line; - let activeCharacter = cell.range.end.character; - // if cell is only one line long, select the end of that line - if (cell.range.start.line === cell.range.end.line) { - anchorLine = cell.range.start.line; - achorCharacter = editor.document.lineAt(anchorLine).range.end.character; - activeLine = anchorLine; - activeCharacter = achorCharacter; - } - if (isAnchorLessEqualActive) { - selections.push(new Selection(anchorLine, achorCharacter, activeLine, activeCharacter)); - } else { - selections.push(new Selection(activeLine, activeCharacter, anchorLine, achorCharacter)); - } - } - editor.selections = selections; - } - - @captureTelemetry(Telemetry.ExtendSelectionByCellAbove) - public extendSelectionByCellAbove() { - // This behaves similarly to excel "Extend Selection by One Cell Above". - // The direction of the selection matters (i.e. where the active cursor) - // position is. First, it ensures that complete cells are selection. - // If so, then if active cursor is in cells below it contracts the - // selection range. If the active cursor is above, it expands the - // selection range. - const editor = this.documentManager.activeTextEditor; - if (!editor || !editor.selection) { - return; - } - const currentSelection = editor.selection; - const startEndCellIndex = this.getStartEndCellIndex(editor.selection); - if (!startEndCellIndex) { - return; - } - - const isAnchorLessThanActive = editor.selection.anchor.isBefore(editor.selection.active); - - const cells = this.cells; - const startCellIndex = startEndCellIndex[0]; - const endCellIndex = startEndCellIndex[1]; - const startCell = cells[startCellIndex]; - const endCell = cells[endCellIndex]; - - if ( - !startCell.range.start.isEqual(currentSelection.start) || - !endCell.range.end.isEqual(currentSelection.end) - ) { - // full cell range not selected, first select a full cell range. - let selection: Selection; - if (isAnchorLessThanActive) { - if (startCellIndex < endCellIndex) { - // active at end of cell before endCell - selection = new Selection(startCell.range.start, cells[endCellIndex - 1].range.end); - } else { - // active at end of startCell - selection = new Selection(startCell.range.end, startCell.range.start); - } - } else { - // active at start of start cell. - selection = new Selection(endCell.range.end, startCell.range.start); - } - editor.selection = selection; - } else { - let newCell: ICellRange | undefined; - // full cell range is selected now decide if expanding or contracting? - if (isAnchorLessThanActive && startCellIndex < endCellIndex) { - // anchor is above active, contract selection by cell below. - newCell = cells[endCellIndex - 1]; - editor.selection = new Selection(startCell.range.start, newCell.range.end); - } else { - // anchor is below active, expand selection by cell above. - if (startCellIndex > 0) { - newCell = cells[startCellIndex - 1]; - editor.selection = new Selection(endCell.range.end, newCell.range.start); - } - } - - if (newCell) { - editor.revealRange(newCell.range, TextEditorRevealType.Default); - } - } - } - - @captureTelemetry(Telemetry.ExtendSelectionByCellBelow) - public extendSelectionByCellBelow() { - // This behaves similarly to excel "Extend Selection by One Cell Above". - // The direction of the selection matters (i.e. where the active cursor) - // position is. First, it ensures that complete cells are selection. - // If so, then if active cursor is in cells below it expands the - // selection range. If the active cursor is above, it contracts the - // selection range. - const editor = this.documentManager.activeTextEditor; - if (!editor || !editor.selection) { - return; - } - const currentSelection = editor.selection; - const startEndCellIndex = this.getStartEndCellIndex(editor.selection); - if (!startEndCellIndex) { - return; - } - - const isAnchorLessEqualActive = editor.selection.anchor.isBeforeOrEqual(editor.selection.active); - - const cells = this.cells; - const startCellIndex = startEndCellIndex[0]; - const endCellIndex = startEndCellIndex[1]; - const startCell = cells[startCellIndex]; - const endCell = cells[endCellIndex]; - - if ( - !startCell.range.start.isEqual(currentSelection.start) || - !endCell.range.end.isEqual(currentSelection.end) - ) { - // full cell range not selected, first select a full cell range. - let selection: Selection; - if (isAnchorLessEqualActive) { - // active at start of start cell. - selection = new Selection(startCell.range.start, endCell.range.end); - } else { - if (startCellIndex < endCellIndex) { - // active at end of cell before endCell - selection = new Selection(cells[startCellIndex + 1].range.start, endCell.range.end); - } else { - // active at end of startCell - selection = new Selection(endCell.range.start, endCell.range.end); - } - } - editor.selection = selection; - } else { - let newCell: ICellRange | undefined; - // full cell range is selected now decide if expanding or contracting? - if (isAnchorLessEqualActive || startCellIndex === endCellIndex) { - // anchor is above active, expand selection by cell below. - if (endCellIndex < cells.length - 1) { - newCell = cells[endCellIndex + 1]; - editor.selection = new Selection(startCell.range.start, newCell.range.end); - } - } else { - // anchor is below active, contract selection by cell above. - if (startCellIndex < endCellIndex) { - newCell = cells[startCellIndex + 1]; - editor.selection = new Selection(endCell.range.end, newCell.range.start); - } - } - - if (newCell) { - editor.revealRange(newCell.range, TextEditorRevealType.Default); - } - } - } - - @captureTelemetry(Telemetry.MoveCellsUp) - public async moveCellsUp(): Promise { - await this.moveCellsDirection(true); - } - - @captureTelemetry(Telemetry.MoveCellsDown) - public async moveCellsDown(): Promise { - await this.moveCellsDirection(false); - } - - @captureTelemetry(Telemetry.ChangeCellToMarkdown) - public changeCellToMarkdown() { - this.applyToCells((editor, cell, _) => { - return this.changeCellTo(editor, cell, 'markdown'); - }); - } - - @captureTelemetry(Telemetry.ChangeCellToCode) - public changeCellToCode() { - this.applyToCells((editor, cell, _) => { - return this.changeCellTo(editor, cell, 'code'); - }); - } - - @captureTelemetry(Telemetry.GotoNextCellInFile) - public gotoNextCell() { - const editor = this.documentManager.activeTextEditor; - if (!editor || !editor.selection) { - return; - } - - const currentSelection = editor.selection; - - const currentRunCellLens = this.getCurrentCellLens(currentSelection.start); - const nextRunCellLens = this.getNextCellLens(currentSelection.start); - - if (currentRunCellLens && nextRunCellLens) { - this.advanceToRange(nextRunCellLens.range); - } - } - - @captureTelemetry(Telemetry.GotoPrevCellInFile) - public gotoPreviousCell() { - const editor = this.documentManager.activeTextEditor; - if (!editor || !editor.selection) { - return; - } - - const currentSelection = editor.selection; - - const currentRunCellLens = this.getCurrentCellLens(currentSelection.start); - const prevRunCellLens = this.getPreviousCellLens(currentSelection.start); - - if (currentRunCellLens && prevRunCellLens) { - this.advanceToRange(prevRunCellLens.range); - } - } - - private applyToCells(callback: (editor: TextEditor, cell: ICellRange, cellIndex: number) => void) { - const editor = this.documentManager.activeTextEditor; - const startEndCellIndex = this.getStartEndCellIndex(editor?.selection); - if (!editor || !startEndCellIndex) { - return; - } - const cells = this.cells; - const startIndex = startEndCellIndex[0]; - const endIndex = startEndCellIndex[1]; - for (let cellIndex = startIndex; cellIndex <= endIndex; cellIndex += 1) { - callback(editor, cells[cellIndex], cellIndex); - } - } - - private changeCellTo(editor: TextEditor, cell: ICellRange, toCellType: nbformat.CellType) { - // change cell from code -> markdown or markdown -> code - if (toCellType === 'raw') { - throw Error('Cell Type raw not implemented'); - } - - // don't change cell type if already that type - if (cell.cell_type === toCellType) { - return; - } - const cellMatcher = new CellMatcher(this.configService.getSettings(editor.document.uri).datascience); - const definitionLine = editor.document.lineAt(cell.range.start.line); - const definitionText = editor.document.getText(definitionLine.range); - - // new definition text - const cellMarker = this.getDefaultCellMarker(editor.document.uri); - const definitionMatch = - toCellType === 'markdown' - ? cellMatcher.codeExecRegEx.exec(definitionText) // code -> markdown - : cellMatcher.markdownExecRegEx.exec(definitionText); // markdown -> code - if (!definitionMatch) { - return; - } - const definitionExtra = definitionMatch[definitionMatch.length - 1]; - const newDefinitionText = - toCellType === 'markdown' - ? `${cellMarker} [markdown]${definitionExtra}` // code -> markdown - : `${cellMarker}${definitionExtra}`; // markdown -> code - - editor.edit(async (editBuilder) => { - editBuilder.replace(definitionLine.range, newDefinitionText); - cell.cell_type = toCellType; - if (cell.range.start.line < cell.range.end.line) { - editor.selection = new Selection( - cell.range.start.line + 1, - 0, - cell.range.end.line, - cell.range.end.character - ); - // ensure all lines in markdown cell have a comment. - // these are not included in the test because it's unclear - // how TypeMoq works with them. - commands.executeCommand('editor.action.removeCommentLine'); - if (toCellType === 'markdown') { - commands.executeCommand('editor.action.addCommentLine'); - } - } - }); - } - - private async moveCellsDirection(directionUp: boolean): Promise { - const editor = this.documentManager.activeTextEditor; - if (!editor || !editor.selection) { - return false; - } - const startEndCellIndex = this.getStartEndCellIndex(editor.selection); - if (!startEndCellIndex) { - return false; - } - const startCellIndex = startEndCellIndex[0]; - const endCellIndex = startEndCellIndex[1]; - const cells = this.cells; - const startCell = cells[startCellIndex]; - const endCell = cells[endCellIndex]; - if (!startCell || !endCell) { - return false; - } - const currentRange = new Range(startCell.range.start, endCell.range.end); - const relativeSelectionRange = new Range( - editor.selection.start.line - currentRange.start.line, - editor.selection.start.character, - editor.selection.end.line - currentRange.start.line, - editor.selection.end.character - ); - const isActiveBeforeAnchor = editor.selection.active.isBefore(editor.selection.anchor); - let thenSetSelection: Thenable; - if (directionUp) { - if (startCellIndex === 0) { - return false; - } else { - const aboveCell = cells[startCellIndex - 1]; - const thenExchangeTextLines = this.exchangeTextLines(editor, aboveCell.range, currentRange); - thenSetSelection = thenExchangeTextLines.then((isEditSuccessful) => { - if (isEditSuccessful) { - editor.selection = new Selection( - aboveCell.range.start.line + relativeSelectionRange.start.line, - relativeSelectionRange.start.character, - aboveCell.range.start.line + relativeSelectionRange.end.line, - relativeSelectionRange.end.character - ); - } - return isEditSuccessful; - }); - } - } else { - if (endCellIndex === cells.length - 1) { - return false; - } else { - const belowCell = cells[endCellIndex + 1]; - const thenExchangeTextLines = this.exchangeTextLines(editor, currentRange, belowCell.range); - const belowCellLineLength = belowCell.range.end.line - belowCell.range.start.line; - const aboveCellLineLength = currentRange.end.line - currentRange.start.line; - const diffCellLineLength = belowCellLineLength - aboveCellLineLength; - thenSetSelection = thenExchangeTextLines.then((isEditSuccessful) => { - if (isEditSuccessful) { - editor.selection = new Selection( - belowCell.range.start.line + diffCellLineLength + relativeSelectionRange.start.line, - relativeSelectionRange.start.character, - belowCell.range.start.line + diffCellLineLength + relativeSelectionRange.end.line, - relativeSelectionRange.end.character - ); - } - return isEditSuccessful; - }); - } - } - return thenSetSelection.then((isEditSuccessful) => { - if (isEditSuccessful && isActiveBeforeAnchor) { - editor.selection = new Selection(editor.selection.active, editor.selection.anchor); - } - return true; - }); - } - - private exchangeTextLines(editor: TextEditor, aboveRange: Range, belowRange: Range): Thenable { - const aboveStartLine = aboveRange.start.line; - const aboveEndLine = aboveRange.end.line; - const belowStartLine = belowRange.start.line; - const belowEndLine = belowRange.end.line; - - if (aboveEndLine >= belowStartLine) { - throw RangeError(`Above lines must be fully above not ${aboveEndLine} <= ${belowStartLine}`); - } - - const above = new Range( - aboveStartLine, - 0, - aboveEndLine, - editor.document.lineAt(aboveEndLine).range.end.character - ); - const aboveText = editor.document.getText(above); - - const below = new Range( - belowStartLine, - 0, - belowEndLine, - editor.document.lineAt(belowEndLine).range.end.character - ); - const belowText = editor.document.getText(below); - - let betweenText = ''; - if (aboveEndLine + 1 < belowStartLine) { - const betweenStatLine = aboveEndLine + 1; - const betweenEndLine = belowStartLine - 1; - const between = new Range( - betweenStatLine, - 0, - betweenEndLine, - editor.document.lineAt(betweenEndLine).range.end.character - ); - betweenText = `${editor.document.getText(between)}\n`; - } - - const newText = `${belowText}\n${betweenText}${aboveText}`; - const newRange = new Range(above.start, below.end); - return editor.edit((editBuilder) => { - editBuilder.replace(newRange, newText); - this.codeLensUpdatedEvent.fire(); - }); - } - - private getStartEndCells(selection: Selection): ICellRange[] | undefined { - const startEndCellIndex = this.getStartEndCellIndex(selection); - if (startEndCellIndex) { - const startCell = this.getCellFromIndex(startEndCellIndex[0]); - const endCell = this.getCellFromIndex(startEndCellIndex[1]); - return [startCell, endCell]; - } - } - - private getStartEndCellIndex(selection?: Selection): number[] | undefined { - if (!selection) { - return undefined; - } - let startCellIndex = this.getCellIndex(selection.start); - let endCellIndex = startCellIndex; - // handle if the selection is the same line, hence same cell - if (selection.start.line !== selection.end.line) { - endCellIndex = this.getCellIndex(selection.end); - } - // handle when selection is above the top most cell - if (startCellIndex === -1) { - if (endCellIndex === -1) { - return undefined; - } else { - // selected a range above the first cell. - startCellIndex = 0; - const startCell = this.getCellFromIndex(0); - if (selection.start.line > startCell.range.start.line) { - throw RangeError( - `Should not be able to pick a range with an end in a cell and start after a cell. ${selection.start.line} > ${startCell.range.end.line}` - ); - } - } - } - if (startCellIndex >= 0 && endCellIndex >= 0) { - return [startCellIndex, endCellIndex]; - } - } - - private insertCell(editor: TextEditor, line: number) { - // insertCell - // - // Inserts a cell at current line defined as two new lines and then - // moves cursor to within the cell. - // ``` - // # %% - // - // ``` - // - const cellDelineator = this.getDefaultCellMarker(editor.document.uri); - let newCell = `${cellDelineator}\n\n`; - if (line >= editor.document.lineCount) { - newCell = `\n${cellDelineator}\n`; - } - - const cellStartPosition = new Position(line, 0); - const newCursorPosition = new Position(line + 1, 0); - - editor.edit((editBuilder) => { - editBuilder.insert(cellStartPosition, newCell); - this.codeLensUpdatedEvent.fire(); - }); - - editor.selection = new Selection(newCursorPosition, newCursorPosition); - } - - private getDefaultCellMarker(resource: Resource): string { - return ( - this.configService.getSettings(resource).datascience.defaultCellMarker || Identifiers.DefaultCodeCellMarker - ); - } - - private onCodeLensFactoryUpdated(): void { - // Update our code lenses. - if (this.document) { - this.codeLenses = this.codeLensFactory.createCodeLenses(this.document); - this.cells = this.codeLensFactory.getCellRanges(this.document); - } - this.codeLensUpdatedEvent.fire(); - } - - private onDocumentClosed(doc: TextDocument): void { - if (this.document && this.fs.arePathsSame(doc.uri, this.document.uri)) { - this.codeLensUpdatedEvent.dispose(); - this.closeDocumentDisposable?.dispose(); // NOSONAR - this.updateRequiredDisposable?.dispose(); // NOSONAR - } - } - - private async addCode( - code: string, - file: Uri, - line: number, - editor?: TextEditor, - debug?: boolean - ): Promise { - let result = false; - try { - const stopWatch = new StopWatch(); - const activeInteractiveWindow = await this.interactiveWindowProvider.getOrCreate(file); - if (debug) { - result = await activeInteractiveWindow.debugCode(code, file, line, editor); - } else { - result = await activeInteractiveWindow.addCode(code, file, line, editor); - } - this.sendPerceivedCellExecute(stopWatch); - } catch (err) { - await this.dataScienceErrorHandler.handleError(err); - } - - return result; - } - - private async addErrorMessage(file: Uri, leftCount: number): Promise { - // Only show an error message if any left - if (leftCount > 0) { - const message = localize.DataScience.cellStopOnErrorFormatMessage().format(leftCount.toString()); - try { - const activeInteractiveWindow = await this.interactiveWindowProvider.getOrCreate(file); - return activeInteractiveWindow.addMessage(message); - } catch (err) { - await this.dataScienceErrorHandler.handleError(err); - } - } - } - - private sendPerceivedCellExecute(runningStopWatch?: StopWatch) { - if (runningStopWatch) { - if (!CodeWatcher.sentExecuteCellTelemetry) { - CodeWatcher.sentExecuteCellTelemetry = true; - sendTelemetryEvent(Telemetry.ExecuteCellPerceivedCold, runningStopWatch.elapsedTime); - } else { - sendTelemetryEvent(Telemetry.ExecuteCellPerceivedWarm, runningStopWatch.elapsedTime); - } - } - } - - private async runMatchingCell(range: Range, advance?: boolean, debug?: boolean) { - const currentRunCellLens = this.getCurrentCellLens(range.start); - const nextRunCellLens = this.getNextCellLens(range.start); - - if (currentRunCellLens) { - // Move the next cell if allowed. - if (advance) { - if (nextRunCellLens) { - this.advanceToRange(nextRunCellLens.range); - } else { - // insert new cell at bottom after current - const editor = this.documentManager.activeTextEditor; - if (editor) { - this.insertCell(editor, currentRunCellLens.range.end.line + 1); - } - } - } - - // Run the cell after moving the selection - if (this.document) { - // Use that to get our code. - const code = this.document.getText(currentRunCellLens.range); - await this.addCode( - code, - this.document.uri, - currentRunCellLens.range.start.line, - this.documentManager.activeTextEditor, - debug - ); - } - } - } - - private getCellIndex(position: Position): number { - return this.cells.findIndex((cell) => position && cell.range.contains(position)); - } - - private getCellFromIndex(index: number): ICellRange { - const cells = this.cells; - const indexBounded = getIndex(index, cells.length); - return cells[indexBounded]; - } - - private getCellFromPosition(position?: Position): ICellRange | undefined { - if (!position) { - const editor = this.documentManager.activeTextEditor; - if (editor && editor.selection) { - position = editor.selection.active; - } - } - if (position) { - const index = this.getCellIndex(position); - if (index >= 0) { - return this.cells[index]; - } - } - } - - private getCurrentCellLens(pos: Position): CodeLens | undefined { - return this.codeLenses.find( - (l) => l.range.contains(pos) && l.command !== undefined && l.command.command === Commands.RunCell - ); - } - - private getNextCellLens(pos: Position): CodeLens | undefined { - const currentIndex = this.codeLenses.findIndex( - (l) => l.range.contains(pos) && l.command !== undefined && l.command.command === Commands.RunCell - ); - if (currentIndex >= 0) { - return this.codeLenses.find( - (l: CodeLens, i: number) => - l.command !== undefined && l.command.command === Commands.RunCell && i > currentIndex - ); - } - return undefined; - } - - private getPreviousCellLens(pos: Position): CodeLens | undefined { - const currentIndex = this.codeLenses.findIndex( - (l) => l.range.contains(pos) && l.command !== undefined && l.command.command === Commands.RunCell - ); - if (currentIndex >= 1) { - return this.codeLenses.find( - (l: CodeLens, i: number) => l.command !== undefined && i < currentIndex && i + 1 === currentIndex - ); - } - return undefined; - } - - private async runFileInteractiveInternal(debug: boolean) { - if (this.document) { - const code = this.document.getText(); - await this.addCode(code, this.document.uri, 0, undefined, debug); - } - } - - // Advance the cursor to the selected range - private advanceToRange(targetRange: Range) { - const editor = this.documentManager.activeTextEditor; - const newSelection = new Selection(targetRange.start, targetRange.start); - if (editor) { - editor.selection = newSelection; - editor.revealRange(targetRange, TextEditorRevealType.Default); - } - } -} diff --git a/src/client/datascience/editor-integration/decorator.ts b/src/client/datascience/editor-integration/decorator.ts deleted file mode 100644 index f85e2723ed7d..000000000000 --- a/src/client/datascience/editor-integration/decorator.ts +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { inject, injectable } from 'inversify'; -import * as vscode from 'vscode'; - -import { IExtensionSingleActivationService } from '../../activation/types'; -import { IDocumentManager, IVSCodeNotebook } from '../../common/application/types'; -import { PYTHON_LANGUAGE } from '../../common/constants'; -import { IConfigurationService, IDisposable, IDisposableRegistry } from '../../common/types'; -import { generateCellRangesFromDocument } from '../cellFactory'; - -@injectable() -export class Decorator implements IExtensionSingleActivationService, IDisposable { - private activeCellTop: vscode.TextEditorDecorationType | undefined; - private activeCellBottom: vscode.TextEditorDecorationType | undefined; - private cellSeparatorType: vscode.TextEditorDecorationType | undefined; - private timer: NodeJS.Timer | undefined | number; - - constructor( - @inject(IDocumentManager) private documentManager: IDocumentManager, - @inject(IDisposableRegistry) disposables: IDisposableRegistry, - @inject(IConfigurationService) private configuration: IConfigurationService, - @inject(IVSCodeNotebook) private vsCodeNotebook: IVSCodeNotebook - ) { - this.computeDecorations(); - disposables.push(this); - disposables.push(this.configuration.getSettings(undefined).onDidChange(this.settingsChanged, this)); - disposables.push(this.documentManager.onDidChangeActiveTextEditor(this.changedEditor, this)); - disposables.push(this.documentManager.onDidChangeTextEditorSelection(this.changedSelection, this)); - disposables.push(this.documentManager.onDidChangeTextDocument(this.changedDocument, this)); - this.settingsChanged(); - } - - public activate(): Promise { - // We don't need to do anything here as we already did all of our work in the - // constructor. - return Promise.resolve(); - } - - public dispose() { - if (this.timer) { - // tslint:disable-next-line: no-any - clearTimeout(this.timer as any); - } - } - - private settingsChanged() { - if (this.documentManager.activeTextEditor) { - this.triggerUpdate(this.documentManager.activeTextEditor); - } - } - - private changedEditor(editor: vscode.TextEditor | undefined) { - this.triggerUpdate(editor); - } - - private changedDocument(e: vscode.TextDocumentChangeEvent) { - if (this.documentManager.activeTextEditor && e.document === this.documentManager.activeTextEditor.document) { - this.triggerUpdate(this.documentManager.activeTextEditor); - } - } - - private changedSelection(e: vscode.TextEditorSelectionChangeEvent) { - if (e.textEditor && e.textEditor.selection.anchor) { - this.triggerUpdate(e.textEditor); - } - } - - private triggerUpdate(editor: vscode.TextEditor | undefined) { - if (this.timer) { - // tslint:disable-next-line: no-any - clearTimeout(this.timer as any); - } - this.timer = setTimeout(() => this.update(editor), 100); - } - - private computeDecorations() { - this.activeCellTop = this.documentManager.createTextEditorDecorationType({ - borderColor: new vscode.ThemeColor('peekView.border'), - borderWidth: '2px 0px 0px 0px', - borderStyle: 'solid', - isWholeLine: true - }); - this.activeCellBottom = this.documentManager.createTextEditorDecorationType({ - borderColor: new vscode.ThemeColor('peekView.border'), - borderWidth: '0px 0px 1px 0px', - borderStyle: 'solid', - isWholeLine: true - }); - this.cellSeparatorType = this.documentManager.createTextEditorDecorationType({ - borderColor: new vscode.ThemeColor('editor.lineHighlightBorder'), - borderWidth: '1px 0px 0px 0px', - borderStyle: 'solid', - isWholeLine: true - }); - } - - private update(editor: vscode.TextEditor | undefined) { - if ( - editor && - editor.document && - editor.document.languageId === PYTHON_LANGUAGE && - !this.vsCodeNotebook.activeNotebookEditor && - this.activeCellTop && - this.cellSeparatorType && - this.activeCellBottom - ) { - const settings = this.configuration.getSettings(editor.document.uri).datascience; - if (settings.decorateCells && settings.enabled) { - // Find all of the cells - const cells = generateCellRangesFromDocument(editor.document, settings); - - // Find the range for our active cell. - const currentRange = cells.map((c) => c.range).filter((r) => r.contains(editor.selection.anchor)); - const rangeTop = - currentRange.length > 0 ? [new vscode.Range(currentRange[0].start, currentRange[0].start)] : []; - const rangeBottom = - currentRange.length > 0 ? [new vscode.Range(currentRange[0].end, currentRange[0].end)] : []; - editor.setDecorations(this.activeCellTop, rangeTop); - editor.setDecorations(this.activeCellBottom, rangeBottom); - - // Find the start range for the rest - const startRanges = cells.map((c) => new vscode.Range(c.range.start, c.range.start)); - editor.setDecorations(this.cellSeparatorType, startRanges); - } else { - editor.setDecorations(this.activeCellTop, []); - editor.setDecorations(this.activeCellBottom, []); - editor.setDecorations(this.cellSeparatorType, []); - } - } - } -} diff --git a/src/client/datascience/editor-integration/hoverProvider.ts b/src/client/datascience/editor-integration/hoverProvider.ts deleted file mode 100644 index 23723262de08..000000000000 --- a/src/client/datascience/editor-integration/hoverProvider.ts +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { inject, injectable, named } from 'inversify'; - -import * as vscode from 'vscode'; -import { Cancellation } from '../../common/cancellation'; -import { PYTHON } from '../../common/constants'; -import { RunByLine } from '../../common/experiments/groups'; -import { traceError } from '../../common/logger'; - -import { IExperimentsManager } from '../../common/types'; -import { sleep } from '../../common/utils/async'; -import { noop } from '../../common/utils/misc'; -import { Identifiers } from '../constants'; -import { - ICell, - IDataScienceFileSystem, - IInteractiveWindowProvider, - IJupyterVariables, - INotebook, - INotebookExecutionLogger -} from '../types'; - -// This class provides hashes for debugging jupyter cells. Call getHashes just before starting debugging to compute all of the -// hashes for cells. -@injectable() -export class HoverProvider implements INotebookExecutionLogger, vscode.HoverProvider { - private runFiles = new Set(); - private enabled = false; - private hoverProviderRegistration: vscode.Disposable | undefined; - - constructor( - @inject(IExperimentsManager) experimentsManager: IExperimentsManager, - @inject(IJupyterVariables) @named(Identifiers.KERNEL_VARIABLES) private variableProvider: IJupyterVariables, - @inject(IInteractiveWindowProvider) private interactiveProvider: IInteractiveWindowProvider, - @inject(IDataScienceFileSystem) private readonly fs: IDataScienceFileSystem - ) { - this.enabled = experimentsManager.inExperiment(RunByLine.experiment); - } - - public dispose() { - if (this.hoverProviderRegistration) { - this.hoverProviderRegistration.dispose(); - } - } - - // tslint:disable-next-line: no-any - public onKernelRestarted() { - this.runFiles.clear(); - } - - public async preExecute(cell: ICell, silent: boolean): Promise { - try { - if (!silent && cell.file && cell.file !== Identifiers.EmptyFileName) { - const size = this.runFiles.size; - this.runFiles.add(cell.file.toLocaleLowerCase()); - if (size !== this.runFiles.size) { - this.initializeHoverProvider(); - } - } - } catch (exc) { - // Don't let exceptions in a preExecute mess up normal operation - traceError(exc); - } - } - - public async postExecute(_cell: ICell, _silent: boolean): Promise { - noop(); - } - - public provideHover( - document: vscode.TextDocument, - position: vscode.Position, - token: vscode.CancellationToken - ): vscode.ProviderResult { - const timeoutHandler = async () => { - await sleep(100); - return null; - }; - return Promise.race([timeoutHandler(), this.getVariableHover(document, position, token)]); - } - - private initializeHoverProvider() { - if (!this.hoverProviderRegistration) { - if (this.enabled) { - this.hoverProviderRegistration = vscode.languages.registerHoverProvider(PYTHON, this); - } else { - this.hoverProviderRegistration = { - dispose: noop - }; - } - } - } - - private getVariableHover( - document: vscode.TextDocument, - position: vscode.Position, - token: vscode.CancellationToken - ): Promise { - // Make sure to fail as soon as the cancel token is signaled - return Cancellation.race(async (t) => { - const range = document.getWordRangeAtPosition(position); - if (range) { - const word = document.getText(range); - if (word) { - // See if we have any matching notebooks - const notebooks = this.getMatchingNotebooks(document); - if (notebooks && notebooks.length) { - // Just use the first one to reply if more than one. - const match = await Promise.race( - notebooks.map((n) => this.variableProvider.getMatchingVariable(n, word, t)) - ); - if (match) { - return { - contents: [`${word} = ${match.value}`] - }; - } - } - } - } - return null; - }, token); - } - - private getMatchingNotebooks(document: vscode.TextDocument): INotebook[] { - // First see if we have an interactive window who's owner is this document - let result = this.interactiveProvider.windows - .filter((w) => w.notebook && w.owner && this.fs.arePathsSame(w.owner, document.uri)) - .map((w) => w.notebook!); - if (!result || result.length === 0) { - // Not a match on the owner, find all that were submitters? Might be a bit risky - result = this.interactiveProvider.windows - .filter((w) => w.notebook && w.submitters.find((s) => this.fs.arePathsSame(s, document.uri))) - .map((w) => w.notebook!); - } - return result; - } -} diff --git a/src/client/datascience/errorHandler/errorHandler.ts b/src/client/datascience/errorHandler/errorHandler.ts deleted file mode 100644 index f0fa2f29cd95..000000000000 --- a/src/client/datascience/errorHandler/errorHandler.ts +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import { inject, injectable } from 'inversify'; -import { IApplicationShell } from '../../common/application/types'; -import { traceError } from '../../common/logger'; -import * as localize from '../../common/utils/localize'; -import { noop } from '../../common/utils/misc'; -import { JupyterInstallError } from '../jupyter/jupyterInstallError'; -import { JupyterSelfCertsError } from '../jupyter/jupyterSelfCertsError'; -import { JupyterZMQBinariesNotFoundError } from '../jupyter/jupyterZMQBinariesNotFoundError'; -import { JupyterServerSelector } from '../jupyter/serverSelector'; -import { IDataScienceErrorHandler, IJupyterInterpreterDependencyManager } from '../types'; -@injectable() -export class DataScienceErrorHandler implements IDataScienceErrorHandler { - constructor( - @inject(IApplicationShell) private applicationShell: IApplicationShell, - @inject(IJupyterInterpreterDependencyManager) protected dependencyManager: IJupyterInterpreterDependencyManager, - @inject(JupyterServerSelector) private serverSelector: JupyterServerSelector - ) {} - - public async handleError(err: Error): Promise { - if (err instanceof JupyterInstallError) { - await this.dependencyManager.installMissingDependencies(err); - } else if (err instanceof JupyterZMQBinariesNotFoundError) { - await this.showZMQError(err); - } else if (err instanceof JupyterSelfCertsError) { - // Don't show the message for self cert errors - noop(); - } else if (err.message) { - this.applicationShell.showErrorMessage(err.message); - } else { - this.applicationShell.showErrorMessage(err.toString()); - } - traceError('DataScience Error', err); - } - - private async showZMQError(err: JupyterZMQBinariesNotFoundError) { - // Ask the user to always pick remote as this is their only option - const selectNewServer = localize.DataScience.selectNewServer(); - this.applicationShell - .showErrorMessage(localize.DataScience.nativeDependencyFail().format(err.toString()), selectNewServer) - .then((selection) => { - if (selection === selectNewServer) { - this.serverSelector.selectJupyterURI(false).ignoreErrors(); - } - }); - } -} diff --git a/src/client/datascience/export/README.md b/src/client/datascience/export/README.md deleted file mode 100644 index 0e1b31b9adf4..000000000000 --- a/src/client/datascience/export/README.md +++ /dev/null @@ -1,13 +0,0 @@ - -## TO ADD A NEW EXPORT METHOD -1. Create a new command in src/client/datascience/constants -2. Register the command in this file -3. Add an item to the quick pick menu for your new export method from inside the getExportQuickPickItems() method (in this file). -4. Add a new command to the command pallete in package.json (optional) -5. Declare and add your file extensions inside exportManagerFilePicker -6. Declare and add your export method inside exportManager -7. Create an injectable class that implements IExport and register it in src/client/datascience/serviceRegistry -8. Implement the export method on your new class -9. Inject the class inside exportManager -10. Add a case for your new export method and call the export method of your new class with the appropriate arguments -11. Add telementry and status messages diff --git a/src/client/datascience/export/exportBase.ts b/src/client/datascience/export/exportBase.ts deleted file mode 100644 index 866cd243a7e0..000000000000 --- a/src/client/datascience/export/exportBase.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { CancellationToken, Uri } from 'vscode'; - -import { IPythonExecutionFactory, IPythonExecutionService } from '../../common/process/types'; -import { reportAction } from '../progress/decorator'; -import { ReportableAction } from '../progress/types'; -import { IDataScienceFileSystem, IJupyterSubCommandExecutionService, INotebookImporter } from '../types'; -import { ExportFormat, IExport } from './types'; - -@injectable() -export class ExportBase implements IExport { - constructor( - @inject(IPythonExecutionFactory) protected readonly pythonExecutionFactory: IPythonExecutionFactory, - @inject(IJupyterSubCommandExecutionService) - protected jupyterService: IJupyterSubCommandExecutionService, - @inject(IDataScienceFileSystem) protected readonly fs: IDataScienceFileSystem, - @inject(INotebookImporter) protected readonly importer: INotebookImporter - ) {} - - // tslint:disable-next-line: no-empty - public async export(_source: Uri, _target: Uri, _token: CancellationToken): Promise {} - - @reportAction(ReportableAction.PerformingExport) - public async executeCommand( - source: Uri, - target: Uri, - format: ExportFormat, - token: CancellationToken - ): Promise { - if (token.isCancellationRequested) { - return; - } - - const service = await this.getExecutionService(source); - if (!service) { - return; - } - - if (token.isCancellationRequested) { - return; - } - - const tempTarget = await this.fs.createTemporaryLocalFile(path.extname(target.fsPath)); - const args = [ - source.fsPath, - '--to', - format, - '--output', - path.basename(tempTarget.filePath), - '--output-dir', - path.dirname(tempTarget.filePath) - ]; - const result = await service.execModule('jupyter', ['nbconvert'].concat(args), { - throwOnStdErr: false, - encoding: 'utf8', - token: token - }); - - if (token.isCancellationRequested) { - tempTarget.dispose(); - return; - } - - try { - if ((await this.fs.stat(Uri.file(tempTarget.filePath))).size > 1) { - await this.fs.copyLocal(tempTarget.filePath, target.fsPath); - } else { - throw new Error('File size is zero during conversion. Outputting error.'); - } - } catch { - throw new Error(result.stderr); - } finally { - tempTarget.dispose(); - } - } - - protected async getExecutionService(source: Uri): Promise { - const interpreter = await this.jupyterService.getSelectedInterpreter(); - if (!interpreter) { - return; - } - return this.pythonExecutionFactory.createActivatedEnvironment({ - resource: source, - interpreter, - allowEnvironmentFetchExceptions: false, - bypassCondaExecution: true - }); - } -} diff --git a/src/client/datascience/export/exportDependencyChecker.ts b/src/client/datascience/export/exportDependencyChecker.ts deleted file mode 100644 index e357d63ed020..000000000000 --- a/src/client/datascience/export/exportDependencyChecker.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { inject, injectable } from 'inversify'; -import * as localize from '../../common/utils/localize'; -import { ProgressReporter } from '../progress/progressReporter'; -import { IJupyterExecution, IJupyterInterpreterDependencyManager } from '../types'; -import { ExportFormat } from './types'; - -@injectable() -export class ExportDependencyChecker { - constructor( - @inject(IJupyterExecution) private jupyterExecution: IJupyterExecution, - @inject(IJupyterInterpreterDependencyManager) - private readonly dependencyManager: IJupyterInterpreterDependencyManager, - @inject(ProgressReporter) private readonly progressReporter: ProgressReporter - ) {} - - public async checkDependencies(format: ExportFormat) { - // Before we try the import, see if we don't support it, if we don't give a chance to install dependencies - const reporter = this.progressReporter.createProgressIndicator(`Exporting to ${format}`); - try { - if (!(await this.jupyterExecution.getImportPackageVersion())) { - await this.dependencyManager.installMissingDependencies(); - if (!(await this.jupyterExecution.getImportPackageVersion())) { - throw new Error(localize.DataScience.jupyterNbConvertNotSupported()); - } - } - } finally { - reporter.dispose(); - } - } -} diff --git a/src/client/datascience/export/exportFileOpener.ts b/src/client/datascience/export/exportFileOpener.ts deleted file mode 100644 index d2d1e086efdc..000000000000 --- a/src/client/datascience/export/exportFileOpener.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { inject, injectable } from 'inversify'; -import { Position, Uri } from 'vscode'; -import { IApplicationShell, IDocumentManager } from '../../common/application/types'; -import { PYTHON_LANGUAGE } from '../../common/constants'; - -import { IBrowserService } from '../../common/types'; -import * as localize from '../../common/utils/localize'; -import { sendTelemetryEvent } from '../../telemetry'; -import { Telemetry } from '../constants'; -import { IDataScienceFileSystem } from '../types'; -import { ExportFormat } from './types'; - -@injectable() -export class ExportFileOpener { - constructor( - @inject(IDocumentManager) protected readonly documentManager: IDocumentManager, - @inject(IDataScienceFileSystem) private readonly fs: IDataScienceFileSystem, - @inject(IApplicationShell) private readonly applicationShell: IApplicationShell, - @inject(IBrowserService) private readonly browserService: IBrowserService - ) {} - - public async openFile(format: ExportFormat, uri: Uri) { - if (format === ExportFormat.python) { - await this.openPythonFile(uri); - sendTelemetryEvent(Telemetry.ExportNotebookAs, undefined, { - format: format, - successful: true, - opened: true - }); - } else { - const opened = await this.askOpenFile(uri); - sendTelemetryEvent(Telemetry.ExportNotebookAs, undefined, { - format: format, - successful: true, - opened: opened - }); - } - } - - private async openPythonFile(uri: Uri): Promise { - const contents = await this.fs.readFile(uri); - await this.fs.delete(uri); - const doc = await this.documentManager.openTextDocument({ language: PYTHON_LANGUAGE, content: contents }); - const editor = await this.documentManager.showTextDocument(doc); - // Edit the document so that it is dirty (add a space at the end) - editor.edit((editBuilder) => { - editBuilder.insert(new Position(editor.document.lineCount, 0), '\n'); - }); - } - - private async askOpenFile(uri: Uri): Promise { - const yes = localize.DataScience.openExportFileYes(); - const no = localize.DataScience.openExportFileNo(); - const items = [yes, no]; - - const selected = await this.applicationShell - .showInformationMessage(localize.DataScience.openExportedFileMessage(), ...items) - .then((item) => item); - - if (selected === yes) { - this.browserService.launch(uri.toString()); - return true; - } - return false; - } -} diff --git a/src/client/datascience/export/exportManager.ts b/src/client/datascience/export/exportManager.ts deleted file mode 100644 index dc13b73ccd0d..000000000000 --- a/src/client/datascience/export/exportManager.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { inject, injectable, named } from 'inversify'; -import { CancellationToken } from 'monaco-editor'; -import * as path from 'path'; -import { Uri } from 'vscode'; -import { IApplicationShell } from '../../common/application/types'; -import { traceError } from '../../common/logger'; -import { TemporaryDirectory } from '../../common/platform/types'; -import * as localize from '../../common/utils/localize'; -import { sendTelemetryEvent } from '../../telemetry'; -import { Telemetry } from '../constants'; -import { ProgressReporter } from '../progress/progressReporter'; -import { IDataScienceFileSystem, INotebookModel } from '../types'; -import { ExportDependencyChecker } from './exportDependencyChecker'; -import { ExportFileOpener } from './exportFileOpener'; -import { ExportUtil } from './exportUtil'; -import { ExportFormat, IExport, IExportManager, IExportManagerFilePicker } from './types'; - -@injectable() -export class ExportManager implements IExportManager { - constructor( - @inject(IExport) @named(ExportFormat.pdf) private readonly exportToPDF: IExport, - @inject(IExport) @named(ExportFormat.html) private readonly exportToHTML: IExport, - @inject(IExport) @named(ExportFormat.python) private readonly exportToPython: IExport, - @inject(IDataScienceFileSystem) private readonly fs: IDataScienceFileSystem, - @inject(IExportManagerFilePicker) private readonly filePicker: IExportManagerFilePicker, - @inject(ProgressReporter) private readonly progressReporter: ProgressReporter, - @inject(ExportUtil) private readonly exportUtil: ExportUtil, - @inject(IApplicationShell) private readonly applicationShell: IApplicationShell, - @inject(ExportFileOpener) private readonly exportFileOpener: ExportFileOpener, - @inject(ExportDependencyChecker) private exportDepedencyChecker: ExportDependencyChecker - ) {} - - public async export(format: ExportFormat, model: INotebookModel, defaultFileName?: string): Promise { - let target; - try { - await this.exportDepedencyChecker.checkDependencies(format); - target = await this.getTargetFile(format, model, defaultFileName); - if (!target) { - return; - } - await this.performExport(format, model, target); - } catch (e) { - traceError('Export failed', e); - sendTelemetryEvent(Telemetry.ExportNotebookAsFailed, undefined, { format: format }); - - if (format === ExportFormat.pdf) { - traceError(localize.DataScience.exportToPDFDependencyMessage()); - } - - this.showExportFailed(localize.DataScience.exportFailedGeneralMessage()); - } - } - - private async performExport(format: ExportFormat, model: INotebookModel, target: Uri) { - /* Need to make a temp directory here, instead of just a temp file. This is because - we need to store the contents of the notebook in a file that is named the same - as what we want the title of the exported file to be. To ensure this file path will be unique - we store it in a temp directory. The name of the file matters because when - exporting to certain formats the filename is used within the exported document as the title. */ - const tempDir = await this.exportUtil.generateTempDir(); - const source = await this.makeSourceFile(target, model, tempDir); - - const reporter = this.progressReporter.createProgressIndicator(`Exporting to ${format}`, true); - try { - await this.exportToFormat(source, target, format, reporter.token); - } finally { - tempDir.dispose(); - reporter.dispose(); - } - - if (reporter.token.isCancellationRequested) { - sendTelemetryEvent(Telemetry.ExportNotebookAs, undefined, { format: format, cancelled: true }); - return; - } - await this.exportFileOpener.openFile(format, target); - } - - private async getTargetFile( - format: ExportFormat, - model: INotebookModel, - defaultFileName?: string - ): Promise { - let target; - - if (format !== ExportFormat.python) { - target = await this.filePicker.getExportFileLocation(format, model.file, defaultFileName); - } else { - target = Uri.file((await this.fs.createTemporaryLocalFile('.py')).filePath); - } - - return target; - } - - private async makeSourceFile(target: Uri, model: INotebookModel, tempDir: TemporaryDirectory): Promise { - // Creates a temporary file with the same base name as the target file - const fileName = path.basename(target.fsPath, path.extname(target.fsPath)); - const sourceFilePath = await this.exportUtil.makeFileInDirectory(model, `${fileName}.ipynb`, tempDir.path); - return Uri.file(sourceFilePath); - } - - private showExportFailed(msg: string) { - // tslint:disable-next-line: messages-must-be-localized - this.applicationShell.showErrorMessage(`${localize.DataScience.failedExportMessage()} ${msg}`).then(); - } - - private async exportToFormat(source: Uri, target: Uri, format: ExportFormat, cancelToken: CancellationToken) { - if (format === ExportFormat.pdf) { - // When exporting to PDF we need to remove any SVG output. This is due to an error - // with nbconvert and a dependency of its called InkScape. - await this.exportUtil.removeSvgs(source); - } - - switch (format) { - case ExportFormat.python: - await this.exportToPython.export(source, target, cancelToken); - break; - - case ExportFormat.pdf: - await this.exportToPDF.export(source, target, cancelToken); - break; - - case ExportFormat.html: - await this.exportToHTML.export(source, target, cancelToken); - break; - - default: - break; - } - } -} diff --git a/src/client/datascience/export/exportManagerFilePicker.ts b/src/client/datascience/export/exportManagerFilePicker.ts deleted file mode 100644 index c92436c295dd..000000000000 --- a/src/client/datascience/export/exportManagerFilePicker.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { inject, injectable, named } from 'inversify'; -import * as path from 'path'; -import { Memento, SaveDialogOptions, Uri } from 'vscode'; -import { IApplicationShell } from '../../common/application/types'; -import { IMemento, WORKSPACE_MEMENTO } from '../../common/types'; -import { ExportNotebookSettings } from '../interactive-common/interactiveWindowTypes'; -import { ExportFormat, IExportManagerFilePicker } from './types'; - -// File extensions for each export method -export const PDFExtensions = { PDF: ['pdf'] }; -export const HTMLExtensions = { HTML: ['html', 'htm'] }; -export const PythonExtensions = { Python: ['py'] }; - -@injectable() -export class ExportManagerFilePicker implements IExportManagerFilePicker { - private readonly defaultExportSaveLocation = ''; // set default save location - - constructor( - @inject(IApplicationShell) private readonly applicationShell: IApplicationShell, - @inject(IMemento) @named(WORKSPACE_MEMENTO) private workspaceStorage: Memento - ) {} - - public async getExportFileLocation( - format: ExportFormat, - source: Uri, - defaultFileName?: string - ): Promise { - // map each export method to a set of file extensions - let fileExtensions; - let extension: string | undefined; - switch (format) { - case ExportFormat.python: - fileExtensions = PythonExtensions; - extension = '.py'; - break; - - case ExportFormat.pdf: - extension = '.pdf'; - fileExtensions = PDFExtensions; - break; - - case ExportFormat.html: - extension = '.html'; - fileExtensions = HTMLExtensions; - break; - - default: - return; - } - - const targetFileName = defaultFileName - ? defaultFileName - : `${path.basename(source.fsPath, path.extname(source.fsPath))}${extension}`; - - const dialogUri = Uri.file(path.join(this.getLastFileSaveLocation().fsPath, targetFileName)); - const options: SaveDialogOptions = { - defaultUri: dialogUri, - saveLabel: 'Export', - filters: fileExtensions - }; - - const uri = await this.applicationShell.showSaveDialog(options); - if (uri) { - await this.updateFileSaveLocation(uri); - } - - return uri; - } - - private getLastFileSaveLocation(): Uri { - const filePath = this.workspaceStorage.get( - ExportNotebookSettings.lastSaveLocation, - this.defaultExportSaveLocation - ); - - return Uri.file(filePath); - } - - private async updateFileSaveLocation(value: Uri) { - const location = path.dirname(value.fsPath); - await this.workspaceStorage.update(ExportNotebookSettings.lastSaveLocation, location); - } -} diff --git a/src/client/datascience/export/exportToHTML.ts b/src/client/datascience/export/exportToHTML.ts deleted file mode 100644 index d12c92502402..000000000000 --- a/src/client/datascience/export/exportToHTML.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { injectable } from 'inversify'; -import { CancellationToken, Uri } from 'vscode'; -import { ExportBase } from './exportBase'; -import { ExportFormat } from './types'; - -@injectable() -export class ExportToHTML extends ExportBase { - public async export(source: Uri, target: Uri, token: CancellationToken): Promise { - await this.executeCommand(source, target, ExportFormat.html, token); - } -} diff --git a/src/client/datascience/export/exportToPDF.ts b/src/client/datascience/export/exportToPDF.ts deleted file mode 100644 index 0b4b6afba335..000000000000 --- a/src/client/datascience/export/exportToPDF.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { injectable } from 'inversify'; -import { CancellationToken, Uri } from 'vscode'; -import { ExportBase } from './exportBase'; -import { ExportFormat } from './types'; - -@injectable() -export class ExportToPDF extends ExportBase { - public async export(source: Uri, target: Uri, token: CancellationToken): Promise { - await this.executeCommand(source, target, ExportFormat.pdf, token); - } -} diff --git a/src/client/datascience/export/exportToPython.ts b/src/client/datascience/export/exportToPython.ts deleted file mode 100644 index b72168886123..000000000000 --- a/src/client/datascience/export/exportToPython.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { injectable } from 'inversify'; -import { CancellationToken, Uri } from 'vscode'; -import { ExportBase } from './exportBase'; - -@injectable() -export class ExportToPython extends ExportBase { - public async export(source: Uri, target: Uri, token: CancellationToken): Promise { - if (token.isCancellationRequested) { - return; - } - const contents = await this.importer.importFromFile(source); - await this.fs.writeFile(target, contents); - } -} diff --git a/src/client/datascience/export/exportUtil.ts b/src/client/datascience/export/exportUtil.ts deleted file mode 100644 index 9e9f2c9c97c8..000000000000 --- a/src/client/datascience/export/exportUtil.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { nbformat } from '@jupyterlab/coreutils'; -import { inject, injectable } from 'inversify'; -import * as os from 'os'; -import * as path from 'path'; -import * as uuid from 'uuid/v4'; -import { CancellationTokenSource, Uri } from 'vscode'; -import { TemporaryDirectory } from '../../common/platform/types'; -import { sleep } from '../../common/utils/async'; -import { ICell, IDataScienceFileSystem, INotebookExporter, INotebookModel, INotebookStorage } from '../types'; - -@injectable() -export class ExportUtil { - constructor( - @inject(IDataScienceFileSystem) private fs: IDataScienceFileSystem, - @inject(INotebookStorage) private notebookStorage: INotebookStorage, - @inject(INotebookExporter) private jupyterExporter: INotebookExporter - ) {} - - public async generateTempDir(): Promise { - const resultDir = path.join(os.tmpdir(), uuid()); - await this.fs.createLocalDirectory(resultDir); - - return { - path: resultDir, - dispose: async () => { - // Try ten times. Process may still be up and running. - // We don't want to do async as async dispose means it may never finish and then we don't - // delete - let count = 0; - while (count < 10) { - try { - await this.fs.deleteLocalDirectory(resultDir); - count = 10; - } catch { - await sleep(3000); - count += 1; - } - } - } - }; - } - - public async makeFileInDirectory(model: INotebookModel, fileName: string, dirPath: string): Promise { - const newFilePath = path.join(dirPath, fileName); - - await this.fs.writeLocalFile(newFilePath, model.getContent()); - - return newFilePath; - } - - public async getModelFromCells(cells: ICell[]): Promise { - const tempDir = await this.generateTempDir(); - const tempFile = await this.fs.createTemporaryLocalFile('.ipynb'); - let model: INotebookModel; - - try { - await this.jupyterExporter.exportToFile(cells, tempFile.filePath, false); - const newPath = path.join(tempDir.path, '.ipynb'); - await this.fs.copyLocal(tempFile.filePath, newPath); - model = await this.notebookStorage.getOrCreateModel({ file: Uri.file(newPath) }); - } finally { - tempFile.dispose(); - tempDir.dispose(); - } - - return model; - } - - public async removeSvgs(source: Uri) { - const model = await this.notebookStorage.getOrCreateModel({ file: source }); - - const newCells: ICell[] = []; - for (const cell of model.cells) { - const outputs = cell.data.outputs; - if (outputs as nbformat.IOutput[]) { - this.removeSvgFromOutputs(outputs as nbformat.IOutput[]); - } - newCells.push(cell); - } - model.update({ - kind: 'modify', - newCells: newCells, - oldCells: model.cells as ICell[], - oldDirty: false, - newDirty: false, - source: 'user' - }); - await this.notebookStorage.save(model, new CancellationTokenSource().token); - } - - private removeSvgFromOutputs(outputs: nbformat.IOutput[]) { - const SVG = 'image/svg+xml'; - const PNG = 'image/png'; - for (const output of outputs as nbformat.IOutput[]) { - if (output.data as nbformat.IMimeBundle) { - const data = output.data as nbformat.IMimeBundle; - // only remove the svg if there is a png available - if (!(SVG in data)) { - continue; - } - if (PNG in data) { - delete data[SVG]; - } - } - } - } -} diff --git a/src/client/datascience/export/types.ts b/src/client/datascience/export/types.ts deleted file mode 100644 index d1ec1dc8e484..000000000000 --- a/src/client/datascience/export/types.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { CancellationToken, Uri } from 'vscode'; -import { INotebookModel } from '../types'; - -export enum ExportFormat { - pdf = 'pdf', - html = 'html', - python = 'python' -} - -export const IExportManager = Symbol('IExportManager'); -export interface IExportManager { - export(format: ExportFormat, model: INotebookModel, defaultFileName?: string): Promise; -} - -export const IExport = Symbol('IExport'); -export interface IExport { - export(source: Uri, target: Uri, token: CancellationToken): Promise; -} - -export const IExportManagerFilePicker = Symbol('IExportManagerFilePicker'); -export interface IExportManagerFilePicker { - getExportFileLocation(format: ExportFormat, source: Uri, defaultFileName?: string): Promise; -} diff --git a/src/client/datascience/gather/gatherListener.ts b/src/client/datascience/gather/gatherListener.ts deleted file mode 100644 index 11e71948c613..000000000000 --- a/src/client/datascience/gather/gatherListener.ts +++ /dev/null @@ -1,326 +0,0 @@ -import { inject, injectable } from 'inversify'; -import { IDisposable } from 'monaco-editor'; -import * as uuid from 'uuid/v4'; -import { Event, EventEmitter, Position, Uri, ViewColumn } from 'vscode'; -import { createMarkdownCell } from '../../../datascience-ui/common/cellFactory'; -import { IApplicationShell, IDocumentManager } from '../../common/application/types'; -import { PYTHON_LANGUAGE } from '../../common/constants'; -import { traceError } from '../../common/logger'; - -import type { nbformat } from '@jupyterlab/coreutils'; -import { IConfigurationService, Resource } from '../../common/types'; -import * as localize from '../../common/utils/localize'; -import { noop } from '../../common/utils/misc'; -import { StopWatch } from '../../common/utils/stopWatch'; -import { sendTelemetryEvent } from '../../telemetry'; -import { generateCellsFromString } from '../cellFactory'; -import { Identifiers, Telemetry } from '../constants'; -import { - IInteractiveWindowMapping, - INotebookIdentity, - InteractiveWindowMessages -} from '../interactive-common/interactiveWindowTypes'; -import { - ICell, - IDataScienceFileSystem, - IGatherLogger, - IGatherProvider, - IInteractiveWindowListener, - INotebook, - INotebookEditorProvider, - INotebookExecutionLogger, - INotebookExporter, - INotebookProvider -} from '../types'; - -@injectable() -export class GatherListener implements IInteractiveWindowListener { - // tslint:disable-next-line: no-any - private postEmitter: EventEmitter<{ message: string; payload: any }> = new EventEmitter<{ - message: string; - // tslint:disable-next-line: no-any - payload: any; - }>(); - private notebookUri: Uri | undefined; - private gatherProvider: IGatherProvider | undefined; - private gatherTimer: StopWatch | undefined; - private linesSubmitted: number = 0; - private cellsSubmitted: number = 0; - - constructor( - @inject(IApplicationShell) private applicationShell: IApplicationShell, - @inject(INotebookExporter) private jupyterExporter: INotebookExporter, - @inject(INotebookEditorProvider) private ipynbProvider: INotebookEditorProvider, - @inject(INotebookProvider) private notebookProvider: INotebookProvider, - @inject(IConfigurationService) private configService: IConfigurationService, - @inject(IDocumentManager) private documentManager: IDocumentManager, - @inject(IDataScienceFileSystem) private fs: IDataScienceFileSystem - ) {} - - public dispose() { - noop(); - } - - // tslint:disable-next-line: no-any - public get postMessage(): Event<{ message: string; payload: any }> { - return this.postEmitter.event; - } - - // tslint:disable-next-line: no-any - public onMessage(message: string, payload?: any): void { - switch (message) { - case InteractiveWindowMessages.NotebookExecutionActivated: - this.handleMessage(message, payload, this.doInitGather); - break; - - case InteractiveWindowMessages.GatherCode: - this.postEmitter.fire({ - message: InteractiveWindowMessages.Gathering, - payload: { cellId: payload.id, gathering: true } - }); - this.handleMessage(message, payload, this.doGather); - break; - - case InteractiveWindowMessages.GatherCodeToScript: - this.postEmitter.fire({ - message: InteractiveWindowMessages.Gathering, - payload: { cellId: payload.id, gathering: true } - }); - this.handleMessage(message, payload, this.doGatherToScript); - break; - - case InteractiveWindowMessages.RestartKernel: - this.linesSubmitted = 0; - this.cellsSubmitted = 0; - if (this.gatherProvider) { - try { - this.gatherProvider.resetLog(); - } catch (e) { - traceError('Gather: Exception at Reset Log', e); - sendTelemetryEvent(Telemetry.GatherException, undefined, { exceptionType: 'reset' }); - } - } - break; - - case InteractiveWindowMessages.FinishCell: - const cell = payload.cell as ICell; - if (cell && cell.data && cell.data.source) { - const lineCount: number = cell.data.source.length as number; - this.linesSubmitted += lineCount; - this.cellsSubmitted += 1; - } - break; - - default: - break; - } - } - - private handleMessage( - _message: T, - // tslint:disable:no-any - payload: any, - handler: (args: M[T]) => void - ) { - const args = payload as M[T]; - handler.bind(this)(args); - } - - private doInitGather(payload: INotebookIdentity & { owningResource: Resource }): void { - this.initGather(payload).ignoreErrors(); - } - - private async initGather(identity: INotebookIdentity & { owningResource: Resource }) { - this.notebookUri = identity.resource; - - const nb = await this.notebookProvider.getOrCreateNotebook({ identity: this.notebookUri, getOnly: true }); - // If we have an executing notebook, get its gather execution service. - if (nb) { - this.gatherProvider = this.getGatherProvider(nb); - } - } - - private getGatherProvider(nb: INotebook): any | undefined { - const gatherLogger = ( - nb.getLoggers().find((logger: INotebookExecutionLogger) => (logger).getGatherProvider) - ); - - if (gatherLogger) { - return gatherLogger.getGatherProvider(); - } - } - - private doGather(payload: ICell): Promise { - return this.gatherCodeInternal(payload) - .catch((err) => { - traceError(`Gather to Notebook error: ${err}`); - this.applicationShell.showErrorMessage(err); - }) - .finally(() => - this.postEmitter.fire({ - message: InteractiveWindowMessages.Gathering, - payload: { cellId: payload.id, gathering: false } - }) - ); - } - - private doGatherToScript(payload: ICell): Promise { - return this.gatherCodeInternal(payload, true) - .catch((err) => { - traceError(`Gather to Script error: ${err}`); - this.applicationShell.showErrorMessage(err); - }) - .finally(() => - this.postEmitter.fire({ - message: InteractiveWindowMessages.Gathering, - payload: { cellId: payload.id, gathering: false } - }) - ); - } - - private gatherCodeInternal = async (cell: ICell, toScript: boolean = false) => { - this.gatherTimer = new StopWatch(); - let slicedProgram: string | undefined; - - try { - slicedProgram = this.gatherProvider - ? this.gatherProvider.gatherCode(cell) - : localize.DataScience.gatherError(); - } catch (e) { - traceError('Gather: Exception at gatherCode', e); - sendTelemetryEvent(Telemetry.GatherException, undefined, { exceptionType: 'gather' }); - const newline = '\n'; - const defaultCellMarker = - this.configService.getSettings().datascience.defaultCellMarker || Identifiers.DefaultCodeCellMarker; - slicedProgram = defaultCellMarker + newline + localize.DataScience.gatherError() + newline + (e as string); - } - - if (!slicedProgram) { - sendTelemetryEvent(Telemetry.GatherCompleted, this.gatherTimer?.elapsedTime, { result: 'err' }); - } else { - const gatherToScript: boolean = this.configService.getSettings().datascience.gatherToScript || toScript; - - if (gatherToScript) { - await this.showFile(slicedProgram, cell.file); - sendTelemetryEvent(Telemetry.GatherCompleted, this.gatherTimer?.elapsedTime, { result: 'script' }); - } else { - await this.showNotebook(slicedProgram, cell); - sendTelemetryEvent(Telemetry.GatherCompleted, this.gatherTimer?.elapsedTime, { - result: 'notebook' - }); - } - - sendTelemetryEvent(Telemetry.GatherStats, undefined, { - linesSubmitted: this.linesSubmitted, - cellsSubmitted: this.cellsSubmitted, - linesGathered: slicedProgram.trim().splitLines().length, - cellsGathered: generateCellsFromString(slicedProgram).length - }); - } - }; - - private async showNotebook(slicedProgram: string, cell: ICell) { - if (slicedProgram) { - const file = - cell.file === Identifiers.EmptyFileName && this.notebookUri ? this.notebookUri.fsPath : cell.file; - - let cells: ICell[] = [ - { - id: uuid(), - file: '', - line: 0, - state: 0, - data: createMarkdownCell(localize.DataScience.gatheredNotebookDescriptionInMarkdown().format(file)) - } - ]; - - // Create new notebook with the returned program and open it. - cells = cells.concat(generateCellsFromString(slicedProgram)); - - // Try to get a kernelspec - let kernelspec: nbformat.IKernelspecMetadata | undefined; - try { - const text = await this.fs.readLocalFile(file); - const json = JSON.parse(text); - kernelspec = json.metadata.kernelspec; - } catch (e) { - traceError('Gather: No kernelspec found', e); - } - - const notebook = await this.jupyterExporter.translateToNotebook(cells, undefined, kernelspec); - if (notebook) { - const contents = JSON.stringify(notebook); - const editor = await this.ipynbProvider.createNew(contents); - - let disposableNotebookSaved: IDisposable; - let disposableNotebookClosed: IDisposable; - - const savedHandler = () => { - sendTelemetryEvent(Telemetry.GatheredNotebookSaved); - if (disposableNotebookSaved) { - disposableNotebookSaved.dispose(); - } - if (disposableNotebookClosed) { - disposableNotebookClosed.dispose(); - } - }; - - const closedHandler = () => { - if (disposableNotebookSaved) { - disposableNotebookSaved.dispose(); - } - if (disposableNotebookClosed) { - disposableNotebookClosed.dispose(); - } - }; - - disposableNotebookSaved = editor.saved(savedHandler); - disposableNotebookClosed = editor.closed(closedHandler); - } - } - } - - private async showFile(slicedProgram: string, filename: string) { - const defaultCellMarker = - this.configService.getSettings().datascience.defaultCellMarker || Identifiers.DefaultCodeCellMarker; - - if (slicedProgram) { - // Remove all cell definitions and newlines - const re = new RegExp(`^(${defaultCellMarker}.*|\\s*)\n`, 'gm'); - slicedProgram = slicedProgram.replace(re, ''); - } - - const annotatedScript = `${localize.DataScience.gatheredScriptDescription()}${defaultCellMarker}\n${slicedProgram}`; - - // Don't want to open the gathered code on top of the interactive window - let viewColumn: ViewColumn | undefined; - const fileNameMatch = this.documentManager.visibleTextEditors.filter((textEditor) => - this.fs.areLocalPathsSame(textEditor.document.fileName, filename) - ); - const definedVisibleEditors = this.documentManager.visibleTextEditors.filter( - (textEditor) => textEditor.viewColumn !== undefined - ); - if (this.documentManager.visibleTextEditors.length > 0 && fileNameMatch.length > 0) { - // Original file is visible - viewColumn = fileNameMatch[0].viewColumn; - } else if (this.documentManager.visibleTextEditors.length > 0 && definedVisibleEditors.length > 0) { - // There is a visible text editor, just not the original file. Make sure viewColumn isn't undefined - viewColumn = definedVisibleEditors[0].viewColumn; - } else { - // Only one panel open and interactive window is occupying it, or original file is open but hidden - viewColumn = ViewColumn.Beside; - } - - // Create a new open editor with the returned program in the right panel - const doc = await this.documentManager.openTextDocument({ - content: annotatedScript, - language: PYTHON_LANGUAGE - }); - const editor = await this.documentManager.showTextDocument(doc, viewColumn); - - // Edit the document so that it is dirty (add a space at the end) - editor.edit((editBuilder) => { - editBuilder.insert(new Position(editor.document.lineCount, 0), '\n'); - }); - } -} diff --git a/src/client/datascience/gather/gatherLogger.ts b/src/client/datascience/gather/gatherLogger.ts deleted file mode 100644 index e5b625d26a2f..000000000000 --- a/src/client/datascience/gather/gatherLogger.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { inject, injectable } from 'inversify'; -// tslint:disable-next-line: no-require-imports -import cloneDeep = require('lodash/cloneDeep'); -import { extensions } from 'vscode'; -import { concatMultilineString } from '../../../datascience-ui/common'; -import { traceError, traceInfo } from '../../common/logger'; -import { IConfigurationService } from '../../common/types'; -import { noop } from '../../common/utils/misc'; -import { sendTelemetryEvent } from '../../telemetry'; -import { CellMatcher } from '../cellMatcher'; -import { GatherExtension, Telemetry } from '../constants'; -import { ICell as IVscCell, IGatherLogger, IGatherProvider } from '../types'; - -@injectable() -export class GatherLogger implements IGatherLogger { - private gather: IGatherProvider | undefined; - constructor(@inject(IConfigurationService) private configService: IConfigurationService) { - this.initGatherExtension().ignoreErrors(); - } - - public dispose() { - noop(); - } - public onKernelRestarted() { - noop(); - } - - public async preExecute(_vscCell: IVscCell, _silent: boolean): Promise { - // This function is just implemented here for compliance with the INotebookExecutionLogger interface - noop(); - } - - public async postExecute(vscCell: IVscCell, _silent: boolean): Promise { - if (this.gather) { - // Don't log if vscCell.data.source is an empty string or if it was - // silently executed. Original Jupyter extension also does this. - if (vscCell.data.source !== '' && !_silent) { - // First make a copy of this cell, as we are going to modify it - const cloneCell: IVscCell = cloneDeep(vscCell); - - // Strip first line marker. We can't do this at JupyterServer.executeCodeObservable because it messes up hashing - const cellMatcher = new CellMatcher(this.configService.getSettings().datascience); - cloneCell.data.source = cellMatcher.stripFirstMarker(concatMultilineString(vscCell.data.source)); - - try { - this.gather.logExecution(cloneCell); - } catch (e) { - traceError('Gather: Exception at Log Execution', e); - sendTelemetryEvent(Telemetry.GatherException, undefined, { exceptionType: 'log' }); - } - } - } - } - - public getGatherProvider(): IGatherProvider | undefined { - return this.gather; - } - - private async initGatherExtension() { - const ext = extensions.getExtension(GatherExtension); - if (ext) { - sendTelemetryEvent(Telemetry.GatherIsInstalled); - if (!ext.isActive) { - try { - await ext.activate(); - } catch (e) { - traceError('Gather: Exception at Activate', e); - sendTelemetryEvent(Telemetry.GatherException, undefined, { exceptionType: 'activate' }); - } - } - const api = ext.exports; - try { - this.gather = api.getGatherProvider(); - } catch { - traceInfo(`Gather not installed`); - } - } - } -} diff --git a/src/client/datascience/interactive-common/debugListener.ts b/src/client/datascience/interactive-common/debugListener.ts deleted file mode 100644 index c50fb893ced5..000000000000 --- a/src/client/datascience/interactive-common/debugListener.ts +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; - -import { inject, injectable, named } from 'inversify'; -import { DebugSession, Event, EventEmitter } from 'vscode'; - -import { noop } from '../../common/utils/misc'; -import { Identifiers } from '../constants'; -import { IInteractiveWindowListener, IJupyterDebugService } from '../types'; -import { InteractiveWindowMessages } from './interactiveWindowTypes'; - -// tslint:disable: no-any -@injectable() -export class DebugListener implements IInteractiveWindowListener { - private postEmitter: EventEmitter<{ message: string; payload: any }> = new EventEmitter<{ - message: string; - payload: any; - }>(); - constructor( - @inject(IJupyterDebugService) - @named(Identifiers.MULTIPLEXING_DEBUGSERVICE) - private debugService: IJupyterDebugService - ) { - this.debugService.onDidChangeActiveDebugSession(this.onChangeDebugSession.bind(this)); - } - - public get postMessage(): Event<{ message: string; payload: any }> { - return this.postEmitter.event; - } - - public onMessage(message: string, _payload?: any): void { - switch (message) { - default: - break; - } - } - public dispose(): void | undefined { - noop(); - } - - private onChangeDebugSession(e: DebugSession | undefined) { - if (e) { - this.postEmitter.fire({ message: InteractiveWindowMessages.StartDebugging, payload: undefined }); - } else { - this.postEmitter.fire({ message: InteractiveWindowMessages.StopDebugging, payload: undefined }); - } - } -} diff --git a/src/client/datascience/interactive-common/intellisense/conversion.ts b/src/client/datascience/interactive-common/intellisense/conversion.ts deleted file mode 100644 index 6dd99b2b41fc..000000000000 --- a/src/client/datascience/interactive-common/intellisense/conversion.ts +++ /dev/null @@ -1,348 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../../common/extensions'; - -import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; -import * as vscode from 'vscode'; -import * as vscodeLanguageClient from 'vscode-languageclient/node'; - -// See the comment on convertCompletionItemKind below -// Here's the monaco enum: -enum monacoCompletionItemKind { - Method = 0, - Function = 1, - Constructor = 2, - Field = 3, - Variable = 4, - Class = 5, - Struct = 6, - Interface = 7, - Module = 8, - Property = 9, - Event = 10, - Operator = 11, - Unit = 12, - Value = 13, - Constant = 14, - Enum = 15, - EnumMember = 16, - Keyword = 17, - Text = 18, - Color = 19, - File = 20, - Reference = 21, - Customcolor = 22, - Folder = 23, - TypeParameter = 24, - Snippet = 25 -} -// - -// Left side is the vscode value. -const mapCompletionItemKind: Map = new Map([ - [vscode.CompletionItemKind.Text, monacoCompletionItemKind.Text], // Text - [vscode.CompletionItemKind.Method, monacoCompletionItemKind.Method], // Method - [vscode.CompletionItemKind.Function, monacoCompletionItemKind.Function], // Function - [vscode.CompletionItemKind.Constructor, monacoCompletionItemKind.Constructor], // Constructor - [vscode.CompletionItemKind.Field, monacoCompletionItemKind.Field], // Field - [vscode.CompletionItemKind.Variable, monacoCompletionItemKind.Variable], // Variable - [vscode.CompletionItemKind.Class, monacoCompletionItemKind.Class], // Class - [vscode.CompletionItemKind.Interface, monacoCompletionItemKind.Interface], // Interface - [vscode.CompletionItemKind.Module, monacoCompletionItemKind.Module], // Module - [vscode.CompletionItemKind.Property, monacoCompletionItemKind.Property], // Property - [vscode.CompletionItemKind.Unit, monacoCompletionItemKind.Unit], // Unit - [vscode.CompletionItemKind.Value, monacoCompletionItemKind.Value], // Value - [vscode.CompletionItemKind.Enum, monacoCompletionItemKind.Enum], // Enum - [vscode.CompletionItemKind.Keyword, monacoCompletionItemKind.Keyword], // Keyword - [vscode.CompletionItemKind.Snippet, monacoCompletionItemKind.Snippet], // Snippet - [vscode.CompletionItemKind.Color, monacoCompletionItemKind.Color], // Color - [vscode.CompletionItemKind.File, monacoCompletionItemKind.File], // File - [vscode.CompletionItemKind.Reference, monacoCompletionItemKind.Reference], // Reference - [vscode.CompletionItemKind.Folder, monacoCompletionItemKind.Folder], // Folder - [vscode.CompletionItemKind.EnumMember, monacoCompletionItemKind.EnumMember], // EnumMember - [vscode.CompletionItemKind.Constant, monacoCompletionItemKind.Constant], // Constant - [vscode.CompletionItemKind.Struct, monacoCompletionItemKind.Struct], // Struct - [vscode.CompletionItemKind.Event, monacoCompletionItemKind.Event], // Event - [vscode.CompletionItemKind.Operator, monacoCompletionItemKind.Operator], // Operator - [vscode.CompletionItemKind.TypeParameter, monacoCompletionItemKind.TypeParameter] // TypeParameter -]); - -// Left side is the monaco value. -const reverseMapCompletionItemKind: Map = new Map( - [ - [monacoCompletionItemKind.Text, vscode.CompletionItemKind.Text], // Text - [monacoCompletionItemKind.Method, vscode.CompletionItemKind.Method], // Method - [monacoCompletionItemKind.Function, vscode.CompletionItemKind.Function], // Function - [monacoCompletionItemKind.Constructor, vscode.CompletionItemKind.Constructor], // Constructor - [monacoCompletionItemKind.Field, vscode.CompletionItemKind.Field], // Field - [monacoCompletionItemKind.Variable, vscode.CompletionItemKind.Variable], // Variable - [monacoCompletionItemKind.Class, vscode.CompletionItemKind.Class], // Class - [monacoCompletionItemKind.Interface, vscode.CompletionItemKind.Interface], // Interface - [monacoCompletionItemKind.Module, vscode.CompletionItemKind.Module], // Module - [monacoCompletionItemKind.Property, vscode.CompletionItemKind.Property], // Property - [monacoCompletionItemKind.Unit, vscode.CompletionItemKind.Unit], // Unit - [monacoCompletionItemKind.Value, vscode.CompletionItemKind.Value], // Value - [monacoCompletionItemKind.Enum, vscode.CompletionItemKind.Enum], // Enum - [monacoCompletionItemKind.Keyword, vscode.CompletionItemKind.Keyword], // Keyword - [monacoCompletionItemKind.Snippet, vscode.CompletionItemKind.Snippet], // Snippet - [monacoCompletionItemKind.Color, vscode.CompletionItemKind.Color], // Color - [monacoCompletionItemKind.File, vscode.CompletionItemKind.File], // File - [monacoCompletionItemKind.Reference, vscode.CompletionItemKind.Reference], // Reference - [monacoCompletionItemKind.Folder, vscode.CompletionItemKind.Folder], // Folder - [monacoCompletionItemKind.EnumMember, vscode.CompletionItemKind.EnumMember], // EnumMember - [monacoCompletionItemKind.Constant, vscode.CompletionItemKind.Constant], // Constant - [monacoCompletionItemKind.Struct, vscode.CompletionItemKind.Struct], // Struct - [monacoCompletionItemKind.Event, vscode.CompletionItemKind.Event], // Event - [monacoCompletionItemKind.Operator, vscode.CompletionItemKind.Operator], // Operator - [monacoCompletionItemKind.TypeParameter, vscode.CompletionItemKind.TypeParameter] // TypeParameter - ] -); - -const mapJupyterKind: Map = new Map([ - ['method', monacoCompletionItemKind.Method], - ['function', monacoCompletionItemKind.Function], - ['constructor', monacoCompletionItemKind.Constructor], - ['field', monacoCompletionItemKind.Field], - ['variable', monacoCompletionItemKind.Variable], - ['class', monacoCompletionItemKind.Class], - ['struct', monacoCompletionItemKind.Struct], - ['interface', monacoCompletionItemKind.Interface], - ['module', monacoCompletionItemKind.Module], - ['property', monacoCompletionItemKind.Property], - ['event', monacoCompletionItemKind.Event], - ['operator', monacoCompletionItemKind.Operator], - ['unit', monacoCompletionItemKind.Unit], - ['value', monacoCompletionItemKind.Value], - ['constant', monacoCompletionItemKind.Constant], - ['enum', monacoCompletionItemKind.Enum], - ['enumMember', monacoCompletionItemKind.EnumMember], - ['keyword', monacoCompletionItemKind.Keyword], - ['text', monacoCompletionItemKind.Text], - ['color', monacoCompletionItemKind.Color], - ['file', monacoCompletionItemKind.File], - ['reference', monacoCompletionItemKind.Reference], - ['customcolor', monacoCompletionItemKind.Customcolor], - ['folder', monacoCompletionItemKind.Folder], - ['typeParameter', monacoCompletionItemKind.TypeParameter], - ['snippet', monacoCompletionItemKind.Snippet], - ['', monacoCompletionItemKind.Field] -]); - -function convertToMonacoRange(range: vscodeLanguageClient.Range | undefined): monacoEditor.IRange | undefined { - if (range) { - return { - startLineNumber: range.start.line + 1, - startColumn: range.start.character + 1, - endLineNumber: range.end.line + 1, - endColumn: range.end.character + 1 - }; - } -} - -function convertToVSCodeRange(range: monacoEditor.IRange | undefined): vscode.Range | undefined { - if (range) { - return new vscode.Range( - new vscode.Position(range.startLineNumber - 1, range.startColumn - 1), - new vscode.Position(range.endLineNumber - 1, range.endColumn - 1) - ); - } -} - -// Something very fishy. If the monacoEditor.languages.CompletionItemKind is included here, we get this error on startup -// Activating extension `ms-python.python` failed: Unexpected token { -// extensionHostProcess.js:457 -// Here is the error stack: f:\vscode-python\node_modules\monaco-editor\esm\vs\editor\editor.api.js:5 -// import { EDITOR_DEFAULTS } from './common/config/editorOptions.js'; -// Instead just use a map -function convertToMonacoCompletionItemKind(kind?: number): number { - const value = kind ? mapCompletionItemKind.get(kind) : monacoCompletionItemKind.Property; // Property is 9 - if (value) { - return value; - } - return monacoCompletionItemKind.Property; -} - -function convertToVSCodeCompletionItemKind(kind?: number): vscode.CompletionItemKind { - const value = kind ? reverseMapCompletionItemKind.get(kind) : vscode.CompletionItemKind.Property; - if (value) { - return value; - } - return vscode.CompletionItemKind.Property; -} - -const SnippetEscape = 4; - -export function convertToMonacoCompletionItem( - item: vscodeLanguageClient.CompletionItem, - requiresKindConversion: boolean -): monacoEditor.languages.CompletionItem { - // They should be pretty much identical? Except for ranges. - // tslint:disable-next-line: no-object-literal-type-assertion no-any - const result = ({ ...item } as any) as monacoEditor.languages.CompletionItem; - if (requiresKindConversion) { - result.kind = convertToMonacoCompletionItemKind(item.kind); - } - - // Make sure we have insert text, otherwise the monaco editor will crash on trying to hit tab or enter on the text - if (!result.insertText && result.label) { - result.insertText = result.label; - } - - // tslint:disable-next-line: no-any - const snippet = (result.insertText as any) as vscode.SnippetString; - if (snippet.value) { - result.insertTextRules = SnippetEscape; - // Monaco can't handle the snippetText value, so rewrite it. - result.insertText = snippet.value; - } - - // Make sure we don't have _documentPosition. It holds onto a huge tree of information - // tslint:disable-next-line: no-any - const resultAny = result as any; - if (resultAny._documentPosition) { - delete resultAny._documentPosition; - } - - return result; -} - -export function convertToVSCodeCompletionItem(item: monacoEditor.languages.CompletionItem): vscode.CompletionItem { - // tslint:disable-next-line: no-object-literal-type-assertion no-any - const result = ({ ...item } as any) as vscode.CompletionItem; - - if (item.kind && result.kind) { - result.kind = convertToVSCodeCompletionItemKind(item.kind); - } - - if (item.range && result.range) { - result.range = convertToVSCodeRange(item.range); - } - - return result; -} - -export function convertToMonacoCompletionList( - result: - | vscodeLanguageClient.CompletionList - | vscodeLanguageClient.CompletionItem[] - | vscode.CompletionItem[] - | vscode.CompletionList - | null, - requiresKindConversion: boolean -): monacoEditor.languages.CompletionList { - if (result) { - if (result.hasOwnProperty('items')) { - const list = result as vscodeLanguageClient.CompletionList; - return { - suggestions: list.items.map((l) => convertToMonacoCompletionItem(l, requiresKindConversion)), - incomplete: list.isIncomplete - }; - } else { - // Must be one of the two array types since there's no items property. - const array = result as vscodeLanguageClient.CompletionItem[]; - return { - suggestions: array.map((l) => convertToMonacoCompletionItem(l, requiresKindConversion)), - incomplete: false - }; - } - } - - return { - suggestions: [], - incomplete: false - }; -} - -function convertToMonacoMarkdown( - strings: - | vscodeLanguageClient.MarkupContent - | vscodeLanguageClient.MarkedString - | vscodeLanguageClient.MarkedString[] - | vscode.MarkedString - | vscode.MarkedString[] -): monacoEditor.IMarkdownString[] { - if (strings.hasOwnProperty('kind')) { - const content = strings as vscodeLanguageClient.MarkupContent; - return [ - { - value: content.value - } - ]; - } else if (strings.hasOwnProperty('value')) { - // tslint:disable-next-line: no-any - const content = strings as any; - return [ - { - value: content.value - } - ]; - } else if (typeof strings === 'string') { - return [ - { - value: strings.toString() - } - ]; - } else if (Array.isArray(strings)) { - const array = strings as vscodeLanguageClient.MarkedString[]; - return array.map((a) => convertToMonacoMarkdown(a)[0]); - } - - return []; -} - -export function convertToMonacoHover( - result: vscodeLanguageClient.Hover | vscode.Hover | null | undefined -): monacoEditor.languages.Hover { - if (result) { - return { - contents: convertToMonacoMarkdown(result.contents), - range: convertToMonacoRange(result.range) - }; - } - - return { - contents: [] - }; -} - -export function convertStringsToSuggestions( - strings: ReadonlyArray, - range: monacoEditor.IRange, - // tslint:disable-next-line: no-any - metadata: any -): monacoEditor.languages.CompletionItem[] { - // Try to compute kind from the metadata. - let kinds: number[]; - if (metadata && metadata._jupyter_types_experimental) { - // tslint:disable-next-line: no-any - kinds = metadata._jupyter_types_experimental.map((e: any) => { - const result = mapJupyterKind.get(e.type); - return result ? result : 3; // If not found use Field = 3 - }); - } - - return strings.map((s: string, i: number) => { - return { - label: s, - insertText: s, - sortText: s, - kind: kinds ? kinds[i] : 3, // Note: importing the monacoEditor.languages.CompletionItemKind causes a failure in loading the extension. So we use numbers. - range - }; - }); -} - -export function convertToMonacoSignatureHelp( - result: vscodeLanguageClient.SignatureHelp | vscode.SignatureHelp | null -): monacoEditor.languages.SignatureHelp { - if (result) { - return result as monacoEditor.languages.SignatureHelp; - } - - return { - signatures: [], - activeParameter: 0, - activeSignature: 0 - }; -} diff --git a/src/client/datascience/interactive-common/intellisense/intellisenseDocument.ts b/src/client/datascience/interactive-common/intellisense/intellisenseDocument.ts deleted file mode 100644 index 52bbc2afbf5e..000000000000 --- a/src/client/datascience/interactive-common/intellisense/intellisenseDocument.ts +++ /dev/null @@ -1,693 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../../common/extensions'; - -import { EndOfLine, Position, Range, TextDocument, TextDocumentContentChangeEvent, TextLine, Uri } from 'vscode'; -import * as vscodeLanguageClient from 'vscode-languageclient/node'; - -import { PYTHON_LANGUAGE } from '../../../common/constants'; -import { Identifiers } from '../../constants'; -import { IEditorContentChange } from '../interactiveWindowTypes'; -import { DefaultWordPattern, ensureValidWordDefinition, getWordAtText, regExpLeadsToEndlessLoop } from './wordHelper'; - -class IntellisenseLine implements TextLine { - private _range: Range; - private _rangeWithLineBreak: Range; - private _firstNonWhitespaceIndex: number | undefined; - private _isEmpty: boolean | undefined; - - constructor(private _contents: string, private _line: number, private _offset: number) { - this._range = new Range(new Position(_line, 0), new Position(_line, _contents.length)); - this._rangeWithLineBreak = new Range(this.range.start, new Position(_line, _contents.length + 1)); - } - - public get offset(): number { - return this._offset; - } - public get lineNumber(): number { - return this._line; - } - public get text(): string { - return this._contents; - } - public get range(): Range { - return this._range; - } - public get rangeIncludingLineBreak(): Range { - return this._rangeWithLineBreak; - } - public get firstNonWhitespaceCharacterIndex(): number { - if (this._firstNonWhitespaceIndex === undefined) { - this._firstNonWhitespaceIndex = this._contents.trimLeft().length - this._contents.length; - } - return this._firstNonWhitespaceIndex; - } - public get isEmptyOrWhitespace(): boolean { - if (this._isEmpty === undefined) { - this._isEmpty = this._contents.length === 0 || this._contents.trim().length === 0; - } - return this._isEmpty; - } -} - -interface ICellRange { - id: string; - start: number; - fullEnd: number; - currentEnd: number; -} - -export interface ICellData { - text: string; - offset: number; -} - -export class IntellisenseDocument implements TextDocument { - private _uri: Uri; - private _version: number = 1; - private _lines: IntellisenseLine[] = []; - private _contents: string = ''; - private _cellRanges: ICellRange[] = []; - private inEditMode: boolean = false; - - constructor(fileName: string) { - // The file passed in is the base Uri for where we're basing this - // document. - // - // What about liveshare? - this._uri = Uri.file(fileName); - - // We should start our edit offset at 0. Each cell should end with a '/n' - this._cellRanges.push({ id: Identifiers.EditCellId, start: 0, fullEnd: 0, currentEnd: 0 }); - } - - public get uri(): Uri { - return this._uri; - } - public get fileName(): string { - return this._uri.fsPath; - } - - public get isUntitled(): boolean { - return true; - } - - public get isReadOnly(): boolean { - return !this.inEditMode; - } - public get languageId(): string { - return PYTHON_LANGUAGE; - } - public get version(): number { - return this._version; - } - public get isDirty(): boolean { - return true; - } - public get isClosed(): boolean { - return false; - } - public save(): Thenable { - return Promise.resolve(true); - } - public get eol(): EndOfLine { - return EndOfLine.LF; - } - public get lineCount(): number { - return this._lines.length; - } - - public lineAt(position: Position | number): TextLine { - if (typeof position === 'number') { - return this._lines[position as number]; - } else { - return this._lines[position.line]; - } - } - public offsetAt(position: Position): number { - return this.convertToOffset(position); - } - public positionAt(offset: number): Position { - let line = 0; - let ch = 0; - while (line + 1 < this._lines.length && this._lines[line + 1].offset <= offset) { - line += 1; - } - if (line < this._lines.length) { - ch = offset - this._lines[line].offset; - } - return new Position(line, ch); - } - public getText(range?: Range | undefined): string { - if (!range) { - return this._contents; - } else { - const startOffset = this.convertToOffset(range.start); - const endOffset = this.convertToOffset(range.end); - return this._contents.substr(startOffset, endOffset - startOffset); - } - } - - public getFullContentChanges(): TextDocumentContentChangeEvent[] { - return [ - { - range: this.createSerializableRange(new Position(0, 0), new Position(0, 0)), - rangeOffset: 0, - rangeLength: 0, // Adds are always zero - text: this._contents - } - ]; - } - - public getWordRangeAtPosition(position: Position, regexp?: RegExp | undefined): Range | undefined { - if (!regexp) { - // use default when custom-regexp isn't provided - regexp = DefaultWordPattern; - } else if (regExpLeadsToEndlessLoop(regexp)) { - // use default when custom-regexp is bad - console.warn( - `[getWordRangeAtPosition]: ignoring custom regexp '${regexp.source}' because it matches the empty string.` - ); - regexp = DefaultWordPattern; - } - - const wordAtText = getWordAtText( - position.character + 1, - ensureValidWordDefinition(regexp), - this._lines[position.line].text, - 0 - ); - - if (wordAtText) { - return new Range(position.line, wordAtText.startColumn - 1, position.line, wordAtText.endColumn - 1); - } - return undefined; - } - public validateRange(range: Range): Range { - return range; - } - public validatePosition(position: Position): Position { - return position; - } - - public get textDocumentItem(): vscodeLanguageClient.TextDocumentItem { - return { - uri: this._uri.toString(), - languageId: this.languageId, - version: this.version, - text: this.getText() - }; - } - - public get textDocumentId(): vscodeLanguageClient.VersionedTextDocumentIdentifier { - return { - uri: this._uri.toString(), - version: this.version - }; - } - - public loadAllCells( - cells: { code: string; id: string }[], - notebookType: 'interactive' | 'native' - ): TextDocumentContentChangeEvent[] { - if (!this.inEditMode && notebookType === 'native') { - this.inEditMode = true; - return this.reloadCells(cells); - } - return []; - } - - public reloadCells(cells: { code: string; id: string }[]): TextDocumentContentChangeEvent[] { - this._version += 1; - - // Normalize all of the cells, removing \r and separating each - // with a newline - const normalized = cells.map((c) => { - return { - id: c.id, - code: `${c.code.replace(/\r/g, '')}\n` - }; - }); - - // Contents are easy, just load all of the code in a row - this._contents = - normalized && normalized.length - ? normalized - .map((c) => c.code) - .reduce((p, c) => { - return `${p}${c}`; - }) - : ''; - - // Cell ranges are slightly more complicated - let prev: number = 0; - this._cellRanges = normalized.map((c) => { - const result = { - id: c.id, - start: prev, - fullEnd: prev + c.code.length, - currentEnd: prev + c.code.length - }; - prev += c.code.length; - return result; - }); - - // Then create the lines. - this._lines = this.createLines(); - - // Return our changes - return [ - { - range: this.createSerializableRange(new Position(0, 0), new Position(0, 0)), - rangeOffset: 0, - rangeLength: 0, // Adds are always zero - text: this._contents - } - ]; - } - - public addCell(fullCode: string, currentCode: string, id: string): TextDocumentContentChangeEvent[] { - // This should only happen once for each cell. - this._version += 1; - - // Get rid of windows line endings. We're normalizing on linux - const normalized = fullCode.replace(/\r/g, ''); - const normalizedCurrent = currentCode.replace(/\r/g, ''); - - // This item should go just before the edit cell - - // Make sure to put a newline between this code and the next code - const newCode = `${normalized}\n`; - const newCurrentCode = `${normalizedCurrent}\n`; - - // We should start just before the last cell. - const fromOffset = this.getEditCellOffset(); - - // Split our text between the edit text and the cells above - const before = this._contents.substr(0, fromOffset); - const after = this._contents.substr(fromOffset); - const fromPosition = this.positionAt(fromOffset); - - // Save the range for this cell () - this._cellRanges.splice(this._cellRanges.length - 1, 0, { - id, - start: fromOffset, - fullEnd: fromOffset + newCode.length, - currentEnd: fromOffset + newCurrentCode.length - }); - - // Update our entire contents and recompute our lines - this._contents = `${before}${newCode}${after}`; - this._lines = this.createLines(); - this._cellRanges[this._cellRanges.length - 1].start += newCode.length; - this._cellRanges[this._cellRanges.length - 1].fullEnd += newCode.length; - this._cellRanges[this._cellRanges.length - 1].currentEnd += newCode.length; - - return [ - { - range: this.createSerializableRange(fromPosition, fromPosition), - rangeOffset: fromOffset, - rangeLength: 0, // Adds are always zero - text: newCode - } - ]; - } - - public reloadCell(id: string, code: string): TextDocumentContentChangeEvent[] { - this._version += 1; - - // Make sure to put a newline between this code and the next code - const newCode = `${code.replace(/\r/g, '')}\n`; - - // Figure where this goes - const index = this._cellRanges.findIndex((r) => r.id === id); - if (index >= 0) { - const start = this.positionAt(this._cellRanges[index].start); - const end = this.positionAt(this._cellRanges[index].currentEnd); - return this.removeRange(newCode, start, end, index); - } - - return []; - } - - public insertCell( - id: string, - code: string, - codeCellAboveOrIndex: string | undefined | number - ): TextDocumentContentChangeEvent[] { - // This should only happen once for each cell. - this._version += 1; - - // Make sure to put a newline between this code and the next code - const newCode = `${code.replace(/\r/g, '')}\n`; - - // Figure where this goes - const aboveIndex = this._cellRanges.findIndex((r) => r.id === codeCellAboveOrIndex); - const insertIndex = typeof codeCellAboveOrIndex === 'number' ? codeCellAboveOrIndex : aboveIndex + 1; - - // Compute where we start from. - const fromOffset = - insertIndex < this._cellRanges.length ? this._cellRanges[insertIndex].start : this._contents.length; - - // Split our text between the text and the cells above - const before = this._contents.substr(0, fromOffset); - const after = this._contents.substr(fromOffset); - const fromPosition = this.positionAt(fromOffset); - - // Update our entire contents and recompute our lines - this._contents = `${before}${newCode}${after}`; - this._lines = this.createLines(); - - // Move all the other cell ranges down - for (let i = insertIndex; i <= this._cellRanges.length - 1; i += 1) { - this._cellRanges[i].start += newCode.length; - this._cellRanges[i].fullEnd += newCode.length; - this._cellRanges[i].currentEnd += newCode.length; - } - this._cellRanges.splice(insertIndex, 0, { - id, - start: fromOffset, - fullEnd: fromOffset + newCode.length, - currentEnd: fromOffset + newCode.length - }); - - return [ - { - range: this.createSerializableRange(fromPosition, fromPosition), - rangeOffset: fromOffset, - rangeLength: 0, // Adds are always zero - text: newCode - } - ]; - } - - public removeAllCells(): TextDocumentContentChangeEvent[] { - // Remove everything - if (this.inEditMode) { - this._version += 1; - - // Compute the offset for the edit cell - const toOffset = this._cellRanges.length > 0 ? this._cellRanges[this._cellRanges.length - 1].fullEnd : 0; - const from = this.positionAt(0); - const to = this.positionAt(toOffset); - - // Remove the entire range. - const result = this.removeRange('', from, to, 0); - - // Update our cell range - this._cellRanges = []; - - return result; - } - - return []; - } - - public editCell(editorChanges: IEditorContentChange[], id: string): TextDocumentContentChangeEvent[] { - this._version += 1; - - // Convert the range to local (and remove 1 based) - if (editorChanges && editorChanges.length) { - const normalized = editorChanges[0].text.replace(/\r/g, ''); - - // Figure out which cell we're editing. - const cellIndex = this._cellRanges.findIndex((c) => c.id === id); - if (cellIndex >= 0 && (id === Identifiers.EditCellId || this.inEditMode)) { - // This is an actual edit. - // Line/column are within this cell. Use its offset to compute the real position - const editPos = this.positionAt(this._cellRanges[cellIndex].start); - const from = new Position( - editPos.line + editorChanges[0].range.startLineNumber - 1, - editorChanges[0].range.startColumn - 1 - ); - const to = new Position( - editPos.line + editorChanges[0].range.endLineNumber - 1, - editorChanges[0].range.endColumn - 1 - ); - - // Remove this range from the contents and return the change. - return this.removeRange(normalized, from, to, cellIndex); - } else if (cellIndex >= 0) { - // This is an edit of a read only cell. Just replace our currentEnd position - const newCode = `${normalized}\n`; - this._cellRanges[cellIndex].currentEnd = this._cellRanges[cellIndex].start + newCode.length; - } - } - - return []; - } - - public remove(id: string): TextDocumentContentChangeEvent[] { - let change: TextDocumentContentChangeEvent[] = []; - - const index = this._cellRanges.findIndex((c) => c.id === id); - // Ignore unless in edit mode. For non edit mode, cells are still there. - if (index >= 0 && this.inEditMode) { - this._version += 1; - - const found = this._cellRanges[index]; - const foundLength = found.currentEnd - found.start; - const from = new Position(this.getLineFromOffset(found.start), 0); - const to = this.positionAt(found.currentEnd); - - // Remove from the cell ranges. - for (let i = index + 1; i <= this._cellRanges.length - 1; i += 1) { - this._cellRanges[i].start -= foundLength; - this._cellRanges[i].fullEnd -= foundLength; - this._cellRanges[i].currentEnd -= foundLength; - } - this._cellRanges.splice(index, 1); - - // Recreate the contents - const before = this._contents.substr(0, found.start); - const after = this._contents.substr(found.currentEnd); - this._contents = `${before}${after}`; - this._lines = this.createLines(); - - change = [ - { - range: this.createSerializableRange(from, to), - rangeOffset: found.start, - rangeLength: foundLength, - text: '' - } - ]; - } - - return change; - } - - public swap(first: string, second: string): TextDocumentContentChangeEvent[] { - let change: TextDocumentContentChangeEvent[] = []; - - const firstIndex = this._cellRanges.findIndex((c) => c.id === first); - const secondIndex = this._cellRanges.findIndex((c) => c.id === second); - if (firstIndex >= 0 && secondIndex >= 0 && firstIndex !== secondIndex && this.inEditMode) { - this._version += 1; - - const topIndex = firstIndex < secondIndex ? firstIndex : secondIndex; - const bottomIndex = firstIndex > secondIndex ? firstIndex : secondIndex; - const top = { ...this._cellRanges[topIndex] }; - const bottom = { ...this._cellRanges[bottomIndex] }; - - const from = new Position(this.getLineFromOffset(top.start), 0); - const to = this.positionAt(bottom.currentEnd); - - // Swap everything - this._cellRanges[topIndex].id = bottom.id; - this._cellRanges[topIndex].fullEnd = top.start + (bottom.fullEnd - bottom.start); - this._cellRanges[topIndex].currentEnd = top.start + (bottom.currentEnd - bottom.start); - this._cellRanges[bottomIndex].id = top.id; - this._cellRanges[bottomIndex].start = this._cellRanges[topIndex].fullEnd; - this._cellRanges[bottomIndex].fullEnd = this._cellRanges[topIndex].fullEnd + (top.fullEnd - top.start); - this._cellRanges[bottomIndex].currentEnd = - this._cellRanges[topIndex].fullEnd + (top.currentEnd - top.start); - - const fromOffset = this.convertToOffset(from); - const toOffset = this.convertToOffset(to); - - // Recreate our contents, and then recompute all of our lines - const before = this._contents.substr(0, fromOffset); - const topText = this._contents.substr(top.start, top.fullEnd - top.start); - const bottomText = this._contents.substr(bottom.start, bottom.fullEnd - bottom.start); - const after = this._contents.substr(toOffset); - const replacement = `${bottomText}${topText}`; - this._contents = `${before}${replacement}${after}`; - this._lines = this.createLines(); - - // Change is a full replacement - change = [ - { - range: this.createSerializableRange(from, to), - rangeOffset: fromOffset, - rangeLength: toOffset - fromOffset, - text: replacement - } - ]; - } - - return change; - } - - public removeAll(): TextDocumentContentChangeEvent[] { - let change: TextDocumentContentChangeEvent[] = []; - // Ignore unless in edit mode. - if (this._lines.length > 0 && this.inEditMode) { - this._version += 1; - - const from = this._lines[0].range.start; - const to = this._lines[this._lines.length - 1].rangeIncludingLineBreak.end; - const length = this._contents.length; - this._cellRanges = []; - this._contents = ''; - this._lines = []; - - change = [ - { - range: this.createSerializableRange(from, to), - rangeOffset: 0, - rangeLength: length, - text: '' - } - ]; - } - - return change; - } - - public convertToDocumentPosition(id: string, line: number, ch: number): Position { - // Monaco is 1 based, and we need to add in our cell offset. - const cellIndex = this._cellRanges.findIndex((c) => c.id === id); - if (cellIndex >= 0) { - // Line/column are within this cell. Use its offset to compute the real position - const editLine = this.positionAt(this._cellRanges[cellIndex].start); - const docLine = line - 1 + editLine.line; - const docCh = ch - 1; - return new Position(docLine, docCh); - } - - // We can't find a cell that matches. Just remove the 1 based - return new Position(line - 1, ch - 1); - } - - public getCellData(cellId: string) { - const range = this._cellRanges.find((cellRange) => cellRange.id === cellId); - if (range) { - return { - offset: range.start, - text: this._contents.substring(range.start, range.currentEnd) - }; - } - } - - public getEditCellContent() { - return this._contents.substr(this.getEditCellOffset()); - } - - public getEditCellOffset(cellId?: string) { - // in native editor - if (this.inEditMode && cellId) { - const cell = this._cellRanges.find((c) => c.id === cellId); - - if (cell) { - return cell.start; - } - } - - // in interactive window - return this._cellRanges && this._cellRanges.length > 0 - ? this._cellRanges[this._cellRanges.length - 1].start - : 0; - } - - private getLineFromOffset(offset: number) { - let lineCounter = 0; - - for (let i = 0; i < offset; i += 1) { - if (this._contents[i] === '\n') { - lineCounter += 1; - } - } - - return lineCounter; - } - - private removeRange( - newText: string, - from: Position, - to: Position, - cellIndex: number - ): TextDocumentContentChangeEvent[] { - const fromOffset = this.convertToOffset(from); - const toOffset = this.convertToOffset(to); - - // Recreate our contents, and then recompute all of our lines - const before = this._contents.substr(0, fromOffset); - const after = this._contents.substr(toOffset); - this._contents = `${before}${newText}${after}`; - this._lines = this.createLines(); - - // Update ranges after this. All should move by the diff in length, although the current one - // should stay at the same start point. - const lengthDiff = newText.length - (toOffset - fromOffset); - for (let i = cellIndex; i < this._cellRanges.length; i += 1) { - if (i !== cellIndex) { - this._cellRanges[i].start += lengthDiff; - } - this._cellRanges[i].fullEnd += lengthDiff; - this._cellRanges[i].currentEnd += lengthDiff; - } - - return [ - { - range: this.createSerializableRange(from, to), - rangeOffset: fromOffset, - rangeLength: toOffset - fromOffset, - text: newText - } - ]; - } - - private createLines(): IntellisenseLine[] { - const split = this._contents.splitLines({ trim: false, removeEmptyEntries: false }); - let prevLine: IntellisenseLine | undefined; - return split.map((s, i) => { - const nextLine = this.createTextLine(s, i, prevLine); - prevLine = nextLine; - return nextLine; - }); - } - - private createTextLine(line: string, index: number, prevLine: IntellisenseLine | undefined): IntellisenseLine { - return new IntellisenseLine( - line, - index, - prevLine ? prevLine.offset + prevLine.rangeIncludingLineBreak.end.character : 0 - ); - } - - private convertToOffset(pos: Position): number { - if (pos.line < this._lines.length) { - return this._lines[pos.line].offset + pos.character; - } - return this._contents.length; - } - - private createSerializableRange(start: Position, end: Position): Range { - // This funciton is necessary so that the Range can be passed back - // over a remote connection without including all of the extra fields that - // VS code puts into a Range object. - const result = { - start: { - line: start.line, - character: start.character - }, - end: { - line: end.line, - character: end.character - } - }; - return result as Range; - } -} diff --git a/src/client/datascience/interactive-common/intellisense/intellisenseLine.ts b/src/client/datascience/interactive-common/intellisense/intellisenseLine.ts deleted file mode 100644 index 54dc5a7e5c2e..000000000000 --- a/src/client/datascience/interactive-common/intellisense/intellisenseLine.ts +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../../common/extensions'; - -import { Position, Range, TextLine } from 'vscode'; - -export class IntellisenseLine implements TextLine { - private _range: Range; - private _rangeWithLineBreak: Range; - private _firstNonWhitespaceIndex: number | undefined; - private _isEmpty: boolean | undefined; - - constructor(private _contents: string, private _line: number, private _offset: number) { - this._range = new Range(new Position(_line, 0), new Position(_line, _contents.length)); - this._rangeWithLineBreak = new Range(this.range.start, new Position(_line, _contents.length + 1)); - } - - public get offset(): number { - return this._offset; - } - public get lineNumber(): number { - return this._line; - } - public get text(): string { - return this._contents; - } - public get range(): Range { - return this._range; - } - public get rangeIncludingLineBreak(): Range { - return this._rangeWithLineBreak; - } - public get firstNonWhitespaceCharacterIndex(): number { - if (this._firstNonWhitespaceIndex === undefined) { - this._firstNonWhitespaceIndex = this._contents.trimLeft().length - this._contents.length; - } - return this._firstNonWhitespaceIndex; - } - public get isEmptyOrWhitespace(): boolean { - if (this._isEmpty === undefined) { - this._isEmpty = this._contents.length === 0 || this._contents.trim().length === 0; - } - return this._isEmpty; - } -} diff --git a/src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts b/src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts deleted file mode 100644 index 9124ccd5a842..000000000000 --- a/src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts +++ /dev/null @@ -1,953 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../../common/extensions'; - -import { inject, injectable, named } from 'inversify'; -import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; -import * as path from 'path'; -import * as uuid from 'uuid/v4'; -import { - CancellationTokenSource, - CompletionItem, - Event, - EventEmitter, - Hover, - MarkdownString, - SignatureHelpContext, - SignatureInformation, - TextDocumentContentChangeEvent, - Uri -} from 'vscode'; -import { CancellationToken } from 'vscode-jsonrpc'; -import * as vscodeLanguageClient from 'vscode-languageclient/node'; -import { concatMultilineString } from '../../../../datascience-ui/common'; -import { IWorkspaceService } from '../../../common/application/types'; -import { CancellationError } from '../../../common/cancellation'; -import { traceError, traceWarning } from '../../../common/logger'; -import { TemporaryFile } from '../../../common/platform/types'; -import { Resource } from '../../../common/types'; -import { createDeferred, Deferred, sleep, waitForPromise } from '../../../common/utils/async'; -import { noop } from '../../../common/utils/misc'; -import { HiddenFileFormatString } from '../../../constants'; -import { IInterpreterService } from '../../../interpreter/contracts'; -import { PythonEnvironment } from '../../../pythonEnvironments/info'; -import { sendTelemetryWhenDone } from '../../../telemetry'; -import { JupyterExtensionIntegration } from '../../api/jupyterIntegration'; -import { Identifiers, Settings, Telemetry } from '../../constants'; -import { - ICell, - IDataScienceFileSystem, - IInteractiveWindowListener, - IJupyterVariables, - INotebook, - INotebookCompletion, - INotebookProvider -} from '../../types'; -import { - ICancelIntellisenseRequest, - IInteractiveWindowMapping, - ILoadAllCells, - INotebookIdentity, - InteractiveWindowMessages, - IProvideCompletionItemsRequest, - IProvideHoverRequest, - IProvideSignatureHelpRequest, - IResolveCompletionItemRequest, - NotebookModelChange -} from '../interactiveWindowTypes'; -import { - convertStringsToSuggestions, - convertToMonacoCompletionItem, - convertToMonacoCompletionList, - convertToMonacoHover, - convertToMonacoSignatureHelp, - convertToVSCodeCompletionItem -} from './conversion'; -import { IntellisenseDocument } from './intellisenseDocument'; -import { NotebookLanguageServer } from './notebookLanguageServer'; - -// These regexes are used to get the text from jupyter output by recognizing escape charactor \x1b -const DocStringRegex = /\x1b\[1;31mDocstring:\x1b\[0m\s+([\s\S]*?)\r?\n\x1b\[1;31m/; -const SignatureTextRegex = /\x1b\[1;31mSignature:\x1b\[0m\s+([\s\S]*?)\r?\n\x1b\[1;31m/; -const TypeRegex = /\x1b\[1;31mType:\x1b\[0m\s+(.*)/; - -// This regex is to parse the name and the signature in the signature text from Jupyter, -// Example string: some_func(param1=1, param2=2) -> int -// match[1]: some_func -// match[2]: (param1=1, param2=2) -> int -const SignatureRegex = /(.+?)(\(([\s\S]*)\)(\s*->[\s\S]*)?)/; -const GeneralCallableSignature = '(*args, **kwargs)'; -// This regex is to detect whether a markdown provided by the language server is a callable and get its signature. -// Example string: ```python\n(function) some_func: (*args, **kwargs) -> None\n``` -// match[1]: (*args, **kwargs) -// If the string is not a callable, no match will be found. -// Example string: ```python\n(variable) some_var: Any\n``` -const CallableRegex = /python\n\(.+?\) \S+?: (\([\s\S]+?\))/; - -// tslint:disable:no-any -@injectable() -export class IntellisenseProvider implements IInteractiveWindowListener { - public get postMessage(): Event<{ message: string; payload: any }> { - return this.postEmitter.event; - } - private documentPromise: Deferred | undefined; - private temporaryFile: TemporaryFile | undefined; - private postEmitter: EventEmitter<{ message: string; payload: any }> = new EventEmitter<{ - message: string; - payload: any; - }>(); - private cancellationSources: Map = new Map(); - private notebookIdentity: Uri | undefined; - private notebookType: 'interactive' | 'native' = 'interactive'; - private potentialResource: Uri | undefined; - private sentOpenDocument: boolean = false; - private languageServer: NotebookLanguageServer | undefined; - private resource: Resource; - private interpreter: PythonEnvironment | undefined; - - constructor( - @inject(IWorkspaceService) private workspaceService: IWorkspaceService, - @inject(IDataScienceFileSystem) private fs: IDataScienceFileSystem, - @inject(INotebookProvider) private notebookProvider: INotebookProvider, - @inject(IInterpreterService) private interpreterService: IInterpreterService, - @inject(JupyterExtensionIntegration) private jupyterApiProvider: JupyterExtensionIntegration, - @inject(IJupyterVariables) @named(Identifiers.ALL_VARIABLES) private variableProvider: IJupyterVariables - ) {} - - public dispose() { - if (this.temporaryFile) { - this.temporaryFile.dispose(); - } - if (this.languageServer) { - this.languageServer.dispose(); - this.languageServer = undefined; - } - } - - public onMessage(message: string, payload?: any) { - switch (message) { - case InteractiveWindowMessages.CancelCompletionItemsRequest: - case InteractiveWindowMessages.CancelHoverRequest: - this.dispatchMessage(message, payload, this.handleCancel); - break; - - case InteractiveWindowMessages.ProvideCompletionItemsRequest: - this.dispatchMessage(message, payload, this.handleCompletionItemsRequest); - break; - - case InteractiveWindowMessages.ProvideHoverRequest: - this.dispatchMessage(message, payload, this.handleHoverRequest); - break; - - case InteractiveWindowMessages.ProvideSignatureHelpRequest: - this.dispatchMessage(message, payload, this.handleSignatureHelpRequest); - break; - - case InteractiveWindowMessages.ResolveCompletionItemRequest: - this.dispatchMessage(message, payload, this.handleResolveCompletionItemRequest); - break; - - case InteractiveWindowMessages.UpdateModel: - this.dispatchMessage(message, payload, this.update); - break; - - case InteractiveWindowMessages.RestartKernel: - this.dispatchMessage(message, payload, this.restartKernel); - break; - - case InteractiveWindowMessages.NotebookIdentity: - this.dispatchMessage(message, payload, this.setIdentity); - break; - - case InteractiveWindowMessages.NotebookExecutionActivated: - this.dispatchMessage(message, payload, this.updateIdentity); - break; - - case InteractiveWindowMessages.LoadAllCellsComplete: - this.dispatchMessage(message, payload, this.loadAllCells); - break; - - default: - break; - } - } - - public getDocument(resource?: Uri): Promise { - if (!this.documentPromise) { - this.documentPromise = createDeferred(); - - // Create our dummy document. Compute a file path for it. - if (this.workspaceService.rootPath || resource) { - const dir = resource ? path.dirname(resource.fsPath) : this.workspaceService.rootPath!; - const dummyFilePath = path.join(dir, HiddenFileFormatString.format(uuid().replace(/-/g, ''))); - this.documentPromise.resolve(new IntellisenseDocument(dummyFilePath)); - } else { - this.fs - .createTemporaryLocalFile('.py') - .then((t) => { - this.temporaryFile = t; - const dummyFilePath = this.temporaryFile.filePath; - this.documentPromise!.resolve(new IntellisenseDocument(dummyFilePath)); - }) - .catch((e) => { - this.documentPromise!.reject(e); - }); - } - } - - return this.documentPromise.promise; - } - - protected async getLanguageServer(token: CancellationToken): Promise { - // Resource should be our potential resource if its set. Otherwise workspace root - const resource = - this.potentialResource || - (this.workspaceService.rootPath ? Uri.parse(this.workspaceService.rootPath) : undefined); - - // Interpreter should be the interpreter currently active in the notebook - const activeNotebook = await this.getNotebook(token); - const interpreter = activeNotebook - ? activeNotebook.getMatchingInterpreter() - : await this.interpreterService.getActiveInterpreter(resource); - - const newPath = resource; - const oldPath = this.resource; - - // See if the resource or the interpreter are different - if ( - (newPath && !oldPath) || - (newPath && oldPath && !this.fs.arePathsSame(newPath, oldPath)) || - interpreter?.path !== this.interpreter?.path || - this.languageServer === undefined - ) { - this.resource = resource; - this.interpreter = interpreter; - - // Get an instance of the language server (so we ref count it ) - try { - const languageServer = await NotebookLanguageServer.create( - this.jupyterApiProvider, - resource, - interpreter - ); - - // Dispose of our old language service - this.languageServer?.dispose(); - - // This new language server does not know about our document, so tell it. - const document = await this.getDocument(); - if (document && languageServer) { - // If we already sent an open document, that means we need to send both the open and - // the new changes - if (this.sentOpenDocument) { - languageServer.sendOpen(document); - languageServer.sendChanges(document, document.getFullContentChanges()); - } else { - this.sentOpenDocument = true; - languageServer.sendOpen(document); - } - } - - // Save the ref. - this.languageServer = languageServer; - } catch (e) { - traceError(e); - } - } - return this.languageServer; - } - - protected async provideCompletionItems( - position: monacoEditor.Position, - context: monacoEditor.languages.CompletionContext, - cellId: string, - token: CancellationToken - ): Promise { - const [languageServer, document] = await Promise.all([this.getLanguageServer(token), this.getDocument()]); - if (languageServer && document) { - const docPos = document.convertToDocumentPosition(cellId, position.lineNumber, position.column); - const result = await languageServer.provideCompletionItems(document, docPos, token, context); - if (result) { - return convertToMonacoCompletionList(result, true); - } - } - - return { - suggestions: [], - incomplete: false - }; - } - - protected async provideHover( - position: monacoEditor.Position, - wordAtPosition: string | undefined, - cellId: string, - token: CancellationToken - ): Promise { - const [languageServer, document, variableHover] = await Promise.all([ - this.getLanguageServer(token), - this.getDocument(), - this.getVariableHover(wordAtPosition, token) - ]); - if (!variableHover && languageServer && document) { - const docPos = document.convertToDocumentPosition(cellId, position.lineNumber, position.column); - const [lsResult, jupyterResult] = await Promise.all([ - languageServer.provideHover(document, docPos, token), - Promise.race([ - this.provideJupyterHover(position, cellId, token), - sleep(Settings.IntellisenseTimeout).then(() => undefined) - ]) - ]); - const jupyterHover = jupyterResult ? convertToMonacoHover(jupyterResult) : undefined; - const lsHover = lsResult ? convertToMonacoHover(lsResult) : undefined; - // If lsHover is not valid or it is not a callable with hints, - // while the jupyter hover is a callable with hint, - // we prefer to use jupyterHover which provides better callable hints from jupyter kernel. - const preferJupyterHover = - jupyterHover && - jupyterHover.contents[0] && - this.isCallableWithGoodHint(jupyterHover.contents[0].value) && - (!lsHover || !lsHover.contents[0] || !this.isCallableWithGoodHint(lsHover.contents[0].value)); - if (preferJupyterHover && jupyterHover) { - return jupyterHover; - } else if (lsHover) { - return lsHover; - } - } else if (variableHover) { - return convertToMonacoHover(variableHover); - } - - return { - contents: [] - }; - } - - protected async provideSignatureHelp( - position: monacoEditor.Position, - context: monacoEditor.languages.SignatureHelpContext, - cellId: string, - token: CancellationToken - ): Promise { - const [languageServer, document] = await Promise.all([this.getLanguageServer(token), this.getDocument()]); - if (languageServer && document) { - const docPos = document.convertToDocumentPosition(cellId, position.lineNumber, position.column); - const result = await languageServer.provideSignatureHelp( - document, - docPos, - token, - context as SignatureHelpContext - ); - if (result) { - return convertToMonacoSignatureHelp(result); - } - } - - return { - signatures: [], - activeParameter: 0, - activeSignature: 0 - }; - } - - protected async resolveCompletionItem( - position: monacoEditor.Position, - item: monacoEditor.languages.CompletionItem, - cellId: string, - token: CancellationToken - ): Promise { - const [languageServer, document] = await Promise.all([this.getLanguageServer(token), this.getDocument()]); - if (languageServer && document) { - const vscodeCompItem: CompletionItem = convertToVSCodeCompletionItem(item); - - // Needed by Jedi in completionSource.ts to resolve the item - const docPos = document.convertToDocumentPosition(cellId, position.lineNumber, position.column); - (vscodeCompItem as any)._documentPosition = { document, position: docPos }; - - const result = await languageServer.resolveCompletionItem(vscodeCompItem, token); - if (result) { - // Convert expects vclc completion item, but takes both vclc and vscode items so just cast here - return convertToMonacoCompletionItem(result as vscodeLanguageClient.CompletionItem, true); - } - } - - // If we can't fill in the extra info, just return the item - return item; - } - - protected async handleChanges( - document: IntellisenseDocument, - changes: TextDocumentContentChangeEvent[] - ): Promise { - // For the dot net language server, we have to send extra data to the language server - if (document) { - // Broadcast an update to the language server - const languageServer = await this.getLanguageServer(CancellationToken.None); - if (languageServer) { - if (!this.sentOpenDocument) { - this.sentOpenDocument = true; - return languageServer.sendOpen(document); - } else { - return languageServer.sendChanges(document, changes); - } - } - } - } - - private isCallableWithGoodHint(markdown: string): boolean { - // Check whether the markdown is a callable with the hint that is not (*args, **kwargs) - const match = CallableRegex.exec(markdown); - return match !== null && match[1] !== GeneralCallableSignature; - } - - private convertDocMarkDown(doc: string): string { - // For the argument definitions (Starts with :param/:type/:return), to make markdown works well, we need to: - // 1. Add one more line break; - // 2. Replace '_' with '\_'; - const docLines = doc.splitLines({ trim: false, removeEmptyEntries: false }); - return docLines.map((line) => (line.startsWith(':') ? `\n${line.replace(/_/g, '\\_')}` : line)).join('\n'); - } - - private async provideJupyterHover( - position: monacoEditor.Position, - cellId: string, - token: CancellationToken - ): Promise { - // Currently we only get the callable information from jupyter, - // this aims to handle the case that language server cannot well recognize the dynamically created callables. - const callable = await this.getJupyterCallableInspectResult(position, cellId, token); - if (callable) { - const signatureMarkdown = `\`\`\`python\n(${callable.type}) ${callable.name}: ${callable.signature}\n\`\`\``; - const docMarkdown = this.convertDocMarkDown(callable.doc); - const result = new MarkdownString(`${signatureMarkdown}\n\n${docMarkdown}`); - return { contents: [result] }; - } - return undefined; - } - - private dispatchMessage( - _message: T, - payload: any, - handler: (args: M[T]) => void - ) { - const args = payload as M[T]; - handler.bind(this)(args); - } - - private postResponse(type: T, payload?: M[T]): void { - const response = payload as any; - if (response && response.id) { - const cancelSource = this.cancellationSources.get(response.id); - if (cancelSource) { - cancelSource.dispose(); - this.cancellationSources.delete(response.id); - } - } - this.postEmitter.fire({ message: type.toString(), payload }); - } - - private handleCancel(request: ICancelIntellisenseRequest) { - const cancelSource = this.cancellationSources.get(request.requestId); - if (cancelSource) { - cancelSource.cancel(); - cancelSource.dispose(); - this.cancellationSources.delete(request.requestId); - } - } - - private handleCompletionItemsRequest(request: IProvideCompletionItemsRequest) { - // Create a cancellation source. We'll use this for our sub class request and a jupyter one - const cancelSource = new CancellationTokenSource(); - this.cancellationSources.set(request.requestId, cancelSource); - - const getCompletions = async (): Promise => { - const emptyList: monacoEditor.languages.CompletionList = { - dispose: noop, - incomplete: false, - suggestions: [] - }; - - const lsCompletions = this.provideCompletionItems( - request.position, - request.context, - request.cellId, - cancelSource.token - ); - - const jupyterCompletions = this.provideJupyterCompletionItems( - request.position, - request.context, - request.cellId, - cancelSource.token - ); - - // Capture telemetry for each of the two providers. - // Telemetry will be used to improve how we handle intellisense to improve response times for code completion. - // NOTE: If this code is around after a few months, telemetry isn't used, or we don't need it anymore. - // I.e. delete this code. - sendTelemetryWhenDone(Telemetry.CompletionTimeFromLS, lsCompletions); - sendTelemetryWhenDone(Telemetry.CompletionTimeFromJupyter, jupyterCompletions); - - return this.combineCompletions( - await Promise.all([ - // Ensure we wait for a result from language server (assumption is LS is faster). - // Telemetry will prove/disprove this assumption and we'll change this code accordingly. - lsCompletions, - // Wait for a max of n ms before ignoring results from jupyter (jupyter completion is generally slower). - Promise.race([jupyterCompletions, sleep(Settings.IntellisenseTimeout).then(() => emptyList)]) - ]) - ); - }; - - // Combine all of the results together. - this.postTimedResponse([getCompletions()], InteractiveWindowMessages.ProvideCompletionItemsResponse, (c) => { - const list = this.combineCompletions(c); - return { list, requestId: request.requestId }; - }); - } - - private handleResolveCompletionItemRequest(request: IResolveCompletionItemRequest) { - // Create a cancellation source. We'll use this for our sub class request and a jupyter one - const cancelSource = new CancellationTokenSource(); - this.cancellationSources.set(request.requestId, cancelSource); - - // Combine all of the results together. - this.postTimedResponse( - [this.resolveCompletionItem(request.position, request.item, request.cellId, cancelSource.token)], - InteractiveWindowMessages.ResolveCompletionItemResponse, - (c) => { - if (c && c[0]) { - return { item: c[0], requestId: request.requestId }; - } else { - return { item: request.item, requestId: request.requestId }; - } - } - ); - } - - private handleHoverRequest(request: IProvideHoverRequest) { - const cancelSource = new CancellationTokenSource(); - this.cancellationSources.set(request.requestId, cancelSource); - this.postTimedResponse( - [this.provideHover(request.position, request.wordAtPosition, request.cellId, cancelSource.token)], - InteractiveWindowMessages.ProvideHoverResponse, - (h) => { - if (h && h[0]) { - return { hover: h[0]!, requestId: request.requestId }; - } else { - return { hover: { contents: [] }, requestId: request.requestId }; - } - } - ); - } - - private convertCallableInspectResult(text: string) { - // This method will parse the inspect result from jupyter and get the following values of a callable: - // Name, Type (function or method), Signature, Documentation - - const docMatch = DocStringRegex.exec(text); - // Variable type will be used in hover result, it could be function/method - const typeMatch = TypeRegex.exec(text); - - const signatureTextMatch = SignatureTextRegex.exec(text); - // The signature text returned by jupyter contains escape sequences, we need to remove them. - // See https://en.wikipedia.org/wiki/ANSI_escape_code#Escape_sequences - const signatureText = signatureTextMatch ? signatureTextMatch[1].replace(/\x1b\[[;\d]+m/g, '') : ''; - // Use this to get different parts of the signature: 1: Callable name, 2: Callable signature - const signatureMatch = SignatureRegex.exec(signatureText); - - if (docMatch && typeMatch && signatureMatch) { - return { - name: signatureMatch[1], - type: typeMatch[1], - signature: signatureMatch[2], - doc: docMatch[1] - }; - } - return undefined; - } - - private async getJupyterCallableInspectResult( - position: monacoEditor.Position, - cellId: string, - cancelToken: CancellationToken - ) { - try { - const [activeNotebook, document] = await Promise.all([this.getNotebook(cancelToken), this.getDocument()]); - if (activeNotebook && document) { - const data = document.getCellData(cellId); - if (data) { - const offsetInCode = this.getOffsetInCode(data.text, position); - const jupyterResults = await activeNotebook.inspect(data.text, offsetInCode, cancelToken); - if (jupyterResults && jupyterResults.hasOwnProperty('text/plain')) { - return this.convertCallableInspectResult((jupyterResults as any)['text/plain'].toString()); - } - } - } - } catch (e) { - if (!(e instanceof CancellationError)) { - traceWarning(e); - } - } - return undefined; - } - - private async provideJupyterSignatureHelp( - position: monacoEditor.Position, - cellId: string, - cancelToken: CancellationToken - ): Promise { - const callable = await this.getJupyterCallableInspectResult(position, cellId, cancelToken); - let signatures: SignatureInformation[] = []; - if (callable) { - const signatureInfo: SignatureInformation = { - label: callable.signature, - documentation: callable.doc, - parameters: [] - }; - signatures = [signatureInfo]; - } - return { - signatures: signatures, - activeParameter: 0, - activeSignature: 0 - }; - } - - private getOffsetInCode(text: string, position: monacoEditor.Position) { - const lines = text.splitLines({ trim: false, removeEmptyEntries: false }); - return lines.reduce((a: number, c: string, i: number) => { - if (i < position.lineNumber - 1) { - return a + c.length + 1; - } else if (i === position.lineNumber - 1) { - return a + position.column - 1; - } else { - return a; - } - }, 0); - } - - private async provideJupyterCompletionItems( - position: monacoEditor.Position, - _context: monacoEditor.languages.CompletionContext, - cellId: string, - cancelToken: CancellationToken - ): Promise { - try { - const [activeNotebook, document] = await Promise.all([this.getNotebook(cancelToken), this.getDocument()]); - if (activeNotebook && document) { - const data = document.getCellData(cellId); - - if (data) { - const offsetInCode = this.getOffsetInCode(data.text, position); - const jupyterResults = await activeNotebook.getCompletion(data.text, offsetInCode, cancelToken); - if (jupyterResults && jupyterResults.matches) { - const filteredMatches = this.filterJupyterMatches(document, jupyterResults, cellId, position); - - const baseOffset = data.offset; - const basePosition = document.positionAt(baseOffset); - const startPosition = document.positionAt(jupyterResults.cursor.start + baseOffset); - const endPosition = document.positionAt(jupyterResults.cursor.end + baseOffset); - const range: monacoEditor.IRange = { - startLineNumber: startPosition.line + 1 - basePosition.line, // monaco is 1 based - startColumn: startPosition.character + 1, - endLineNumber: endPosition.line + 1 - basePosition.line, - endColumn: endPosition.character + 1 - }; - return { - suggestions: convertStringsToSuggestions(filteredMatches, range, jupyterResults.metadata), - incomplete: false - }; - } - } - } - } catch (e) { - if (!(e instanceof CancellationError)) { - traceWarning(e); - } - } - - return { - suggestions: [], - incomplete: false - }; - } - - // The suggestions that the kernel is giving always include magic commands. That is confusing to the user. - // This function is called by provideJupyterCompletionItems to filter those magic commands when not in an empty line of code. - private filterJupyterMatches( - document: IntellisenseDocument, - jupyterResults: INotebookCompletion, - cellId: string, - position: monacoEditor.Position - ) { - // If the line we're analyzing is empty or a whitespace, we filter out the magic commands - // as its confusing to see them appear after a . or inside (). - const pos = document.convertToDocumentPosition(cellId, position.lineNumber, position.column); - const line = document.lineAt(pos); - return line.isEmptyOrWhitespace - ? jupyterResults.matches - : jupyterResults.matches.filter((match) => !match.startsWith('%')); - } - - private postTimedResponse( - promises: Promise[], - message: T, - formatResponse: (val: (R | null)[]) => M[T] - ) { - // Time all of the promises to make sure they don't take too long. - // Even if LS or Jupyter doesn't complete within e.g. 30s, then we should return an empty response (no point waiting that long). - const timed = promises.map((p) => waitForPromise(p, Settings.MaxIntellisenseTimeout)); - - // Wait for all of of the timings. - const all = Promise.all(timed); - all.then((r) => { - this.postResponse(message, formatResponse(r)); - }).catch((_e) => { - this.postResponse(message, formatResponse([null])); - }); - } - - private combineCompletions( - list: (monacoEditor.languages.CompletionList | null)[] - ): monacoEditor.languages.CompletionList { - // Note to self. We're eliminating duplicates ourselves. The alternative would be to - // have more than one intellisense provider at the monaco editor level and return jupyter - // results independently. Maybe we switch to this when jupyter resides on the react side. - const uniqueSuggestions: Map = new Map< - string, - monacoEditor.languages.CompletionItem - >(); - list.forEach((c) => { - if (c) { - c.suggestions.forEach((s) => { - if (!uniqueSuggestions.has(s.insertText)) { - uniqueSuggestions.set(s.insertText, s); - } - }); - } - }); - - return { - suggestions: Array.from(uniqueSuggestions.values()), - incomplete: false - }; - } - - private handleSignatureHelpRequest(request: IProvideSignatureHelpRequest) { - const cancelSource = new CancellationTokenSource(); - this.cancellationSources.set(request.requestId, cancelSource); - - const getSignatureHelp = async (): Promise => { - const jupyterSignatureHelp = this.provideJupyterSignatureHelp( - request.position, - request.cellId, - cancelSource.token - ); - - const lsSignatureHelp = this.provideSignatureHelp( - request.position, - request.context, - request.cellId, - cancelSource.token - ); - - const defaultHelp = { - signatures: [], - activeParameter: 0, - activeSignature: 0 - }; - - const [lsHelp, jupyterHelp] = await Promise.all([ - lsSignatureHelp, - Promise.race([jupyterSignatureHelp, sleep(Settings.IntellisenseTimeout).then(() => defaultHelp)]) - ]); - // Only when language server result is not valid or the signature is (*args, **kwargs) , we prefer to use the result from jupyter. - const preferJupyterHelp = - (!lsHelp.signatures[0] || lsHelp.signatures[0].label.startsWith(GeneralCallableSignature)) && - jupyterHelp.signatures[0]; - return preferJupyterHelp ? jupyterHelp : lsHelp; - }; - - this.postTimedResponse([getSignatureHelp()], InteractiveWindowMessages.ProvideSignatureHelpResponse, (s) => { - if (s && s[0]) { - return { signatureHelp: s[0]!, requestId: request.requestId }; - } else { - return { - signatureHelp: { signatures: [], activeParameter: 0, activeSignature: 0 }, - requestId: request.requestId - }; - } - }); - } - - private async update(request: NotebookModelChange): Promise { - // See where this request is coming from - switch (request.source) { - case 'redo': - case 'user': - return this.handleRedo(request); - case 'undo': - return this.handleUndo(request); - default: - break; - } - } - - private convertToDocCells(cells: ICell[]): { code: string; id: string }[] { - return cells - .filter((c) => c.data.cell_type === 'code') - .map((c) => { - return { code: concatMultilineString(c.data.source), id: c.id }; - }); - } - - private async handleUndo(request: NotebookModelChange): Promise { - const document = await this.getDocument(); - let changes: TextDocumentContentChangeEvent[] = []; - switch (request.kind) { - case 'clear': - // This one can be ignored, it only clears outputs - break; - case 'edit': - changes = document.editCell(request.reverse, request.id); - break; - case 'add': - case 'insert': - changes = document.remove(request.cell.id); - break; - case 'modify': - // This one can be ignored. it's only used for updating cell finished state. - break; - case 'remove': - changes = document.insertCell( - request.cell.id, - concatMultilineString(request.cell.data.source), - request.index - ); - break; - case 'remove_all': - changes = document.reloadCells(this.convertToDocCells(request.oldCells)); - break; - case 'swap': - changes = document.swap(request.secondCellId, request.firstCellId); - break; - case 'version': - // Also ignored. updates version which we don't keep track of. - break; - default: - break; - } - - return this.handleChanges(document, changes); - } - - private async handleRedo(request: NotebookModelChange): Promise { - const document = await this.getDocument(); - let changes: TextDocumentContentChangeEvent[] = []; - switch (request.kind) { - case 'clear': - // This one can be ignored, it only clears outputs - break; - case 'edit': - changes = document.editCell(request.forward, request.id); - break; - case 'add': - changes = document.addCell(request.fullText, request.currentText, request.cell.id); - break; - case 'insert': - changes = document.insertCell( - request.cell.id, - concatMultilineString(request.cell.data.source), - request.codeCellAboveId || request.index - ); - break; - case 'modify': - // This one can be ignored. it's only used for updating cell finished state. - break; - case 'remove': - changes = document.remove(request.cell.id); - break; - case 'remove_all': - changes = document.removeAll(); - break; - case 'swap': - changes = document.swap(request.firstCellId, request.secondCellId); - break; - case 'version': - // Also ignored. updates version which we don't keep track of. - break; - default: - break; - } - - return this.handleChanges(document, changes); - } - - private async loadAllCells(payload: ILoadAllCells) { - const document = await this.getDocument(); - if (document) { - const changes = document.loadAllCells( - payload.cells - .filter((c) => c.data.cell_type === 'code') - .map((cell) => { - return { - code: concatMultilineString(cell.data.source), - id: cell.id - }; - }), - this.notebookType - ); - - await this.handleChanges(document, changes); - } - } - - private async restartKernel(): Promise { - // This is the one that acts like a reset if this is the interactive window - const document = await this.getDocument(); - if (document && document.isReadOnly) { - this.sentOpenDocument = false; - const changes = document.removeAllCells(); - return this.handleChanges(document, changes); - } - } - - private setIdentity(identity: INotebookIdentity) { - this.notebookIdentity = identity.resource; - this.potentialResource = - identity.resource.scheme !== Identifiers.HistoryPurpose ? identity.resource : undefined; - this.notebookType = identity.type; - } - - private updateIdentity(identity: INotebookIdentity & { owningResource: Resource }) { - this.potentialResource = identity.owningResource ? identity.owningResource : this.potentialResource; - } - - private async getNotebook(token: CancellationToken): Promise { - return this.notebookIdentity - ? this.notebookProvider.getOrCreateNotebook({ identity: this.notebookIdentity, getOnly: true, token }) - : undefined; - } - - private async getVariableHover( - wordAtPosition: string | undefined, - token: CancellationToken - ): Promise { - if (wordAtPosition) { - const notebook = await this.getNotebook(token); - if (notebook) { - try { - const value = await this.variableProvider.getMatchingVariable(notebook, wordAtPosition, token); - if (value) { - return { - contents: [`${wordAtPosition}: ${value.type} = ${value.value}`] - }; - } - } catch (exc) { - traceError(`Exception attempting to retrieve hover for variables`, exc); - } - } - } - } -} diff --git a/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts b/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts deleted file mode 100644 index 33d635b17e21..000000000000 --- a/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { - CancellationToken, - CompletionContext, - CompletionItem, - Disposable, - Position, - SignatureHelpContext, - TextDocument, - TextDocumentContentChangeEvent -} from 'vscode'; -import * as c2p from 'vscode-languageclient/lib/common/codeConverter'; -import * as p2c from 'vscode-languageclient/lib/common/protocolConverter'; -import * as vscodeLanguageClient from 'vscode-languageclient/node'; -import * as lsp from 'vscode-languageserver-protocol'; -import { ILanguageServerConnection } from '../../../activation/types'; -import { Resource } from '../../../common/types'; -import { createDeferred } from '../../../common/utils/async'; -import { PythonEnvironment } from '../../../pythonEnvironments/info'; -import { ILanguageServer, JupyterExtensionIntegration } from '../../api/jupyterIntegration'; - -/** - * Class that wraps a language server for use by webview based notebooks - */ -export class NotebookLanguageServer implements Disposable { - private code2ProtocolConverter = c2p.createConverter(); - private protocol2CodeConverter = p2c.createConverter(); - private connection: ILanguageServerConnection; - private capabilities: lsp.ServerCapabilities; - private disposeConnection: () => void; - private constructor(ls: ILanguageServer) { - this.connection = ls.connection; - this.capabilities = ls.capabilities; - this.disposeConnection = ls.dispose.bind(ls); - } - - public static async create( - jupyterApiProvider: JupyterExtensionIntegration, - resource: Resource, - interpreter: PythonEnvironment | undefined - ): Promise { - // Create a server wrapper if we can get a connection to a language server - const deferred = createDeferred(); - jupyterApiProvider.registerApi({ - registerPythonApi: (api) => { - api.getLanguageServer(interpreter ? interpreter : resource) - .then((c) => { - if (c) { - deferred.resolve(new NotebookLanguageServer(c)); - } else { - deferred.resolve(undefined); - } - }) - .catch(deferred.reject); - } - }); - return deferred.promise; - } - - public dispose() { - this.disposeConnection(); - } - - public sendOpen(document: TextDocument) { - this.connection.sendNotification( - vscodeLanguageClient.DidOpenTextDocumentNotification.type, - this.code2ProtocolConverter.asOpenTextDocumentParams(document) - ); - } - - public sendChanges(document: TextDocument, changes: TextDocumentContentChangeEvent[]) { - // If the language client doesn't support incremental, just send the whole document - if (this.textDocumentSyncKind === vscodeLanguageClient.TextDocumentSyncKind.Full) { - this.connection.sendNotification( - vscodeLanguageClient.DidChangeTextDocumentNotification.type, - this.code2ProtocolConverter.asChangeTextDocumentParams(document) - ); - } else { - this.connection.sendNotification( - vscodeLanguageClient.DidChangeTextDocumentNotification.type, - this.code2ProtocolConverter.asChangeTextDocumentParams({ - document, - contentChanges: changes - }) - ); - } - } - - public async provideCompletionItems( - document: TextDocument, - position: Position, - token: CancellationToken, - context: CompletionContext - ) { - const args = this.code2ProtocolConverter.asCompletionParams(document, position, context); - const result = await this.connection.sendRequest(vscodeLanguageClient.CompletionRequest.type, args, token); - if (result) { - return this.protocol2CodeConverter.asCompletionResult(result); - } - } - - public async provideSignatureHelp( - document: TextDocument, - position: Position, - token: CancellationToken, - _context: SignatureHelpContext - ) { - const args: vscodeLanguageClient.TextDocumentPositionParams = { - textDocument: this.code2ProtocolConverter.asTextDocumentIdentifier(document), - position: this.code2ProtocolConverter.asPosition(position) - }; - const result = await this.connection.sendRequest(vscodeLanguageClient.SignatureHelpRequest.type, args, token); - if (result) { - return this.protocol2CodeConverter.asSignatureHelp(result); - } - } - - public async provideHover(document: TextDocument, position: Position, token: CancellationToken) { - const args: vscodeLanguageClient.TextDocumentPositionParams = { - textDocument: this.code2ProtocolConverter.asTextDocumentIdentifier(document), - position: this.code2ProtocolConverter.asPosition(position) - }; - const result = await this.connection.sendRequest(vscodeLanguageClient.HoverRequest.type, args, token); - if (result) { - return this.protocol2CodeConverter.asHover(result); - } - } - - public async resolveCompletionItem(item: CompletionItem, token: CancellationToken) { - const result = await this.connection.sendRequest( - vscodeLanguageClient.CompletionResolveRequest.type, - this.code2ProtocolConverter.asCompletionItem(item), - token - ); - if (result) { - return this.protocol2CodeConverter.asCompletionItem(result); - } - } - - private get textDocumentSyncKind(): vscodeLanguageClient.TextDocumentSyncKind { - if (this.capabilities.textDocumentSync) { - const syncOptions = this.capabilities.textDocumentSync; - const syncKind = - syncOptions !== undefined && syncOptions.hasOwnProperty('change') - ? (syncOptions as vscodeLanguageClient.TextDocumentSyncOptions).change - : syncOptions; - if (syncKind !== undefined) { - return syncKind as vscodeLanguageClient.TextDocumentSyncKind; - } - } - - // Default is full if not provided - return vscodeLanguageClient.TextDocumentSyncKind.Full; - } -} diff --git a/src/client/datascience/interactive-common/intellisense/wordHelper.ts b/src/client/datascience/interactive-common/intellisense/wordHelper.ts deleted file mode 100644 index e8ae47b1bd06..000000000000 --- a/src/client/datascience/interactive-common/intellisense/wordHelper.ts +++ /dev/null @@ -1,170 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// Borrowed this from the vscode source. From here: -// src\vs\editor\common\model\wordHelper.ts - -export interface IWordAtPosition { - readonly word: string; - readonly startColumn: number; - readonly endColumn: number; -} - -export const USUAL_WORD_SEPARATORS = '`~!@#$%^&*()-=+[{]}\\|;:\'",.<>/?'; - -/** - * Create a word definition regular expression based on default word separators. - * Optionally provide allowed separators that should be included in words. - * - * The default would look like this: - * /(-?\d*\.\d\w*)|([^\`\~\!\@\#\$\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g - */ -function createWordRegExp(allowInWords: string = ''): RegExp { - let source = '(-?\\d*\\.\\d\\w*)|([^'; - for (const sep of USUAL_WORD_SEPARATORS) { - if (allowInWords.indexOf(sep) >= 0) { - continue; - } - source += `\\${sep}`; - } - source += '\\s]+)'; - return new RegExp(source, 'g'); -} - -// catches numbers (including floating numbers) in the first group, and alphanum in the second -export const DEFAULT_WORD_REGEXP = createWordRegExp(); - -export function ensureValidWordDefinition(wordDefinition?: RegExp | null): RegExp { - let result: RegExp = DEFAULT_WORD_REGEXP; - - if (wordDefinition && wordDefinition instanceof RegExp) { - if (!wordDefinition.global) { - let flags = 'g'; - if (wordDefinition.ignoreCase) { - flags += 'i'; - } - if (wordDefinition.multiline) { - flags += 'm'; - } - // tslint:disable-next-line: no-any - if ((wordDefinition as any).unicode) { - flags += 'u'; - } - result = new RegExp(wordDefinition.source, flags); - } else { - result = wordDefinition; - } - } - - result.lastIndex = 0; - - return result; -} - -function getWordAtPosFast( - column: number, - wordDefinition: RegExp, - text: string, - textOffset: number -): IWordAtPosition | null { - // find whitespace enclosed text around column and match from there - - const pos = column - 1 - textOffset; - const start = text.lastIndexOf(' ', pos - 1) + 1; - - wordDefinition.lastIndex = start; - let match: RegExpMatchArray | null = wordDefinition.exec(text); - while (match) { - const matchIndex = match.index || 0; - if (matchIndex <= pos && wordDefinition.lastIndex >= pos) { - return { - word: match[0], - startColumn: textOffset + 1 + matchIndex, - endColumn: textOffset + 1 + wordDefinition.lastIndex - }; - } - match = wordDefinition.exec(text); - } - - return null; -} - -function getWordAtPosSlow( - column: number, - wordDefinition: RegExp, - text: string, - textOffset: number -): IWordAtPosition | null { - // matches all words starting at the beginning - // of the input until it finds a match that encloses - // the desired column. slow but correct - - const pos = column - 1 - textOffset; - wordDefinition.lastIndex = 0; - - let match: RegExpMatchArray | null = wordDefinition.exec(text); - while (match) { - const matchIndex = match.index || 0; - if (matchIndex > pos) { - // |nW -> matched only after the pos - return null; - } else if (wordDefinition.lastIndex >= pos) { - // W|W -> match encloses pos - return { - word: match[0], - startColumn: textOffset + 1 + matchIndex, - endColumn: textOffset + 1 + wordDefinition.lastIndex - }; - } - match = wordDefinition.exec(text); - } - - return null; -} - -export function getWordAtText( - column: number, - wordDefinition: RegExp, - text: string, - textOffset: number -): IWordAtPosition | null { - // if `words` can contain whitespace character we have to use the slow variant - // otherwise we use the fast variant of finding a word - wordDefinition.lastIndex = 0; - const match = wordDefinition.exec(text); - if (!match) { - return null; - } - // todo@joh the `match` could already be the (first) word - const ret = - match[0].indexOf(' ') >= 0 - ? // did match a word which contains a space character -> use slow word find - getWordAtPosSlow(column, wordDefinition, text, textOffset) - : // sane word definition -> use fast word find - getWordAtPosFast(column, wordDefinition, text, textOffset); - - // both (getWordAtPosFast and getWordAtPosSlow) leave the wordDefinition-RegExp - // in an undefined state and to not confuse other users of the wordDefinition - // we reset the lastIndex - wordDefinition.lastIndex = 0; - - return ret; -} - -export function regExpLeadsToEndlessLoop(regexp: RegExp): boolean { - // Exit early if it's one of these special cases which are meant to match - // against an empty string - if (regexp.source === '^' || regexp.source === '^$' || regexp.source === '$' || regexp.source === '^\\s*$') { - return false; - } - - // We check against an empty string. If the regular expression doesn't advance - // (e.g. ends in an endless loop) it will match an empty string. - const match = regexp.exec(''); - // tslint:disable-next-line: no-any - return !!(match && regexp.lastIndex === 0); -} - -export const DefaultWordPattern = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g; diff --git a/src/client/datascience/interactive-common/interactiveBase.ts b/src/client/datascience/interactive-common/interactiveBase.ts deleted file mode 100644 index 01b55deed731..000000000000 --- a/src/client/datascience/interactive-common/interactiveBase.ts +++ /dev/null @@ -1,1571 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; - -import type { nbformat } from '@jupyterlab/coreutils'; -import type { KernelMessage } from '@jupyterlab/services'; -import * as os from 'os'; -import * as path from 'path'; -import * as uuid from 'uuid/v4'; -import { - CancellationToken, - commands, - ConfigurationTarget, - Event, - EventEmitter, - Memento, - Position, - Range, - Selection, - TextEditor, - Uri, - ViewColumn -} from 'vscode'; -import { Disposable } from 'vscode-jsonrpc'; -import { ServerStatus } from '../../../datascience-ui/interactive-common/mainState'; -import { - IApplicationShell, - ICommandManager, - IDocumentManager, - ILiveShareApi, - IWebviewPanelProvider, - IWorkspaceService -} from '../../common/application/types'; -import { CancellationError } from '../../common/cancellation'; -import { EXTENSION_ROOT_DIR, isTestExecution, PYTHON_LANGUAGE } from '../../common/constants'; -import { RunByLine } from '../../common/experiments/groups'; -import { traceError, traceInfo, traceWarning } from '../../common/logger'; - -import { isNil } from 'lodash'; -import { IConfigurationService, IDisposableRegistry, IExperimentsManager } from '../../common/types'; -import { createDeferred, Deferred } from '../../common/utils/async'; -import * as localize from '../../common/utils/localize'; -import { isUntitledFile, noop } from '../../common/utils/misc'; -import { StopWatch } from '../../common/utils/stopWatch'; -import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; -import { generateCellRangesFromDocument } from '../cellFactory'; -import { CellMatcher } from '../cellMatcher'; -import { addToUriList, translateKernelLanguageToMonaco } from '../common'; -import { Commands, Identifiers, Settings, Telemetry } from '../constants'; -import { ColumnWarningSize, IDataViewerFactory } from '../data-viewing/types'; -import { - IAddedSysInfo, - ICopyCode, - IGotoCode, - IInteractiveWindowMapping, - INotebookIdentity, - InteractiveWindowMessages, - IReExecuteCells, - IRemoteAddCode, - IRemoteReexecuteCode, - IShowDataViewer, - ISubmitNewCell, - SysInfoReason, - VariableExplorerStateKeys -} from '../interactive-common/interactiveWindowTypes'; -import { JupyterInvalidKernelError } from '../jupyter/jupyterInvalidKernelError'; -import { - getDisplayNameOrNameOfKernelConnection, - getKernelConnectionLanguage, - kernelConnectionMetadataHasKernelModel, - kernelConnectionMetadataHasKernelSpec -} from '../jupyter/kernels/helpers'; -import { JupyterKernelPromiseFailedError } from '../jupyter/kernels/jupyterKernelPromiseFailedError'; -import { KernelSelector } from '../jupyter/kernels/kernelSelector'; -import { KernelConnectionMetadata } from '../jupyter/kernels/types'; -import { CssMessages, SharedMessages } from '../messages'; -import { - CellState, - ICell, - ICodeCssGenerator, - IDataScienceErrorHandler, - IDataScienceFileSystem, - IInteractiveBase, - IInteractiveWindowInfo, - IInteractiveWindowListener, - IJupyterDebugger, - IJupyterVariableDataProviderFactory, - IJupyterVariables, - IJupyterVariablesRequest, - IJupyterVariablesResponse, - IMessageCell, - INotebook, - INotebookExporter, - INotebookMetadataLive, - INotebookProvider, - INotebookProviderConnection, - InterruptResult, - IStatusProvider, - IThemeFinder, - WebViewViewChangeEventArgs -} from '../types'; -import { WebviewPanelHost } from '../webviews/webviewPanelHost'; -import { InteractiveWindowMessageListener } from './interactiveWindowMessageListener'; -import { serializeLanguageConfiguration } from './serialization'; - -export abstract class InteractiveBase extends WebviewPanelHost implements IInteractiveBase { - public get notebook(): INotebook | undefined { - return this._notebook; - } - - public get id(): string { - return this._id; - } - - public get onExecutedCode(): Event { - return this.executeEvent.event; - } - public get ready(): Event { - return this.readyEvent.event; - } - - protected abstract get notebookMetadata(): INotebookMetadataLive | undefined; - - protected abstract get notebookIdentity(): INotebookIdentity; - - private unfinishedCells: ICell[] = []; - private restartingKernel: boolean = false; - private perceivedJupyterStartupTelemetryCaptured: boolean = false; - private potentiallyUnfinishedStatus: Disposable[] = []; - private addSysInfoPromise: Deferred | undefined; - private _notebook: INotebook | undefined; - private _id: string; - private executeEvent: EventEmitter = new EventEmitter(); - private connectionAndNotebookPromise: Promise | undefined; - private notebookPromise: Promise | undefined; - private setDarkPromise: Deferred | undefined; - private readyEvent = new EventEmitter(); - - constructor( - private readonly listeners: IInteractiveWindowListener[], - liveShare: ILiveShareApi, - protected applicationShell: IApplicationShell, - protected documentManager: IDocumentManager, - provider: IWebviewPanelProvider, - private disposables: IDisposableRegistry, - cssGenerator: ICodeCssGenerator, - themeFinder: IThemeFinder, - private statusProvider: IStatusProvider, - protected fs: IDataScienceFileSystem, - protected configuration: IConfigurationService, - protected jupyterExporter: INotebookExporter, - workspaceService: IWorkspaceService, - private dataExplorerFactory: IDataViewerFactory, - private jupyterVariableDataProviderFactory: IJupyterVariableDataProviderFactory, - private jupyterVariables: IJupyterVariables, - private jupyterDebugger: IJupyterDebugger, - protected errorHandler: IDataScienceErrorHandler, - protected readonly commandManager: ICommandManager, - protected globalStorage: Memento, - protected workspaceStorage: Memento, - rootPath: string, - scripts: string[], - title: string, - viewColumn: ViewColumn, - experimentsManager: IExperimentsManager, - private readonly notebookProvider: INotebookProvider, - useCustomEditorApi: boolean, - private selector: KernelSelector - ) { - super( - configuration, - provider, - cssGenerator, - themeFinder, - workspaceService, - (c, v, d) => new InteractiveWindowMessageListener(liveShare, c, v, d), - rootPath, - scripts, - title, - viewColumn, - useCustomEditorApi, - experimentsManager.inExperiment(RunByLine.experiment) - ); - - // Create our unique id. We use this to skip messages we send to other interactive windows - this._id = uuid(); - - // Listen for active text editor changes. This is the only way we can tell that we might be needing to gain focus - const handler = this.documentManager.onDidChangeActiveTextEditor(() => this.activating()); - this.disposables.push(handler); - - // For each listener sign up for their post events - this.listeners.forEach((l) => l.postMessage((e) => this.postMessageInternal(e.message, e.payload))); - // Channel for listeners to send messages to the interactive base. - this.listeners.forEach((l) => { - if (l.postInternalMessage) { - l.postInternalMessage((e) => this.onMessage(e.message, e.payload)); - } - }); - - // Tell each listener our identity. Can't do it here though as were in the constructor for the base class - setTimeout(() => { - this.listeners.forEach((l) => - l.onMessage(InteractiveWindowMessages.NotebookIdentity, this.notebookIdentity) - ); - }, 0); - - // When a notebook provider first makes its connection check it to see if we should create a notebook - this.disposables.push( - notebookProvider.onConnectionMade(this.createNotebookIfProviderConnectionExists.bind(this)) - ); - - // When a notebook provider indicates a kernel change, change our UI - this.disposables.push(notebookProvider.onPotentialKernelChanged(this.potentialKernelChanged.bind(this))); - - // When the variable service requests a refresh, refresh our variable list - this.disposables.push(this.jupyterVariables.refreshRequired(this.refreshVariables.bind(this))); - - // If we have already auto started our server then we can go ahead and try to create a notebook on construction - setTimeout(() => { - this.createNotebookIfProviderConnectionExists().ignoreErrors(); - }, 0); - } - - // tslint:disable-next-line: no-any no-empty cyclomatic-complexity max-func-body-length - public onMessage(message: string, payload: any) { - switch (message) { - case InteractiveWindowMessages.ConvertUriForUseInWebViewRequest: - const request = payload as Uri; - const response = { request, response: this.asWebviewUri(request) }; - this.postMessageToListeners(InteractiveWindowMessages.ConvertUriForUseInWebViewResponse, response); - break; - - case InteractiveWindowMessages.Started: - // Send the first settings message - this.onDataScienceSettingsChanged().ignoreErrors(); - - // Send the loc strings (skip during testing as it takes up a lot of memory) - const locStrings = isTestExecution() ? '{}' : localize.getCollectionJSON(); - this.postMessageInternal(SharedMessages.LocInit, locStrings).ignoreErrors(); - this.variableExplorerHeightRequest() - .then((data) => - this.postMessageInternal( - InteractiveWindowMessages.VariableExplorerHeightResponse, - data - ).ignoreErrors() - ) - .catch(); // do nothing - break; - - case InteractiveWindowMessages.GotoCodeCell: - this.handleMessage(message, payload, this.gotoCode); - break; - - case InteractiveWindowMessages.CopyCodeCell: - this.handleMessage(message, payload, this.copyCode); - break; - - case InteractiveWindowMessages.RestartKernel: - this.restartKernel().ignoreErrors(); - break; - - case InteractiveWindowMessages.Interrupt: - this.interruptKernel().ignoreErrors(); - break; - - case InteractiveWindowMessages.SendInfo: - this.handleMessage(message, payload, this.updateContexts); - break; - - case InteractiveWindowMessages.SubmitNewCell: - this.handleMessage(message, payload, this.submitNewCell); - break; - - case InteractiveWindowMessages.ReExecuteCells: - this.handleMessage(message, payload, this.reexecuteCells); - break; - - case InteractiveWindowMessages.Undo: - this.logTelemetry(Telemetry.Undo); - break; - - case InteractiveWindowMessages.Redo: - this.logTelemetry(Telemetry.Redo); - break; - - case InteractiveWindowMessages.ExpandAll: - this.logTelemetry(Telemetry.ExpandAll); - break; - - case InteractiveWindowMessages.CollapseAll: - this.logTelemetry(Telemetry.CollapseAll); - break; - - case InteractiveWindowMessages.VariableExplorerToggle: - this.variableExplorerToggle(payload); - break; - - case InteractiveWindowMessages.SetVariableExplorerHeight: - this.setVariableExplorerHeight(payload).ignoreErrors(); - break; - - case InteractiveWindowMessages.AddedSysInfo: - this.handleMessage(message, payload, this.onAddedSysInfo); - break; - - case InteractiveWindowMessages.RemoteAddCode: - this.handleMessage(message, payload, this.onRemoteAddedCode); - break; - - case InteractiveWindowMessages.RemoteReexecuteCode: - this.handleMessage(message, payload, this.onRemoteReexecuteCode); - break; - - case InteractiveWindowMessages.ShowDataViewer: - this.handleMessage(message, payload, this.showDataViewer); - break; - - case InteractiveWindowMessages.GetVariablesRequest: - this.handleMessage(message, payload, this.requestVariables); - break; - - case InteractiveWindowMessages.LoadTmLanguageRequest: - this.handleMessage(message, payload, this.requestTmLanguage); - break; - - case InteractiveWindowMessages.LoadOnigasmAssemblyRequest: - this.handleMessage(message, payload, this.requestOnigasm); - break; - - case InteractiveWindowMessages.SelectKernel: - this.handleMessage(message, payload, this.selectNewKernel); - break; - - case InteractiveWindowMessages.SelectJupyterServer: - this.handleMessage(message, payload, this.selectServer); - break; - - case InteractiveWindowMessages.OpenSettings: - this.handleMessage(message, payload, this.openSettings); - break; - - case InteractiveWindowMessages.MonacoReady: - this.readyEvent.fire(); - break; - - default: - break; - } - - // Let our listeners handle the message too - this.postMessageToListeners(message, payload); - - // Pass onto our base class. - super.onMessage(message, payload); - - // After our base class handles some stuff, handle it ourselves too. - switch (message) { - case CssMessages.GetCssRequest: - // Update the notebook if we have one: - if (this._notebook) { - this.isDark() - .then((d) => (this._notebook ? this._notebook.setMatplotLibStyle(d) : Promise.resolve())) - .ignoreErrors(); - } - break; - - default: - break; - } - } - - public dispose() { - // Fire ready event in case anything is waiting on it. - this.readyEvent.fire(); - - // Dispose of the web panel. - super.dispose(); - // Tell listeners we're closing. They can decide if they should dispose themselves or not. - this.listeners.forEach((l) => l.onMessage(InteractiveWindowMessages.NotebookClose, this.notebookIdentity)); - this.updateContexts(undefined); - } - - public startProgress() { - this.postMessage(InteractiveWindowMessages.StartProgress).ignoreErrors(); - } - - public stopProgress() { - this.postMessage(InteractiveWindowMessages.StopProgress).ignoreErrors(); - } - - @captureTelemetry(Telemetry.Undo) - public undoCells() { - this.postMessage(InteractiveWindowMessages.Undo).ignoreErrors(); - } - - @captureTelemetry(Telemetry.Redo) - public redoCells() { - this.postMessage(InteractiveWindowMessages.Redo).ignoreErrors(); - } - - @captureTelemetry(Telemetry.DeleteAllCells) - public removeAllCells() { - this.postMessage(InteractiveWindowMessages.DeleteAllCells).ignoreErrors(); - } - - @captureTelemetry(Telemetry.RestartKernel) - public async restartKernel(internal: boolean = false): Promise { - // Only log this if it's user requested restart - if (!internal) { - this.logTelemetry(Telemetry.RestartKernelCommand); - } - - if (this._notebook && !this.restartingKernel) { - this.restartingKernel = true; - this.startProgress(); - - try { - if (await this.shouldAskForRestart()) { - // Ask the user if they want us to restart or not. - const message = localize.DataScience.restartKernelMessage(); - const yes = localize.DataScience.restartKernelMessageYes(); - const dontAskAgain = localize.DataScience.restartKernelMessageDontAskAgain(); - const no = localize.DataScience.restartKernelMessageNo(); - - const v = await this.applicationShell.showInformationMessage(message, yes, dontAskAgain, no); - if (v === dontAskAgain) { - await this.disableAskForRestart(); - await this.restartKernelInternal(); - } else if (v === yes) { - await this.restartKernelInternal(); - } - } else { - await this.restartKernelInternal(); - } - } finally { - this.restartingKernel = false; - this.stopProgress(); - } - } - } - - @captureTelemetry(Telemetry.Interrupt) - public async interruptKernel(): Promise { - if (this._notebook && !this.restartingKernel) { - const status = this.statusProvider.set( - localize.DataScience.interruptKernelStatus(), - true, - undefined, - undefined, - this - ); - - try { - const settings = this.configuration.getSettings(this.owningResource); - const interruptTimeout = settings.datascience.jupyterInterruptTimeout; - - const result = await this._notebook.interruptKernel(interruptTimeout); - status.dispose(); - - // We timed out, ask the user if they want to restart instead. - if (result === InterruptResult.TimedOut && !this.restartingKernel) { - const message = localize.DataScience.restartKernelAfterInterruptMessage(); - const yes = localize.DataScience.restartKernelMessageYes(); - const no = localize.DataScience.restartKernelMessageNo(); - const v = await this.applicationShell.showInformationMessage(message, yes, no); - if (v === yes) { - await this.restartKernelInternal(); - } - } else if (result === InterruptResult.Restarted) { - // Uh-oh, keyboard interrupt crashed the kernel. - this.addSysInfo(SysInfoReason.Interrupt).ignoreErrors(); - } - } catch (err) { - status.dispose(); - traceError(err); - this.applicationShell.showErrorMessage(err); - } - } - } - - @captureTelemetry(Telemetry.CopySourceCode, undefined, false) - public copyCode(args: ICopyCode) { - return this.copyCodeInternal(args.source).catch((err) => { - this.applicationShell.showErrorMessage(err); - }); - } - - public abstract hasCell(id: string): Promise; - - protected onViewStateChanged(args: WebViewViewChangeEventArgs) { - // Only activate if the active editor is empty. This means that - // vscode thinks we are actually supposed to have focus. It would be - // nice if they would more accurately tell us this, but this works for now. - // Essentially the problem is the webPanel.active state doesn't track - // if the focus is supposed to be in the webPanel or not. It only tracks if - // it's been activated. However if there's no active text editor and we're active, we - // can safely attempt to give ourselves focus. This won't actually give us focus if we aren't - // allowed to have it. - if (args.current.active && !args.previous.active) { - this.activating().ignoreErrors(); - } - - // Tell our listeners, they may need to know too - this.listeners.forEach((l) => (l.onViewStateChanged ? l.onViewStateChanged(args) : noop())); - } - - protected async activating() { - // Only activate if the active editor is empty. This means that - // vscode thinks we are actually supposed to have focus. It would be - // nice if they would more accurately tell us this, but this works for now. - // Essentially the problem is the webPanel.active state doesn't track - // if the focus is supposed to be in the webPanel or not. It only tracks if - // it's been activated. However if there's no active text editor and we're active, we - // can safely attempt to give ourselves focus. This won't actually give us focus if we aren't - // allowed to have it. - if (this.viewState.active && !this.documentManager.activeTextEditor) { - // Force the webpanel to reveal and take focus. - await super.show(false); - - // Send this to the react control - await this.postMessage(InteractiveWindowMessages.Activate); - } - } - - // Submits a new cell to the window - protected abstract submitNewCell(info: ISubmitNewCell): void; - - // Re-executes cells already in the window - protected reexecuteCells(_info: IReExecuteCells): void { - // Default is not to do anything. This only works in the native editor - } - - protected abstract updateContexts(info: IInteractiveWindowInfo | undefined): void; - - protected abstract closeBecauseOfFailure(exc: Error): Promise; - - protected abstract updateNotebookOptions(kernelConnection: KernelConnectionMetadata): Promise; - - protected async clearResult(id: string): Promise { - await this.ensureConnectionAndNotebook(); - if (this._notebook) { - this._notebook.clear(id); - } - } - - protected async setLaunchingFile(file: string): Promise { - if (file !== Identifiers.EmptyFileName && this._notebook) { - await this._notebook.setLaunchingFile(file); - } - } - - protected getNotebook(): INotebook | undefined { - return this._notebook; - } - - // tslint:disable-next-line: max-func-body-length - protected async submitCode( - code: string, - file: string, - line: number, - id?: string, - data?: nbformat.ICodeCell | nbformat.IRawCell | nbformat.IMarkdownCell, - debugInfo?: { runByLine: boolean; hashFileName?: string }, - cancelToken?: CancellationToken - ): Promise { - traceInfo(`Submitting code for ${this.id}`); - const stopWatch = - this._notebook && !this.perceivedJupyterStartupTelemetryCaptured ? new StopWatch() : undefined; - let result = true; - // Do not execute or render empty code cells - const cellMatcher = new CellMatcher(this.configService.getSettings(this.owningResource).datascience); - if (cellMatcher.stripFirstMarker(code).length === 0) { - return result; - } - - // Start a status item - const status = this.setStatus(localize.DataScience.executingCode(), false); - - // Transmit this submission to all other listeners (in a live share session) - if (!id) { - id = uuid(); - this.shareMessage(InteractiveWindowMessages.RemoteAddCode, { - code, - file, - line, - id, - originator: this.id, - debug: debugInfo !== undefined ? true : false - }); - } - - // Create a deferred object that will wait until the status is disposed - const finishedAddingCode = createDeferred(); - const actualDispose = status.dispose.bind(status); - status.dispose = () => { - finishedAddingCode.resolve(); - actualDispose(); - }; - - try { - // Make sure we're loaded first. - await this.ensureConnectionAndNotebook(); - - // Make sure we set the dark setting - await this.ensureDarkSet(); - - // Then show our webpanel - await this.show(true); - - // Add our sys info if necessary - if (file !== Identifiers.EmptyFileName) { - await this.addSysInfo(SysInfoReason.Start); - } - - if (this._notebook) { - // Before we try to execute code make sure that we have an initial directory set - // Normally set via the workspace, but we might not have one here if loading a single loose file - await this.setLaunchingFile(file); - - if (debugInfo) { - // Attach our debugger based on run by line setting - if (debugInfo.runByLine && debugInfo.hashFileName) { - await this.jupyterDebugger.startRunByLine(this._notebook, debugInfo.hashFileName); - } else if (!debugInfo.runByLine) { - await this.jupyterDebugger.startDebugging(this._notebook); - } else { - throw Error('Missing hash file name when running by line'); - } - } - - // If the file isn't unknown, set the active kernel's __file__ variable to point to that same file. - if (file !== Identifiers.EmptyFileName) { - await this._notebook.execute( - `__file__ = '${file.replace(/\\/g, '\\\\')}'`, - file, - line, - uuid(), - cancelToken, - true - ); - } - if (stopWatch && !this.perceivedJupyterStartupTelemetryCaptured) { - this.perceivedJupyterStartupTelemetryCaptured = true; - sendTelemetryEvent(Telemetry.PerceivedJupyterStartupNotebook, stopWatch?.elapsedTime); - const disposable = this._notebook.onSessionStatusChanged((e) => { - if (e === ServerStatus.Busy) { - sendTelemetryEvent(Telemetry.StartExecuteNotebookCellPerceivedCold, stopWatch?.elapsedTime); - disposable.dispose(); - } - }); - } - const owningResource = this.owningResource; - const observable = this._notebook.executeObservable(code, file, line, id, false); - - // Indicate we executed some code - this.executeEvent.fire(code); - - // Sign up for cell changes - observable.subscribe( - (cells: ICell[]) => { - // Combine the cell data with the possible input data (so we don't lose anything that might have already been in the cells) - const combined = cells.map(this.combineData.bind(undefined, data)); - - // Then send the combined output to the UI - this.sendCellsToWebView(combined); - - // Any errors will move our result to false (if allowed) - if (this.configuration.getSettings(owningResource).datascience.stopOnError) { - result = result && cells.find((c) => c.state === CellState.error) === undefined; - } - }, - (error) => { - traceError(`Error executing a cell: `, error); - status.dispose(); - if (!(error instanceof CancellationError)) { - this.applicationShell.showErrorMessage(error.toString()); - } - }, - () => { - // Indicate executing until this cell is done. - status.dispose(); - } - ); - - // Wait for the cell to finish - await finishedAddingCode.promise; - traceInfo(`Finished execution for ${id}`); - } - } finally { - status.dispose(); - - if (debugInfo) { - if (this._notebook) { - await this.jupyterDebugger.stopDebugging(this._notebook); - } - } - } - - return result; - } - - protected addMessageImpl(message: string): void { - const cell: ICell = { - id: uuid(), - file: Identifiers.EmptyFileName, - line: 0, - state: CellState.finished, - data: { - cell_type: 'messages', - messages: [message], - source: [], - metadata: {} - } - }; - - // Do the same thing that happens when new code is added. - this.sendCellsToWebView([cell]); - } - - protected sendCellsToWebView(cells: ICell[]) { - // Send each cell to the other side - cells.forEach((cell: ICell) => { - switch (cell.state) { - case CellState.init: - // Tell the react controls we have a new cell - this.postMessage(InteractiveWindowMessages.StartCell, cell).ignoreErrors(); - - // Keep track of this unfinished cell so if we restart we can finish right away. - this.unfinishedCells.push(cell); - break; - - case CellState.executing: - // Tell the react controls we have an update - this.postMessage(InteractiveWindowMessages.UpdateCellWithExecutionResults, cell).ignoreErrors(); - break; - - case CellState.error: - case CellState.finished: - // Tell the react controls we're done - this.postMessage(InteractiveWindowMessages.FinishCell, { - cell, - notebookIdentity: this.notebookIdentity.resource - }).ignoreErrors(); - - // Remove from the list of unfinished cells - this.unfinishedCells = this.unfinishedCells.filter((c) => c.id !== cell.id); - break; - - default: - break; // might want to do a progress bar or something - } - }); - } - - protected postMessage( - type: T, - payload?: M[T] - ): Promise { - // First send to our listeners - this.postMessageToListeners(type.toString(), payload); - - // Then send it to the webview - return super.postMessage(type, payload); - } - - protected handleMessage( - _message: T, - // tslint:disable-next-line:no-any - payload: any, - handler: (args: M[T]) => void - ) { - const args = payload as M[T]; - handler.bind(this)(args); - } - - protected setStatus = (message: string, showInWebView: boolean): Disposable => { - const result = this.statusProvider.set(message, showInWebView, undefined, undefined, this); - this.potentiallyUnfinishedStatus.push(result); - return result; - }; - - protected async addSysInfo(reason: SysInfoReason): Promise { - if (!this.addSysInfoPromise || reason !== SysInfoReason.Start) { - traceInfo(`Adding sys info for ${this.id} ${reason}`); - const deferred = createDeferred(); - this.addSysInfoPromise = deferred; - - // Generate a new sys info cell and send it to the web panel. - const sysInfo = await this.generateSysInfoCell(reason); - if (sysInfo) { - this.sendCellsToWebView([sysInfo]); - } - - // For anything but start, tell the other sides of a live share session - if (reason !== SysInfoReason.Start && sysInfo) { - this.shareMessage(InteractiveWindowMessages.AddedSysInfo, { - type: reason, - sysInfoCell: sysInfo, - id: this.id, - notebookIdentity: this.notebookIdentity.resource - }); - } - - // For a restart, tell our window to reset - if (reason === SysInfoReason.Restart || reason === SysInfoReason.New) { - this.postMessage(InteractiveWindowMessages.RestartKernel).ignoreErrors(); - } - - traceInfo(`Sys info for ${this.id} ${reason} complete`); - deferred.resolve(true); - } else if (this.addSysInfoPromise) { - traceInfo(`Wait for sys info for ${this.id} ${reason}`); - await this.addSysInfoPromise.promise; - } - } - - protected async ensureConnectionAndNotebook(): Promise { - // Start over if we somehow end up with a disposed notebook. - if (this._notebook && this._notebook.disposed) { - this._notebook = undefined; - this.notebookPromise = undefined; - this.connectionAndNotebookPromise = undefined; - } - if (!this.connectionAndNotebookPromise) { - this.connectionAndNotebookPromise = this.ensureConnectionAndNotebookImpl(); - } - try { - await this.connectionAndNotebookPromise; - } catch (e) { - // Reset the load promise. Don't want to keep hitting the same error - this.connectionAndNotebookPromise = undefined; - throw e; - } - } - - // ensureNotebook can be called apart from ensureNotebookAndServer and it needs - // the same protection to not be called twice - // tslint:disable-next-line: member-ordering - protected async ensureNotebook(serverConnection: INotebookProviderConnection): Promise { - if (!this.notebookPromise) { - this.notebookPromise = this.ensureNotebookImpl(serverConnection); - } - try { - await this.notebookPromise; - } catch (e) { - // Reset the load promise. Don't want to keep hitting the same error - this.notebookPromise = undefined; - - throw e; - } - } - - protected async createNotebookIfProviderConnectionExists(): Promise { - // Check to see if we are already connected to our provider - const providerConnection = await this.notebookProvider.connect({ getOnly: true }); - - if (providerConnection) { - try { - await this.ensureNotebook(providerConnection); - } catch (e) { - this.errorHandler.handleError(e).ignoreErrors(); - } - } else { - // Just send a kernel update so it shows something - this.postMessage(InteractiveWindowMessages.UpdateKernel, { - jupyterServerStatus: ServerStatus.NotStarted, - serverName: await this.getServerDisplayName(undefined), - kernelName: '', - language: PYTHON_LANGUAGE - }).ignoreErrors(); - } - } - - protected async getServerDisplayName(serverConnection: INotebookProviderConnection | undefined): Promise { - // If we don't have a server connection, make one if remote. We need the remote connection in order - // to compute the display name. However only do this if the user is allowing auto start. - if ( - !serverConnection && - this.configService.getSettings(this.owningResource).datascience.jupyterServerURI !== - Settings.JupyterServerLocalLaunch && - !this.configService.getSettings(this.owningResource).datascience.disableJupyterAutoStart - ) { - serverConnection = await this.notebookProvider.connect({ disableUI: true }); - } - - let displayName = - serverConnection?.displayName || - (!serverConnection?.localLaunch ? serverConnection?.url : undefined) || - (this.configService.getSettings().datascience.jupyterServerURI === Settings.JupyterServerLocalLaunch - ? localize.DataScience.localJupyterServer() - : localize.DataScience.serverNotStarted()); - - if (serverConnection) { - // Determine the connection URI of the connected server to display - if (serverConnection.localLaunch) { - displayName = localize.DataScience.localJupyterServer(); - } else { - // Log this remote URI into our MRU list - addToUriList( - this.globalStorage, - !isNil(serverConnection.url) ? serverConnection.url : serverConnection.displayName, - Date.now(), - serverConnection.displayName - ); - } - } - - return displayName; - } - - private combineData( - oldData: nbformat.ICodeCell | nbformat.IRawCell | nbformat.IMarkdownCell | undefined, - cell: ICell - ): ICell { - if (oldData) { - const result = { - ...cell, - data: { - ...oldData, - ...cell.data, - metadata: { - ...oldData.metadata, - ...cell.data.metadata - } - } - }; - // Workaround the nyc compiler problem. - // tslint:disable-next-line: no-any - return (result as any) as ICell; - } - // tslint:disable-next-line: no-any - return (cell as any) as ICell; - } - - private async ensureConnectionAndNotebookImpl(): Promise { - // Make sure we're loaded first. - try { - traceInfo('Waiting for jupyter server and web panel ...'); - const serverConnection = await this.notebookProvider.connect({ getOnly: false, disableUI: false }); - if (serverConnection) { - await this.ensureNotebook(serverConnection); - } - } catch (exc) { - // We should dispose ourselves if the load fails. Othewise the user - // updates their install and we just fail again because the load promise is the same. - await this.closeBecauseOfFailure(exc); - - // Finally throw the exception so the user can do something about it. - throw exc; - } - } - - // tslint:disable-next-line: no-any - private postMessageToListeners(message: string, payload: any) { - if (this.listeners) { - this.listeners.forEach((l) => l.onMessage(message, payload)); - } - } - - private async shouldAskForRestart(): Promise { - const settings = this.configuration.getSettings(this.owningResource); - return settings && settings.datascience && settings.datascience.askForKernelRestart === true; - } - - private async disableAskForRestart(): Promise { - const settings = this.configuration.getSettings(this.owningResource); - if (settings && settings.datascience) { - settings.datascience.askForKernelRestart = false; - this.configuration - .updateSetting('dataScience.askForKernelRestart', false, undefined, ConfigurationTarget.Global) - .ignoreErrors(); - } - } - - private async shouldAskForLargeData(): Promise { - const settings = this.configuration.getSettings(this.owningResource); - return settings && settings.datascience && settings.datascience.askForLargeDataFrames === true; - } - - private async disableAskForLargeData(): Promise { - const settings = this.configuration.getSettings(this.owningResource); - if (settings && settings.datascience) { - settings.datascience.askForLargeDataFrames = false; - this.configuration - .updateSetting('dataScience.askForLargeDataFrames', false, undefined, ConfigurationTarget.Global) - .ignoreErrors(); - } - } - - private async checkColumnSize(columnSize: number): Promise { - if (columnSize > ColumnWarningSize && (await this.shouldAskForLargeData())) { - const message = localize.DataScience.tooManyColumnsMessage(); - const yes = localize.DataScience.tooManyColumnsYes(); - const no = localize.DataScience.tooManyColumnsNo(); - const dontAskAgain = localize.DataScience.tooManyColumnsDontAskAgain(); - - const result = await this.applicationShell.showWarningMessage(message, yes, no, dontAskAgain); - if (result === dontAskAgain) { - await this.disableAskForLargeData(); - } - return result === yes; - } - return true; - } - - private async showDataViewer(request: IShowDataViewer): Promise { - try { - if (await this.checkColumnSize(request.columnSize)) { - const jupyterVariableDataProvider = await this.jupyterVariableDataProviderFactory.create( - request.variable, - this._notebook! - ); - const title: string = `${localize.DataScience.dataExplorerTitle()} - ${request.variable.name}`; - await this.dataExplorerFactory.create(jupyterVariableDataProvider, title); - } - } catch (e) { - this.applicationShell.showErrorMessage(e.toString()); - } - } - - private onAddedSysInfo(sysInfo: IAddedSysInfo) { - // See if this is from us or not. - if (sysInfo.id !== this.id) { - // Not from us, must come from a different interactive window. Add to our - // own to keep in sync - if (sysInfo.sysInfoCell) { - this.sendCellsToWebView([sysInfo.sysInfoCell]); - } - } - } - - private async onRemoteReexecuteCode(args: IRemoteReexecuteCode) { - // Make sure this is valid - if (args && args.id && args.file && args.originator !== this.id) { - try { - // On a reexecute clear the previous execution - if (this._notebook) { - this._notebook.clear(args.id); - } - - // Indicate this in our telemetry. - // Add new telemetry type - sendTelemetryEvent(Telemetry.RemoteReexecuteCode); - - // Submit this item as new code. - this.submitCode( - args.code, - args.file, - args.line, - args.id, - undefined, - args.debug ? { runByLine: false } : undefined - ).ignoreErrors(); - } catch (exc) { - this.errorHandler.handleError(exc).ignoreErrors(); - } - } - } - - private async onRemoteAddedCode(args: IRemoteAddCode) { - // Make sure this is valid - if (args && args.id && args.file && args.originator !== this.id) { - try { - // Indicate this in our telemetry. - sendTelemetryEvent(Telemetry.RemoteAddCode); - - // Submit this item as new code. - await this.submitCode( - args.code, - args.file, - args.line, - args.id, - undefined, - args.debug ? { runByLine: false } : undefined - ); - } catch (exc) { - this.errorHandler.handleError(exc).ignoreErrors(); - } - } - } - - private finishOutstandingCells() { - this.unfinishedCells.forEach((c) => { - c.state = CellState.error; - this.postMessage(InteractiveWindowMessages.FinishCell, { - cell: c, - notebookIdentity: this.notebookIdentity.resource - }).ignoreErrors(); - }); - this.unfinishedCells = []; - this.potentiallyUnfinishedStatus.forEach((s) => s.dispose()); - this.potentiallyUnfinishedStatus = []; - } - - private async restartKernelInternal(): Promise { - this.restartingKernel = true; - - // First we need to finish all outstanding cells. - this.finishOutstandingCells(); - - // Set our status - const status = this.statusProvider.set( - localize.DataScience.restartingKernelStatus(), - true, - undefined, - undefined, - this - ); - - try { - if (this._notebook) { - await this._notebook.restartKernel( - (await this.generateDataScienceExtraSettings()).jupyterInterruptTimeout - ); - await this.addSysInfo(SysInfoReason.Restart); - - // Compute if dark or not. - const knownDark = await this.isDark(); - - // Before we run any cells, update the dark setting - await this._notebook.setMatplotLibStyle(knownDark); - } - } catch (exc) { - // If we get a kernel promise failure, then restarting timed out. Just shutdown and restart the entire server - if (exc instanceof JupyterKernelPromiseFailedError && this._notebook) { - await this._notebook.dispose(); - await this.ensureConnectionAndNotebook(); - await this.addSysInfo(SysInfoReason.Restart); - } else { - // Show the error message - this.applicationShell.showErrorMessage(exc); - traceError(exc); - } - } finally { - status.dispose(); - this.restartingKernel = false; - } - } - - private logTelemetry = (event: Telemetry) => { - sendTelemetryEvent(event); - }; - - private selectNewKernel() { - // This is handled by a command. - this.commandManager.executeCommand(Commands.SwitchJupyterKernel, { - identity: this.notebookIdentity.resource, - resource: this.owningResource, - currentKernelDisplayName: - this.notebookMetadata?.kernelspec?.display_name || - this.notebookMetadata?.kernelspec?.name || - getDisplayNameOrNameOfKernelConnection(this._notebook?.getKernelConnection()) - }); - } - - private async createNotebook(serverConnection: INotebookProviderConnection): Promise { - let notebook: INotebook | undefined; - while (!notebook) { - try { - notebook = await this.notebookProvider.getOrCreateNotebook({ - identity: this.notebookIdentity.resource, - resource: this.owningResource, - metadata: this.notebookMetadata - }); - if (notebook) { - const executionActivation = { ...this.notebookIdentity, owningResource: this.owningResource }; - this.postMessageToListeners( - InteractiveWindowMessages.NotebookExecutionActivated, - executionActivation - ); - } - } catch (e) { - // If we get an invalid kernel error, make sure to ask the user to switch - if (e instanceof JupyterInvalidKernelError && serverConnection && serverConnection.localLaunch) { - // Ask the user for a new local kernel - const newKernel = await this.selector.askForLocalKernel( - this.owningResource, - serverConnection.type, - e.kernelConnectionMetadata - ); - if (newKernel && kernelConnectionMetadataHasKernelSpec(newKernel) && newKernel.kernelSpec) { - this.commandManager.executeCommand( - Commands.SetJupyterKernel, - newKernel, - this.notebookIdentity.resource, - this.owningResource - ); - } - } else { - throw e; - } - } - } - return notebook; - } - - private async listenToNotebookEvents(notebook: INotebook): Promise { - const statusChangeHandler = async (status: ServerStatus) => { - const connectionMetadata = notebook.getKernelConnection(); - const name = getDisplayNameOrNameOfKernelConnection(connectionMetadata); - - await this.postMessage(InteractiveWindowMessages.UpdateKernel, { - jupyterServerStatus: status, - serverName: await this.getServerDisplayName(notebook.connection), - kernelName: name, - language: translateKernelLanguageToMonaco( - getKernelConnectionLanguage(connectionMetadata) || PYTHON_LANGUAGE - ) - }); - }; - notebook.onSessionStatusChanged(statusChangeHandler); - this.disposables.push(notebook.onKernelChanged(this.kernelChangeHandler.bind(this))); - - // Fire the status changed handler at least once (might have already been running and so won't show a status update) - statusChangeHandler(notebook.status).ignoreErrors(); - - // Also listen to iopub messages so we can update other cells on update_display_data - notebook.registerIOPubListener(this.handleKernelMessage.bind(this)); - } - - private async ensureNotebookImpl(serverConnection: INotebookProviderConnection): Promise { - // Create a new notebook if we need to. - if (!this._notebook) { - // While waiting make the notebook look busy - this.postMessage(InteractiveWindowMessages.UpdateKernel, { - jupyterServerStatus: ServerStatus.Busy, - serverName: await this.getServerDisplayName(serverConnection), - kernelName: '', - language: PYTHON_LANGUAGE - }).ignoreErrors(); - - this._notebook = await this.createNotebook(serverConnection); - - // If that works notify the UI and listen to status changes. - if (this._notebook && this._notebook.identity) { - return this.listenToNotebookEvents(this._notebook); - } - } - } - - private refreshVariables() { - this.postMessage(InteractiveWindowMessages.ForceVariableRefresh).ignoreErrors(); - } - - private async potentialKernelChanged(data: { - identity: Uri; - kernelConnection: KernelConnectionMetadata; - }): Promise { - const specOrModel = kernelConnectionMetadataHasKernelModel(data.kernelConnection) - ? data.kernelConnection.kernelModel - : data.kernelConnection.kernelSpec; - if (!this._notebook && specOrModel && this.notebookIdentity.resource.toString() === data.identity.toString()) { - // No notebook, send update to UI anyway - this.postMessage(InteractiveWindowMessages.UpdateKernel, { - jupyterServerStatus: ServerStatus.NotStarted, - serverName: await this.getServerDisplayName(undefined), - kernelName: getDisplayNameOrNameOfKernelConnection(data.kernelConnection), - language: translateKernelLanguageToMonaco( - getKernelConnectionLanguage(data.kernelConnection) || PYTHON_LANGUAGE - ) - }).ignoreErrors(); - - // Update our model - this.updateNotebookOptions(data.kernelConnection).ignoreErrors(); - } - } - - @captureTelemetry(Telemetry.GotoSourceCode, undefined, false) - private gotoCode(args: IGotoCode) { - this.gotoCodeInternal(args.file, args.line).catch((err) => { - this.applicationShell.showErrorMessage(err); - }); - } - - private async gotoCodeInternal(file: string, line: number) { - let editor: TextEditor | undefined; - - if (await this.fs.localFileExists(file)) { - editor = await this.documentManager.showTextDocument(Uri.file(file), { viewColumn: ViewColumn.One }); - } else { - // File URI isn't going to work. Look through the active text documents - editor = this.documentManager.visibleTextEditors.find((te) => te.document.fileName === file); - if (editor) { - editor.show(); - } - } - - // If we found the editor change its selection - if (editor) { - editor.revealRange(new Range(line, 0, line, 0)); - editor.selection = new Selection(new Position(line, 0), new Position(line, 0)); - } - } - - private async copyCodeInternal(source: string) { - let editor = this.documentManager.activeTextEditor; - if (!editor || editor.document.languageId !== PYTHON_LANGUAGE) { - // Find the first visible python editor - const pythonEditors = this.documentManager.visibleTextEditors.filter( - (e) => e.document.languageId === PYTHON_LANGUAGE || e.document.isUntitled - ); - - if (pythonEditors.length > 0) { - editor = pythonEditors[0]; - } - } - if (editor && (editor.document.languageId === PYTHON_LANGUAGE || editor.document.isUntitled)) { - // Figure out if any cells in this document already. - const ranges = generateCellRangesFromDocument( - editor.document, - await this.generateDataScienceExtraSettings() - ); - const hasCellsAlready = ranges.length > 0; - const line = editor.selection.start.line; - const revealLine = line + 1; - const defaultCellMarker = - this.configService.getSettings(this.owningResource).datascience.defaultCellMarker || - Identifiers.DefaultCodeCellMarker; - let newCode = `${source}${os.EOL}`; - if (hasCellsAlready) { - // See if inside of a range or not. - const matchingRange = ranges.find((r) => r.range.start.line <= line && r.range.end.line >= line); - - // If in the middle, wrap the new code - if (matchingRange && matchingRange.range.start.line < line && line < editor.document.lineCount - 1) { - newCode = `${defaultCellMarker}${os.EOL}${source}${os.EOL}${defaultCellMarker}${os.EOL}`; - } else { - newCode = `${defaultCellMarker}${os.EOL}${source}${os.EOL}`; - } - } else if (editor.document.lineCount <= 0 || editor.document.isUntitled) { - // No lines in the document at all, just insert new code - newCode = `${defaultCellMarker}${os.EOL}${source}${os.EOL}`; - } - - await editor.edit((editBuilder) => { - editBuilder.insert(new Position(line, 0), newCode); - }); - editor.revealRange(new Range(revealLine, 0, revealLine + source.split('\n').length + 3, 0)); - - // Move selection to just beyond the text we input so that the next - // paste will be right after - const selectionLine = line + newCode.split('\n').length - 1; - editor.selection = new Selection(new Position(selectionLine, 0), new Position(selectionLine, 0)); - } - } - - private async ensureDarkSet(): Promise { - if (!this.setDarkPromise) { - this.setDarkPromise = createDeferred(); - - // Wait for the web panel to get the isDark setting - const knownDark = await this.isDark(); - - // Before we run any cells, update the dark setting - if (this._notebook) { - await this._notebook.setMatplotLibStyle(knownDark); - } - - this.setDarkPromise.resolve(true); - } else { - await this.setDarkPromise.promise; - } - } - - private generateSysInfoCell = async (reason: SysInfoReason): Promise => { - // Execute the code 'import sys\r\nsys.version' and 'import sys\r\nsys.executable' to get our - // version and executable - if (this._notebook) { - const message = await this.generateSysInfoMessage(reason); - - // The server handles getting this data. - const sysInfo = await this._notebook.getSysInfo(); - if (sysInfo) { - // Connection string only for our initial start, not restart or interrupt - let connectionString: string = ''; - if (reason === SysInfoReason.Start) { - connectionString = this.generateConnectionInfoString(this._notebook.connection); - } - - // Update our sys info with our locally applied data. - const cell = sysInfo.data as IMessageCell; - if (cell) { - cell.messages.unshift(message); - if (connectionString && connectionString.length) { - cell.messages.unshift(connectionString); - } - } - - return sysInfo; - } - } - }; - - private async generateSysInfoMessage(reason: SysInfoReason): Promise { - switch (reason) { - case SysInfoReason.Start: - return localize.DataScience.pythonVersionHeader(); - break; - case SysInfoReason.Restart: - return localize.DataScience.pythonRestartHeader(); - break; - case SysInfoReason.Interrupt: - return localize.DataScience.pythonInterruptFailedHeader(); - break; - case SysInfoReason.New: - return localize.DataScience.pythonNewHeader(); - break; - case SysInfoReason.Connect: - return localize.DataScience.pythonConnectHeader(); - break; - default: - traceError('Invalid SysInfoReason'); - return ''; - break; - } - } - - private generateConnectionInfoString(connInfo: INotebookProviderConnection | undefined): string { - return connInfo?.displayName || ''; - } - - private async requestVariables(args: IJupyterVariablesRequest): Promise { - // Request our new list of variables - const response: IJupyterVariablesResponse = this._notebook - ? await this.jupyterVariables.getVariables(this._notebook, args) - : { - totalCount: 0, - pageResponse: [], - pageStartIndex: args?.startIndex, - executionCount: args?.executionCount, - refreshCount: args?.refreshCount || 0 - }; - - this.postMessage(InteractiveWindowMessages.GetVariablesResponse, response).ignoreErrors(); - sendTelemetryEvent(Telemetry.VariableExplorerVariableCount, undefined, { variableCount: response.totalCount }); - } - - // tslint:disable-next-line: no-any - private variableExplorerToggle = (payload?: any) => { - // Direct undefined check as false boolean will skip code - if (payload !== undefined) { - const openValue = payload as boolean; - - // Log the state in our Telemetry - sendTelemetryEvent(Telemetry.VariableExplorerToggled, undefined, { - open: openValue, - runByLine: this.jupyterDebugger.isRunningByLine - }); - } - }; - - // tslint:disable-next-line: no-any - private async setVariableExplorerHeight(payload?: any) { - // Store variable explorer height based on file name in workspace storage - if (payload !== undefined) { - const updatedHeights = payload as { containerHeight: number; gridHeight: number }; - const uri = this.owningResource; // Get file name - - if (!uri) { - return; - } - // Storing an object that looks like - // { "fully qualified Path to 1.ipynb": 1234, - // "fully qualified path to 2.ipynb": 1234 } - - // tslint:disable-next-line: no-any - const value = this.workspaceStorage.get(VariableExplorerStateKeys.height, {} as any); - value[uri.toString()] = updatedHeights; - this.workspaceStorage.update(VariableExplorerStateKeys.height, value); - } - } - - private async variableExplorerHeightRequest(): Promise< - { containerHeight: number; gridHeight: number } | undefined - > { - const uri = this.owningResource; // Get file name - - if (!uri || isUntitledFile(uri)) { - return; // don't restore height of untitled notebooks - } - - // tslint:disable-next-line: no-any - const value = this.workspaceStorage.get(VariableExplorerStateKeys.height, {} as any); - const uriString = uri.toString(); - if (uriString in value) { - return value[uriString]; - } - } - - private async requestTmLanguage(languageId: string) { - // Get the contents of the appropriate tmLanguage file. - traceInfo('Request for tmlanguage file.'); - const languageJson = await this.themeFinder.findTmLanguage(languageId); - const languageConfiguration = serializeLanguageConfiguration( - await this.themeFinder.findLanguageConfiguration(languageId) - ); - const extensions = languageId === PYTHON_LANGUAGE ? ['.py'] : []; - const scopeName = `scope.${languageId}`; // This works for python, not sure about c# etc. - this.postMessage(InteractiveWindowMessages.LoadTmLanguageResponse, { - languageJSON: languageJson ?? '', - languageConfiguration, - extensions, - scopeName, - languageId - }).ignoreErrors(); - } - - private async requestOnigasm(): Promise { - // Look for the file next or our current file (this is where it's installed in the vsix) - let filePath = path.join(__dirname, 'node_modules', 'onigasm', 'lib', 'onigasm.wasm'); - traceInfo(`Request for onigasm file at ${filePath}`); - if (this.fs) { - if (await this.fs.localFileExists(filePath)) { - const contents = await this.fs.readLocalData(filePath); - this.postMessage(InteractiveWindowMessages.LoadOnigasmAssemblyResponse, contents).ignoreErrors(); - } else { - // During development it's actually in the node_modules folder - filePath = path.join(EXTENSION_ROOT_DIR, 'node_modules', 'onigasm', 'lib', 'onigasm.wasm'); - traceInfo(`Backup request for onigasm file at ${filePath}`); - if (await this.fs.localFileExists(filePath)) { - const contents = await this.fs.readLocalData(filePath); - this.postMessage(InteractiveWindowMessages.LoadOnigasmAssemblyResponse, contents).ignoreErrors(); - } else { - traceWarning('Onigasm file not found. Colorization will not be available.'); - this.postMessage(InteractiveWindowMessages.LoadOnigasmAssemblyResponse).ignoreErrors(); - } - } - } else { - // This happens during testing. Onigasm not needed as we're not testing colorization. - traceWarning('File system not found. Colorization will not be available.'); - this.postMessage(InteractiveWindowMessages.LoadOnigasmAssemblyResponse).ignoreErrors(); - } - } - - private async selectServer() { - await this.commandManager.executeCommand(Commands.SelectJupyterURI); - } - private async kernelChangeHandler(kernelConnection: KernelConnectionMetadata) { - // Check if we are changing to LiveKernelModel - if (kernelConnection.kind === 'connectToLiveKernel') { - await this.addSysInfo(SysInfoReason.Connect); - } else { - await this.addSysInfo(SysInfoReason.New); - } - return this.updateNotebookOptions(kernelConnection); - } - - private openSettings(setting: string | undefined) { - if (setting) { - commands.executeCommand('workbench.action.openSettings', setting); - } else { - commands.executeCommand('workbench.action.openSettings'); - } - } - - private handleKernelMessage(msg: KernelMessage.IIOPubMessage, _requestId: string) { - // Only care about one sort of message, UpdateDisplayData - // tslint:disable-next-line: no-require-imports - const jupyterLab = require('@jupyterlab/services') as typeof import('@jupyterlab/services'); // NOSONAR - if (jupyterLab.KernelMessage.isUpdateDisplayDataMsg(msg)) { - this.handleUpdateDisplayData(msg as KernelMessage.IUpdateDisplayDataMsg); - } - } - - private handleUpdateDisplayData(msg: KernelMessage.IUpdateDisplayDataMsg) { - // Send to the UI to handle - this.postMessage(InteractiveWindowMessages.UpdateDisplayData, msg).ignoreErrors(); - } -} diff --git a/src/client/datascience/interactive-common/interactiveWindowMessageListener.ts b/src/client/datascience/interactive-common/interactiveWindowMessageListener.ts deleted file mode 100644 index df691ea0497f..000000000000 --- a/src/client/datascience/interactive-common/interactiveWindowMessageListener.ts +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; - -import * as vscode from 'vscode'; -import * as vsls from 'vsls/vscode'; - -import { ILiveShareApi, IWebviewPanel, IWebviewPanelMessageListener } from '../../common/application/types'; -import { Identifiers, LiveShare } from '../constants'; -import { PostOffice } from '../liveshare/postOffice'; -import { InteractiveWindowMessages, InteractiveWindowRemoteMessages } from './interactiveWindowTypes'; - -// tslint:disable:no-any - -// This class listens to messages that come from the local Python Interactive window -export class InteractiveWindowMessageListener implements IWebviewPanelMessageListener { - private postOffice: PostOffice; - private disposedCallback: () => void; - private callback: (message: string, payload: any) => void; - private viewChanged: (panel: IWebviewPanel) => void; - private interactiveWindowMessages: string[] = []; - - constructor( - liveShare: ILiveShareApi, - callback: (message: string, payload: any) => void, - viewChanged: (panel: IWebviewPanel) => void, - disposed: () => void - ) { - this.postOffice = new PostOffice(LiveShare.WebPanelMessageService, liveShare, (api, _command, role, args) => - this.translateHostArgs(api, role, args) - ); - - // Save our dispose callback so we remove our interactive window - this.disposedCallback = disposed; - - // Save our local callback so we can handle the non broadcast case(s) - this.callback = callback; - - // Save view changed so we can forward view change events. - this.viewChanged = viewChanged; - - // Remember the list of interactive window messages we registered for - this.interactiveWindowMessages = this.getInteractiveWindowMessages(); - - // We need to register callbacks for all interactive window messages. - this.interactiveWindowMessages.forEach((m) => { - this.postOffice.registerCallback(m, (a) => callback(m, a)).ignoreErrors(); - }); - } - - public async dispose() { - await this.postOffice.dispose(); - this.disposedCallback(); - } - - public onMessage(message: string, payload: any) { - // We received a message from the local webview. Broadcast it to everybody if it's a remote message - if (InteractiveWindowRemoteMessages.indexOf(message) >= 0) { - this.postOffice.postCommand(message, payload).ignoreErrors(); - } else { - // Send to just our local callback. - this.callback(message, payload); - } - } - - public onChangeViewState(panel: IWebviewPanel) { - // Forward this onto our callback - this.viewChanged(panel); - } - - private getInteractiveWindowMessages(): string[] { - return Object.keys(InteractiveWindowMessages).map((k) => (InteractiveWindowMessages as any)[k].toString()); - } - - private translateHostArgs(api: vsls.LiveShare | null, role: vsls.Role, args: any[]) { - // Figure out the true type of the args - if (api && args && args.length > 0) { - const trueArg = args[0]; - - // See if the trueArg has a 'file' name or not - if (trueArg) { - const keys = Object.keys(trueArg); - keys.forEach((k) => { - if (k.includes('file')) { - if (typeof trueArg[k] === 'string') { - // Pull out the string. We need to convert it to a file or vsls uri based on our role - const file = trueArg[k].toString(); - - // Skip the empty file - if (file !== Identifiers.EmptyFileName) { - const uri = - role === vsls.Role.Host ? vscode.Uri.file(file) : vscode.Uri.parse(`vsls:${file}`); - - // Translate this into the other side. - trueArg[k] = - role === vsls.Role.Host - ? api.convertLocalUriToShared(uri).fsPath - : api.convertSharedUriToLocal(uri).fsPath; - } - } - } - }); - } - } - } -} diff --git a/src/client/datascience/interactive-common/interactiveWindowTypes.ts b/src/client/datascience/interactive-common/interactiveWindowTypes.ts deleted file mode 100644 index 86921d56c919..000000000000 --- a/src/client/datascience/interactive-common/interactiveWindowTypes.ts +++ /dev/null @@ -1,684 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; -import { Uri } from 'vscode'; -import { DebugState, IServerState } from '../../../datascience-ui/interactive-common/mainState'; - -import type { KernelMessage } from '@jupyterlab/services'; -import { DebugProtocol } from 'vscode-debugprotocol'; -import { - CommonActionType, - IAddCellAction, - IChangeGatherStatus, - ILoadIPyWidgetClassFailureAction, - IVariableExplorerHeight, - LoadIPyWidgetClassLoadAction, - NotifyIPyWidgeWidgetVersionNotSupportedAction -} from '../../../datascience-ui/interactive-common/redux/reducers/types'; -import { Resource } from '../../common/types'; -import { NativeKeyboardCommandTelemetry, NativeMouseCommandTelemetry } from '../constants'; -import { WidgetScriptSource } from '../ipywidgets/types'; -import { KernelConnectionMetadata } from '../jupyter/kernels/types'; -import { CssMessages, IGetCssRequest, IGetCssResponse, IGetMonacoThemeRequest, SharedMessages } from '../messages'; -import { IGetMonacoThemeResponse } from '../monacoMessages'; -import { - ICell, - IInteractiveWindowInfo, - IJupyterVariable, - IJupyterVariablesRequest, - IJupyterVariablesResponse, - INotebookModel, - KernelSocketOptions -} from '../types'; -import { ILanguageConfigurationDto } from './serialization'; -import { BaseReduxActionPayload } from './types'; - -export enum InteractiveWindowMessages { - StartCell = 'start_cell', - FinishCell = 'finish_cell', - UpdateCellWithExecutionResults = 'UpdateCellWithExecutionResults', - GotoCodeCell = 'gotocell_code', - CopyCodeCell = 'copycell_code', - NotebookExecutionActivated = 'notebook_execution_activated', - RestartKernel = 'restart_kernel', - Export = 'export_to_ipynb', - ExportNotebookAs = 'export_as_menu', - GetAllCells = 'get_all_cells', - ReturnAllCells = 'return_all_cells', - DeleteAllCells = 'delete_all_cells', - Undo = 'undo', - Redo = 'redo', - ExpandAll = 'expand_all', - CollapseAll = 'collapse_all', - StartProgress = 'start_progress', - StopProgress = 'stop_progress', - Interrupt = 'interrupt', - SubmitNewCell = 'submit_new_cell', - SettingsUpdated = 'settings_updated', - // Message sent to React component from extension asking it to save the notebook. - DoSave = 'DoSave', - SendInfo = 'send_info', - Started = 'started', - ConvertUriForUseInWebViewRequest = 'ConvertUriForUseInWebViewRequest', - ConvertUriForUseInWebViewResponse = 'ConvertUriForUseInWebViewResponse', - AddedSysInfo = 'added_sys_info', - RemoteAddCode = 'remote_add_code', - RemoteReexecuteCode = 'remote_reexecute_code', - Activate = 'activate', - ShowDataViewer = 'show_data_explorer', - GetVariablesRequest = 'get_variables_request', - GetVariablesResponse = 'get_variables_response', - VariableExplorerToggle = 'variable_explorer_toggle', - SetVariableExplorerHeight = 'set_variable_explorer_height', - VariableExplorerHeightResponse = 'variable_explorer_height_response', - ForceVariableRefresh = 'force_variable_refresh', - ProvideCompletionItemsRequest = 'provide_completion_items_request', - CancelCompletionItemsRequest = 'cancel_completion_items_request', - ProvideCompletionItemsResponse = 'provide_completion_items_response', - ProvideHoverRequest = 'provide_hover_request', - CancelHoverRequest = 'cancel_hover_request', - ProvideHoverResponse = 'provide_hover_response', - ProvideSignatureHelpRequest = 'provide_signature_help_request', - CancelSignatureHelpRequest = 'cancel_signature_help_request', - ProvideSignatureHelpResponse = 'provide_signature_help_response', - ResolveCompletionItemRequest = 'resolve_completion_item_request', - CancelResolveCompletionItemRequest = 'cancel_resolve_completion_item_request', - ResolveCompletionItemResponse = 'resolve_completion_item_response', - Sync = 'sync_message_used_to_broadcast_and_sync_editors', - LoadOnigasmAssemblyRequest = 'load_onigasm_assembly_request', - LoadOnigasmAssemblyResponse = 'load_onigasm_assembly_response', - LoadTmLanguageRequest = 'load_tmlanguage_request', - LoadTmLanguageResponse = 'load_tmlanguage_response', - OpenLink = 'open_link', - ShowPlot = 'show_plot', - SavePng = 'save_png', - StartDebugging = 'start_debugging', - StopDebugging = 'stop_debugging', - GatherCode = 'gather_code', - GatherCodeToScript = 'gather_code_to_script', - Gathering = 'gathering', - LaunchNotebookTrustPrompt = 'launch_notebook_trust_prompt', - TrustNotebookComplete = 'trust_notebook_complete', - LoadAllCells = 'load_all_cells', - LoadAllCellsComplete = 'load_all_cells_complete', - ScrollToCell = 'scroll_to_cell', - ReExecuteCells = 'reexecute_cells', - NotebookIdentity = 'identity', - NotebookClose = 'close', - NotebookDirty = 'dirty', - NotebookClean = 'clean', - SaveAll = 'save_all', - NativeCommand = 'native_command', - VariablesComplete = 'variables_complete', - NotebookRunAllCells = 'notebook_run_all_cells', - NotebookRunSelectedCell = 'notebook_run_selected_cell', - NotebookAddCellBelow = 'notebook_add_cell_below', - ExecutionRendered = 'rendered_execution', - FocusedCellEditor = 'focused_cell_editor', - SelectedCell = 'selected_cell', - OutputToggled = 'output_toggled', - UnfocusedCellEditor = 'unfocused_cell_editor', - MonacoReady = 'monaco_ready', - ClearAllOutputs = 'clear_all_outputs', - SelectKernel = 'select_kernel', - UpdateKernel = 'update_kernel', - SelectJupyterServer = 'select_jupyter_server', - UpdateModel = 'update_model', - ReceivedUpdateModel = 'received_update_model', - OpenSettings = 'open_settings', - UpdateDisplayData = 'update_display_data', - IPyWidgetLoadSuccess = 'ipywidget_load_success', - IPyWidgetLoadFailure = 'ipywidget_load_failure', - IPyWidgetRenderFailure = 'ipywidget_render_failure', - IPyWidgetUnhandledKernelMessage = 'ipywidget_unhandled_kernel_message', - IPyWidgetWidgetVersionNotSupported = 'ipywidget_widget_version_not_supported', - RunByLine = 'run_by_line', - Step = 'step', - Continue = 'continue', - ShowContinue = 'show_continue', - ShowBreak = 'show_break', - ShowingIp = 'showing_ip', - DebugStateChange = 'debug_state_change', - KernelIdle = 'kernel_idle', - HasCell = 'has_cell', - HasCellResponse = 'has_cell_response' -} - -export enum IPyWidgetMessages { - IPyWidgets_Ready = 'IPyWidgets_Ready', - IPyWidgets_onRestartKernel = 'IPyWidgets_onRestartKernel', - IPyWidgets_onKernelChanged = 'IPyWidgets_onKernelChanged', - IPyWidgets_updateRequireConfig = 'IPyWidgets_updateRequireConfig', - /** - * UI sends a request to extension to determine whether we have the source for any of the widgets. - */ - IPyWidgets_WidgetScriptSourceRequest = 'IPyWidgets_WidgetScriptSourceRequest', - /** - * Extension sends response to the request with yes/no. - */ - IPyWidgets_WidgetScriptSourceResponse = 'IPyWidgets_WidgetScriptSourceResponse', - IPyWidgets_msg = 'IPyWidgets_msg', - IPyWidgets_binary_msg = 'IPyWidgets_binary_msg', - // Message was received by the widget kernel and added to the msgChain queue for processing - IPyWidgets_msg_received = 'IPyWidgets_msg_received', - // IOPub message was fully handled by the widget kernel - IPyWidgets_iopub_msg_handled = 'IPyWidgets_iopub_msg_handled', - IPyWidgets_kernelOptions = 'IPyWidgets_kernelOptions', - IPyWidgets_registerCommTarget = 'IPyWidgets_registerCommTarget', - IPyWidgets_RegisterMessageHook = 'IPyWidgets_RegisterMessageHook', - // Message sent when the extension has finished an operation requested by the kernel UI for processing a message - IPyWidgets_ExtensionOperationHandled = 'IPyWidgets_ExtensionOperationHandled', - IPyWidgets_RemoveMessageHook = 'IPyWidgets_RemoveMessageHook', - IPyWidgets_MessageHookCall = 'IPyWidgets_MessageHookCall', - IPyWidgets_MessageHookResult = 'IPyWidgets_MessageHookResult', - IPyWidgets_mirror_execute = 'IPyWidgets_mirror_execute' -} - -// These are the messages that will mirror'd to guest/hosts in -// a live share session -export const InteractiveWindowRemoteMessages: string[] = [ - InteractiveWindowMessages.AddedSysInfo.toString(), - InteractiveWindowMessages.RemoteAddCode.toString(), - InteractiveWindowMessages.RemoteReexecuteCode.toString() -]; - -export interface IGotoCode { - file: string; - line: number; -} - -export interface ICopyCode { - source: string; -} - -export enum VariableExplorerStateKeys { - height = 'NBVariableHeights' -} - -export enum ExportNotebookSettings { - lastSaveLocation = 'NBExportSaveLocation' -} - -export enum SysInfoReason { - Start, - Restart, - Interrupt, - New, - Connect -} - -export interface IAddedSysInfo { - type: SysInfoReason; - id: string; - sysInfoCell: ICell; - notebookIdentity: Uri; -} - -export interface IFinishCell { - cell: ICell; - notebookIdentity: Uri; -} - -export interface IExecuteInfo { - code: string; - id: string; - file: string; - line: number; - debug: boolean; -} - -export interface IRemoteAddCode extends IExecuteInfo { - originator: string; -} - -export interface IRemoteReexecuteCode extends IExecuteInfo { - originator: string; -} - -export interface ISubmitNewCell { - code: string; - id: string; -} - -export interface IReExecuteCells { - cellIds: string[]; -} - -export interface IProvideCompletionItemsRequest { - position: monacoEditor.Position; - context: monacoEditor.languages.CompletionContext; - requestId: string; - cellId: string; -} - -export interface IProvideHoverRequest { - position: monacoEditor.Position; - requestId: string; - cellId: string; - wordAtPosition: string | undefined; -} - -export interface IProvideSignatureHelpRequest { - position: monacoEditor.Position; - context: monacoEditor.languages.SignatureHelpContext; - requestId: string; - cellId: string; -} - -export interface ICancelIntellisenseRequest { - requestId: string; -} - -export interface IResolveCompletionItemRequest { - position: monacoEditor.Position; - item: monacoEditor.languages.CompletionItem; - requestId: string; - cellId: string; -} - -export interface IProvideCompletionItemsResponse { - list: monacoEditor.languages.CompletionList; - requestId: string; -} - -export interface IProvideHoverResponse { - hover: monacoEditor.languages.Hover; - requestId: string; -} - -export interface IProvideSignatureHelpResponse { - signatureHelp: monacoEditor.languages.SignatureHelp; - requestId: string; -} - -export interface IResolveCompletionItemResponse { - item: monacoEditor.languages.CompletionItem; - requestId: string; -} - -export interface IPosition { - line: number; - ch: number; -} - -export interface IEditCell { - changes: monacoEditor.editor.IModelContentChange[]; - id: string; -} - -export interface IAddCell { - fullText: string; - currentText: string; - cell: ICell; -} - -export interface IRemoveCell { - id: string; -} - -export interface ISwapCells { - firstCellId: string; - secondCellId: string; -} - -export interface IInsertCell { - cell: ICell; - code: string; - index: number; - codeCellAboveId: string | undefined; -} - -export interface IShowDataViewer { - variable: IJupyterVariable; - columnSize: number; -} - -export interface IRefreshVariablesRequest { - newExecutionCount?: number; -} - -export interface ILoadAllCells { - cells: ICell[]; - isNotebookTrusted?: boolean; -} - -export interface IScrollToCell { - id: string; -} - -export interface INotebookIdentity { - resource: Uri; - type: 'interactive' | 'native'; -} - -export interface ISaveAll { - cells: ICell[]; -} - -export interface INativeCommand { - command: NativeKeyboardCommandTelemetry | NativeMouseCommandTelemetry; -} - -export interface IRenderComplete { - ids: string[]; -} - -export interface IDebugStateChange { - oldState: DebugState; - newState: DebugState; -} - -export interface IFocusedCellEditor { - cellId: string; -} - -export interface INotebookModelChange { - oldDirty: boolean; - newDirty: boolean; - source: 'undo' | 'user' | 'redo'; - model?: INotebookModel; -} - -export interface INotebookModelSaved extends INotebookModelChange { - kind: 'save'; -} -export interface INotebookModelSavedAs extends INotebookModelChange { - kind: 'saveAs'; - target: Uri; - sourceUri: Uri; -} - -export interface INotebookModelRemoveAllChange extends INotebookModelChange { - kind: 'remove_all'; - oldCells: ICell[]; - newCellId: string; -} -export interface INotebookModelModifyChange extends INotebookModelChange { - kind: 'modify'; - newCells: ICell[]; - oldCells: ICell[]; -} -export interface INotebookModelCellExecutionCountChange extends INotebookModelChange { - kind: 'updateCellExecutionCount'; - cellId: string; - executionCount?: number; -} - -export interface INotebookModelClearChange extends INotebookModelChange { - kind: 'clear'; - oldCells: ICell[]; -} - -export interface INotebookModelSwapChange extends INotebookModelChange { - kind: 'swap'; - firstCellId: string; - secondCellId: string; -} - -export interface INotebookModelRemoveChange extends INotebookModelChange { - kind: 'remove'; - cell: ICell; - index: number; -} - -export interface INotebookModelInsertChange extends INotebookModelChange { - kind: 'insert'; - cell: ICell; - index: number; - codeCellAboveId?: string; -} - -export interface INotebookModelAddChange extends INotebookModelChange { - kind: 'add'; - cell: ICell; - fullText: string; - currentText: string; -} - -export interface INotebookModelChangeTypeChange extends INotebookModelChange { - kind: 'changeCellType'; - cell: ICell; -} - -export interface IEditorPosition { - /** - * line number (starts at 1) - */ - readonly lineNumber: number; - /** - * column (the first character in a line is between column 1 and column 2) - */ - readonly column: number; -} - -export interface IEditorRange { - /** - * Line number on which the range starts (starts at 1). - */ - readonly startLineNumber: number; - /** - * Column on which the range starts in line `startLineNumber` (starts at 1). - */ - readonly startColumn: number; - /** - * Line number on which the range ends. - */ - readonly endLineNumber: number; - /** - * Column on which the range ends in line `endLineNumber`. - */ - readonly endColumn: number; -} - -export interface IEditorContentChange { - /** - * The range that got replaced. - */ - readonly range: IEditorRange; - /** - * The offset of the range that got replaced. - */ - readonly rangeOffset: number; - /** - * The length of the range that got replaced. - */ - readonly rangeLength: number; - /** - * The new text for the range. - */ - readonly text: string; - /** - * The cursor position to be set after the change - */ - readonly position: IEditorPosition; -} - -export interface INotebookModelEditChange extends INotebookModelChange { - kind: 'edit'; - forward: IEditorContentChange[]; - reverse: IEditorContentChange[]; - id: string; -} - -export interface INotebookModelVersionChange extends INotebookModelChange { - kind: 'version'; - kernelConnection?: KernelConnectionMetadata; -} - -export type NotebookModelChange = - | INotebookModelSaved - | INotebookModelSavedAs - | INotebookModelModifyChange - | INotebookModelRemoveAllChange - | INotebookModelClearChange - | INotebookModelSwapChange - | INotebookModelRemoveChange - | INotebookModelInsertChange - | INotebookModelAddChange - | INotebookModelEditChange - | INotebookModelVersionChange - | INotebookModelChangeTypeChange - | INotebookModelCellExecutionCountChange; - -export interface IRunByLine { - cell: ICell; - expectedExecutionCount: number; -} - -export interface ILoadTmLanguageResponse { - languageId: string; - scopeName: string; // Name in the tmlanguage scope file (scope.python instead of python) - // tslint:disable-next-line: no-any - languageConfiguration: ILanguageConfigurationDto; - languageJSON: string; // Contents of the tmLanguage.json file - extensions: string[]; // Array of file extensions that map to this language -} - -// Map all messages to specific payloads -export class IInteractiveWindowMapping { - public [IPyWidgetMessages.IPyWidgets_kernelOptions]: KernelSocketOptions; - public [IPyWidgetMessages.IPyWidgets_WidgetScriptSourceRequest]: { moduleName: string; moduleVersion: string }; - public [IPyWidgetMessages.IPyWidgets_WidgetScriptSourceResponse]: WidgetScriptSource; - public [IPyWidgetMessages.IPyWidgets_Ready]: never | undefined; - public [IPyWidgetMessages.IPyWidgets_onRestartKernel]: never | undefined; - public [IPyWidgetMessages.IPyWidgets_onKernelChanged]: never | undefined; - public [IPyWidgetMessages.IPyWidgets_registerCommTarget]: string; - // tslint:disable-next-line: no-any - public [IPyWidgetMessages.IPyWidgets_binary_msg]: { id: string; data: any }; - public [IPyWidgetMessages.IPyWidgets_msg]: { id: string; data: string }; - public [IPyWidgetMessages.IPyWidgets_msg_received]: { id: string }; - public [IPyWidgetMessages.IPyWidgets_iopub_msg_handled]: { id: string }; - public [IPyWidgetMessages.IPyWidgets_RegisterMessageHook]: string; - public [IPyWidgetMessages.IPyWidgets_ExtensionOperationHandled]: { id: string; type: IPyWidgetMessages }; - public [IPyWidgetMessages.IPyWidgets_RemoveMessageHook]: { hookMsgId: string; lastHookedMsgId: string | undefined }; - public [IPyWidgetMessages.IPyWidgets_MessageHookCall]: { - requestId: string; - parentId: string; - msg: KernelMessage.IIOPubMessage; - }; - public [IPyWidgetMessages.IPyWidgets_MessageHookResult]: { - requestId: string; - parentId: string; - msgType: string; - result: boolean; - }; - public [IPyWidgetMessages.IPyWidgets_mirror_execute]: { id: string; msg: KernelMessage.IExecuteRequestMsg }; - public [InteractiveWindowMessages.StartCell]: ICell; - public [InteractiveWindowMessages.ForceVariableRefresh]: never | undefined; - public [InteractiveWindowMessages.FinishCell]: IFinishCell; - public [InteractiveWindowMessages.UpdateCellWithExecutionResults]: ICell; - public [InteractiveWindowMessages.GotoCodeCell]: IGotoCode; - public [InteractiveWindowMessages.CopyCodeCell]: ICopyCode; - public [InteractiveWindowMessages.NotebookExecutionActivated]: INotebookIdentity & { owningResource: Resource }; - public [InteractiveWindowMessages.RestartKernel]: never | undefined; - public [InteractiveWindowMessages.SelectKernel]: IServerState | undefined; - public [InteractiveWindowMessages.SelectJupyterServer]: never | undefined; - public [InteractiveWindowMessages.OpenSettings]: string | undefined; - public [InteractiveWindowMessages.Export]: ICell[]; - public [InteractiveWindowMessages.ExportNotebookAs]: ICell[]; - public [InteractiveWindowMessages.GetAllCells]: never | undefined; - public [InteractiveWindowMessages.ReturnAllCells]: ICell[]; - public [InteractiveWindowMessages.DeleteAllCells]: IAddCellAction; - public [InteractiveWindowMessages.Undo]: never | undefined; - public [InteractiveWindowMessages.Redo]: never | undefined; - public [InteractiveWindowMessages.ExpandAll]: never | undefined; - public [InteractiveWindowMessages.CollapseAll]: never | undefined; - public [InteractiveWindowMessages.StartProgress]: never | undefined; - public [InteractiveWindowMessages.StopProgress]: never | undefined; - public [InteractiveWindowMessages.Interrupt]: never | undefined; - public [InteractiveWindowMessages.SettingsUpdated]: string; - public [InteractiveWindowMessages.SubmitNewCell]: ISubmitNewCell; - public [InteractiveWindowMessages.SendInfo]: IInteractiveWindowInfo; - public [InteractiveWindowMessages.Started]: never | undefined; - public [InteractiveWindowMessages.AddedSysInfo]: IAddedSysInfo; - public [InteractiveWindowMessages.RemoteAddCode]: IRemoteAddCode; - public [InteractiveWindowMessages.RemoteReexecuteCode]: IRemoteReexecuteCode; - public [InteractiveWindowMessages.Activate]: never | undefined; - public [InteractiveWindowMessages.ShowDataViewer]: IShowDataViewer; - public [InteractiveWindowMessages.GetVariablesRequest]: IJupyterVariablesRequest; - public [InteractiveWindowMessages.GetVariablesResponse]: IJupyterVariablesResponse; - public [InteractiveWindowMessages.VariableExplorerToggle]: boolean; - public [InteractiveWindowMessages.SetVariableExplorerHeight]: IVariableExplorerHeight; - public [InteractiveWindowMessages.VariableExplorerHeightResponse]: IVariableExplorerHeight; - public [CssMessages.GetCssRequest]: IGetCssRequest; - public [CssMessages.GetCssResponse]: IGetCssResponse; - public [CssMessages.GetMonacoThemeRequest]: IGetMonacoThemeRequest; - public [CssMessages.GetMonacoThemeResponse]: IGetMonacoThemeResponse; - public [InteractiveWindowMessages.ProvideCompletionItemsRequest]: IProvideCompletionItemsRequest; - public [InteractiveWindowMessages.CancelCompletionItemsRequest]: ICancelIntellisenseRequest; - public [InteractiveWindowMessages.ProvideCompletionItemsResponse]: IProvideCompletionItemsResponse; - public [InteractiveWindowMessages.ProvideHoverRequest]: IProvideHoverRequest; - public [InteractiveWindowMessages.CancelHoverRequest]: ICancelIntellisenseRequest; - public [InteractiveWindowMessages.ProvideHoverResponse]: IProvideHoverResponse; - public [InteractiveWindowMessages.ProvideSignatureHelpRequest]: IProvideSignatureHelpRequest; - public [InteractiveWindowMessages.CancelSignatureHelpRequest]: ICancelIntellisenseRequest; - public [InteractiveWindowMessages.ProvideSignatureHelpResponse]: IProvideSignatureHelpResponse; - public [InteractiveWindowMessages.ResolveCompletionItemRequest]: IResolveCompletionItemRequest; - public [InteractiveWindowMessages.CancelResolveCompletionItemRequest]: ICancelIntellisenseRequest; - public [InteractiveWindowMessages.ResolveCompletionItemResponse]: IResolveCompletionItemResponse; - public [InteractiveWindowMessages.LoadOnigasmAssemblyRequest]: never | undefined; - public [InteractiveWindowMessages.LoadOnigasmAssemblyResponse]: Buffer; - public [InteractiveWindowMessages.LoadTmLanguageRequest]: string; - public [InteractiveWindowMessages.LoadTmLanguageResponse]: ILoadTmLanguageResponse; - public [InteractiveWindowMessages.OpenLink]: string | undefined; - public [InteractiveWindowMessages.ShowPlot]: string | undefined; - public [InteractiveWindowMessages.SavePng]: string | undefined; - public [InteractiveWindowMessages.StartDebugging]: never | undefined; - public [InteractiveWindowMessages.StopDebugging]: never | undefined; - public [InteractiveWindowMessages.GatherCode]: ICell; - public [InteractiveWindowMessages.GatherCodeToScript]: ICell; - public [InteractiveWindowMessages.Gathering]: IChangeGatherStatus; - public [InteractiveWindowMessages.LaunchNotebookTrustPrompt]: never | undefined; - public [InteractiveWindowMessages.TrustNotebookComplete]: never | undefined; - public [InteractiveWindowMessages.LoadAllCells]: ILoadAllCells; - public [InteractiveWindowMessages.LoadAllCellsComplete]: ILoadAllCells; - public [InteractiveWindowMessages.ScrollToCell]: IScrollToCell; - public [InteractiveWindowMessages.ReExecuteCells]: IReExecuteCells; - public [InteractiveWindowMessages.NotebookIdentity]: INotebookIdentity; - public [InteractiveWindowMessages.NotebookClose]: INotebookIdentity; - public [InteractiveWindowMessages.NotebookDirty]: never | undefined; - public [InteractiveWindowMessages.NotebookClean]: never | undefined; - public [InteractiveWindowMessages.SaveAll]: ISaveAll; - public [InteractiveWindowMessages.Sync]: { - type: InteractiveWindowMessages | SharedMessages | CommonActionType; - // tslint:disable-next-line: no-any - payload: BaseReduxActionPayload; - }; - public [InteractiveWindowMessages.NativeCommand]: INativeCommand; - public [InteractiveWindowMessages.VariablesComplete]: never | undefined; - public [InteractiveWindowMessages.NotebookRunAllCells]: never | undefined; - public [InteractiveWindowMessages.NotebookRunSelectedCell]: never | undefined; - public [InteractiveWindowMessages.NotebookAddCellBelow]: IAddCellAction; - public [InteractiveWindowMessages.DoSave]: never | undefined; - public [InteractiveWindowMessages.ExecutionRendered]: never | undefined; - public [InteractiveWindowMessages.FocusedCellEditor]: IFocusedCellEditor; - public [InteractiveWindowMessages.SelectedCell]: IFocusedCellEditor; - public [InteractiveWindowMessages.OutputToggled]: never | undefined; - public [InteractiveWindowMessages.UnfocusedCellEditor]: never | undefined; - public [InteractiveWindowMessages.MonacoReady]: never | undefined; - public [InteractiveWindowMessages.ClearAllOutputs]: never | undefined; - public [InteractiveWindowMessages.UpdateKernel]: IServerState | undefined; - public [InteractiveWindowMessages.UpdateModel]: NotebookModelChange; - public [InteractiveWindowMessages.ReceivedUpdateModel]: never | undefined; - public [SharedMessages.UpdateSettings]: string; - public [SharedMessages.LocInit]: string; - public [InteractiveWindowMessages.UpdateDisplayData]: KernelMessage.IUpdateDisplayDataMsg; - public [InteractiveWindowMessages.IPyWidgetLoadSuccess]: LoadIPyWidgetClassLoadAction; - public [InteractiveWindowMessages.IPyWidgetLoadFailure]: ILoadIPyWidgetClassFailureAction; - public [InteractiveWindowMessages.IPyWidgetWidgetVersionNotSupported]: NotifyIPyWidgeWidgetVersionNotSupportedAction; - public [InteractiveWindowMessages.ConvertUriForUseInWebViewRequest]: Uri; - public [InteractiveWindowMessages.ConvertUriForUseInWebViewResponse]: { request: Uri; response: Uri }; - public [InteractiveWindowMessages.IPyWidgetRenderFailure]: Error; - public [InteractiveWindowMessages.IPyWidgetUnhandledKernelMessage]: KernelMessage.IMessage; - public [InteractiveWindowMessages.RunByLine]: IRunByLine; - public [InteractiveWindowMessages.Continue]: never | undefined; - public [InteractiveWindowMessages.ShowBreak]: { frames: DebugProtocol.StackFrame[]; cell: ICell }; - public [InteractiveWindowMessages.ShowContinue]: ICell; - public [InteractiveWindowMessages.Step]: never | undefined; - public [InteractiveWindowMessages.ShowingIp]: never | undefined; - public [InteractiveWindowMessages.KernelIdle]: never | undefined; - public [InteractiveWindowMessages.DebugStateChange]: IDebugStateChange; - public [InteractiveWindowMessages.HasCell]: string; - public [InteractiveWindowMessages.HasCellResponse]: { id: string; result: boolean }; -} diff --git a/src/client/datascience/interactive-common/linkProvider.ts b/src/client/datascience/interactive-common/linkProvider.ts deleted file mode 100644 index ac14dd430fae..000000000000 --- a/src/client/datascience/interactive-common/linkProvider.ts +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; - -import { inject, injectable } from 'inversify'; -import { commands, Event, EventEmitter, Position, Range, Selection, TextEditorRevealType, Uri } from 'vscode'; - -import { IApplicationShell, ICommandManager, IDocumentManager } from '../../common/application/types'; - -import * as localize from '../../common/utils/localize'; -import { noop } from '../../common/utils/misc'; -import { IDataScienceFileSystem, IInteractiveWindowListener } from '../types'; -import { InteractiveWindowMessages } from './interactiveWindowTypes'; - -const LineQueryRegex = /line=(\d+)/; - -// The following list of commands represent those that can be executed -// in a markdown cell using the syntax: https://command:[my.vscode.command]. -const linkCommandAllowList = [ - 'python.datascience.gatherquality', - 'python.datascience.latestExtension', - 'python.datascience.enableLoadingWidgetScriptsFromThirdPartySource' -]; - -// tslint:disable: no-any -@injectable() -export class LinkProvider implements IInteractiveWindowListener { - private postEmitter: EventEmitter<{ message: string; payload: any }> = new EventEmitter<{ - message: string; - payload: any; - }>(); - constructor( - @inject(IApplicationShell) private applicationShell: IApplicationShell, - @inject(IDataScienceFileSystem) private fs: IDataScienceFileSystem, - @inject(IDocumentManager) private documentManager: IDocumentManager, - @inject(ICommandManager) private commandManager: ICommandManager - ) { - noop(); - } - - public get postMessage(): Event<{ message: string; payload: any }> { - return this.postEmitter.event; - } - - public onMessage(message: string, payload?: any): void { - switch (message) { - case InteractiveWindowMessages.OpenLink: - if (payload) { - // Special case file URIs - const href: string = payload.toString(); - if (href.startsWith('file')) { - this.openFile(href); - } else if (href.startsWith('https://command:')) { - const temp: string = href.split(':')[2]; - const params: string[] = temp.includes('/?') ? temp.split('/?')[1].split(',') : []; - let command = temp.split('/?')[0]; - if (command.endsWith('/')) { - command = command.substring(0, command.length - 1); - } - if (linkCommandAllowList.includes(command)) { - commands.executeCommand(command, params); - } - } else { - this.applicationShell.openUrl(href); - } - } - break; - case InteractiveWindowMessages.SavePng: - if (payload) { - // Payload should contain the base 64 encoded string. Ask the user to save the file - const filtersObject: Record = {}; - filtersObject[localize.DataScience.pngFilter()] = ['png']; - - // Ask the user what file to save to - this.applicationShell - .showSaveDialog({ - saveLabel: localize.DataScience.savePngTitle(), - filters: filtersObject - }) - .then((f) => { - if (f) { - const buffer = new Buffer(payload.replace('data:image/png;base64', ''), 'base64'); - this.fs.writeFile(f, buffer).ignoreErrors(); - } - }); - } - break; - default: - break; - } - } - public dispose(): void | undefined { - noop(); - } - - private openFile(fileUri: string) { - const uri = Uri.parse(fileUri); - let selection: Range = new Range(new Position(0, 0), new Position(0, 0)); - if (uri.query) { - // Might have a line number query on the file name - const lineMatch = LineQueryRegex.exec(uri.query); - if (lineMatch) { - const lineNumber = parseInt(lineMatch[1], 10); - selection = new Range(new Position(lineNumber, 0), new Position(lineNumber, 0)); - } - } - - // Show the matching editor if there is one - let editor = this.documentManager.visibleTextEditors.find((e) => this.fs.arePathsSame(e.document.uri, uri)); - if (editor) { - this.documentManager - .showTextDocument(editor.document, { selection, viewColumn: editor.viewColumn }) - .then((e) => { - e.revealRange(selection, TextEditorRevealType.InCenter); - }); - } else { - // Not a visible editor, try opening otherwise - this.commandManager.executeCommand('vscode.open', uri).then(() => { - // See if that opened a text document - editor = this.documentManager.visibleTextEditors.find((e) => this.fs.arePathsSame(e.document.uri, uri)); - if (editor) { - // Force the selection to change - editor.revealRange(selection); - editor.selection = new Selection(selection.start, selection.start); - } - }); - } - } -} diff --git a/src/client/datascience/interactive-common/notebookProvider.ts b/src/client/datascience/interactive-common/notebookProvider.ts deleted file mode 100644 index 23a8cda190cb..000000000000 --- a/src/client/datascience/interactive-common/notebookProvider.ts +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { EventEmitter, Uri } from 'vscode'; -import { ServerStatus } from '../../../datascience-ui/interactive-common/mainState'; -import { IWorkspaceService } from '../../common/application/types'; -import { traceWarning } from '../../common/logger'; -import { IDisposableRegistry, Resource } from '../../common/types'; -import { noop } from '../../common/utils/misc'; -import { Identifiers } from '../constants'; -import { KernelConnectionMetadata } from '../jupyter/kernels/types'; -import { - ConnectNotebookProviderOptions, - GetNotebookOptions, - IJupyterNotebookProvider, - INotebook, - INotebookProvider, - INotebookProviderConnection, - IRawNotebookProvider -} from '../types'; - -@injectable() -export class NotebookProvider implements INotebookProvider { - private readonly notebooks = new Map>(); - private _notebookCreated = new EventEmitter<{ identity: Uri; notebook: INotebook }>(); - private readonly _onSessionStatusChanged = new EventEmitter<{ status: ServerStatus; notebook: INotebook }>(); - private _connectionMade = new EventEmitter(); - private _potentialKernelChanged = new EventEmitter<{ identity: Uri; kernelConnection: KernelConnectionMetadata }>(); - private _type: 'jupyter' | 'raw' = 'jupyter'; - public get activeNotebooks() { - return [...this.notebooks.values()]; - } - public get onSessionStatusChanged() { - return this._onSessionStatusChanged.event; - } - public get onPotentialKernelChanged() { - return this._potentialKernelChanged.event; - } - constructor( - @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry, - @inject(IRawNotebookProvider) private readonly rawNotebookProvider: IRawNotebookProvider, - @inject(IJupyterNotebookProvider) private readonly jupyterNotebookProvider: IJupyterNotebookProvider, - @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService - ) { - this.rawNotebookProvider - .supported() - .then((b) => (this._type = b ? 'raw' : 'jupyter')) - .ignoreErrors(); - } - public get onNotebookCreated() { - return this._notebookCreated.event; - } - - public get onConnectionMade() { - return this._connectionMade.event; - } - - public get type(): 'jupyter' | 'raw' { - return this._type; - } - - // Disconnect from the specified provider - public async disconnect(options: ConnectNotebookProviderOptions): Promise { - // Only need to disconnect from actual jupyter servers - if (!(await this.rawNotebookProvider.supported())) { - return this.jupyterNotebookProvider.disconnect(options); - } - } - - // Attempt to connect to our server provider, and if we do, return the connection info - public async connect(options: ConnectNotebookProviderOptions): Promise { - // Connect to either a jupyter server or a stubbed out raw notebook "connection" - if (await this.rawNotebookProvider.supported()) { - return this.rawNotebookProvider.connect({ - ...options, - onConnectionMade: this.fireConnectionMade.bind(this) - }); - } else { - return this.jupyterNotebookProvider.connect({ - ...options, - onConnectionMade: this.fireConnectionMade.bind(this) - }); - } - } - public disposeAssociatedNotebook(options: { identity: Uri }) { - const nbPromise = this.notebooks.get(options.identity.toString()); - if (!nbPromise) { - return; - } - this.notebooks.delete(options.identity.toString()); - nbPromise - .then((nb) => nb.dispose()) - .catch((ex) => traceWarning('Failed to dispose notebook in disposeAssociatedNotebook', ex)); - } - public async getOrCreateNotebook(options: GetNotebookOptions): Promise { - const rawKernel = await this.rawNotebookProvider.supported(); - - // Check our own promise cache - if (this.notebooks.get(options.identity.toString())) { - return this.notebooks.get(options.identity.toString())!!; - } - - // Check to see if our provider already has this notebook - const notebook = rawKernel - ? await this.rawNotebookProvider.getNotebook(options.identity, options.token) - : await this.jupyterNotebookProvider.getNotebook(options); - if (notebook) { - this.cacheNotebookPromise(options.identity, Promise.resolve(notebook)); - return notebook; - } - - // If get only, don't create a notebook - if (options.getOnly) { - return undefined; - } - - // We want to cache a Promise from the create functions - // but jupyterNotebookProvider.createNotebook can be undefined if the server is not available - // so check for our connection here first - if (!rawKernel) { - if (!(await this.jupyterNotebookProvider.connect(options))) { - return undefined; - } - } - - // Finally create if needed - let resource: Resource = options.resource; - if (options.identity.scheme === Identifiers.HistoryPurpose && !resource) { - // If we have any workspaces, then use the first available workspace. - // This is required, else using `undefined` as a resource when we have worksapce folders is a different meaning. - // This means interactive window doesn't properly support mult-root workspaces as we pick first workspace. - // Ideally we need to pick the resource of the corresponding Python file. - resource = this.workspaceService.hasWorkspaceFolders - ? this.workspaceService.workspaceFolders![0]!.uri - : undefined; - } - const promise = rawKernel - ? this.rawNotebookProvider.createNotebook( - options.identity, - resource, - options.disableUI, - options.metadata, - options.token - ) - : this.jupyterNotebookProvider.createNotebook(options); - - this.cacheNotebookPromise(options.identity, promise); - - return promise; - } - - // This method is here so that the kernel selector can pick a kernel and not have - // to know about any of the UI that's active. - public firePotentialKernelChanged(identity: Uri, kernel: KernelConnectionMetadata) { - this._potentialKernelChanged.fire({ identity, kernelConnection: kernel }); - } - - private fireConnectionMade() { - this._connectionMade.fire(); - } - - // Cache the promise that will return a notebook - private cacheNotebookPromise(identity: Uri, promise: Promise) { - this.notebooks.set(identity.toString(), promise); - - // Remove promise from cache if the same promise still exists. - const removeFromCache = () => { - const cachedPromise = this.notebooks.get(identity.toString()); - if (cachedPromise === promise) { - this.notebooks.delete(identity.toString()); - } - }; - - promise - .then((nb) => { - // If the notebook is disposed, remove from cache. - nb.onDisposed(removeFromCache); - nb.onSessionStatusChanged( - (e) => this._onSessionStatusChanged.fire({ status: e, notebook: nb }), - this, - this.disposables - ); - this._notebookCreated.fire({ identity: identity, notebook: nb }); - }) - .catch(noop); - - // If promise fails, then remove the promise from cache. - promise.catch(removeFromCache); - } -} diff --git a/src/client/datascience/interactive-common/notebookServerProvider.ts b/src/client/datascience/interactive-common/notebookServerProvider.ts deleted file mode 100644 index c2f50a9ae286..000000000000 --- a/src/client/datascience/interactive-common/notebookServerProvider.ts +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { CancellationToken, ConfigurationTarget, EventEmitter, Uri } from 'vscode'; -import { IApplicationShell } from '../../common/application/types'; -import { CancellationError, wrapCancellationTokens } from '../../common/cancellation'; -import { traceInfo } from '../../common/logger'; -import { IConfigurationService } from '../../common/types'; -import * as localize from '../../common/utils/localize'; -import { IInterpreterService } from '../../interpreter/contracts'; -import { sendTelemetryEvent } from '../../telemetry'; -import { Identifiers, Settings, Telemetry } from '../constants'; -import { JupyterInstallError } from '../jupyter/jupyterInstallError'; -import { JupyterSelfCertsError } from '../jupyter/jupyterSelfCertsError'; -import { JupyterZMQBinariesNotFoundError } from '../jupyter/jupyterZMQBinariesNotFoundError'; -import { ProgressReporter } from '../progress/progressReporter'; -import { - GetServerOptions, - IJupyterExecution, - IJupyterServerProvider, - INotebook, - INotebookServer, - INotebookServerOptions -} from '../types'; - -@injectable() -export class NotebookServerProvider implements IJupyterServerProvider { - private serverPromise: Promise | undefined; - private allowingUI = false; - private _notebookCreated = new EventEmitter<{ identity: Uri; notebook: INotebook }>(); - constructor( - @inject(ProgressReporter) private readonly progressReporter: ProgressReporter, - @inject(IConfigurationService) private readonly configuration: IConfigurationService, - @inject(IJupyterExecution) private readonly jupyterExecution: IJupyterExecution, - @inject(IApplicationShell) private readonly applicationShell: IApplicationShell, - @inject(IInterpreterService) private readonly interpreterService: IInterpreterService - ) {} - public get onNotebookCreated() { - return this._notebookCreated.event; - } - - public async getOrCreateServer( - options: GetServerOptions, - token?: CancellationToken - ): Promise { - const serverOptions = this.getNotebookServerOptions(); - - // If we are just fetching or only want to create for local, see if exists - if (options.getOnly || (options.localOnly && serverOptions.uri)) { - return this.jupyterExecution.getServer(serverOptions); - } else { - // Otherwise create a new server - return this.createServer(options, token).then((val) => { - // If we created a new server notify of our first time provider connection - if (val && options.onConnectionMade) { - options.onConnectionMade(); - } - - return val; - }); - } - } - - private async createServer( - options: GetServerOptions, - token?: CancellationToken - ): Promise { - // When we finally try to create a server, update our flag indicating if we're going to allow UI or not. This - // allows the server to be attempted without a UI, but a future request can come in and use the same startup - this.allowingUI = options.disableUI ? this.allowingUI : true; - - if (!this.serverPromise) { - // Start a server - this.serverPromise = this.startServer(token); - } - try { - return await this.serverPromise; - } catch (e) { - // Don't cache the error - this.serverPromise = undefined; - throw e; - } - } - - private async startServer(token?: CancellationToken): Promise { - const serverOptions = this.getNotebookServerOptions(); - - traceInfo(`Checking for server existence.`); - - // Status depends upon if we're about to connect to existing server or not. - const progressReporter = this.allowingUI - ? (await this.jupyterExecution.getServer(serverOptions)) - ? this.progressReporter.createProgressIndicator(localize.DataScience.connectingToJupyter()) - : this.progressReporter.createProgressIndicator(localize.DataScience.startingJupyter()) - : undefined; - - // Check to see if we support ipykernel or not - try { - traceInfo(`Checking for server usability.`); - - const usable = await this.checkUsable(serverOptions); - if (!usable) { - traceInfo('Server not usable (should ask for install now)'); - // Indicate failing. - throw new JupyterInstallError( - localize.DataScience.jupyterNotSupported().format(await this.jupyterExecution.getNotebookError()), - localize.DataScience.pythonInteractiveHelpLink() - ); - } - // Then actually start the server - traceInfo(`Starting notebook server.`); - const result = await this.jupyterExecution.connectToNotebookServer( - serverOptions, - wrapCancellationTokens(progressReporter?.token, token) - ); - traceInfo(`Server started.`); - return result; - } catch (e) { - progressReporter?.dispose(); // NOSONAR - // If user cancelled, then do nothing. - if (progressReporter && progressReporter.token.isCancellationRequested && e instanceof CancellationError) { - return; - } - - // Also tell jupyter execution to reset its search. Otherwise we've just cached - // the failure there - await this.jupyterExecution.refreshCommands(); - - if (e instanceof JupyterSelfCertsError) { - // On a self cert error, warn the user and ask if they want to change the setting - const enableOption: string = localize.DataScience.jupyterSelfCertEnable(); - const closeOption: string = localize.DataScience.jupyterSelfCertClose(); - this.applicationShell - .showErrorMessage( - localize.DataScience.jupyterSelfCertFail().format(e.message), - enableOption, - closeOption - ) - .then((value) => { - if (value === enableOption) { - sendTelemetryEvent(Telemetry.SelfCertsMessageEnabled); - this.configuration - .updateSetting( - 'dataScience.allowUnauthorizedRemoteConnection', - true, - undefined, - ConfigurationTarget.Workspace - ) - .ignoreErrors(); - } else if (value === closeOption) { - sendTelemetryEvent(Telemetry.SelfCertsMessageClose); - } - }); - throw e; - } else { - throw e; - } - } finally { - progressReporter?.dispose(); // NOSONAR - } - } - - private async checkUsable(options: INotebookServerOptions): Promise { - try { - if (options && !options.uri) { - const usableInterpreter = await this.jupyterExecution.getUsableJupyterPython(); - return usableInterpreter ? true : false; - } else { - return true; - } - } catch (e) { - if (e instanceof JupyterZMQBinariesNotFoundError) { - throw e; - } - const activeInterpreter = await this.interpreterService.getActiveInterpreter(undefined); - // Can't find a usable interpreter, show the error. - if (activeInterpreter) { - const displayName = activeInterpreter.displayName - ? activeInterpreter.displayName - : activeInterpreter.path; - throw new Error( - localize.DataScience.jupyterNotSupportedBecauseOfEnvironment().format(displayName, e.toString()) - ); - } else { - throw new JupyterInstallError( - localize.DataScience.jupyterNotSupported().format(await this.jupyterExecution.getNotebookError()), - localize.DataScience.pythonInteractiveHelpLink() - ); - } - } - } - - private getNotebookServerOptions(): INotebookServerOptions { - // Since there's one server per session, don't use a resource to figure out these settings - const settings = this.configuration.getSettings(undefined); - let serverURI: string | undefined = settings.datascience.jupyterServerURI; - const useDefaultConfig: boolean | undefined = settings.datascience.useDefaultConfigForJupyter; - - // For the local case pass in our URI as undefined, that way connect doesn't have to check the setting - if (serverURI.toLowerCase() === Settings.JupyterServerLocalLaunch) { - serverURI = undefined; - } - - return { - uri: serverURI, - skipUsingDefaultConfig: !useDefaultConfig, - purpose: Identifiers.HistoryPurpose, - allowUI: this.allowUI.bind(this) - }; - } - - private allowUI(): boolean { - return this.allowingUI; - } -} diff --git a/src/client/datascience/interactive-common/notebookUsageTracker.ts b/src/client/datascience/interactive-common/notebookUsageTracker.ts deleted file mode 100644 index cef91dcddaa7..000000000000 --- a/src/client/datascience/interactive-common/notebookUsageTracker.ts +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -import { EventEmitter } from 'vscode'; -import { IExtensionSingleActivationService } from '../../activation/types'; -import { IWorkspaceService } from '../../common/application/types'; -import { IDisposableRegistry } from '../../common/types'; -import { sendTelemetryEvent } from '../../telemetry'; -import { Telemetry } from '../constants'; -import { INotebookEditor, INotebookEditorProvider } from '../types'; - -/** - * This class tracks opened notebooks, # of notebooks in workspace & # of executed notebooks. - */ -@injectable() -export class NotebookUsageTracker implements IExtensionSingleActivationService { - protected readonly _onDidChangeActiveNotebookEditor = new EventEmitter(); - protected readonly _onDidOpenNotebookEditor = new EventEmitter(); - private readonly executedEditors = new Set(); - private notebookCount: number = 0; - private openedNotebookCount: number = 0; - constructor( - @inject(INotebookEditorProvider) private readonly editorProvider: INotebookEditorProvider, - @inject(IWorkspaceService) private readonly workspace: IWorkspaceService, - @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry - ) {} - - public async activate(): Promise { - // Look through the file system for ipynb files to see how many we have in the workspace. Don't wait - // on this though. - const findFilesPromise = this.workspace.findFiles('**/*.ipynb'); - if (findFilesPromise && findFilesPromise.then) { - findFilesPromise.then((r) => (this.notebookCount += r.length)); - } - this.editorProvider.onDidOpenNotebookEditor(this.onEditorOpened, this, this.disposables); - } - public dispose() { - // Send a bunch of telemetry - if (this.openedNotebookCount) { - sendTelemetryEvent(Telemetry.NotebookOpenCount, undefined, { count: this.openedNotebookCount }); - } - if (this.executedEditors.size) { - sendTelemetryEvent(Telemetry.NotebookRunCount, undefined, { count: this.executedEditors.size }); - } - if (this.notebookCount) { - sendTelemetryEvent(Telemetry.NotebookWorkspaceCount, undefined, { count: this.notebookCount }); - } - } - private onEditorOpened(editor: INotebookEditor): void { - this.openedNotebookCount += 1; - if (editor.model?.isUntitled) { - this.notebookCount += 1; - } - if (!this.executedEditors.has(editor)) { - editor.executed((e) => this.executedEditors.add(e), this, this.disposables); - } - } -} diff --git a/src/client/datascience/interactive-common/serialization.ts b/src/client/datascience/interactive-common/serialization.ts deleted file mode 100644 index da853c44f6ea..000000000000 --- a/src/client/datascience/interactive-common/serialization.ts +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import { CharacterPair, CommentRule, EnterAction, IndentationRule, LanguageConfiguration, OnEnterRule } from 'vscode'; - -// tslint:disable: no-any - -export interface IRegExpDto { - pattern: string; - flags?: string; -} -export interface IIndentationRuleDto { - decreaseIndentPattern: IRegExpDto; - increaseIndentPattern: IRegExpDto; - indentNextLinePattern?: IRegExpDto; - unIndentedLinePattern?: IRegExpDto; -} -export interface IOnEnterRuleDto { - beforeText: IRegExpDto; - afterText?: IRegExpDto; - oneLineAboveText?: IRegExpDto; - action: EnterAction; -} -export interface ILanguageConfigurationDto { - comments?: CommentRule; - brackets?: CharacterPair[]; - wordPattern?: IRegExpDto; - indentationRules?: IIndentationRuleDto; - onEnterRules?: IOnEnterRuleDto[]; - __electricCharacterSupport?: { - brackets?: any; - docComment?: { - scope: string; - open: string; - lineStart: string; - close?: string; - }; - }; - __characterPairSupport?: { - autoClosingPairs: { - open: string; - close: string; - notIn?: string[]; - }[]; - }; -} -// Copied most of this from VS code directly. - -function regExpFlags(regexp: RegExp): string { - return ( - (regexp.global ? 'g' : '') + - (regexp.ignoreCase ? 'i' : '') + - (regexp.multiline ? 'm' : '') + - ((regexp as any) /* standalone editor compilation */.unicode ? 'u' : '') - ); -} - -function _serializeRegExp(regExp: RegExp): IRegExpDto { - return { - pattern: regExp.source, - flags: regExpFlags(regExp) - }; -} - -function _serializeIndentationRule(indentationRule: IndentationRule): IIndentationRuleDto { - return { - decreaseIndentPattern: _serializeRegExp(indentationRule.decreaseIndentPattern), - increaseIndentPattern: _serializeRegExp(indentationRule.increaseIndentPattern), - indentNextLinePattern: indentationRule.indentNextLinePattern - ? _serializeRegExp(indentationRule.indentNextLinePattern) - : undefined, - unIndentedLinePattern: indentationRule.unIndentedLinePattern - ? _serializeRegExp(indentationRule.unIndentedLinePattern) - : undefined - }; -} - -function _serializeOnEnterRule(onEnterRule: OnEnterRule): IOnEnterRuleDto { - return { - beforeText: _serializeRegExp(onEnterRule.beforeText), - afterText: onEnterRule.afterText ? _serializeRegExp(onEnterRule.afterText) : undefined, - oneLineAboveText: (onEnterRule as any).oneLineAboveText - ? _serializeRegExp((onEnterRule as any).oneLineAboveText) - : undefined, - action: onEnterRule.action - }; -} - -function _serializeOnEnterRules(onEnterRules: OnEnterRule[]): IOnEnterRuleDto[] { - return onEnterRules.map(_serializeOnEnterRule); -} - -function _reviveRegExp(regExp: IRegExpDto): RegExp { - return new RegExp(regExp.pattern, regExp.flags); -} - -function _reviveIndentationRule(indentationRule: IIndentationRuleDto): IndentationRule { - return { - decreaseIndentPattern: _reviveRegExp(indentationRule.decreaseIndentPattern), - increaseIndentPattern: _reviveRegExp(indentationRule.increaseIndentPattern), - indentNextLinePattern: indentationRule.indentNextLinePattern - ? _reviveRegExp(indentationRule.indentNextLinePattern) - : undefined, - unIndentedLinePattern: indentationRule.unIndentedLinePattern - ? _reviveRegExp(indentationRule.unIndentedLinePattern) - : undefined - }; -} - -function _reviveOnEnterRule(onEnterRule: IOnEnterRuleDto): OnEnterRule { - return { - beforeText: _reviveRegExp(onEnterRule.beforeText), - afterText: onEnterRule.afterText ? _reviveRegExp(onEnterRule.afterText) : undefined, - oneLineAboveText: onEnterRule.oneLineAboveText ? _reviveRegExp(onEnterRule.oneLineAboveText) : undefined, - action: onEnterRule.action - } as any; -} - -function _reviveOnEnterRules(onEnterRules: IOnEnterRuleDto[]): OnEnterRule[] { - return onEnterRules.map(_reviveOnEnterRule); -} - -export function serializeLanguageConfiguration( - configuration: LanguageConfiguration | undefined -): ILanguageConfigurationDto { - return { - ...configuration, - wordPattern: configuration?.wordPattern ? _serializeRegExp(configuration.wordPattern) : undefined, - indentationRules: configuration?.indentationRules - ? _serializeIndentationRule(configuration.indentationRules) - : undefined, - onEnterRules: configuration?.onEnterRules ? _serializeOnEnterRules(configuration.onEnterRules) : undefined - }; -} - -export function deserializeLanguageConfiguration(configuration: ILanguageConfigurationDto): LanguageConfiguration { - return { - ...configuration, - wordPattern: configuration.wordPattern ? _reviveRegExp(configuration.wordPattern) : undefined, - indentationRules: configuration.indentationRules - ? _reviveIndentationRule(configuration.indentationRules) - : undefined, - onEnterRules: configuration.onEnterRules ? _reviveOnEnterRules(configuration.onEnterRules) : undefined - }; -} diff --git a/src/client/datascience/interactive-common/showPlotListener.ts b/src/client/datascience/interactive-common/showPlotListener.ts deleted file mode 100644 index 7151c9ffd108..000000000000 --- a/src/client/datascience/interactive-common/showPlotListener.ts +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; - -import { inject, injectable } from 'inversify'; -import { Event, EventEmitter } from 'vscode'; - -import { noop } from '../../common/utils/misc'; -import { IInteractiveWindowListener, IPlotViewerProvider } from '../types'; -import { InteractiveWindowMessages } from './interactiveWindowTypes'; - -// tslint:disable: no-any -@injectable() -export class ShowPlotListener implements IInteractiveWindowListener { - private postEmitter: EventEmitter<{ message: string; payload: any }> = new EventEmitter<{ - message: string; - payload: any; - }>(); - constructor(@inject(IPlotViewerProvider) private provider: IPlotViewerProvider) { - noop(); - } - - public get postMessage(): Event<{ message: string; payload: any }> { - return this.postEmitter.event; - } - - public onMessage(message: string, payload?: any): void { - switch (message) { - case InteractiveWindowMessages.ShowPlot: - if (payload) { - this.provider.showPlot(payload).ignoreErrors(); - break; - } - break; - default: - break; - } - } - public dispose(): void | undefined { - noop(); - } -} diff --git a/src/client/datascience/interactive-common/synchronization.ts b/src/client/datascience/interactive-common/synchronization.ts deleted file mode 100644 index 7a0fd4394ae2..000000000000 --- a/src/client/datascience/interactive-common/synchronization.ts +++ /dev/null @@ -1,303 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import { - CommonActionType, - CommonActionTypeMapping -} from '../../../datascience-ui/interactive-common/redux/reducers/types'; -import { CssMessages, SharedMessages } from '../messages'; -import { - IInteractiveWindowMapping, - InteractiveWindowMessages, - IPyWidgetMessages, - NotebookModelChange -} from './interactiveWindowTypes'; - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -export enum MessageType { - /** - * Action dispatched as result of some user action. - */ - other = 0, - /** - * Action dispatched to re-broadcast a message across other editors of the same file in the same session. - */ - syncAcrossSameNotebooks = 1 << 0, - /** - * Action dispatched to re-broadcast a message across other sessions (live share). - */ - syncWithLiveShare = 1 << 1, - noIdea = 1 << 2 -} - -// tslint:disable-next-line: no-any -type MessageAction = (payload: any) => boolean; - -type MessageMapping = { - [P in keyof T]: MessageType | MessageAction; -}; - -export type IInteractiveActionMapping = MessageMapping; - -// Do not change to a dictionary or a record. -// The current structure ensures all new enums added will be categorized. -// This way, if a new message is added, we'll make the decision early on whether it needs to be synchronized and how. -// Rather than waiting for users to report issues related to new messages. -const messageWithMessageTypes: MessageMapping & MessageMapping = { - [CommonActionType.ADD_AND_FOCUS_NEW_CELL]: MessageType.other, - [CommonActionType.ADD_NEW_CELL]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, - [CommonActionType.ARROW_DOWN]: MessageType.syncWithLiveShare, - [CommonActionType.ARROW_UP]: MessageType.syncWithLiveShare, - [CommonActionType.CHANGE_CELL_TYPE]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, - [CommonActionType.CLICK_CELL]: MessageType.syncWithLiveShare, - [CommonActionType.CONTINUE]: MessageType.other, - [CommonActionType.DELETE_CELL]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, - [CommonActionType.CODE_CREATED]: MessageType.noIdea, - [CommonActionType.COPY_CELL_CODE]: MessageType.other, - [CommonActionType.EDITOR_LOADED]: MessageType.other, - [CommonActionType.EDIT_CELL]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, - [CommonActionType.EXECUTE_CELL_AND_ADVANCE]: MessageType.other, - [CommonActionType.EXECUTE_ABOVE]: MessageType.other, - [CommonActionType.EXECUTE_ALL_CELLS]: MessageType.other, - [CommonActionType.EXECUTE_CELL]: MessageType.other, - [CommonActionType.EXECUTE_CELL_AND_BELOW]: MessageType.other, - [CommonActionType.EXPORT]: MessageType.other, - [CommonActionType.EXPORT_NOTEBOOK_AS]: MessageType.other, - [CommonActionType.FOCUS_CELL]: MessageType.syncWithLiveShare, - [CommonActionType.GATHER_CELL]: MessageType.other, - [CommonActionType.GATHER_CELL_TO_SCRIPT]: MessageType.other, - [CommonActionType.GET_VARIABLE_DATA]: MessageType.other, - [CommonActionType.GOTO_CELL]: MessageType.syncWithLiveShare, - [CommonActionType.INSERT_ABOVE_AND_FOCUS_NEW_CELL]: MessageType.other, - [CommonActionType.INSERT_ABOVE]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, - [CommonActionType.INSERT_ABOVE_FIRST_AND_FOCUS_NEW_CELL]: MessageType.other, - [CommonActionType.INSERT_ABOVE_FIRST]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, - [CommonActionType.INSERT_BELOW]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, - [CommonActionType.INSERT_BELOW_AND_FOCUS_NEW_CELL]: MessageType.other, - [CommonActionType.INTERRUPT_KERNEL]: MessageType.other, - [CommonActionType.LAUNCH_NOTEBOOK_TRUST_PROMPT]: MessageType.other, - [CommonActionType.LOADED_ALL_CELLS]: MessageType.other, - [CommonActionType.LINK_CLICK]: MessageType.other, - [CommonActionType.MOVE_CELL_DOWN]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, - [CommonActionType.MOVE_CELL_UP]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, - [CommonActionType.OPEN_SETTINGS]: MessageType.other, - [CommonActionType.RESTART_KERNEL]: MessageType.other, - [CommonActionType.RUN_BY_LINE]: MessageType.other, - [CommonActionType.SAVE]: MessageType.other, - [CommonActionType.SCROLL]: MessageType.syncWithLiveShare, - [CommonActionType.SELECT_CELL]: MessageType.syncWithLiveShare, - [CommonActionType.SELECT_SERVER]: MessageType.other, - [CommonActionType.SEND_COMMAND]: MessageType.other, - [CommonActionType.SHOW_DATA_VIEWER]: MessageType.other, - [CommonActionType.STEP]: MessageType.other, - [CommonActionType.SUBMIT_INPUT]: MessageType.other, - [CommonActionType.TOGGLE_INPUT_BLOCK]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, - [CommonActionType.TOGGLE_LINE_NUMBERS]: MessageType.syncWithLiveShare, - [CommonActionType.TOGGLE_OUTPUT]: MessageType.syncWithLiveShare, - [CommonActionType.TOGGLE_VARIABLE_EXPLORER]: MessageType.syncWithLiveShare, - [CommonActionType.SET_VARIABLE_EXPLORER_HEIGHT]: MessageType.other, - [CommonActionType.UNFOCUS_CELL]: MessageType.syncWithLiveShare, - [CommonActionType.UNMOUNT]: MessageType.other, - [CommonActionType.PostOutgoingMessage]: MessageType.other, - [CommonActionType.REFRESH_VARIABLES]: MessageType.other, - [CommonActionType.FOCUS_INPUT]: MessageType.other, - [CommonActionType.LOAD_IPYWIDGET_CLASS_SUCCESS]: MessageType.other, - [CommonActionType.LOAD_IPYWIDGET_CLASS_FAILURE]: MessageType.other, - [CommonActionType.IPYWIDGET_WIDGET_VERSION_NOT_SUPPORTED]: MessageType.other, - [CommonActionType.IPYWIDGET_RENDER_FAILURE]: MessageType.other, - - // Types from InteractiveWindowMessages - [InteractiveWindowMessages.Activate]: MessageType.other, - [InteractiveWindowMessages.AddedSysInfo]: MessageType.other, - [InteractiveWindowMessages.CancelCompletionItemsRequest]: MessageType.other, - [InteractiveWindowMessages.CancelHoverRequest]: MessageType.other, - [InteractiveWindowMessages.CancelResolveCompletionItemRequest]: MessageType.other, - [InteractiveWindowMessages.CancelSignatureHelpRequest]: MessageType.other, - [InteractiveWindowMessages.ClearAllOutputs]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, - [InteractiveWindowMessages.CollapseAll]: MessageType.syncWithLiveShare, - [InteractiveWindowMessages.Continue]: MessageType.other, - [InteractiveWindowMessages.CopyCodeCell]: MessageType.other, - [InteractiveWindowMessages.DebugStateChange]: MessageType.other, - [InteractiveWindowMessages.DeleteAllCells]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, - [InteractiveWindowMessages.DoSave]: MessageType.other, - [InteractiveWindowMessages.ExecutionRendered]: MessageType.other, - [InteractiveWindowMessages.ExpandAll]: MessageType.syncWithLiveShare, - [InteractiveWindowMessages.Export]: MessageType.other, - [InteractiveWindowMessages.ExportNotebookAs]: MessageType.other, - [InteractiveWindowMessages.FinishCell]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, - [InteractiveWindowMessages.FocusedCellEditor]: MessageType.syncWithLiveShare, - [InteractiveWindowMessages.GatherCode]: MessageType.other, - [InteractiveWindowMessages.GatherCodeToScript]: MessageType.other, - [InteractiveWindowMessages.Gathering]: MessageType.other, - [InteractiveWindowMessages.GetAllCells]: MessageType.other, - [InteractiveWindowMessages.ForceVariableRefresh]: MessageType.other, - [InteractiveWindowMessages.GetVariablesRequest]: MessageType.other, - [InteractiveWindowMessages.GetVariablesResponse]: MessageType.other, - [InteractiveWindowMessages.GotoCodeCell]: MessageType.syncWithLiveShare, - [InteractiveWindowMessages.GotoCodeCell]: MessageType.syncWithLiveShare, - [InteractiveWindowMessages.HasCell]: MessageType.other, - [InteractiveWindowMessages.HasCellResponse]: MessageType.other, - [InteractiveWindowMessages.Interrupt]: MessageType.other, - [InteractiveWindowMessages.IPyWidgetLoadSuccess]: MessageType.other, - [InteractiveWindowMessages.IPyWidgetLoadFailure]: MessageType.other, - [InteractiveWindowMessages.IPyWidgetRenderFailure]: MessageType.other, - [InteractiveWindowMessages.IPyWidgetUnhandledKernelMessage]: MessageType.other, - [InteractiveWindowMessages.IPyWidgetWidgetVersionNotSupported]: MessageType.other, - [InteractiveWindowMessages.KernelIdle]: MessageType.other, - [InteractiveWindowMessages.LaunchNotebookTrustPrompt]: MessageType.other, - [InteractiveWindowMessages.TrustNotebookComplete]: MessageType.other, - [InteractiveWindowMessages.LoadAllCells]: MessageType.other, - [InteractiveWindowMessages.LoadAllCellsComplete]: MessageType.other, - [InteractiveWindowMessages.LoadOnigasmAssemblyRequest]: MessageType.other, - [InteractiveWindowMessages.LoadOnigasmAssemblyResponse]: MessageType.other, - [InteractiveWindowMessages.LoadTmLanguageRequest]: MessageType.other, - [InteractiveWindowMessages.LoadTmLanguageResponse]: MessageType.other, - [InteractiveWindowMessages.MonacoReady]: MessageType.other, - [InteractiveWindowMessages.NativeCommand]: MessageType.other, - [InteractiveWindowMessages.NotebookAddCellBelow]: - MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, - [InteractiveWindowMessages.NotebookClean]: MessageType.other, - [InteractiveWindowMessages.NotebookDirty]: MessageType.other, - [InteractiveWindowMessages.NotebookExecutionActivated]: MessageType.other, - [InteractiveWindowMessages.NotebookIdentity]: MessageType.other, - [InteractiveWindowMessages.NotebookClose]: MessageType.other, - [InteractiveWindowMessages.NotebookRunAllCells]: MessageType.other, - [InteractiveWindowMessages.NotebookRunSelectedCell]: MessageType.other, - [InteractiveWindowMessages.OpenLink]: MessageType.other, - [InteractiveWindowMessages.OpenSettings]: MessageType.other, - [InteractiveWindowMessages.OutputToggled]: MessageType.other, - [InteractiveWindowMessages.ProvideCompletionItemsRequest]: MessageType.other, - [InteractiveWindowMessages.ProvideCompletionItemsResponse]: MessageType.other, - [InteractiveWindowMessages.ProvideHoverRequest]: MessageType.other, - [InteractiveWindowMessages.ProvideHoverResponse]: MessageType.other, - [InteractiveWindowMessages.ProvideSignatureHelpRequest]: MessageType.other, - [InteractiveWindowMessages.ProvideSignatureHelpResponse]: MessageType.other, - [InteractiveWindowMessages.ReExecuteCells]: MessageType.other, - [InteractiveWindowMessages.Redo]: MessageType.other, - [InteractiveWindowMessages.RemoteAddCode]: MessageType.other, - [InteractiveWindowMessages.ReceivedUpdateModel]: MessageType.other, - [InteractiveWindowMessages.RemoteReexecuteCode]: MessageType.other, - [InteractiveWindowMessages.ResolveCompletionItemRequest]: MessageType.other, - [InteractiveWindowMessages.ResolveCompletionItemResponse]: MessageType.other, - [InteractiveWindowMessages.RestartKernel]: MessageType.other, - [InteractiveWindowMessages.ReturnAllCells]: MessageType.other, - [InteractiveWindowMessages.RunByLine]: MessageType.other, - [InteractiveWindowMessages.SaveAll]: MessageType.other, - [InteractiveWindowMessages.SavePng]: MessageType.other, - [InteractiveWindowMessages.ScrollToCell]: MessageType.syncWithLiveShare, - [InteractiveWindowMessages.SelectedCell]: MessageType.other, - [InteractiveWindowMessages.SelectJupyterServer]: MessageType.other, - [InteractiveWindowMessages.SelectKernel]: MessageType.other, - [InteractiveWindowMessages.SendInfo]: MessageType.other, - [InteractiveWindowMessages.SettingsUpdated]: MessageType.other, - [InteractiveWindowMessages.ShowBreak]: MessageType.other, - [InteractiveWindowMessages.ShowingIp]: MessageType.other, - [InteractiveWindowMessages.ShowContinue]: MessageType.other, - [InteractiveWindowMessages.ShowDataViewer]: MessageType.other, - [InteractiveWindowMessages.ShowPlot]: MessageType.other, - [InteractiveWindowMessages.StartCell]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, - [InteractiveWindowMessages.StartDebugging]: MessageType.other, - [InteractiveWindowMessages.StartProgress]: MessageType.other, - [InteractiveWindowMessages.Started]: MessageType.other, - [InteractiveWindowMessages.Step]: MessageType.other, - [InteractiveWindowMessages.StopDebugging]: MessageType.other, - [InteractiveWindowMessages.StopProgress]: MessageType.other, - [InteractiveWindowMessages.SubmitNewCell]: MessageType.other, - [InteractiveWindowMessages.Sync]: MessageType.other, - [InteractiveWindowMessages.Undo]: MessageType.other, - [InteractiveWindowMessages.UnfocusedCellEditor]: MessageType.syncWithLiveShare, - [InteractiveWindowMessages.UpdateCellWithExecutionResults]: - MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, - [InteractiveWindowMessages.UpdateModel]: checkSyncUpdateModel, - [InteractiveWindowMessages.UpdateKernel]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, - [InteractiveWindowMessages.UpdateDisplayData]: MessageType.syncWithLiveShare, - [InteractiveWindowMessages.VariableExplorerToggle]: MessageType.other, - [InteractiveWindowMessages.SetVariableExplorerHeight]: MessageType.other, - [InteractiveWindowMessages.VariableExplorerHeightResponse]: MessageType.other, - [InteractiveWindowMessages.VariablesComplete]: MessageType.other, - [InteractiveWindowMessages.ConvertUriForUseInWebViewRequest]: MessageType.other, - [InteractiveWindowMessages.ConvertUriForUseInWebViewResponse]: MessageType.other, - // Types from CssMessages - [CssMessages.GetCssRequest]: MessageType.other, - [CssMessages.GetCssResponse]: MessageType.other, - [CssMessages.GetMonacoThemeRequest]: MessageType.other, - [CssMessages.GetMonacoThemeResponse]: MessageType.other, - // Types from Shared Messages - [SharedMessages.LocInit]: MessageType.other, - [SharedMessages.Started]: MessageType.other, - [SharedMessages.UpdateSettings]: MessageType.other, - // IpyWidgets - [IPyWidgetMessages.IPyWidgets_kernelOptions]: MessageType.syncAcrossSameNotebooks, - [IPyWidgetMessages.IPyWidgets_Ready]: MessageType.noIdea, - [IPyWidgetMessages.IPyWidgets_WidgetScriptSourceRequest]: MessageType.noIdea, - [IPyWidgetMessages.IPyWidgets_WidgetScriptSourceResponse]: MessageType.syncAcrossSameNotebooks, - [IPyWidgetMessages.IPyWidgets_onKernelChanged]: MessageType.syncAcrossSameNotebooks, - [IPyWidgetMessages.IPyWidgets_onRestartKernel]: MessageType.syncAcrossSameNotebooks, - [IPyWidgetMessages.IPyWidgets_msg]: MessageType.noIdea, - [IPyWidgetMessages.IPyWidgets_binary_msg]: MessageType.noIdea, - [IPyWidgetMessages.IPyWidgets_msg_received]: MessageType.noIdea, - [IPyWidgetMessages.IPyWidgets_iopub_msg_handled]: MessageType.noIdea, - [IPyWidgetMessages.IPyWidgets_registerCommTarget]: MessageType.noIdea, - [IPyWidgetMessages.IPyWidgets_MessageHookCall]: MessageType.noIdea, - [IPyWidgetMessages.IPyWidgets_MessageHookResult]: MessageType.noIdea, - [IPyWidgetMessages.IPyWidgets_RegisterMessageHook]: MessageType.noIdea, - [IPyWidgetMessages.IPyWidgets_ExtensionOperationHandled]: MessageType.noIdea, - [IPyWidgetMessages.IPyWidgets_RemoveMessageHook]: MessageType.noIdea, - [IPyWidgetMessages.IPyWidgets_mirror_execute]: MessageType.noIdea -}; - -/** - * Function to check if a NotebookModelChange should be sync'd across editors or not - */ -function checkSyncUpdateModel(payload: NotebookModelChange): boolean { - // Only sync user changes - return payload.source === 'user'; -} - -/** - * If the original message was a sync message, then do not send messages to extension. - * We allow messages to be sent to extension ONLY when the original message was triggered by the user. - * - * @export - * @param {MessageType} [messageType] - * @returns - */ -export function checkToPostBasedOnOriginalMessageType(messageType?: MessageType): boolean { - if (!messageType) { - return true; - } - if ( - (messageType & MessageType.syncAcrossSameNotebooks) === MessageType.syncAcrossSameNotebooks || - (messageType & MessageType.syncWithLiveShare) === MessageType.syncWithLiveShare - ) { - return false; - } - - return true; -} - -// tslint:disable-next-line: no-any -export function shouldRebroadcast(message: keyof IInteractiveWindowMapping, payload: any): [boolean, MessageType] { - // Get the configured type for this message (whether it should be re-broadcasted or not). - const messageTypeOrFunc: MessageType | undefined | MessageAction = messageWithMessageTypes[message]; - const messageType = - typeof messageTypeOrFunc !== 'function' ? (messageTypeOrFunc as number) : MessageType.syncAcrossSameNotebooks; - // Support for liveshare is turned off for now, we can enable that later. - // I.e. we only support synchronizing across editors in the same session. - if ( - messageType === undefined || - (messageType & MessageType.syncAcrossSameNotebooks) !== MessageType.syncAcrossSameNotebooks - ) { - return [false, MessageType.other]; - } - - if (typeof messageTypeOrFunc === 'function') { - return [messageTypeOrFunc(payload), messageType]; - } - - return [ - (messageType & MessageType.syncAcrossSameNotebooks) > 0 || (messageType & MessageType.syncWithLiveShare) > 0, - messageType - ]; -} diff --git a/src/client/datascience/interactive-common/types.ts b/src/client/datascience/interactive-common/types.ts deleted file mode 100644 index 3c22935868a2..000000000000 --- a/src/client/datascience/interactive-common/types.ts +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { CommonActionType } from '../../../datascience-ui/interactive-common/redux/reducers/types'; -import { CssMessages, SharedMessages } from '../messages'; -import { InteractiveWindowMessages } from './interactiveWindowTypes'; -import { MessageType } from './synchronization'; - -// Stuff common to React and Extensions. - -type BaseData = { - messageType?: MessageType; - /** - * Tells us whether this message is incoming for reducer use or - * whether this is a message that needs to be sent out to extension (from reducer). - */ - messageDirection?: 'incoming' | 'outgoing'; -}; - -type BaseDataWithPayload = { - messageType?: MessageType; - /** - * Tells us whether this message is incoming for reducer use or - * whether this is a message that needs to be sent out to extension (from reducer). - */ - messageDirection?: 'incoming' | 'outgoing'; - data: T; -}; - -// This forms the base content of every payload in all dispatchers. -export type BaseReduxActionPayload = T extends never - ? T extends undefined - ? BaseData - : BaseDataWithPayload - : BaseDataWithPayload; -export type SyncPayload = { - type: InteractiveWindowMessages | SharedMessages | CommonActionType | CssMessages; - // tslint:disable-next-line: no-any - payload: BaseReduxActionPayload; -}; diff --git a/src/client/datascience/interactive-ipynb/autoSaveService.ts b/src/client/datascience/interactive-ipynb/autoSaveService.ts deleted file mode 100644 index 6920c74a21f1..000000000000 --- a/src/client/datascience/interactive-ipynb/autoSaveService.ts +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { ConfigurationChangeEvent, Event, EventEmitter, TextEditor, Uri, WindowState } from 'vscode'; -import { IApplicationShell, IDocumentManager, IWorkspaceService } from '../../common/application/types'; -import '../../common/extensions'; - -import { IDisposable } from '../../common/types'; -import { INotebookIdentity, InteractiveWindowMessages } from '../interactive-common/interactiveWindowTypes'; -import { - FileSettings, - IDataScienceFileSystem, - IInteractiveWindowListener, - INotebookEditor, - INotebookEditorProvider, - WebViewViewChangeEventArgs -} from '../types'; - -// tslint:disable: no-any - -/** - * Sends notifications to Notebooks to save the notebook. - * Based on auto save settings, this class will regularly check for changes and send a save requet. - * If window state changes or active editor changes, then notify notebooks (if auto save is configured to do so). - * Monitor save and modified events on editor to determine its current dirty state. - * - * @export - * @class AutoSaveService - * @implements {IInteractiveWindowListener} - */ -@injectable() -export class AutoSaveService implements IInteractiveWindowListener { - private postEmitter: EventEmitter<{ message: string; payload: any }> = new EventEmitter<{ - message: string; - payload: any; - }>(); - private disposables: IDisposable[] = []; - private notebookUri?: Uri; - private timeout?: ReturnType; - private visible: boolean | undefined; - private active: boolean | undefined; - constructor( - @inject(IApplicationShell) appShell: IApplicationShell, - @inject(IDocumentManager) documentManager: IDocumentManager, - @inject(INotebookEditorProvider) private readonly notebookEditorProvider: INotebookEditorProvider, - @inject(IDataScienceFileSystem) private readonly fs: IDataScienceFileSystem, - @inject(IWorkspaceService) private readonly workspace: IWorkspaceService - ) { - this.workspace.onDidChangeConfiguration(this.onSettingsChanded.bind(this), this, this.disposables); - this.disposables.push(appShell.onDidChangeWindowState(this.onDidChangeWindowState.bind(this))); - this.disposables.push(documentManager.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor.bind(this))); - } - - public get postMessage(): Event<{ message: string; payload: any }> { - return this.postEmitter.event; - } - - public onMessage(message: string, payload?: any): void { - if (message === InteractiveWindowMessages.NotebookIdentity) { - this.notebookUri = (payload as INotebookIdentity).resource; - } else if (message === InteractiveWindowMessages.NotebookClose) { - this.dispose(); - } else if (message === InteractiveWindowMessages.LoadAllCellsComplete) { - const notebook = this.getNotebook(); - if (!notebook) { - return; - } - this.disposables.push(notebook.modified(this.onNotebookModified, this, this.disposables)); - this.disposables.push(notebook.saved(this.onNotebookSaved, this, this.disposables)); - } - } - public onViewStateChanged(args: WebViewViewChangeEventArgs) { - let changed = false; - if (this.visible !== args.current.visible) { - this.visible = args.current.visible; - changed = true; - } - if (this.active !== args.current.active) { - this.active = args.current.active; - changed = true; - } - if (changed) { - const settings = this.getAutoSaveSettings(); - if (settings && settings.autoSave === 'onFocusChange') { - this.save(); - } - } - } - public dispose(): void | undefined { - this.disposables.filter((item) => !!item).forEach((item) => item.dispose()); - this.clearTimeout(); - } - private onNotebookModified(_: INotebookEditor) { - // If we haven't started a timer, then start if necessary. - if (!this.timeout) { - this.setTimer(); - } - } - private onNotebookSaved(_: INotebookEditor) { - // If we haven't started a timer, then start if necessary. - if (!this.timeout) { - this.setTimer(); - } - } - private getNotebook(): INotebookEditor | undefined { - const uri = this.notebookUri; - if (!uri) { - return; - } - return this.notebookEditorProvider.editors.find((item) => - this.fs.areLocalPathsSame(item.file.fsPath, uri.fsPath) - ); - } - private getAutoSaveSettings(): FileSettings { - const filesConfig = this.workspace.getConfiguration('files', this.notebookUri); - return { - autoSave: filesConfig.get('autoSave', 'off'), - autoSaveDelay: filesConfig.get('autoSaveDelay', 1000) - }; - } - private onSettingsChanded(e: ConfigurationChangeEvent) { - if ( - e.affectsConfiguration('files.autoSave', this.notebookUri) || - e.affectsConfiguration('files.autoSaveDelay', this.notebookUri) - ) { - // Reset the timer, as we may have increased it, turned it off or other. - this.clearTimeout(); - this.setTimer(); - } - } - private setTimer() { - const settings = this.getAutoSaveSettings(); - if (!settings || settings.autoSave === 'off') { - return; - } - if (settings && settings.autoSave === 'afterDelay') { - // Add a timeout to save after n milli seconds. - // Do not use setInterval, as that will cause all handlers to queue up. - this.timeout = setTimeout(() => { - this.save(); - }, settings.autoSaveDelay); - } - } - private clearTimeout() { - if (this.timeout) { - clearTimeout(this.timeout); - this.timeout = undefined; - } - } - private save() { - this.clearTimeout(); - const notebook = this.getNotebook(); - if (notebook && notebook.isDirty && !notebook.isUntitled) { - // Notify webview to perform a save. - this.postEmitter.fire({ message: InteractiveWindowMessages.DoSave, payload: undefined }); - } else { - this.setTimer(); - } - } - private onDidChangeWindowState(_state: WindowState) { - const settings = this.getAutoSaveSettings(); - if (settings && (settings.autoSave === 'onWindowChange' || settings.autoSave === 'onFocusChange')) { - this.save(); - } - } - private onDidChangeActiveTextEditor(_e?: TextEditor) { - const settings = this.getAutoSaveSettings(); - if (settings && settings.autoSave === 'onFocusChange') { - this.save(); - } - } -} diff --git a/src/client/datascience/interactive-ipynb/digestStorage.ts b/src/client/datascience/interactive-ipynb/digestStorage.ts deleted file mode 100644 index 7d0def08b1fa..000000000000 --- a/src/client/datascience/interactive-ipynb/digestStorage.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { createHash, randomBytes } from 'crypto'; -import { inject, injectable } from 'inversify'; -import * as os from 'os'; -import * as path from 'path'; -import { Uri } from 'vscode'; -import { traceError, traceInfo } from '../../common/logger'; -import { isFileNotFoundError } from '../../common/platform/errors'; -import { IExtensionContext } from '../../common/types'; -import { IDataScienceFileSystem, IDigestStorage } from '../types'; - -@injectable() -export class DigestStorage implements IDigestStorage { - public readonly key: Promise; - private digestDir: Promise; - private loggedFileLocations = new Set(); - - constructor( - @inject(IDataScienceFileSystem) private fs: IDataScienceFileSystem, - @inject(IExtensionContext) private extensionContext: IExtensionContext - ) { - this.key = this.initKey(); - this.digestDir = this.initDir(); - } - - public async saveDigest(uri: Uri, signature: string) { - const fileLocation = await this.getFileLocation(uri); - // Since the signature is a hex digest, the character 'z' is being used to delimit the start and end of a single digest - try { - await this.saveDigestInner(uri, fileLocation, signature); - } catch (err) { - // The nbsignatures dir is only initialized on extension activation. - // If the user deletes it to reset trust, the next attempt to trust - // an untrusted notebook in the same session will fail because the parent - // directory does not exist. - if (isFileNotFoundError(err)) { - // Gracefully recover from such errors by reinitializing directory and retrying - await this.initDir(); - await this.saveDigestInner(uri, fileLocation, signature); - } else { - traceError(err); - } - } - } - - public async containsDigest(uri: Uri, signature: string) { - const fileLocation = await this.getFileLocation(uri); - try { - const digests = await this.fs.readLocalFile(fileLocation); - return digests.indexOf(`z${signature}z`) >= 0; - } catch (err) { - if (!isFileNotFoundError(err)) { - traceError(err); // Don't log the error if the file simply doesn't exist - } - return false; - } - } - - private async saveDigestInner(uri: Uri, fileLocation: string, signature: string) { - await this.fs.appendLocalFile(fileLocation, `z${signature}z\n`); - if (!this.loggedFileLocations.has(fileLocation)) { - traceInfo(`Wrote trust for ${uri.toString()} to ${fileLocation}`); - this.loggedFileLocations.add(fileLocation); - } - } - - private async getFileLocation(uri: Uri): Promise { - const normalizedName = os.platform() === 'win32' ? uri.fsPath.toLowerCase() : uri.fsPath; - const hashedName = createHash('sha256').update(normalizedName).digest('hex'); - return path.join(await this.digestDir, hashedName); - } - - private async initDir(): Promise { - const defaultDigestDirLocation = this.getDefaultLocation('nbsignatures'); - if (!(await this.fs.localDirectoryExists(defaultDigestDirLocation))) { - await this.fs.createLocalDirectory(defaultDigestDirLocation); - } - return defaultDigestDirLocation; - } - - /** - * Get or create a local secret key, used in computing HMAC hashes of trusted - * checkpoints in the notebook's execution history - */ - private async initKey(): Promise { - const defaultKeyFileLocation = this.getDefaultLocation('nbsecret'); - - if (await this.fs.localFileExists(defaultKeyFileLocation)) { - // if the keyfile already exists, bail out - return this.fs.readLocalFile(defaultKeyFileLocation); - } else { - // If it doesn't exist, create one - // Key must be generated from a cryptographically secure pseudorandom function: - // https://nodejs.org/api/crypto.html#crypto_crypto_randombytes_size_callback - // No callback is provided so random bytes will be generated synchronously - const key = randomBytes(1024).toString('hex'); - await this.fs.writeLocalFile(defaultKeyFileLocation, key); - return key; - } - } - - private getDefaultLocation(fileName: string) { - const dir = this.extensionContext.globalStoragePath; - if (dir) { - return path.join(dir, fileName); - } - throw new Error('Unable to locate extension global storage path for trusted digest storage'); - } -} diff --git a/src/client/datascience/interactive-ipynb/nativeEditor.ts b/src/client/datascience/interactive-ipynb/nativeEditor.ts deleted file mode 100644 index 096fcb4e6cd9..000000000000 --- a/src/client/datascience/interactive-ipynb/nativeEditor.ts +++ /dev/null @@ -1,827 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as path from 'path'; -import { - CancellationToken, - CancellationTokenSource, - Event, - EventEmitter, - Memento, - Uri, - ViewColumn, - WebviewPanel -} from 'vscode'; -import '../../common/extensions'; - -import * as uuid from 'uuid/v4'; -import { createErrorOutput } from '../../../datascience-ui/common/cellFactory'; -import { - IApplicationShell, - ICommandManager, - IDocumentManager, - ILiveShareApi, - IWebviewPanelProvider, - IWorkspaceService -} from '../../common/application/types'; -import { ContextKey } from '../../common/contextKey'; -import { traceError, traceInfo } from '../../common/logger'; - -import { - IAsyncDisposableRegistry, - IConfigurationService, - IDisposableRegistry, - IExperimentsManager, - Resource -} from '../../common/types'; -import { StopWatch } from '../../common/utils/stopWatch'; -import { EXTENSION_ROOT_DIR } from '../../constants'; -import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; -import { Commands, EditorContexts, Identifiers, Telemetry } from '../constants'; -import { InteractiveBase } from '../interactive-common/interactiveBase'; -import { - INativeCommand, - INotebookIdentity, - InteractiveWindowMessages, - IReExecuteCells, - IRunByLine, - ISubmitNewCell, - NotebookModelChange, - SysInfoReason, - VariableExplorerStateKeys -} from '../interactive-common/interactiveWindowTypes'; -import { - CellState, - ICell, - ICodeCssGenerator, - IDataScienceErrorHandler, - IDataScienceFileSystem, - IInteractiveWindowInfo, - IInteractiveWindowListener, - IJupyterDebugger, - IJupyterVariableDataProviderFactory, - IJupyterVariables, - INotebookEditor, - INotebookEditorProvider, - INotebookExporter, - INotebookExtensibility, - INotebookImporter, - INotebookMetadataLive, - INotebookModel, - INotebookProvider, - IStatusProvider, - IThemeFinder, - ITrustService, - WebViewViewChangeEventArgs -} from '../types'; -import { NativeEditorSynchronizer } from './nativeEditorSynchronizer'; - -import type { nbformat } from '@jupyterlab/coreutils'; -// tslint:disable-next-line: no-require-imports -import cloneDeep = require('lodash/cloneDeep'); -import { concatMultilineString, splitMultilineString } from '../../../datascience-ui/common'; -import { ServerStatus } from '../../../datascience-ui/interactive-common/mainState'; -import { isTestExecution, PYTHON_LANGUAGE } from '../../common/constants'; -import { translateKernelLanguageToMonaco } from '../common'; -import { IDataViewerFactory } from '../data-viewing/types'; -import { getCellHashProvider } from '../editor-integration/cellhashprovider'; -import { KernelSelector } from '../jupyter/kernels/kernelSelector'; -import { KernelConnectionMetadata } from '../jupyter/kernels/types'; - -const nativeEditorDir = path.join(EXTENSION_ROOT_DIR, 'out', 'datascience-ui', 'notebook'); -export class NativeEditor extends InteractiveBase implements INotebookEditor { - public get onDidChangeViewState(): Event { - return this._onDidChangeViewState.event; - } - public get notebookExtensibility(): INotebookExtensibility { - return this.nbExtensibility; - } - - public get visible(): boolean { - return this.viewState.visible; - } - - public get active(): boolean { - return this.viewState.active; - } - - public get file(): Uri { - if (this.model) { - return this.model.file; - } - return Uri.file(''); - } - - public get isUntitled(): boolean { - return this.model ? this.model.isUntitled : false; - } - - public get closed(): Event { - return this.closedEvent.event; - } - - public get executed(): Event { - return this.executedEvent.event; - } - - public get modified(): Event { - return this.modifiedEvent.event; - } - public get saved(): Event { - return this.savedEvent.event; - } - - public get isDirty(): boolean { - return this.model ? this.model.isDirty : false; - } - public get model(): Readonly { - return this._model; - } - public readonly type: 'old' | 'custom' = 'custom'; - protected savedEvent: EventEmitter = new EventEmitter(); - protected closedEvent: EventEmitter = new EventEmitter(); - protected modifiedEvent: EventEmitter = new EventEmitter(); - - private sentExecuteCellTelemetry: boolean = false; - private _onDidChangeViewState = new EventEmitter(); - private executedEvent: EventEmitter = new EventEmitter(); - private startupTimer: StopWatch = new StopWatch(); - private loadedAllCells: boolean = false; - private executeCancelTokens = new Set(); - private loadPromise: Promise; - private previouslyNotTrusted: boolean = false; - - constructor( - listeners: IInteractiveWindowListener[], - liveShare: ILiveShareApi, - applicationShell: IApplicationShell, - documentManager: IDocumentManager, - provider: IWebviewPanelProvider, - disposables: IDisposableRegistry, - cssGenerator: ICodeCssGenerator, - themeFinder: IThemeFinder, - statusProvider: IStatusProvider, - fs: IDataScienceFileSystem, - configuration: IConfigurationService, - commandManager: ICommandManager, - jupyterExporter: INotebookExporter, - workspaceService: IWorkspaceService, - private readonly synchronizer: NativeEditorSynchronizer, - private editorProvider: INotebookEditorProvider, - dataExplorerFactory: IDataViewerFactory, - jupyterVariableDataProviderFactory: IJupyterVariableDataProviderFactory, - jupyterVariables: IJupyterVariables, - jupyterDebugger: IJupyterDebugger, - protected readonly importer: INotebookImporter, - errorHandler: IDataScienceErrorHandler, - globalStorage: Memento, - workspaceStorage: Memento, - experimentsManager: IExperimentsManager, - asyncRegistry: IAsyncDisposableRegistry, - notebookProvider: INotebookProvider, - useCustomEditorApi: boolean, - private trustService: ITrustService, - private _model: INotebookModel, - webviewPanel: WebviewPanel | undefined, - selector: KernelSelector, - private nbExtensibility: INotebookExtensibility - ) { - super( - listeners, - liveShare, - applicationShell, - documentManager, - provider, - disposables, - cssGenerator, - themeFinder, - statusProvider, - fs, - configuration, - jupyterExporter, - workspaceService, - dataExplorerFactory, - jupyterVariableDataProviderFactory, - jupyterVariables, - jupyterDebugger, - errorHandler, - commandManager, - globalStorage, - workspaceStorage, - nativeEditorDir, - [ - path.join(nativeEditorDir, 'require.js'), - path.join(nativeEditorDir, 'ipywidgets.js'), - path.join(nativeEditorDir, 'monaco.bundle.js'), - path.join(nativeEditorDir, 'commons.initial.bundle.js'), - path.join(nativeEditorDir, 'nativeEditor.js') - ], - path.basename(_model.file.fsPath), - ViewColumn.Active, - experimentsManager, - notebookProvider, - useCustomEditorApi, - selector - ); - asyncRegistry.push(this); - asyncRegistry.push(this.trustService.onDidSetNotebookTrust(this.monitorChangesToTrust, this)); - this.synchronizer.subscribeToUserActions(this, this.postMessage.bind(this)); - - traceInfo(`Loading web panel for ${this.model.file}`); - - // Load the web panel using our file path so it can find - // relative files next to the notebook. - this.loadPromise = super - .loadWebPanel(path.dirname(this.file.fsPath), webviewPanel) - .catch((e) => this.errorHandler.handleError(e)); - - // Sign up for dirty events - this._model.changed(this.modelChanged.bind(this)); - this.previouslyNotTrusted = !this._model.isTrusted; - } - - public async show(preserveFocus: boolean = true) { - await this.loadPromise; - return super.show(preserveFocus); - } - public dispose(): Promise { - super.dispose(); - this.model?.dispose(); // NOSONAR - return this.close(); - } - - // tslint:disable-next-line: no-any - public onMessage(message: string, payload: any) { - super.onMessage(message, payload); - switch (message) { - case InteractiveWindowMessages.Started: - if (this.model) { - // Load our cells, but don't wait for this to finish, otherwise the window won't load. - this.sendInitialCellsToWebView([...this.model.cells], this.model.isTrusted) - .then(() => { - // May alread be dirty, if so send a message - if (this.model?.isDirty) { - this.postMessage(InteractiveWindowMessages.NotebookDirty).ignoreErrors(); - } - }) - .catch((exc) => traceError('Error loading cells: ', exc)); - } - break; - case InteractiveWindowMessages.Sync: - this.synchronizer.notifyUserAction(payload, this); - break; - - case InteractiveWindowMessages.ReExecuteCells: - this.executedEvent.fire(this); - break; - - case InteractiveWindowMessages.SaveAll: - this.handleMessage(message, payload, this.saveAll); - break; - - case InteractiveWindowMessages.ExportNotebookAs: - this.handleMessage(message, payload, this.exportAs); - break; - - case InteractiveWindowMessages.UpdateModel: - this.handleMessage(message, payload, this.updateModel); - break; - - case InteractiveWindowMessages.NativeCommand: - this.handleMessage(message, payload, this.logNativeCommand); - break; - - // call this to update the whole document for intellisense - case InteractiveWindowMessages.LoadAllCellsComplete: - this.handleMessage(message, payload, this.loadCellsComplete); - break; - - case InteractiveWindowMessages.LaunchNotebookTrustPrompt: - this.handleMessage(message, payload, this.launchNotebookTrustPrompt); - break; - - case InteractiveWindowMessages.RestartKernel: - this.interruptExecution(); - break; - - case InteractiveWindowMessages.Interrupt: - this.interruptExecution(); - break; - - case InteractiveWindowMessages.RunByLine: - this.handleMessage(message, payload, this.handleRunByLine); - break; - - default: - break; - } - } - - public get notebookMetadata(): INotebookMetadataLive | undefined { - return this.model.metadata; - } - - public async updateNotebookOptions(kernelConnection: KernelConnectionMetadata): Promise { - if (this.model) { - const change: NotebookModelChange = { - kind: 'version', - kernelConnection, - oldDirty: this.model.isDirty, - newDirty: true, - source: 'user' - }; - this.updateModel(change); - } - } - - public async hasCell(id: string): Promise { - if (this.model && this.model.cells.find((c) => c.id === id)) { - return true; - } - return false; - } - - public runAllCells() { - this.postMessage(InteractiveWindowMessages.NotebookRunAllCells).ignoreErrors(); - } - - public runSelectedCell() { - this.postMessage(InteractiveWindowMessages.NotebookRunSelectedCell).ignoreErrors(); - } - - public addCellBelow() { - this.postMessage(InteractiveWindowMessages.NotebookAddCellBelow, { newCellId: uuid() }).ignoreErrors(); - } - - public get owningResource(): Resource { - // Resource to use for loading and our identity are the same. - return this.notebookIdentity.resource; - } - - public expandAllCells(): void { - throw Error('Not implemented Exception'); - } - public collapseAllCells(): void { - throw Error('Not implemented Exception'); - } - - protected addSysInfo(reason: SysInfoReason): Promise { - // We need to send a message when restarting - if (reason === SysInfoReason.Restart || reason === SysInfoReason.New) { - this.postMessage(InteractiveWindowMessages.RestartKernel).ignoreErrors(); - } - - // These are not supported. - return Promise.resolve(); - } - - protected async createNotebookIfProviderConnectionExists() { - if (this._model.isTrusted) { - await super.createNotebookIfProviderConnectionExists(); - } - } - - protected submitCode( - code: string, - file: string, - line: number, - id?: string, - data?: nbformat.ICodeCell | nbformat.IRawCell | nbformat.IMarkdownCell, - debugOptions?: { runByLine: boolean; hashFileName?: string }, - cancelToken?: CancellationToken - ): Promise { - const stopWatch = new StopWatch(); - return super - .submitCode(code, file, line, id, data, debugOptions, cancelToken) - .finally(() => this.sendPerceivedCellExecute(stopWatch)); - } - - @captureTelemetry(Telemetry.SubmitCellThroughInput, undefined, false) - // tslint:disable-next-line:no-any - protected submitNewCell(info: ISubmitNewCell) { - // If there's any payload, it has the code and the id - if (info && info.code && info.id) { - try { - // Activate the other side, and send as if came from a file - this.editorProvider - .show(this.file) - .then((_v) => { - this.shareMessage(InteractiveWindowMessages.RemoteAddCode, { - code: info.code, - file: Identifiers.EmptyFileName, - line: 0, - id: info.id, - originator: this.id, - debug: false - }); - }) - .ignoreErrors(); - // Send to ourselves. - this.submitCode(info.code, Identifiers.EmptyFileName, 0, info.id).ignoreErrors(); - } catch (exc) { - this.errorHandler.handleError(exc).ignoreErrors(); - } - } - } - - @captureTelemetry(Telemetry.ExecuteNativeCell, undefined, true) - // tslint:disable-next-line:no-any - protected async reexecuteCells(info: IReExecuteCells): Promise { - // This is here for existing functional tests that somehow pass undefined into this method. - if (!this.model || !info || !Array.isArray(info.cellIds)) { - return; - } - const tokenSource = new CancellationTokenSource(); - this.executeCancelTokens.add(tokenSource); - const cellsExecuting = new Set(); - try { - for (let i = 0; i < info.cellIds.length && !tokenSource.token.isCancellationRequested; i += 1) { - const cell = this.model.cells.find((item) => item.id === info.cellIds[i]); - if (!cell) { - continue; - } - cellsExecuting.add(cell); - await this.reexecuteCell(cell, tokenSource.token); - cellsExecuting.delete(cell); - - // Check the new state of our cell - const resultCell = this.model.cells.find((item) => item.id === cell.id); - - // Bail on the rest of our cells if one comes back with an error - if ( - this.configuration.getSettings(this.owningResource).datascience.stopOnError && - resultCell && - resultCell.state === CellState.error - ) { - // Set the remaining cells as finished and break out - if (i < info.cellIds.length) { - const unExecutedCellIds = info.cellIds.slice(i + 1, info.cellIds.length); - unExecutedCellIds.forEach((cellId) => { - const unexecutedCell = this.model.cells.find((item) => item.id === cellId); - if (unexecutedCell) { - this.finishCell(unexecutedCell); - } - }); - } - break; - } - } - } catch (exc) { - // Tell the other side we restarted the kernel. This will stop all executions - this.postMessage(InteractiveWindowMessages.RestartKernel).ignoreErrors(); - - // Handle an error - await this.errorHandler.handleError(exc); - } finally { - this.executeCancelTokens.delete(tokenSource); - - // Make sure everything is marked as finished or error after the final finished - cellsExecuting.forEach((cell) => this.finishCell(cell)); - } - } - - protected get notebookIdentity(): INotebookIdentity { - return { - resource: this.file, - type: 'native' - }; - } - - protected async setLaunchingFile(_file: string): Promise { - // For the native editor, use our own file as the path - const notebook = this.getNotebook(); - if (notebook) { - await notebook.setLaunchingFile(this.file.fsPath); - } - } - - protected sendCellsToWebView(cells: ICell[]) { - // Filter out sysinfo messages. Don't want to show those - const filtered = cells.filter((c) => c.data.cell_type !== 'messages'); - - // Update these cells in our storage only when cells are finished - const modified = filtered.filter((c) => c.state === CellState.finished || c.state === CellState.error); - const unmodified = this.model?.cells.filter((c) => modified.find((m) => m.id === c.id)); - if (modified.length > 0 && unmodified && this.model) { - // As this point, we're updating the model because of changes to the cell as a result of execution. - // The output and execution count change, however we're just going to update everything. - // But, we should not update the `source`. The only time source can change is when a request comes from the UI. - // Perhaps we need a finer grained update (update only output and execution count along with `source=execution`). - // For now retain source from previous model. - // E.g. user executes a cell, in the mean time they update the text. Now model contains new value. - // However once execution has completed, this code will update the model with results from previous execution (prior to edit). - // We now need to give preference to the text in the model, over the old one that was executed. - modified.forEach((modifiedCell) => { - const originalCell = unmodified.find((unmodifiedCell) => unmodifiedCell.id === modifiedCell.id); - if (originalCell) { - modifiedCell.data.source = originalCell.data.source; - } - }); - this.model.update({ - source: 'user', - kind: 'modify', - newCells: modified, - oldCells: cloneDeep(unmodified), - oldDirty: this.model.isDirty, - newDirty: true - }); - } - - // Tell storage about our notebook object - const notebook = this.getNotebook(); - if (notebook && this.model) { - const kernelConnection = notebook.getKernelConnection(); - this.model.update({ - source: 'user', - kind: 'version', - oldDirty: this.model.isDirty, - newDirty: this.model.isDirty, - kernelConnection - }); - } - - // Send onto the webview. - super.sendCellsToWebView(filtered); - } - - protected updateContexts(info: IInteractiveWindowInfo | undefined) { - // This should be called by the python interactive window every - // time state changes. We use this opportunity to update our - // extension contexts - if (this.commandManager && this.commandManager.executeCommand) { - const nativeContext = new ContextKey(EditorContexts.HaveNative, this.commandManager); - nativeContext.set(!this.isDisposed).catch(); - const interactiveCellsContext = new ContextKey(EditorContexts.HaveNativeCells, this.commandManager); - const redoableContext = new ContextKey(EditorContexts.HaveNativeRedoableCells, this.commandManager); - const hasCellSelectedContext = new ContextKey(EditorContexts.HaveCellSelected, this.commandManager); - if (info) { - interactiveCellsContext.set(info.cellCount > 0).catch(); - redoableContext.set(info.redoCount > 0).catch(); - hasCellSelectedContext.set(info.selectedCell ? true : false).catch(); - } else { - hasCellSelectedContext.set(false).catch(); - interactiveCellsContext.set(false).catch(); - redoableContext.set(false).catch(); - } - } - } - - protected async onViewStateChanged(args: WebViewViewChangeEventArgs) { - super.onViewStateChanged(args); - - // Update our contexts - const nativeContext = new ContextKey(EditorContexts.HaveNative, this.commandManager); - nativeContext.set(args.current.visible && args.current.active).catch(); - this._onDidChangeViewState.fire(); - } - - protected async closeBecauseOfFailure(_exc: Error): Promise { - // Actually don't close, just let the error bubble out - } - - protected async close(): Promise { - // Fire our event - this.closedEvent.fire(this); - } - - protected saveAll() { - this.commandManager.executeCommand('workbench.action.files.save', this.file); - } - - private async modelChanged(change: NotebookModelChange) { - if (change.source !== 'user') { - // VS code is telling us to broadcast this to our UI. Tell the UI about the new change. Remove the - // the model so this doesn't have to be stringified - await this.postMessage(InteractiveWindowMessages.UpdateModel, { ...change, model: undefined }); - } - if (change.kind === 'saveAs' && change.model) { - const newFileName = change.model.file.toString(); - const oldFileName = change.sourceUri.toString(); - - if (newFileName !== oldFileName) { - // If the filename has changed - this.renameVariableExplorerHeights(oldFileName, newFileName); - } - } - - // Use the current state of the model to indicate dirty (not the message itself) - if (this.model && change.newDirty !== change.oldDirty) { - this.modifiedEvent.fire(this); - if (this.model.isDirty) { - await this.postMessage(InteractiveWindowMessages.NotebookDirty); - } else { - // Then tell the UI - await this.postMessage(InteractiveWindowMessages.NotebookClean); - } - } - } - private async monitorChangesToTrust() { - if (this.previouslyNotTrusted && this.model?.isTrusted) { - this.previouslyNotTrusted = false; - // Tell UI to update main state - this.postMessage(InteractiveWindowMessages.TrustNotebookComplete).ignoreErrors(); - } - } - private renameVariableExplorerHeights(name: string, updatedName: string) { - // Updates the workspace storage to reflect the updated name of the notebook - // should be called if the name of the notebook changes - // tslint:disable-next-line: no-any - const value = this.workspaceStorage.get(VariableExplorerStateKeys.height, {} as any); - if (!(name in value)) { - return; // Nothing to update - } - - value[updatedName] = value[name]; - delete value[name]; - this.workspaceStorage.update(VariableExplorerStateKeys.height, value); - } - - private async launchNotebookTrustPrompt() { - if (this.model && !this.model.isTrusted) { - await this.commandManager.executeCommand(Commands.TrustNotebook, this.model.file); - } - } - - private interruptExecution() { - this.executeCancelTokens.forEach((t) => t.cancel()); - } - - private finishCell(cell: ICell) { - this.sendCellsToWebView([ - { - ...cell, - state: CellState.finished - } - ]); - } - - private async reexecuteCell(cell: ICell, cancelToken: CancellationToken): Promise { - try { - // If there's any payload, it has the code and the id - if (cell.id && cell.data.cell_type !== 'messages') { - traceInfo(`Executing cell ${cell.id}`); - - // Clear the result if we've run before - await this.clearResult(cell.id); - - // Clear 'per run' data passed to WebView before execution - if (cell.data.metadata.tags !== undefined) { - cell.data.metadata.tags = cell.data.metadata.tags.filter((t) => t !== 'outputPrepend'); - } - - const code = concatMultilineString(cell.data.source); - // Send to ourselves. - await this.submitCode(code, Identifiers.EmptyFileName, 0, cell.id, cell.data, undefined, cancelToken); - } - } catch (exc) { - traceInfo(`Exception executing cell ${cell.id}: `, exc); - - // Make this error our cell output - this.sendCellsToWebView([ - { - // tslint:disable-next-line: no-any - data: { ...cell.data, outputs: [createErrorOutput(exc)] } as any, // nyc compiler issue - id: cell.id, - file: Identifiers.EmptyFileName, - line: 0, - state: CellState.error - } - ]); - - throw exc; - } finally { - if (cell && cell.id) { - traceInfo(`Finished executing cell ${cell.id}`); - } - } - } - - private sendPerceivedCellExecute(runningStopWatch?: StopWatch) { - if (runningStopWatch) { - const props = { notebook: true }; - if (!this.sentExecuteCellTelemetry) { - this.sentExecuteCellTelemetry = true; - sendTelemetryEvent(Telemetry.ExecuteCellPerceivedCold, runningStopWatch.elapsedTime, props); - } else { - sendTelemetryEvent(Telemetry.ExecuteCellPerceivedWarm, runningStopWatch.elapsedTime, props); - } - } - } - - private updateModel(change: NotebookModelChange) { - // Send to our model using a command. User has done something that changes the model - if (change.source === 'user' && this.model) { - // Note, originally this was posted with a command but sometimes had problems - // with commands being handled out of order. - this.model.update(change); - } - } - - private async sendInitialCellsToWebView(cells: ICell[], isNotebookTrusted: boolean): Promise { - sendTelemetryEvent(Telemetry.CellCount, undefined, { count: cells.length }); - - return this.postMessage(InteractiveWindowMessages.LoadAllCells, { - cells, - isNotebookTrusted - }); - } - - private async exportAs(): Promise { - const activeEditor = this.editorProvider.activeEditor; - if (!activeEditor || !activeEditor.model) { - return; - } - this.commandManager.executeCommand(Commands.Export, activeEditor.model, undefined); - } - - private logNativeCommand(args: INativeCommand) { - sendTelemetryEvent(args.command); - } - - private async loadCellsComplete() { - if (!this.loadedAllCells) { - this.loadedAllCells = true; - sendTelemetryEvent(Telemetry.NotebookOpenTime, this.startupTimer.elapsedTime); - } - - // If we don't have a server right now, at least show our kernel name (this seems to slow down tests - // too much though) - if (!isTestExecution()) { - const metadata = this.notebookMetadata; - if (!this.notebook && metadata?.kernelspec) { - this.postMessage(InteractiveWindowMessages.UpdateKernel, { - jupyterServerStatus: ServerStatus.NotStarted, - serverName: await this.getServerDisplayName(undefined), - kernelName: metadata.kernelspec.display_name ?? metadata.kernelspec.name, - language: translateKernelLanguageToMonaco( - (metadata.kernelspec.language as string) ?? PYTHON_LANGUAGE - ) - }).ignoreErrors(); - } - } - } - - @captureTelemetry(Telemetry.RunByLineStart) - private async handleRunByLine(runByLine: IRunByLine) { - try { - // If there's any payload, it has the code and the id - if (runByLine.cell.id && runByLine.cell.data.cell_type === 'code') { - traceInfo(`Running by line cell ${runByLine.cell.id}`); - - // Clear the result if we've run before - await this.clearResult(runByLine.cell.id); - - // Generate a hash file name for this cell. - const notebook = this.getNotebook(); - if (notebook) { - const hashProvider = getCellHashProvider(notebook); - if (hashProvider) { - // Add the breakpoint to the first line of the cell so we actually stop - // on the first line. - const newSource = splitMultilineString(runByLine.cell.data.source); - newSource.splice(0, -1, 'breakpoint()\n'); - runByLine.cell.data.source = newSource; - - const hashFileName = hashProvider.generateHashFileName( - runByLine.cell, - runByLine.expectedExecutionCount - ); - const code = concatMultilineString(runByLine.cell.data.source); - // Send to ourselves. - await this.submitCode( - code, - Identifiers.EmptyFileName, - 0, - runByLine.cell.id, - runByLine.cell.data, - { - runByLine: true, - hashFileName - } - ); - } - } - } else { - throw new Error('Run by line started with an invalid cell'); - } - } catch (exc) { - // Make this error our cell output - this.sendCellsToWebView([ - { - // tslint:disable-next-line: no-any - data: { ...runByLine.cell.data, outputs: [createErrorOutput(exc)] } as any, // nyc compiler issue - id: runByLine.cell.id, - file: Identifiers.EmptyFileName, - line: 0, - state: CellState.error - } - ]); - - throw exc; - } finally { - if (runByLine.cell && runByLine.cell.id) { - traceInfo(`Finished run by line on cell ${runByLine.cell.id}`); - } - } - } -} diff --git a/src/client/datascience/interactive-ipynb/nativeEditorCommandListener.ts b/src/client/datascience/interactive-ipynb/nativeEditorCommandListener.ts deleted file mode 100644 index 20629faff4ae..000000000000 --- a/src/client/datascience/interactive-ipynb/nativeEditorCommandListener.ts +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { Uri } from 'vscode'; - -import { ICommandManager } from '../../common/application/types'; -import { traceError } from '../../common/logger'; -import { IDisposableRegistry } from '../../common/types'; -import { captureTelemetry } from '../../telemetry'; -import { CommandSource } from '../../testing/common/constants'; -import { Commands, Telemetry } from '../constants'; -import { IDataScienceCommandListener, IDataScienceErrorHandler, INotebookEditorProvider } from '../types'; - -@injectable() -export class NativeEditorCommandListener implements IDataScienceCommandListener { - constructor( - @inject(IDisposableRegistry) private disposableRegistry: IDisposableRegistry, - @inject(INotebookEditorProvider) private provider: INotebookEditorProvider, - @inject(IDataScienceErrorHandler) private dataScienceErrorHandler: IDataScienceErrorHandler - ) {} - - public register(commandManager: ICommandManager): void { - this.disposableRegistry.push( - commandManager.registerCommand(Commands.NotebookEditorUndoCells, () => this.undoCells()) - ); - this.disposableRegistry.push( - commandManager.registerCommand(Commands.NotebookEditorRedoCells, () => this.redoCells()) - ); - this.disposableRegistry.push( - commandManager.registerCommand(Commands.NotebookEditorRemoveAllCells, () => this.removeAllCells()) - ); - this.disposableRegistry.push( - commandManager.registerCommand(Commands.NotebookEditorInterruptKernel, () => this.interruptKernel()) - ); - this.disposableRegistry.push( - commandManager.registerCommand(Commands.NotebookEditorRestartKernel, () => this.restartKernel()) - ); - this.disposableRegistry.push( - commandManager.registerCommand( - Commands.OpenNotebook, - (file?: Uri, _cmdSource: CommandSource = CommandSource.commandPalette) => this.openNotebook(file) - ) - ); - this.disposableRegistry.push( - commandManager.registerCommand(Commands.NotebookEditorRunAllCells, () => this.runAllCells()) - ); - this.disposableRegistry.push( - commandManager.registerCommand(Commands.NotebookEditorRunSelectedCell, () => this.runSelectedCell()) - ); - this.disposableRegistry.push( - commandManager.registerCommand(Commands.NotebookEditorAddCellBelow, () => this.addCellBelow()) - ); - } - - private runAllCells() { - const activeEditor = this.provider.activeEditor; - if (activeEditor) { - activeEditor.runAllCells(); - } - } - - private runSelectedCell() { - const activeEditor = this.provider.activeEditor; - if (activeEditor) { - activeEditor.runSelectedCell(); - } - } - - private addCellBelow() { - const activeEditor = this.provider.activeEditor; - if (activeEditor) { - activeEditor.addCellBelow(); - } - } - - private undoCells() { - const activeEditor = this.provider.activeEditor; - if (activeEditor) { - activeEditor.undoCells(); - } - } - - private redoCells() { - const activeEditor = this.provider.activeEditor; - if (activeEditor) { - activeEditor.redoCells(); - } - } - - private removeAllCells() { - const activeEditor = this.provider.activeEditor; - if (activeEditor) { - activeEditor.removeAllCells(); - } - } - - private interruptKernel() { - const activeEditor = this.provider.activeEditor; - if (activeEditor) { - activeEditor.interruptKernel().ignoreErrors(); - } - } - - private async restartKernel() { - const activeEditor = this.provider.activeEditor; - if (activeEditor) { - await activeEditor.restartKernel().catch(traceError.bind('Failed to restart kernel')); - } - } - - @captureTelemetry(Telemetry.OpenNotebook, { scope: 'command' }, false) - private async openNotebook(file?: Uri): Promise { - if (file && path.extname(file.fsPath).toLocaleLowerCase() === '.ipynb') { - try { - // Then take the contents and load it. - await this.provider.open(file); - } catch (e) { - return this.dataScienceErrorHandler.handleError(e); - } - } - } -} diff --git a/src/client/datascience/interactive-ipynb/nativeEditorOldWebView.ts b/src/client/datascience/interactive-ipynb/nativeEditorOldWebView.ts deleted file mode 100644 index 1b8bf399b6b7..000000000000 --- a/src/client/datascience/interactive-ipynb/nativeEditorOldWebView.ts +++ /dev/null @@ -1,309 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; - -import * as path from 'path'; -import { CancellationTokenSource, Memento, Uri, WebviewPanel } from 'vscode'; - -import { - IApplicationShell, - ICommandManager, - IDocumentManager, - ILiveShareApi, - IWebviewPanelProvider, - IWorkspaceService -} from '../../common/application/types'; -import { traceError } from '../../common/logger'; - -import { - IAsyncDisposableRegistry, - IConfigurationService, - IDisposableRegistry, - IExperimentsManager -} from '../../common/types'; -import * as localize from '../../common/utils/localize'; -import { noop } from '../../common/utils/misc'; -import { captureTelemetry } from '../../telemetry'; -import { Commands, Telemetry } from '../constants'; -import { IDataViewerFactory } from '../data-viewing/types'; -import { InteractiveWindowMessages } from '../interactive-common/interactiveWindowTypes'; -import { KernelSelector } from '../jupyter/kernels/kernelSelector'; -import { INotebookStorageProvider } from '../notebookStorage/notebookStorageProvider'; -import { - ICodeCssGenerator, - IDataScienceErrorHandler, - IDataScienceFileSystem, - IInteractiveWindowListener, - IJupyterDebugger, - IJupyterVariableDataProviderFactory, - IJupyterVariables, - INotebookEditorProvider, - INotebookExporter, - INotebookExtensibility, - INotebookImporter, - INotebookModel, - INotebookProvider, - IStatusProvider, - IThemeFinder, - ITrustService -} from '../types'; -import { NativeEditor } from './nativeEditor'; -import { NativeEditorSynchronizer } from './nativeEditorSynchronizer'; - -export enum AskForSaveResult { - Yes, - No, - Cancel -} - -export class NativeEditorOldWebView extends NativeEditor { - public readonly type = 'old'; - public get visible(): boolean { - return this.viewState.visible; - } - public get active(): boolean { - return this.viewState.active; - } - - private isPromptingToSaveToDisc: boolean = false; - - constructor( - listeners: IInteractiveWindowListener[], - liveShare: ILiveShareApi, - applicationShell: IApplicationShell, - documentManager: IDocumentManager, - provider: IWebviewPanelProvider, - disposables: IDisposableRegistry, - cssGenerator: ICodeCssGenerator, - themeFinder: IThemeFinder, - statusProvider: IStatusProvider, - fs: IDataScienceFileSystem, - configuration: IConfigurationService, - commandManager: ICommandManager, - jupyterExporter: INotebookExporter, - workspaceService: IWorkspaceService, - synchronizer: NativeEditorSynchronizer, - editorProvider: INotebookEditorProvider, - dataExplorerFactory: IDataViewerFactory, - - jupyterVariableDataProviderFactory: IJupyterVariableDataProviderFactory, - jupyterVariables: IJupyterVariables, - jupyterDebugger: IJupyterDebugger, - importer: INotebookImporter, - errorHandler: IDataScienceErrorHandler, - globalStorage: Memento, - workspaceStorage: Memento, - experimentsManager: IExperimentsManager, - asyncRegistry: IAsyncDisposableRegistry, - notebookProvider: INotebookProvider, - useCustomEditorApi: boolean, - private readonly storage: INotebookStorageProvider, - trustService: ITrustService, - model: INotebookModel, - webviewPanel: WebviewPanel | undefined, - selector: KernelSelector, - notebookExtensibility: INotebookExtensibility - ) { - super( - listeners, - liveShare, - applicationShell, - documentManager, - provider, - disposables, - cssGenerator, - themeFinder, - statusProvider, - fs, - configuration, - commandManager, - jupyterExporter, - workspaceService, - synchronizer, - editorProvider, - dataExplorerFactory, - jupyterVariableDataProviderFactory, - jupyterVariables, - jupyterDebugger, - importer, - errorHandler, - globalStorage, - workspaceStorage, - experimentsManager, - asyncRegistry, - notebookProvider, - useCustomEditorApi, - trustService, - model, - webviewPanel, - selector, - notebookExtensibility - ); - asyncRegistry.push(this); - // No ui syncing in old notebooks. - synchronizer.disable(); - - // Update our title to match - this.setTitle(path.basename(model.file.fsPath)); - - // Update dirty if model started out that way - if (this.model?.isDirty) { - this.setDirty().ignoreErrors(); - } - - this.model?.changed(() => { - if (this.model?.isDirty) { - this.setDirty().ignoreErrors(); - } else { - this.setClean().ignoreErrors(); - } - }); - } - - protected async close(): Promise { - // Ask user if they want to save. It seems hotExit has no bearing on - // whether or not we should ask - if (this.isDirty) { - const askResult = await this.askForSave(); - switch (askResult) { - case AskForSaveResult.Yes: - // Save the file - await this.saveToDisk(); - - // Close it - await super.close(); - break; - - case AskForSaveResult.No: - // If there were changes, delete them - if (this.model) { - await this.storage.deleteBackup(this.model); - } - // Close it - await super.close(); - break; - - default: { - await super.close(); - await this.reopen(); - break; - } - } - } else { - // Not dirty, just close normally. - await super.close(); - } - } - - protected saveAll() { - this.saveToDisk().ignoreErrors(); - } - - /** - * Used closed notebook with unsaved changes, then when prompted they clicked cancel. - * Clicking cancel means we need to keep the nb open. - * Hack is to re-open nb with old changes. - */ - private async reopen(): Promise { - if (this.model) { - // Skip doing this if auto save is enabled. - const filesConfig = this.workspaceService.getConfiguration('files', this.file); - const autoSave = filesConfig.get('autoSave', 'off'); - if (autoSave === 'off' || this.isUntitled) { - await this.storage.backup(this.model, new CancellationTokenSource().token); - } - this.commandManager.executeCommand(Commands.OpenNotebookNonCustomEditor, this.model.file).then(noop, noop); - } - } - - private async askForSave(): Promise { - const message1 = localize.DataScience.dirtyNotebookMessage1().format(`${path.basename(this.file.fsPath)}`); - const message2 = localize.DataScience.dirtyNotebookMessage2(); - const yes = localize.DataScience.dirtyNotebookYes(); - const no = localize.DataScience.dirtyNotebookNo(); - const result = await this.applicationShell.showInformationMessage( - // tslint:disable-next-line: messages-must-be-localized - `${message1}\n${message2}`, - { modal: true }, - yes, - no - ); - switch (result) { - case yes: - return AskForSaveResult.Yes; - - case no: - return AskForSaveResult.No; - - default: - return AskForSaveResult.Cancel; - } - } - private async setDirty(): Promise { - // Then update dirty flag. - if (this.isDirty) { - this.setTitle(`${path.basename(this.file.fsPath)}*`); - - // Tell the webview we're dirty - await this.postMessage(InteractiveWindowMessages.NotebookDirty); - - // Tell listeners we're dirty - this.modifiedEvent.fire(this); - } - } - - private async setClean(): Promise { - if (!this.isDirty) { - this.setTitle(`${path.basename(this.file.fsPath)}`); - await this.postMessage(InteractiveWindowMessages.NotebookClean); - } - } - - @captureTelemetry(Telemetry.Save, undefined, true) - private async saveToDisk(): Promise { - // If we're already in the middle of prompting the user to save, then get out of here. - // We could add a debounce decorator, unfortunately that slows saving (by waiting for no more save events to get sent). - if ((this.isPromptingToSaveToDisc && this.isUntitled) || !this.model) { - return; - } - try { - if (!this.isUntitled) { - await this.commandManager.executeCommand(Commands.SaveNotebookNonCustomEditor, this.model); - this.savedEvent.fire(this); - return; - } - // Ask user for a save as dialog if no title - let fileToSaveTo: Uri | undefined = this.file; - - this.isPromptingToSaveToDisc = true; - const filtersKey = localize.DataScience.dirtyNotebookDialogFilter(); - const filtersObject: { [name: string]: string[] } = {}; - filtersObject[filtersKey] = ['ipynb']; - - const defaultUri = - Array.isArray(this.workspaceService.workspaceFolders) && - this.workspaceService.workspaceFolders.length > 0 - ? this.workspaceService.workspaceFolders[0].uri - : undefined; - fileToSaveTo = await this.applicationShell.showSaveDialog({ - saveLabel: localize.DataScience.dirtyNotebookDialogTitle(), - filters: filtersObject, - defaultUri - }); - - if (fileToSaveTo) { - await this.commandManager.executeCommand( - Commands.SaveAsNotebookNonCustomEditor, - this.model, - fileToSaveTo - ); - this.savedEvent.fire(this); - } - } catch (e) { - traceError('Failed to Save nb', e); - } finally { - this.isPromptingToSaveToDisc = false; - } - } -} diff --git a/src/client/datascience/interactive-ipynb/nativeEditorProviderOld.ts b/src/client/datascience/interactive-ipynb/nativeEditorProviderOld.ts deleted file mode 100644 index 99a857a61834..000000000000 --- a/src/client/datascience/interactive-ipynb/nativeEditorProviderOld.ts +++ /dev/null @@ -1,413 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { CancellationTokenSource, Memento, TextDocument, TextEditor, Uri, WebviewPanel } from 'vscode'; - -import { CancellationToken } from 'vscode-jsonrpc'; -import { - IApplicationShell, - ICommandManager, - ICustomEditorService, - IDocumentManager, - ILiveShareApi, - IWebviewPanelProvider, - IWorkspaceService -} from '../../common/application/types'; -import { JUPYTER_LANGUAGE, UseCustomEditorApi } from '../../common/constants'; - -import { - GLOBAL_MEMENTO, - IAsyncDisposableRegistry, - IConfigurationService, - IDisposableRegistry, - IExperimentsManager, - IMemento, - WORKSPACE_MEMENTO -} from '../../common/types'; -import { createDeferred } from '../../common/utils/async'; -import { isNotebookCell, noop } from '../../common/utils/misc'; -import { IServiceContainer } from '../../ioc/types'; -import { Commands, Identifiers } from '../constants'; -import { IDataViewerFactory } from '../data-viewing/types'; -import { NotebookModelChange } from '../interactive-common/interactiveWindowTypes'; -import { KernelSelector } from '../jupyter/kernels/kernelSelector'; -import { NativeEditorProvider } from '../notebookStorage/nativeEditorProvider'; -import { INotebookStorageProvider } from '../notebookStorage/notebookStorageProvider'; -import { VSCodeNotebookModel } from '../notebookStorage/vscNotebookModel'; -import { - ICodeCssGenerator, - IDataScienceErrorHandler, - IDataScienceFileSystem, - IInteractiveWindowListener, - IJupyterDebugger, - IJupyterVariableDataProviderFactory, - IJupyterVariables, - INotebookEditor, - INotebookEditorProvider, - INotebookExporter, - INotebookExtensibility, - INotebookImporter, - INotebookModel, - INotebookProvider, - IStatusProvider, - IThemeFinder, - ITrustService -} from '../types'; -import { NativeEditor } from './nativeEditor'; -import { NativeEditorOldWebView } from './nativeEditorOldWebView'; -import { NativeEditorSynchronizer } from './nativeEditorSynchronizer'; - -// tslint:disable-next-line:no-require-imports no-var-requires -const debounce = require('lodash/debounce') as typeof import('lodash/debounce'); - -@injectable() -export class NativeEditorProviderOld extends NativeEditorProvider { - public get activeEditor(): INotebookEditor | undefined { - const active = [...this.activeEditors.entries()].find((e) => e[1].active); - if (active) { - return active[1]; - } - } - - public get editors(): INotebookEditor[] { - return [...this.activeEditors.values()]; - } - private activeEditors: Map = new Map(); - private readonly _autoSaveNotebookInHotExitFile = new WeakMap(); - constructor( - @inject(IServiceContainer) serviceContainer: IServiceContainer, - @inject(IAsyncDisposableRegistry) asyncRegistry: IAsyncDisposableRegistry, - @inject(IDisposableRegistry) disposables: IDisposableRegistry, - @inject(IWorkspaceService) workspace: IWorkspaceService, - @inject(IConfigurationService) configuration: IConfigurationService, - @inject(ICustomEditorService) customEditorService: ICustomEditorService, - @inject(IDataScienceFileSystem) fs: IDataScienceFileSystem, - @inject(IDocumentManager) private documentManager: IDocumentManager, - @inject(ICommandManager) private readonly cmdManager: ICommandManager, - @inject(IDataScienceErrorHandler) private dataScienceErrorHandler: IDataScienceErrorHandler, - @inject(INotebookStorageProvider) storage: INotebookStorageProvider, - @inject(INotebookProvider) notebookProvider: INotebookProvider - ) { - super( - serviceContainer, - asyncRegistry, - disposables, - workspace, - configuration, - customEditorService, - storage, - notebookProvider, - fs - ); - - // No live share sync required as open document from vscode will give us our contents. - - this.disposables.push( - this.documentManager.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditorHandler.bind(this)) - ); - this.disposables.push( - this.cmdManager.registerCommand(Commands.SaveNotebookNonCustomEditor, async (model: INotebookModel) => { - await this.storage.save(model, new CancellationTokenSource().token); - }) - ); - this.disposables.push( - this.cmdManager.registerCommand( - Commands.SaveAsNotebookNonCustomEditor, - async (model: INotebookModel, targetResource: Uri) => { - await this.storage.saveAs(model, targetResource); - const customDocument = this.customDocuments.get(model.file.fsPath); - if (customDocument) { - this.customDocuments.delete(model.file.fsPath); - this.customDocuments.set(targetResource.fsPath, { ...customDocument, uri: targetResource }); - } - } - ) - ); - - this.disposables.push( - this.cmdManager.registerCommand(Commands.OpenNotebookNonCustomEditor, async (resource: Uri) => { - await this.open(resource); - }) - ); - - // Since we may have activated after a document was opened, also run open document for all documents. - // This needs to be async though. Iterating over all of these in the .ctor is crashing the extension - // host, so postpone till after the ctor is finished. - setTimeout(() => { - if (this.documentManager.textDocuments && this.documentManager.textDocuments.forEach) { - this.documentManager.textDocuments.forEach((doc) => this.openNotebookAndCloseEditor(doc, false)); - } - }, 0); - } - - public async open(file: Uri): Promise { - // Save a custom document as we use it to search for the object later. - if (!this.customDocuments.has(file.fsPath)) { - // Required for old editor - this.customDocuments.set(file.fsPath, { - uri: file, - dispose: noop - }); - } - - // See if this file is open or not already - let editor = this.activeEditors.get(file.fsPath); - if (!editor) { - // Note: create will fire the open event. - editor = await this.create(file); - } else { - await this.showEditor(editor); - } - return editor; - } - - public async show(file: Uri): Promise { - // See if this file is open or not already - const editor = this.activeEditors.get(file.fsPath); - if (editor) { - await this.showEditor(editor); - } - return editor; - } - - protected openedEditor(e: INotebookEditor) { - super.openedEditor(e); - this.activeEditors.set(e.file.fsPath, e); - this.disposables.push(e.saved(this.onSavedEditor.bind(this, e.file.fsPath))); - this._onDidChangeActiveNotebookEditor.fire(this.activeEditor); - } - - protected async modelEdited(model: INotebookModel, e: NotebookModelChange) { - const actualModel = e.model || model; // Test mocks can screw up bound values. - if (actualModel && e.kind !== 'save' && e.kind !== 'saveAs' && e.source === 'user') { - // This isn't necessary with the custom editor api because the custom editor will - // cause backup to be called appropriately. - let debounceFunc = this._autoSaveNotebookInHotExitFile.get(actualModel); - if (!debounceFunc) { - debounceFunc = debounce(this.autoSaveNotebookInHotExitFile.bind(this, actualModel), 250); - this._autoSaveNotebookInHotExitFile.set(actualModel, debounceFunc); - } - debounceFunc(); - } - } - - protected createNotebookEditor(model: INotebookModel, panel?: WebviewPanel): NativeEditor { - const editor = new NativeEditorOldWebView( - this.serviceContainer.getAll(IInteractiveWindowListener), - this.serviceContainer.get(ILiveShareApi), - this.serviceContainer.get(IApplicationShell), - this.serviceContainer.get(IDocumentManager), - this.serviceContainer.get(IWebviewPanelProvider), - this.serviceContainer.get(IDisposableRegistry), - this.serviceContainer.get(ICodeCssGenerator), - this.serviceContainer.get(IThemeFinder), - this.serviceContainer.get(IStatusProvider), - this.serviceContainer.get(IDataScienceFileSystem), - this.serviceContainer.get(IConfigurationService), - this.serviceContainer.get(ICommandManager), - this.serviceContainer.get(INotebookExporter), - this.serviceContainer.get(IWorkspaceService), - this.serviceContainer.get(NativeEditorSynchronizer), - this.serviceContainer.get(INotebookEditorProvider), - this.serviceContainer.get(IDataViewerFactory), - this.serviceContainer.get(IJupyterVariableDataProviderFactory), - this.serviceContainer.get(IJupyterVariables, Identifiers.ALL_VARIABLES), - this.serviceContainer.get(IJupyterDebugger), - this.serviceContainer.get(INotebookImporter), - this.serviceContainer.get(IDataScienceErrorHandler), - this.serviceContainer.get(IMemento, GLOBAL_MEMENTO), - this.serviceContainer.get(IMemento, WORKSPACE_MEMENTO), - this.serviceContainer.get(IExperimentsManager), - this.serviceContainer.get(IAsyncDisposableRegistry), - this.serviceContainer.get(INotebookProvider), - this.serviceContainer.get(UseCustomEditorApi), - this.serviceContainer.get(INotebookStorageProvider), - this.serviceContainer.get(ITrustService), - model, - panel, - this.serviceContainer.get(KernelSelector), - this.serviceContainer.get(INotebookExtensibility) - ); - this.activeEditors.set(model.file.fsPath, editor); - this.disposables.push(editor.closed(this.onClosedEditor.bind(this))); - this.openedEditor(editor); - return editor; - } - - protected async loadNotebookEditor(resource: Uri, panel?: WebviewPanel) { - const result = await super.loadNotebookEditor(resource, panel); - - // Wait for monaco ready (it's not really useable until it has a language) - const readyPromise = createDeferred(); - const disposable = result.ready(() => readyPromise.resolve()); - await result.show(); - await readyPromise.promise; - disposable.dispose(); - - return result; - } - - private autoSaveNotebookInHotExitFile(model: INotebookModel) { - // Refetch settings each time as they can change before the debounce can happen - const fileSettings = this.workspace.getConfiguration('files', model.file); - // We need to backup, only if auto save if turned off and not an untitled file. - if (fileSettings.get('autoSave', 'off') !== 'off' && !model.isUntitled) { - return; - } - this.storage.backup(model, CancellationToken.None).ignoreErrors(); - } - - /** - * Open ipynb files when user opens an ipynb file. - * - * @private - * @memberof NativeEditorProvider - */ - private onDidChangeActiveTextEditorHandler(editor?: TextEditor) { - // I we're a source control diff view, then ignore this editor. - if (!editor || this.isEditorPartOfDiffView(editor)) { - return; - } - this.openNotebookAndCloseEditor(editor.document, true).ignoreErrors(); - } - - private async showEditor(editor: INotebookEditor) { - await editor.show(); - this._onDidChangeActiveNotebookEditor.fire(this.activeEditor); - } - - private async create(file: Uri): Promise { - let editor = this.activeEditors.get(file.fsPath); - if (!editor) { - editor = await this.loadNotebookEditor(file); - await this.showEditor(editor); - } - return editor; - } - - private onClosedEditor(e: INotebookEditor) { - this.activeEditors.delete(e.file.fsPath); - this._onDidChangeActiveNotebookEditor.fire(this.activeEditor); - } - private onSavedEditor(oldPath: string, e: INotebookEditor) { - // Switch our key for this editor - if (this.activeEditors.has(oldPath)) { - this.activeEditors.delete(oldPath); - } - this.activeEditors.set(e.file.fsPath, e); - - // Remove backup storage - this.loadModel({ file: Uri.file(oldPath) }) - .then((m) => this.storage.deleteBackup(m)) - .ignoreErrors(); - } - - private openNotebookAndCloseEditor = async ( - document: TextDocument, - closeDocumentBeforeOpeningNotebook: boolean - ) => { - // See if this is an ipynb file - if (this.isNotebook(document) && this.configuration.getSettings(document.uri).datascience.useNotebookEditor) { - if (await this.isDocumentOpenedInVSCodeNotebook(document)) { - return; - } - - const closeActiveEditorCommand = 'workbench.action.closeActiveEditor'; - try { - const uri = document.uri; - - if (closeDocumentBeforeOpeningNotebook) { - if ( - !this.documentManager.activeTextEditor || - this.documentManager.activeTextEditor.document !== document - ) { - await this.documentManager.showTextDocument(document); - } - await this.cmdManager.executeCommand(closeActiveEditorCommand); - } - - // Open our own editor. - await this.open(uri); - - if (!closeDocumentBeforeOpeningNotebook) { - // Then switch back to the ipynb and close it. - // If we don't do it in this order, the close will switch to the wrong item - await this.documentManager.showTextDocument(document); - await this.cmdManager.executeCommand(closeActiveEditorCommand); - } - } catch (e) { - return this.dataScienceErrorHandler.handleError(e); - } - } - }; - /** - * If the INotebookModel associated with a Notebook is of type VSCodeNotebookModel, then its used with a VSC Notebook. - * I.e. document is already opened in a VSC Notebook. - */ - private async isDocumentOpenedInVSCodeNotebook(document: TextDocument): Promise { - const model = await this.loadModel({ file: document.uri }); - // This is temporary code. - return model instanceof VSCodeNotebookModel; - } - /** - * Check if user is attempting to compare two ipynb files. - * If yes, then return `true`, else `false`. - * - * @private - * @param {TextEditor} editor - * @memberof NativeEditorProvider - */ - private isEditorPartOfDiffView(editor?: TextEditor) { - if (!editor) { - return false; - } - // There's no easy way to determine if the user is openeing a diff view. - // One simple way is to check if there are 2 editor opened, and if both editors point to the same file - // One file with the `file` scheme and the other with the `git` scheme. - if (this.documentManager.visibleTextEditors.length <= 1) { - return false; - } - - // If we have both `git` & `file`/`git` schemes for the same file, then we're most likely looking at a diff view. - // Also ensure both editors are in the same view column. - // Possible we have a git diff view (with two editors git and file scheme), and we open the file view - // on the side (different view column). - const gitSchemeEditor = this.documentManager.visibleTextEditors.find( - (editorUri) => - editorUri.document && - editorUri.document.uri.scheme === 'git' && - editorUri.document.uri.fsPath === editor.document.uri.fsPath - ); - - if (!gitSchemeEditor) { - return false; - } - - // Look for other editors with the same file name that have a scheme of file/git and same viewcolumn. - const fileSchemeEditor = this.documentManager.visibleTextEditors.find( - (editorUri) => - editorUri !== gitSchemeEditor && - this.fs.arePathsSame(editorUri.document.uri, editor.document.uri) && - editorUri.viewColumn === gitSchemeEditor.viewColumn - ); - if (!fileSchemeEditor) { - return false; - } - - // Also confirm the document we have passed in, belongs to one of the editors. - // If its not, then its another document (that is not in the diff view). - return gitSchemeEditor === editor || fileSchemeEditor === editor; - } - private isNotebook(document: TextDocument) { - // Skip opening anything from git as we should use the git viewer. - const validUriScheme = document.uri.scheme !== 'git'; - return ( - validUriScheme && - !isNotebookCell(document) && - (document.languageId === JUPYTER_LANGUAGE || - path.extname(document.fileName).toLocaleLowerCase() === '.ipynb') - ); - } -} diff --git a/src/client/datascience/interactive-ipynb/nativeEditorRunByLineListener.ts b/src/client/datascience/interactive-ipynb/nativeEditorRunByLineListener.ts deleted file mode 100644 index fc2860cae74f..000000000000 --- a/src/client/datascience/interactive-ipynb/nativeEditorRunByLineListener.ts +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; - -import { inject, injectable, named } from 'inversify'; -import { - DebugAdapterTracker, - DebugAdapterTrackerFactory, - DebugSession, - Event, - EventEmitter, - ProviderResult -} from 'vscode'; - -import { PYTHON_LANGUAGE } from '../../common/constants'; -import { traceInfo } from '../../common/logger'; -import { noop } from '../../common/utils/misc'; -import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; -import { Identifiers, Telemetry } from '../constants'; -import { InteractiveWindowMessages, IRunByLine } from '../interactive-common/interactiveWindowTypes'; -import { ICell, IInteractiveWindowListener, IJupyterDebugService } from '../types'; - -// tslint:disable: no-any -/** - * Native editor listener that responds to run by line commands from the UI and uses - * those commands to control a debug client. - */ -@injectable() -export class NativeEditorRunByLineListener - implements IInteractiveWindowListener, DebugAdapterTrackerFactory, DebugAdapterTracker { - private postEmitter: EventEmitter<{ message: string; payload: any }> = new EventEmitter<{ - message: string; - payload: any; - }>(); - private currentCellBeingRun: ICell | undefined; - - constructor( - @inject(IJupyterDebugService) - @named(Identifiers.RUN_BY_LINE_DEBUGSERVICE) - private debugService: IJupyterDebugService - ) { - debugService.registerDebugAdapterTrackerFactory(PYTHON_LANGUAGE, this); - } - - public createDebugAdapterTracker(_session: DebugSession): ProviderResult { - return this; - } - - public get postMessage(): Event<{ message: string; payload: any }> { - return this.postEmitter.event; - } - - public onExit() { - this.currentCellBeingRun = undefined; - } - - public onDidSendMessage?(message: any): void { - if (message.type === 'event' && message.event === 'stopped') { - // We've stopped at breakpoint. Get the top most stack frame to figure out our IP - this.handleBreakEvent().ignoreErrors(); - } else if (message.type === 'event' && (message.command === 'next' || message.command === 'continue')) { - this.handleContinueEvent().ignoreErrors(); - } - } - - public onMessage(message: string, payload?: any): void { - switch (message) { - case InteractiveWindowMessages.Interrupt: - if (this.debugService.activeDebugSession && this.currentCellBeingRun) { - sendTelemetryEvent(Telemetry.RunByLineStop); - } - break; - - case InteractiveWindowMessages.Step: - this.handleStep().ignoreErrors(); - break; - - case InteractiveWindowMessages.Continue: - this.handleContinue().ignoreErrors(); - break; - - case InteractiveWindowMessages.RunByLine: - this.saveCell(payload); - break; - - default: - break; - } - } - public dispose(): void | undefined { - noop(); - } - - private saveCell(runByLine: IRunByLine) { - this.currentCellBeingRun = { ...runByLine.cell }; - } - - private async handleBreakEvent() { - // First get the stack - const frames = await this.debugService.getStack(); - - // Then force a variable refresh - await this.debugService.requestVariables(); - - // If we got frames, tell the UI - if (frames && frames.length > 0) { - traceInfo(`Broke into ${frames[0].source?.path}:${frames[0].line}`); - // Tell the UI to move to a new location - this.postEmitter.fire({ - message: InteractiveWindowMessages.ShowBreak, - payload: { frames, cell: this.currentCellBeingRun } - }); - } - } - - @captureTelemetry(Telemetry.RunByLineStep) - private async handleStep() { - // User issued a step command. - this.postEmitter.fire({ message: InteractiveWindowMessages.ShowContinue, payload: this.currentCellBeingRun }); - return this.debugService.step(); - } - - private async handleContinue() { - // User issued a continue command - this.postEmitter.fire({ message: InteractiveWindowMessages.ShowContinue, payload: this.currentCellBeingRun }); - return this.debugService.continue(); - } - - private async handleContinueEvent() { - // Tell the ui to erase the current IP - this.postEmitter.fire({ message: InteractiveWindowMessages.ShowContinue, payload: this.currentCellBeingRun }); - } -} diff --git a/src/client/datascience/interactive-ipynb/nativeEditorSynchronizer.ts b/src/client/datascience/interactive-ipynb/nativeEditorSynchronizer.ts deleted file mode 100644 index 74d7ecd1c734..000000000000 --- a/src/client/datascience/interactive-ipynb/nativeEditorSynchronizer.ts +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; - -import { IInteractiveWindowMapping, InteractiveWindowMessages } from '../interactive-common/interactiveWindowTypes'; -import { SyncPayload } from '../interactive-common/types'; -import { IDataScienceFileSystem, INotebookEditor } from '../types'; - -// tslint:disable: no-any - -type UserActionNotificationCallback = ( - type: T, - payload?: M[T] -) => void; - -@injectable() -export class NativeEditorSynchronizer { - private registeredNotebooks = new Map(); - private enabled = true; - constructor(@inject(IDataScienceFileSystem) private readonly fs: IDataScienceFileSystem) {} - public notifyUserAction(message: SyncPayload, editor: INotebookEditor) { - if (!this.enabled) { - return; - } - this.registeredNotebooks.forEach((cb, item) => { - if (item !== editor && this.fs.arePathsSame(item.file, editor.file)) { - cb(InteractiveWindowMessages.Sync, message as any); - } - }); - } - public subscribeToUserActions(editor: INotebookEditor, cb: UserActionNotificationCallback) { - this.registeredNotebooks.set(editor, cb); - } - public disable() { - this.enabled = false; - this.registeredNotebooks.clear(); - } -} diff --git a/src/client/datascience/interactive-ipynb/nativeEditorViewTracker.ts b/src/client/datascience/interactive-ipynb/nativeEditorViewTracker.ts deleted file mode 100644 index bdf71a4cdb6c..000000000000 --- a/src/client/datascience/interactive-ipynb/nativeEditorViewTracker.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { inject, injectable, named } from 'inversify'; -import { Memento, Uri } from 'vscode'; -import { IExtensionSingleActivationService } from '../../activation/types'; -import { UseCustomEditorApi } from '../../common/constants'; -import { IDisposableRegistry, IMemento, WORKSPACE_MEMENTO } from '../../common/types'; -import { INotebookEditor, INotebookEditorProvider } from '../types'; - -const MEMENTO_KEY = 'nativeEditorViewTracking'; -/** - * This class tracks opened notebooks and stores the list of files in a memento. On next activation - * this list of files is then opened. - * Untitled files are tracked too, but they should only open if they're dirty. - */ -@injectable() -export class NativeEditorViewTracker implements IExtensionSingleActivationService { - constructor( - @inject(INotebookEditorProvider) private readonly editorProvider: INotebookEditorProvider, - @inject(IMemento) @named(WORKSPACE_MEMENTO) private readonly workspaceMemento: Memento, - @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, - @inject(UseCustomEditorApi) private readonly useCustomEditorApi: boolean - ) { - if (!useCustomEditorApi) { - disposableRegistry.push(editorProvider.onDidOpenNotebookEditor(this.onOpenedEditor.bind(this))); - disposableRegistry.push(editorProvider.onDidCloseNotebookEditor(this.onClosedEditor.bind(this))); - } - } - - public async activate(): Promise { - // On activate get the list and eliminate any dupes that might have snuck in. - const set = new Set(this.workspaceMemento.get(MEMENTO_KEY) || []); - await this.workspaceMemento.update(MEMENTO_KEY, undefined); - - // Then open each one if not using the custom editor api - if (!this.useCustomEditorApi) { - set.forEach((l) => { - const uri = Uri.parse(l); - if (uri) { - this.editorProvider.open(uri).ignoreErrors(); - } - }); - } - } - - private onOpenedEditor(editor: INotebookEditor) { - // Save this as a file that should be reopened in this workspace - const list = this.workspaceMemento.get(MEMENTO_KEY) || []; - const fileKey = editor.file.toString(); - - // Skip untitled files. They have to be changed first. - if (!list.includes(fileKey) && (!editor.model.isUntitled || editor.isDirty)) { - this.workspaceMemento.update(MEMENTO_KEY, [...list, fileKey]); - } else if (editor.model.isUntitled && editor.model) { - editor.model.changed(this.onUntitledChanged.bind(this, editor.file)); - } - } - - private onUntitledChanged(file: Uri) { - const list = this.workspaceMemento.get(MEMENTO_KEY) || []; - const fileKey = file.toString(); - if (!list.includes(fileKey)) { - this.workspaceMemento.update(MEMENTO_KEY, [...list, fileKey]); - } - } - - private onClosedEditor(editor: INotebookEditor) { - // Save this as a file that should not be reopened in this workspace if this is the - // last editor for this file - const fileKey = editor.file.toString(); - if (!this.editorProvider.editors.find((e) => e.file.toString() === fileKey && e !== editor)) { - const list = this.workspaceMemento.get(MEMENTO_KEY) || []; - this.workspaceMemento.update( - MEMENTO_KEY, - list.filter((e) => e !== fileKey) - ); - } - } -} diff --git a/src/client/datascience/interactive-ipynb/trustCommandHandler.ts b/src/client/datascience/interactive-ipynb/trustCommandHandler.ts deleted file mode 100644 index b325032c8fc4..000000000000 --- a/src/client/datascience/interactive-ipynb/trustCommandHandler.ts +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { commands, Uri } from 'vscode'; -import { IExtensionSingleActivationService } from '../../activation/types'; -import { IApplicationShell, ICommandManager } from '../../common/application/types'; -import { ContextKey } from '../../common/contextKey'; -import '../../common/extensions'; -import { IDisposableRegistry } from '../../common/types'; -import { swallowExceptions } from '../../common/utils/decorators'; -import { DataScience } from '../../common/utils/localize'; -import { sendTelemetryEvent } from '../../telemetry'; -import { Commands, Telemetry } from '../constants'; -import { INotebookStorageProvider } from '../notebookStorage/notebookStorageProvider'; -import { INotebookEditorProvider, ITrustService } from '../types'; - -@injectable() -export class TrustCommandHandler implements IExtensionSingleActivationService { - constructor( - @inject(ITrustService) private readonly trustService: ITrustService, - @inject(INotebookEditorProvider) private readonly editorProvider: INotebookEditorProvider, - @inject(INotebookStorageProvider) private readonly storageProvider: INotebookStorageProvider, - @inject(ICommandManager) private readonly commandManager: ICommandManager, - @inject(IApplicationShell) private readonly applicationShell: IApplicationShell, - @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry - ) {} - public async activate(): Promise { - this.activateInBackground().ignoreErrors(); - } - public async activateInBackground(): Promise { - const context = new ContextKey('python.datascience.trustfeatureenabled', this.commandManager); - context.set(true).ignoreErrors(); - this.disposables.push(this.commandManager.registerCommand(Commands.TrustNotebook, this.onTrustNotebook, this)); - } - @swallowExceptions('Trusting notebook') - private async onTrustNotebook(uri?: Uri) { - uri = uri ?? this.editorProvider.activeEditor?.file; - if (!uri) { - return; - } - - const model = await this.storageProvider.getOrCreateModel({ file: uri }); - if (model.isTrusted) { - return; - } - - const selection = await this.applicationShell.showErrorMessage( - DataScience.launchNotebookTrustPrompt(), - DataScience.trustNotebook(), - DataScience.doNotTrustNotebook(), - DataScience.trustAllNotebooks() - ); - sendTelemetryEvent(Telemetry.NotebookTrustPromptShown); - - switch (selection) { - case DataScience.trustAllNotebooks(): - commands.executeCommand('workbench.action.openSettings', 'python.dataScience.alwaysTrustNotebooks'); - sendTelemetryEvent(Telemetry.TrustAllNotebooks); - break; - case DataScience.trustNotebook(): - // Update model trust - model.trust(); - const contents = model.getContent(); - await this.trustService.trustNotebook(model.file, contents); - sendTelemetryEvent(Telemetry.TrustNotebook); - break; - case DataScience.doNotTrustNotebook(): - sendTelemetryEvent(Telemetry.DoNotTrustNotebook); - break; - default: - break; - } - } -} diff --git a/src/client/datascience/interactive-ipynb/trustService.ts b/src/client/datascience/interactive-ipynb/trustService.ts deleted file mode 100644 index 3b5516cb2e8f..000000000000 --- a/src/client/datascience/interactive-ipynb/trustService.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { createHmac } from 'crypto'; -import { inject, injectable } from 'inversify'; -import { EventEmitter, Uri } from 'vscode'; -import { IConfigurationService } from '../../common/types'; -import { IDigestStorage, ITrustService } from '../types'; - -@injectable() -export class TrustService implements ITrustService { - public get onDidSetNotebookTrust() { - return this._onDidSetNotebookTrust.event; - } - private get alwaysTrustNotebooks() { - return this.configService.getSettings().datascience.alwaysTrustNotebooks; - } - protected readonly _onDidSetNotebookTrust = new EventEmitter(); - constructor( - @inject(IDigestStorage) private readonly digestStorage: IDigestStorage, - @inject(IConfigurationService) private configService: IConfigurationService - ) {} - - /** - * When a notebook is opened, we check the database to see if a trusted checkpoint - * for this notebook exists by computing and looking up its digest. - * If the digest does not exist, the notebook is marked untrusted. - * Once a notebook is loaded in an untrusted state, no code will be executed and no - * markdown will be rendered until notebook as a whole is marked trusted - */ - public async isNotebookTrusted(uri: Uri, notebookContents: string) { - if (this.alwaysTrustNotebooks) { - return true; // Skip check if user manually overrode our trust checking - } - // Compute digest and see if notebook is trusted - const digest = await this.computeDigest(notebookContents); - return this.digestStorage.containsDigest(uri, digest); - } - - /** - * Call this method on a notebook save - * It will add a new trusted checkpoint to the local database if it's safe to do so - * I.e. if the notebook has already been trusted by the user - */ - public async trustNotebook(uri: Uri, notebookContents: string) { - if (!this.alwaysTrustNotebooks) { - // Only update digest store if the user wants us to check trust - const digest = await this.computeDigest(notebookContents); - await this.digestStorage.saveDigest(uri, digest); - this._onDidSetNotebookTrust.fire(); - } - } - - private async computeDigest(notebookContents: string) { - const hmac = createHmac('sha256', await this.digestStorage.key); - hmac.update(notebookContents); - return hmac.digest('hex'); - } -} diff --git a/src/client/datascience/interactive-window/identity.ts b/src/client/datascience/interactive-window/identity.ts deleted file mode 100644 index 745630a85ae1..000000000000 --- a/src/client/datascience/interactive-window/identity.ts +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import * as path from 'path'; -import * as uuid from 'uuid/v4'; -import { Uri } from 'vscode'; -import '../../common/extensions'; -import * as localize from '../../common/utils/localize'; - -let identities: string[] = []; -let createCount = 0; - -export function getDefaultInteractiveIdentity(): Uri { - // Always return the first one - if (identities.length <= 0) { - identities.push(uuid()); - } - return Uri.parse(`history://${identities[0]}`); -} - -// Between test runs reset our identity -export function resetIdentity() { - createCount = 0; - identities = []; -} - -export function createInteractiveIdentity(): Uri { - if (createCount > 0 || identities.length <= 0) { - identities.push(uuid()); - } - createCount += 1; - return Uri.parse(`history://${identities[identities.length - 1]}`); -} - -export function createExportInteractiveIdentity(): Uri { - return Uri.parse(`history://${uuid()}`); -} - -export function getInteractiveWindowTitle(owner: Uri): string { - return localize.DataScience.interactiveWindowTitleFormat().format(path.basename(owner.fsPath)); -} diff --git a/src/client/datascience/interactive-window/interactiveWindow.ts b/src/client/datascience/interactive-window/interactiveWindow.ts deleted file mode 100644 index 4e60a2b94188..000000000000 --- a/src/client/datascience/interactive-window/interactiveWindow.ts +++ /dev/null @@ -1,534 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import type { nbformat } from '@jupyterlab/coreutils'; -import * as path from 'path'; -import { Event, EventEmitter, Memento, Uri, ViewColumn } from 'vscode'; -import { - IApplicationShell, - ICommandManager, - IDocumentManager, - ILiveShareApi, - IWebviewPanelProvider, - IWorkspaceService -} from '../../common/application/types'; -import { ContextKey } from '../../common/contextKey'; -import '../../common/extensions'; -import { traceError } from '../../common/logger'; - -import { - IConfigurationService, - IDisposableRegistry, - IExperimentsManager, - InteractiveWindowMode, - IPersistentStateFactory, - Resource -} from '../../common/types'; -import { createDeferred, Deferred } from '../../common/utils/async'; -import * as localize from '../../common/utils/localize'; -import { EXTENSION_ROOT_DIR } from '../../constants'; -import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; -import { Commands, EditorContexts, Identifiers, Telemetry } from '../constants'; -import { IDataViewerFactory } from '../data-viewing/types'; -import { ExportUtil } from '../export/exportUtil'; -import { InteractiveBase } from '../interactive-common/interactiveBase'; -import { - INotebookIdentity, - InteractiveWindowMessages, - ISubmitNewCell, - NotebookModelChange, - SysInfoReason -} from '../interactive-common/interactiveWindowTypes'; -import { KernelSelector } from '../jupyter/kernels/kernelSelector'; -import { KernelConnectionMetadata } from '../jupyter/kernels/types'; -import { - ICell, - ICodeCssGenerator, - IDataScienceErrorHandler, - IDataScienceFileSystem, - IInteractiveWindow, - IInteractiveWindowInfo, - IInteractiveWindowListener, - IInteractiveWindowLoadable, - IInteractiveWindowProvider, - IJupyterDebugger, - IJupyterVariableDataProviderFactory, - IJupyterVariables, - INotebookExporter, - INotebookModel, - INotebookProvider, - IStatusProvider, - IThemeFinder, - WebViewViewChangeEventArgs -} from '../types'; -import { createInteractiveIdentity, getInteractiveWindowTitle } from './identity'; - -const historyReactDir = path.join(EXTENSION_ROOT_DIR, 'out', 'datascience-ui', 'notebook'); - -export class InteractiveWindow extends InteractiveBase implements IInteractiveWindowLoadable { - public get onDidChangeViewState(): Event { - return this._onDidChangeViewState.event; - } - public get visible(): boolean { - return this.viewState.visible; - } - public get active(): boolean { - return this.viewState.active; - } - - public get closed(): Event { - return this.closedEvent.event; - } - public get owner(): Resource { - return this._owner; - } - public get submitters(): Uri[] { - return this._submitters; - } - public get identity(): Uri { - return this._identity; - } - private _onDidChangeViewState = new EventEmitter(); - private closedEvent: EventEmitter = new EventEmitter(); - private waitingForExportCells: boolean = false; - private trackedJupyterStart: boolean = false; - private _owner: Uri | undefined; - private _identity: Uri = createInteractiveIdentity(); - private _submitters: Uri[] = []; - private pendingHasCell = new Map>(); - private mode: InteractiveWindowMode = 'multiple'; - private loadPromise: Promise; - constructor( - listeners: IInteractiveWindowListener[], - liveShare: ILiveShareApi, - applicationShell: IApplicationShell, - documentManager: IDocumentManager, - statusProvider: IStatusProvider, - provider: IWebviewPanelProvider, - disposables: IDisposableRegistry, - cssGenerator: ICodeCssGenerator, - themeFinder: IThemeFinder, - fs: IDataScienceFileSystem, - configuration: IConfigurationService, - commandManager: ICommandManager, - jupyterExporter: INotebookExporter, - workspaceService: IWorkspaceService, - private interactiveWindowProvider: IInteractiveWindowProvider, - dataExplorerFactory: IDataViewerFactory, - jupyterVariableDataProviderFactory: IJupyterVariableDataProviderFactory, - jupyterVariables: IJupyterVariables, - jupyterDebugger: IJupyterDebugger, - errorHandler: IDataScienceErrorHandler, - private readonly stateFactory: IPersistentStateFactory, - globalStorage: Memento, - workspaceStorage: Memento, - experimentsManager: IExperimentsManager, - notebookProvider: INotebookProvider, - useCustomEditorApi: boolean, - private exportUtil: ExportUtil, - owner: Resource, - mode: InteractiveWindowMode, - title: string | undefined, - selector: KernelSelector - ) { - super( - listeners, - liveShare, - applicationShell, - documentManager, - provider, - disposables, - cssGenerator, - themeFinder, - statusProvider, - fs, - configuration, - jupyterExporter, - workspaceService, - dataExplorerFactory, - jupyterVariableDataProviderFactory, - jupyterVariables, - jupyterDebugger, - errorHandler, - commandManager, - globalStorage, - workspaceStorage, - historyReactDir, - [ - path.join(historyReactDir, 'require.js'), - path.join(historyReactDir, 'ipywidgets.js'), - path.join(historyReactDir, 'monaco.bundle.js'), - path.join(historyReactDir, 'commons.initial.bundle.js'), - path.join(historyReactDir, 'interactiveWindow.js') - ], - localize.DataScience.interactiveWindowTitle(), - ViewColumn.Two, - experimentsManager, - notebookProvider, - useCustomEditorApi, - selector - ); - - // Send a telemetry event to indicate window is opening - sendTelemetryEvent(Telemetry.OpenedInteractiveWindow); - - // Set our owner and first submitter - this._owner = owner; - this.mode = mode; - if (owner) { - this._submitters.push(owner); - } - - // When opening we have to load the web panel. - this.loadPromise = this.loadWebPanel(this.owner ? path.dirname(this.owner.fsPath) : process.cwd()) - .then(async () => { - // Always load our notebook. - await this.ensureConnectionAndNotebook(); - - // Then the initial sys info - await this.addSysInfo(SysInfoReason.Start); - }) - .catch((e) => this.errorHandler.handleError(e)); - - // Update the title if possible - if (this.owner && mode === 'perFile') { - this.setTitle(getInteractiveWindowTitle(this.owner)); - } else if (title) { - this.setTitle(title); - } - } - - public async show(preserveFocus: boolean = true): Promise { - await this.loadPromise; - return super.show(preserveFocus); - } - - public dispose() { - super.dispose(); - if (this.notebook) { - this.notebook.dispose().ignoreErrors(); - } - if (this.closedEvent) { - this.closedEvent.fire(this); - } - } - - public addMessage(message: string): Promise { - this.addMessageImpl(message); - return Promise.resolve(); - } - - public changeMode(mode: InteractiveWindowMode): void { - if (this.mode !== mode) { - this.mode = mode; - if (this.owner && mode === 'perFile') { - this.setTitle(getInteractiveWindowTitle(this.owner)); - } - } - } - - public async addCode(code: string, file: Uri, line: number): Promise { - return this.addOrDebugCode(code, file, line, false); - } - - public exportCells() { - // First ask for all cells. Set state to indicate waiting for result - this.waitingForExportCells = true; - - // Telemetry will fire when the export function is called. - this.postMessage(InteractiveWindowMessages.GetAllCells).ignoreErrors(); - } - - // tslint:disable-next-line: no-any - public onMessage(message: string, payload: any) { - super.onMessage(message, payload); - - switch (message) { - case InteractiveWindowMessages.Export: - this.handleMessage(message, payload, this.export); - break; - - case InteractiveWindowMessages.ReturnAllCells: - this.handleMessage(message, payload, this.handleReturnAllCells); - break; - - case InteractiveWindowMessages.UpdateModel: - this.handleMessage(message, payload, this.handleModelChange); - break; - - case InteractiveWindowMessages.ExportNotebookAs: - this.handleMessage(message, payload, this.exportAs); - break; - - case InteractiveWindowMessages.HasCellResponse: - this.handleMessage(message, payload, this.handleHasCellResponse); - break; - - default: - break; - } - } - - public async debugCode(code: string, file: Uri, line: number): Promise { - let saved = true; - // Make sure the file is saved before debugging - const doc = this.documentManager.textDocuments.find((d) => this.fs.areLocalPathsSame(d.fileName, file.fsPath)); - if (doc && doc.isUntitled) { - // Before we start, get the list of documents - const beforeSave = [...this.documentManager.textDocuments]; - - saved = await doc.save(); - - // If that worked, we have to open the new document. It should be - // the new entry in the list - if (saved) { - const diff = this.documentManager.textDocuments.filter((f) => beforeSave.indexOf(f) === -1); - if (diff && diff.length > 0) { - file = diff[0].uri; - - // Open the new document - await this.documentManager.openTextDocument(file); - } - } - } - - // Call the internal method if we were able to save - if (saved) { - return this.addOrDebugCode(code, file, line, true); - } - - return false; - } - - @captureTelemetry(Telemetry.ExpandAll) - public expandAllCells() { - this.postMessage(InteractiveWindowMessages.ExpandAll).ignoreErrors(); - } - - @captureTelemetry(Telemetry.CollapseAll) - public collapseAllCells() { - this.postMessage(InteractiveWindowMessages.CollapseAll).ignoreErrors(); - } - - @captureTelemetry(Telemetry.ScrolledToCell) - public scrollToCell(id: string): void { - this.show(false) - .then(() => { - return this.postMessage(InteractiveWindowMessages.ScrollToCell, { id }); - }) - .ignoreErrors(); - } - - public hasCell(id: string): Promise { - let deferred = this.pendingHasCell.get(id); - if (!deferred) { - deferred = createDeferred(); - this.pendingHasCell.set(id, deferred); - this.postMessage(InteractiveWindowMessages.HasCell, id).ignoreErrors(); - } - return deferred.promise; - } - - public get owningResource(): Resource { - if (this.owner) { - return this.owner; - } - const root = this.workspaceService.rootPath; - if (root) { - return Uri.file(root); - } - return undefined; - } - protected async addSysInfo(reason: SysInfoReason): Promise { - await super.addSysInfo(reason); - - // If `reason == Start`, then this means UI has been updated with the last - // pience of informaiotn (which was sys info), and now UI can be deemed as having been loaded. - // Marking a UI as having been loaded is done by sending a message `LoadAllCells`, even though we're not loading any cells. - // We're merely using existing messages (from NativeEditor). - if (reason === SysInfoReason.Start) { - this.postMessage(InteractiveWindowMessages.LoadAllCells, { cells: [] }).ignoreErrors(); - } - } - protected async onViewStateChanged(args: WebViewViewChangeEventArgs) { - super.onViewStateChanged(args); - this._onDidChangeViewState.fire(); - } - - @captureTelemetry(Telemetry.SubmitCellThroughInput, undefined, false) - // tslint:disable-next-line:no-any - protected submitNewCell(info: ISubmitNewCell) { - // If there's any payload, it has the code and the id - if (info && info.code && info.id) { - // Send to ourselves. - this.submitCode(info.code, Identifiers.EmptyFileName, 0, info.id).ignoreErrors(); - - // Activate the other side, and send as if came from a file - this.interactiveWindowProvider - .synchronize(this) - .then((_v) => { - this.shareMessage(InteractiveWindowMessages.RemoteAddCode, { - code: info.code, - file: Identifiers.EmptyFileName, - line: 0, - id: info.id, - originator: this.id, - debug: false - }); - }) - .ignoreErrors(); - } - } - - protected get notebookMetadata(): nbformat.INotebookMetadata | undefined { - return undefined; - } - - protected async updateNotebookOptions(_kernelConnection: KernelConnectionMetadata): Promise { - // Do nothing as this data isn't stored in our options. - } - - protected get notebookIdentity(): INotebookIdentity { - // Use this identity for the lifetime of the notebook - return { - resource: this._identity, - type: 'interactive' - }; - } - - protected updateContexts(info: IInteractiveWindowInfo | undefined) { - // This should be called by the python interactive window every - // time state changes. We use this opportunity to update our - // extension contexts - if (this.commandManager && this.commandManager.executeCommand) { - const interactiveContext = new ContextKey(EditorContexts.HaveInteractive, this.commandManager); - interactiveContext.set(!this.isDisposed).catch(); - const interactiveCellsContext = new ContextKey(EditorContexts.HaveInteractiveCells, this.commandManager); - const redoableContext = new ContextKey(EditorContexts.HaveRedoableCells, this.commandManager); - const hasCellSelectedContext = new ContextKey(EditorContexts.HaveCellSelected, this.commandManager); - if (info) { - interactiveCellsContext.set(info.cellCount > 0).catch(); - redoableContext.set(info.redoCount > 0).catch(); - hasCellSelectedContext.set(info.selectedCell ? true : false).catch(); - } else { - interactiveCellsContext.set(false).catch(); - redoableContext.set(false).catch(); - hasCellSelectedContext.set(false).catch(); - } - } - } - - protected async closeBecauseOfFailure(_exc: Error): Promise { - this.dispose(); - } - protected ensureConnectionAndNotebook(): Promise { - // Keep track of users who have used interactive window in a worksapce folder. - // To be used if/when changing workflows related to startup of jupyter. - if (!this.trackedJupyterStart) { - this.trackedJupyterStart = true; - const store = this.stateFactory.createGlobalPersistentState('INTERACTIVE_WINDOW_USED', false); - store.updateValue(true).ignoreErrors(); - } - return super.ensureConnectionAndNotebook(); - } - - private async addOrDebugCode(code: string, file: Uri, line: number, debug: boolean): Promise { - if (this.owner && !this.fs.areLocalPathsSame(file.fsPath, this.owner.fsPath)) { - sendTelemetryEvent(Telemetry.NewFileForInteractiveWindow); - } - // Update the owner for this window if not already set - if (!this._owner) { - this._owner = file; - - // Update the title if we're in per file mode - if (this.mode === 'perFile') { - this.setTitle(getInteractiveWindowTitle(file)); - } - } - - // Add to the list of 'submitters' for this window. - if (!this._submitters.find((s) => this.fs.areLocalPathsSame(s.fsPath, file.fsPath))) { - this._submitters.push(file); - } - - // Make sure our web panel opens. - await this.show(); - - // Tell the webpanel about the new directory. - this.updateCwd(path.dirname(file.fsPath)); - - // Call the internal method. - return this.submitCode(code, file.fsPath, line, undefined, undefined, debug ? { runByLine: false } : undefined); - } - - @captureTelemetry(Telemetry.ExportNotebookInteractive, undefined, false) - // tslint:disable-next-line: no-any no-empty - private async export(cells: ICell[]) { - // Should be an array of cells - if (cells && this.applicationShell) { - // Indicate busy - this.startProgress(); - try { - const filtersKey = localize.DataScience.exportDialogFilter(); - const filtersObject: Record = {}; - filtersObject[filtersKey] = ['ipynb']; - - // Bring up the open file dialog box - const uri = await this.applicationShell.showSaveDialog({ - saveLabel: localize.DataScience.exportDialogTitle(), - filters: filtersObject - }); - if (uri) { - await this.jupyterExporter.exportToFile(cells, uri.fsPath); - } - } finally { - this.stopProgress(); - } - } - } - - private async exportAs(cells: ICell[]) { - let model: INotebookModel; - - this.startProgress(); - try { - model = await this.exportUtil.getModelFromCells(cells); - } finally { - this.stopProgress(); - } - if (model) { - let defaultFileName; - if (this.submitters && this.submitters.length) { - const lastSubmitter = this.submitters[this.submitters.length - 1]; - defaultFileName = path.basename(lastSubmitter.fsPath, path.extname(lastSubmitter.fsPath)); - } - this.commandManager.executeCommand(Commands.Export, model, defaultFileName); - } - } - - private handleModelChange(update: NotebookModelChange) { - // Send telemetry for delete and delete all. We don't send telemetry for the other updates yet - if (update.source === 'user') { - if (update.kind === 'remove_all') { - sendTelemetryEvent(Telemetry.DeleteAllCells); - } else if (update.kind === 'remove') { - sendTelemetryEvent(Telemetry.DeleteCell); - } - } - } - - // tslint:disable-next-line:no-any - private handleReturnAllCells(cells: ICell[]) { - // See what we're waiting for. - if (this.waitingForExportCells) { - this.export(cells).catch((ex) => traceError('Error exporting:', ex)); - } - } - - private handleHasCellResponse(response: { id: string; result: boolean }) { - const deferred = this.pendingHasCell.get(response.id); - if (deferred) { - deferred.resolve(response.result); - this.pendingHasCell.delete(response.id); - } - } -} diff --git a/src/client/datascience/interactive-window/interactiveWindowCommandListener.ts b/src/client/datascience/interactive-window/interactiveWindowCommandListener.ts deleted file mode 100644 index f68d89c200c1..000000000000 --- a/src/client/datascience/interactive-window/interactiveWindowCommandListener.ts +++ /dev/null @@ -1,499 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; - -import { inject, injectable } from 'inversify'; -import * as uuid from 'uuid/v4'; -import { Range, TextDocument, Uri } from 'vscode'; -import { CancellationToken, CancellationTokenSource } from 'vscode-jsonrpc'; -import { IApplicationShell, ICommandManager, IDocumentManager } from '../../common/application/types'; -import { CancellationError } from '../../common/cancellation'; -import { PYTHON_LANGUAGE } from '../../common/constants'; -import { traceError, traceInfo } from '../../common/logger'; -import { IConfigurationService, IDisposableRegistry } from '../../common/types'; -import * as localize from '../../common/utils/localize'; -import { captureTelemetry } from '../../telemetry'; -import { CommandSource } from '../../testing/common/constants'; -import { generateCellRangesFromDocument, generateCellsFromDocument } from '../cellFactory'; -import { Commands, Telemetry } from '../constants'; -import { ExportFormat, IExportManager } from '../export/types'; -import { JupyterInstallError } from '../jupyter/jupyterInstallError'; -import { INotebookStorageProvider } from '../notebookStorage/notebookStorageProvider'; -import { - IDataScienceCommandListener, - IDataScienceErrorHandler, - IDataScienceFileSystem, - IInteractiveBase, - IInteractiveWindowProvider, - IJupyterExecution, - INotebook, - INotebookEditorProvider, - INotebookExporter, - INotebookProvider, - IStatusProvider -} from '../types'; -import { createExportInteractiveIdentity } from './identity'; - -@injectable() -export class InteractiveWindowCommandListener implements IDataScienceCommandListener { - constructor( - @inject(IDisposableRegistry) private disposableRegistry: IDisposableRegistry, - @inject(IInteractiveWindowProvider) private interactiveWindowProvider: IInteractiveWindowProvider, - @inject(INotebookExporter) private jupyterExporter: INotebookExporter, - @inject(IJupyterExecution) private jupyterExecution: IJupyterExecution, - @inject(INotebookProvider) private notebookProvider: INotebookProvider, - @inject(IDocumentManager) private documentManager: IDocumentManager, - @inject(IApplicationShell) private applicationShell: IApplicationShell, - @inject(IDataScienceFileSystem) private fileSystem: IDataScienceFileSystem, - @inject(IConfigurationService) private configuration: IConfigurationService, - @inject(IStatusProvider) private statusProvider: IStatusProvider, - @inject(IDataScienceErrorHandler) private dataScienceErrorHandler: IDataScienceErrorHandler, - @inject(INotebookEditorProvider) protected ipynbProvider: INotebookEditorProvider, - @inject(IExportManager) private exportManager: IExportManager, - @inject(INotebookStorageProvider) private notebookStorageProvider: INotebookStorageProvider - ) {} - - public register(commandManager: ICommandManager): void { - let disposable = commandManager.registerCommand(Commands.CreateNewInteractive, () => - this.createNewInteractiveWindow() - ); - this.disposableRegistry.push(disposable); - disposable = commandManager.registerCommand( - Commands.ImportNotebook, - (file?: Uri, _cmdSource: CommandSource = CommandSource.commandPalette) => { - return this.listenForErrors(() => { - if (file) { - return this.importNotebookOnFile(file); - } else { - return this.importNotebook(); - } - }); - } - ); - this.disposableRegistry.push(disposable); - disposable = commandManager.registerCommand( - Commands.ImportNotebookFile, - (file?: Uri, _cmdSource: CommandSource = CommandSource.commandPalette) => { - return this.listenForErrors(() => { - if (file) { - return this.importNotebookOnFile(file); - } else { - return this.importNotebook(); - } - }); - } - ); - this.disposableRegistry.push(disposable); - disposable = commandManager.registerCommand( - Commands.ExportFileAsNotebook, - (file?: Uri, _cmdSource: CommandSource = CommandSource.commandPalette) => { - return this.listenForErrors(() => { - if (file) { - return this.exportFile(file); - } else { - const activeEditor = this.documentManager.activeTextEditor; - if (activeEditor && activeEditor.document.languageId === PYTHON_LANGUAGE) { - return this.exportFile(activeEditor.document.uri); - } - } - - return Promise.resolve(); - }); - } - ); - this.disposableRegistry.push(disposable); - disposable = commandManager.registerCommand( - Commands.ExportFileAndOutputAsNotebook, - (file: Uri, _cmdSource: CommandSource = CommandSource.commandPalette) => { - return this.listenForErrors(() => { - if (file) { - return this.exportFileAndOutput(file); - } else { - const activeEditor = this.documentManager.activeTextEditor; - if (activeEditor && activeEditor.document.languageId === PYTHON_LANGUAGE) { - return this.exportFileAndOutput(activeEditor.document.uri); - } - } - return Promise.resolve(); - }); - } - ); - this.disposableRegistry.push(disposable); - this.disposableRegistry.push(commandManager.registerCommand(Commands.UndoCells, () => this.undoCells())); - this.disposableRegistry.push(commandManager.registerCommand(Commands.RedoCells, () => this.redoCells())); - this.disposableRegistry.push( - commandManager.registerCommand(Commands.RemoveAllCells, () => this.removeAllCells()) - ); - this.disposableRegistry.push( - commandManager.registerCommand(Commands.InterruptKernel, () => this.interruptKernel()) - ); - this.disposableRegistry.push( - commandManager.registerCommand(Commands.RestartKernel, () => this.restartKernel()) - ); - this.disposableRegistry.push( - commandManager.registerCommand(Commands.ExpandAllCells, () => this.expandAllCells()) - ); - this.disposableRegistry.push( - commandManager.registerCommand(Commands.CollapseAllCells, () => this.collapseAllCells()) - ); - this.disposableRegistry.push( - commandManager.registerCommand(Commands.ExportOutputAsNotebook, () => this.exportCells()) - ); - this.disposableRegistry.push( - commandManager.registerCommand(Commands.ScrollToCell, (file: Uri, id: string) => - this.scrollToCell(file, id) - ) - ); - } - - // tslint:disable:no-any - private async listenForErrors(promise: () => Promise): Promise { - let result: any; - try { - result = await promise(); - return result; - } catch (err) { - if (!(err instanceof CancellationError)) { - if (err.message) { - traceError(err.message); - this.applicationShell.showErrorMessage(err.message); - } else { - traceError(err.toString()); - this.applicationShell.showErrorMessage(err.toString()); - } - } else { - traceInfo('Canceled'); - } - } - return result; - } - - private showInformationMessage(message: string, question?: string): Thenable { - if (question) { - return this.applicationShell.showInformationMessage(message, question); - } else { - return this.applicationShell.showInformationMessage(message); - } - } - - @captureTelemetry(Telemetry.ExportPythonFileInteractive, undefined, false) - private async exportFile(file: Uri): Promise { - if (file && file.fsPath && file.fsPath.length > 0) { - // If the current file is the active editor, then generate cells from the document. - const activeEditor = this.documentManager.activeTextEditor; - if (activeEditor && this.fileSystem.arePathsSame(activeEditor.document.uri, file)) { - const cells = generateCellsFromDocument( - activeEditor.document, - this.configuration.getSettings(activeEditor.document.uri).datascience - ); - if (cells) { - const filtersKey = localize.DataScience.exportDialogFilter(); - const filtersObject: { [name: string]: string[] } = {}; - filtersObject[filtersKey] = ['ipynb']; - - // Bring up the save file dialog box - const uri = await this.applicationShell.showSaveDialog({ - saveLabel: localize.DataScience.exportDialogTitle(), - filters: filtersObject - }); - await this.waitForStatus( - async () => { - if (uri) { - let directoryChange; - const settings = this.configuration.getSettings(activeEditor.document.uri); - if (settings.datascience.changeDirOnImportExport) { - directoryChange = uri; - } - - const notebook = await this.jupyterExporter.translateToNotebook( - cells, - directoryChange?.fsPath - ); - await this.fileSystem.writeFile(uri, JSON.stringify(notebook)); - } - }, - localize.DataScience.exportingFormat(), - file.fsPath - ); - // When all done, show a notice that it completed. - if (uri && uri.fsPath) { - const openQuestion1 = localize.DataScience.exportOpenQuestion1(); - const openQuestion2 = (await this.jupyterExecution.isSpawnSupported()) - ? localize.DataScience.exportOpenQuestion() - : undefined; - const questions = [openQuestion1, ...(openQuestion2 ? [openQuestion2] : [])]; - const selection = await this.applicationShell.showInformationMessage( - localize.DataScience.exportDialogComplete().format(uri.fsPath), - ...questions - ); - if (selection === openQuestion1) { - await this.ipynbProvider.open(uri); - } - if (selection === openQuestion2) { - // If the user wants to, open the notebook they just generated. - this.jupyterExecution.spawnNotebook(uri.fsPath).ignoreErrors(); - } - } - } - } - } - } - - @captureTelemetry(Telemetry.ExportPythonFileAndOutputInteractive, undefined, false) - private async exportFileAndOutput(file: Uri): Promise { - if (file && file.fsPath && file.fsPath.length > 0 && (await this.jupyterExecution.isNotebookSupported())) { - // If the current file is the active editor, then generate cells from the document. - const activeEditor = this.documentManager.activeTextEditor; - if ( - activeEditor && - activeEditor.document && - this.fileSystem.arePathsSame(activeEditor.document.uri, file) - ) { - const ranges = generateCellRangesFromDocument(activeEditor.document); - if (ranges.length > 0) { - // Ask user for path - const output = await this.showExportDialog(); - - // If that worked, we need to start a jupyter server to get our output values. - // In the future we could potentially only update changed cells. - if (output) { - // Create a cancellation source so we can cancel starting the jupyter server if necessary - const cancelSource = new CancellationTokenSource(); - - // Then wait with status that lets the user cancel - await this.waitForStatus( - () => { - try { - return this.exportCellsWithOutput( - ranges, - activeEditor.document, - output, - cancelSource.token - ); - } catch (err) { - if (!(err instanceof CancellationError)) { - this.showInformationMessage( - localize.DataScience.exportDialogFailed().format(err) - ); - } - } - return Promise.resolve(); - }, - localize.DataScience.exportingFormat(), - file.fsPath, - () => { - cancelSource.cancel(); - } - ); - - // When all done, show a notice that it completed. - const openQuestion1 = localize.DataScience.exportOpenQuestion1(); - const openQuestion2 = (await this.jupyterExecution.isSpawnSupported()) - ? localize.DataScience.exportOpenQuestion() - : undefined; - const questions = [openQuestion1, ...(openQuestion2 ? [openQuestion2] : [])]; - const selection = await this.applicationShell.showInformationMessage( - localize.DataScience.exportDialogComplete().format(output.fsPath), - ...questions - ); - if (selection === openQuestion1) { - await this.ipynbProvider.open(output); - } - if (selection === openQuestion2) { - // If the user wants to, open the notebook they just generated. - this.jupyterExecution.spawnNotebook(output.fsPath).ignoreErrors(); - } - return output; - } - } - } - } else { - await this.dataScienceErrorHandler.handleError( - new JupyterInstallError( - localize.DataScience.jupyterNotSupported().format(await this.jupyterExecution.getNotebookError()), - localize.DataScience.pythonInteractiveHelpLink() - ) - ); - } - } - - private async exportCellsWithOutput( - ranges: { range: Range; title: string }[], - document: TextDocument, - file: Uri, - cancelToken: CancellationToken - ): Promise { - let notebook: INotebook | undefined; - try { - const settings = this.configuration.getSettings(document.uri); - // Create a new notebook - notebook = await this.notebookProvider.getOrCreateNotebook({ identity: createExportInteractiveIdentity() }); - // If that works, then execute all of the cells. - const cells = Array.prototype.concat( - ...(await Promise.all( - ranges.map((r) => { - const code = document.getText(r.range); - return notebook - ? notebook.execute(code, document.fileName, r.range.start.line, uuid(), cancelToken) - : []; - }) - )) - ); - // Then save them to the file - let directoryChange; - if (settings.datascience.changeDirOnImportExport) { - directoryChange = file; - } - const notebookJson = await this.jupyterExporter.translateToNotebook(cells, directoryChange?.fsPath); - await this.fileSystem.writeFile(file, JSON.stringify(notebookJson)); - } finally { - if (notebook) { - await notebook.dispose(); - } - } - } - - private async showExportDialog(): Promise { - const filtersKey = localize.DataScience.exportDialogFilter(); - const filtersObject: { [name: string]: string[] } = {}; - filtersObject[filtersKey] = ['ipynb']; - - // Bring up the save file dialog box - return this.applicationShell.showSaveDialog({ - saveLabel: localize.DataScience.exportDialogTitle(), - filters: filtersObject - }); - } - - private undoCells() { - const interactiveWindow = this.interactiveWindowProvider.activeWindow; - if (interactiveWindow) { - interactiveWindow.undoCells(); - } - } - - private redoCells() { - const interactiveWindow = this.interactiveWindowProvider.activeWindow; - if (interactiveWindow) { - interactiveWindow.redoCells(); - } - } - - private removeAllCells() { - const interactiveWindow = this.interactiveWindowProvider.activeWindow; - if (interactiveWindow) { - interactiveWindow.removeAllCells(); - } - } - - private interruptKernel() { - const interactiveWindow = this.interactiveWindowProvider.activeWindow; - if (interactiveWindow) { - interactiveWindow.interruptKernel().ignoreErrors(); - } - } - - private restartKernel() { - const interactiveWindow = this.interactiveWindowProvider.activeWindow; - if (interactiveWindow) { - interactiveWindow.restartKernel().ignoreErrors(); - } - } - - private expandAllCells() { - const interactiveWindow = this.interactiveWindowProvider.activeWindow; - if (interactiveWindow) { - interactiveWindow.expandAllCells(); - } - } - - private collapseAllCells() { - const interactiveWindow = this.interactiveWindowProvider.activeWindow; - if (interactiveWindow) { - interactiveWindow.collapseAllCells(); - } - } - - private exportCells() { - const interactiveWindow = this.interactiveWindowProvider.activeWindow; - if (interactiveWindow) { - interactiveWindow.exportCells(); - } - } - - @captureTelemetry(Telemetry.CreateNewInteractive, undefined, false) - private async createNewInteractiveWindow(): Promise { - await this.interactiveWindowProvider.getOrCreate(undefined); - } - - private waitForStatus( - promise: () => Promise, - format: string, - file?: string, - canceled?: () => void, - interactiveWindow?: IInteractiveBase - ): Promise { - const message = file ? format.format(file) : format; - return this.statusProvider.waitWithStatus(promise, message, true, undefined, canceled, interactiveWindow); - } - - @captureTelemetry(Telemetry.ImportNotebook, { scope: 'command' }, false) - private async importNotebook(): Promise { - const filtersKey = localize.DataScience.importDialogFilter(); - const filtersObject: { [name: string]: string[] } = {}; - filtersObject[filtersKey] = ['ipynb']; - - const uris = await this.applicationShell.showOpenDialog({ - openLabel: localize.DataScience.importDialogTitle(), - filters: filtersObject - }); - - if (uris && uris.length > 0) { - // Don't call the other overload as we'll end up with double telemetry. - await this.waitForStatus( - async () => { - const contents = await this.fileSystem.readFile(uris[0]); - const model = await this.notebookStorageProvider.createNew(contents); - await this.exportManager.export(ExportFormat.python, model); - }, - localize.DataScience.importingFormat(), - uris[0].fsPath - ); - } - } - - @captureTelemetry(Telemetry.ImportNotebook, { scope: 'file' }, false) - private async importNotebookOnFile(file: Uri): Promise { - if (file.fsPath && file.fsPath.length > 0) { - await this.waitForStatus( - async () => { - const contents = await this.fileSystem.readFile(file); - const model = await this.notebookStorageProvider.createNew(contents); - await this.exportManager.export(ExportFormat.python, model); - }, - localize.DataScience.importingFormat(), - file.fsPath - ); - } - } - - private async scrollToCell(file: Uri, id: string): Promise { - if (id && file) { - // Find the interactive windows that have this file as a submitter - const possibles = this.interactiveWindowProvider.windows.filter( - (w) => w.submitters.findIndex((s) => this.fileSystem.areLocalPathsSame(s.fsPath, file.fsPath)) >= 0 - ); - - // Scroll to cell in the one that has the cell. We need this so - // we don't activate all of them. - // tslint:disable-next-line: prefer-for-of - for (let i = 0; i < possibles.length; i += 1) { - if (await possibles[i].hasCell(id)) { - possibles[i].scrollToCell(id); - break; - } - } - } - } -} diff --git a/src/client/datascience/interactive-window/interactiveWindowProvider.ts b/src/client/datascience/interactive-window/interactiveWindowProvider.ts deleted file mode 100644 index 1884ff01cb9c..000000000000 --- a/src/client/datascience/interactive-window/interactiveWindowProvider.ts +++ /dev/null @@ -1,336 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { inject, injectable, named } from 'inversify'; -import * as uuid from 'uuid/v4'; -import { ConfigurationTarget, Event, EventEmitter, Memento, Uri } from 'vscode'; -import * as vsls from 'vsls/vscode'; - -import { - IApplicationShell, - ICommandManager, - IDocumentManager, - ILiveShareApi, - IWebviewPanelProvider, - IWorkspaceService -} from '../../common/application/types'; -import { UseCustomEditorApi } from '../../common/constants'; - -import { - GLOBAL_MEMENTO, - IAsyncDisposable, - IAsyncDisposableRegistry, - IConfigurationService, - IDisposableRegistry, - IExperimentsManager, - IMemento, - InteractiveWindowMode, - IPersistentStateFactory, - Resource, - WORKSPACE_MEMENTO -} from '../../common/types'; -import { createDeferred, Deferred } from '../../common/utils/async'; -import * as localize from '../../common/utils/localize'; -import { IServiceContainer } from '../../ioc/types'; -import { Identifiers, LiveShare, LiveShareCommands } from '../constants'; -import { IDataViewerFactory } from '../data-viewing/types'; -import { ExportUtil } from '../export/exportUtil'; -import { KernelSelector } from '../jupyter/kernels/kernelSelector'; -import { PostOffice } from '../liveshare/postOffice'; -import { - ICodeCssGenerator, - IDataScienceErrorHandler, - IDataScienceFileSystem, - IInteractiveWindow, - IInteractiveWindowListener, - IInteractiveWindowLoadable, - IInteractiveWindowProvider, - IJupyterDebugger, - IJupyterVariableDataProviderFactory, - IJupyterVariables, - INotebookExporter, - INotebookProvider, - IStatusProvider, - IThemeFinder -} from '../types'; -import { InteractiveWindow } from './interactiveWindow'; - -interface ISyncData { - count: number; - waitable: Deferred; -} - -// Export for testing -export const AskedForPerFileSettingKey = 'ds_asked_per_file_interactive'; - -@injectable() -export class InteractiveWindowProvider implements IInteractiveWindowProvider, IAsyncDisposable { - public get onDidChangeActiveInteractiveWindow(): Event { - return this._onDidChangeActiveInteractiveWindow.event; - } - public get activeWindow(): IInteractiveWindow | undefined { - return this._windows.find((w) => w.active && w.visible); - } - public get windows(): ReadonlyArray { - return this._windows; - } - private readonly _onDidChangeActiveInteractiveWindow = new EventEmitter(); - private lastActiveInteractiveWindow: IInteractiveWindow | undefined; - private postOffice: PostOffice; - private id: string; - private pendingSyncs: Map = new Map(); - private _windows: IInteractiveWindowLoadable[] = []; - constructor( - @inject(ILiveShareApi) liveShare: ILiveShareApi, - @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(IAsyncDisposableRegistry) asyncRegistry: IAsyncDisposableRegistry, - @inject(IDisposableRegistry) private disposables: IDisposableRegistry, - @inject(IDataScienceFileSystem) private readonly fs: IDataScienceFileSystem, - @inject(IConfigurationService) private readonly configService: IConfigurationService, - @inject(IMemento) @named(GLOBAL_MEMENTO) private readonly globalMemento: Memento, - @inject(IApplicationShell) private readonly appShell: IApplicationShell - ) { - asyncRegistry.push(this); - - // Create a post office so we can make sure interactive windows are created at the same time - // on both sides. - this.postOffice = new PostOffice(LiveShare.InteractiveWindowProviderService, liveShare); - - // Listen for peer changes - this.postOffice.peerCountChanged((n) => this.onPeerCountChanged(n)); - - // Listen for messages so we force a create on both sides. - this.postOffice - .registerCallback(LiveShareCommands.interactiveWindowCreate, this.onRemoteCreate, this) - .ignoreErrors(); - this.postOffice - .registerCallback(LiveShareCommands.interactiveWindowCreateSync, this.onRemoteSync, this) - .ignoreErrors(); - - // Make a unique id so we can tell who sends a message - this.id = uuid(); - } - - public async getOrCreate(resource: Resource): Promise { - // Ask for a configuration change if appropriate - const mode = await this.getInteractiveMode(resource); - - // See if we already have a match - let result = this.get(resource, mode) as InteractiveWindow; - if (!result) { - // No match. Create a new item. - result = this.create(resource, mode); - - // Wait for monaco ready (it's not really useable until it has a language) - const readyPromise = createDeferred(); - const disposable = result.ready(() => readyPromise.resolve()); - - // Wait for monaco ready - await readyPromise.promise; - disposable.dispose(); - } - - // Wait for synchronization in liveshare - await this.synchronize(result); - - return result; - } - - public dispose(): Promise { - return this.postOffice.dispose(); - } - - public async synchronize(window: IInteractiveWindow): Promise { - // Create a new pending wait if necessary - if (this.postOffice.peerCount > 0 || this.postOffice.role === vsls.Role.Guest) { - const key = window.identity.toString(); - const owner = window.owner?.toString(); - const waitable = createDeferred(); - this.pendingSyncs.set(key, { count: this.postOffice.peerCount, waitable }); - - // Make sure all providers have an active interactive window - await this.postOffice.postCommand(LiveShareCommands.interactiveWindowCreate, this.id, key, owner); - - // Wait for the waitable to be signaled or the peer count on the post office to change - await waitable.promise; - } - } - - protected create(resource: Resource, mode: InteractiveWindowMode): InteractiveWindow { - const title = - mode === 'multiple' || (mode === 'perFile' && !resource) - ? localize.DataScience.interactiveWindowTitleFormat().format(`#${this._windows.length + 1}`) - : undefined; - - // Set it as soon as we create it. The .ctor for the interactive window - // may cause a subclass to talk to the IInteractiveWindowProvider to get the active interactive window. - const result = new InteractiveWindow( - this.serviceContainer.getAll(IInteractiveWindowListener), - this.serviceContainer.get(ILiveShareApi), - this.serviceContainer.get(IApplicationShell), - this.serviceContainer.get(IDocumentManager), - this.serviceContainer.get(IStatusProvider), - this.serviceContainer.get(IWebviewPanelProvider), - this.serviceContainer.get(IDisposableRegistry), - this.serviceContainer.get(ICodeCssGenerator), - this.serviceContainer.get(IThemeFinder), - this.serviceContainer.get(IDataScienceFileSystem), - this.serviceContainer.get(IConfigurationService), - this.serviceContainer.get(ICommandManager), - this.serviceContainer.get(INotebookExporter), - this.serviceContainer.get(IWorkspaceService), - this, - this.serviceContainer.get(IDataViewerFactory), - this.serviceContainer.get(IJupyterVariableDataProviderFactory), - this.serviceContainer.get(IJupyterVariables, Identifiers.ALL_VARIABLES), - this.serviceContainer.get(IJupyterDebugger), - this.serviceContainer.get(IDataScienceErrorHandler), - this.serviceContainer.get(IPersistentStateFactory), - this.serviceContainer.get(IMemento, GLOBAL_MEMENTO), - this.serviceContainer.get(IMemento, WORKSPACE_MEMENTO), - this.serviceContainer.get(IExperimentsManager), - this.serviceContainer.get(INotebookProvider), - this.serviceContainer.get(UseCustomEditorApi), - this.serviceContainer.get(ExportUtil), - resource, - mode, - title, - this.serviceContainer.get(KernelSelector) - ); - this._windows.push(result); - - // This is the last interactive window at the moment (as we're about to create it) - this.lastActiveInteractiveWindow = result; - - // When shutting down, we fire an event - const handler = result.closed(this.onInteractiveWindowClosed); - this.disposables.push(result); - this.disposables.push(handler); - this.disposables.push(result.onDidChangeViewState(this.raiseOnDidChangeActiveInteractiveWindow.bind(this))); - - // Show in the background - result.show().ignoreErrors(); - - return result; - } - - private async getInteractiveMode(resource: Resource): Promise { - let result = this.configService.getSettings(resource).datascience.interactiveWindowMode; - - // Ask user if still at default value and they're opening a second file. - if ( - result === 'multiple' && - resource && - !this.globalMemento.get(AskedForPerFileSettingKey) && - this._windows.length === 1 - ) { - // See if the first window was tied to a file or not. - const firstWindow = this._windows.find((w) => w.owner); - if (firstWindow) { - this.globalMemento.update(AskedForPerFileSettingKey, true); - const questions = [ - localize.DataScience.interactiveWindowModeBannerSwitchYes(), - localize.DataScience.interactiveWindowModeBannerSwitchNo() - ]; - // Ask user if they'd like to switch to per file or not. - const response = await this.appShell.showInformationMessage( - localize.DataScience.interactiveWindowModeBannerTitle(), - ...questions - ); - if (response === questions[0]) { - result = 'perFile'; - firstWindow.changeMode(result); - await this.configService.updateSetting( - 'dataScience.interactiveWindowMode', - result, - resource, - ConfigurationTarget.Global - ); - } - } - } - return result; - } - - private get(owner: Resource, interactiveMode: InteractiveWindowMode): IInteractiveWindow | undefined { - // Single mode means there's only ever one. - if (interactiveMode === 'single') { - return this._windows.length > 0 ? this._windows[0] : undefined; - } - - // Multiple means use last active window or create a new one - // if not owned. - if (interactiveMode === 'multiple') { - // Owner being undefined means create a new window, othewise use - // the last active window. - return owner ? this.activeWindow || this.lastActiveInteractiveWindow || this._windows[0] : undefined; - } - - // Otherwise match the owner. - return this._windows.find((w) => { - if (!owner && !w.owner) { - return true; - } - if (owner && w.owner && this.fs.areLocalPathsSame(owner.fsPath, w.owner.fsPath)) { - return true; - } - return false; - }); - } - - private raiseOnDidChangeActiveInteractiveWindow() { - // Update last active window (remember changes to the active window) - this.lastActiveInteractiveWindow = this.activeWindow ? this.activeWindow : this.lastActiveInteractiveWindow; - this._onDidChangeActiveInteractiveWindow.fire(this.activeWindow); - } - private onPeerCountChanged(newCount: number) { - // If we're losing peers, resolve all syncs - if (newCount < this.postOffice.peerCount) { - this.pendingSyncs.forEach((v) => v.waitable.resolve()); - this.pendingSyncs.clear(); - } - } - - // tslint:disable-next-line:no-any - private async onRemoteCreate(...args: any[]) { - // Should be 3 args, the originator of the create, the key, and the owner. Key isn't used here - // but it is passed through to the response. - if (args.length > 1 && args[0].toString() !== this.id) { - // The other side is creating a interactive window. Create on this side. We don't need to show - // it as the running of new code should do that. - const owner = args[2] ? Uri.parse(args[2].toString()) : undefined; - const mode = await this.getInteractiveMode(owner); - if (!this.get(owner, mode)) { - this.create(owner, mode); - } - - // Tell the requestor that we got its message (it should be waiting for all peers to sync) - this.postOffice.postCommand(LiveShareCommands.interactiveWindowCreateSync, ...args).ignoreErrors(); - } - } - - // tslint:disable-next-line:no-any - private onRemoteSync(...args: any[]) { - // Should be 3 args, the originator of the create, the key, and the owner (owner used on other call) - if (args.length > 1 && args[0].toString() === this.id) { - // Update our pending wait count on the matching pending sync - const key = args[1].toString(); - const sync = this.pendingSyncs.get(key); - if (sync) { - sync.count -= 1; - if (sync.count <= 0) { - sync.waitable.resolve(); - this.pendingSyncs.delete(key); - } - } - } - } - - private onInteractiveWindowClosed = (interactiveWindow: IInteractiveWindow) => { - this._windows = this._windows.filter((w) => w !== interactiveWindow); - if (this.lastActiveInteractiveWindow === interactiveWindow) { - this.lastActiveInteractiveWindow = this._windows[0]; - } - this.raiseOnDidChangeActiveInteractiveWindow(); - }; -} diff --git a/src/client/datascience/ipywidgets/cdnWidgetScriptSourceProvider.ts b/src/client/datascience/ipywidgets/cdnWidgetScriptSourceProvider.ts deleted file mode 100644 index 47937d0970e6..000000000000 --- a/src/client/datascience/ipywidgets/cdnWidgetScriptSourceProvider.ts +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { sha256 } from 'hash.js'; -import * as path from 'path'; -import request from 'request'; -import { Uri } from 'vscode'; -import { traceError, traceInfo } from '../../common/logger'; -import { TemporaryFile } from '../../common/platform/types'; -import { IConfigurationService, IHttpClient, WidgetCDNs } from '../../common/types'; -import { createDeferred, sleep } from '../../common/utils/async'; -import { IDataScienceFileSystem, ILocalResourceUriConverter } from '../types'; -import { IWidgetScriptSourceProvider, WidgetScriptSource } from './types'; - -// Source borrowed from https://github.com/jupyter-widgets/ipywidgets/blob/54941b7a4b54036d089652d91b39f937bde6b6cd/packages/html-manager/src/libembed-amd.ts#L33 -const unpgkUrl = 'https://unpkg.com/'; -const jsdelivrUrl = 'https://cdn.jsdelivr.net/npm/'; - -// tslint:disable: no-var-requires no-require-imports -const sanitize = require('sanitize-filename'); - -function moduleNameToCDNUrl(cdn: string, moduleName: string, moduleVersion: string) { - let packageName = moduleName; - let fileName = 'index'; // default filename - // if a '/' is present, like 'foo/bar', packageName is changed to 'foo', and path to 'bar' - // We first find the first '/' - let index = moduleName.indexOf('/'); - if (index !== -1 && moduleName[0] === '@') { - // if we have a namespace, it's a different story - // @foo/bar/baz should translate to @foo/bar and baz - // so we find the 2nd '/' - index = moduleName.indexOf('/', index + 1); - } - if (index !== -1) { - fileName = moduleName.substr(index + 1); - packageName = moduleName.substr(0, index); - } - if (cdn === jsdelivrUrl) { - // Js Delivr doesn't support ^ in the version. It needs an exact version - if (moduleVersion.startsWith('^')) { - moduleVersion = moduleVersion.slice(1); - } - // Js Delivr also needs the .js file on the end. - if (!fileName.endsWith('.js')) { - fileName = fileName.concat('.js'); - } - } - return `${cdn}${packageName}@${moduleVersion}/dist/${fileName}`; -} - -function getCDNPrefix(cdn?: WidgetCDNs): string | undefined { - switch (cdn) { - case 'unpkg.com': - return unpgkUrl; - case 'jsdelivr.com': - return jsdelivrUrl; - default: - break; - } -} -/** - * Widget scripts are found in CDN. - * Given an widget module name & version, this will attempt to find the Url on a CDN. - * We'll need to stick to the order of preference prescribed by the user. - */ -export class CDNWidgetScriptSourceProvider implements IWidgetScriptSourceProvider { - private get cdnProviders(): readonly WidgetCDNs[] { - const settings = this.configurationSettings.getSettings(undefined); - return settings.datascience.widgetScriptSources; - } - private cache = new Map(); - constructor( - private readonly configurationSettings: IConfigurationService, - private readonly httpClient: IHttpClient, - private readonly localResourceUriConverter: ILocalResourceUriConverter, - private readonly fs: IDataScienceFileSystem - ) {} - public dispose() { - this.cache.clear(); - } - public async getWidgetScriptSource(moduleName: string, moduleVersion: string): Promise { - // First see if we already have it downloaded. - const key = this.getModuleKey(moduleName, moduleVersion); - const diskPath = path.join(this.localResourceUriConverter.rootScriptFolder.fsPath, key, 'index.js'); - let cached = this.cache.get(key); - let tempFile: TemporaryFile | undefined; - - // Might be on disk, try there first. - if (!cached) { - if (diskPath && (await this.fs.localFileExists(diskPath))) { - const scriptUri = (await this.localResourceUriConverter.asWebviewUri(Uri.file(diskPath))).toString(); - cached = { moduleName, scriptUri, source: 'cdn' }; - this.cache.set(key, cached); - } - } - - // If still not found, download it. - if (!cached) { - try { - // Make sure the disk path directory exists. We'll be downloading it to there. - await this.fs.createLocalDirectory(path.dirname(diskPath)); - - // Then get the first one that returns. - tempFile = await this.downloadFastestCDN(moduleName, moduleVersion); - if (tempFile) { - // Need to copy from the temporary file to our real file (note: VSC filesystem fails to copy so just use straight file system) - await this.fs.copyLocal(tempFile.filePath, diskPath); - - // Now we can generate the script URI so the local converter doesn't try to copy it. - const scriptUri = ( - await this.localResourceUriConverter.asWebviewUri(Uri.file(diskPath)) - ).toString(); - cached = { moduleName, scriptUri, source: 'cdn' }; - } else { - cached = { moduleName }; - } - } catch (exc) { - traceError('Error downloading from CDN: ', exc); - cached = { moduleName }; - } finally { - if (tempFile) { - tempFile.dispose(); - } - } - this.cache.set(key, cached); - } - - return cached; - } - - private async downloadFastestCDN(moduleName: string, moduleVersion: string) { - const deferred = createDeferred(); - Promise.all( - // For each CDN, try to download it. - this.cdnProviders.map((cdn) => - this.downloadFromCDN(moduleName, moduleVersion, cdn).then((t) => { - // First one to get here wins. Meaning the first one that - // returns a valid temporary file. If a request doesn't download it will - // return undefined. - if (!deferred.resolved && t) { - deferred.resolve(t); - } - }) - ) - ) - .then((_a) => { - // If after running all requests, we're still not resolved, then return empty. - // This would happen if both unpkg.com and jsdelivr failed. - if (!deferred.resolved) { - deferred.resolve(undefined); - } - }) - .ignoreErrors(); - - // Note, we only wait until one download finishes. We don't need to wait - // for everybody (hence the use of the deferred) - return deferred.promise; - } - - private async downloadFromCDN( - moduleName: string, - moduleVersion: string, - cdn: WidgetCDNs - ): Promise { - // First validate CDN - const downloadUrl = await this.generateDownloadUri(moduleName, moduleVersion, cdn); - if (downloadUrl) { - // Then see if we can download the file. - try { - return await this.downloadFile(downloadUrl); - } catch (exc) { - // Something goes wrong, just fail - } - } - } - - private async generateDownloadUri( - moduleName: string, - moduleVersion: string, - cdn: WidgetCDNs - ): Promise { - const cdnBaseUrl = getCDNPrefix(cdn); - if (cdnBaseUrl) { - return moduleNameToCDNUrl(cdnBaseUrl, moduleName, moduleVersion); - } - return undefined; - } - - private getModuleKey(moduleName: string, moduleVersion: string) { - return sanitize(sha256().update(`${moduleName}${moduleVersion}`).digest('hex')); - } - - private handleResponse(req: request.Request, filePath: string): Promise { - const deferred = createDeferred(); - // tslint:disable-next-line: no-any - const errorHandler = (e: any) => { - traceError('Error downloading from CDN', e); - deferred.resolve(false); - }; - req.on('response', (r) => { - if (r.statusCode === 200) { - const ws = this.fs.createLocalWriteStream(filePath); - r.on('error', errorHandler) - .pipe(ws) - .on('close', () => deferred.resolve(true)); - } else if (r.statusCode === 429) { - // Special case busy. Sleep for 500 milliseconds - sleep(500) - .then(() => deferred.resolve(false)) - .ignoreErrors(); - } else { - deferred.resolve(false); - } - }).on('error', errorHandler); - return deferred.promise; - } - - private async downloadFile(downloadUrl: string): Promise { - // Create a temp file to download the results to - const tempFile = await this.fs.createTemporaryLocalFile('.js'); - - // Otherwise do an http get on the url. Retry at least 5 times - let retryCount = 5; - let success = false; - while (retryCount > 0 && !success) { - let req: request.Request; - try { - req = await this.httpClient.downloadFile(downloadUrl); - success = await this.handleResponse(req, tempFile.filePath); - } catch (exc) { - traceInfo(`Error downloading from ${downloadUrl}: `, exc); - } finally { - retryCount -= 1; - } - } - - // Once we make it out, return result - if (success) { - return tempFile; - } else { - tempFile.dispose(); - } - } -} diff --git a/src/client/datascience/ipywidgets/constants.ts b/src/client/datascience/ipywidgets/constants.ts deleted file mode 100644 index a413d7ec7490..000000000000 --- a/src/client/datascience/ipywidgets/constants.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -export const WIDGET_MIMETYPE = 'application/vnd.jupyter.widget-view+json'; diff --git a/src/client/datascience/ipywidgets/ipyWidgetMessageDispatcher.ts b/src/client/datascience/ipywidgets/ipyWidgetMessageDispatcher.ts deleted file mode 100644 index 71d7bbd61d76..000000000000 --- a/src/client/datascience/ipywidgets/ipyWidgetMessageDispatcher.ts +++ /dev/null @@ -1,504 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import type { KernelMessage } from '@jupyterlab/services'; -import * as util from 'util'; -import * as uuid from 'uuid/v4'; -import { Event, EventEmitter, Uri } from 'vscode'; -import type { Data as WebSocketData } from 'ws'; -import { traceError, traceInfo } from '../../common/logger'; -import { IDisposable } from '../../common/types'; -import { createDeferred, Deferred } from '../../common/utils/async'; -import { noop } from '../../common/utils/misc'; -import { deserializeDataViews, serializeDataViews } from '../../common/utils/serializers'; -import { sendTelemetryEvent } from '../../telemetry'; -import { Identifiers, Telemetry } from '../constants'; -import { IInteractiveWindowMapping, IPyWidgetMessages } from '../interactive-common/interactiveWindowTypes'; -import { INotebook, INotebookProvider, KernelSocketInformation } from '../types'; -import { WIDGET_MIMETYPE } from './constants'; -import { IIPyWidgetMessageDispatcher, IPyWidgetMessage } from './types'; - -type PendingMessage = { - resultPromise: Deferred; - startTime: number; -}; - -// tslint:disable: no-any -/** - * This class maps between messages from the react code and talking to a real kernel. - */ -export class IPyWidgetMessageDispatcher implements IIPyWidgetMessageDispatcher { - public get postMessage(): Event { - return this._postMessageEmitter.event; - } - private readonly commTargetsRegistered = new Set(); - private jupyterLab?: typeof import('@jupyterlab/services'); - private pendingTargetNames = new Set(); - private notebook?: INotebook; - private _postMessageEmitter = new EventEmitter(); - private messageHooks = new Map boolean | PromiseLike>(); - private pendingHookRemovals = new Map(); - private messageHookRequests = new Map>(); - - private readonly disposables: IDisposable[] = []; - private kernelRestartHandlerAttached?: boolean; - private kernelSocketInfo?: KernelSocketInformation; - private sentKernelOptions = false; - private kernelWasConnectedAtleastOnce?: boolean; - private disposed = false; - private pendingMessages: string[] = []; - private subscribedToKernelSocket: boolean = false; - private waitingMessageIds = new Map(); - private totalWaitTime: number = 0; - private totalWaitedMessages: number = 0; - private hookCount: number = 0; - private fullHandleMessage?: { id: string; promise: Deferred }; - /** - * This will be true if user has executed something that has resulted in the use of ipywidgets. - * We make this determinination based on whether we see messages coming from backend kernel of a specific shape. - * E.g. if it contains ipywidget mime type, then widgets are in use. - */ - private isUsingIPyWidgets?: boolean; - private readonly deserialize: (data: string | ArrayBuffer) => KernelMessage.IMessage; - - constructor(private readonly notebookProvider: INotebookProvider, public readonly notebookIdentity: Uri) { - // Always register this comm target. - // Possible auto start is disabled, and when cell is executed with widget stuff, this comm target will not have - // been reigstered, in which case kaboom. As we know this is always required, pre-register this. - this.pendingTargetNames.add('jupyter.widget'); - notebookProvider.onNotebookCreated( - (e) => { - if (e.identity.toString() === notebookIdentity.toString()) { - this.initialize().ignoreErrors(); - } - }, - this, - this.disposables - ); - this.mirrorSend = this.mirrorSend.bind(this); - this.onKernelSocketMessage = this.onKernelSocketMessage.bind(this); - // tslint:disable-next-line: no-require-imports - const jupyterLabSerialize = require('@jupyterlab/services/lib/kernel/serialize') as typeof import('@jupyterlab/services/lib/kernel/serialize'); // NOSONAR - this.deserialize = jupyterLabSerialize.deserialize; - } - public dispose() { - // Send overhead telemetry for our message hooking - this.sendOverheadTelemetry(); - this.disposed = true; - while (this.disposables.length) { - const disposable = this.disposables.shift(); - disposable?.dispose(); // NOSONAR - } - } - - public receiveMessage(message: IPyWidgetMessage): void { - if (process.env.VSC_PYTHON_LOG_IPYWIDGETS && message.message.includes('IPyWidgets_')) { - traceInfo(`IPyWidgetMessage: ${util.inspect(message)}`); - } - switch (message.message) { - case IPyWidgetMessages.IPyWidgets_Ready: - this.sendKernelOptions(); - this.initialize().ignoreErrors(); - break; - case IPyWidgetMessages.IPyWidgets_msg: - this.sendRawPayloadToKernelSocket(message.payload); - break; - case IPyWidgetMessages.IPyWidgets_binary_msg: - this.sendRawPayloadToKernelSocket(deserializeDataViews(message.payload)![0]); - break; - - case IPyWidgetMessages.IPyWidgets_msg_received: - this.onKernelSocketResponse(message.payload); - break; - - case IPyWidgetMessages.IPyWidgets_registerCommTarget: - this.registerCommTarget(message.payload).ignoreErrors(); - break; - - case IPyWidgetMessages.IPyWidgets_RegisterMessageHook: - this.registerMessageHook(message.payload); - break; - - case IPyWidgetMessages.IPyWidgets_RemoveMessageHook: - this.possiblyRemoveMessageHook(message.payload); - break; - - case IPyWidgetMessages.IPyWidgets_MessageHookResult: - this.handleMessageHookResponse(message.payload); - break; - - case IPyWidgetMessages.IPyWidgets_iopub_msg_handled: - this.iopubMessageHandled(message.payload); - break; - - default: - break; - } - } - public sendRawPayloadToKernelSocket(payload?: any) { - this.pendingMessages.push(payload); - this.sendPendingMessages(); - } - public async registerCommTarget(targetName: string) { - this.pendingTargetNames.add(targetName); - await this.initialize(); - } - - public async initialize() { - if (!this.jupyterLab) { - // Lazy load jupyter lab for faster extension loading. - // tslint:disable-next-line:no-require-imports - this.jupyterLab = require('@jupyterlab/services') as typeof import('@jupyterlab/services'); // NOSONAR - } - - // If we have any pending targets, register them now - const notebook = await this.getNotebook(); - if (notebook) { - this.subscribeToKernelSocket(notebook); - this.registerCommTargets(notebook); - } - } - protected raisePostMessage( - message: IPyWidgetMessages, - payload: M[T] - ) { - this._postMessageEmitter.fire({ message, payload }); - } - private subscribeToKernelSocket(notebook: INotebook) { - if (this.subscribedToKernelSocket) { - return; - } - this.subscribedToKernelSocket = true; - // Listen to changes to kernel socket (e.g. restarts or changes to kernel). - notebook.kernelSocket.subscribe((info) => { - // Remove old handlers. - this.kernelSocketInfo?.socket?.removeReceiveHook(this.onKernelSocketMessage); // NOSONAR - this.kernelSocketInfo?.socket?.removeSendHook(this.mirrorSend); // NOSONAR - - if (this.kernelWasConnectedAtleastOnce) { - // this means we restarted the kernel and we now have new information. - // Discard all of the messages upto this point. - while (this.pendingMessages.length) { - this.pendingMessages.shift(); - } - this.sentKernelOptions = false; - this.waitingMessageIds.forEach((d) => d.resultPromise.resolve()); - this.waitingMessageIds.clear(); - this.messageHookRequests.forEach((m) => m.resolve(false)); - this.messageHookRequests.clear(); - this.messageHooks.clear(); - this.sendRestartKernel(); - } - if (!info || !info.socket) { - // No kernel socket information, hence nothing much we can do. - this.kernelSocketInfo = undefined; - return; - } - - this.kernelWasConnectedAtleastOnce = true; - this.kernelSocketInfo = info; - this.kernelSocketInfo.socket?.addReceiveHook(this.onKernelSocketMessage); // NOSONAR - this.kernelSocketInfo.socket?.addSendHook(this.mirrorSend); // NOSONAR - this.sendKernelOptions(); - // Since we have connected to a kernel, send any pending messages. - this.registerCommTargets(notebook); - this.sendPendingMessages(); - }); - } - /** - * Pass this information to UI layer so it can create a dummy kernel with same information. - * Information includes kernel connection info (client id, user name, model, etc). - */ - private sendKernelOptions() { - if (!this.kernelSocketInfo) { - return; - } - if (!this.sentKernelOptions) { - this.sentKernelOptions = true; - this.raisePostMessage(IPyWidgetMessages.IPyWidgets_kernelOptions, this.kernelSocketInfo.options); - } - } - private async mirrorSend(data: any, _cb?: (err?: Error) => void): Promise { - // If this is shell control message, mirror to the other side. This is how - // we get the kernel in the UI to have the same set of futures we have on this side - if (typeof data === 'string') { - const startTime = Date.now(); - // tslint:disable-next-line: no-require-imports - const msg = this.deserialize(data); - if (msg.channel === 'shell' && msg.header.msg_type === 'execute_request') { - const promise = this.mirrorExecuteRequest(msg as KernelMessage.IExecuteRequestMsg); // NOSONAR - // If there are no ipywidgets thusfar in the notebook, then no need to synchronize messages. - if (this.isUsingIPyWidgets) { - await promise; - } - this.totalWaitTime = Date.now() - startTime; - this.totalWaitedMessages += 1; - } - } - } - - private sendRestartKernel() { - this.raisePostMessage(IPyWidgetMessages.IPyWidgets_onRestartKernel, undefined); - } - - private mirrorExecuteRequest(msg: KernelMessage.IExecuteRequestMsg) { - const promise = createDeferred(); - this.waitingMessageIds.set(msg.header.msg_id, { startTime: Date.now(), resultPromise: promise }); - this.raisePostMessage(IPyWidgetMessages.IPyWidgets_mirror_execute, { id: msg.header.msg_id, msg }); - return promise.promise; - } - - // Determine if a message can just be added into the message queue or if we need to wait for it to be - // fully handled on both the UI and extension side before we process the next message incoming - private messageNeedsFullHandle(message: any) { - // We only get a handled callback for iopub messages, so this channel must be iopub - if (message.channel === 'iopub') { - if (message.header?.msg_type === 'comm_msg') { - // IOPub comm messages need to be fully handled - return true; - } - } - - return false; - } - - // Callback from the UI kernel when an iopubMessage has been fully handled - private iopubMessageHandled(payload: any) { - const msgId = payload.id; - // We don't fully handle all iopub messages, so check our id here - if (this.fullHandleMessage && this.fullHandleMessage.id === msgId) { - this.fullHandleMessage.promise.resolve(); - this.fullHandleMessage = undefined; - } - } - - private async onKernelSocketMessage(data: WebSocketData): Promise { - // Hooks expect serialized data as this normally comes from a WebSocket - let message; - - if (!this.isUsingIPyWidgets) { - if (!message) { - message = this.deserialize(data as any) as any; - } - - // Check for hints that would indicate whether ipywidgest are used in outputs. - if ( - message.content && - message.content.data && - (message.content.data[WIDGET_MIMETYPE] || message.content.target_name === Identifiers.DefaultCommTarget) - ) { - this.isUsingIPyWidgets = true; - } - } - - const msgUuid = uuid(); - const promise = createDeferred(); - this.waitingMessageIds.set(msgUuid, { startTime: Date.now(), resultPromise: promise }); - - // Check if we need to fully handle this message on UI and Extension side before we move to the next - if (this.isUsingIPyWidgets) { - if (!message) { - message = this.deserialize(data as any) as any; - } - if (this.messageNeedsFullHandle(message)) { - this.fullHandleMessage = { id: message.header.msg_id, promise: createDeferred() }; - } - } - - if (typeof data === 'string') { - this.raisePostMessage(IPyWidgetMessages.IPyWidgets_msg, { id: msgUuid, data }); - } else { - this.raisePostMessage(IPyWidgetMessages.IPyWidgets_binary_msg, { - id: msgUuid, - data: serializeDataViews([data as any]) - }); - } - - // There are three handling states that we have for messages here - // 1. If we have not detected ipywidget usage at all, we just forward messages to the kernel - // 2. If we have detected ipywidget usage. We wait on our message to be received, but not - // possibly processed yet by the UI kernel. This make sure our ordering is in sync - // 3. For iopub comm messages we wait for them to be fully handled by the UI kernel - // and the Extension kernel as they may be required to do things like - // register message hooks on both sides before we process the nextExtension message - - // If there are no ipywidgets thusfar in the notebook, then no need to synchronize messages. - if (this.isUsingIPyWidgets) { - await promise.promise; - - // Comm specific iopub messages we need to wait until they are full handled - // by both the UI and extension side before we move forward - if (this.fullHandleMessage) { - await this.fullHandleMessage.promise.promise; - this.fullHandleMessage = undefined; - } - } - } - private onKernelSocketResponse(payload: { id: string }) { - const pending = this.waitingMessageIds.get(payload.id); - if (pending) { - this.waitingMessageIds.delete(payload.id); - this.totalWaitTime += Date.now() - pending.startTime; - this.totalWaitedMessages += 1; - pending.resultPromise.resolve(); - } - } - private sendPendingMessages() { - if (!this.notebook || !this.kernelSocketInfo) { - return; - } - while (this.pendingMessages.length) { - try { - this.kernelSocketInfo.socket?.sendToRealKernel(this.pendingMessages[0]); // NOSONAR - this.pendingMessages.shift(); - } catch (ex) { - traceError('Failed to send message to Kernel', ex); - return; - } - } - } - - private registerCommTargets(notebook: INotebook) { - while (this.pendingTargetNames.size > 0) { - const targetNames = Array.from([...this.pendingTargetNames.values()]); - const targetName = targetNames.shift(); - if (!targetName) { - continue; - } - if (this.commTargetsRegistered.has(targetName)) { - // Already registered. - return; - } - - traceInfo(`Registering commtarget ${targetName}`); - this.commTargetsRegistered.add(targetName); - this.pendingTargetNames.delete(targetName); - - // Skip the predefined target. It should have been registered - // inside the kernel on startup. However we - // still need to track it here. - if (targetName !== Identifiers.DefaultCommTarget) { - notebook.registerCommTarget(targetName, noop); - } - } - } - - private async getNotebook(): Promise { - if (this.notebookIdentity && !this.notebook) { - this.notebook = await this.notebookProvider.getOrCreateNotebook({ - identity: this.notebookIdentity, - getOnly: true - }); - } - if (this.notebook && !this.kernelRestartHandlerAttached) { - this.kernelRestartHandlerAttached = true; - this.disposables.push(this.notebook.onKernelRestarted(this.handleKernelRestarts, this)); - } - return this.notebook; - } - /** - * When a kernel restarts, we need to ensure the comm targets are re-registered. - * This must happen before anything else is processed. - */ - private async handleKernelRestarts() { - if (this.disposed || this.commTargetsRegistered.size === 0 || !this.notebook) { - return; - } - // Ensure we re-register the comm targets. - Array.from(this.commTargetsRegistered.keys()).forEach((targetName) => { - this.commTargetsRegistered.delete(targetName); - this.pendingTargetNames.add(targetName); - }); - - this.subscribeToKernelSocket(this.notebook); - this.registerCommTargets(this.notebook); - } - - private registerMessageHook(msgId: string) { - try { - if (this.notebook && !this.messageHooks.has(msgId)) { - this.hookCount += 1; - const callback = this.messageHookCallback.bind(this); - this.messageHooks.set(msgId, callback); - this.notebook.registerMessageHook(msgId, callback); - } - } finally { - // Regardless of if we registered successfully or not, send back a message to the UI - // that we are done with extension side handling of this message - this.raisePostMessage(IPyWidgetMessages.IPyWidgets_ExtensionOperationHandled, { - id: msgId, - type: IPyWidgetMessages.IPyWidgets_RegisterMessageHook - }); - } - } - - private possiblyRemoveMessageHook(args: { hookMsgId: string; lastHookedMsgId: string | undefined }) { - // Message hooks might need to be removed after a certain message is processed. - try { - if (args.lastHookedMsgId) { - this.pendingHookRemovals.set(args.lastHookedMsgId, args.hookMsgId); - } else { - this.removeMessageHook(args.hookMsgId); - } - } finally { - // Regardless of if we removed the hook, added to pending removals or just failed, send back a message to the UI - // that we are done with extension side handling of this message - this.raisePostMessage(IPyWidgetMessages.IPyWidgets_ExtensionOperationHandled, { - id: args.hookMsgId, - type: IPyWidgetMessages.IPyWidgets_RemoveMessageHook - }); - } - } - - private removeMessageHook(msgId: string) { - if (this.notebook && this.messageHooks.has(msgId)) { - const callback = this.messageHooks.get(msgId); - this.messageHooks.delete(msgId); - this.notebook.removeMessageHook(msgId, callback!); - } - } - - private async messageHookCallback(msg: KernelMessage.IIOPubMessage): Promise { - const promise = createDeferred(); - const requestId = uuid(); - // tslint:disable-next-line: no-any - const parentId = (msg.parent_header as any).msg_id; - if (this.messageHooks.has(parentId)) { - this.messageHookRequests.set(requestId, promise); - this.raisePostMessage(IPyWidgetMessages.IPyWidgets_MessageHookCall, { requestId, parentId, msg }); - } else { - promise.resolve(true); - } - - // Might have a pending removal. We may have delayed removing a message hook until a message was actually - // processed. - if (this.pendingHookRemovals.has(msg.header.msg_id)) { - const hookId = this.pendingHookRemovals.get(msg.header.msg_id); - this.pendingHookRemovals.delete(msg.header.msg_id); - this.removeMessageHook(hookId!); - } - - return promise.promise; - } - - private handleMessageHookResponse(args: { requestId: string; parentId: string; msgType: string; result: boolean }) { - const promise = this.messageHookRequests.get(args.requestId); - if (promise) { - this.messageHookRequests.delete(args.requestId); - - // During a comm message, make sure all messages come out. - promise.resolve(args.msgType.includes('comm') ? true : args.result); - } - } - - private sendOverheadTelemetry() { - sendTelemetryEvent(Telemetry.IPyWidgetOverhead, 0, { - totalOverheadInMs: this.totalWaitTime, - numberOfMessagesWaitedOn: this.totalWaitedMessages, - averageWaitTime: this.totalWaitTime / this.totalWaitedMessages, - numberOfRegisteredHooks: this.hookCount - }); - } -} diff --git a/src/client/datascience/ipywidgets/ipyWidgetMessageDispatcherFactory.ts b/src/client/datascience/ipywidgets/ipyWidgetMessageDispatcherFactory.ts deleted file mode 100644 index 5519cfad3439..000000000000 --- a/src/client/datascience/ipywidgets/ipyWidgetMessageDispatcherFactory.ts +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { Event, EventEmitter, Uri } from 'vscode'; -import { IDisposable, IDisposableRegistry } from '../../common/types'; -import { INotebook, INotebookProvider } from '../types'; -import { IPyWidgetMessageDispatcher } from './ipyWidgetMessageDispatcher'; -import { IIPyWidgetMessageDispatcher, IPyWidgetMessage } from './types'; - -/** - * This just wraps the iPyWidgetMessageDispatcher class. - * When raising events for arrived messages, this class will first raise events for - * all messages that arrived before this class was contructed. - */ -class IPyWidgetMessageDispatcherWithOldMessages implements IIPyWidgetMessageDispatcher { - public get postMessage(): Event { - return this._postMessageEmitter.event; - } - private _postMessageEmitter = new EventEmitter(); - private readonly disposables: IDisposable[] = []; - constructor( - private readonly baseMulticaster: IPyWidgetMessageDispatcher, - private oldMessages: ReadonlyArray - ) { - baseMulticaster.postMessage(this.raisePostMessage, this, this.disposables); - } - - public dispose() { - while (this.disposables.length) { - const disposable = this.disposables.shift(); - disposable?.dispose(); // NOSONAR - } - } - public async initialize() { - return this.baseMulticaster.initialize(); - } - - public receiveMessage(message: IPyWidgetMessage) { - this.baseMulticaster.receiveMessage(message); - } - private raisePostMessage(message: IPyWidgetMessage) { - // Send all of the old messages the notebook may not have received. - // Also send them in the same order. - this.oldMessages.forEach((oldMessage) => { - this._postMessageEmitter.fire(oldMessage); - }); - this.oldMessages = []; - this._postMessageEmitter.fire(message); - } -} - -/** - * Creates the dispatcher responsible for sending the ipywidget messages to notebooks. - * The way ipywidgets work are as follows: - * - IpyWidget framework registers with kernel (registerCommTarget). - * - IpyWidgets listen to messages from kernel (iopub). - * - IpyWidgets maintain their own state. - * - IpyWidgets build their state slowly based on messages arriving/being sent from iopub. - * - When kernel finally sends a message `display xyz`, ipywidgets looks for data related `xyz` and displays it. - * I.e. by now, ipywidgets has all of the data related to `xyz`. `xyz` is merely an id. - * I.e. kernel merely sends a message saying `ipywidgets please display the UI related to id xyz`. - * The terminoloy used by ipywidgest for the identifier is the `model id`. - * - * Now, if we have another UI opened for the same notebook, e.g. multiple notebooks, we need all of this informaiton. - * I.e. ipywidgets needs all of the information prior to the `display xyz command` form kernel. - * For this to happen, ipywidgets needs to be sent all of the messages from the time it reigstered for a comm target in the original notebook. - * - * Solution: - * - Save all of the messages sent to ipywidgets. - * - When we open a new notebook, then re-send all of these messages to this new ipywidgets manager in the second notebook. - * - Now, both ipywidget managers in both notebooks have the same data, hence are able to render the same controls. - */ -@injectable() -export class IPyWidgetMessageDispatcherFactory implements IDisposable { - private readonly messageDispatchers = new Map(); - private readonly messages: IPyWidgetMessage[] = []; - private disposed = false; - private disposables: IDisposable[] = []; - constructor( - @inject(INotebookProvider) private notebookProvider: INotebookProvider, - @inject(IDisposableRegistry) disposables: IDisposableRegistry - ) { - disposables.push(this); - notebookProvider.onNotebookCreated((e) => this.trackDisposingOfNotebook(e.notebook), this, this.disposables); - - notebookProvider.activeNotebooks.forEach((nbPromise) => - nbPromise.then((notebook) => this.trackDisposingOfNotebook(notebook)).ignoreErrors() - ); - } - - public dispose() { - this.disposed = true; - while (this.disposables.length) { - this.disposables.shift()?.dispose(); // NOSONAR - } - } - public create(identity: Uri): IIPyWidgetMessageDispatcher { - let baseDispatcher = this.messageDispatchers.get(identity.fsPath); - if (!baseDispatcher) { - baseDispatcher = new IPyWidgetMessageDispatcher(this.notebookProvider, identity); - this.messageDispatchers.set(identity.fsPath, baseDispatcher); - - // Capture all messages so we can re-play messages that others missed. - this.disposables.push(baseDispatcher.postMessage(this.onMessage, this)); - } - - // If we have messages upto this point, then capture those messages, - // & pass to the dispatcher so it can re-broadcast those old messages. - // If there are no old messages, even then return a new instance of the class. - // This way, the reference to that will be controlled by calling code. - const dispatcher = new IPyWidgetMessageDispatcherWithOldMessages( - baseDispatcher, - this.messages as ReadonlyArray - ); - this.disposables.push(dispatcher); - return dispatcher; - } - private trackDisposingOfNotebook(notebook: INotebook) { - if (this.disposed) { - return; - } - notebook.onDisposed( - () => { - const item = this.messageDispatchers.get(notebook.identity.fsPath); - this.messageDispatchers.delete(notebook.identity.fsPath); - item?.dispose(); // NOSONAR - }, - this, - this.disposables - ); - } - - private onMessage(_message: IPyWidgetMessage) { - // Disabled for now, as this has the potential to consume a lot of resources (memory). - // One solution - store n messages in array, then use file as storage. - // Next problem, data at rest is not encrypted, now we need to encrypt. - // Till we decide, lets disable this. - //this.messages.push(message); - } -} diff --git a/src/client/datascience/ipywidgets/ipyWidgetScriptSource.ts b/src/client/datascience/ipywidgets/ipyWidgetScriptSource.ts deleted file mode 100644 index e99730484a12..000000000000 --- a/src/client/datascience/ipywidgets/ipyWidgetScriptSource.ts +++ /dev/null @@ -1,310 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; -import type * as jupyterlabService from '@jupyterlab/services'; -import { sha256 } from 'hash.js'; -import { inject, injectable } from 'inversify'; -import { IDisposable } from 'monaco-editor'; -import * as path from 'path'; -import { Event, EventEmitter, Uri } from 'vscode'; -import { IApplicationShell, IWorkspaceService } from '../../common/application/types'; -import { traceError, traceInfo } from '../../common/logger'; - -import { - IConfigurationService, - IDisposableRegistry, - IExtensionContext, - IHttpClient, - IPersistentStateFactory -} from '../../common/types'; -import { createDeferred, Deferred } from '../../common/utils/async'; -import { getOSType, OSType } from '../../common/utils/platform'; -import { IInterpreterService } from '../../interpreter/contracts'; -import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { sendTelemetryEvent } from '../../telemetry'; -import { Telemetry } from '../constants'; -import { - INotebookIdentity, - InteractiveWindowMessages, - IPyWidgetMessages -} from '../interactive-common/interactiveWindowTypes'; -import { - IDataScienceFileSystem, - IInteractiveWindowListener, - ILocalResourceUriConverter, - INotebook, - INotebookProvider -} from '../types'; -import { IPyWidgetScriptSourceProvider } from './ipyWidgetScriptSourceProvider'; -import { WidgetScriptSource } from './types'; -// tslint:disable: no-var-requires no-require-imports -const sanitize = require('sanitize-filename'); - -@injectable() -export class IPyWidgetScriptSource implements IInteractiveWindowListener, ILocalResourceUriConverter { - // tslint:disable-next-line: no-any - public get postMessage(): Event<{ message: string; payload: any }> { - return this.postEmitter.event; - } - // tslint:disable-next-line: no-any - public get postInternalMessage(): Event<{ message: string; payload: any }> { - return this.postInternalMessageEmitter.event; - } - private readonly resourcesMappedToExtensionFolder = new Map>(); - private notebookIdentity?: Uri; - private postEmitter = new EventEmitter<{ - message: string; - // tslint:disable-next-line: no-any - payload: any; - }>(); - private postInternalMessageEmitter = new EventEmitter<{ - message: string; - // tslint:disable-next-line: no-any - payload: any; - }>(); - private notebook?: INotebook; - private jupyterLab?: typeof jupyterlabService; - private scriptProvider?: IPyWidgetScriptSourceProvider; - private disposables: IDisposable[] = []; - private interpreterForWhichWidgetSourcesWereFetched?: PythonEnvironment; - /** - * Key value pair of widget modules along with the version that needs to be loaded. - */ - private pendingModuleRequests = new Map(); - private readonly uriConversionPromises = new Map>(); - private readonly targetWidgetScriptsFolder: string; - private readonly _rootScriptFolder: string; - private readonly createTargetWidgetScriptsFolder: Promise; - constructor( - @inject(IDisposableRegistry) disposables: IDisposableRegistry, - @inject(INotebookProvider) private readonly notebookProvider: INotebookProvider, - @inject(IDataScienceFileSystem) private readonly fs: IDataScienceFileSystem, - @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, - @inject(IConfigurationService) private readonly configurationSettings: IConfigurationService, - @inject(IHttpClient) private readonly httpClient: IHttpClient, - @inject(IApplicationShell) private readonly appShell: IApplicationShell, - @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, - @inject(IPersistentStateFactory) private readonly stateFactory: IPersistentStateFactory, - @inject(IExtensionContext) extensionContext: IExtensionContext - ) { - this._rootScriptFolder = path.join(extensionContext.extensionPath, 'tmp', 'scripts'); - this.targetWidgetScriptsFolder = path.join(this._rootScriptFolder, 'nbextensions'); - this.createTargetWidgetScriptsFolder = this.fs - .localDirectoryExists(this.targetWidgetScriptsFolder) - .then(async (exists) => { - if (!exists) { - await this.fs.createLocalDirectory(this.targetWidgetScriptsFolder); - } - return this.targetWidgetScriptsFolder; - }); - disposables.push(this); - this.notebookProvider.onNotebookCreated( - (e) => { - if (e.identity.toString() === this.notebookIdentity?.toString()) { - this.initialize().catch(traceError.bind('Failed to initialize')); - } - }, - this, - this.disposables - ); - } - /** - * This method is called to convert a Uri to a format such that it can be used in a webview. - * WebViews only allow files that are part of extension and the same directory where notebook lives. - * To ensure widgets can find the js files, we copy the script file to a into the extensionr folder `tmp/nbextensions`. - * (storing files in `tmp/nbextensions` is relatively safe as this folder gets deleted when ever a user updates to a new version of VSC). - * Hence we need to copy for every version of the extension. - * Copying into global workspace folder would also work, but over time this folder size could grow (in an unmanaged way). - */ - public async asWebviewUri(localResource: Uri): Promise { - // Make a copy of the local file if not already in the correct location - if (!this.isInScriptPath(localResource.fsPath)) { - if (this.notebookIdentity && !this.resourcesMappedToExtensionFolder.has(localResource.fsPath)) { - const deferred = createDeferred(); - this.resourcesMappedToExtensionFolder.set(localResource.fsPath, deferred.promise); - try { - // Create a file name such that it will be unique and consistent across VSC reloads. - // Only if original file has been modified should we create a new copy of the sam file. - const fileHash: string = await this.fs.getFileHash(localResource.fsPath); - const uniqueFileName = sanitize( - sha256().update(`${localResource.fsPath}${fileHash}`).digest('hex') - ); - const targetFolder = await this.createTargetWidgetScriptsFolder; - const mappedResource = Uri.file( - path.join(targetFolder, `${uniqueFileName}${path.basename(localResource.fsPath)}`) - ); - if (!(await this.fs.localFileExists(mappedResource.fsPath))) { - await this.fs.copyLocal(localResource.fsPath, mappedResource.fsPath); - } - traceInfo(`Widget Script file ${localResource.fsPath} mapped to ${mappedResource.fsPath}`); - deferred.resolve(mappedResource); - } catch (ex) { - traceError(`Failed to map widget Script file ${localResource.fsPath}`); - deferred.reject(ex); - } - } - localResource = await this.resourcesMappedToExtensionFolder.get(localResource.fsPath)!; - } - const key = localResource.toString(); - if (!this.uriConversionPromises.has(key)) { - this.uriConversionPromises.set(key, createDeferred()); - // Send a request for the translation. - this.postInternalMessageEmitter.fire({ - message: InteractiveWindowMessages.ConvertUriForUseInWebViewRequest, - payload: localResource - }); - } - return this.uriConversionPromises.get(key)!.promise; - } - - public get rootScriptFolder(): Uri { - return Uri.file(this._rootScriptFolder); - } - - public dispose() { - while (this.disposables.length) { - this.disposables.shift()?.dispose(); // NOSONAR - } - } - - // tslint:disable-next-line: no-any - public onMessage(message: string, payload?: any): void { - if (message === InteractiveWindowMessages.NotebookIdentity) { - this.saveIdentity(payload).catch((ex) => - traceError(`Failed to initialize ${(this as Object).constructor.name}`, ex) - ); - } else if (message === InteractiveWindowMessages.NotebookClose) { - this.dispose(); - } else if (message === InteractiveWindowMessages.ConvertUriForUseInWebViewResponse) { - const response: undefined | { request: Uri; response: Uri } = payload; - if (response && this.uriConversionPromises.get(response.request.toString())) { - this.uriConversionPromises.get(response.request.toString())!.resolve(response.response); - } - } else if (message === IPyWidgetMessages.IPyWidgets_WidgetScriptSourceRequest) { - if (payload) { - const { moduleName, moduleVersion } = payload as { moduleName: string; moduleVersion: string }; - this.sendWidgetSource(moduleName, moduleVersion).catch( - traceError.bind('Failed to send widget sources upon ready') - ); - } - } - } - - /** - * Send the widget script source for a specific widget module & version. - * This is a request made when a widget is certainly used in a notebook. - */ - private async sendWidgetSource(moduleName?: string, moduleVersion: string = '*') { - // Standard widgets area already available, hence no need to look for them. - if (!moduleName || moduleName.startsWith('@jupyter')) { - return; - } - if (!this.notebook || !this.scriptProvider) { - this.pendingModuleRequests.set(moduleName, moduleVersion); - return; - } - - let widgetSource: WidgetScriptSource = { moduleName }; - try { - widgetSource = await this.scriptProvider.getWidgetScriptSource(moduleName, moduleVersion); - } catch (ex) { - traceError('Failed to get widget source due to an error', ex); - sendTelemetryEvent(Telemetry.HashedIPyWidgetScriptDiscoveryError); - } finally { - // Send to UI (even if there's an error) continues instead of hanging while waiting for a response. - this.postEmitter.fire({ - message: IPyWidgetMessages.IPyWidgets_WidgetScriptSourceResponse, - payload: widgetSource - }); - } - } - private async saveIdentity(args: INotebookIdentity) { - this.notebookIdentity = args.resource; - await this.initialize(); - } - - private async initialize() { - if (!this.jupyterLab) { - // Lazy load jupyter lab for faster extension loading. - // tslint:disable-next-line:no-require-imports - this.jupyterLab = require('@jupyterlab/services') as typeof jupyterlabService; // NOSONAR - } - - if (!this.notebookIdentity) { - return; - } - if (!this.notebook) { - this.notebook = await this.notebookProvider.getOrCreateNotebook({ - identity: this.notebookIdentity, - disableUI: true, - getOnly: true - }); - } - if (!this.notebook) { - return; - } - if (this.scriptProvider) { - return; - } - this.scriptProvider = new IPyWidgetScriptSourceProvider( - this.notebook, - this, - this.fs, - this.interpreterService, - this.appShell, - this.configurationSettings, - this.workspaceService, - this.stateFactory, - this.httpClient - ); - await this.initializeNotebook(); - } - private async initializeNotebook() { - if (!this.notebook) { - return; - } - this.notebook.onDisposed(() => this.dispose()); - // When changing a kernel, we might have a new interpreter. - this.notebook.onKernelChanged( - () => { - // If underlying interpreter has changed, then refresh list of widget sources. - // After all, different kernels have different widgets. - if ( - this.notebook?.getMatchingInterpreter() && - this.notebook?.getMatchingInterpreter() === this.interpreterForWhichWidgetSourcesWereFetched - ) { - return; - } - // Let UI know that kernel has changed. - this.postEmitter.fire({ message: IPyWidgetMessages.IPyWidgets_onKernelChanged, payload: undefined }); - }, - this, - this.disposables - ); - this.handlePendingRequests(); - } - private handlePendingRequests() { - const pendingModuleNames = Array.from(this.pendingModuleRequests.keys()); - while (pendingModuleNames.length) { - const moduleName = pendingModuleNames.shift(); - if (moduleName) { - const moduleVersion = this.pendingModuleRequests.get(moduleName)!; - this.pendingModuleRequests.delete(moduleName); - this.sendWidgetSource(moduleName, moduleVersion).catch( - traceError.bind(`Failed to send WidgetScript for ${moduleName}`) - ); - } - } - } - - private isInScriptPath(filePath: string) { - const scriptPath = path.normalize(this._rootScriptFolder); - filePath = path.normalize(filePath); - if (getOSType() === OSType.Windows) { - return filePath.toUpperCase().startsWith(scriptPath.toUpperCase()); - } else { - return filePath.startsWith(scriptPath); - } - } -} diff --git a/src/client/datascience/ipywidgets/ipyWidgetScriptSourceProvider.ts b/src/client/datascience/ipywidgets/ipyWidgetScriptSourceProvider.ts deleted file mode 100644 index 13b6c3fb8871..000000000000 --- a/src/client/datascience/ipywidgets/ipyWidgetScriptSourceProvider.ts +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { sha256 } from 'hash.js'; -import { ConfigurationChangeEvent, ConfigurationTarget } from 'vscode'; -import { IApplicationShell, IWorkspaceService } from '../../common/application/types'; -import '../../common/extensions'; -import { traceError } from '../../common/logger'; - -import { - IConfigurationService, - IHttpClient, - IPersistentState, - IPersistentStateFactory, - WidgetCDNs -} from '../../common/types'; -import { createDeferred, Deferred } from '../../common/utils/async'; -import { Common, DataScience } from '../../common/utils/localize'; -import { noop } from '../../common/utils/misc'; -import { IInterpreterService } from '../../interpreter/contracts'; -import { sendTelemetryEvent } from '../../telemetry'; -import { Telemetry } from '../constants'; -import { IDataScienceFileSystem, ILocalResourceUriConverter, INotebook } from '../types'; -import { CDNWidgetScriptSourceProvider } from './cdnWidgetScriptSourceProvider'; -import { LocalWidgetScriptSourceProvider } from './localWidgetScriptSourceProvider'; -import { RemoteWidgetScriptSourceProvider } from './remoteWidgetScriptSourceProvider'; -import { IWidgetScriptSourceProvider, WidgetScriptSource } from './types'; - -const GlobalStateKeyToTrackIfUserConfiguredCDNAtLeastOnce = 'IPYWidgetCDNConfigured'; -const GlobalStateKeyToNeverWarnAboutScriptsNotFoundOnCDN = 'IPYWidgetNotFoundOnCDN'; - -/** - * This class decides where to get widget scripts from. - * Whether its cdn or local or other, and also controls the order/priority. - * If user changes the order, this will react to those configuration setting changes. - * If user has not configured antying, user will be presented with a prompt. - */ -export class IPyWidgetScriptSourceProvider implements IWidgetScriptSourceProvider { - private readonly notifiedUserAboutWidgetScriptNotFound = new Set(); - private scriptProviders?: IWidgetScriptSourceProvider[]; - private configurationPromise?: Deferred; - private get configuredScriptSources(): readonly WidgetCDNs[] { - const settings = this.configurationSettings.getSettings(undefined); - return settings.datascience.widgetScriptSources; - } - private readonly userConfiguredCDNAtLeastOnce: IPersistentState; - private readonly neverWarnAboutScriptsNotFoundOnCDN: IPersistentState; - constructor( - private readonly notebook: INotebook, - private readonly localResourceUriConverter: ILocalResourceUriConverter, - private readonly fs: IDataScienceFileSystem, - private readonly interpreterService: IInterpreterService, - private readonly appShell: IApplicationShell, - private readonly configurationSettings: IConfigurationService, - private readonly workspaceService: IWorkspaceService, - private readonly stateFactory: IPersistentStateFactory, - private readonly httpClient: IHttpClient - ) { - this.userConfiguredCDNAtLeastOnce = this.stateFactory.createGlobalPersistentState( - GlobalStateKeyToTrackIfUserConfiguredCDNAtLeastOnce, - false - ); - this.neverWarnAboutScriptsNotFoundOnCDN = this.stateFactory.createGlobalPersistentState( - GlobalStateKeyToNeverWarnAboutScriptsNotFoundOnCDN, - false - ); - } - public initialize() { - this.workspaceService.onDidChangeConfiguration(this.onSettingsChagned.bind(this)); - } - public dispose() { - this.disposeScriptProviders(); - } - /** - * We know widgets are being used, at this point prompt user if required. - */ - public async getWidgetScriptSource( - moduleName: string, - moduleVersion: string - ): Promise> { - await this.configureWidgets(); - if (!this.scriptProviders) { - this.rebuildProviders(); - } - - // Get script sources in order, if one works, then get out. - const scriptSourceProviders = (this.scriptProviders || []).slice(); - let found: WidgetScriptSource = { moduleName }; - while (scriptSourceProviders.length) { - const scriptProvider = scriptSourceProviders.shift(); - if (!scriptProvider) { - continue; - } - const source = await scriptProvider.getWidgetScriptSource(moduleName, moduleVersion); - // If we found the script source, then use that. - if (source.scriptUri) { - found = source; - break; - } - } - - sendTelemetryEvent(Telemetry.HashedIPyWidgetNameUsed, undefined, { - hashedName: sha256().update(found.moduleName).digest('hex'), - source: found.source, - cdnSearched: this.configuredScriptSources.length > 0 - }); - - if (!found.scriptUri) { - traceError(`Script source for Widget ${moduleName}@${moduleVersion} not found`); - } - this.handleWidgetSourceNotFoundOnCDN(found).ignoreErrors(); - return found; - } - private async handleWidgetSourceNotFoundOnCDN(widgetSource: WidgetScriptSource) { - // if widget exists nothing to do. - if (widgetSource.source === 'cdn' || this.neverWarnAboutScriptsNotFoundOnCDN.value === true) { - return; - } - if ( - this.notifiedUserAboutWidgetScriptNotFound.has(widgetSource.moduleName) || - this.configuredScriptSources.length === 0 - ) { - return; - } - this.notifiedUserAboutWidgetScriptNotFound.add(widgetSource.moduleName); - const selection = await this.appShell.showWarningMessage( - DataScience.widgetScriptNotFoundOnCDNWidgetMightNotWork().format(widgetSource.moduleName), - Common.ok(), - Common.doNotShowAgain(), - Common.reportThisIssue() - ); - switch (selection) { - case Common.doNotShowAgain(): - return this.neverWarnAboutScriptsNotFoundOnCDN.updateValue(true); - case Common.reportThisIssue(): - return this.appShell.openUrl('https://aka.ms/CreatePVSCDataScienceIssue'); - default: - noop(); - } - } - - private onSettingsChagned(e: ConfigurationChangeEvent) { - if (e.affectsConfiguration('python.dataScience.widgetScriptSources')) { - this.rebuildProviders(); - } - } - private disposeScriptProviders() { - while (this.scriptProviders && this.scriptProviders.length) { - const item = this.scriptProviders.shift(); - if (item) { - item.dispose(); - } - } - } - private rebuildProviders() { - this.disposeScriptProviders(); - - const scriptProviders: IWidgetScriptSourceProvider[] = []; - - // If we're allowed to use CDN providers, then use them, and use in order of preference. - if (this.configuredScriptSources.length > 0) { - scriptProviders.push( - new CDNWidgetScriptSourceProvider( - this.configurationSettings, - this.httpClient, - this.localResourceUriConverter, - this.fs - ) - ); - } - if (this.notebook.connection && this.notebook.connection.localLaunch) { - scriptProviders.push( - new LocalWidgetScriptSourceProvider( - this.notebook, - this.localResourceUriConverter, - this.fs, - this.interpreterService - ) - ); - } else { - if (this.notebook.connection) { - scriptProviders.push(new RemoteWidgetScriptSourceProvider(this.notebook.connection, this.httpClient)); - } - } - - this.scriptProviders = scriptProviders; - } - - private async configureWidgets(): Promise { - if (this.configuredScriptSources.length !== 0) { - return; - } - - if (this.userConfiguredCDNAtLeastOnce.value) { - return; - } - - if (this.configurationPromise) { - return this.configurationPromise.promise; - } - this.configurationPromise = createDeferred(); - sendTelemetryEvent(Telemetry.IPyWidgetPromptToUseCDN); - const selection = await this.appShell.showInformationMessage( - DataScience.useCDNForWidgets(), - Common.ok(), - Common.cancel(), - Common.doNotShowAgain() - ); - - let selectionForTelemetry: 'ok' | 'cancel' | 'dismissed' | 'doNotShowAgain' = 'dismissed'; - switch (selection) { - case Common.ok(): { - selectionForTelemetry = 'ok'; - // always search local interpreter or attempt to fetch scripts from remote jupyter server as backups. - await Promise.all([ - this.updateScriptSources(['jsdelivr.com', 'unpkg.com']), - this.userConfiguredCDNAtLeastOnce.updateValue(true) - ]); - break; - } - case Common.doNotShowAgain(): { - selectionForTelemetry = 'doNotShowAgain'; - // At a minimum search local interpreter or attempt to fetch scripts from remote jupyter server. - await Promise.all([this.updateScriptSources([]), this.userConfiguredCDNAtLeastOnce.updateValue(true)]); - break; - } - default: - selectionForTelemetry = selection === Common.cancel() ? 'cancel' : 'dismissed'; - break; - } - - sendTelemetryEvent(Telemetry.IPyWidgetPromptToUseCDNSelection, undefined, { selection: selectionForTelemetry }); - this.configurationPromise.resolve(); - } - private async updateScriptSources(scriptSources: WidgetCDNs[]) { - const targetSetting = 'dataScience.widgetScriptSources'; - await this.configurationSettings.updateSetting( - targetSetting, - scriptSources, - undefined, - ConfigurationTarget.Global - ); - } -} diff --git a/src/client/datascience/ipywidgets/ipywidgetHandler.ts b/src/client/datascience/ipywidgets/ipywidgetHandler.ts deleted file mode 100644 index c2f569070d80..000000000000 --- a/src/client/datascience/ipywidgets/ipywidgetHandler.ts +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import type { KernelMessage } from '@jupyterlab/services'; -import { inject, injectable, named } from 'inversify'; -import stripAnsi from 'strip-ansi'; -import { Event, EventEmitter, Uri } from 'vscode'; -import { - ILoadIPyWidgetClassFailureAction, - LoadIPyWidgetClassLoadAction, - NotifyIPyWidgeWidgetVersionNotSupportedAction -} from '../../../datascience-ui/interactive-common/redux/reducers/types'; -import { EnableIPyWidgets } from '../../common/experiments/groups'; -import { traceError, traceInfo } from '../../common/logger'; -import { IDisposableRegistry, IExperimentsManager, IOutputChannel } from '../../common/types'; -import * as localize from '../../common/utils/localize'; -import { sendTelemetryEvent } from '../../telemetry'; -import { JUPYTER_OUTPUT_CHANNEL, Telemetry } from '../constants'; -import { INotebookIdentity, InteractiveWindowMessages } from '../interactive-common/interactiveWindowTypes'; -import { IInteractiveWindowListener, INotebookProvider } from '../types'; -import { IPyWidgetMessageDispatcherFactory } from './ipyWidgetMessageDispatcherFactory'; -import { IIPyWidgetMessageDispatcher } from './types'; - -/** - * This class handles all of the ipywidgets communication with the notebook - */ -@injectable() -// -export class IPyWidgetHandler implements IInteractiveWindowListener { - // tslint:disable-next-line: no-any - public get postMessage(): Event<{ message: string; payload: any }> { - return this.postEmitter.event; - } - private ipyWidgetMessageDispatcher?: IIPyWidgetMessageDispatcher; - private notebookIdentity: Uri | undefined; - // tslint:disable-next-line: no-any - private postEmitter: EventEmitter<{ message: string; payload: any }> = new EventEmitter<{ - message: string; - // tslint:disable-next-line: no-any - payload: any; - }>(); - // tslint:disable-next-line: no-require-imports - private hashFn = require('hash.js').sha256; - private enabled = false; - - constructor( - @inject(INotebookProvider) notebookProvider: INotebookProvider, - @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry, - @inject(IPyWidgetMessageDispatcherFactory) - private readonly widgetMessageDispatcherFactory: IPyWidgetMessageDispatcherFactory, - @inject(IExperimentsManager) readonly experimentsManager: IExperimentsManager, - @inject(IOutputChannel) @named(JUPYTER_OUTPUT_CHANNEL) private jupyterOutput: IOutputChannel - ) { - disposables.push( - notebookProvider.onNotebookCreated(async (e) => { - if (e.identity.toString() === this.notebookIdentity?.toString()) { - await this.initialize(); - } - }) - ); - - this.enabled = experimentsManager.inExperiment(EnableIPyWidgets.experiment); - } - - public dispose() { - this.ipyWidgetMessageDispatcher?.dispose(); // NOSONAR - } - - // tslint:disable-next-line: no-any - public onMessage(message: string, payload?: any): void { - if (message === InteractiveWindowMessages.NotebookIdentity) { - this.saveIdentity(payload).catch((ex) => traceError('Failed to initialize ipywidgetHandler', ex)); - } else if (message === InteractiveWindowMessages.IPyWidgetLoadSuccess) { - this.sendLoadSucceededTelemetry(payload); - } else if (message === InteractiveWindowMessages.IPyWidgetLoadFailure) { - this.sendLoadFailureTelemetry(payload); - } else if (message === InteractiveWindowMessages.IPyWidgetWidgetVersionNotSupported) { - this.sendUnsupportedWidgetVersionFailureTelemetry(payload); - } else if (message === InteractiveWindowMessages.IPyWidgetRenderFailure) { - this.sendRenderFailureTelemetry(payload); - } else if (message === InteractiveWindowMessages.IPyWidgetUnhandledKernelMessage) { - this.handleUnhandledMessage(payload); - } - // tslint:disable-next-line: no-any - this.getIPyWidgetMessageDispatcher()?.receiveMessage({ message: message as any, payload }); // NOSONAR - } - - private hash(s: string): string { - return this.hashFn().update(s).digest('hex'); - } - - private sendLoadSucceededTelemetry(payload: LoadIPyWidgetClassLoadAction) { - try { - sendTelemetryEvent(Telemetry.IPyWidgetLoadSuccess, 0, { - moduleHash: this.hash(payload.moduleName), - moduleVersion: payload.moduleVersion - }); - } catch { - // do nothing on failure - } - } - - private sendLoadFailureTelemetry(payload: ILoadIPyWidgetClassFailureAction) { - try { - sendTelemetryEvent(Telemetry.IPyWidgetLoadFailure, 0, { - isOnline: payload.isOnline, - moduleHash: this.hash(payload.moduleName), - moduleVersion: payload.moduleVersion, - timedout: payload.timedout - }); - } catch { - // do nothing on failure - } - } - private sendUnsupportedWidgetVersionFailureTelemetry(payload: NotifyIPyWidgeWidgetVersionNotSupportedAction) { - try { - sendTelemetryEvent(Telemetry.IPyWidgetWidgetVersionNotSupportedLoadFailure, 0, { - moduleHash: this.hash(payload.moduleName), - moduleVersion: payload.moduleVersion - }); - } catch { - // do nothing on failure - } - } - private sendRenderFailureTelemetry(payload: Error) { - try { - traceError('Error rendering a widget: ', payload); - sendTelemetryEvent(Telemetry.IPyWidgetRenderFailure); - } catch { - // Do nothing on a failure - } - } - - private handleUnhandledMessage(msg: KernelMessage.IMessage) { - // Skip status messages - if (msg.header.msg_type !== 'status') { - try { - // Special case errors, strip ansi codes from tracebacks so they print better. - if (msg.header.msg_type === 'error') { - const errorMsg = msg as KernelMessage.IErrorMsg; - errorMsg.content.traceback = errorMsg.content.traceback.map(stripAnsi); - } - traceInfo(`Unhandled widget kernel message: ${msg.header.msg_type} ${msg.content}`); - this.jupyterOutput.appendLine( - localize.DataScience.unhandledMessage().format(msg.header.msg_type, JSON.stringify(msg.content)) - ); - sendTelemetryEvent(Telemetry.IPyWidgetUnhandledMessage, undefined, { msg_type: msg.header.msg_type }); - } catch { - // Don't care if this doesn't get logged - } - } - } - private getIPyWidgetMessageDispatcher() { - if (!this.notebookIdentity || !this.enabled) { - return; - } - if (!this.ipyWidgetMessageDispatcher) { - this.ipyWidgetMessageDispatcher = this.widgetMessageDispatcherFactory.create(this.notebookIdentity); - } - return this.ipyWidgetMessageDispatcher; - } - - private async saveIdentity(args: INotebookIdentity) { - this.notebookIdentity = args.resource; - - const dispatcher = this.getIPyWidgetMessageDispatcher(); - if (dispatcher) { - this.disposables.push(dispatcher.postMessage((msg) => this.postEmitter.fire(msg))); - } - - await this.initialize(); - } - - private async initialize() { - if (!this.notebookIdentity) { - return; - } - const dispatcher = this.getIPyWidgetMessageDispatcher(); - if (dispatcher) { - await dispatcher.initialize(); - } - } -} diff --git a/src/client/datascience/ipywidgets/localWidgetScriptSourceProvider.ts b/src/client/datascience/ipywidgets/localWidgetScriptSourceProvider.ts deleted file mode 100644 index 82dd1714835e..000000000000 --- a/src/client/datascience/ipywidgets/localWidgetScriptSourceProvider.ts +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as path from 'path'; -import { Uri } from 'vscode'; -import { traceError } from '../../common/logger'; - -import { IInterpreterService } from '../../interpreter/contracts'; -import { captureTelemetry } from '../../telemetry'; -import { Telemetry } from '../constants'; -import { - getInterpreterFromKernelConnectionMetadata, - getKernelPathFromKernelConnection, - isPythonKernelConnection -} from '../jupyter/kernels/helpers'; -import { IDataScienceFileSystem, ILocalResourceUriConverter, INotebook } from '../types'; -import { IWidgetScriptSourceProvider, WidgetScriptSource } from './types'; - -/** - * Widget scripts are found in /share/jupyter/nbextensions. - * Here's an example: - * /share/jupyter/nbextensions/k3d/index.js - * /share/jupyter/nbextensions/nglview/index.js - * /share/jupyter/nbextensions/bqplot/index.js - */ -export class LocalWidgetScriptSourceProvider implements IWidgetScriptSourceProvider { - private cachedWidgetScripts?: Promise; - constructor( - private readonly notebook: INotebook, - private readonly localResourceUriConverter: ILocalResourceUriConverter, - private readonly fs: IDataScienceFileSystem, - private readonly interpreterService: IInterpreterService - ) {} - public async getWidgetScriptSource(moduleName: string): Promise> { - const sources = await this.getWidgetScriptSources(); - const found = sources.find((item) => item.moduleName.toLowerCase() === moduleName.toLowerCase()); - return found || { moduleName }; - } - public dispose() { - // Noop. - } - public async getWidgetScriptSources(ignoreCache?: boolean): Promise> { - if (!ignoreCache && this.cachedWidgetScripts) { - return this.cachedWidgetScripts; - } - return (this.cachedWidgetScripts = this.getWidgetScriptSourcesWithoutCache()); - } - @captureTelemetry(Telemetry.DiscoverIPyWidgetNamesLocalPerf) - private async getWidgetScriptSourcesWithoutCache(): Promise { - const sysPrefix = await this.getSysPrefixOfKernel(); - if (!sysPrefix) { - return []; - } - - const nbextensionsPath = path.join(sysPrefix, 'share', 'jupyter', 'nbextensions'); - // Search only one level deep, hence `*/index.js`. - const files = await this.fs.searchLocal(`*${path.sep}index.js`, nbextensionsPath); - - const validFiles = files.filter((file) => { - // Should be of the form `/index.js` - const parts = file.split('/'); // On windows this uses the unix separator too. - if (parts.length !== 2) { - traceError('Incorrect file found when searching for nnbextension entrypoints'); - return false; - } - return true; - }); - - const mappedFiles = validFiles.map(async (file) => { - // Should be of the form `/index.js` - const parts = file.split('/'); - const moduleName = parts[0]; - - const fileUri = Uri.file(path.join(nbextensionsPath, file)); - const scriptUri = (await this.localResourceUriConverter.asWebviewUri(fileUri)).toString(); - // tslint:disable-next-line: no-unnecessary-local-variable - const widgetScriptSource: WidgetScriptSource = { moduleName, scriptUri, source: 'local' }; - return widgetScriptSource; - }); - // tslint:disable-next-line: no-any - return Promise.all(mappedFiles as any); - } - private async getSysPrefixOfKernel() { - const kernelConnectionMetadata = this.notebook.getKernelConnection(); - if (!kernelConnectionMetadata) { - return; - } - const interpreter = getInterpreterFromKernelConnectionMetadata(kernelConnectionMetadata); - if (interpreter?.sysPrefix) { - return interpreter?.sysPrefix; - } - if (!isPythonKernelConnection(kernelConnectionMetadata)) { - return; - } - const interpreterOrKernelPath = - interpreter?.path || getKernelPathFromKernelConnection(kernelConnectionMetadata); - if (!interpreterOrKernelPath) { - return; - } - const interpreterInfo = await this.interpreterService - .getInterpreterDetails(interpreterOrKernelPath) - .catch( - traceError.bind(`Failed to get interpreter details for Kernel/Interpreter ${interpreterOrKernelPath}`) - ); - - if (interpreterInfo) { - return interpreterInfo?.sysPrefix; - } - } -} diff --git a/src/client/datascience/ipywidgets/remoteWidgetScriptSourceProvider.ts b/src/client/datascience/ipywidgets/remoteWidgetScriptSourceProvider.ts deleted file mode 100644 index 194c9ee00b67..000000000000 --- a/src/client/datascience/ipywidgets/remoteWidgetScriptSourceProvider.ts +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { traceWarning } from '../../common/logger'; -import { IHttpClient } from '../../common/types'; -import { IJupyterConnection } from '../types'; -import { IWidgetScriptSourceProvider, WidgetScriptSource } from './types'; - -/** - * When using a remote jupyter connection the widget scripts are accessible over - * `/nbextensions/moduleName/index` - */ -export class RemoteWidgetScriptSourceProvider implements IWidgetScriptSourceProvider { - public static validUrls = new Map(); - constructor(private readonly connection: IJupyterConnection, private readonly httpClient: IHttpClient) {} - public dispose() { - // Noop. - } - public async getWidgetScriptSource(moduleName: string, moduleVersion: string): Promise { - const scriptUri = `${this.connection.baseUrl}nbextensions/${moduleName}/index.js`; - const exists = await this.getUrlForWidget(scriptUri); - if (exists) { - return { moduleName, scriptUri, source: 'cdn' }; - } - traceWarning(`Widget Script not found for ${moduleName}@${moduleVersion}`); - return { moduleName }; - } - private async getUrlForWidget(url: string): Promise { - if (RemoteWidgetScriptSourceProvider.validUrls.has(url)) { - return RemoteWidgetScriptSourceProvider.validUrls.get(url)!; - } - - const exists = await this.httpClient.exists(url); - RemoteWidgetScriptSourceProvider.validUrls.set(url, exists); - return exists; - } -} diff --git a/src/client/datascience/ipywidgets/types.ts b/src/client/datascience/ipywidgets/types.ts deleted file mode 100644 index e09243ffa1fa..000000000000 --- a/src/client/datascience/ipywidgets/types.ts +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { Event } from 'vscode'; -import { IDisposable } from '../../common/types'; -import { IPyWidgetMessages } from '../interactive-common/interactiveWindowTypes'; - -export interface IPyWidgetMessage { - message: IPyWidgetMessages; - // tslint:disable-next-line: no-any - payload: any; -} - -/** - * Used to send/receive messages related to IPyWidgets - */ -export interface IIPyWidgetMessageDispatcher extends IDisposable { - // tslint:disable-next-line: no-any - postMessage: Event; - // tslint:disable-next-line: no-any - receiveMessage(message: IPyWidgetMessage): void; - initialize(): Promise; -} - -/** - * Name value pair of widget name/module along with the Uri to the script. - */ -export type WidgetScriptSource = { - moduleName: string; - /** - * Where is the script being source from. - */ - source?: 'cdn' | 'local' | 'remote'; - /** - * Resource Uri (not using Uri type as this needs to be sent from extension to UI). - */ - scriptUri?: string; -}; - -/** - * Used to get an entry for widget (or all of them). - */ -export interface IWidgetScriptSourceProvider extends IDisposable { - /** - * Return the script path for the requested module. - * This is called when ipywidgets needs a source for a particular widget. - */ - getWidgetScriptSource(moduleName: string, moduleVersion: string): Promise>; -} diff --git a/src/client/datascience/jupyter/commandLineSelector.ts b/src/client/datascience/jupyter/commandLineSelector.ts deleted file mode 100644 index 9b8c8dfc1b8b..000000000000 --- a/src/client/datascience/jupyter/commandLineSelector.ts +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -// tslint:disable-next-line: import-name -import parseArgsStringToArgv from 'string-argv'; -import { ConfigurationChangeEvent, ConfigurationTarget, QuickPickItem, Uri } from 'vscode'; -import { IApplicationShell, ICommandManager, IWorkspaceService } from '../../common/application/types'; -import { IConfigurationService } from '../../common/types'; -import { DataScience } from '../../common/utils/localize'; -import { - IMultiStepInput, - IMultiStepInputFactory, - InputStep, - IQuickPickParameters -} from '../../common/utils/multiStepInput'; -import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; -import { Telemetry } from '../constants'; - -@injectable() -export class JupyterCommandLineSelector { - private readonly defaultLabel = `$(zap) ${DataScience.jupyterCommandLineDefaultLabel()}`; - private readonly customLabel = `$(gear) ${DataScience.jupyterCommandLineCustomLabel()}`; - constructor( - @inject(IMultiStepInputFactory) private readonly multiStepFactory: IMultiStepInputFactory, - @inject(IConfigurationService) private configuration: IConfigurationService, - @inject(IWorkspaceService) workspaceService: IWorkspaceService, - @inject(IApplicationShell) private appShell: IApplicationShell, - @inject(ICommandManager) private commandManager: ICommandManager - ) { - workspaceService.onDidChangeConfiguration(this.onDidChangeConfiguration.bind(this)); - } - - @captureTelemetry(Telemetry.SelectJupyterURI) - public selectJupyterCommandLine(file: Uri): Promise { - const multiStep = this.multiStepFactory.create<{}>(); - return multiStep.run(this.startSelectingCommandLine.bind(this, file), {}); - } - - private async onDidChangeConfiguration(e: ConfigurationChangeEvent) { - if (e.affectsConfiguration('python.dataScience.jupyterCommandLineArguments')) { - const reload = DataScience.jupyterCommandLineReloadAnswer(); - const item = await this.appShell.showInformationMessage( - DataScience.jupyterCommandLineReloadQuestion(), - reload - ); - if (item === reload) { - this.commandManager.executeCommand('workbench.action.reloadWindow'); - } - } - } - - private async startSelectingCommandLine( - file: Uri, - input: IMultiStepInput<{}>, - _state: {} - ): Promise | void> { - // First step, show a quick pick to choose either the custom or the default. - // newChoice element will be set if the user picked 'enter a new server' - const item = await input.showQuickPick>({ - placeholder: DataScience.jupyterCommandLineQuickPickPlaceholder(), - items: this.getPickList(), - title: DataScience.jupyterCommandLineQuickPickTitle() - }); - if (item.label === this.defaultLabel) { - await this.setJupyterCommandLine(''); - } else { - return this.selectCustomCommandLine.bind(this, file); - } - } - private async selectCustomCommandLine( - file: Uri, - input: IMultiStepInput<{}>, - _state: {} - ): Promise | void> { - // Ask the user to enter a command line - const result = await input.showInputBox({ - title: DataScience.jupyterCommandLinePrompt(), - value: this.configuration.getSettings(file).datascience.jupyterCommandLineArguments.join(' '), - validate: this.validate, - prompt: '' - }); - - if (result) { - await this.setJupyterCommandLine(result); - } - } - - private async setJupyterCommandLine(val: string): Promise { - if (val) { - sendTelemetryEvent(Telemetry.JupyterCommandLineNonDefault); - } - const split = parseArgsStringToArgv(val); - await this.configuration.updateSetting( - 'dataScience.jupyterCommandLineArguments', - split, - undefined, - ConfigurationTarget.Workspace - ); - } - - private validate = async (_inputText: string): Promise => { - return undefined; - }; - - private getPickList(): QuickPickItem[] { - // Always have 'local' and 'custom' - const items: QuickPickItem[] = []; - items.push({ label: this.defaultLabel, detail: DataScience.jupyterCommandLineDefaultDetail() }); - items.push({ label: this.customLabel, detail: DataScience.jupyterCommandLineCustomDetail() }); - - return items; - } -} diff --git a/src/client/datascience/jupyter/debuggerVariableRegistration.ts b/src/client/datascience/jupyter/debuggerVariableRegistration.ts deleted file mode 100644 index 176b1a622936..000000000000 --- a/src/client/datascience/jupyter/debuggerVariableRegistration.ts +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { inject, injectable, named } from 'inversify'; -import { DebugAdapterTracker, DebugAdapterTrackerFactory, DebugSession, ProviderResult } from 'vscode'; -import { IExtensionSingleActivationService } from '../../activation/types'; -import { IDebugService } from '../../common/application/types'; -import { PYTHON_LANGUAGE } from '../../common/constants'; -import { IDisposableRegistry } from '../../common/types'; -import { Identifiers } from '../constants'; -import { IJupyterDebugService, IJupyterVariables } from '../types'; - -@injectable() -export class DebuggerVariableRegistration implements IExtensionSingleActivationService, DebugAdapterTrackerFactory { - constructor( - @inject(IJupyterDebugService) @named(Identifiers.MULTIPLEXING_DEBUGSERVICE) private debugService: IDebugService, - @inject(IDisposableRegistry) private disposables: IDisposableRegistry, - @inject(IJupyterVariables) @named(Identifiers.DEBUGGER_VARIABLES) private debugVariables: DebugAdapterTracker - ) {} - public activate(): Promise { - this.disposables.push(this.debugService.registerDebugAdapterTrackerFactory(PYTHON_LANGUAGE, this)); - return Promise.resolve(); - } - - public createDebugAdapterTracker(_session: DebugSession): ProviderResult { - return this.debugVariables; - } -} diff --git a/src/client/datascience/jupyter/debuggerVariables.ts b/src/client/datascience/jupyter/debuggerVariables.ts deleted file mode 100644 index 6b4093774ad1..000000000000 --- a/src/client/datascience/jupyter/debuggerVariables.ts +++ /dev/null @@ -1,312 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { inject, injectable, named } from 'inversify'; - -import { DebugAdapterTracker, Disposable, Event, EventEmitter } from 'vscode'; -import { DebugProtocol } from 'vscode-debugprotocol'; -import { IDebugService } from '../../common/application/types'; -import { traceError } from '../../common/logger'; -import { IConfigurationService, Resource } from '../../common/types'; -import { sendTelemetryEvent } from '../../telemetry'; -import { DataFrameLoading, Identifiers, Telemetry } from '../constants'; -import { - IConditionalJupyterVariables, - IJupyterDebugService, - IJupyterVariable, - IJupyterVariablesRequest, - IJupyterVariablesResponse, - INotebook -} from '../types'; - -const DataViewableTypes: Set = new Set(['DataFrame', 'list', 'dict', 'ndarray', 'Series']); -const KnownExcludedVariables = new Set(['In', 'Out', 'exit', 'quit']); - -@injectable() -export class DebuggerVariables implements IConditionalJupyterVariables, DebugAdapterTracker { - private refreshEventEmitter = new EventEmitter(); - private lastKnownVariables: IJupyterVariable[] = []; - private topMostFrameId = 0; - private importedIntoKernel = new Set(); - private watchedNotebooks = new Map(); - private debuggingStarted = false; - constructor( - @inject(IJupyterDebugService) @named(Identifiers.MULTIPLEXING_DEBUGSERVICE) private debugService: IDebugService, - @inject(IConfigurationService) private configService: IConfigurationService - ) {} - - public get refreshRequired(): Event { - return this.refreshEventEmitter.event; - } - - public get active(): boolean { - return this.debugService.activeDebugSession !== undefined && this.debuggingStarted; - } - - // IJupyterVariables implementation - public async getVariables( - notebook: INotebook, - request: IJupyterVariablesRequest - ): Promise { - // Listen to notebook events if we haven't already - this.watchNotebook(notebook); - - const result: IJupyterVariablesResponse = { - executionCount: request.executionCount, - pageStartIndex: 0, - pageResponse: [], - totalCount: 0, - refreshCount: request.refreshCount - }; - - if (this.active) { - const startPos = request.startIndex ? request.startIndex : 0; - const chunkSize = request.pageSize ? request.pageSize : 100; - result.pageStartIndex = startPos; - - // Do one at a time. All at once doesn't work as they all have to wait for each other anyway - for (let i = startPos; i < startPos + chunkSize && i < this.lastKnownVariables.length; i += 1) { - const fullVariable = !this.lastKnownVariables[i].truncated - ? this.lastKnownVariables[i] - : await this.getFullVariable(this.lastKnownVariables[i], notebook); - this.lastKnownVariables[i] = fullVariable; - result.pageResponse.push(fullVariable); - } - result.totalCount = this.lastKnownVariables.length; - } - - return result; - } - - public async getMatchingVariable(notebook: INotebook, name: string): Promise { - if (this.active) { - // Note, full variable results isn't necessary for this call. It only really needs the variable value. - const result = this.lastKnownVariables.find((v) => v.name === name); - if (result) { - if (notebook.identity.fsPath.endsWith('.ipynb')) { - sendTelemetryEvent(Telemetry.RunByLineVariableHover); - } - } - return result; - } - } - - public async getDataFrameInfo(targetVariable: IJupyterVariable, notebook: INotebook): Promise { - if (!this.active) { - // No active server just return the unchanged target variable - return targetVariable; - } - // Listen to notebook events if we haven't already - this.watchNotebook(notebook); - - // See if we imported or not into the kernel our special function - await this.importDataFrameScripts(notebook); - - // Then eval calling the main function with our target variable - const results = await this.evaluate( - `${DataFrameLoading.DataFrameInfoFunc}(${targetVariable.name})`, - // tslint:disable-next-line: no-any - (targetVariable as any).frameId - ); - - // Results should be the updated variable. - return { - ...targetVariable, - ...JSON.parse(results.result.slice(1, -1)) - }; - } - - public async getDataFrameRows( - targetVariable: IJupyterVariable, - notebook: INotebook, - start: number, - end: number - ): Promise<{}> { - // Run the get dataframe rows script - if (!this.debugService.activeDebugSession || targetVariable.columns === undefined) { - // No active server just return no rows - return {}; - } - // Listen to notebook events if we haven't already - this.watchNotebook(notebook); - - // See if we imported or not into the kernel our special function - await this.importDataFrameScripts(notebook); - - // Since the debugger splits up long requests, split this based on the number of items. - - // Maximum 100 cells at a time or one row - // tslint:disable-next-line: no-any - let output: any; - const minnedEnd = Math.min(targetVariable.rowCount || 0, end); - const totalRowCount = end - start; - const cellsPerRow = targetVariable.columns!.length; - const chunkSize = Math.floor(Math.max(1, Math.min(100 / cellsPerRow, totalRowCount / cellsPerRow))); - for (let pos = start; pos < end; pos += chunkSize) { - const chunkEnd = Math.min(pos + chunkSize, minnedEnd); - const results = await this.evaluate( - `${DataFrameLoading.DataFrameRowFunc}(${targetVariable.name}, ${pos}, ${chunkEnd})`, - // tslint:disable-next-line: no-any - (targetVariable as any).frameId - ); - const chunkResults = JSON.parse(results.result.slice(1, -1)); - if (output && output.data) { - output = { - ...output, - data: output.data.concat(chunkResults.data) - }; - } else { - output = chunkResults; - } - } - - // Results should be the rows. - return output; - } - - // tslint:disable-next-line: no-any - public onDidSendMessage(message: any) { - // When the initialize response comes back, indicate we have started. - if (message.type === 'response' && message.command === 'initialize') { - this.debuggingStarted = true; - } else if (message.type === 'response' && message.command === 'variables' && message.body) { - // If using the interactive debugger, update our variables. - // tslint:disable-next-line: no-suspicious-comment - // TODO: Figure out what resource to use - this.updateVariables(undefined, message as DebugProtocol.VariablesResponse); - } else if (message.type === 'response' && message.command === 'stackTrace') { - // This should be the top frame. We need to use this to compute the value of a variable - this.updateStackFrame(message as DebugProtocol.StackTraceResponse); - } else if (message.type === 'event' && message.event === 'terminated') { - // When the debugger exits, make sure the variables are cleared - this.lastKnownVariables = []; - this.topMostFrameId = 0; - this.debuggingStarted = false; - this.refreshEventEmitter.fire(); - } - } - - private watchNotebook(notebook: INotebook) { - const key = notebook.identity.toString(); - if (!this.watchedNotebooks.has(key)) { - const disposables: Disposable[] = []; - disposables.push(notebook.onKernelChanged(this.resetImport.bind(this, key))); - disposables.push(notebook.onKernelRestarted(this.resetImport.bind(this, key))); - disposables.push( - notebook.onDisposed(() => { - this.resetImport(key); - disposables.forEach((d) => d.dispose()); - this.watchedNotebooks.delete(key); - }) - ); - this.watchedNotebooks.set(key, disposables); - } - } - - private resetImport(key: string) { - this.importedIntoKernel.delete(key); - } - - // tslint:disable-next-line: no-any - private async evaluate(code: string, frameId?: number): Promise { - if (this.debugService.activeDebugSession) { - const results = await this.debugService.activeDebugSession.customRequest('evaluate', { - expression: code, - frameId: this.topMostFrameId || frameId, - context: 'repl' - }); - if (results && results.result !== 'None') { - return results; - } else { - traceError(`Cannot evaluate ${code}`); - return undefined; - } - } - throw Error('Debugger is not active, cannot evaluate.'); - } - - private async importDataFrameScripts(notebook: INotebook): Promise { - try { - const key = notebook.identity.toString(); - if (!this.importedIntoKernel.has(key)) { - await this.evaluate(DataFrameLoading.DataFrameSysImport); - await this.evaluate(DataFrameLoading.DataFrameInfoImport); - await this.evaluate(DataFrameLoading.DataFrameRowImport); - await this.evaluate(DataFrameLoading.VariableInfoImport); - this.importedIntoKernel.add(key); - } - } catch (exc) { - traceError('Error attempting to import in debugger', exc); - } - } - - private updateStackFrame(stackResponse: DebugProtocol.StackTraceResponse) { - if (stackResponse.body.stackFrames[0]) { - this.topMostFrameId = stackResponse.body.stackFrames[0].id; - } - } - - private async getFullVariable(variable: IJupyterVariable, notebook: INotebook): Promise { - // See if we imported or not into the kernel our special function - await this.importDataFrameScripts(notebook); - - // Then eval calling the variable info function with our target variable - const results = await this.evaluate( - `${DataFrameLoading.VariableInfoFunc}(${variable.name})`, - // tslint:disable-next-line: no-any - (variable as any).frameId - ); - if (results && results.result) { - // Results should be the updated variable. - return { - ...variable, - truncated: false, - ...JSON.parse(results.result.slice(1, -1)) - }; - } else { - // If no results, just return current value. Better than nothing. - return variable; - } - } - - private updateVariables(resource: Resource, variablesResponse: DebugProtocol.VariablesResponse) { - const exclusionList = this.configService.getSettings(resource).datascience.variableExplorerExclude - ? this.configService.getSettings().datascience.variableExplorerExclude?.split(';') - : []; - - const allowedVariables = variablesResponse.body.variables.filter((v) => { - if (!v.name || !v.type || !v.value) { - return false; - } - if (exclusionList && exclusionList.includes(v.type)) { - return false; - } - if (v.name.startsWith('_')) { - return false; - } - if (KnownExcludedVariables.has(v.name)) { - return false; - } - if (v.type === 'NoneType') { - return false; - } - return true; - }); - - this.lastKnownVariables = allowedVariables.map((v) => { - return { - name: v.name, - type: v.type!, - count: 0, - shape: '', - size: 0, - supportsDataExplorer: DataViewableTypes.has(v.type || ''), - value: v.value, - truncated: true, - frameId: v.variablesReference - }; - }); - - this.refreshEventEmitter.fire(); - } -} diff --git a/src/client/datascience/jupyter/interpreter/README.md b/src/client/datascience/jupyter/interpreter/README.md deleted file mode 100644 index c8f28751630c..000000000000 --- a/src/client/datascience/jupyter/interpreter/README.md +++ /dev/null @@ -1 +0,0 @@ -# Contains code related to the interpreter(s) used to start Jupyter, get kernel specs, etc. diff --git a/src/client/datascience/jupyter/interpreter/jupyterCommand.ts b/src/client/datascience/jupyter/interpreter/jupyterCommand.ts deleted file mode 100644 index 7204f12a3b28..000000000000 --- a/src/client/datascience/jupyter/interpreter/jupyterCommand.ts +++ /dev/null @@ -1,339 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { SpawnOptions } from 'child_process'; -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { traceError } from '../../../common/logger'; -import { - ExecutionResult, - IProcessService, - IProcessServiceFactory, - IPythonDaemonExecutionService, - IPythonExecutionFactory, - IPythonExecutionService, - ObservableExecutionResult -} from '../../../common/process/types'; -import { EXTENSION_ROOT_DIR } from '../../../constants'; -import { IEnvironmentActivationService } from '../../../interpreter/activation/types'; -import { IInterpreterService } from '../../../interpreter/contracts'; -import { PythonEnvironment } from '../../../pythonEnvironments/info'; -import { JupyterCommands, JupyterDaemonModule } from '../../constants'; -import { IJupyterCommand, IJupyterCommandFactory } from '../../types'; - -// JupyterCommand objects represent some process that can be launched that should be guaranteed to work because it -// was found by testing it previously -class ProcessJupyterCommand implements IJupyterCommand { - private exe: string; - private requiredArgs: string[]; - private launcherPromise: Promise; - private interpreterPromise: Promise; - private activationHelper: IEnvironmentActivationService; - - constructor( - exe: string, - args: string[], - processServiceFactory: IProcessServiceFactory, - activationHelper: IEnvironmentActivationService, - interpreterService: IInterpreterService - ) { - this.exe = exe; - this.requiredArgs = args; - this.launcherPromise = processServiceFactory.create(); - this.activationHelper = activationHelper; - this.interpreterPromise = interpreterService.getInterpreterDetails(this.exe).catch((_e) => undefined); - } - - public interpreter(): Promise { - return this.interpreterPromise; - } - - public async execObservable(args: string[], options: SpawnOptions): Promise> { - const newOptions = { ...options }; - newOptions.env = await this.fixupEnv(newOptions.env); - const launcher = await this.launcherPromise; - const newArgs = [...this.requiredArgs, ...args]; - return launcher.execObservable(this.exe, newArgs, newOptions); - } - - public async exec(args: string[], options: SpawnOptions): Promise> { - const newOptions = { ...options }; - newOptions.env = await this.fixupEnv(newOptions.env); - const launcher = await this.launcherPromise; - const newArgs = [...this.requiredArgs, ...args]; - return launcher.exec(this.exe, newArgs, newOptions); - } - - private fixupEnv(_env?: NodeJS.ProcessEnv): Promise { - if (this.activationHelper) { - return this.activationHelper.getActivatedEnvironmentVariables(undefined); - } - - return Promise.resolve(process.env); - } -} - -class InterpreterJupyterCommand implements IJupyterCommand { - protected interpreterPromise: Promise; - private pythonLauncher: Promise; - - constructor( - protected readonly moduleName: string, - protected args: string[], - protected readonly pythonExecutionFactory: IPythonExecutionFactory, - private readonly _interpreter: PythonEnvironment, - isActiveInterpreter: boolean - ) { - this.interpreterPromise = Promise.resolve(this._interpreter); - this.pythonLauncher = this.interpreterPromise.then(async (interpreter) => { - // Create a daemon only if the interpreter is the same as the current interpreter. - // We don't want too many daemons (we don't want one for each of the users interpreter on their machine). - if (isActiveInterpreter) { - const svc = await pythonExecutionFactory.createDaemon({ - daemonModule: JupyterDaemonModule, - pythonPath: interpreter!.path - }); - - // If we're using this command to start notebook, then ensure the daemon can start a notebook inside it. - if ( - (moduleName.toLowerCase() === 'jupyter' && - args.join(' ').toLowerCase().startsWith('-m jupyter notebook')) || - (moduleName.toLowerCase() === 'notebook' && args.join(' ').toLowerCase().startsWith('-m notebook')) - ) { - try { - const output = await svc.exec( - [ - path.join( - EXTENSION_ROOT_DIR, - 'pythonFiles', - 'vscode_datascience_helpers', - 'jupyter_nbInstalled.py' - ) - ], - {} - ); - if (output.stdout.toLowerCase().includes('available')) { - return svc; - } - } catch (ex) { - traceError('Checking whether notebook is importable failed', ex); - } - } - } - return pythonExecutionFactory.createActivatedEnvironment({ - interpreter: this._interpreter, - bypassCondaExecution: true - }); - }); - } - public interpreter(): Promise { - return this.interpreterPromise; - } - - public async execObservable(args: string[], options: SpawnOptions): Promise> { - const newOptions = { ...options, extraVariables: { PYTHONWARNINGS: 'ignore' } }; - const launcher = await this.pythonLauncher; - const newArgs = [...this.args, ...args]; - const moduleName = newArgs[1]; - newArgs.shift(); // Remove '-m' - newArgs.shift(); // Remove module name - return launcher.execModuleObservable(moduleName, newArgs, newOptions); - } - - public async exec(args: string[], options: SpawnOptions): Promise> { - const newOptions = { ...options, extraVariables: { PYTHONWARNINGS: 'ignore' } }; - const launcher = await this.pythonLauncher; - const newArgs = [...this.args, ...args]; - const moduleName = newArgs[1]; - newArgs.shift(); // Remove '-m' - newArgs.shift(); // Remove module name - return launcher.execModule(moduleName, newArgs, newOptions); - } -} - -/** - * This class is used to launch the notebook. - * I.e. anything to do with the command `python -m jupyter notebook` or `python -m notebook`. - * - * @class InterpreterJupyterNotebookCommand - * @implements {IJupyterCommand} - */ -export class InterpreterJupyterNotebookCommand extends InterpreterJupyterCommand { - constructor( - moduleName: string, - args: string[], - pythonExecutionFactory: IPythonExecutionFactory, - interpreter: PythonEnvironment, - isActiveInterpreter: boolean - ) { - super(moduleName, args, pythonExecutionFactory, interpreter, isActiveInterpreter); - } -} - -/** - * This class is used to handle kernelspecs. - * I.e. anything to do with the command `python -m jupyter kernelspec`. - * - * @class InterpreterJupyterKernelSpecCommand - * @implements {IJupyterCommand} - */ -// tslint:disable-next-line: max-classes-per-file -export class InterpreterJupyterKernelSpecCommand extends InterpreterJupyterCommand { - constructor( - moduleName: string, - args: string[], - pythonExecutionFactory: IPythonExecutionFactory, - interpreter: PythonEnvironment, - isActiveInterpreter: boolean - ) { - super(moduleName, args, pythonExecutionFactory, interpreter, isActiveInterpreter); - } - - /** - * Kernelspec subcommand requires special treatment. - * Its possible the sub command hasn't been registered (i.e. jupyter kernelspec command hasn't been installed). - * However its possible the kernlspec modules are available. - * So here's what we have: - * - python -m jupyter kernelspec --version (throws an error, as kernelspect sub command not installed) - * - `import jupyter_client.kernelspec` (works, hence kernelspec modules are available) - * - Problem is daemon will say that `kernelspec` is avaiable, as daemon can work with the `jupyter_client.kernelspec`. - * But rest of extension will assume kernelspec is available and `python -m jupyter kenerlspec --version` will fall over. - * Solution: - * - Run using daemon wrapper code if possible (we don't know whether daemon or python process will run kernel spec). - * - Now, its possible the python daemon process is busy in which case we fall back (in daemon wrapper) to using a python process to run the code. - * - However `python -m jupyter kernelspec` will fall over (as such a sub command hasn't been installed), hence calling daemon code will fail. - * - What we do in such an instance is run the python code `python xyz.py` to deal with kernels. - * If that works, great. - * If that fails, then we know that `kernelspec` sub command doesn't exist and `import jupyter_client.kernelspec` also doesn't work. - * In such a case re-throw the exception from the first execution (possibly the daemon wrapper). - * @param {string[]} args - * @param {SpawnOptions} options - * @returns {Promise>} - * @memberof InterpreterJupyterKernelSpecCommand - */ - public async exec(args: string[], options: SpawnOptions): Promise> { - let exception: Error | undefined; - let output: ExecutionResult = { stdout: '' }; - try { - output = await super.exec(args, options); - } catch (ex) { - exception = ex; - } - - if (!output.stderr && !exception) { - return output; - } - - const defaultAction = () => { - if (exception) { - traceError(`Exception attempting to enumerate kernelspecs: `, exception); - throw exception; - } - return output; - }; - - // We're only interested in `python -m jupyter kernelspec` - const interpreter = await this.interpreter(); - if ( - !interpreter || - this.moduleName.toLowerCase() !== 'jupyter' || - this.args.join(' ').toLowerCase() !== `-m jupyter ${JupyterCommands.KernelSpecCommand}`.toLowerCase() - ) { - return defaultAction(); - } - - // Otherwise try running a script instead. - try { - if (args.join(' ').toLowerCase() === 'list --json') { - // Try getting kernels using python script, if that fails (even if there's output in stderr) rethrow original exception. - output = await this.getKernelSpecList(interpreter, options); - return output; - } else if (args.join(' ').toLowerCase() === '--version') { - // Try getting kernelspec version using python script, if that fails (even if there's output in stderr) rethrow original exception. - output = await this.getKernelSpecVersion(interpreter, options); - return output; - } - } catch (innerEx) { - traceError('Failed to get a list of the kernelspec using python script', innerEx); - } - return defaultAction(); - } - - private async getKernelSpecList(interpreter: PythonEnvironment, options: SpawnOptions) { - // Try getting kernels using python script, if that fails (even if there's output in stderr) rethrow original exception. - const activatedEnv = await this.pythonExecutionFactory.createActivatedEnvironment({ - interpreter, - bypassCondaExecution: true - }); - return activatedEnv.exec( - [path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'vscode_datascience_helpers', 'getJupyterKernels.py')], - { ...options, throwOnStdErr: true } - ); - } - private async getKernelSpecVersion(interpreter: PythonEnvironment, options: SpawnOptions) { - // Try getting kernels using python script, if that fails (even if there's output in stderr) rethrow original exception. - const activatedEnv = await this.pythonExecutionFactory.createActivatedEnvironment({ - interpreter, - bypassCondaExecution: true - }); - return activatedEnv.exec( - [ - path.join( - EXTENSION_ROOT_DIR, - 'pythonFiles', - 'vscode_datascience_helpers', - 'getJupyterKernelspecVersion.py' - ) - ], - { ...options, throwOnStdErr: true } - ); - } -} - -// tslint:disable-next-line: max-classes-per-file -@injectable() -export class JupyterCommandFactory implements IJupyterCommandFactory { - constructor( - @inject(IPythonExecutionFactory) private readonly executionFactory: IPythonExecutionFactory, - @inject(IEnvironmentActivationService) private readonly activationHelper: IEnvironmentActivationService, - @inject(IProcessServiceFactory) private readonly processServiceFactory: IProcessServiceFactory, - @inject(IInterpreterService) private readonly interpreterService: IInterpreterService - ) {} - - public createInterpreterCommand( - command: JupyterCommands, - moduleName: string, - args: string[], - interpreter: PythonEnvironment, - isActiveInterpreter: boolean - ): IJupyterCommand { - if (command === JupyterCommands.NotebookCommand) { - return new InterpreterJupyterNotebookCommand( - moduleName, - args, - this.executionFactory, - interpreter, - isActiveInterpreter - ); - } else if (command === JupyterCommands.KernelSpecCommand) { - return new InterpreterJupyterKernelSpecCommand( - moduleName, - args, - this.executionFactory, - interpreter, - isActiveInterpreter - ); - } - return new InterpreterJupyterCommand(moduleName, args, this.executionFactory, interpreter, isActiveInterpreter); - } - - public createProcessCommand(exe: string, args: string[]): IJupyterCommand { - return new ProcessJupyterCommand( - exe, - args, - this.processServiceFactory, - this.activationHelper, - this.interpreterService - ); - } -} diff --git a/src/client/datascience/jupyter/interpreter/jupyterInterpreterDependencyService.ts b/src/client/datascience/jupyter/interpreter/jupyterInterpreterDependencyService.ts deleted file mode 100644 index c93626f704d1..000000000000 --- a/src/client/datascience/jupyter/interpreter/jupyterInterpreterDependencyService.ts +++ /dev/null @@ -1,369 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { SemVer } from 'semver'; -import { CancellationToken } from 'vscode'; -import { IApplicationShell } from '../../../common/application/types'; -import { Cancellation, createPromiseFromCancellation, wrapCancellationTokens } from '../../../common/cancellation'; -import { ProductNames } from '../../../common/installer/productNames'; -import { traceError } from '../../../common/logger'; -import { IInstaller, InstallerResponse, Product } from '../../../common/types'; -import { Common, DataScience } from '../../../common/utils/localize'; -import { noop } from '../../../common/utils/misc'; -import { PythonEnvironment } from '../../../pythonEnvironments/info'; -import { sendTelemetryEvent } from '../../../telemetry'; -import { parseSemVer } from '../../common'; -import { HelpLinks, JupyterCommands, Telemetry } from '../../constants'; -import { reportAction } from '../../progress/decorator'; -import { ReportableAction } from '../../progress/types'; -import { IJupyterCommandFactory } from '../../types'; -import { JupyterInstallError } from '../jupyterInstallError'; - -export enum JupyterInterpreterDependencyResponse { - ok, - selectAnotherInterpreter, - cancel -} - -/** - * Sorts the given list of products (in place) in the order in which they need to be installed. - * E.g. when installing the modules `notebook` and `Jupyter`, its best to first install `Jupyter`. - * - * @param {Product[]} products - */ -function sortProductsInOrderForInstallation(products: Product[]) { - products.sort((a, b) => { - if (a === Product.jupyter) { - return -1; - } - if (b === Product.jupyter) { - return 1; - } - if (a === Product.notebook) { - return -1; - } - if (b === Product.notebook) { - return 1; - } - return 0; - }); -} -/** - * Given a list of products, this will return an error message of the form: - * `Data Science library jupyter not installed` - * `Data Science libraries, jupyter and notebook not installed` - * `Data Science libraries, jupyter, notebook and nbconvert not installed` - * - * @export - * @param {Product[]} products - * @param {string} [interpreterName] - * @returns {string} - */ -export function getMessageForLibrariesNotInstalled(products: Product[], interpreterName?: string): string { - // Even though kernelspec cannot be installed, display it so user knows what is missing. - const names = products - .map((product) => ProductNames.get(product)) - .filter((name) => !!name) - .map((name) => name as string); - - switch (names.length) { - case 0: - return ''; - case 1: - return interpreterName - ? DataScience.libraryRequiredToLaunchJupyterNotInstalledInterpreter().format(interpreterName, names[0]) - : DataScience.libraryRequiredToLaunchJupyterNotInstalled().format(names[0]); - default: { - const lastItem = names.pop(); - return interpreterName - ? DataScience.librariesRequiredToLaunchJupyterNotInstalledInterpreter().format( - interpreterName, - `${names.join(', ')} ${Common.and()} ${lastItem}` - ) - : DataScience.librariesRequiredToLaunchJupyterNotInstalled().format( - `${names.join(', ')} ${Common.and()} ${lastItem}` - ); - } - } -} - -/** - * Responsible for managing dependencies of a Python interpreter required to run Jupyter. - * If required modules aren't installed, will prompt user to install them or select another interpreter. - * - * @export - * @class JupyterInterpreterDependencyService - */ -@injectable() -export class JupyterInterpreterDependencyService { - /** - * Keeps track of the fact that all dependencies are available in an interpreter. - * This cache will be cleared only after reloading VS Code or when the background code detects that modules are not available. - * E.g. every time a user makes a request to get the interpreter information, we use the cache if everything is ok. - * However we still run the code in the background to check if the modules are available, and then update the cache with the results. - * - * @private - * @memberof JupyterInterpreterDependencyService - */ - private readonly dependenciesInstalledInInterpreter = new Set(); - /** - * Same as `dependenciesInstalledInInterpreter`. - * - * @private - * @memberof JupyterInterpreterDependencyService - */ - private readonly nbconvertInstalledInInterpreter = new Set(); - constructor( - @inject(IApplicationShell) private readonly applicationShell: IApplicationShell, - @inject(IInstaller) private readonly installer: IInstaller, - @inject(IJupyterCommandFactory) private readonly commandFactory: IJupyterCommandFactory - ) {} - /** - * Configures the python interpreter to ensure it can run Jupyter server by installing any missing dependencies. - * If user opts not to install they can opt to select another interpreter. - * - * @param {PythonEnvironment} interpreter - * @param {JupyterInstallError} [_error] - * @param {CancellationToken} [token] - * @returns {Promise} - * @memberof JupyterInterpreterDependencyService - */ - @reportAction(ReportableAction.InstallingMissingDependencies) - public async installMissingDependencies( - interpreter: PythonEnvironment, - _error?: JupyterInstallError, - token?: CancellationToken - ): Promise { - const missingProducts = await this.getDependenciesNotInstalled(interpreter, token); - if (Cancellation.isCanceled(token)) { - return JupyterInterpreterDependencyResponse.cancel; - } - if (missingProducts.length === 0) { - return JupyterInterpreterDependencyResponse.ok; - } - - const message = getMessageForLibrariesNotInstalled(missingProducts, interpreter.displayName); - - sendTelemetryEvent(Telemetry.JupyterNotInstalledErrorShown); - const selection = await this.applicationShell.showErrorMessage( - message, - DataScience.jupyterInstall(), - DataScience.selectDifferentJupyterInterpreter(), - DataScience.pythonInteractiveHelpLink() - ); - - if (Cancellation.isCanceled(token)) { - return JupyterInterpreterDependencyResponse.cancel; - } - - switch (selection) { - case DataScience.jupyterInstall(): { - // Ignore kernelspec as it not something that can be installed. - // If kernelspec isn't available, then re-install `Jupyter`. - if (missingProducts.includes(Product.kernelspec) && !missingProducts.includes(Product.jupyter)) { - missingProducts.push(Product.jupyter); - } - const productsToInstall = missingProducts.filter((product) => product !== Product.kernelspec); - // Install jupyter, then notebook, then others in that order. - sortProductsInOrderForInstallation(productsToInstall); - - let productToInstall = productsToInstall.shift(); - const cancellatonPromise = createPromiseFromCancellation({ - cancelAction: 'resolve', - defaultValue: InstallerResponse.Ignore, - token - }); - while (productToInstall) { - // Always pass a cancellation token to `install`, to ensure it waits until the module is installed. - const response = await Promise.race([ - this.installer.install(productToInstall, interpreter, wrapCancellationTokens(token)), - cancellatonPromise - ]); - if (response === InstallerResponse.Installed) { - productToInstall = productsToInstall.shift(); - continue; - } else { - return JupyterInterpreterDependencyResponse.cancel; - } - } - sendTelemetryEvent(Telemetry.UserInstalledJupyter); - - // Check if kernelspec module is something that accessible. - return this.checkKernelSpecAvailability(interpreter); - } - - case DataScience.selectDifferentJupyterInterpreter(): { - sendTelemetryEvent(Telemetry.UserDidNotInstallJupyter); - return JupyterInterpreterDependencyResponse.selectAnotherInterpreter; - } - - case DataScience.pythonInteractiveHelpLink(): { - this.applicationShell.openUrl(HelpLinks.PythonInteractiveHelpLink); - sendTelemetryEvent(Telemetry.UserDidNotInstallJupyter); - return JupyterInterpreterDependencyResponse.cancel; - } - - default: - sendTelemetryEvent(Telemetry.UserDidNotInstallJupyter); - return JupyterInterpreterDependencyResponse.cancel; - } - } - /** - * Whether all dependencies required to start & use a jupyter server are available in the provided interpreter. - * - * @param {PythonEnvironment} interpreter - * @param {CancellationToken} [token] - * @returns {Promise} - * @memberof JupyterInterpreterConfigurationService - */ - public async areDependenciesInstalled(interpreter: PythonEnvironment, token?: CancellationToken): Promise { - return this.getDependenciesNotInstalled(interpreter, token).then((items) => items.length === 0); - } - - /** - * Whether its possible to export ipynb to other formats. - * Basically checks whether nbconvert is installed. - * - * @param {PythonEnvironment} interpreter - * @param {CancellationToken} [_token] - * @returns {Promise} - * @memberof JupyterInterpreterConfigurationService - */ - public async isExportSupported(interpreter: PythonEnvironment, _token?: CancellationToken): Promise { - if (this.nbconvertInstalledInInterpreter.has(interpreter.path)) { - return true; - } - const installed = this.installer.isInstalled(Product.nbconvert, interpreter).then((result) => result === true); - if (installed) { - this.nbconvertInstalledInInterpreter.add(interpreter.path); - } - return installed; - } - - public async getNbConvertVersion( - interpreter: PythonEnvironment, - _token?: CancellationToken - ): Promise { - const command = this.commandFactory.createInterpreterCommand( - JupyterCommands.ConvertCommand, - 'jupyter', - ['-m', 'jupyter', 'nbconvert'], - interpreter, - false - ); - - const result = await command.exec(['--version'], { throwOnStdErr: true }); - - return parseSemVer(result.stdout); - } - - /** - * Gets a list of the dependencies not installed, dependencies that are required to launch the jupyter notebook server. - * - * @param {PythonEnvironment} interpreter - * @param {CancellationToken} [token] - * @returns {Promise} - * @memberof JupyterInterpreterConfigurationService - */ - public async getDependenciesNotInstalled( - interpreter: PythonEnvironment, - token?: CancellationToken - ): Promise { - // If we know that all modules were available at one point in time, then use that cache. - if (this.dependenciesInstalledInInterpreter.has(interpreter.path)) { - return []; - } - - const notInstalled: Product[] = []; - await Promise.race([ - Promise.all([ - this.installer - .isInstalled(Product.jupyter, interpreter) - .then((installed) => (installed ? noop() : notInstalled.push(Product.jupyter))), - this.installer - .isInstalled(Product.notebook, interpreter) - .then((installed) => (installed ? noop() : notInstalled.push(Product.notebook))) - ]), - createPromiseFromCancellation({ cancelAction: 'resolve', defaultValue: undefined, token }) - ]); - - if (notInstalled.length > 0) { - return notInstalled; - } - if (Cancellation.isCanceled(token)) { - return []; - } - // Perform this check only if jupyter & notebook modules are installed. - const products = await this.isKernelSpecAvailable(interpreter, token).then((installed) => - installed ? [] : [Product.kernelspec] - ); - if (products.length === 0) { - this.dependenciesInstalledInInterpreter.add(interpreter.path); - } - return products; - } - - /** - * Checks whether the jupyter sub command kernelspec is available. - * - * @private - * @param {PythonEnvironment} interpreter - * @param {CancellationToken} [_token] - * @returns {Promise} - * @memberof JupyterInterpreterConfigurationService - */ - private async isKernelSpecAvailable(interpreter: PythonEnvironment, _token?: CancellationToken): Promise { - const command = this.commandFactory.createInterpreterCommand( - JupyterCommands.KernelSpecCommand, - 'jupyter', - ['-m', 'jupyter', 'kernelspec'], - interpreter, - false - ); - return command - .exec(['--version'], { throwOnStdErr: true }) - .then(() => true) - .catch((e) => { - traceError(`Kernel spec not found: `, e); - sendTelemetryEvent(Telemetry.KernelSpecNotFound); - return false; - }); - } - - /** - * Even if jupyter module is installed, its possible kernelspec isn't available. - * Possible user has an old version of jupyter or something is corrupted. - * This is an edge case, and we need to handle this. - * Current solution is to get user to select another interpreter or update jupyter/python (we don't know what is wrong). - * - * @private - * @param {PythonEnvironment} interpreter - * @param {CancellationToken} [token] - * @returns {Promise} - * @memberof JupyterInterpreterConfigurationService - */ - private async checkKernelSpecAvailability( - interpreter: PythonEnvironment, - token?: CancellationToken - ): Promise { - if (await this.isKernelSpecAvailable(interpreter)) { - return JupyterInterpreterDependencyResponse.ok; - } - // Indicate no kernel spec module. - sendTelemetryEvent(Telemetry.JupyterInstalledButNotKernelSpecModule); - if (Cancellation.isCanceled(token)) { - return JupyterInterpreterDependencyResponse.cancel; - } - const selectionFromError = await this.applicationShell.showErrorMessage( - DataScience.jupyterKernelSpecModuleNotFound().format(interpreter.path), - DataScience.selectDifferentJupyterInterpreter(), - Common.cancel() - ); - return selectionFromError === DataScience.selectDifferentJupyterInterpreter() - ? JupyterInterpreterDependencyResponse.selectAnotherInterpreter - : JupyterInterpreterDependencyResponse.cancel; - } -} diff --git a/src/client/datascience/jupyter/interpreter/jupyterInterpreterOldCacheStateStore.ts b/src/client/datascience/jupyter/interpreter/jupyterInterpreterOldCacheStateStore.ts deleted file mode 100644 index 86b8a4d26889..000000000000 --- a/src/client/datascience/jupyter/interpreter/jupyterInterpreterOldCacheStateStore.ts +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IWorkspaceService } from '../../../common/application/types'; -import { IPersistentState, IPersistentStateFactory } from '../../../common/types'; - -type CacheInfo = { - /** - * Cache store (across VSC sessions). - * - * @type {IPersistentState} - */ - state: IPersistentState; -}; - -@injectable() -export class JupyterInterpreterOldCacheStateStore { - private readonly workspaceJupyterInterpreter: CacheInfo; - private readonly globalJupyterInterpreter: CacheInfo; - constructor( - @inject(IWorkspaceService) private readonly workspace: IWorkspaceService, - @inject(IPersistentStateFactory) persistentStateFactory: IPersistentStateFactory - ) { - // Cache stores to keep track of jupyter interpreters found. - const workspaceState = persistentStateFactory.createWorkspacePersistentState( - 'DS-VSC-JupyterInterpreter' - ); - const globalState = persistentStateFactory.createGlobalPersistentState('DS-VSC-JupyterInterpreter'); - this.workspaceJupyterInterpreter = { state: workspaceState }; - this.globalJupyterInterpreter = { state: globalState }; - } - private get cacheStore(): CacheInfo { - return this.workspace.hasWorkspaceFolders ? this.workspaceJupyterInterpreter : this.globalJupyterInterpreter; - } - public getCachedInterpreterPath(): string | undefined { - return this.cacheStore.state.value; - } - public async clearCache(): Promise { - await this.cacheStore.state.updateValue(undefined); - } -} diff --git a/src/client/datascience/jupyter/interpreter/jupyterInterpreterSelectionCommand.ts b/src/client/datascience/jupyter/interpreter/jupyterInterpreterSelectionCommand.ts deleted file mode 100644 index 7ee3aaf42cf0..000000000000 --- a/src/client/datascience/jupyter/interpreter/jupyterInterpreterSelectionCommand.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IExtensionSingleActivationService } from '../../../activation/types'; -import { ICommandManager } from '../../../common/application/types'; -import { IDisposableRegistry } from '../../../common/types'; -import { sendTelemetryEvent } from '../../../telemetry'; -import { Telemetry } from '../../constants'; -import { JupyterInterpreterService } from './jupyterInterpreterService'; - -@injectable() -export class JupyterInterpreterSelectionCommand implements IExtensionSingleActivationService { - constructor( - @inject(JupyterInterpreterService) private readonly service: JupyterInterpreterService, - @inject(ICommandManager) private readonly cmdManager: ICommandManager, - @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry - ) {} - public async activate(): Promise { - this.disposables.push( - this.cmdManager.registerCommand('python.datascience.selectJupyterInterpreter', () => { - sendTelemetryEvent(Telemetry.SelectJupyterInterpreterCommand); - this.service.selectInterpreter().ignoreErrors(); - }) - ); - } -} diff --git a/src/client/datascience/jupyter/interpreter/jupyterInterpreterSelector.ts b/src/client/datascience/jupyter/interpreter/jupyterInterpreterSelector.ts deleted file mode 100644 index 396119f68af0..000000000000 --- a/src/client/datascience/jupyter/interpreter/jupyterInterpreterSelector.ts +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { QuickPickOptions } from 'vscode'; -import { CancellationToken } from 'vscode-jsonrpc'; -import { IApplicationShell, IWorkspaceService } from '../../../common/application/types'; -import { Cancellation } from '../../../common/cancellation'; -import { IPathUtils } from '../../../common/types'; -import { DataScience } from '../../../common/utils/localize'; -import { IInterpreterSelector } from '../../../interpreter/configuration/types'; -import { PythonEnvironment } from '../../../pythonEnvironments/info'; -import { JupyterInterpreterStateStore } from './jupyterInterpreterStateStore'; - -/** - * Displays interpreter select and returns the selection to the user. - * - * @export - * @class JupyterInterpreterSelector - */ -@injectable() -export class JupyterInterpreterSelector { - constructor( - @inject(IInterpreterSelector) private readonly interpreterSelector: IInterpreterSelector, - @inject(IApplicationShell) private readonly applicationShell: IApplicationShell, - @inject(JupyterInterpreterStateStore) private readonly interpreterSelectionState: JupyterInterpreterStateStore, - @inject(IWorkspaceService) private readonly workspace: IWorkspaceService, - @inject(IPathUtils) private readonly pathUtils: IPathUtils - ) {} - /** - * Displays interpreter selector and returns the selection. - * - * @param {CancellationToken} [token] - * @returns {(Promise)} - * @memberof JupyterInterpreterSelector - */ - public async selectInterpreter(token?: CancellationToken): Promise { - const workspace = this.workspace.getWorkspaceFolder(undefined); - const currentPythonPath = this.interpreterSelectionState.selectedPythonPath - ? this.pathUtils.getDisplayName(this.interpreterSelectionState.selectedPythonPath, workspace?.uri.fsPath) - : undefined; - - const suggestions = await this.interpreterSelector.getSuggestions(undefined); - if (Cancellation.isCanceled(token)) { - return; - } - const quickPickOptions: QuickPickOptions = { - matchOnDetail: true, - matchOnDescription: true, - placeHolder: currentPythonPath - ? DataScience.currentlySelectedJupyterInterpreterForPlaceholder().format(currentPythonPath) - : '' - }; - - const selection = await this.applicationShell.showQuickPick(suggestions, quickPickOptions); - if (!selection) { - return; - } - return selection.interpreter; - } -} diff --git a/src/client/datascience/jupyter/interpreter/jupyterInterpreterService.ts b/src/client/datascience/jupyter/interpreter/jupyterInterpreterService.ts deleted file mode 100644 index 720ac972f3e4..000000000000 --- a/src/client/datascience/jupyter/interpreter/jupyterInterpreterService.ts +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { Event, EventEmitter } from 'vscode'; -import { CancellationToken } from 'vscode-jsonrpc'; -import { createPromiseFromCancellation } from '../../../common/cancellation'; -import '../../../common/extensions'; -import { noop } from '../../../common/utils/misc'; -import { IInterpreterService } from '../../../interpreter/contracts'; -import { PythonEnvironment } from '../../../pythonEnvironments/info'; -import { sendTelemetryEvent } from '../../../telemetry'; -import { Telemetry } from '../../constants'; -import { JupyterInstallError } from '../jupyterInstallError'; -import { - JupyterInterpreterDependencyResponse, - JupyterInterpreterDependencyService -} from './jupyterInterpreterDependencyService'; -import { JupyterInterpreterOldCacheStateStore } from './jupyterInterpreterOldCacheStateStore'; -import { JupyterInterpreterSelector } from './jupyterInterpreterSelector'; -import { JupyterInterpreterStateStore } from './jupyterInterpreterStateStore'; - -@injectable() -export class JupyterInterpreterService { - private _selectedInterpreter?: PythonEnvironment; - private _onDidChangeInterpreter = new EventEmitter(); - private getInitialInterpreterPromise: Promise | undefined; - public get onDidChangeInterpreter(): Event { - return this._onDidChangeInterpreter.event; - } - - constructor( - @inject(JupyterInterpreterOldCacheStateStore) - private readonly oldVersionCacheStateStore: JupyterInterpreterOldCacheStateStore, - @inject(JupyterInterpreterStateStore) private readonly interpreterSelectionState: JupyterInterpreterStateStore, - @inject(JupyterInterpreterSelector) private readonly jupyterInterpreterSelector: JupyterInterpreterSelector, - @inject(JupyterInterpreterDependencyService) - private readonly interpreterConfiguration: JupyterInterpreterDependencyService, - @inject(IInterpreterService) private readonly interpreterService: IInterpreterService - ) {} - /** - * Gets the selected interpreter configured to run Jupyter. - * - * @param {CancellationToken} [token] - * @returns {(Promise)} - * @memberof JupyterInterpreterService - */ - public async getSelectedInterpreter(token?: CancellationToken): Promise { - // Before we return _selected interpreter make sure that we have run our initial set interpreter once - // because _selectedInterpreter can be changed by other function and at other times, this promise - // is cached to only run once - await this.setInitialInterpreter(token); - - return this._selectedInterpreter; - } - - // To be run one initial time. Check our saved locations and then current interpreter to try to start off - // with a valid jupyter interpreter - public async setInitialInterpreter(token?: CancellationToken): Promise { - if (!this.getInitialInterpreterPromise) { - this.getInitialInterpreterPromise = this.getInitialInterpreterImpl(token).then((result) => { - // Set ourselves as a valid interpreter if we found something - if (result) { - this.changeSelectedInterpreterProperty(result); - } - return result; - }); - } - - return this.getInitialInterpreterPromise; - } - - /** - * Selects and interpreter to run jupyter server. - * Validates and configures the interpreter. - * Once completed, the interpreter is stored in settings, else user can select another interpreter. - * - * @param {CancellationToken} [token] - * @returns {(Promise)} - * @memberof JupyterInterpreterService - */ - public async selectInterpreter(token?: CancellationToken): Promise { - const resolveToUndefinedWhenCancelled = createPromiseFromCancellation({ - cancelAction: 'resolve', - defaultValue: undefined, - token - }); - const interpreter = await Promise.race([ - this.jupyterInterpreterSelector.selectInterpreter(), - resolveToUndefinedWhenCancelled - ]); - if (!interpreter) { - sendTelemetryEvent(Telemetry.SelectJupyterInterpreter, undefined, { result: 'notSelected' }); - return; - } - - const result = await this.interpreterConfiguration.installMissingDependencies(interpreter, undefined, token); - switch (result) { - case JupyterInterpreterDependencyResponse.ok: { - await this.setAsSelectedInterpreter(interpreter); - return interpreter; - } - case JupyterInterpreterDependencyResponse.cancel: - sendTelemetryEvent(Telemetry.SelectJupyterInterpreter, undefined, { result: 'installationCancelled' }); - return; - default: - return this.selectInterpreter(token); - } - } - - // Install jupyter dependencies in the current jupyter selected interpreter - // If there is no jupyter selected interpreter, prompt for install into the - // current active interpreter and set as active if successful - public async installMissingDependencies(err?: JupyterInstallError): Promise { - const jupyterInterpreter = await this.getSelectedInterpreter(); - let interpreter = jupyterInterpreter; - if (!interpreter) { - // Use current interpreter. - interpreter = await this.interpreterService.getActiveInterpreter(undefined); - if (!interpreter) { - // Unlikely scenario, user hasn't selected python, python extension will fall over. - // Get user to select something. - await this.selectInterpreter(); - return; - } - } - - const response = await this.interpreterConfiguration.installMissingDependencies(interpreter, err); - if (response === JupyterInterpreterDependencyResponse.selectAnotherInterpreter) { - await this.selectInterpreter(); - } else if (response === JupyterInterpreterDependencyResponse.ok) { - // We might have installed jupyter in a new active interpreter here, if we did and the install - // went ok we also want to select that interpreter as our jupyter selected interperter - // so that on next launch we use it correctly - if (interpreter !== jupyterInterpreter) { - await this.setAsSelectedInterpreter(interpreter); - } - } - } - - // Set the specified interpreter as our current selected interpreter. Public so can - // be set by the test code. - public async setAsSelectedInterpreter(interpreter: PythonEnvironment): Promise { - // Make sure that our initial set has happened before we allow a set so that - // calculation of the initial interpreter doesn't clobber the existing one - await this.setInitialInterpreter(); - this.changeSelectedInterpreterProperty(interpreter); - } - - // Check the location that we stored jupyter launch path in the old version - // if it's there, return it and clear the location - private getInterpreterFromChangeOfOlderVersionOfExtension(): string | undefined { - const pythonPath = this.oldVersionCacheStateStore.getCachedInterpreterPath(); - if (!pythonPath) { - return; - } - - // Clear the cache to not check again - this.oldVersionCacheStateStore.clearCache().ignoreErrors(); - return pythonPath; - } - - private changeSelectedInterpreterProperty(interpreter: PythonEnvironment) { - this._selectedInterpreter = interpreter; - this._onDidChangeInterpreter.fire(interpreter); - this.interpreterSelectionState.updateSelectedPythonPath(interpreter.path); - sendTelemetryEvent(Telemetry.SelectJupyterInterpreter, undefined, { result: 'selected' }); - } - - // For a given python path check if it can run jupyter for us - // if so, return the interpreter - private async validateInterpreterPath( - pythonPath: string, - token?: CancellationToken - ): Promise { - try { - const resolveToUndefinedWhenCancelled = createPromiseFromCancellation({ - cancelAction: 'resolve', - defaultValue: undefined, - token - }); - - // First see if we can get interpreter details - const interpreter = await Promise.race([ - this.interpreterService.getInterpreterDetails(pythonPath, undefined), - resolveToUndefinedWhenCancelled - ]); - if (interpreter) { - // Then check that dependencies are installed - if (await this.interpreterConfiguration.areDependenciesInstalled(interpreter, token)) { - return interpreter; - } - } - } catch (_err) { - // For any errors we are ok with just returning undefined for an invalid interpreter - noop(); - } - return undefined; - } - - private async getInitialInterpreterImpl(token?: CancellationToken): Promise { - let interpreter: PythonEnvironment | undefined; - - // Check the old version location first, we will clear it if we find it here - const oldVersionPythonPath = this.getInterpreterFromChangeOfOlderVersionOfExtension(); - if (oldVersionPythonPath) { - interpreter = await this.validateInterpreterPath(oldVersionPythonPath, token); - } - - // Next check the saved global path - if (!interpreter && this.interpreterSelectionState.selectedPythonPath) { - interpreter = await this.validateInterpreterPath(this.interpreterSelectionState.selectedPythonPath, token); - - // If we had a global path, but it's not valid, trash it - if (!interpreter) { - this.interpreterSelectionState.updateSelectedPythonPath(undefined); - } - } - - // Nothing saved found, so check our current interpreter - if (!interpreter) { - const currentInterpreter = await this.interpreterService.getActiveInterpreter(undefined); - - if (currentInterpreter) { - // If the current active interpreter has everything installed already just use that - if (await this.interpreterConfiguration.areDependenciesInstalled(currentInterpreter, token)) { - interpreter = currentInterpreter; - } - } - } - - return interpreter; - } -} diff --git a/src/client/datascience/jupyter/interpreter/jupyterInterpreterStateStore.ts b/src/client/datascience/jupyter/interpreter/jupyterInterpreterStateStore.ts deleted file mode 100644 index 3fecb01848db..000000000000 --- a/src/client/datascience/jupyter/interpreter/jupyterInterpreterStateStore.ts +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable, named } from 'inversify'; -import { Memento } from 'vscode'; -import { GLOBAL_MEMENTO, IMemento } from '../../../common/types'; -import { noop } from '../../../common/utils/misc'; - -const key = 'INTERPRETER_PATH_SELECTED_FOR_JUPYTER_SERVER'; -const keySelected = 'INTERPRETER_PATH_WAS_SELECTED_FOR_JUPYTER_SERVER'; -/** - * Keeps track of whether the user ever selected an interpreter to be used as the global jupyter interpreter. - * Keeps track of the interpreter path of the interpreter used as the global jupyter interpreter. - * - * @export - * @class JupyterInterpreterStateStore - */ -@injectable() -export class JupyterInterpreterStateStore { - private _interpreterPath?: string; - constructor(@inject(IMemento) @named(GLOBAL_MEMENTO) private readonly memento: Memento) {} - - /** - * Whether the user set an interpreter at least once (an interpreter for starting of jupyter). - * - * @readonly - * @type {Promise} - */ - public get interpreterSetAtleastOnce(): boolean { - return !!this.selectedPythonPath || this.memento.get(keySelected, false); - } - public get selectedPythonPath(): string | undefined { - return this._interpreterPath || this.memento.get(key, undefined); - } - public updateSelectedPythonPath(value: string | undefined) { - this._interpreterPath = value; - this.memento.update(key, value).then(noop, noop); - this.memento.update(keySelected, true).then(noop, noop); - } -} diff --git a/src/client/datascience/jupyter/interpreter/jupyterInterpreterSubCommandExecutionService.ts b/src/client/datascience/jupyter/interpreter/jupyterInterpreterSubCommandExecutionService.ts deleted file mode 100644 index 905cd6eb42bc..000000000000 --- a/src/client/datascience/jupyter/interpreter/jupyterInterpreterSubCommandExecutionService.ts +++ /dev/null @@ -1,290 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable, named } from 'inversify'; -import * as path from 'path'; -import { SemVer } from 'semver'; -import { CancellationToken, Uri } from 'vscode'; -import { Cancellation } from '../../../common/cancellation'; -import { traceError, traceInfo, traceWarning } from '../../../common/logger'; - -import { - IPythonDaemonExecutionService, - IPythonExecutionFactory, - ObservableExecutionResult, - SpawnOptions -} from '../../../common/process/types'; -import { IOutputChannel, IPathUtils, Product } from '../../../common/types'; -import { DataScience } from '../../../common/utils/localize'; -import { noop } from '../../../common/utils/misc'; -import { EXTENSION_ROOT_DIR } from '../../../constants'; -import { IInterpreterService } from '../../../interpreter/contracts'; -import { PythonEnvironment } from '../../../pythonEnvironments/info'; -import { sendTelemetryEvent } from '../../../telemetry'; -import { JUPYTER_OUTPUT_CHANNEL, JupyterDaemonModule, Telemetry } from '../../constants'; -import { reportAction } from '../../progress/decorator'; -import { ReportableAction } from '../../progress/types'; -import { - IDataScienceFileSystem, - IJupyterInterpreterDependencyManager, - IJupyterSubCommandExecutionService -} from '../../types'; -import { JupyterServerInfo } from '../jupyterConnection'; -import { JupyterInstallError } from '../jupyterInstallError'; -import { JupyterKernelSpec, parseKernelSpecs } from '../kernels/jupyterKernelSpec'; -import { - getMessageForLibrariesNotInstalled, - JupyterInterpreterDependencyService -} from './jupyterInterpreterDependencyService'; -import { JupyterInterpreterService } from './jupyterInterpreterService'; - -/** - * Responsible for execution of jupyter sub commands using a single/global interpreter set aside for launching jupyter server. - * - * @export - * @class JupyterCommandFinderInterpreterExecutionService - * @implements {IJupyterSubCommandExecutionService} - */ -@injectable() -export class JupyterInterpreterSubCommandExecutionService - implements IJupyterSubCommandExecutionService, IJupyterInterpreterDependencyManager { - constructor( - @inject(JupyterInterpreterService) private readonly jupyterInterpreter: JupyterInterpreterService, - @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, - @inject(JupyterInterpreterDependencyService) - private readonly jupyterDependencyService: JupyterInterpreterDependencyService, - @inject(IDataScienceFileSystem) private readonly fs: IDataScienceFileSystem, - @inject(IPythonExecutionFactory) private readonly pythonExecutionFactory: IPythonExecutionFactory, - @inject(IOutputChannel) @named(JUPYTER_OUTPUT_CHANNEL) private readonly jupyterOutputChannel: IOutputChannel, - @inject(IPathUtils) private readonly pathUtils: IPathUtils - ) {} - - /** - * This is a noop, implemented for backwards compatibility. - * - * @returns {Promise} - * @memberof JupyterInterpreterSubCommandExecutionService - */ - public async refreshCommands(): Promise { - noop(); - } - public async isNotebookSupported(token?: CancellationToken): Promise { - const interpreter = await this.jupyterInterpreter.getSelectedInterpreter(token); - if (!interpreter) { - return false; - } - return this.jupyterDependencyService.areDependenciesInstalled(interpreter, token); - } - public async getExportPackageVersion(token?: CancellationToken): Promise { - const interpreter = await this.jupyterInterpreter.getSelectedInterpreter(token); - if (!interpreter) { - return; - } - - // If nbconvert is there check and return the version - if (await this.jupyterDependencyService.isExportSupported(interpreter, token)) { - return this.jupyterDependencyService.getNbConvertVersion(interpreter, token); - } - } - public async getReasonForJupyterNotebookNotBeingSupported(token?: CancellationToken): Promise { - let interpreter = await this.jupyterInterpreter.getSelectedInterpreter(token); - if (!interpreter) { - // Use current interpreter. - interpreter = await this.interpreterService.getActiveInterpreter(undefined); - if (!interpreter) { - // Unlikely scenario, user hasn't selected python, python extension will fall over. - // Get user to select something. - return DataScience.selectJupyterInterpreter(); - } - } - const productsNotInstalled = await this.jupyterDependencyService.getDependenciesNotInstalled( - interpreter, - token - ); - if (productsNotInstalled.length === 0) { - return ''; - } - - if (productsNotInstalled.length === 1 && productsNotInstalled[0] === Product.kernelspec) { - return DataScience.jupyterKernelSpecModuleNotFound().format(interpreter.path); - } - - return getMessageForLibrariesNotInstalled(productsNotInstalled, interpreter.displayName); - } - public async getSelectedInterpreter(token?: CancellationToken): Promise { - return this.jupyterInterpreter.getSelectedInterpreter(token); - } - public async startNotebook( - notebookArgs: string[], - options: SpawnOptions - ): Promise> { - const interpreter = await this.getSelectedInterpreterAndThrowIfNotAvailable(options.token); - this.jupyterOutputChannel.appendLine( - DataScience.startingJupyterLogMessage().format( - this.pathUtils.getDisplayName(interpreter.path), - notebookArgs.join(' ') - ) - ); - const executionService = await this.pythonExecutionFactory.createDaemon({ - daemonModule: JupyterDaemonModule, - pythonPath: interpreter.path - }); - return executionService.execModuleObservable('jupyter', ['notebook'].concat(notebookArgs), options); - } - - public async getRunningJupyterServers(token?: CancellationToken): Promise { - const interpreter = await this.getSelectedInterpreterAndThrowIfNotAvailable(token); - const daemon = await this.pythonExecutionFactory.createDaemon({ - daemonModule: JupyterDaemonModule, - pythonPath: interpreter.path - }); - - // We have a small python file here that we will execute to get the server info from all running Jupyter instances - const newOptions: SpawnOptions = { mergeStdOutErr: true, token: token }; - const file = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'vscode_datascience_helpers', 'getServerInfo.py'); - const serverInfoString = await daemon.exec([file], newOptions); - - let serverInfos: JupyterServerInfo[]; - try { - // Parse out our results, return undefined if we can't suss it out - serverInfos = JSON.parse(serverInfoString.stdout.trim()) as JupyterServerInfo[]; - } catch (err) { - traceWarning('Failed to parse JSON when getting server info out from getServerInfo.py', err); - return; - } - return serverInfos; - } - - @reportAction(ReportableAction.ExportNotebookToPython) - public async exportNotebookToPython(file: Uri, template?: string, token?: CancellationToken): Promise { - // Before we export check if our selected interpreter is available and supports export - let interpreter = await this.getSelectedInterpreter(token); - if (!interpreter || !(await this.jupyterDependencyService.isExportSupported(interpreter, token))) { - // If not available or not supported install missing dependecies - await this.installMissingDependencies(); - - // Install missing dependencies might change the selected interpreter, so check the new one - interpreter = await this.getSelectedInterpreterAndThrowIfNotAvailable(token); - - if (!(await this.jupyterDependencyService.isExportSupported(interpreter, token))) { - throw new Error(DataScience.jupyterNbConvertNotSupported()); - } - } - - const daemon = await this.pythonExecutionFactory.createDaemon({ - daemonModule: JupyterDaemonModule, - pythonPath: interpreter.path - }); - // Wait for the nbconvert to finish - const args = template - ? [file.fsPath, '--to', 'python', '--stdout', '--template', template] - : [file.fsPath, '--to', 'python', '--stdout']; - - // Ignore stderr, as nbconvert writes conversion result to stderr. - // stdout contains the generated python code. - return daemon - .execModule('jupyter', ['nbconvert'].concat(args), { throwOnStdErr: false, encoding: 'utf8', token }) - .then((output) => { - // We can't check stderr (as nbconvert puts diag output there) but we need to verify here that we actually - // converted something. If it's zero size then just raise an error - if (output.stdout === '') { - traceError('nbconvert zero size output'); - throw new Error(output.stderr); - } else { - return output.stdout; - } - }); - } - public async openNotebook(notebookFile: string): Promise { - const interpreter = await this.getSelectedInterpreterAndThrowIfNotAvailable(); - // Do not use the daemon for this, its a waste resources. The user will manage the lifecycle of this process. - const executionService = await this.pythonExecutionFactory.createActivatedEnvironment({ - interpreter, - bypassCondaExecution: true, - allowEnvironmentFetchExceptions: true - }); - const args: string[] = [`--NotebookApp.file_to_run=${notebookFile}`]; - - // Don't wait for the exec to finish and don't dispose. It's up to the user to kill the process - executionService - .execModule('jupyter', ['notebook'].concat(args), { throwOnStdErr: false, encoding: 'utf8' }) - .ignoreErrors(); - } - - public async getKernelSpecs(token?: CancellationToken): Promise { - const interpreter = await this.getSelectedInterpreterAndThrowIfNotAvailable(token); - const daemon = await this.pythonExecutionFactory.createDaemon({ - daemonModule: JupyterDaemonModule, - pythonPath: interpreter.path - }); - if (Cancellation.isCanceled(token)) { - return []; - } - try { - traceInfo('Asking for kernelspecs from jupyter'); - const spawnOptions = { throwOnStdErr: true, encoding: 'utf8' }; - // Ask for our current list. - const stdoutFromDaemonPromise = await daemon - .execModule('jupyter', ['kernelspec', 'list', '--json'], spawnOptions) - .then((output) => output.stdout) - .catch((daemonEx) => { - sendTelemetryEvent(Telemetry.KernelSpecNotFound); - traceError('Failed to list kernels from daemon', daemonEx); - return ''; - }); - // Possible we cannot import ipykernel for some reason. (use as backup option). - const stdoutFromFileExecPromise = daemon - .exec( - [ - path.join( - EXTENSION_ROOT_DIR, - 'pythonFiles', - 'vscode_datascience_helpers', - 'getJupyterKernels.py' - ) - ], - spawnOptions - ) - .then((output) => output.stdout) - .catch((fileEx) => { - traceError('Failed to list kernels from getJupyterKernels.py', fileEx); - return ''; - }); - - const [stdoutFromDaemon, stdoutFromFileExec] = await Promise.all([ - stdoutFromDaemonPromise, - stdoutFromFileExecPromise - ]); - - return parseKernelSpecs( - stdoutFromDaemon || stdoutFromFileExec, - this.fs, - this.pythonExecutionFactory, - token - ).catch((parserError) => { - traceError('Failed to parse kernelspecs', parserError); - // This is failing for some folks. In that case return nothing - return []; - }); - } catch (ex) { - traceError('Failed to list kernels', ex); - // This is failing for some folks. In that case return nothing - return []; - } - } - - public async installMissingDependencies(err?: JupyterInstallError): Promise { - await this.jupyterInterpreter.installMissingDependencies(err); - } - - private async getSelectedInterpreterAndThrowIfNotAvailable(token?: CancellationToken): Promise { - const interpreter = await this.jupyterInterpreter.getSelectedInterpreter(token); - if (!interpreter) { - const reason = await this.getReasonForJupyterNotebookNotBeingSupported(); - throw new JupyterInstallError(reason, DataScience.pythonInteractiveHelpLink()); - } - return interpreter; - } -} diff --git a/src/client/datascience/jupyter/invalidNotebookFileError.ts b/src/client/datascience/jupyter/invalidNotebookFileError.ts deleted file mode 100644 index eb8989a55409..000000000000 --- a/src/client/datascience/jupyter/invalidNotebookFileError.ts +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; -import * as localize from '../../common/utils/localize'; - -export class InvalidNotebookFileError extends Error { - constructor(file?: string) { - super( - file - ? localize.DataScience.invalidNotebookFileErrorFormat().format(file) - : localize.DataScience.invalidNotebookFileError() - ); - } -} diff --git a/src/client/datascience/jupyter/jupyterCellOutputMimeTypeTracker.ts b/src/client/datascience/jupyter/jupyterCellOutputMimeTypeTracker.ts deleted file mode 100644 index b595b232341f..000000000000 --- a/src/client/datascience/jupyter/jupyterCellOutputMimeTypeTracker.ts +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import type { nbformat } from '@jupyterlab/coreutils'; -import { sha256 } from 'hash.js'; -import { inject, injectable } from 'inversify'; -import { IExtensionSingleActivationService } from '../../activation/types'; -import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; -import { Telemetry } from '../constants'; -import { CellState, ICell, INotebookEditor, INotebookEditorProvider, INotebookExecutionLogger } from '../types'; -// tslint:disable-next-line:no-require-imports no-var-requires -const flatten = require('lodash/flatten') as typeof import('lodash/flatten'); - -@injectable() -export class CellOutputMimeTypeTracker implements IExtensionSingleActivationService, INotebookExecutionLogger { - private pendingChecks = new Map(); - private sentMimeTypes: Set = new Set(); - - constructor(@inject(INotebookEditorProvider) private notebookEditorProvider: INotebookEditorProvider) { - this.notebookEditorProvider.onDidOpenNotebookEditor((t) => this.onOpenedOrClosedNotebook(t)); - } - - public dispose() { - this.pendingChecks.clear(); - } - - public onKernelRestarted() { - // Do nothing on restarted - } - public async preExecute(_cell: ICell, _silent: boolean): Promise { - // Do nothing on pre execute - } - public async postExecute(cell: ICell, silent: boolean): Promise { - if (!silent && cell.data.cell_type === 'code') { - this.scheduleCheck(this.createCellKey(cell), this.checkCell.bind(this, cell)); - } - } - public async activate(): Promise { - // Act like all of our open documents just opened; our timeout will make sure this is delayed. - this.notebookEditorProvider.editors.forEach((e) => this.onOpenedOrClosedNotebook(e)); - } - - private onOpenedOrClosedNotebook(e: INotebookEditor) { - if (e.file) { - this.scheduleCheck(e.file.fsPath, this.checkNotebook.bind(this, e)); - } - } - private getCellOutputMimeTypes(cell: ICell): string[] { - if (cell.data.cell_type === 'markdown') { - return ['markdown']; - } - if (cell.data.cell_type !== 'code') { - return []; - } - if (!Array.isArray(cell.data.outputs)) { - return []; - } - switch (cell.state) { - case CellState.editing: - case CellState.error: - case CellState.executing: - return []; - default: { - return flatten(cell.data.outputs.map(this.getOutputMimeTypes.bind(this))); - } - } - } - private getOutputMimeTypes(output: nbformat.IOutput): string[] { - // tslint:disable-next-line: no-any - const outputType: nbformat.OutputType = output.output_type as any; - switch (outputType) { - case 'error': - return []; - case 'stream': - return ['stream']; - case 'display_data': - case 'update_display_data': - case 'execute_result': - // tslint:disable-next-line: no-any - const data = (output as any).data; - return data ? Object.keys(data) : []; - default: - // If we have a large number of these, then something is wrong. - return ['unrecognized_cell_output']; - } - } - - private scheduleCheck(id: string, check: () => void) { - // If already scheduled, cancel. - const currentTimeout = this.pendingChecks.get(id); - if (currentTimeout) { - // tslint:disable-next-line: no-any - clearTimeout(currentTimeout as any); - this.pendingChecks.delete(id); - } - - // Now schedule a new one. - // Wait five seconds to make sure we don't already have this document pending. - this.pendingChecks.set(id, setTimeout(check, 5000)); - } - - private createCellKey(cell: ICell): string { - return `${cell.file}${cell.id}`; - } - - @captureTelemetry(Telemetry.HashedCellOutputMimeTypePerf) - private checkCell(cell: ICell) { - this.pendingChecks.delete(this.createCellKey(cell)); - this.getCellOutputMimeTypes(cell).forEach(this.sendTelemetry.bind(this)); - } - - @captureTelemetry(Telemetry.HashedNotebookCellOutputMimeTypePerf) - private checkNotebook(e: INotebookEditor) { - this.pendingChecks.delete(e.file.fsPath); - e.model?.cells.forEach(this.checkCell.bind(this)); - } - - private sendTelemetry(mimeType: string) { - // No need to send duplicate telemetry or waste CPU cycles on an unneeded hash. - if (this.sentMimeTypes.has(mimeType)) { - return; - } - this.sentMimeTypes.add(mimeType); - // Hash the package name so that we will never accidentally see a - // user's private package name. - const hashedName = sha256().update(mimeType).digest('hex'); - - const lowerMimeType = mimeType.toLowerCase(); - // The following gives us clues of the mimetype. - const props = { - hashedName, - hasText: lowerMimeType.includes('text'), - hasLatex: lowerMimeType.includes('latex'), - hasHtml: lowerMimeType.includes('html'), - hasSvg: lowerMimeType.includes('svg'), - hasXml: lowerMimeType.includes('xml'), - hasJson: lowerMimeType.includes('json'), - hasImage: lowerMimeType.includes('image'), - hasGeo: lowerMimeType.includes('geo'), - hasPlotly: lowerMimeType.includes('plotly'), - hasVega: lowerMimeType.includes('vega'), - hasWidget: lowerMimeType.includes('widget'), - hasJupyter: lowerMimeType.includes('jupyter'), - hasVnd: lowerMimeType.includes('vnd') - }; - sendTelemetryEvent(Telemetry.HashedCellOutputMimeType, undefined, props); - } -} diff --git a/src/client/datascience/jupyter/jupyterConnectError.ts b/src/client/datascience/jupyter/jupyterConnectError.ts deleted file mode 100644 index 9cd33eae3a3b..000000000000 --- a/src/client/datascience/jupyter/jupyterConnectError.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; - -export class JupyterConnectError extends Error { - constructor(message: string, stderr?: string) { - super(message + (stderr ? `\n${stderr}` : '')); - } -} diff --git a/src/client/datascience/jupyter/jupyterConnection.ts b/src/client/datascience/jupyter/jupyterConnection.ts deleted file mode 100644 index 691ca2f9462d..000000000000 --- a/src/client/datascience/jupyter/jupyterConnection.ts +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; - -import { ChildProcess } from 'child_process'; -import { Subscription } from 'rxjs'; -import { CancellationToken, Disposable, Event, EventEmitter } from 'vscode'; -import { Cancellation, CancellationError } from '../../common/cancellation'; -import { traceInfo, traceWarning } from '../../common/logger'; - -import { ObservableExecutionResult, Output } from '../../common/process/types'; -import { IConfigurationService, IDisposable } from '../../common/types'; -import { createDeferred, Deferred } from '../../common/utils/async'; -import * as localize from '../../common/utils/localize'; -import { IServiceContainer } from '../../ioc/types'; -import { RegExpValues } from '../constants'; -import { IDataScienceFileSystem, IJupyterConnection } from '../types'; -import { JupyterConnectError } from './jupyterConnectError'; - -// tslint:disable-next-line:no-require-imports no-var-requires no-any -const namedRegexp = require('named-js-regexp'); -const urlMatcher = namedRegexp(RegExpValues.UrlPatternRegEx); - -export type JupyterServerInfo = { - base_url: string; - notebook_dir: string; - hostname: string; - password: boolean; - pid: number; - port: number; - secure: boolean; - token: string; - url: string; -}; - -export class JupyterConnectionWaiter implements IDisposable { - private startPromise: Deferred; - private launchTimeout: NodeJS.Timer | number; - private configService: IConfigurationService; - private fs: IDataScienceFileSystem; - private stderr: string[] = []; - private connectionDisposed = false; - private subscriptions: Subscription[] = []; - - constructor( - private readonly launchResult: ObservableExecutionResult, - private readonly notebookDir: string, - private readonly rootDir: string, - private readonly getServerInfo: (cancelToken?: CancellationToken) => Promise, - serviceContainer: IServiceContainer, - private cancelToken?: CancellationToken - ) { - this.configService = serviceContainer.get(IConfigurationService); - this.fs = serviceContainer.get(IDataScienceFileSystem); - - // Cancel our start promise if a cancellation occurs - if (cancelToken) { - cancelToken.onCancellationRequested(() => this.startPromise.reject(new CancellationError())); - } - - // Setup our start promise - this.startPromise = createDeferred(); - - // We want to reject our Jupyter connection after a specific timeout - const settings = this.configService.getSettings(undefined); - const jupyterLaunchTimeout = settings.datascience.jupyterLaunchTimeout; - - this.launchTimeout = setTimeout(() => { - this.launchTimedOut(); - }, jupyterLaunchTimeout); - - // Listen for crashes - let exitCode = '0'; - if (launchResult.proc) { - launchResult.proc.on('exit', (c) => (exitCode = c ? c.toString() : '0')); - } - let stderr = ''; - // Listen on stderr for its connection information - this.subscriptions.push( - launchResult.out.subscribe( - (output: Output) => { - if (output.source === 'stderr') { - stderr += output.out; - this.stderr.push(output.out); - this.extractConnectionInformation(stderr); - } else { - this.output(output.out); - } - }, - (e) => this.rejectStartPromise(e.message), - // If the process dies, we can't extract connection information. - () => this.rejectStartPromise(localize.DataScience.jupyterServerCrashed().format(exitCode)) - ) - ); - } - public dispose() { - // tslint:disable-next-line: no-any - clearTimeout(this.launchTimeout as any); - this.subscriptions.forEach((d) => d.unsubscribe()); - } - - public waitForConnection(): Promise { - return this.startPromise.promise; - } - - private createConnection(baseUrl: string, token: string, hostName: string, processDisposable: Disposable) { - // tslint:disable-next-line: no-use-before-declare - return new JupyterConnection(baseUrl, token, hostName, this.rootDir, processDisposable, this.launchResult.proc); - } - - // tslint:disable-next-line:no-any - private output(data: any) { - if (!this.connectionDisposed) { - traceInfo(data.toString('utf8')); - } - } - - // From a list of jupyter server infos try to find the matching jupyter that we launched - // tslint:disable-next-line:no-any - private getJupyterURL(serverInfos: JupyterServerInfo[] | undefined, data: any) { - if (serverInfos && serverInfos.length > 0 && !this.startPromise.completed) { - const matchInfo = serverInfos.find((info) => - this.fs.areLocalPathsSame(this.notebookDir, info.notebook_dir) - ); - if (matchInfo) { - const url = matchInfo.url; - const token = matchInfo.token; - const host = matchInfo.hostname; - this.resolveStartPromise(url, token, host); - } - } - // At this point we failed to get the server info or a matching server via the python code, so fall back to - // our URL parse - if (!this.startPromise.completed) { - this.getJupyterURLFromString(data); - } - } - - // tslint:disable-next-line:no-any - private getJupyterURLFromString(data: any) { - // tslint:disable-next-line:no-any - const urlMatch = urlMatcher.exec(data) as any; - const groups = urlMatch.groups() as RegExpValues.IUrlPatternGroupType; - if (urlMatch && !this.startPromise.completed && groups && (groups.LOCAL || groups.IP)) { - // Rebuild the URI from our group hits - const host = groups.LOCAL ? groups.LOCAL : groups.IP; - const uriString = `${groups.PREFIX}${host}${groups.REST}`; - - // URL is not being found for some reason. Pull it in forcefully - // tslint:disable-next-line:no-require-imports - const URL = require('url').URL; - let url: URL; - try { - url = new URL(uriString); - } catch (err) { - // Failed to parse the url either via server infos or the string - this.rejectStartPromise(localize.DataScience.jupyterLaunchNoURL()); - return; - } - - // Here we parsed the URL correctly - this.resolveStartPromise( - `${url.protocol}//${url.host}${url.pathname}`, - `${url.searchParams.get('token')}`, - url.hostname - ); - } - } - - // tslint:disable-next-line:no-any - private extractConnectionInformation = (data: any) => { - this.output(data); - - const httpMatch = RegExpValues.HttpPattern.exec(data); - - if (httpMatch && this.notebookDir && this.startPromise && !this.startPromise.completed && this.getServerInfo) { - // .then so that we can keep from pushing aync up to the subscribed observable function - this.getServerInfo(this.cancelToken) - .then((serverInfos) => this.getJupyterURL(serverInfos, data)) - .catch((ex) => traceWarning('Failed to get server info', ex)); - } - - // Sometimes jupyter will return a 403 error. Not sure why. We used - // to fail on this, but it looks like jupyter works with this error in place. - }; - - private launchTimedOut = () => { - if (!this.startPromise.completed) { - this.rejectStartPromise(localize.DataScience.jupyterLaunchTimedOut()); - } - }; - - private resolveStartPromise = (baseUrl: string, token: string, hostName: string) => { - // tslint:disable-next-line: no-any - clearTimeout(this.launchTimeout as any); - if (!this.startPromise.rejected) { - const connection = this.createConnection(baseUrl, token, hostName, this.launchResult); - const origDispose = connection.dispose.bind(connection); - connection.dispose = () => { - // Stop listening when we disconnect - this.connectionDisposed = true; - return origDispose(); - }; - this.startPromise.resolve(connection); - } - }; - - // tslint:disable-next-line:no-any - private rejectStartPromise = (message: string) => { - // tslint:disable-next-line: no-any - clearTimeout(this.launchTimeout as any); - if (!this.startPromise.resolved) { - this.startPromise.reject( - Cancellation.isCanceled(this.cancelToken) - ? new CancellationError() - : new JupyterConnectError(message, this.stderr.join('\n')) - ); - } - }; -} - -// Represents an active connection to a running jupyter notebook -class JupyterConnection implements IJupyterConnection { - public readonly localLaunch: boolean = true; - public readonly type = 'jupyter'; - public valid: boolean = true; - public localProcExitCode: number | undefined; - private eventEmitter: EventEmitter = new EventEmitter(); - constructor( - public readonly baseUrl: string, - public readonly token: string, - public readonly hostName: string, - public readonly rootDirectory: string, - private readonly disposable: Disposable, - childProc: ChildProcess | undefined - ) { - // If the local process exits, set our exit code and fire our event - if (childProc) { - childProc.on('exit', (c) => { - // Our code expects the exit code to be of type `number` or `undefined`. - const code = typeof c === 'number' ? c : 0; - this.valid = false; - this.localProcExitCode = code; - this.eventEmitter.fire(code); - }); - } - } - - public get displayName(): string { - return getJupyterConnectionDisplayName(this.token, this.baseUrl); - } - - public get disconnected(): Event { - return this.eventEmitter.event; - } - - public dispose() { - if (this.disposable) { - this.disposable.dispose(); - } - } -} - -export function getJupyterConnectionDisplayName(token: string, baseUrl: string): string { - const tokenString = token.length > 0 ? `?token=${token}` : ''; - return `${baseUrl}${tokenString}`; -} diff --git a/src/client/datascience/jupyter/jupyterDataRateLimitError.ts b/src/client/datascience/jupyter/jupyterDataRateLimitError.ts deleted file mode 100644 index 7e08688651dd..000000000000 --- a/src/client/datascience/jupyter/jupyterDataRateLimitError.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as localize from '../../common/utils/localize'; - -export class JupyterDataRateLimitError extends Error { - constructor() { - super(localize.DataScience.jupyterDataRateExceeded()); - } -} diff --git a/src/client/datascience/jupyter/jupyterDebugger.ts b/src/client/datascience/jupyter/jupyterDebugger.ts deleted file mode 100644 index 3d04d70c14af..000000000000 --- a/src/client/datascience/jupyter/jupyterDebugger.ts +++ /dev/null @@ -1,554 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import type { nbformat } from '@jupyterlab/coreutils'; -import { inject, injectable, named } from 'inversify'; -import * as path from 'path'; -import * as uuid from 'uuid/v4'; -import { DebugConfiguration, Disposable } from 'vscode'; -import * as vsls from 'vsls/vscode'; -import { concatMultilineString } from '../../../datascience-ui/common'; -import { ServerStatus } from '../../../datascience-ui/interactive-common/mainState'; -import { IApplicationShell } from '../../common/application/types'; -import { traceError, traceInfo, traceWarning } from '../../common/logger'; -import { IPlatformService } from '../../common/platform/types'; -import { IConfigurationService, Version } from '../../common/types'; -import * as localize from '../../common/utils/localize'; -import { EXTENSION_ROOT_DIR } from '../../constants'; -import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; -import { traceCellResults } from '../common'; -import { Identifiers, Telemetry } from '../constants'; -import { - CellState, - ICell, - ICellHashListener, - IFileHashes, - IJupyterConnection, - IJupyterDebugger, - IJupyterDebugService, - INotebook, - ISourceMapRequest -} from '../types'; -import { JupyterDebuggerNotInstalledError } from './jupyterDebuggerNotInstalledError'; -import { JupyterDebuggerRemoteNotSupported } from './jupyterDebuggerRemoteNotSupported'; -import { ILiveShareHasRole } from './liveshare/types'; - -const pythonShellCommand = `_sysexec = sys.executable\r\n_quoted_sysexec = '"' + _sysexec + '"'\r\n!{_quoted_sysexec}`; - -@injectable() -export class JupyterDebugger implements IJupyterDebugger, ICellHashListener { - private requiredDebugpyVersion: Version = { major: 1, minor: 0, patch: 0, build: [], prerelease: [], raw: '' }; - private configs: Map = new Map(); - private readonly debuggerPackage: string; - private readonly enableDebuggerCode: string; - private readonly waitForDebugClientCode: string; - private readonly tracingEnableCode: string; - private readonly tracingDisableCode: string; - private runningByLine: boolean = false; - constructor( - @inject(IApplicationShell) private appShell: IApplicationShell, - @inject(IConfigurationService) private configService: IConfigurationService, - @inject(IJupyterDebugService) - @named(Identifiers.MULTIPLEXING_DEBUGSERVICE) - private debugService: IJupyterDebugService, - @inject(IPlatformService) private platform: IPlatformService - ) { - this.debuggerPackage = 'debugpy'; - this.enableDebuggerCode = `import debugpy;debugpy.listen(('localhost', 0))`; - this.waitForDebugClientCode = `import debugpy;debugpy.wait_for_client()`; - this.tracingEnableCode = `from debugpy import trace_this_thread;trace_this_thread(True)`; - this.tracingDisableCode = `from debugpy import trace_this_thread;trace_this_thread(False)`; - } - - public get isRunningByLine(): boolean { - return this.debugService.activeDebugSession !== undefined && this.runningByLine; - } - - public startRunByLine(notebook: INotebook, cellHashFileName: string): Promise { - this.runningByLine = true; - traceInfo(`Running by line for ${cellHashFileName}`); - const config: Partial = { - justMyCode: false, - rules: [ - { - include: false, - path: '**/*' - }, - { - include: true, - path: cellHashFileName - } - ] - }; - return this.startDebugSession((c) => this.debugService.startRunByLine(c), notebook, config, true); - } - - public async startDebugging(notebook: INotebook): Promise { - const settings = this.configService.getSettings(notebook.resource); - return this.startDebugSession( - (c) => this.debugService.startDebugging(undefined, c), - notebook, - { - justMyCode: settings.datascience.debugJustMyCode - }, - false - ); - } - - public async stopDebugging(notebook: INotebook): Promise { - this.runningByLine = false; - const config = this.configs.get(notebook.identity.toString()); - if (config) { - traceInfo('stop debugging'); - - // Tell our debug service to shutdown if possible - this.debugService.stop(); - - // Disable tracing after we disconnect because we don't want to step through this - // code if the user was in step mode. - if (notebook.status !== ServerStatus.Dead && notebook.status !== ServerStatus.NotStarted) { - await this.executeSilently(notebook, this.tracingDisableCode); - } - } - } - - public onRestart(notebook: INotebook): void { - this.configs.delete(notebook.identity.toString()); - } - - public async hashesUpdated(hashes: IFileHashes[]): Promise { - // Make sure that we have an active debugging session at this point - if (this.debugService.activeDebugSession) { - await Promise.all( - hashes.map((fileHash) => { - return this.debugService.activeDebugSession!.customRequest( - 'setPydevdSourceMap', - this.buildSourceMap(fileHash) - ); - }) - ); - } - } - - private async startDebugSession( - startCommand: (config: DebugConfiguration) => Thenable, - notebook: INotebook, - extraConfig: Partial, - runByLine: boolean - ) { - traceInfo('start debugging'); - - // Try to connect to this notebook - const config = await this.connect(notebook, runByLine, extraConfig); - if (config) { - traceInfo('connected to notebook during debugging'); - - // First check if this is a live share session. Skip debugging attach on the guest - // tslint:disable-next-line: no-any - const hasRole = (notebook as any) as ILiveShareHasRole; - if (hasRole && hasRole.role && hasRole.role === vsls.Role.Guest) { - traceInfo('guest mode attach skipped'); - } else { - await startCommand(config); - - // Force the debugger to update its list of breakpoints. This is used - // to make sure the breakpoint list is up to date when we do code file hashes - this.debugService.removeBreakpoints([]); - } - - // Wait for attach before we turn on tracing and allow the code to run, if the IDE is already attached this is just a no-op - const importResults = await this.executeSilently(notebook, this.waitForDebugClientCode); - if (importResults.length === 0 || importResults[0].state === CellState.error) { - traceWarning(`${this.debuggerPackage} not found in path.`); - } else { - traceCellResults('import startup', importResults); - } - - // Then enable tracing - await this.executeSilently(notebook, this.tracingEnableCode); - } - } - - private async connect( - notebook: INotebook, - runByLine: boolean, - extraConfig: Partial - ): Promise { - // If we already have configuration, we're already attached, don't do it again. - const key = notebook.identity.toString(); - let result = this.configs.get(key); - if (result) { - return { - ...result, - ...extraConfig - }; - } - traceInfo('enable debugger attach'); - - // Append any specific debugger paths that we have - await this.appendDebuggerPaths(notebook); - - // Check the version of debugger that we have already installed - const debuggerVersion = await this.debuggerCheck(notebook); - const requiredVersion = this.requiredDebugpyVersion; - - // If we don't have debugger installed or the version is too old then we need to install it - if (!debuggerVersion || !this.debuggerMeetsRequirement(debuggerVersion, requiredVersion)) { - await this.promptToInstallDebugger(notebook, debuggerVersion, runByLine); - } - - // Connect local or remote based on what type of notebook we're talking to - result = { - type: 'python', - name: 'IPython', - request: 'attach', - ...extraConfig - }; - const connectionInfo = notebook.connection; - if (connectionInfo && !connectionInfo.localLaunch) { - const { host, port } = await this.connectToRemote(notebook, connectionInfo); - result.host = host; - result.port = port; - } else { - const { host, port } = await this.connectToLocal(notebook); - result.host = host; - result.port = port; - } - - if (result.port) { - this.configs.set(notebook.identity.toString(), result); - - // Sign up for any change to the kernel to delete this config. - const disposables: Disposable[] = []; - const clear = () => { - this.configs.delete(key); - disposables.forEach((d) => d.dispose()); - }; - disposables.push(notebook.onDisposed(clear)); - disposables.push(notebook.onKernelRestarted(clear)); - disposables.push(notebook.onKernelChanged(clear)); - } - - return result; - } - - /** - * Gets the path to debugger. - * Temporary hack to check if python >= 3.7 and if experiments is enabled, then use new debugger, else old. - * (temporary to hard-code and use these in here). - * The old debugger will soon go away into oblivion... - * @private - * @param {INotebook} _notebook - * @returns {Promise} - * @memberof JupyterDebugger - */ - private async getDebuggerPath(_notebook: INotebook): Promise { - // We are here so this is NOT python 3.7, return debugger without wheels - return path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'lib', 'python'); - } - private async calculateDebuggerPathList(notebook: INotebook): Promise { - const extraPaths: string[] = []; - - // Add the settings path first as it takes precedence over the ptvsd extension path - // tslint:disable-next-line:no-multiline-string - let settingsPath = this.configService.getSettings(notebook.resource).datascience.debugpyDistPath; - // Escape windows path chars so they end up in the source escaped - if (settingsPath) { - if (this.platform.isWindows) { - settingsPath = settingsPath.replace(/\\/g, '\\\\'); - } - - extraPaths.push(settingsPath); - } - - // For a local connection we also need will append on the path to the debugger - // installed locally by the extension - // Actually until this is resolved: https://github.com/microsoft/vscode-python/issues/7615, skip adding - // this path. - const connectionInfo = notebook.connection; - if (connectionInfo && connectionInfo.localLaunch) { - let localPath = await this.getDebuggerPath(notebook); - if (this.platform.isWindows) { - localPath = localPath.replace(/\\/g, '\\\\'); - } - extraPaths.push(localPath); - } - - if (extraPaths && extraPaths.length > 0) { - return extraPaths.reduce((totalPath, currentPath) => { - if (totalPath.length === 0) { - totalPath = `'${currentPath}'`; - } else { - totalPath = `${totalPath}, '${currentPath}'`; - } - - return totalPath; - }, ''); - } - - return undefined; - } - - // Append our local debugger path and debugger settings path to sys.path - private async appendDebuggerPaths(notebook: INotebook): Promise { - const debuggerPathList = await this.calculateDebuggerPathList(notebook); - - if (debuggerPathList && debuggerPathList.length > 0) { - const result = await this.executeSilently( - notebook, - `import sys\r\nsys.path.extend([${debuggerPathList}])\r\nsys.path` - ); - traceCellResults('Appending paths', result); - } - } - - private buildSourceMap(fileHash: IFileHashes): ISourceMapRequest { - const sourceMapRequest: ISourceMapRequest = { source: { path: fileHash.file }, pydevdSourceMaps: [] }; - - sourceMapRequest.pydevdSourceMaps = fileHash.hashes.map((cellHash) => { - return { - line: cellHash.line, - endLine: cellHash.endLine, - runtimeSource: { path: `` }, - runtimeLine: cellHash.runtimeLine - }; - }); - - return sourceMapRequest; - } - - private executeSilently(notebook: INotebook, code: string): Promise { - return notebook.execute(code, Identifiers.EmptyFileName, 0, uuid(), undefined, true); - } - - private async debuggerCheck(notebook: INotebook): Promise { - // We don't want to actually import the debugger to check version so run - // python instead. If we import an old version it's hard to get rid of on - // an 'upgrade needed' scenario - // tslint:disable-next-line:no-multiline-string - const debuggerPathList = await this.calculateDebuggerPathList(notebook); - - let code; - if (debuggerPathList) { - code = `import sys\r\n${pythonShellCommand} -c "import sys;sys.path.extend([${debuggerPathList}]);sys.path;import ${this.debuggerPackage};print(${this.debuggerPackage}.__version__)"`; - } else { - code = `import sys\r\n${pythonShellCommand} -c "import ${this.debuggerPackage};print(${this.debuggerPackage}.__version__)"`; - } - - const debuggerVersionResults = await this.executeSilently(notebook, code); - const purpose = 'parseDebugpyVersionInfo'; - return this.parseVersionInfo(debuggerVersionResults, purpose); - } - - private parseVersionInfo( - cells: ICell[], - purpose: 'parseDebugpyVersionInfo' | 'pythonVersionInfo' - ): Version | undefined { - if (cells.length < 1 || cells[0].state !== CellState.finished) { - traceCellResults(purpose, cells); - return undefined; - } - - const targetCell = cells[0]; - - const outputString = this.extractOutput(targetCell); - - if (outputString) { - // Pull out the version number, note that we can't use SemVer here as python packages don't follow it - const packageVersionRegex = /([0-9]+).([0-9]+).([0-9a-zA-Z]+)/; - const packageVersionMatch = packageVersionRegex.exec(outputString); - - if (packageVersionMatch) { - const major = parseInt(packageVersionMatch[1], 10); - const minor = parseInt(packageVersionMatch[2], 10); - const patch = parseInt(packageVersionMatch[3], 10); - return { - major, - minor, - patch, - build: [], - prerelease: [], - raw: `${major}.${minor}.${patch}` - }; - } - } - - traceCellResults(purpose, cells); - - return undefined; - } - - // Check to see if the we have the required version of debugger to support debugging - private debuggerMeetsRequirement(version: Version, required: Version): boolean { - return version.major > required.major || (version.major === required.major && version.minor >= required.minor); - } - - @captureTelemetry(Telemetry.DebugpyPromptToInstall) - private async promptToInstallDebugger( - notebook: INotebook, - oldVersion: Version | undefined, - runByLine: boolean - ): Promise { - const updateMessage = runByLine - ? localize.DataScience.jupyterDebuggerInstallUpdateRunByLine().format(this.debuggerPackage) - : localize.DataScience.jupyterDebuggerInstallUpdate().format(this.debuggerPackage); - const newMessage = runByLine - ? localize.DataScience.jupyterDebuggerInstallNewRunByLine().format(this.debuggerPackage) - : localize.DataScience.jupyterDebuggerInstallNew().format(this.debuggerPackage); - const promptMessage = oldVersion ? updateMessage : newMessage; - const result = await this.appShell.showInformationMessage( - promptMessage, - localize.DataScience.jupyterDebuggerInstallYes(), - localize.DataScience.jupyterDebuggerInstallNo() - ); - - if (result === localize.DataScience.jupyterDebuggerInstallYes()) { - await this.installDebugger(notebook); - } else { - // If they don't want to install, throw so we exit out of debugging - sendTelemetryEvent(Telemetry.DebugpyInstallCancelled); - throw new JupyterDebuggerNotInstalledError(this.debuggerPackage); - } - } - - private async installDebugger(notebook: INotebook): Promise { - // tslint:disable-next-line:no-multiline-string - const debuggerInstallResults = await this.executeSilently( - notebook, - `import sys\r\n${pythonShellCommand} -m pip install -U ${this.debuggerPackage}` - ); - traceInfo(`Installing ${this.debuggerPackage}`); - - if (debuggerInstallResults.length > 0) { - const installResultsString = this.extractOutput(debuggerInstallResults[0]); - - if (installResultsString && installResultsString.includes('Successfully installed')) { - sendTelemetryEvent(Telemetry.DebugpySuccessfullyInstalled); - traceInfo(`${this.debuggerPackage} successfully installed`); - return; - } - } - traceCellResults(`Installing ${this.debuggerPackage}`, debuggerInstallResults); - sendTelemetryEvent(Telemetry.DebugpyInstallFailed); - traceError(`Failed to install ${this.debuggerPackage}`); - // Failed to install debugger, throw to exit debugging - throw new JupyterDebuggerNotInstalledError(this.debuggerPackage); - } - - // Pull our connection info out from the cells returned by enable_attach - private parseConnectInfo(cells: ICell[]): { port: number; host: string } { - if (cells.length > 0) { - let enableAttachString = this.extractOutput(cells[0]); - if (enableAttachString) { - enableAttachString = enableAttachString.trimQuotes(); - - // Important: This regex matches the format of the string returned from enable_attach. When - // doing enable_attach remotely, make sure to print out a string in the format ('host', port) - const debugInfoRegEx = /\('(.*?)', ([0-9]*)\)/; - const debugInfoMatch = debugInfoRegEx.exec(enableAttachString); - if (debugInfoMatch) { - return { - port: parseInt(debugInfoMatch[2], 10), - host: debugInfoMatch[1] - }; - } - } - } - // if we cannot parse the connect information, throw so we exit out of debugging - if (cells[0]?.data) { - const outputs = cells[0].data.outputs as nbformat.IOutput[]; - if (outputs[0]) { - const error = outputs[0] as nbformat.IError; - throw new JupyterDebuggerNotInstalledError(this.debuggerPackage, error.ename); - } - } - throw new JupyterDebuggerNotInstalledError( - localize.DataScience.jupyterDebuggerOutputParseError().format(this.debuggerPackage) - ); - } - - private extractOutput(cell: ICell): string | undefined { - if (cell.state === CellState.error || cell.state === CellState.finished) { - const outputs = cell.data.outputs as nbformat.IOutput[]; - if (outputs.length > 0) { - const data = outputs[0].data; - if (data && data.hasOwnProperty('text/plain')) { - // tslint:disable-next-line:no-any - return concatMultilineString((data as any)['text/plain']); - } - if (outputs[0].output_type === 'stream') { - const stream = outputs[0] as nbformat.IStream; - return concatMultilineString(stream.text, true); - } - } - } - return undefined; - } - - private async connectToLocal(notebook: INotebook): Promise<{ port: number; host: string }> { - const enableDebuggerResults = await this.executeSilently(notebook, this.enableDebuggerCode); - - // Save our connection info to this notebook - return this.parseConnectInfo(enableDebuggerResults); - } - - private async connectToRemote( - _notebook: INotebook, - _connectionInfo: IJupyterConnection - ): Promise<{ port: number; host: string }> { - // We actually need a token. This isn't supported at the moment - throw new JupyterDebuggerRemoteNotSupported(); - - // let portNumber = this.configService.getSettings().datascience.remoteDebuggerPort; - // if (!portNumber) { - // portNumber = -1; - // } - - // // Loop through a bunch of ports until we find one we can use. Note how we - // // are connecting to '0.0.0.0' here. That's the location as far as ptvsd is concerned. - // const attachCode = portNumber !== -1 ? - // `import ptvsd - // ptvsd.enable_attach(('0.0.0.0', ${portNumber})) - // print("('${connectionInfo.hostName}', ${portNumber})")` : - // // tslint:disable-next-line: no-multiline-string - // `import ptvsd - // port = ${Settings.RemoteDebuggerPortBegin} - // attached = False - // while not attached and port <= ${Settings.RemoteDebuggerPortEnd}: - // try: - // ptvsd.enable_attach(('0.0.0.0', port)) - // print("('${connectionInfo.hostName}', " + str(port) + ")") - // attached = True - // except Exception as e: - // print("Exception: " + str(e)) - // port +=1`; - // const enableDebuggerResults = await this.executeSilently(server, attachCode); - - // // Save our connection info to this server - // const result = this.parseConnectInfo(enableDebuggerResults, false); - - // // If that didn't work, throw an error so somebody can open the port - // if (!result) { - // throw new JupyterDebuggerPortNotAvailableError(portNumber, Settings.RemoteDebuggerPortBegin, Settings.RemoteDebuggerPortEnd); - // } - - // // Double check, open a socket? This won't work if we're remote ourselves. Actually the debug adapter runs - // // from the remote machine. - // try { - // const deferred = createDeferred(); - // const socket = net.createConnection(result.port, result.host, () => { - // deferred.resolve(); - // }); - // socket.on('error', (err) => deferred.reject(err)); - // socket.setTimeout(2000, () => deferred.reject(new Error('Timeout trying to ping remote debugger'))); - // await deferred.promise; - // socket.end(); - // } catch (exc) { - // traceWarning(`Cannot connect to remote debugger at ${result.host}:${result.port} => ${exc}`); - // // We can't connect. Must be a firewall issue - // throw new JupyterDebuggerPortBlockedError(portNumber, Settings.RemoteDebuggerPortBegin, Settings.RemoteDebuggerPortEnd); - // } - - // return result; - } -} diff --git a/src/client/datascience/jupyter/jupyterDebuggerNotInstalledError.ts b/src/client/datascience/jupyter/jupyterDebuggerNotInstalledError.ts deleted file mode 100644 index 755d5f208ee1..000000000000 --- a/src/client/datascience/jupyter/jupyterDebuggerNotInstalledError.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; -import * as localize from '../../common/utils/localize'; - -export class JupyterDebuggerNotInstalledError extends Error { - constructor(debuggerPkg: string, message?: string) { - const errorMessage = message - ? message - : localize.DataScience.jupyterDebuggerNotInstalledError().format(debuggerPkg); - super(errorMessage); - } -} diff --git a/src/client/datascience/jupyter/jupyterDebuggerPortBlockedError.ts b/src/client/datascience/jupyter/jupyterDebuggerPortBlockedError.ts deleted file mode 100644 index 8ff97f941dda..000000000000 --- a/src/client/datascience/jupyter/jupyterDebuggerPortBlockedError.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; -import * as localize from '../../common/utils/localize'; - -export class JupyterDebuggerPortBlockedError extends Error { - constructor(portNumber: number, rangeBegin: number, rangeEnd: number) { - super( - portNumber === -1 - ? localize.DataScience.jupyterDebuggerPortBlockedSearchError().format( - rangeBegin.toString(), - rangeEnd.toString() - ) - : localize.DataScience.jupyterDebuggerPortBlockedError().format(portNumber.toString()) - ); - } -} diff --git a/src/client/datascience/jupyter/jupyterDebuggerPortNotAvailableError.ts b/src/client/datascience/jupyter/jupyterDebuggerPortNotAvailableError.ts deleted file mode 100644 index 1a33e9eb00ec..000000000000 --- a/src/client/datascience/jupyter/jupyterDebuggerPortNotAvailableError.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; -import * as localize from '../../common/utils/localize'; - -export class JupyterDebuggerPortNotAvailableError extends Error { - constructor(portNumber: number, rangeBegin: number, rangeEnd: number) { - super( - portNumber === -1 - ? localize.DataScience.jupyterDebuggerPortNotAvailableSearchError().format( - rangeBegin.toString(), - rangeEnd.toString() - ) - : localize.DataScience.jupyterDebuggerPortNotAvailableError().format(portNumber.toString()) - ); - } -} diff --git a/src/client/datascience/jupyter/jupyterDebuggerRemoteNotSupported.ts b/src/client/datascience/jupyter/jupyterDebuggerRemoteNotSupported.ts deleted file mode 100644 index 03c09ef8d1dd..000000000000 --- a/src/client/datascience/jupyter/jupyterDebuggerRemoteNotSupported.ts +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; -import * as localize from '../../common/utils/localize'; - -export class JupyterDebuggerRemoteNotSupported extends Error { - constructor() { - super(localize.DataScience.remoteDebuggerNotSupported()); - } -} diff --git a/src/client/datascience/jupyter/jupyterExecution.ts b/src/client/datascience/jupyter/jupyterExecution.ts deleted file mode 100644 index b0f3b495bf06..000000000000 --- a/src/client/datascience/jupyter/jupyterExecution.ts +++ /dev/null @@ -1,454 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as path from 'path'; -import { SemVer } from 'semver'; -import * as uuid from 'uuid/v4'; -import { CancellationToken, CancellationTokenSource, Event, EventEmitter, Uri } from 'vscode'; - -import { IApplicationShell, ILiveShareApi, IWorkspaceService } from '../../common/application/types'; -import { Cancellation } from '../../common/cancellation'; -import { WrappedError } from '../../common/errors/errorUtils'; -import { traceError, traceInfo } from '../../common/logger'; -import { IConfigurationService, IDisposableRegistry, IOutputChannel } from '../../common/types'; -import * as localize from '../../common/utils/localize'; -import { noop } from '../../common/utils/misc'; -import { StopWatch } from '../../common/utils/stopWatch'; -import { IInterpreterService } from '../../interpreter/contracts'; -import { IServiceContainer } from '../../ioc/types'; -import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; -import { JupyterSessionStartError } from '../baseJupyterSession'; -import { Commands, Identifiers, Telemetry } from '../constants'; -import { reportAction } from '../progress/decorator'; -import { ReportableAction } from '../progress/types'; -import { - IJupyterConnection, - IJupyterExecution, - IJupyterServerUri, - IJupyterSessionManagerFactory, - IJupyterSubCommandExecutionService, - IJupyterUriProviderRegistration, - INotebookServer, - INotebookServerLaunchInfo, - INotebookServerOptions, - JupyterServerUriHandle -} from '../types'; -import { JupyterSelfCertsError } from './jupyterSelfCertsError'; -import { createRemoteConnectionInfo, expandWorkingDir } from './jupyterUtils'; -import { JupyterWaitForIdleError } from './jupyterWaitForIdleError'; -import { getDisplayNameOrNameOfKernelConnection, kernelConnectionMetadataHasKernelSpec } from './kernels/helpers'; -import { KernelSelector } from './kernels/kernelSelector'; -import { KernelConnectionMetadata } from './kernels/types'; -import { NotebookStarter } from './notebookStarter'; - -const LocalHosts = ['localhost', '127.0.0.1', '::1']; - -export class JupyterExecutionBase implements IJupyterExecution { - private usablePythonInterpreter: PythonEnvironment | undefined; - private startedEmitter: EventEmitter = new EventEmitter(); - private disposed: boolean = false; - private readonly jupyterInterpreterService: IJupyterSubCommandExecutionService; - private readonly jupyterPickerRegistration: IJupyterUriProviderRegistration; - private uriToJupyterServerUri = new Map(); - private pendingTimeouts: (NodeJS.Timeout | number)[] = []; - - constructor( - _liveShare: ILiveShareApi, - private readonly interpreterService: IInterpreterService, - private readonly disposableRegistry: IDisposableRegistry, - private readonly workspace: IWorkspaceService, - private readonly configuration: IConfigurationService, - private readonly kernelSelector: KernelSelector, - private readonly notebookStarter: NotebookStarter, - private readonly appShell: IApplicationShell, - private readonly jupyterOutputChannel: IOutputChannel, - private readonly serviceContainer: IServiceContainer - ) { - this.jupyterInterpreterService = serviceContainer.get( - IJupyterSubCommandExecutionService - ); - this.jupyterPickerRegistration = serviceContainer.get( - IJupyterUriProviderRegistration - ); - this.disposableRegistry.push(this.interpreterService.onDidChangeInterpreter(() => this.onSettingsChanged())); - this.disposableRegistry.push(this); - - if (workspace) { - const disposable = workspace.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration('python.dataScience', undefined)) { - // When config changes happen, recreate our commands. - this.onSettingsChanged(); - } - if (e.affectsConfiguration('python.dataScience.jupyterServerURI', undefined)) { - // When server URI changes, clear our pending URI timeouts - this.clearTimeouts(); - } - }); - this.disposableRegistry.push(disposable); - } - } - - public get serverStarted(): Event { - return this.startedEmitter.event; - } - - public dispose(): Promise { - this.disposed = true; - this.clearTimeouts(); - return Promise.resolve(); - } - - public async refreshCommands(): Promise { - await this.jupyterInterpreterService.refreshCommands(); - } - - public isNotebookSupported(cancelToken?: CancellationToken): Promise { - // See if we can find the command notebook - return this.jupyterInterpreterService.isNotebookSupported(cancelToken); - } - - public async getNotebookError(): Promise { - return this.jupyterInterpreterService.getReasonForJupyterNotebookNotBeingSupported(); - } - - public async getUsableJupyterPython(cancelToken?: CancellationToken): Promise { - // Only try to compute this once. - if (!this.usablePythonInterpreter && !this.disposed) { - this.usablePythonInterpreter = await Cancellation.race( - () => this.jupyterInterpreterService.getSelectedInterpreter(cancelToken), - cancelToken - ); - } - return this.usablePythonInterpreter; - } - - @reportAction(ReportableAction.CheckingIfImportIsSupported) - public async getImportPackageVersion(cancelToken?: CancellationToken): Promise { - // See if we can find the command nbconvert - return this.jupyterInterpreterService.getExportPackageVersion(cancelToken); - } - - public isSpawnSupported(cancelToken?: CancellationToken): Promise { - // Supported if we can run a notebook - return this.isNotebookSupported(cancelToken); - } - - //tslint:disable:cyclomatic-complexity max-func-body-length - public connectToNotebookServer( - options?: INotebookServerOptions, - cancelToken?: CancellationToken - ): Promise { - // Return nothing if we cancel - // tslint:disable-next-line: max-func-body-length - return Cancellation.race(async () => { - let result: INotebookServer | undefined; - let connection: IJupyterConnection | undefined; - let kernelConnectionMetadata: KernelConnectionMetadata | undefined; - let kernelConnectionMetadataPromise: Promise = Promise.resolve< - KernelConnectionMetadata | undefined - >(undefined); - traceInfo(`Connecting to ${options ? options.purpose : 'unknown type of'} server`); - const allowUI = !options || options.allowUI(); - const kernelSpecCancelSource = new CancellationTokenSource(); - if (cancelToken) { - cancelToken.onCancellationRequested(() => { - kernelSpecCancelSource.cancel(); - }); - } - const isLocalConnection = !options || !options.uri; - - if (isLocalConnection) { - // Get hold of the kernelspec and corresponding (matching) interpreter that'll be used as the spec. - // We can do this in parallel, while starting the server (faster). - traceInfo(`Getting kernel specs for ${options ? options.purpose : 'unknown type of'} server`); - kernelConnectionMetadataPromise = this.kernelSelector.getPreferredKernelForLocalConnection( - undefined, - 'jupyter', - undefined, - options?.metadata, - !allowUI, - kernelSpecCancelSource.token - ); - } - - // Try to connect to our jupyter process. Check our setting for the number of tries - let tryCount = 1; - const maxTries = this.configuration.getSettings(undefined).datascience.jupyterLaunchRetries; - const stopWatch = new StopWatch(); - while (tryCount <= maxTries && !this.disposed) { - try { - // Start or connect to the process - [connection, kernelConnectionMetadata] = await Promise.all([ - this.startOrConnect(options, cancelToken), - kernelConnectionMetadataPromise - ]); - - if (!connection.localLaunch && LocalHosts.includes(connection.hostName.toLowerCase())) { - sendTelemetryEvent(Telemetry.ConnectRemoteJupyterViaLocalHost); - } - // Create a server tha t we will then attempt to connect to. - result = this.serviceContainer.get(INotebookServer); - - // In a remote non quest situation, figure out a kernel spec too. - if ( - (!kernelConnectionMetadata || - !kernelConnectionMetadataHasKernelSpec(kernelConnectionMetadata)) && - connection && - !options?.skipSearchingForKernel - ) { - const sessionManagerFactory = this.serviceContainer.get( - IJupyterSessionManagerFactory - ); - const sessionManager = await sessionManagerFactory.create(connection); - kernelConnectionMetadata = await this.kernelSelector.getPreferredKernelForRemoteConnection( - undefined, - sessionManager, - options?.metadata, - cancelToken - ); - await sessionManager.dispose(); - } - - // Populate the launch info that we are starting our server with - const launchInfo: INotebookServerLaunchInfo = { - connectionInfo: connection!, - kernelConnectionMetadata, - workingDir: options ? options.workingDir : undefined, - uri: options ? options.uri : undefined, - purpose: options ? options.purpose : uuid() - }; - - // tslint:disable-next-line: no-constant-condition - while (true) { - try { - traceInfo( - `Connecting to process for ${options ? options.purpose : 'unknown type of'} server` - ); - await result.connect(launchInfo, cancelToken); - traceInfo( - `Connection complete for ${options ? options.purpose : 'unknown type of'} server` - ); - break; - } catch (ex) { - traceError('Failed to connect to server', ex); - if (ex instanceof JupyterSessionStartError && isLocalConnection && allowUI) { - // Keep retrying, until it works or user cancels. - // Sometimes if a bad kernel is selected, starting a session can fail. - // In such cases we need to let the user know about this and prompt them to select another kernel. - const message = localize.DataScience.sessionStartFailedWithKernel().format( - getDisplayNameOrNameOfKernelConnection(launchInfo.kernelConnectionMetadata), - Commands.ViewJupyterOutput - ); - const selectKernel = localize.DataScience.selectDifferentKernel(); - const cancel = localize.Common.cancel(); - const selection = await this.appShell.showErrorMessage(message, selectKernel, cancel); - if (selection === selectKernel) { - const sessionManagerFactory = this.serviceContainer.get< - IJupyterSessionManagerFactory - >(IJupyterSessionManagerFactory); - const sessionManager = await sessionManagerFactory.create(connection); - const kernelInterpreter = await this.kernelSelector.selectLocalKernel( - undefined, - 'jupyter', - new StopWatch(), - sessionManager, - cancelToken, - getDisplayNameOrNameOfKernelConnection(launchInfo.kernelConnectionMetadata) - ); - if (kernelInterpreter) { - launchInfo.kernelConnectionMetadata = kernelInterpreter; - continue; - } - } - } - throw ex; - } - } - - sendTelemetryEvent( - isLocalConnection ? Telemetry.ConnectLocalJupyter : Telemetry.ConnectRemoteJupyter - ); - return result; - } catch (err) { - // Cleanup after ourselves. server may be running partially. - if (result) { - traceInfo(`Killing server because of error ${err}`); - await result.dispose(); - } - if (err instanceof JupyterWaitForIdleError && tryCount < maxTries) { - // Special case. This sometimes happens where jupyter doesn't ever connect. Cleanup after - // ourselves and propagate the failure outwards. - traceInfo('Retry because of wait for idle problem.'); - sendTelemetryEvent(Telemetry.SessionIdleTimeout); - - // Close existing connection. - connection?.dispose(); - tryCount += 1; - } else if (connection) { - kernelSpecCancelSource.cancel(); - - // If this is occurring during shutdown, don't worry about it. - if (this.disposed) { - return undefined; - } - - // Something else went wrong - if (!isLocalConnection) { - sendTelemetryEvent(Telemetry.ConnectRemoteFailedJupyter); - - // Check for the self signed certs error specifically - if (err.message.indexOf('reason: self signed certificate') >= 0) { - sendTelemetryEvent(Telemetry.ConnectRemoteSelfCertFailedJupyter); - throw new JupyterSelfCertsError(connection.baseUrl); - } else { - throw new WrappedError( - localize.DataScience.jupyterNotebookRemoteConnectFailed().format( - connection.baseUrl, - err - ), - err - ); - } - } else { - sendTelemetryEvent(Telemetry.ConnectFailedJupyter); - throw new WrappedError( - localize.DataScience.jupyterNotebookConnectFailed().format(connection.baseUrl, err), - err - ); - } - } else { - kernelSpecCancelSource.cancel(); - throw err; - } - } - } - - // If we're here, then starting jupyter timeout. - // Kill any existing connections. - connection?.dispose(); - sendTelemetryEvent(Telemetry.JupyterStartTimeout, stopWatch.elapsedTime, { - timeout: stopWatch.elapsedTime - }); - if (allowUI) { - this.appShell - .showErrorMessage(localize.DataScience.jupyterStartTimedout(), localize.Common.openOutputPanel()) - .then((selection) => { - if (selection === localize.Common.openOutputPanel()) { - this.jupyterOutputChannel.show(); - } - }, noop); - } - }, cancelToken); - } - - public async spawnNotebook(file: string): Promise { - return this.jupyterInterpreterService.openNotebook(file); - } - - public async importNotebook(file: Uri, template: string | undefined): Promise { - return this.jupyterInterpreterService.exportNotebookToPython(file, template); - } - - public getServer(_options?: INotebookServerOptions): Promise { - // This is cached at the host or guest level - return Promise.resolve(undefined); - } - - private async startOrConnect( - options?: INotebookServerOptions, - cancelToken?: CancellationToken - ): Promise { - // If our uri is undefined or if it's set to local launch we need to launch a server locally - if (!options || !options.uri) { - // If that works, then attempt to start the server - traceInfo(`Launching ${options ? options.purpose : 'unknown type of'} server`); - const useDefaultConfig = !options || options.skipUsingDefaultConfig ? false : true; - - // Expand the working directory. Create a dummy launching file in the root path (so we expand correctly) - const workingDirectory = expandWorkingDir( - options?.workingDir, - this.workspace.rootPath ? path.join(this.workspace.rootPath, `${uuid()}.txt`) : undefined, - this.workspace - ); - - const connection = await this.startNotebookServer( - useDefaultConfig, - this.configuration.getSettings(undefined).datascience.jupyterCommandLineArguments, - workingDirectory, - cancelToken - ); - if (connection) { - return connection; - } else { - // Throw a cancellation error if we were canceled. - Cancellation.throwIfCanceled(cancelToken); - - // Otherwise we can't connect - throw new Error(localize.DataScience.jupyterNotebookFailure().format('')); - } - } else { - // Prepare our map of server URIs - await this.updateServerUri(options.uri); - - // If we have a URI spec up a connection info for it - return createRemoteConnectionInfo(options.uri, this.getServerUri.bind(this)); - } - } - - // tslint:disable-next-line: max-func-body-length - @captureTelemetry(Telemetry.StartJupyter) - private async startNotebookServer( - useDefaultConfig: boolean, - customCommandLine: string[], - workingDirectory: string, - cancelToken?: CancellationToken - ): Promise { - return this.notebookStarter.start(useDefaultConfig, customCommandLine, workingDirectory, cancelToken); - } - private onSettingsChanged() { - // Clear our usableJupyterInterpreter so that we recompute our values - this.usablePythonInterpreter = undefined; - } - - private extractJupyterServerHandleAndId(uri: string): { handle: JupyterServerUriHandle; id: string } | undefined { - const url: URL = new URL(uri); - - // Id has to be there too. - const id = url.searchParams.get(Identifiers.REMOTE_URI_ID_PARAM); - const uriHandle = url.searchParams.get(Identifiers.REMOTE_URI_HANDLE_PARAM); - return id && uriHandle ? { handle: uriHandle, id } : undefined; - } - - private clearTimeouts() { - // tslint:disable-next-line: no-any - this.pendingTimeouts.forEach((t) => clearTimeout(t as any)); - this.pendingTimeouts = []; - } - - private getServerUri(uri: string): IJupyterServerUri | undefined { - const idAndHandle = this.extractJupyterServerHandleAndId(uri); - if (idAndHandle) { - return this.uriToJupyterServerUri.get(uri); - } - } - - private async updateServerUri(uri: string): Promise { - const idAndHandle = this.extractJupyterServerHandleAndId(uri); - if (idAndHandle) { - const serverUri = await this.jupyterPickerRegistration.getJupyterServerUri( - idAndHandle.id, - idAndHandle.handle - ); - this.uriToJupyterServerUri.set(uri, serverUri); - // See if there's an expiration date - if (serverUri.expiration) { - const timeoutInMS = serverUri.expiration.getTime() - Date.now(); - // Week seems long enough (in case the expiration is ridiculous) - if (timeoutInMS > 0 && timeoutInMS < 604800000) { - this.pendingTimeouts.push(setTimeout(() => this.updateServerUri(uri).ignoreErrors(), timeoutInMS)); - } - } - } - } -} diff --git a/src/client/datascience/jupyter/jupyterExecutionFactory.ts b/src/client/datascience/jupyter/jupyterExecutionFactory.ts deleted file mode 100644 index a2d2b636e92b..000000000000 --- a/src/client/datascience/jupyter/jupyterExecutionFactory.ts +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { inject, injectable, named } from 'inversify'; -import { SemVer } from 'semver'; -import { CancellationToken, Event, EventEmitter, Uri } from 'vscode'; - -import { IApplicationShell, ILiveShareApi, IWorkspaceService } from '../../common/application/types'; - -import { - IAsyncDisposable, - IAsyncDisposableRegistry, - IConfigurationService, - IDisposableRegistry, - IOutputChannel -} from '../../common/types'; -import { IInterpreterService } from '../../interpreter/contracts'; -import { IServiceContainer } from '../../ioc/types'; -import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { JUPYTER_OUTPUT_CHANNEL } from '../constants'; -import { IDataScienceFileSystem, IJupyterExecution, INotebookServer, INotebookServerOptions } from '../types'; -import { KernelSelector } from './kernels/kernelSelector'; -import { GuestJupyterExecution } from './liveshare/guestJupyterExecution'; -import { HostJupyterExecution } from './liveshare/hostJupyterExecution'; -import { IRoleBasedObject, RoleBasedFactory } from './liveshare/roleBasedFactory'; -import { NotebookStarter } from './notebookStarter'; - -interface IJupyterExecutionInterface extends IRoleBasedObject, IJupyterExecution {} - -// tslint:disable:callable-types -type JupyterExecutionClassType = { - new ( - liveShare: ILiveShareApi, - interpreterService: IInterpreterService, - disposableRegistry: IDisposableRegistry, - asyncRegistry: IAsyncDisposableRegistry, - fileSystem: IDataScienceFileSystem, - workspace: IWorkspaceService, - configuration: IConfigurationService, - kernelSelector: KernelSelector, - notebookStarter: NotebookStarter, - appShell: IApplicationShell, - jupyterOutputChannel: IOutputChannel, - serviceContainer: IServiceContainer - ): IJupyterExecutionInterface; -}; -// tslint:enable:callable-types - -@injectable() -export class JupyterExecutionFactory implements IJupyterExecution, IAsyncDisposable { - private executionFactory: RoleBasedFactory; - private sessionChangedEventEmitter: EventEmitter = new EventEmitter(); - private serverStartedEventEmitter: EventEmitter = new EventEmitter< - INotebookServerOptions | undefined - >(); - - constructor( - @inject(ILiveShareApi) liveShare: ILiveShareApi, - @inject(IInterpreterService) interpreterService: IInterpreterService, - @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, - @inject(IAsyncDisposableRegistry) asyncRegistry: IAsyncDisposableRegistry, - @inject(IDataScienceFileSystem) fs: IDataScienceFileSystem, - @inject(IWorkspaceService) workspace: IWorkspaceService, - @inject(IConfigurationService) configuration: IConfigurationService, - @inject(KernelSelector) kernelSelector: KernelSelector, - @inject(NotebookStarter) notebookStarter: NotebookStarter, - @inject(IApplicationShell) appShell: IApplicationShell, - @inject(IOutputChannel) @named(JUPYTER_OUTPUT_CHANNEL) jupyterOutputChannel: IOutputChannel, - @inject(IServiceContainer) serviceContainer: IServiceContainer - ) { - asyncRegistry.push(this); - this.executionFactory = new RoleBasedFactory( - liveShare, - HostJupyterExecution, - GuestJupyterExecution, - liveShare, - interpreterService, - disposableRegistry, - asyncRegistry, - fs, - workspace, - configuration, - kernelSelector, - notebookStarter, - appShell, - jupyterOutputChannel, - serviceContainer - ); - this.executionFactory.sessionChanged(() => this.onSessionChanged()); - } - - public get sessionChanged(): Event { - return this.sessionChangedEventEmitter.event; - } - - public get serverStarted(): Event { - return this.serverStartedEventEmitter.event; - } - - public async dispose(): Promise { - // Dispose of our execution object - const execution = await this.executionFactory.get(); - return execution.dispose(); - } - - public async refreshCommands(): Promise { - const execution = await this.executionFactory.get(); - return execution.refreshCommands(); - } - - public async isNotebookSupported(cancelToken?: CancellationToken): Promise { - const execution = await this.executionFactory.get(); - return execution.isNotebookSupported(cancelToken); - } - - public async getNotebookError(): Promise { - const execution = await this.executionFactory.get(); - return execution.getNotebookError(); - } - - public async getImportPackageVersion(cancelToken?: CancellationToken): Promise { - const execution = await this.executionFactory.get(); - return execution.getImportPackageVersion(cancelToken); - } - public async isSpawnSupported(cancelToken?: CancellationToken): Promise { - const execution = await this.executionFactory.get(); - return execution.isSpawnSupported(cancelToken); - } - public async connectToNotebookServer( - options?: INotebookServerOptions, - cancelToken?: CancellationToken - ): Promise { - const execution = await this.executionFactory.get(); - const server = await execution.connectToNotebookServer(options, cancelToken); - if (server) { - this.serverStartedEventEmitter.fire(options); - } - return server; - } - public async spawnNotebook(file: string): Promise { - const execution = await this.executionFactory.get(); - return execution.spawnNotebook(file); - } - public async importNotebook(file: Uri, template: string | undefined): Promise { - const execution = await this.executionFactory.get(); - return execution.importNotebook(file, template); - } - public async getUsableJupyterPython(cancelToken?: CancellationToken): Promise { - const execution = await this.executionFactory.get(); - return execution.getUsableJupyterPython(cancelToken); - } - public async getServer(options?: INotebookServerOptions): Promise { - const execution = await this.executionFactory.get(); - return execution.getServer(options); - } - - private onSessionChanged() { - this.sessionChangedEventEmitter.fire(); - } -} diff --git a/src/client/datascience/jupyter/jupyterExporter.ts b/src/client/datascience/jupyter/jupyterExporter.ts deleted file mode 100644 index c867685a4856..000000000000 --- a/src/client/datascience/jupyter/jupyterExporter.ts +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import type { nbformat } from '@jupyterlab/coreutils'; -import { inject, injectable } from 'inversify'; -import * as os from 'os'; -import * as path from 'path'; -import * as uuid from 'uuid/v4'; - -import { Uri } from 'vscode'; -import { concatMultilineString } from '../../../datascience-ui/common'; -import { createCodeCell } from '../../../datascience-ui/common/cellFactory'; -import { IApplicationShell, IWorkspaceService } from '../../common/application/types'; -import { traceError } from '../../common/logger'; -import { IPlatformService } from '../../common/platform/types'; -import { IConfigurationService } from '../../common/types'; -import * as localize from '../../common/utils/localize'; -import { noop } from '../../common/utils/misc'; -import { CellMatcher } from '../cellMatcher'; -import { pruneCell } from '../common'; -import { CodeSnippets, Identifiers } from '../constants'; -import { - CellState, - ICell, - IDataScienceErrorHandler, - IDataScienceFileSystem, - IJupyterExecution, - INotebookEditorProvider, - INotebookExporter, - ITrustService -} from '../types'; - -@injectable() -export class JupyterExporter implements INotebookExporter { - constructor( - @inject(IJupyterExecution) private jupyterExecution: IJupyterExecution, - @inject(IWorkspaceService) private workspaceService: IWorkspaceService, - @inject(IConfigurationService) private configService: IConfigurationService, - @inject(IDataScienceFileSystem) private fileSystem: IDataScienceFileSystem, - @inject(IPlatformService) private readonly platform: IPlatformService, - @inject(IApplicationShell) private readonly applicationShell: IApplicationShell, - @inject(INotebookEditorProvider) protected ipynbProvider: INotebookEditorProvider, - @inject(IDataScienceErrorHandler) protected errorHandler: IDataScienceErrorHandler, - @inject(ITrustService) private readonly trustService: ITrustService - ) {} - - public dispose() { - noop(); - } - - public async exportToFile(cells: ICell[], file: string, showOpenPrompt: boolean = true): Promise { - let directoryChange; - const settings = this.configService.getSettings(); - if (settings.datascience.changeDirOnImportExport) { - directoryChange = file; - } - - const notebook = await this.translateToNotebook(cells, directoryChange); - - try { - // tslint:disable-next-line: no-any - const contents = JSON.stringify(notebook); - await this.trustService.trustNotebook(Uri.file(file), contents); - await this.fileSystem.writeFile(Uri.file(file), contents); - if (!showOpenPrompt) { - return; - } - const openQuestion1 = localize.DataScience.exportOpenQuestion1(); - const openQuestion2 = (await this.jupyterExecution.isSpawnSupported()) - ? localize.DataScience.exportOpenQuestion() - : undefined; - this.showInformationMessage( - localize.DataScience.exportDialogComplete().format(file), - openQuestion1, - openQuestion2 - ).then(async (str: string | undefined) => { - try { - if (str === openQuestion2 && openQuestion2) { - // If the user wants to, open the notebook they just generated. - await this.jupyterExecution.spawnNotebook(file); - } else if (str === openQuestion1) { - await this.ipynbProvider.open(Uri.file(file)); - } - } catch (e) { - await this.errorHandler.handleError(e); - } - }); - } catch (exc) { - traceError('Error in exporting notebook file'); - this.applicationShell.showInformationMessage(localize.DataScience.exportDialogFailed().format(exc)); - } - } - public async translateToNotebook( - cells: ICell[], - changeDirectory?: string, - kernelSpec?: nbformat.IKernelspecMetadata - ): Promise { - // If requested, add in a change directory cell to fix relative paths - if (changeDirectory && this.configService.getSettings().datascience.changeDirOnImportExport) { - cells = await this.addDirectoryChangeCell(cells, changeDirectory); - } - - const pythonNumber = await this.extractPythonMainVersion(); - - // Use this to build our metadata object - const metadata: nbformat.INotebookMetadata = { - language_info: { - codemirror_mode: { - name: 'ipython', - version: pythonNumber - }, - file_extension: '.py', - mimetype: 'text/x-python', - name: 'python', - nbconvert_exporter: 'python', - pygments_lexer: `ipython${pythonNumber}`, - version: pythonNumber - }, - orig_nbformat: 2, - // tslint:disable-next-line: no-any - kernelspec: kernelSpec as any - }; - - // Create an object for matching cell definitions - const matcher = new CellMatcher(this.configService.getSettings().datascience); - - // Combine this into a JSON object - return { - cells: this.pruneCells(cells, matcher), - nbformat: 4, - nbformat_minor: 2, - metadata: metadata - }; - } - - private showInformationMessage( - message: string, - question1: string, - question2?: string - ): Thenable { - if (question2) { - return this.applicationShell.showInformationMessage(message, question1, question2); - } else { - return this.applicationShell.showInformationMessage(message, question1); - } - } - - // For exporting, put in a cell that will change the working directory back to the workspace directory so relative data paths will load correctly - private addDirectoryChangeCell = async (cells: ICell[], file: string): Promise => { - const changeDirectory = await this.calculateDirectoryChange(file, cells); - - if (changeDirectory) { - const exportChangeDirectory = CodeSnippets.ChangeDirectory.join(os.EOL).format( - localize.DataScience.exportChangeDirectoryComment(), - CodeSnippets.ChangeDirectoryCommentIdentifier, - changeDirectory - ); - - const cell: ICell = { - data: createCodeCell(exportChangeDirectory), - id: uuid(), - file: Identifiers.EmptyFileName, - line: 0, - state: CellState.finished - }; - - return [cell, ...cells]; - } else { - return cells; - } - }; - - // When we export we want to our change directory back to the first real file that we saw run from any workspace folder - private firstWorkspaceFolder = async (cells: ICell[]): Promise => { - for (const cell of cells) { - const filename = cell.file; - - // First check that this is an absolute file that exists (we add in temp files to run system cell) - if (path.isAbsolute(filename) && (await this.fileSystem.localFileExists(filename))) { - // We've already check that workspace folders above - for (const folder of this.workspaceService.workspaceFolders!) { - if (filename.toLowerCase().startsWith(folder.uri.fsPath.toLowerCase())) { - return folder.uri.fsPath; - } - } - } - } - - return undefined; - }; - - private calculateDirectoryChange = async (notebookFile: string, cells: ICell[]): Promise => { - // Make sure we don't already have a cell with a ChangeDirectory comment in it. - let directoryChange: string | undefined; - const haveChangeAlready = cells.find((c) => - concatMultilineString(c.data.source).includes(CodeSnippets.ChangeDirectoryCommentIdentifier) - ); - if (!haveChangeAlready) { - const notebookFilePath = path.dirname(notebookFile); - // First see if we have a workspace open, this only works if we have a workspace root to be relative to - if (this.workspaceService.hasWorkspaceFolders) { - const workspacePath = await this.firstWorkspaceFolder(cells); - - // Make sure that we have everything that we need here - if ( - workspacePath && - path.isAbsolute(workspacePath) && - notebookFilePath && - path.isAbsolute(notebookFilePath) - ) { - directoryChange = path.relative(notebookFilePath, workspacePath); - } - } - } - - // If path.relative can't calculate a relative path, then it just returns the full second path - // so check here, we only want this if we were able to calculate a relative path, no network shares or drives - if (directoryChange && !path.isAbsolute(directoryChange)) { - // Escape windows path chars so they end up in the source escaped - if (this.platform.isWindows) { - directoryChange = directoryChange.replace('\\', '\\\\'); - } - - return directoryChange; - } else { - return undefined; - } - }; - - private pruneCells = (cells: ICell[], cellMatcher: CellMatcher): nbformat.IBaseCell[] => { - // First filter out sys info cells. Jupyter doesn't understand these - const filtered = cells.filter((c) => c.data.cell_type !== 'messages'); - - // Then prune each cell down to just the cell data. - return filtered.map((c) => this.pruneCell(c, cellMatcher)); - }; - - private pruneCell = (cell: ICell, cellMatcher: CellMatcher): nbformat.IBaseCell => { - // Prune with the common pruning function first. - const copy = pruneCell({ ...cell.data }); - - // Remove the #%% of the top of the source if there is any. We don't need - // this to end up in the exported ipynb file. - copy.source = this.pruneSource(cell.data.source, cellMatcher); - return copy; - }; - - private pruneSource = (source: nbformat.MultilineString, cellMatcher: CellMatcher): nbformat.MultilineString => { - // Remove the comments on the top if there. - if (Array.isArray(source) && source.length > 0) { - if (cellMatcher.isCell(source[0])) { - return source.slice(1); - } - } else { - const array = source - .toString() - .split('\n') - .map((s) => `${s}\n`); - if (array.length > 0 && cellMatcher.isCell(array[0])) { - return array.slice(1); - } - } - - return source; - }; - - private extractPythonMainVersion = async (): Promise => { - // Use the active interpreter - const usableInterpreter = await this.jupyterExecution.getUsableJupyterPython(); - return usableInterpreter && usableInterpreter.version ? usableInterpreter.version.major : 3; - }; -} diff --git a/src/client/datascience/jupyter/jupyterImporter.ts b/src/client/datascience/jupyter/jupyterImporter.ts deleted file mode 100644 index eaecc62e2d9e..000000000000 --- a/src/client/datascience/jupyter/jupyterImporter.ts +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; - -import { inject, injectable } from 'inversify'; -import * as os from 'os'; -import * as path from 'path'; - -import { Uri } from 'vscode'; -import { IWorkspaceService } from '../../common/application/types'; -import { traceError } from '../../common/logger'; -import { IPlatformService } from '../../common/platform/types'; -import { IConfigurationService, IDisposableRegistry } from '../../common/types'; -import * as localize from '../../common/utils/localize'; -import { noop } from '../../common/utils/misc'; -import { CodeSnippets, Identifiers } from '../constants'; -import { - IDataScienceFileSystem, - IJupyterExecution, - IJupyterInterpreterDependencyManager, - INotebookImporter -} from '../types'; - -@injectable() -export class JupyterImporter implements INotebookImporter { - public isDisposed: boolean = false; - // Template that changes markdown cells to have # %% [markdown] in the comments - private readonly nbconvertBaseTemplateFormat = - // tslint:disable-next-line:no-multiline-string - `{%- extends '{0}' -%} -{% block codecell %} -{1} -{{ super() }} -{% endblock codecell %} -{% block in_prompt %}{% endblock in_prompt %} -{% block input %}{{ cell.source | ipython2python }}{% endblock input %} -{% block markdowncell scoped %}{0} [markdown] -{{ cell.source | comment_lines }} -{% endblock markdowncell %}`; - private readonly nbconvert5Null = 'null.tpl'; - private readonly nbconvert6Null = 'base/null.j2'; - private template5Promise?: Promise; - private template6Promise?: Promise; - - constructor( - @inject(IDataScienceFileSystem) private fs: IDataScienceFileSystem, - @inject(IDisposableRegistry) private disposableRegistry: IDisposableRegistry, - @inject(IConfigurationService) private configuration: IConfigurationService, - @inject(IJupyterExecution) private jupyterExecution: IJupyterExecution, - @inject(IWorkspaceService) private workspaceService: IWorkspaceService, - @inject(IPlatformService) private readonly platform: IPlatformService, - @inject(IJupyterInterpreterDependencyManager) - private readonly dependencyManager: IJupyterInterpreterDependencyManager - ) {} - - public async importFromFile(sourceFile: Uri): Promise { - // If the user has requested it, add a cd command to the imported file so that relative paths still work - const settings = this.configuration.getSettings(); - let directoryChange: string | undefined; - if (settings.datascience.changeDirOnImportExport) { - directoryChange = await this.calculateDirectoryChange(sourceFile); - } - - // Before we try the import, see if we don't support it, if we don't give a chance to install dependencies - if (!(await this.jupyterExecution.getImportPackageVersion())) { - await this.dependencyManager.installMissingDependencies(); - } - - const nbConvertVersion = await this.jupyterExecution.getImportPackageVersion(); - // Use the jupyter nbconvert functionality to turn the notebook into a python file - if (nbConvertVersion) { - // nbconvert 5 and 6 use a different base template file - // Create and select the correct one - let template: string | undefined; - if (nbConvertVersion.major >= 6) { - if (!this.template6Promise) { - this.template6Promise = this.createTemplateFile(true); - } - - template = await this.template6Promise; - } else { - if (!this.template5Promise) { - this.template5Promise = this.createTemplateFile(false); - } - - template = await this.template5Promise; - } - - let fileOutput: string = await this.jupyterExecution.importNotebook(sourceFile, template); - if (fileOutput.includes('get_ipython()')) { - fileOutput = this.addIPythonImport(fileOutput); - } - if (directoryChange) { - fileOutput = this.addDirectoryChange(fileOutput, directoryChange); - } - return this.addInstructionComments(fileOutput); - } - - throw new Error(localize.DataScience.jupyterNbConvertNotSupported()); - } - - public dispose = () => { - this.isDisposed = true; - }; - - private addInstructionComments = (pythonOutput: string): string => { - const comments = localize.DataScience.instructionComments().format(this.defaultCellMarker); - return comments.concat(pythonOutput); - }; - - private get defaultCellMarker(): string { - return this.configuration.getSettings().datascience.defaultCellMarker || Identifiers.DefaultCodeCellMarker; - } - - private addIPythonImport = (pythonOutput: string): string => { - return CodeSnippets.ImportIPython.format(this.defaultCellMarker, pythonOutput); - }; - - private addDirectoryChange = (pythonOutput: string, directoryChange: string): string => { - const newCode = CodeSnippets.ChangeDirectory.join(os.EOL).format( - localize.DataScience.importChangeDirectoryComment().format(this.defaultCellMarker), - CodeSnippets.ChangeDirectoryCommentIdentifier, - directoryChange - ); - return newCode.concat(pythonOutput); - }; - - // When importing a file, calculate if we can create a %cd so that the relative paths work - private async calculateDirectoryChange(notebookFile: Uri): Promise { - let directoryChange: string | undefined; - try { - // Make sure we don't already have an import/export comment in the file - const contents = await this.fs.readFile(notebookFile); - const haveChangeAlready = contents.includes(CodeSnippets.ChangeDirectoryCommentIdentifier); - - if (!haveChangeAlready) { - const notebookFilePath = path.dirname(notebookFile.fsPath); - // First see if we have a workspace open, this only works if we have a workspace root to be relative to - if (this.workspaceService.hasWorkspaceFolders) { - const workspacePath = this.workspaceService.workspaceFolders![0].uri.fsPath; - - // Make sure that we have everything that we need here - if ( - workspacePath && - path.isAbsolute(workspacePath) && - notebookFilePath && - path.isAbsolute(notebookFilePath) - ) { - directoryChange = path.relative(workspacePath, notebookFilePath); - } - } - } - - // If path.relative can't calculate a relative path, then it just returns the full second path - // so check here, we only want this if we were able to calculate a relative path, no network shares or drives - if (directoryChange && !path.isAbsolute(directoryChange)) { - // Escape windows path chars so they end up in the source escaped - if (this.platform.isWindows) { - directoryChange = directoryChange.replace('\\', '\\\\'); - } - - return directoryChange; - } else { - return undefined; - } - } catch (e) { - traceError(e); - } - } - - private async createTemplateFile(nbconvert6: boolean): Promise { - // Create a temp file on disk - const file = await this.fs.createTemporaryLocalFile('.tpl'); - - // Write our template into it - if (file) { - try { - // Save this file into our disposables so the temp file goes away - this.disposableRegistry.push(file); - await this.fs.appendLocalFile( - file.filePath, - this.nbconvertBaseTemplateFormat.format( - nbconvert6 ? this.nbconvert6Null : this.nbconvert5Null, - this.defaultCellMarker - ) - ); - - // Now we should have a template that will convert - return file.filePath; - } catch { - noop(); - } - } - } -} diff --git a/src/client/datascience/jupyter/jupyterInstallError.ts b/src/client/datascience/jupyter/jupyterInstallError.ts deleted file mode 100644 index 6af845382351..000000000000 --- a/src/client/datascience/jupyter/jupyterInstallError.ts +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; -import { HelpLinks } from '../constants'; - -export class JupyterInstallError extends Error { - public action: string; - public actionTitle: string; - - constructor(message: string, actionFormatString: string) { - super(message); - this.action = HelpLinks.PythonInteractiveHelpLink; - this.actionTitle = actionFormatString.format(HelpLinks.PythonInteractiveHelpLink); - } -} diff --git a/src/client/datascience/jupyter/jupyterInterruptError.ts b/src/client/datascience/jupyter/jupyterInterruptError.ts deleted file mode 100644 index 172899250d3d..000000000000 --- a/src/client/datascience/jupyter/jupyterInterruptError.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -export class JupyterInterruptError extends Error { - constructor(message: string) { - super(message); - } -} diff --git a/src/client/datascience/jupyter/jupyterInvalidKernelError.ts b/src/client/datascience/jupyter/jupyterInvalidKernelError.ts deleted file mode 100644 index 1dc6735a8e90..000000000000 --- a/src/client/datascience/jupyter/jupyterInvalidKernelError.ts +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import * as localize from '../../common/utils/localize'; -import { sendTelemetryEvent } from '../../telemetry'; -import { Telemetry } from '../constants'; -import { getDisplayNameOrNameOfKernelConnection } from './kernels/helpers'; -import { KernelConnectionMetadata } from './kernels/types'; - -export class JupyterInvalidKernelError extends Error { - constructor(public readonly kernelConnectionMetadata: KernelConnectionMetadata | undefined) { - super( - localize.DataScience.kernelInvalid().format( - getDisplayNameOrNameOfKernelConnection(kernelConnectionMetadata) - ) - ); - sendTelemetryEvent(Telemetry.KernelInvalid); - } -} diff --git a/src/client/datascience/jupyter/jupyterNotebook.ts b/src/client/datascience/jupyter/jupyterNotebook.ts deleted file mode 100644 index c5479a0f2521..000000000000 --- a/src/client/datascience/jupyter/jupyterNotebook.ts +++ /dev/null @@ -1,1429 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import type { nbformat } from '@jupyterlab/coreutils'; -import type { Kernel, KernelMessage } from '@jupyterlab/services'; -import type { JSONObject } from '@phosphor/coreutils'; -import { Observable } from 'rxjs/Observable'; -import { Subscriber } from 'rxjs/Subscriber'; -import * as uuid from 'uuid/v4'; -import { Disposable, Event, EventEmitter, Uri } from 'vscode'; -import { CancellationToken } from 'vscode-jsonrpc'; -import { ServerStatus } from '../../../datascience-ui/interactive-common/mainState'; -import { IApplicationShell, ILiveShareApi, IWorkspaceService } from '../../common/application/types'; -import { CancellationError, createPromiseFromCancellation } from '../../common/cancellation'; -import '../../common/extensions'; -import { traceError, traceInfo, traceWarning } from '../../common/logger'; - -import { IConfigurationService, IDisposableRegistry, Resource } from '../../common/types'; -import { createDeferred, Deferred, waitForPromise } from '../../common/utils/async'; -import * as localize from '../../common/utils/localize'; -import { noop } from '../../common/utils/misc'; -import { StopWatch } from '../../common/utils/stopWatch'; -import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; -import { generateCells } from '../cellFactory'; -import { CellMatcher } from '../cellMatcher'; -import { CodeSnippets, Identifiers, Telemetry } from '../constants'; -import { - CellState, - ICell, - IDataScienceFileSystem, - IJupyterSession, - INotebook, - INotebookCompletion, - INotebookExecutionInfo, - INotebookExecutionLogger, - InterruptResult, - KernelSocketInformation -} from '../types'; -import { expandWorkingDir } from './jupyterUtils'; -import { KernelConnectionMetadata } from './kernels/types'; - -// tslint:disable-next-line: no-require-imports -import cloneDeep = require('lodash/cloneDeep'); -import { concatMultilineString, formatStreamText, splitMultilineString } from '../../../datascience-ui/common'; -import { RefBool } from '../../common/refBool'; -import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { getInterpreterFromKernelConnectionMetadata, isPythonKernelConnection } from './kernels/helpers'; - -class CellSubscriber { - public get startTime(): number { - return this._startTime; - } - - public get onCanceled(): Event { - return this.canceledEvent.event; - } - - public get promise(): Promise { - return this.deferred.promise; - } - - public get cell(): ICell { - return this.cellRef; - } - public executionState?: Kernel.Status; - private deferred: Deferred = createDeferred(); - private cellRef: ICell; - private subscriber: Subscriber; - private promiseComplete: (self: CellSubscriber) => void; - private canceledEvent: EventEmitter = new EventEmitter(); - private _startTime: number; - - constructor(cell: ICell, subscriber: Subscriber, promiseComplete: (self: CellSubscriber) => void) { - this.cellRef = cell; - this.subscriber = subscriber; - this.promiseComplete = promiseComplete; - this._startTime = Date.now(); - } - - public isValid(sessionStartTime: number | undefined) { - return sessionStartTime && this.startTime >= sessionStartTime; - } - - public next(sessionStartTime: number | undefined) { - // Tell the subscriber first - if (this.isValid(sessionStartTime)) { - this.subscriber.next(this.cellRef); - } - } - - // tslint:disable-next-line:no-any - public error(sessionStartTime: number | undefined, err: any) { - if (this.isValid(sessionStartTime)) { - this.subscriber.error(err); - } - } - - public complete(sessionStartTime: number | undefined) { - if (this.isValid(sessionStartTime)) { - if (this.cellRef.state !== CellState.error) { - this.cellRef.state = CellState.finished; - } - this.subscriber.next(this.cellRef); - } - this.subscriber.complete(); - - // Then see if we're finished or not. - this.attemptToFinish(); - } - - // tslint:disable-next-line:no-any - public reject(e: any) { - if (!this.deferred.completed) { - this.cellRef.state = CellState.error; - this.subscriber.next(this.cellRef); - this.subscriber.complete(); - this.deferred.reject(e); - this.promiseComplete(this); - } - } - - public cancel() { - this.canceledEvent.fire(); - if (!this.deferred.completed) { - this.cellRef.state = CellState.error; - this.subscriber.next(this.cellRef); - this.subscriber.complete(); - this.deferred.resolve(); - this.promiseComplete(this); - } - } - - private attemptToFinish() { - if ( - !this.deferred.completed && - (this.cell.state === CellState.finished || this.cell.state === CellState.error) - ) { - this.deferred.resolve(this.cell.state); - this.promiseComplete(this); - } - } -} - -// This code is based on the examples here: -// https://www.npmjs.com/package/@jupyterlab/services - -export class JupyterNotebookBase implements INotebook { - private sessionStartTime: number; - private pendingCellSubscriptions: CellSubscriber[] = []; - private ranInitialSetup = false; - private _resource: Resource; - private _identity: Uri; - private _disposed: boolean = false; - private _workingDirectory: string | undefined; - private _executionInfo: INotebookExecutionInfo; - private onStatusChangedEvent: EventEmitter | undefined; - public get onDisposed(): Event { - return this.disposedEvent.event; - } - public get onKernelChanged(): Event { - return this.kernelChanged.event; - } - public get disposed() { - return this._disposed; - } - private kernelChanged = new EventEmitter(); - public get onKernelRestarted(): Event { - return this.kernelRestarted.event; - } - public get onKernelInterrupted(): Event { - return this.kernelInterrupted.event; - } - private readonly kernelRestarted = new EventEmitter(); - private readonly kernelInterrupted = new EventEmitter(); - private disposedEvent = new EventEmitter(); - private sessionStatusChanged: Disposable | undefined; - private initializedMatplotlib = false; - private ioPubListeners = new Set<(msg: KernelMessage.IIOPubMessage, requestId: string) => void>(); - public get kernelSocket(): Observable { - return this.session.kernelSocket; - } - public get session(): IJupyterSession { - return this._session; - } - - constructor( - _liveShare: ILiveShareApi, // This is so the liveshare mixin works - private readonly _session: IJupyterSession, - private configService: IConfigurationService, - private disposableRegistry: IDisposableRegistry, - executionInfo: INotebookExecutionInfo, - private loggers: INotebookExecutionLogger[], - resource: Resource, - identity: Uri, - private getDisposedError: () => Error, - private workspace: IWorkspaceService, - private applicationService: IApplicationShell, - private fs: IDataScienceFileSystem - ) { - this.sessionStartTime = Date.now(); - - const statusChangeHandler = (status: ServerStatus) => { - if (this.onStatusChangedEvent) { - this.onStatusChangedEvent.fire(status); - } - }; - this.sessionStatusChanged = this.session.onSessionStatusChanged(statusChangeHandler); - this._identity = identity; - this._resource = resource; - - // Make a copy of the launch info so we can update it in this class - this._executionInfo = cloneDeep(executionInfo); - } - - public get connection() { - return this._executionInfo.connectionInfo; - } - - public async dispose(): Promise { - if (!this._disposed) { - this._disposed = true; - if (this.onStatusChangedEvent) { - this.onStatusChangedEvent.dispose(); - this.onStatusChangedEvent = undefined; - } - if (this.sessionStatusChanged) { - this.sessionStatusChanged.dispose(); - this.onStatusChangedEvent = undefined; - } - this.loggers.forEach((d) => d.dispose()); - this.disposedEvent.fire(); - - try { - traceInfo(`Shutting down session ${this.identity.toString()}`); - if (this.session) { - await this.session - .dispose() - .catch(traceError.bind('Failed to dispose session from JupyterNotebook')); - } - } catch (exc) { - traceError(`Exception shutting down session `, exc); - } - } - } - public async requestKernelInfo(): Promise { - return this.session.requestKernelInfo(); - } - public get onSessionStatusChanged(): Event { - if (!this.onStatusChangedEvent) { - this.onStatusChangedEvent = new EventEmitter(); - } - return this.onStatusChangedEvent.event; - } - - public get status(): ServerStatus { - if (this.session) { - return this.session.status; - } - return ServerStatus.NotStarted; - } - - public get resource(): Resource { - return this._resource; - } - public get identity(): Uri { - return this._identity; - } - - public waitForIdle(timeoutMs: number): Promise { - return this.session ? this.session.waitForIdle(timeoutMs) : Promise.resolve(); - } - - // Set up our initial plotting and imports - public async initialize(cancelToken?: CancellationToken): Promise { - if (this.ranInitialSetup) { - return; - } - this.ranInitialSetup = true; - this._workingDirectory = undefined; - - try { - // When we start our notebook initial, change to our workspace or user specified root directory - await this.updateWorkingDirectoryAndPath(); - - const settings = this.configService.getSettings(this.resource).datascience; - if (settings && settings.themeMatplotlibPlots) { - // We're theming matplotlibs, so we have to setup our default state. - await this.initializeMatplotlib(cancelToken); - } else { - this.initializedMatplotlib = false; - const configInit = - !settings || settings.enablePlotViewer ? CodeSnippets.ConfigSvg : CodeSnippets.ConfigPng; - traceInfo(`Initialize config for plots for ${this.identity.toString()}`); - await this.executeSilently(configInit, cancelToken); - } - - // Run any startup commands that we specified. Support the old form too - let setting = settings.runStartupCommands || settings.runMagicCommands; - - // Convert to string in case we get an array of startup commands. - if (Array.isArray(setting)) { - setting = setting.join(`\n`); - } - - if (setting) { - // Cleanup the line feeds. User may have typed them into the settings UI so they will have an extra \\ on the front. - const cleanedUp = setting.replace(/\\n/g, '\n'); - const cells = await this.executeSilently(cleanedUp, cancelToken); - traceInfo(`Run startup code for notebook: ${cleanedUp} - results: ${cells.length}`); - } - - traceInfo(`Initial setup complete for ${this.identity.toString()}`); - } catch (e) { - traceWarning(e); - } - } - - public clear(_id: string): void { - // We don't do anything as we don't cache results in this class. - noop(); - } - - public execute( - code: string, - file: string, - line: number, - id: string, - cancelToken?: CancellationToken, - silent?: boolean - ): Promise { - // Create a deferred that we'll fire when we're done - const deferred = createDeferred(); - - // Attempt to evaluate this cell in the jupyter notebook. - const observable = this.executeObservable(code, file, line, id, silent); - let output: ICell[]; - - observable.subscribe( - (cells: ICell[]) => { - output = cells; - }, - (error) => { - deferred.reject(error); - }, - () => { - deferred.resolve(output); - } - ); - - if (cancelToken && cancelToken.onCancellationRequested) { - this.disposableRegistry.push( - cancelToken.onCancellationRequested(() => deferred.reject(new CancellationError())) - ); - } - - // Wait for the execution to finish - return deferred.promise; - } - - public inspect(code: string, offsetInCode = 0, cancelToken?: CancellationToken): Promise { - // Create a deferred that will fire when the request completes - const deferred = createDeferred(); - - // First make sure still valid. - const exitError = this.checkForExit(); - if (exitError) { - // Not running, just exit - deferred.reject(exitError); - } else { - // Ask session for inspect result - this.session - .requestInspect({ code, cursor_pos: offsetInCode, detail_level: 0 }) - .then((r) => { - if (r && r.content.status === 'ok') { - deferred.resolve(r.content.data); - } else { - deferred.resolve(undefined); - } - }) - .catch((ex) => { - deferred.reject(ex); - }); - } - - if (cancelToken) { - this.disposableRegistry.push( - cancelToken.onCancellationRequested(() => deferred.reject(new CancellationError())) - ); - } - - return deferred.promise; - } - - public setLaunchingFile(file: string): Promise { - // Update our working directory if we don't have one set already - return this.updateWorkingDirectoryAndPath(file); - } - - public executeObservable( - code: string, - file: string, - line: number, - id: string, - silent: boolean = false - ): Observable { - // Create an observable and wrap the result so we can time it. - const stopWatch = new StopWatch(); - const result = this.executeObservableImpl(code, file, line, id, silent); - return new Observable((subscriber) => { - result.subscribe( - (cells) => { - subscriber.next(cells); - }, - (error) => { - subscriber.error(error); - }, - () => { - subscriber.complete(); - sendTelemetryEvent(Telemetry.ExecuteCell, stopWatch.elapsedTime); - } - ); - }); - } - - public async getSysInfo(): Promise { - // tslint:disable-next-line:no-multiline-string - const versionCells = await this.executeSilently(`import sys\r\nsys.version`); - // tslint:disable-next-line:no-multiline-string - const pathCells = await this.executeSilently(`import sys\r\nsys.executable`); - // tslint:disable-next-line:no-multiline-string - const notebookVersionCells = await this.executeSilently(`import notebook\r\nnotebook.version_info`); - - // Both should have streamed output - const version = versionCells.length > 0 ? this.extractStreamOutput(versionCells[0]).trimQuotes() : ''; - const notebookVersion = - notebookVersionCells.length > 0 ? this.extractStreamOutput(notebookVersionCells[0]).trimQuotes() : ''; - const pythonPath = versionCells.length > 0 ? this.extractStreamOutput(pathCells[0]).trimQuotes() : ''; - - // Combine this data together to make our sys info - return { - data: { - cell_type: 'messages', - messages: [version, notebookVersion, pythonPath], - metadata: {}, - source: [] - }, - id: uuid(), - file: '', - line: 0, - state: CellState.finished - }; - } - - @captureTelemetry(Telemetry.RestartJupyterTime) - public async restartKernel(timeoutMs: number): Promise { - if (this.session) { - // Update our start time so we don't keep sending responses - this.sessionStartTime = Date.now(); - - traceInfo('restartKernel - finishing cells that are outstanding'); - // Complete all pending as an error. We're restarting - this.finishUncompletedCells(); - traceInfo('restartKernel - restarting kernel'); - - // Restart our kernel - await this.session.restart(timeoutMs); - - // Rerun our initial setup for the notebook - this.ranInitialSetup = false; - traceInfo('restartKernel - initialSetup'); - await this.initialize(); - traceInfo('restartKernel - initialSetup completed'); - - // Tell our loggers - this.loggers.forEach((l) => l.onKernelRestarted()); - - this.kernelRestarted.fire(); - return; - } - - throw this.getDisposedError(); - } - - @captureTelemetry(Telemetry.InterruptJupyterTime) - public async interruptKernel(timeoutMs: number): Promise { - if (this.session) { - // Keep track of our current time. If our start time gets reset, we - // restarted the kernel. - const interruptBeginTime = Date.now(); - - // Get just the first pending cell (it should be the oldest). If it doesn't finish - // by our timeout, then our interrupt didn't work. - const firstPending = - this.pendingCellSubscriptions.length > 0 ? this.pendingCellSubscriptions[0] : undefined; - - // Create a promise that resolves when the first pending cell finishes - const finished = firstPending ? firstPending.promise : Promise.resolve(CellState.finished); - - // Create a deferred promise that resolves if we have a failure - const restarted = createDeferred(); - - // Listen to status change events so we can tell if we're restarting - const restartHandler = (e: ServerStatus) => { - if (e === ServerStatus.Restarting) { - // We restarted the kernel. - this.sessionStartTime = Date.now(); - traceWarning('Kernel restarting during interrupt'); - - // Indicate we have to redo initial setup. We can't wait for starting though - // because sometimes it doesn't happen - this.ranInitialSetup = false; - - // Indicate we restarted the race below - restarted.resolve([]); - - // Fail all of the active (might be new ones) pending cell executes. We restarted. - this.finishUncompletedCells(); - } - }; - const restartHandlerToken = this.session.onSessionStatusChanged(restartHandler); - - // Start our interrupt. If it fails, indicate a restart - this.session.interrupt(timeoutMs).catch((exc) => { - traceWarning(`Error during interrupt: ${exc}`); - restarted.resolve([]); - }); - - try { - // Wait for all of the pending cells to finish or the timeout to fire - const result = await waitForPromise(Promise.race([finished, restarted.promise]), timeoutMs); - - // See if we restarted or not - if (restarted.completed) { - return InterruptResult.Restarted; - } - - if (result === null) { - // We timed out. You might think we should stop our pending list, but that's not - // up to us. The cells are still executing. The user has to request a restart or try again - return InterruptResult.TimedOut; - } - - // Cancel all other pending cells as we interrupted. - this.finishUncompletedCells(); - - // Fire event that we interrupted. - this.kernelInterrupted.fire(); - - // Indicate the interrupt worked. - return InterruptResult.Success; - } catch (exc) { - // Something failed. See if we restarted or not. - if (this.sessionStartTime && interruptBeginTime < this.sessionStartTime) { - return InterruptResult.Restarted; - } - - // Otherwise a real error occurred. - throw exc; - } finally { - restartHandlerToken.dispose(); - } - } - - throw this.getDisposedError(); - } - - public async setMatplotLibStyle(useDark: boolean): Promise { - // Make sure matplotlib is initialized - if (!this.initializedMatplotlib) { - await this.initializeMatplotlib(); - } - - const settings = this.configService.getSettings(this.resource).datascience; - if (settings.themeMatplotlibPlots && !settings.ignoreVscodeTheme) { - // Reset the matplotlib style based on if dark or not. - await this.executeSilently( - useDark - ? "matplotlib.style.use('dark_background')" - : `matplotlib.rcParams.update(${Identifiers.MatplotLibDefaultParams})` - ); - } - } - - public async getCompletion( - cellCode: string, - offsetInCode: number, - cancelToken?: CancellationToken - ): Promise { - if (this.session) { - // If server is busy, then don't delay code completion. - if (this.session.status === ServerStatus.Busy) { - return { - matches: [], - cursor: { start: 0, end: 0 }, - metadata: [] - }; - } - const result = await Promise.race([ - this.session!.requestComplete({ - code: cellCode, - cursor_pos: offsetInCode - }), - createPromiseFromCancellation({ defaultValue: undefined, cancelAction: 'resolve', token: cancelToken }) - ]); - if (result && result.content) { - if ('matches' in result.content) { - return { - matches: result.content.matches, - cursor: { - start: result.content.cursor_start, - end: result.content.cursor_end - }, - metadata: result.content.metadata - }; - } - } - return { - matches: [], - cursor: { start: 0, end: 0 }, - metadata: [] - }; - } - - // Default is just say session was disposed - throw new Error(localize.DataScience.sessionDisposed()); - } - - public getMatchingInterpreter(): PythonEnvironment | undefined { - return getInterpreterFromKernelConnectionMetadata(this.getKernelConnection()) as PythonEnvironment | undefined; - } - - public getKernelConnection(): KernelConnectionMetadata | undefined { - return this._executionInfo.kernelConnectionMetadata; - } - - public async setKernelConnection(connectionMetadata: KernelConnectionMetadata, timeoutMS: number): Promise { - // We need to start a new session with the new kernel spec - if (this.session) { - // Turn off setup - this.ranInitialSetup = false; - - // Change the kernel on the session - await this.session.changeKernel(connectionMetadata, timeoutMS); - - // Change our own kernel spec - // Only after session was successfully created. - this._executionInfo.kernelConnectionMetadata = connectionMetadata; - - // Rerun our initial setup - await this.initialize(); - } else { - // Change our own kernel spec - this._executionInfo.kernelConnectionMetadata = connectionMetadata; - } - - this.kernelChanged.fire(connectionMetadata); - } - - public getLoggers(): INotebookExecutionLogger[] { - return this.loggers; - } - - public registerIOPubListener(listener: (msg: KernelMessage.IIOPubMessage, requestId: string) => void): void { - this.ioPubListeners.add(listener); - } - - public registerCommTarget( - targetName: string, - callback: (comm: Kernel.IComm, msg: KernelMessage.ICommOpenMsg) => void | PromiseLike - ) { - if (this.session) { - this.session.registerCommTarget(targetName, callback); - } else { - throw new Error(localize.DataScience.sessionDisposed()); - } - } - - public sendCommMessage( - buffers: (ArrayBuffer | ArrayBufferView)[], - content: { comm_id: string; data: JSONObject; target_name: string | undefined }, - // tslint:disable-next-line: no-any - metadata: any, - // tslint:disable-next-line: no-any - msgId: any - ): Kernel.IShellFuture< - KernelMessage.IShellMessage<'comm_msg'>, - KernelMessage.IShellMessage - > { - if (this.session) { - return this.session.sendCommMessage(buffers, content, metadata, msgId); - } else { - throw new Error(localize.DataScience.sessionDisposed()); - } - } - - public requestCommInfo( - content: KernelMessage.ICommInfoRequestMsg['content'] - ): Promise { - if (this.session) { - return this.session.requestCommInfo(content); - } else { - throw new Error(localize.DataScience.sessionDisposed()); - } - } - public registerMessageHook( - msgId: string, - hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike - ): void { - if (this.session) { - return this.session.registerMessageHook(msgId, hook); - } else { - throw new Error(localize.DataScience.sessionDisposed()); - } - } - public removeMessageHook( - msgId: string, - hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike - ): void { - if (this.session) { - return this.session.removeMessageHook(msgId, hook); - } else { - throw new Error(localize.DataScience.sessionDisposed()); - } - } - - private async initializeMatplotlib(cancelToken?: CancellationToken): Promise { - const settings = this.configService.getSettings(this.resource).datascience; - if (settings && settings.themeMatplotlibPlots) { - const matplobInit = - !settings || settings.enablePlotViewer - ? CodeSnippets.MatplotLibInitSvg - : CodeSnippets.MatplotLibInitPng; - - traceInfo(`Initialize matplotlib for ${this.identity.toString()}`); - // Force matplotlib to inline and save the default style. We'll use this later if we - // get a request to update style - await this.executeSilently(matplobInit, cancelToken); - - // Use this flag to detemine if we need to rerun this or not. - this.initializedMatplotlib = true; - } - } - - private finishUncompletedCells() { - const copyPending = [...this.pendingCellSubscriptions]; - copyPending.forEach((c) => c.cancel()); - this.pendingCellSubscriptions = []; - } - - @captureTelemetry(Telemetry.HiddenCellTime) - private executeSilently(code: string, cancelToken?: CancellationToken): Promise { - // Create a deferred that we'll fire when we're done - const deferred = createDeferred(); - - // Attempt to evaluate this cell in the jupyter notebook - const observable = this.executeObservableImpl(code, Identifiers.EmptyFileName, 0, uuid(), true); - let output: ICell[]; - - observable.subscribe( - (cells: ICell[]) => { - output = cells; - }, - (error) => { - deferred.reject(error); - }, - () => { - deferred.resolve(output); - } - ); - - if (cancelToken) { - this.disposableRegistry.push( - cancelToken.onCancellationRequested(() => deferred.reject(new CancellationError())) - ); - } - - // Wait for the execution to finish - return deferred.promise; - } - - private extractStreamOutput(cell: ICell): string { - let result = ''; - if (cell.state === CellState.error || cell.state === CellState.finished) { - const outputs = cell.data.outputs as nbformat.IOutput[]; - if (outputs) { - outputs.forEach((o) => { - if (o.output_type === 'stream') { - const stream = o as nbformat.IStream; - result = result.concat(formatStreamText(concatMultilineString(stream.text, true))); - } else { - const data = o.data; - if (data && data.hasOwnProperty('text/plain')) { - // tslint:disable-next-line:no-any - result = result.concat((data as any)['text/plain']); - } - } - }); - } - } - return result; - } - - private executeObservableImpl( - code: string, - file: string, - line: number, - id: string, - silent?: boolean - ): Observable { - // If we have a session, execute the code now. - if (this.session) { - // Generate our cells ahead of time - const cells = generateCells( - this.configService.getSettings(this.resource).datascience, - code, - file, - line, - true, - id - ); - - // Might have more than one (markdown might be split) - if (cells.length > 1) { - // We need to combine results - return this.combineObservables( - this.executeMarkdownObservable(cells[0]), - this.executeCodeObservable(cells[1], silent) - ); - } else if (cells.length > 0) { - // Either markdown or or code - return this.combineObservables( - cells[0].data.cell_type === 'code' - ? this.executeCodeObservable(cells[0], silent) - : this.executeMarkdownObservable(cells[0]) - ); - } - } - - traceError('No session during execute observable'); - - // Can't run because no session - return new Observable((subscriber) => { - subscriber.error(this.getDisposedError()); - subscriber.complete(); - }); - } - - private generateRequest = ( - code: string, - silent?: boolean, - // tslint:disable-next-line: no-any - metadata?: Record - ): Kernel.IShellFuture | undefined => { - //traceInfo(`Executing code in jupyter : ${code}`); - try { - const cellMatcher = new CellMatcher(this.configService.getSettings(this.resource).datascience); - return this.session - ? this.session.requestExecute( - { - // Remove the cell marker if we have one. - code: cellMatcher.stripFirstMarker(code), - stop_on_error: false, - allow_stdin: true, // Allow when silent too in case runStartupCommands asks for a password - store_history: !silent // Silent actually means don't output anything. Store_history is what affects execution_count - }, - silent, // Dispose only silent futures. Otherwise update_display_data doesn't find a future for a previous cell. - metadata - ) - : undefined; - } catch (exc) { - // Any errors generating a request should just be logged. User can't do anything about it. - traceError(exc); - } - - return undefined; - }; - - private combineObservables = (...args: Observable[]): Observable => { - return new Observable((subscriber) => { - // When all complete, we have our results - const results: Record = {}; - - args.forEach((o) => { - o.subscribe( - (c) => { - results[c.id] = c; - - // Convert to an array - const array = Object.keys(results).map((k: string) => { - return results[k]; - }); - - // Update our subscriber of our total results if we have that many - if (array.length === args.length) { - subscriber.next(array); - - // Complete when everybody is finished - if (array.every((a) => a.state === CellState.finished || a.state === CellState.error)) { - subscriber.complete(); - } - } - }, - (e) => { - subscriber.error(e); - } - ); - }); - }); - }; - - private executeMarkdownObservable = (cell: ICell): Observable => { - // Markdown doesn't need any execution - return new Observable((subscriber) => { - subscriber.next(cell); - subscriber.complete(); - }); - }; - - private async updateWorkingDirectoryAndPath(launchingFile?: string): Promise { - if (this._executionInfo && this._executionInfo.connectionInfo.localLaunch && !this._workingDirectory) { - // See what our working dir is supposed to be - const suggested = this._executionInfo.workingDir; - if (suggested && (await this.fs.localDirectoryExists(suggested))) { - // We should use the launch info directory. It trumps the possible dir - this._workingDirectory = suggested; - return this.changeDirectoryIfPossible(this._workingDirectory); - } else if (launchingFile && (await this.fs.localFileExists(launchingFile))) { - // Combine the working directory with this file if possible. - this._workingDirectory = expandWorkingDir( - this._executionInfo.workingDir, - launchingFile, - this.workspace - ); - if (this._workingDirectory) { - return this.changeDirectoryIfPossible(this._workingDirectory); - } - } - } - } - - // Update both current working directory and sys.path with the desired directory - private changeDirectoryIfPossible = async (directory: string): Promise => { - if ( - this._executionInfo && - this._executionInfo.connectionInfo.localLaunch && - isPythonKernelConnection(this._executionInfo.kernelConnectionMetadata) && - (await this.fs.localDirectoryExists(directory)) - ) { - await this.executeSilently(CodeSnippets.UpdateCWDAndPath.format(directory)); - } - }; - - private handleIOPub( - subscriber: CellSubscriber, - silent: boolean | undefined, - clearState: RefBool, - msg: KernelMessage.IIOPubMessage - // tslint:disable-next-line: no-any - ) { - // Let our loggers get a first crack at the message. They may change it - this.getLoggers().forEach((f) => (msg = f.preHandleIOPub ? f.preHandleIOPub(msg) : msg)); - - // tslint:disable-next-line:no-require-imports - const jupyterLab = require('@jupyterlab/services') as typeof import('@jupyterlab/services'); - - // Create a trimming function. Only trim user output. Silent output requires the full thing - const trimFunc = silent ? (s: string) => s : this.trimOutput.bind(this); - let shouldUpdateSubscriber = true; - try { - if (jupyterLab.KernelMessage.isExecuteResultMsg(msg)) { - this.handleExecuteResult(msg as KernelMessage.IExecuteResultMsg, clearState, subscriber.cell, trimFunc); - } else if (jupyterLab.KernelMessage.isExecuteInputMsg(msg)) { - this.handleExecuteInput(msg as KernelMessage.IExecuteInputMsg, clearState, subscriber.cell); - } else if (jupyterLab.KernelMessage.isStatusMsg(msg)) { - // If there is no change in the status, then there's no need to update the subscriber. - // Else we end up sending a number of messages unnecessarily uptream. - const statusMsg = msg as KernelMessage.IStatusMsg; - if (statusMsg.content.execution_state === subscriber.executionState) { - shouldUpdateSubscriber = false; - } - subscriber.executionState = statusMsg.content.execution_state; - this.handleStatusMessage(statusMsg, clearState, subscriber.cell); - } else if (jupyterLab.KernelMessage.isStreamMsg(msg)) { - this.handleStreamMesssage(msg as KernelMessage.IStreamMsg, clearState, subscriber.cell, trimFunc); - } else if (jupyterLab.KernelMessage.isDisplayDataMsg(msg)) { - this.handleDisplayData(msg as KernelMessage.IDisplayDataMsg, clearState, subscriber.cell); - } else if (jupyterLab.KernelMessage.isUpdateDisplayDataMsg(msg)) { - // No new data to update UI, hence do not send updates. - shouldUpdateSubscriber = false; - } else if (jupyterLab.KernelMessage.isClearOutputMsg(msg)) { - this.handleClearOutput(msg as KernelMessage.IClearOutputMsg, clearState, subscriber.cell); - } else if (jupyterLab.KernelMessage.isErrorMsg(msg)) { - this.handleError(msg as KernelMessage.IErrorMsg, clearState, subscriber.cell); - } else if (jupyterLab.KernelMessage.isCommOpenMsg(msg)) { - // No new data to update UI, hence do not send updates. - shouldUpdateSubscriber = false; - } else if (jupyterLab.KernelMessage.isCommMsgMsg(msg)) { - // No new data to update UI, hence do not send updates. - shouldUpdateSubscriber = false; - } else if (jupyterLab.KernelMessage.isCommCloseMsg(msg)) { - // No new data to update UI, hence do not send updates. - shouldUpdateSubscriber = false; - } else { - traceWarning(`Unknown message ${msg.header.msg_type} : hasData=${'data' in msg.content}`); - } - - // Set execution count, all messages should have it - if ('execution_count' in msg.content && typeof msg.content.execution_count === 'number') { - subscriber.cell.data.execution_count = msg.content.execution_count as number; - } - - // Tell all of the listeners about the event. - [...this.ioPubListeners].forEach((l) => l(msg, msg.header.msg_id)); - - // Show our update if any new output. - if (shouldUpdateSubscriber) { - subscriber.next(this.sessionStartTime); - } - } catch (err) { - // If not a restart error, then tell the subscriber - subscriber.error(this.sessionStartTime, err); - } - } - - private checkForExit(): Error | undefined { - if (this._executionInfo && this._executionInfo.connectionInfo && !this._executionInfo.connectionInfo.valid) { - if (this._executionInfo.connectionInfo.type === 'jupyter') { - // Not running, just exit - if (this._executionInfo.connectionInfo.localProcExitCode) { - const exitCode = this._executionInfo.connectionInfo.localProcExitCode; - traceError(`Jupyter crashed with code ${exitCode}`); - return new Error(localize.DataScience.jupyterServerCrashed().format(exitCode.toString())); - } - } - } - - return undefined; - } - - private handleInputRequest(_subscriber: CellSubscriber, msg: KernelMessage.IStdinMessage) { - // Ask the user for input - if (msg.content && 'prompt' in msg.content) { - const hasPassword = msg.content.password !== null && (msg.content.password as boolean); - this.applicationService - .showInputBox({ - prompt: msg.content.prompt ? msg.content.prompt.toString() : '', - ignoreFocusOut: true, - password: hasPassword - }) - .then((v) => { - this.session.sendInputReply(v || ''); - }); - } - } - - private handleReply( - subscriber: CellSubscriber, - silent: boolean | undefined, - clearState: RefBool, - msg: KernelMessage.IShellControlMessage - ) { - // tslint:disable-next-line:no-require-imports - const jupyterLab = require('@jupyterlab/services') as typeof import('@jupyterlab/services'); - - // Create a trimming function. Only trim user output. Silent output requires the full thing - const trimFunc = silent ? (s: string) => s : this.trimOutput.bind(this); - - if (jupyterLab.KernelMessage.isExecuteReplyMsg(msg)) { - this.handleExecuteReply(msg, clearState, subscriber.cell, trimFunc); - - // Set execution count, all messages should have it - if ('execution_count' in msg.content && typeof msg.content.execution_count === 'number') { - subscriber.cell.data.execution_count = msg.content.execution_count as number; - } - - // Send this event. - subscriber.next(this.sessionStartTime); - } - } - - // tslint:disable-next-line: max-func-body-length - private handleCodeRequest = (subscriber: CellSubscriber, silent?: boolean) => { - // Generate a new request if we still can - if (subscriber.isValid(this.sessionStartTime)) { - // Double check process is still running - const exitError = this.checkForExit(); - if (exitError) { - // Not running, just exit - subscriber.error(this.sessionStartTime, exitError); - subscriber.complete(this.sessionStartTime); - } else { - const request = this.generateRequest(concatMultilineString(subscriber.cell.data.source), silent, { - ...subscriber.cell.data.metadata, - ...{ cellId: subscriber.cell.id } - }); - - // Transition to the busy stage - subscriber.cell.state = CellState.executing; - - // Make sure our connection doesn't go down - let exitHandlerDisposable: Disposable | undefined; - if (this._executionInfo && this._executionInfo.connectionInfo) { - // If the server crashes, cancel the current observable - exitHandlerDisposable = this._executionInfo.connectionInfo.disconnected((c) => { - const str = c ? c.toString() : ''; - // Only do an error if we're not disposed. If we're disposed we already shutdown. - if (!this._disposed) { - subscriber.error( - this.sessionStartTime, - new Error(localize.DataScience.jupyterServerCrashed().format(str)) - ); - } - subscriber.complete(this.sessionStartTime); - }); - } - - // Keep track of our clear state - const clearState = new RefBool(false); - - // Listen to the reponse messages and update state as we go - if (request) { - // Stop handling the request if the subscriber is canceled. - subscriber.onCanceled(() => { - request.onIOPub = noop; - request.onStdin = noop; - request.onReply = noop; - }); - - // Listen to messages. - request.onIOPub = this.handleIOPub.bind(this, subscriber, silent, clearState); - request.onStdin = this.handleInputRequest.bind(this, subscriber); - request.onReply = this.handleReply.bind(this, subscriber, silent, clearState); - - // When the request finishes we are done - request.done - .then(() => subscriber.complete(this.sessionStartTime)) - .catch((e) => { - // @jupyterlab/services throws a `Canceled` error when the kernel is interrupted. - // Such an error must be ignored. - if (e && e instanceof Error && e.message === 'Canceled') { - subscriber.complete(this.sessionStartTime); - } else { - subscriber.error(this.sessionStartTime, e); - } - }) - .finally(() => { - if (exitHandlerDisposable) { - exitHandlerDisposable.dispose(); - } - }) - .ignoreErrors(); - } else { - subscriber.error(this.sessionStartTime, this.getDisposedError()); - } - } - } else { - const sessionDate = new Date(this.sessionStartTime!); - const cellDate = new Date(subscriber.startTime); - traceInfo( - `Session start time is newer than cell : \r\n${sessionDate.toTimeString()}\r\n${cellDate.toTimeString()}` - ); - - // Otherwise just set to an error - this.handleInterrupted(subscriber.cell); - subscriber.cell.state = CellState.error; - subscriber.complete(this.sessionStartTime); - } - }; - - private executeCodeObservable(cell: ICell, silent?: boolean): Observable { - return new Observable((subscriber) => { - // Tell our listener. NOTE: have to do this asap so that markdown cells don't get - // run before our cells. - subscriber.next(cell); - const isSilent = silent !== undefined ? silent : false; - - // Wrap the subscriber and save it. It is now pending and waiting completion. Have to do this - // synchronously so it happens before interruptions. - const cellSubscriber = new CellSubscriber(cell, subscriber, (self: CellSubscriber) => { - // Subscriber completed, remove from subscriptions. - this.pendingCellSubscriptions = this.pendingCellSubscriptions.filter((p) => p !== self); - - // Indicate success or failure - this.logPostCode(cell, isSilent).ignoreErrors(); - }); - this.pendingCellSubscriptions.push(cellSubscriber); - - // Log the pre execution. - this.logPreCode(cell, isSilent) - .then(() => { - // Now send our real request. This should call back on the cellsubscriber when it's done. - this.handleCodeRequest(cellSubscriber, silent); - }) - .ignoreErrors(); - }); - } - - private async logPreCode(cell: ICell, silent: boolean): Promise { - await Promise.all(this.loggers.map((l) => l.preExecute(cell, silent))); - } - - private async logPostCode(cell: ICell, silent: boolean): Promise { - await Promise.all(this.loggers.map((l) => l.postExecute(cloneDeep(cell), silent))); - } - - private addToCellData = ( - cell: ICell, - output: nbformat.IExecuteResult | nbformat.IDisplayData | nbformat.IStream | nbformat.IError, - clearState: RefBool - ) => { - const data: nbformat.ICodeCell = cell.data as nbformat.ICodeCell; - - // Clear if necessary - if (clearState.value) { - data.outputs = []; - clearState.update(false); - } - - // Append to the data. - data.outputs = [...data.outputs, output]; - cell.data = data; - }; - - // See this for docs on the messages: - // https://jupyter-client.readthedocs.io/en/latest/messaging.html#messaging-in-jupyter - private handleExecuteResult( - msg: KernelMessage.IExecuteResultMsg, - clearState: RefBool, - cell: ICell, - trimFunc: (str: string) => string - ) { - // Check our length on text output - if (msg.content.data && msg.content.data.hasOwnProperty('text/plain')) { - msg.content.data['text/plain'] = splitMultilineString( - // tslint:disable-next-line: no-any - trimFunc(concatMultilineString(msg.content.data['text/plain'] as any)) - ); - } - - this.addToCellData( - cell, - { - output_type: 'execute_result', - data: msg.content.data, - metadata: msg.content.metadata, - // tslint:disable-next-line: no-any - transient: msg.content.transient as any, // NOSONAR - execution_count: msg.content.execution_count - }, - clearState - ); - } - - private handleExecuteReply( - msg: KernelMessage.IExecuteReplyMsg, - clearState: RefBool, - cell: ICell, - trimFunc: (str: string) => string - ) { - const reply = msg.content as KernelMessage.IExecuteReply; - if (reply.payload) { - reply.payload.forEach((o) => { - if (o.data && o.data.hasOwnProperty('text/plain')) { - // tslint:disable-next-line: no-any - const str = concatMultilineString((o.data as any)['text/plain']); // NOSONAR - const data = trimFunc(str); - this.addToCellData( - cell, - { - // Mark as stream output so the text is formatted because it likely has ansi codes in it. - output_type: 'stream', - text: splitMultilineString(data), - name: 'stdout', - metadata: {}, - execution_count: reply.execution_count - }, - clearState - ); - } - }); - } - } - - private handleExecuteInput(msg: KernelMessage.IExecuteInputMsg, _clearState: RefBool, cell: ICell) { - cell.data.execution_count = msg.content.execution_count; - } - - private handleStatusMessage(msg: KernelMessage.IStatusMsg, _clearState: RefBool, _cell: ICell) { - traceInfo(`Kernel switching to ${msg.content.execution_state}`); - } - - private handleStreamMesssage( - msg: KernelMessage.IStreamMsg, - clearState: RefBool, - cell: ICell, - trimFunc: (str: string) => string - ) { - const data: nbformat.ICodeCell = cell.data as nbformat.ICodeCell; - let originalTextLength = 0; - let trimmedTextLength = 0; - - // Clear output if waiting for a clear - if (clearState.value) { - data.outputs = []; - clearState.update(false); - } - - // Might already have a stream message. If so, just add on to it. - const existing = - data.outputs.length > 0 && data.outputs[data.outputs.length - 1].output_type === 'stream' - ? data.outputs[data.outputs.length - 1] - : undefined; - if (existing) { - const originalText = formatStreamText( - // tslint:disable-next-line: no-any - `${concatMultilineString(existing.text as any)}${concatMultilineString(msg.content.text)}` - ); - originalTextLength = originalText.length; - const newText = trimFunc(originalText); - trimmedTextLength = newText.length; - existing.text = splitMultilineString(newText); - } else { - const originalText = formatStreamText(concatMultilineString(msg.content.text)); - originalTextLength = originalText.length; - // Create a new stream entry - const output: nbformat.IStream = { - output_type: 'stream', - name: msg.content.name, - text: [trimFunc(originalText)] - }; - data.outputs = [...data.outputs, output]; - trimmedTextLength = output.text[0].length; - cell.data = data; - } - - // If the output was trimmed, we add the 'outputPrepend' metadata tag. - // Later, the react side will display a message letting the user know - // the output is trimmed and what setting changes that. - // * If data.metadata.tags is undefined, define it so the following - // code is can rely on it being defined. - if (trimmedTextLength < originalTextLength) { - if (data.metadata.tags === undefined) { - data.metadata.tags = []; - } - data.metadata.tags = data.metadata.tags.filter((t) => t !== 'outputPrepend'); - data.metadata.tags.push('outputPrepend'); - } - } - - private handleDisplayData(msg: KernelMessage.IDisplayDataMsg, clearState: RefBool, cell: ICell) { - const output: nbformat.IDisplayData = { - output_type: 'display_data', - data: msg.content.data, - metadata: msg.content.metadata, - // tslint:disable-next-line: no-any - transient: msg.content.transient as any // NOSONAR - }; - this.addToCellData(cell, output, clearState); - } - - private handleClearOutput(msg: KernelMessage.IClearOutputMsg, clearState: RefBool, cell: ICell) { - // If the message says wait, add every message type to our clear state. This will - // make us wait for this type of output before we clear it. - if (msg && msg.content.wait) { - clearState.update(true); - } else { - // Clear all outputs and start over again. - const data: nbformat.ICodeCell = cell.data as nbformat.ICodeCell; - data.outputs = []; - } - } - - private handleInterrupted(cell: ICell) { - this.handleError( - { - channel: 'iopub', - parent_header: {}, - metadata: {}, - header: { username: '', version: '', session: '', msg_id: '', msg_type: 'error', date: '' }, - content: { - ename: 'KeyboardInterrupt', - evalue: '', - // Does this need to be translated? All depends upon if jupyter does or not - traceback: [ - '---------------------------------------------------------------------------', - 'KeyboardInterrupt: ' - ] - } - }, - new RefBool(false), - cell - ); - } - - private handleError(msg: KernelMessage.IErrorMsg, clearState: RefBool, cell: ICell) { - const output: nbformat.IError = { - output_type: 'error', - ename: msg.content.ename, - evalue: msg.content.evalue, - traceback: msg.content.traceback - }; - if (msg.content.hasOwnProperty('transient')) { - // tslint:disable-next-line: no-any - output.transient = (msg.content as any).transient; - } - this.addToCellData(cell, output, clearState); - cell.state = CellState.error; - - // In the error scenario, we want to stop all other pending cells. - if (this.configService.getSettings(this.resource).datascience.stopOnError) { - this.pendingCellSubscriptions.forEach((c) => { - if (c.cell.id !== cell.id) { - c.cancel(); - } - }); - } - } - - // We have a set limit for the number of output text characters that we display by default - // trim down strings to that limit, assuming at this point we have compressed down to a single string - private trimOutput(outputString: string): string { - const outputLimit = this.configService.getSettings(this.resource).datascience.textOutputLimit; - - if (!outputLimit || outputLimit === 0 || outputString.length <= outputLimit) { - return outputString; - } - - return outputString.substr(outputString.length - outputLimit); - } -} diff --git a/src/client/datascience/jupyter/jupyterNotebookProvider.ts b/src/client/datascience/jupyter/jupyterNotebookProvider.ts deleted file mode 100644 index c1103cab0fb2..000000000000 --- a/src/client/datascience/jupyter/jupyterNotebookProvider.ts +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import * as localize from '../../common/utils/localize'; -import { - ConnectNotebookProviderOptions, - GetNotebookOptions, - IJupyterConnection, - IJupyterNotebookProvider, - IJupyterServerProvider, - INotebook -} from '../types'; - -// When the NotebookProvider looks to create a notebook it uses this class to create a Jupyter notebook -@injectable() -export class JupyterNotebookProvider implements IJupyterNotebookProvider { - constructor(@inject(IJupyterServerProvider) private readonly serverProvider: IJupyterServerProvider) {} - - public async disconnect(options: ConnectNotebookProviderOptions): Promise { - const server = await this.serverProvider.getOrCreateServer(options); - - return server?.dispose(); - } - - public async connect(options: ConnectNotebookProviderOptions): Promise { - const server = await this.serverProvider.getOrCreateServer(options); - return server?.getConnectionInfo(); - } - - public async createNotebook(options: GetNotebookOptions): Promise { - // Make sure we have a server - const server = await this.serverProvider.getOrCreateServer({ - getOnly: options.getOnly, - disableUI: options.disableUI, - token: options.token - }); - - if (server) { - return server.createNotebook(options.resource, options.identity, options.metadata, options.token); - } - // We want createNotebook to always return a notebook promise, so if we don't have a server - // here throw our generic server disposed message that we use in server creatio n - throw new Error(localize.DataScience.sessionDisposed()); - } - public async getNotebook(options: GetNotebookOptions): Promise { - const server = await this.serverProvider.getOrCreateServer({ - getOnly: options.getOnly, - disableUI: options.disableUI, - token: options.token - }); - if (server) { - return server.getNotebook(options.identity, options.token); - } - } -} diff --git a/src/client/datascience/jupyter/jupyterPasswordConnect.ts b/src/client/datascience/jupyter/jupyterPasswordConnect.ts deleted file mode 100644 index 0f1850218be8..000000000000 --- a/src/client/datascience/jupyter/jupyterPasswordConnect.ts +++ /dev/null @@ -1,461 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { Agent as HttpsAgent } from 'https'; -import { inject, injectable } from 'inversify'; -import * as nodeFetch from 'node-fetch'; -import { URLSearchParams } from 'url'; -import { ConfigurationTarget } from 'vscode'; -import { IApplicationShell } from '../../common/application/types'; -import { IAsyncDisposableRegistry, IConfigurationService } from '../../common/types'; -import * as localize from '../../common/utils/localize'; -import { IMultiStepInput, IMultiStepInputFactory } from '../../common/utils/multiStepInput'; -import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; -import { IJupyterPasswordConnect, IJupyterPasswordConnectInfo } from '../types'; -import { Telemetry } from './../constants'; - -@injectable() -export class JupyterPasswordConnect implements IJupyterPasswordConnect { - private savedConnectInfo = new Map>(); - private fetchFunction: (url: nodeFetch.RequestInfo, init?: nodeFetch.RequestInit) => Promise = - nodeFetch.default; - - constructor( - @inject(IApplicationShell) private appShell: IApplicationShell, - @inject(IMultiStepInputFactory) private readonly multiStepFactory: IMultiStepInputFactory, - @inject(IAsyncDisposableRegistry) private readonly asyncDisposableRegistry: IAsyncDisposableRegistry, - @inject(IConfigurationService) private readonly configService: IConfigurationService - ) {} - - @captureTelemetry(Telemetry.GetPasswordAttempt) - public getPasswordConnectionInfo( - url: string, - fetchFunction?: (url: nodeFetch.RequestInfo, init?: nodeFetch.RequestInit) => Promise - ): Promise { - if (!url || url.length < 1) { - return Promise.resolve(undefined); - } - - // Update our fetch function if necessary - if (fetchFunction) { - this.fetchFunction = fetchFunction; - } - - // Add on a trailing slash to our URL if it's not there already - let newUrl = url; - if (newUrl[newUrl.length - 1] !== '/') { - newUrl = `${newUrl}/`; - } - - // See if we already have this data. Don't need to ask for a password more than once. (This can happen in remote when listing kernels) - let result = this.savedConnectInfo.get(newUrl); - if (!result) { - result = this.getNonCachedPasswordConnectionInfo(newUrl); - this.savedConnectInfo.set(newUrl, result); - } - - return result; - } - - private getSessionCookieString(xsrfCookie: string, sessionCookieName: string, sessionCookieValue: string): string { - return `_xsrf=${xsrfCookie}; ${sessionCookieName}=${sessionCookieValue}`; - } - - private async getNonCachedPasswordConnectionInfo(url: string): Promise { - // If jupyter hub, go down a special path of asking jupyter hub for a token - if (await this.isJupyterHub(url)) { - return this.getJupyterHubConnectionInfo(url); - } else { - return this.getJupyterConnectionInfo(url); - } - } - - private async getJupyterHubConnectionInfo(uri: string): Promise { - // First ask for the user name and password - const userNameAndPassword = await this.getUserNameAndPassword(); - if (userNameAndPassword.username || userNameAndPassword.password) { - // Try the login method. It should work and doesn't require a token to be generated. - const result = await this.getJupyterHubConnectionInfoFromLogin( - uri, - userNameAndPassword.username, - userNameAndPassword.password - ); - - // If login method fails, try generating a token - if (!result) { - return this.getJupyterHubConnectionInfoFromApi( - uri, - userNameAndPassword.username, - userNameAndPassword.password - ); - } - - return result; - } - } - - private async getJupyterHubConnectionInfoFromLogin( - uri: string, - username: string, - password: string - ): Promise { - // We're using jupyter hub. Get the base url - const url = new URL(uri); - const baseUrl = `${url.protocol}//${url.host}`; - - const postParams = new URLSearchParams(); - postParams.append('username', username || ''); - postParams.append('password', password || ''); - - let response = await this.makeRequest(`${baseUrl}/hub/login?next=`, { - method: 'POST', - headers: { - Connection: 'keep-alive', - Referer: `${baseUrl}/hub/login`, - 'content-type': 'application/x-www-form-urlencoded;charset=UTF-8' - }, - body: postParams.toString(), - redirect: 'manual' - }); - - // The cookies from that response should be used to make the next set of requests - if (response && response.status === 302) { - const cookies = this.getCookies(response); - const cookieString = [...cookies.entries()].reduce((p, c) => `${p};${c[0]}=${c[1]}`, ''); - // See this API for creating a token - // https://jupyterhub.readthedocs.io/en/stable/_static/rest-api/index.html#operation--users--name--tokens-post - response = await this.makeRequest(`${baseUrl}/hub/api/users/${username}/tokens`, { - method: 'POST', - headers: { - Connection: 'keep-alive', - Cookie: cookieString, - Referer: `${baseUrl}/hub/login` - } - }); - - // That should give us a new token. For now server name is hard coded. Not sure - // how to fetch it other than in the info for a default token - if (response.ok && response.status === 200) { - const body = await response.json(); - if (body && body.token && body.id) { - // Response should have the token to use for this user. - - // Make sure the server is running for this user. Don't need - // to check response as it will fail if already running. - // https://jupyterhub.readthedocs.io/en/stable/_static/rest-api/index.html#operation--users--name--server-post - await this.makeRequest(`${baseUrl}/hub/api/users/${username}/server`, { - method: 'POST', - headers: { - Connection: 'keep-alive', - Cookie: cookieString, - Referer: `${baseUrl}/hub/login` - } - }); - - // This token was generated for this request. We should clean it up when - // the user closes VS code - this.asyncDisposableRegistry.push({ - dispose: async () => { - this.makeRequest(`${baseUrl}/hub/api/users/${username}/tokens/${body.id}`, { - method: 'DELETE', - headers: { - Connection: 'keep-alive', - Cookie: cookieString, - Referer: `${baseUrl}/hub/login` - } - }).ignoreErrors(); // Don't wait for this during shutdown. Just make the request - } - }); - - return { - requestHeaders: {}, - remappedBaseUrl: `${baseUrl}/user/${username}`, - remappedToken: body.token - }; - } - } - } - } - - private async getJupyterHubConnectionInfoFromApi( - uri: string, - username: string, - password: string - ): Promise { - // We're using jupyter hub. Get the base url - const url = new URL(uri); - const baseUrl = `${url.protocol}//${url.host}`; - // Use these in a post request to get the token to use - const response = await this.makeRequest( - `${baseUrl}/hub/api/authorizations/token`, // This seems to be deprecated, but it works. It requests a new token - { - method: 'POST', - headers: { - Connection: 'keep-alive', - 'content-type': 'application/json;charset=UTF-8' - }, - body: `{ "username": "${username || ''}", "password": "${password || ''}" }`, - redirect: 'manual' - } - ); - - if (response.ok && response.status === 200) { - const body = await response.json(); - if (body && body.user && body.user.server && body.token) { - // Response should have the token to use for this user. - return { - requestHeaders: {}, - remappedBaseUrl: `${baseUrl}${body.user.server}`, - remappedToken: body.token - }; - } - } - } - - private async getJupyterConnectionInfo(url: string): Promise { - let xsrfCookie: string | undefined; - let sessionCookieName: string | undefined; - let sessionCookieValue: string | undefined; - - // First determine if we need a password. A request for the base URL with /tree? should return a 302 if we do. - if (await this.needPassword(url)) { - // Get password first - let userPassword = await this.getUserPassword(); - - if (userPassword) { - xsrfCookie = await this.getXSRFToken(url); - - // Then get the session cookie by hitting that same page with the xsrftoken and the password - if (xsrfCookie) { - const sessionResult = await this.getSessionCookie(url, xsrfCookie, userPassword); - sessionCookieName = sessionResult.sessionCookieName; - sessionCookieValue = sessionResult.sessionCookieValue; - } - } else { - // If userPassword is undefined or '' then the user didn't pick a password. In this case return back that we should just try to connect - // like a standard connection. Might be the case where there is no token and no password - return {}; - } - userPassword = undefined; - } else { - // If no password needed, act like empty password and no cookie - return {}; - } - - // If we found everything return it all back if not, undefined as partial is useless - if (xsrfCookie && sessionCookieName && sessionCookieValue) { - sendTelemetryEvent(Telemetry.GetPasswordSuccess); - const cookieString = this.getSessionCookieString(xsrfCookie, sessionCookieName, sessionCookieValue); - const requestHeaders = { Cookie: cookieString, 'X-XSRFToken': xsrfCookie }; - return { requestHeaders }; - } else { - sendTelemetryEvent(Telemetry.GetPasswordFailure); - return undefined; - } - } - - // For HTTPS connections respect our allowUnauthorized setting by adding in an agent to enable that on the request - private addAllowUnauthorized( - url: string, - allowUnauthorized: boolean, - options: nodeFetch.RequestInit - ): nodeFetch.RequestInit { - if (url.startsWith('https') && allowUnauthorized) { - const requestAgent = new HttpsAgent({ rejectUnauthorized: false }); - return { ...options, agent: requestAgent }; - } - - return options; - } - - private async getUserNameAndPassword(): Promise<{ username: string; password: string }> { - const multistep = this.multiStepFactory.create<{ username: string; password: string }>(); - const state = { username: '', password: '' }; - await multistep.run(this.getUserNameMultiStep.bind(this), state); - return state; - } - - private async getUserNameMultiStep( - input: IMultiStepInput<{ username: string; password: string }>, - state: { username: string; password: string } - ) { - state.username = await input.showInputBox({ - title: localize.DataScience.jupyterSelectUserAndPasswordTitle(), - prompt: localize.DataScience.jupyterSelectUserPrompt(), - validate: this.validateUserNameOrPassword, - value: '' - }); - if (state.username) { - return this.getPasswordMultiStep.bind(this); - } - } - - private async validateUserNameOrPassword(_value: string): Promise { - return undefined; - } - - private async getPasswordMultiStep( - input: IMultiStepInput<{ username: string; password: string }>, - state: { username: string; password: string } - ) { - state.password = await input.showInputBox({ - title: localize.DataScience.jupyterSelectUserAndPasswordTitle(), - prompt: localize.DataScience.jupyterSelectPasswordPrompt(), - validate: this.validateUserNameOrPassword, - value: '', - password: true - }); - } - - private async getUserPassword(): Promise { - return this.appShell.showInputBox({ - prompt: localize.DataScience.jupyterSelectPasswordPrompt(), - ignoreFocusOut: true, - password: true - }); - } - - private async getXSRFToken(url: string): Promise { - let xsrfCookie: string | undefined; - - const response = await this.makeRequest(`${url}login?`, { - method: 'get', - redirect: 'manual', - headers: { Connection: 'keep-alive' } - }); - - if (response.ok) { - const cookies = this.getCookies(response); - if (cookies.has('_xsrf')) { - xsrfCookie = cookies.get('_xsrf')?.split(';')[0]; - } - } - - return xsrfCookie; - } - - private async needPassword(url: string): Promise { - // A jupyter server will redirect if you ask for the tree when a login is required - const response = await this.makeRequest(`${url}tree?`, { - method: 'get', - redirect: 'manual', - headers: { Connection: 'keep-alive' } - }); - - return response.status !== 200; - } - - private async makeRequest(url: string, options: nodeFetch.RequestInit): Promise { - const allowUnauthorized = this.configService.getSettings(undefined).datascience - .allowUnauthorizedRemoteConnection; - - // Try once and see if it fails with unauthorized. - try { - return await this.fetchFunction( - url, - this.addAllowUnauthorized(url, allowUnauthorized ? true : false, options) - ); - } catch (e) { - if (e.message.indexOf('reason: self signed certificate') >= 0) { - // Ask user to change setting and possibly try again. - const enableOption: string = localize.DataScience.jupyterSelfCertEnable(); - const closeOption: string = localize.DataScience.jupyterSelfCertClose(); - const value = await this.appShell.showErrorMessage( - localize.DataScience.jupyterSelfCertFail().format(e.message), - enableOption, - closeOption - ); - if (value === enableOption) { - sendTelemetryEvent(Telemetry.SelfCertsMessageEnabled); - await this.configService.updateSetting( - 'dataScience.allowUnauthorizedRemoteConnection', - true, - undefined, - ConfigurationTarget.Workspace - ); - return this.fetchFunction(url, this.addAllowUnauthorized(url, true, options)); - } else if (value === closeOption) { - sendTelemetryEvent(Telemetry.SelfCertsMessageClose); - } - } - throw e; - } - } - - private async isJupyterHub(url: string): Promise { - // See this for the different REST endpoints: - // https://jupyterhub.readthedocs.io/en/stable/_static/rest-api/index.html - - // If the URL has the /user/ option in it, it's likely this is jupyter hub - if (url.toLowerCase().includes('/user/')) { - return true; - } - - // Otherwise request hub/api. This should return the json with the hub version - // if this is a hub url - const response = await this.makeRequest(`${url}hub/api`, { - method: 'get', - redirect: 'manual', - headers: { Connection: 'keep-alive' } - }); - - return response.status === 200; - } - - // Jupyter uses a session cookie to validate so by hitting the login page with the password we can get that cookie and use it ourselves - // This workflow can be seen by running fiddler and hitting the login page with a browser - // First you need a get at the login page to get the xsrf token, then you send back that token along with the password in a post - // That will return back the session cookie. This session cookie then needs to be added to our requests and websockets for @jupyterlab/services - private async getSessionCookie( - url: string, - xsrfCookie: string, - password: string - ): Promise<{ sessionCookieName: string | undefined; sessionCookieValue: string | undefined }> { - let sessionCookieName: string | undefined; - let sessionCookieValue: string | undefined; - // Create the form params that we need - const postParams = new URLSearchParams(); - postParams.append('_xsrf', xsrfCookie); - postParams.append('password', password); - - const response = await this.makeRequest(`${url}login?`, { - method: 'post', - headers: { - Cookie: `_xsrf=${xsrfCookie}`, - Connection: 'keep-alive', - 'content-type': 'application/x-www-form-urlencoded;charset=UTF-8' - }, - body: postParams.toString(), - redirect: 'manual' - }); - - // Now from this result we need to extract the session cookie - if (response.status === 302) { - const cookies = this.getCookies(response); - - // Session cookie is the first one - if (cookies.size > 0) { - sessionCookieName = cookies.entries().next().value[0]; - sessionCookieValue = cookies.entries().next().value[1]; - } - } - - return { sessionCookieName, sessionCookieValue }; - } - - private getCookies(response: nodeFetch.Response): Map { - const cookieList: Map = new Map(); - - const cookies = response.headers.raw()['set-cookie']; - - if (cookies) { - cookies.forEach((value) => { - const cookieKey = value.substring(0, value.indexOf('=')); - const cookieVal = value.substring(value.indexOf('=') + 1); - cookieList.set(cookieKey, cookieVal); - }); - } - - return cookieList; - } -} diff --git a/src/client/datascience/jupyter/jupyterRequest.ts b/src/client/datascience/jupyter/jupyterRequest.ts deleted file mode 100644 index 027103fb8d87..000000000000 --- a/src/client/datascience/jupyter/jupyterRequest.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import * as nodeFetch from 'node-fetch'; - -// Function for creating node Request object that prevents jupyterlab services from writing its own -// authorization header. -// tslint:disable: no-any -export function createAuthorizingRequest(getAuthHeader: () => any) { - class AuthorizingRequest extends nodeFetch.Request { - constructor(input: nodeFetch.RequestInfo, init?: nodeFetch.RequestInit) { - super(input, init); - - // Add all of the authorization parts onto the headers. - const origHeaders = this.headers; - const authorizationHeader = getAuthHeader(); - const keys = Object.keys(authorizationHeader); - keys.forEach((k) => origHeaders.append(k, authorizationHeader[k].toString())); - origHeaders.append('Content-Type', 'application/json'); - - // Rewrite the 'append' method for the headers to disallow 'authorization' after this point - const origAppend = origHeaders.append.bind(origHeaders); - origHeaders.append = (k, v) => { - if (k.toLowerCase() !== 'authorization') { - origAppend(k, v); - } - }; - } - } - return AuthorizingRequest; -} diff --git a/src/client/datascience/jupyter/jupyterSelfCertsError.ts b/src/client/datascience/jupyter/jupyterSelfCertsError.ts deleted file mode 100644 index 0c2ee41a5ae9..000000000000 --- a/src/client/datascience/jupyter/jupyterSelfCertsError.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; - -export class JupyterSelfCertsError extends Error { - constructor(message: string) { - super(message); - } -} diff --git a/src/client/datascience/jupyter/jupyterServer.ts b/src/client/datascience/jupyter/jupyterServer.ts deleted file mode 100644 index b6e28b3cce6c..000000000000 --- a/src/client/datascience/jupyter/jupyterServer.ts +++ /dev/null @@ -1,281 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as uuid from 'uuid/v4'; -import { Disposable, Uri } from 'vscode'; -import { CancellationToken } from 'vscode-jsonrpc'; -import { ILiveShareApi } from '../../common/application/types'; -import '../../common/extensions'; -import { traceError, traceInfo } from '../../common/logger'; -import { - IAsyncDisposableRegistry, - IConfigurationService, - IDisposableRegistry, - IOutputChannel, - Resource -} from '../../common/types'; -import { createDeferred, Deferred, sleep } from '../../common/utils/async'; -import * as localize from '../../common/utils/localize'; -import { noop } from '../../common/utils/misc'; -import { IServiceContainer } from '../../ioc/types'; -import { captureTelemetry } from '../../telemetry'; -import { Telemetry } from '../constants'; -import { - IJupyterConnection, - IJupyterSession, - IJupyterSessionManager, - IJupyterSessionManagerFactory, - INotebook, - INotebookMetadataLive, - INotebookServer, - INotebookServerLaunchInfo -} from '../types'; -import { getDisplayNameOrNameOfKernelConnection } from './kernels/helpers'; - -// This code is based on the examples here: -// https://www.npmjs.com/package/@jupyterlab/services - -export class JupyterServerBase implements INotebookServer { - private launchInfo: INotebookServerLaunchInfo | undefined; - private _id = uuid(); - private connectPromise: Deferred = createDeferred(); - private connectionInfoDisconnectHandler: Disposable | undefined; - private serverExitCode: number | undefined; - private notebooks = new Map>(); - private sessionManager: IJupyterSessionManager | undefined; - private savedSession: IJupyterSession | undefined; - - constructor( - _liveShare: ILiveShareApi, - private asyncRegistry: IAsyncDisposableRegistry, - private disposableRegistry: IDisposableRegistry, - protected readonly configService: IConfigurationService, - private sessionManagerFactory: IJupyterSessionManagerFactory, - private serviceContainer: IServiceContainer, - private jupyterOutputChannel: IOutputChannel - ) { - this.asyncRegistry.push(this); - traceInfo(`Creating jupyter server: ${this._id}`); - } - - public async connect(launchInfo: INotebookServerLaunchInfo, cancelToken?: CancellationToken): Promise { - traceInfo( - `Connecting server ${this.id} kernelSpec ${getDisplayNameOrNameOfKernelConnection( - launchInfo.kernelConnectionMetadata, - 'unknown' - )}` - ); - - // Save our launch info - this.launchInfo = launchInfo; - - // Indicate connect started - this.connectPromise.resolve(launchInfo); - - // Listen to the process going down - if (this.launchInfo && this.launchInfo.connectionInfo) { - this.connectionInfoDisconnectHandler = this.launchInfo.connectionInfo.disconnected((c) => { - try { - this.serverExitCode = c; - traceError(localize.DataScience.jupyterServerCrashed().format(c.toString())); - this.shutdown().ignoreErrors(); - } catch { - noop(); - } - }); - } - - // Indicate we have a new session on the output channel - this.logRemoteOutput(localize.DataScience.connectingToJupyterUri().format(launchInfo.connectionInfo.baseUrl)); - - // Create our session manager - this.sessionManager = await this.sessionManagerFactory.create(launchInfo.connectionInfo); - - // Try creating a session just to ensure we're connected. Callers of this function check to make sure jupyter - // is running and connectable. - let session: IJupyterSession | undefined; - session = await this.sessionManager.startNew( - launchInfo.kernelConnectionMetadata, - launchInfo.connectionInfo.rootDirectory, - cancelToken - ); - const idleTimeout = this.configService.getSettings().datascience.jupyterLaunchTimeout; - // The wait for idle should throw if we can't connect. - await session.waitForIdle(idleTimeout); - // If that works, save this session for the next notebook to use - this.savedSession = session; - } - - @captureTelemetry(Telemetry.JupyterCreatingNotebook, undefined, true) - public createNotebook( - resource: Resource, - identity: Uri, - notebookMetadata?: INotebookMetadataLive, - cancelToken?: CancellationToken - ): Promise { - if (!this.sessionManager || this.isDisposed) { - throw new Error(localize.DataScience.sessionDisposed()); - } - // If we have a saved session send this into the notebook so we don't create a new one - const savedSession = this.savedSession; - this.savedSession = undefined; - - // Create a notebook and return it. - return this.createNotebookInstance( - resource, - identity, - this.sessionManager, - savedSession, - this.disposableRegistry, - this.configService, - this.serviceContainer, - notebookMetadata, - cancelToken - ).then((r) => { - const baseUrl = this.launchInfo?.connectionInfo.baseUrl || ''; - this.logRemoteOutput(localize.DataScience.createdNewNotebook().format(baseUrl)); - return r; - }); - } - - public async shutdown(): Promise { - try { - // Order should be - // 1) connectionInfoDisconnectHandler - listens to process close - // 2) sessions (owned by the notebooks) - // 3) session manager (owned by this object) - // 4) connInfo (owned by this object) - kills the jupyter process - - if (this.connectionInfoDisconnectHandler) { - this.connectionInfoDisconnectHandler.dispose(); - this.connectionInfoDisconnectHandler = undefined; - } - - // Destroy the kernel spec - await this.destroyKernelSpec(); - - // Remove the saved session if we haven't passed it onto a notebook - if (this.savedSession) { - await this.savedSession.dispose(); - this.savedSession = undefined; - } - - traceInfo(`Shutting down notebooks for ${this.id}`); - const notebooks = await Promise.all([...this.notebooks.values()]); - await Promise.all(notebooks.map((n) => n?.dispose())); - traceInfo(`Shut down session manager : ${this.sessionManager ? 'existing' : 'undefined'}`); - if (this.sessionManager) { - // Session manager in remote case may take too long to shutdown. Don't wait that - // long. - const result = await Promise.race([sleep(10_000), this.sessionManager.dispose()]); - if (result === 10_000) { - traceError(`Session shutdown timed out.`); - } - this.sessionManager = undefined; - } - - // After shutting down notebooks and session manager, kill the main process. - if (this.launchInfo && this.launchInfo.connectionInfo) { - traceInfo('Shutdown server - dispose conn info'); - this.launchInfo.connectionInfo.dispose(); // This should kill the process that's running - this.launchInfo = undefined; - } - } catch (e) { - traceError(`Error during shutdown: `, e); - } - } - - public dispose(): Promise { - return this.shutdown(); - } - - public get id(): string { - return this._id; - } - - public waitForConnect(): Promise { - return this.connectPromise.promise; - } - - // Return a copy of the connection information that this server used to connect with - public getConnectionInfo(): IJupyterConnection | undefined { - if (!this.launchInfo) { - return undefined; - } - - // Return a copy with a no-op for dispose - return { - ...this.launchInfo.connectionInfo, - dispose: noop - }; - } - - public getDisposedError(): Error { - // We may have been disposed because of a crash. See if our connection info is indicating shutdown - if (this.serverExitCode) { - return new Error(localize.DataScience.jupyterServerCrashed().format(this.serverExitCode.toString())); - } - - // Default is just say session was disposed - return new Error(localize.DataScience.sessionDisposed()); - } - - public async getNotebook(identity: Uri): Promise { - return this.notebooks.get(identity.toString()); - } - - protected getNotebooks(): Promise[] { - return [...this.notebooks.values()]; - } - - protected setNotebook(identity: Uri, notebook: Promise) { - const removeNotebook = () => { - if (this.notebooks.get(identity.toString()) === notebook) { - this.notebooks.delete(identity.toString()); - } - }; - - notebook - .then((nb) => { - const oldDispose = nb.dispose; - nb.dispose = () => { - this.notebooks.delete(identity.toString()); - return oldDispose(); - }; - }) - .catch(removeNotebook); - - // Save the notebook - this.notebooks.set(identity.toString(), notebook); - } - - protected createNotebookInstance( - _resource: Resource, - _identity: Uri, - _sessionManager: IJupyterSessionManager, - _savedSession: IJupyterSession | undefined, - _disposableRegistry: IDisposableRegistry, - _configService: IConfigurationService, - _serviceContainer: IServiceContainer, - _notebookMetadata?: INotebookMetadataLive, - _cancelToken?: CancellationToken - ): Promise { - throw new Error('You forgot to override createNotebookInstance'); - } - - protected get isDisposed(): boolean { - throw new Error('You forgot to override isDisposed'); - } - - private async destroyKernelSpec() { - if (this.launchInfo) { - this.launchInfo.kernelConnectionMetadata = undefined; - } - } - - private logRemoteOutput(output: string) { - if (this.launchInfo && !this.launchInfo.connectionInfo.localLaunch) { - this.jupyterOutputChannel.appendLine(output); - } - } -} diff --git a/src/client/datascience/jupyter/jupyterServerWrapper.ts b/src/client/datascience/jupyter/jupyterServerWrapper.ts deleted file mode 100644 index 426b1331ec31..000000000000 --- a/src/client/datascience/jupyter/jupyterServerWrapper.ts +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import type { nbformat } from '@jupyterlab/coreutils'; -import { inject, injectable, named } from 'inversify'; -import * as uuid from 'uuid/v4'; -import { Uri } from 'vscode'; -import { CancellationToken } from 'vscode-jsonrpc'; -import * as vsls from 'vsls/vscode'; -import { IApplicationShell, ILiveShareApi, IWorkspaceService } from '../../common/application/types'; -import '../../common/extensions'; - -import { - IAsyncDisposableRegistry, - IConfigurationService, - IDisposableRegistry, - IOutputChannel, - Resource -} from '../../common/types'; -import { IInterpreterService } from '../../interpreter/contracts'; -import { IServiceContainer } from '../../ioc/types'; -import { DataScienceStartupTime, JUPYTER_OUTPUT_CHANNEL } from '../constants'; -import { - IDataScienceFileSystem, - IJupyterConnection, - IJupyterSessionManagerFactory, - INotebook, - INotebookServer, - INotebookServerLaunchInfo -} from '../types'; -import { KernelSelector } from './kernels/kernelSelector'; -import { GuestJupyterServer } from './liveshare/guestJupyterServer'; -import { HostJupyterServer } from './liveshare/hostJupyterServer'; -import { IRoleBasedObject, RoleBasedFactory } from './liveshare/roleBasedFactory'; -import { ILiveShareHasRole } from './liveshare/types'; - -interface IJupyterServerInterface extends IRoleBasedObject, INotebookServer {} - -// tslint:disable:callable-types -type JupyterServerClassType = { - new ( - liveShare: ILiveShareApi, - startupTime: number, - asyncRegistry: IAsyncDisposableRegistry, - disposableRegistry: IDisposableRegistry, - configService: IConfigurationService, - sessionManager: IJupyterSessionManagerFactory, - workspaceService: IWorkspaceService, - serviceContainer: IServiceContainer, - appShell: IApplicationShell, - fs: IDataScienceFileSystem, - kernelSelector: KernelSelector, - interpreterService: IInterpreterService, - outputChannel: IOutputChannel - ): IJupyterServerInterface; -}; -// tslint:enable:callable-types - -// This class wraps either a HostJupyterServer or a GuestJupyterServer based on the liveshare state. It abstracts -// out the live share specific parts. -@injectable() -export class JupyterServerWrapper implements INotebookServer, ILiveShareHasRole { - private serverFactory: RoleBasedFactory; - - private launchInfo: INotebookServerLaunchInfo | undefined; - private _id: string = uuid(); - - constructor( - @inject(ILiveShareApi) liveShare: ILiveShareApi, - @inject(DataScienceStartupTime) startupTime: number, - @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, - @inject(IAsyncDisposableRegistry) asyncRegistry: IAsyncDisposableRegistry, - @inject(IConfigurationService) configService: IConfigurationService, - @inject(IJupyterSessionManagerFactory) sessionManager: IJupyterSessionManagerFactory, - @inject(IWorkspaceService) workspaceService: IWorkspaceService, - @inject(IApplicationShell) appShell: IApplicationShell, - @inject(IDataScienceFileSystem) fs: IDataScienceFileSystem, - @inject(IInterpreterService) interpreterService: IInterpreterService, - @inject(KernelSelector) kernelSelector: KernelSelector, - @inject(IOutputChannel) @named(JUPYTER_OUTPUT_CHANNEL) jupyterOutput: IOutputChannel, - @inject(IServiceContainer) serviceContainer: IServiceContainer - ) { - // The server factory will create the appropriate HostJupyterServer or GuestJupyterServer based on - // the liveshare state. - this.serverFactory = new RoleBasedFactory( - liveShare, - HostJupyterServer, - GuestJupyterServer, - liveShare, - startupTime, - asyncRegistry, - disposableRegistry, - configService, - sessionManager, - workspaceService, - serviceContainer, - appShell, - fs, - kernelSelector, - interpreterService, - jupyterOutput - ); - } - - public get role(): vsls.Role { - return this.serverFactory.role; - } - - public get id(): string { - return this._id; - } - - public async connect(launchInfo: INotebookServerLaunchInfo, cancelToken?: CancellationToken): Promise { - this.launchInfo = launchInfo; - const server = await this.serverFactory.get(); - return server.connect(launchInfo, cancelToken); - } - - public async createNotebook( - resource: Resource, - identity: Uri, - notebookMetadata?: nbformat.INotebookMetadata, - cancelToken?: CancellationToken - ): Promise { - const server = await this.serverFactory.get(); - return server.createNotebook(resource, identity, notebookMetadata, cancelToken); - } - - public async shutdown(): Promise { - const server = await this.serverFactory.get(); - return server.shutdown(); - } - - public async dispose(): Promise { - const server = await this.serverFactory.get(); - return server.dispose(); - } - - // Return a copy of the connection information that this server used to connect with - public getConnectionInfo(): IJupyterConnection | undefined { - if (this.launchInfo) { - return this.launchInfo.connectionInfo; - } - - return undefined; - } - - public async getNotebook(resource: Uri, token?: CancellationToken): Promise { - const server = await this.serverFactory.get(); - return server.getNotebook(resource, token); - } - - public async waitForConnect(): Promise { - const server = await this.serverFactory.get(); - return server.waitForConnect(); - } -} diff --git a/src/client/datascience/jupyter/jupyterSession.ts b/src/client/datascience/jupyter/jupyterSession.ts deleted file mode 100644 index 71d4309cdd88..000000000000 --- a/src/client/datascience/jupyter/jupyterSession.ts +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import type { - Contents, - ContentsManager, - Kernel, - ServerConnection, - Session, - SessionManager -} from '@jupyterlab/services'; -import * as path from 'path'; -import * as uuid from 'uuid/v4'; -import { CancellationToken } from 'vscode-jsonrpc'; -import { Cancellation } from '../../common/cancellation'; -import { traceError, traceInfo } from '../../common/logger'; -import { IOutputChannel } from '../../common/types'; -import * as localize from '../../common/utils/localize'; -import { captureTelemetry } from '../../telemetry'; -import { BaseJupyterSession, JupyterSessionStartError } from '../baseJupyterSession'; -import { Telemetry } from '../constants'; -import { reportAction } from '../progress/decorator'; -import { ReportableAction } from '../progress/types'; -import { IJupyterConnection, ISessionWithSocket } from '../types'; -import { JupyterInvalidKernelError } from './jupyterInvalidKernelError'; -import { JupyterWebSockets } from './jupyterWebSocket'; -import { getNameOfKernelConnection } from './kernels/helpers'; -import { KernelConnectionMetadata } from './kernels/types'; - -export class JupyterSession extends BaseJupyterSession { - constructor( - private connInfo: IJupyterConnection, - private serverSettings: ServerConnection.ISettings, - kernelSpec: KernelConnectionMetadata | undefined, - private sessionManager: SessionManager, - private contentsManager: ContentsManager, - private readonly outputChannel: IOutputChannel, - private readonly restartSessionCreated: (id: Kernel.IKernelConnection) => void, - restartSessionUsed: (id: Kernel.IKernelConnection) => void, - readonly workingDirectory: string, - private readonly idleTimeout: number - ) { - super(restartSessionUsed, workingDirectory); - this.kernelConnectionMetadata = kernelSpec; - } - - @reportAction(ReportableAction.JupyterSessionWaitForIdleSession) - @captureTelemetry(Telemetry.WaitForIdleJupyter, undefined, true) - public waitForIdle(timeout: number): Promise { - // Wait for idle on this session - return this.waitForIdleOnSession(this.session, timeout); - } - - public async connect(timeoutMs: number, cancelToken?: CancellationToken): Promise { - if (!this.connInfo) { - throw new Error(localize.DataScience.sessionDisposed()); - } - - // Start a new session - this.setSession(await this.createNewKernelSession(this.kernelConnectionMetadata, timeoutMs, cancelToken)); - - // Listen for session status changes - this.session?.statusChanged.connect(this.statusHandler); // NOSONAR - - // Made it this far, we're connected now - this.connected = true; - } - - public async createNewKernelSession( - kernelConnection: KernelConnectionMetadata | undefined, - timeoutMS: number, - cancelToken?: CancellationToken - ): Promise { - let newSession: ISessionWithSocket | undefined; - - try { - // Don't immediately assume this kernel is valid. Try creating a session with it first. - if ( - kernelConnection && - kernelConnection.kind === 'connectToLiveKernel' && - kernelConnection.kernelModel.id - ) { - // Remote case. - newSession = this.sessionManager.connectTo(kernelConnection.kernelModel.session); - newSession.isRemoteSession = true; - } else { - newSession = await this.createSession(this.serverSettings, kernelConnection, cancelToken); - } - - // Make sure it is idle before we return - await this.waitForIdleOnSession(newSession, timeoutMS); - } catch (exc) { - traceError('Failed to change kernel', exc); - // Throw a new exception indicating we cannot change. - throw new JupyterInvalidKernelError(kernelConnection); - } - - return newSession; - } - - protected async createRestartSession( - kernelConnection: KernelConnectionMetadata | undefined, - session: ISessionWithSocket, - cancelToken?: CancellationToken - ): Promise { - // We need all of the above to create a restart session - if (!session || !this.contentsManager || !this.sessionManager) { - throw new Error(localize.DataScience.sessionDisposed()); - } - let result: ISessionWithSocket | undefined; - let tryCount = 0; - // tslint:disable-next-line: no-any - let exception: any; - while (tryCount < 3) { - try { - result = await this.createSession(session.serverSettings, kernelConnection, cancelToken); - await this.waitForIdleOnSession(result, this.idleTimeout); - this.restartSessionCreated(result.kernel); - return result; - } catch (exc) { - traceInfo(`Error waiting for restart session: ${exc}`); - tryCount += 1; - if (result) { - this.shutdownSession(result, undefined).ignoreErrors(); - } - result = undefined; - exception = exc; - } - } - throw exception; - } - - protected startRestartSession() { - if (!this.restartSessionPromise && this.session && this.contentsManager) { - this.restartSessionPromise = this.createRestartSession(this.kernelConnectionMetadata, this.session); - } - } - - private async createBackingFile(): Promise { - let backingFile: Contents.IModel; - - // First make sure the notebook is in the right relative path (jupyter expects a relative path with unix delimiters) - const relativeDirectory = path.relative(this.connInfo.rootDirectory, this.workingDirectory).replace(/\\/g, '/'); - - // However jupyter does not support relative paths outside of the original root. - const backingFileOptions: Contents.ICreateOptions = - this.connInfo.localLaunch && !relativeDirectory.startsWith('..') - ? { type: 'notebook', path: relativeDirectory } - : { type: 'notebook' }; - - try { - // Create a temporary notebook for this session. Each needs a unique name (otherwise we get the same session every time) - backingFile = await this.contentsManager.newUntitled(backingFileOptions); - const backingFileDir = path.dirname(backingFile.path); - backingFile = await this.contentsManager.rename( - backingFile.path, - backingFileDir.length && backingFileDir !== '.' - ? `${backingFileDir}/t-${uuid()}.ipynb` - : `t-${uuid()}.ipynb` // Note, the docs say the path uses UNIX delimiters. - ); - } catch (exc) { - // If it failed for local, try without a relative directory - if (this.connInfo.localLaunch) { - backingFile = await this.contentsManager.newUntitled({ type: 'notebook' }); - const backingFileDir = path.dirname(backingFile.path); - backingFile = await this.contentsManager.rename( - backingFile.path, - backingFileDir.length && backingFileDir !== '.' - ? `${backingFileDir}/t-${uuid()}.ipynb` - : `t-${uuid()}.ipynb` // Note, the docs say the path uses UNIX delimiters. - ); - } else { - throw exc; - } - } - - if (backingFile) { - return backingFile; - } - throw new Error(`Backing file cannot be generated for Jupyter connection`); - } - - private async createSession( - serverSettings: ServerConnection.ISettings, - kernelConnection: KernelConnectionMetadata | undefined, - cancelToken?: CancellationToken - ): Promise { - // Create our backing file for the notebook - const backingFile = await this.createBackingFile(); - - // Create our session options using this temporary notebook and our connection info - const options: Session.IOptions = { - path: backingFile.path, - kernelName: getNameOfKernelConnection(kernelConnection) || '', - name: uuid(), // This is crucial to distinguish this session from any other. - serverSettings: serverSettings - }; - - return Cancellation.race( - () => - this.sessionManager!.startNew(options) - .then(async (session) => { - this.logRemoteOutput( - localize.DataScience.createdNewKernel().format(this.connInfo.baseUrl, session.kernel.id) - ); - - // Add on the kernel sock information - // tslint:disable-next-line: no-any - (session as any).kernelSocketInformation = { - socket: JupyterWebSockets.get(session.kernel.id), - options: { - clientId: session.kernel.clientId, - id: session.kernel.id, - model: { ...session.kernel.model }, - userName: session.kernel.username - } - }; - - return session; - }) - .catch((ex) => Promise.reject(new JupyterSessionStartError(ex))) - .finally(() => { - if (this.connInfo) { - this.contentsManager.delete(backingFile.path).ignoreErrors(); - } - }), - cancelToken - ); - } - - private logRemoteOutput(output: string) { - if (this.connInfo && !this.connInfo.localLaunch) { - this.outputChannel.appendLine(output); - } - } -} diff --git a/src/client/datascience/jupyter/jupyterSessionManager.ts b/src/client/datascience/jupyter/jupyterSessionManager.ts deleted file mode 100644 index 7b2ad64eb28e..000000000000 --- a/src/client/datascience/jupyter/jupyterSessionManager.ts +++ /dev/null @@ -1,390 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import type { ContentsManager, Kernel, ServerConnection, Session, SessionManager } from '@jupyterlab/services'; -import { Agent as HttpsAgent } from 'https'; -import * as nodeFetch from 'node-fetch'; -import { EventEmitter } from 'vscode'; -import { CancellationToken } from 'vscode-jsonrpc'; -import { IApplicationShell } from '../../common/application/types'; - -import { traceError, traceInfo } from '../../common/logger'; -import { IConfigurationService, IOutputChannel, IPersistentState, IPersistentStateFactory } from '../../common/types'; -import { sleep } from '../../common/utils/async'; -import * as localize from '../../common/utils/localize'; -import { noop } from '../../common/utils/misc'; -import { - IJupyterConnection, - IJupyterKernel, - IJupyterKernelSpec, - IJupyterPasswordConnect, - IJupyterSession, - IJupyterSessionManager -} from '../types'; -import { createAuthorizingRequest } from './jupyterRequest'; -import { JupyterSession } from './jupyterSession'; -import { createJupyterWebSocket } from './jupyterWebSocket'; -import { createDefaultKernelSpec } from './kernels/helpers'; -import { JupyterKernelSpec } from './kernels/jupyterKernelSpec'; -import { KernelConnectionMetadata } from './kernels/types'; - -// Key for our insecure connection global state -const GlobalStateUserAllowsInsecureConnections = 'DataScienceAllowInsecureConnections'; - -// tslint:disable: no-any - -export class JupyterSessionManager implements IJupyterSessionManager { - private static secureServers = new Map>(); - private sessionManager: SessionManager | undefined; - private contentsManager: ContentsManager | undefined; - private connInfo: IJupyterConnection | undefined; - private serverSettings: ServerConnection.ISettings | undefined; - private _jupyterlab?: typeof import('@jupyterlab/services'); - private readonly userAllowsInsecureConnections: IPersistentState; - private restartSessionCreatedEvent = new EventEmitter(); - private restartSessionUsedEvent = new EventEmitter(); - private get jupyterlab(): typeof import('@jupyterlab/services') { - if (!this._jupyterlab) { - // tslint:disable-next-line: no-require-imports - this._jupyterlab = require('@jupyterlab/services'); - } - return this._jupyterlab!; - } - constructor( - private jupyterPasswordConnect: IJupyterPasswordConnect, - _config: IConfigurationService, - private failOnPassword: boolean | undefined, - private outputChannel: IOutputChannel, - private configService: IConfigurationService, - private readonly appShell: IApplicationShell, - private readonly stateFactory: IPersistentStateFactory - ) { - this.userAllowsInsecureConnections = this.stateFactory.createGlobalPersistentState( - GlobalStateUserAllowsInsecureConnections, - false - ); - } - - public get onRestartSessionCreated() { - return this.restartSessionCreatedEvent.event; - } - - public get onRestartSessionUsed() { - return this.restartSessionUsedEvent.event; - } - public async dispose() { - traceInfo(`Disposing session manager`); - try { - if (this.contentsManager) { - traceInfo('SessionManager - dispose contents manager'); - this.contentsManager.dispose(); - this.contentsManager = undefined; - } - if (this.sessionManager && !this.sessionManager.isDisposed) { - traceInfo('ShutdownSessionAndConnection - dispose session manager'); - // Make sure it finishes startup. - await Promise.race([sleep(10_000), this.sessionManager.ready]); - - // tslint:disable-next-line: no-any - const sessionManager = this.sessionManager as any; - this.sessionManager.dispose(); // Note, shutting down all will kill all kernels on the same connection. We don't want that. - this.sessionManager = undefined; - - // The session manager can actually be stuck in the context of a timer. Clear out the specs inside of - // it so the memory for the session is minimized. Otherwise functional tests can run out of memory - if (sessionManager._specs) { - sessionManager._specs = {}; - } - if (sessionManager._sessions && sessionManager._sessions.clear) { - sessionManager._sessions.clear(); - } - if (sessionManager._pollModels) { - this.clearPoll(sessionManager._pollModels); - } - if (sessionManager._pollSpecs) { - this.clearPoll(sessionManager._pollSpecs); - } - } - } catch (e) { - traceError(`Exception on session manager shutdown: `, e); - } finally { - traceInfo('Finished disposing jupyter session manager'); - } - } - - public getConnInfo(): IJupyterConnection { - return this.connInfo!; - } - - public async initialize(connInfo: IJupyterConnection): Promise { - this.connInfo = connInfo; - this.serverSettings = await this.getServerConnectSettings(connInfo); - this.sessionManager = new this.jupyterlab.SessionManager({ serverSettings: this.serverSettings }); - this.contentsManager = new this.jupyterlab.ContentsManager({ serverSettings: this.serverSettings }); - } - - public async getRunningSessions(): Promise { - if (!this.sessionManager) { - return []; - } - // Not refreshing will result in `running` returning an empty iterator. - await this.sessionManager.refreshRunning(); - - const sessions: Session.IModel[] = []; - const iterator = this.sessionManager.running(); - let session = iterator.next(); - - while (session) { - sessions.push(session); - session = iterator.next(); - } - - return sessions; - } - - public async getRunningKernels(): Promise { - const models = await this.jupyterlab.Kernel.listRunning(this.serverSettings); - // Remove duplicates. - const dup = new Set(); - return models - .map((m) => { - return { - id: m.id, - name: m.name, - lastActivityTime: m.last_activity ? new Date(Date.parse(m.last_activity.toString())) : new Date(), - numberOfConnections: m.connections ? parseInt(m.connections.toString(), 10) : 0 - }; - }) - .filter((item) => { - if (dup.has(item.id)) { - return false; - } - dup.add(item.id); - return true; - }); - } - - public async startNew( - kernelConnection: KernelConnectionMetadata | undefined, - workingDirectory: string, - cancelToken?: CancellationToken - ): Promise { - if (!this.connInfo || !this.sessionManager || !this.contentsManager || !this.serverSettings) { - throw new Error(localize.DataScience.sessionDisposed()); - } - // Create a new session and attempt to connect to it - const session = new JupyterSession( - this.connInfo, - this.serverSettings, - kernelConnection, - this.sessionManager, - this.contentsManager, - this.outputChannel, - this.restartSessionCreatedEvent.fire.bind(this.restartSessionCreatedEvent), - this.restartSessionUsedEvent.fire.bind(this.restartSessionUsedEvent), - workingDirectory, - this.configService.getSettings().datascience.jupyterLaunchTimeout - ); - try { - await session.connect(this.configService.getSettings().datascience.jupyterLaunchTimeout, cancelToken); - } finally { - if (!session.isConnected) { - await session.dispose(); - } - } - return session; - } - - public async getKernelSpecs(): Promise { - if (!this.connInfo || !this.sessionManager || !this.contentsManager) { - throw new Error(localize.DataScience.sessionDisposed()); - } - try { - // Fetch the list the session manager already knows about. Refreshing may not work. - const oldKernelSpecs = - this.sessionManager.specs && Object.keys(this.sessionManager.specs.kernelspecs).length - ? this.sessionManager.specs.kernelspecs - : {}; - - // Wait for the session to be ready - await Promise.race([sleep(10_000), this.sessionManager.ready]); - - // Ask the session manager to refresh its list of kernel specs. This might never - // come back so only wait for ten seconds. - await Promise.race([sleep(10_000), this.sessionManager.refreshSpecs()]); - - // Enumerate all of the kernel specs, turning each into a JupyterKernelSpec - const kernelspecs = - this.sessionManager.specs && Object.keys(this.sessionManager.specs.kernelspecs).length - ? this.sessionManager.specs.kernelspecs - : oldKernelSpecs; - const keys = Object.keys(kernelspecs); - if (keys && keys.length) { - return keys.map((k) => { - const spec = kernelspecs[k]; - return new JupyterKernelSpec(spec) as IJupyterKernelSpec; - }); - } else { - traceError(`SessionManager cannot enumerate kernelspecs. Returning default.`); - // If for some reason the session manager refuses to communicate, fall - // back to a default. This may not exist, but it's likely. - return [createDefaultKernelSpec()]; - } - } catch (e) { - traceError(`SessionManager:getKernelSpecs failure: `, e); - // For some reason this is failing. Just return nothing - return []; - } - } - - // tslint:disable-next-line: no-any - private clearPoll(poll: { _timeout: any }) { - try { - clearTimeout(poll._timeout); - } catch { - noop(); - } - } - - private async getServerConnectSettings(connInfo: IJupyterConnection): Promise { - let serverSettings: Partial = { - baseUrl: connInfo.baseUrl, - appUrl: '', - // A web socket is required to allow token authentication - wsUrl: connInfo.baseUrl.replace('http', 'ws') - }; - - // Before we connect, see if we are trying to make an insecure connection, if we are, warn the user - await this.secureConnectionCheck(connInfo); - - // Agent is allowed to be set on this object, but ts doesn't like it on RequestInit, so any - // tslint:disable-next-line:no-any - let requestInit: any = { cache: 'no-store', credentials: 'same-origin' }; - let cookieString; - // tslint:disable-next-line: no-any - let requestCtor: any = nodeFetch.Request; - - // If authorization header is provided, then we need to prevent jupyterlab services from - // writing the authorization header. - if (connInfo.getAuthHeader) { - requestCtor = createAuthorizingRequest(connInfo.getAuthHeader); - } - - // If no token is specified prompt for a password - if ((connInfo.token === '' || connInfo.token === 'null') && !connInfo.getAuthHeader) { - if (this.failOnPassword) { - throw new Error('Password request not allowed.'); - } - serverSettings = { ...serverSettings, token: '' }; - const pwSettings = await this.jupyterPasswordConnect.getPasswordConnectionInfo(connInfo.baseUrl); - if (pwSettings && pwSettings.requestHeaders) { - requestInit = { ...requestInit, headers: pwSettings.requestHeaders }; - cookieString = (pwSettings.requestHeaders as any).Cookie || ''; - - // Password may have overwritten the base url and token as well - if (pwSettings.remappedBaseUrl) { - (serverSettings as any).baseUrl = pwSettings.remappedBaseUrl; - (serverSettings as any).wsUrl = pwSettings.remappedBaseUrl.replace('http', 'ws'); - } - if (pwSettings.remappedToken) { - (serverSettings as any).token = pwSettings.remappedToken; - } - } else if (pwSettings) { - serverSettings = { ...serverSettings, token: connInfo.token }; - } else { - // Failed to get password info, notify the user - throw new Error(localize.DataScience.passwordFailure()); - } - } else { - serverSettings = { ...serverSettings, token: connInfo.token }; - } - - const allowUnauthorized = this.configService.getSettings(undefined).datascience - .allowUnauthorizedRemoteConnection; - // If this is an https connection and we want to allow unauthorized connections set that option on our agent - // we don't need to save the agent as the previous behaviour is just to create a temporary default agent when not specified - if (connInfo.baseUrl.startsWith('https') && allowUnauthorized) { - const requestAgent = new HttpsAgent({ rejectUnauthorized: false }); - requestInit = { ...requestInit, agent: requestAgent }; - } - - // This replaces the WebSocket constructor in jupyter lab services with our own implementation - // See _createSocket here: - // https://github.com/jupyterlab/jupyterlab/blob/cfc8ebda95e882b4ed2eefd54863bb8cdb0ab763/packages/services/src/kernel/default.ts - serverSettings = { - ...serverSettings, - init: requestInit, - WebSocket: createJupyterWebSocket( - cookieString, - allowUnauthorized, - connInfo.getAuthHeader - // tslint:disable-next-line:no-any - ) as any, - // Redefine fetch to our node-modules so it picks up the correct version. - // Typecasting as any works fine as long as all 3 of these are the same version - // tslint:disable-next-line:no-any - fetch: nodeFetch.default as any, - // tslint:disable-next-line:no-any - Request: requestCtor, - // tslint:disable-next-line:no-any - Headers: nodeFetch.Headers as any - }; - - traceInfo(`Creating server with settings : ${JSON.stringify(serverSettings)}`); - return this.jupyterlab.ServerConnection.makeSettings(serverSettings); - } - - // If connecting on HTTP without a token prompt the user that this connection may not be secure - private async insecureServerWarningPrompt(): Promise { - const insecureMessage = localize.DataScience.insecureSessionMessage(); - const insecureLabels = [ - localize.Common.bannerLabelYes(), - localize.Common.bannerLabelNo(), - localize.Common.doNotShowAgain() - ]; - const response = await this.appShell.showWarningMessage(insecureMessage, ...insecureLabels); - - switch (response) { - case localize.Common.bannerLabelYes(): - // On yes just proceed as normal - return true; - - case localize.Common.doNotShowAgain(): - // For don't ask again turn on the global true - await this.userAllowsInsecureConnections.updateValue(true); - return true; - - case localize.Common.bannerLabelNo(): - default: - // No or for no choice return back false to block - return false; - } - } - - // Check if our server connection is considered secure. If it is not, ask the user if they want to connect - // If not, throw to bail out on the process - private async secureConnectionCheck(connInfo: IJupyterConnection): Promise { - // If they have turned on global server trust then everything is secure - if (this.userAllowsInsecureConnections.value) { - return; - } - - // If they are local launch, https, or have a token, then they are secure - if (connInfo.localLaunch || connInfo.baseUrl.startsWith('https') || connInfo.token !== 'null') { - return; - } - - // At this point prompt the user, cache the promise so we don't ask multiple times for the same server - let serverSecurePromise = JupyterSessionManager.secureServers.get(connInfo.baseUrl); - - if (serverSecurePromise === undefined) { - serverSecurePromise = this.insecureServerWarningPrompt(); - JupyterSessionManager.secureServers.set(connInfo.baseUrl, serverSecurePromise); - } - - // If our server is not secure, throw here to bail out on the process - if (!(await serverSecurePromise)) { - throw new Error(localize.DataScience.insecureSessionDenied()); - } - } -} diff --git a/src/client/datascience/jupyter/jupyterSessionManagerFactory.ts b/src/client/datascience/jupyter/jupyterSessionManagerFactory.ts deleted file mode 100644 index 25a991fb4572..000000000000 --- a/src/client/datascience/jupyter/jupyterSessionManagerFactory.ts +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { inject, injectable, named } from 'inversify'; -import { IApplicationShell } from '../../common/application/types'; - -import type { Kernel } from '@jupyterlab/services'; -import { EventEmitter } from 'vscode'; -import { - IConfigurationService, - IDisposableRegistry, - IOutputChannel, - IPersistentStateFactory -} from '../../common/types'; -import { JUPYTER_OUTPUT_CHANNEL } from '../constants'; -import { - IJupyterConnection, - IJupyterPasswordConnect, - IJupyterSessionManager, - IJupyterSessionManagerFactory -} from '../types'; -import { JupyterSessionManager } from './jupyterSessionManager'; - -@injectable() -export class JupyterSessionManagerFactory implements IJupyterSessionManagerFactory { - private restartSessionCreatedEvent = new EventEmitter(); - private restartSessionUsedEvent = new EventEmitter(); - constructor( - @inject(IJupyterPasswordConnect) private jupyterPasswordConnect: IJupyterPasswordConnect, - @inject(IConfigurationService) private config: IConfigurationService, - @inject(IOutputChannel) @named(JUPYTER_OUTPUT_CHANNEL) private jupyterOutput: IOutputChannel, - @inject(IApplicationShell) private readonly appShell: IApplicationShell, - @inject(IPersistentStateFactory) private readonly stateFactory: IPersistentStateFactory, - @inject(IDisposableRegistry) private readonly disposableRegistry: IDisposableRegistry - ) {} - - /** - * Creates a new IJupyterSessionManager. - * @param connInfo - connection information to the server that's already running. - * @param failOnPassword - whether or not to fail the creation if a password is required. - */ - public async create(connInfo: IJupyterConnection, failOnPassword?: boolean): Promise { - const result = new JupyterSessionManager( - this.jupyterPasswordConnect, - this.config, - failOnPassword, - this.jupyterOutput, - this.config, - this.appShell, - this.stateFactory - ); - await result.initialize(connInfo); - this.disposableRegistry.push( - result.onRestartSessionCreated(this.restartSessionCreatedEvent.fire.bind(this.restartSessionCreatedEvent)) - ); - this.disposableRegistry.push( - result.onRestartSessionUsed(this.restartSessionUsedEvent.fire.bind(this.restartSessionUsedEvent)) - ); - return result; - } - - public get onRestartSessionCreated() { - return this.restartSessionCreatedEvent.event; - } - - public get onRestartSessionUsed() { - return this.restartSessionUsedEvent.event; - } -} diff --git a/src/client/datascience/jupyter/jupyterUtils.ts b/src/client/datascience/jupyter/jupyterUtils.ts deleted file mode 100644 index 73054c152556..000000000000 --- a/src/client/datascience/jupyter/jupyterUtils.ts +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; - -import * as fs from 'fs-extra'; -import * as path from 'path'; -import { Uri } from 'vscode'; - -import { IWorkspaceService } from '../../common/application/types'; -import { Resource } from '../../common/types'; -import { noop } from '../../common/utils/misc'; -import { SystemVariables } from '../../common/variables/systemVariables'; -import { getJupyterConnectionDisplayName } from '../jupyter/jupyterConnection'; -import { IJupyterConnection, IJupyterServerUri } from '../types'; - -export function expandWorkingDir( - workingDir: string | undefined, - launchingFile: string | undefined, - workspace: IWorkspaceService -): string { - if (workingDir) { - const variables = new SystemVariables( - launchingFile ? Uri.file(launchingFile) : undefined, - workspace.rootPath, - workspace - ); - return variables.resolve(workingDir); - } - - // No working dir, just use the path of the launching file. - if (launchingFile) { - return path.dirname(launchingFile); - } - - // No launching file or working dir. Just use the default workspace folder - const workspaceFolder = workspace.getWorkspaceFolder(undefined); - if (workspaceFolder) { - return workspaceFolder.uri.fsPath; - } - - return process.cwd(); -} - -export function createRemoteConnectionInfo( - uri: string, - getJupyterServerUri: (uri: string) => IJupyterServerUri | undefined -): IJupyterConnection { - let url: URL; - try { - url = new URL(uri); - } catch (err) { - // This should already have been parsed when set, so just throw if it's not right here - throw err; - } - - const serverUri = getJupyterServerUri(uri); - const baseUrl = serverUri ? serverUri.baseUrl : `${url.protocol}//${url.host}${url.pathname}`; - const token = serverUri ? serverUri.token : `${url.searchParams.get('token')}`; - const hostName = serverUri ? new URL(serverUri.baseUrl).hostname : url.hostname; - - return { - type: 'jupyter', - baseUrl, - token, - hostName, - localLaunch: false, - localProcExitCode: undefined, - valid: true, - displayName: - serverUri && serverUri.displayName - ? serverUri.displayName - : getJupyterConnectionDisplayName(token, baseUrl), - disconnected: (_l) => { - return { dispose: noop }; - }, - dispose: noop, - rootDirectory: '', - getAuthHeader: serverUri ? () => getJupyterServerUri(uri)?.authorizationHeader : undefined, - url: uri - }; -} - -export async function computeWorkingDirectory(resource: Resource, workspace: IWorkspaceService): Promise { - // Returning directly doesn't seem to work (typescript complains) - // tslint:disable-next-line: no-unnecessary-local-variable - const workingDirectory = - resource && resource.scheme === 'file' && (await fs.pathExists(path.dirname(resource.fsPath))) - ? path.dirname(resource.fsPath) - : workspace.getWorkspaceFolder(resource)?.uri.fsPath || process.cwd(); - - return workingDirectory; -} diff --git a/src/client/datascience/jupyter/jupyterVariables.ts b/src/client/datascience/jupyter/jupyterVariables.ts deleted file mode 100644 index a9cf9958a118..000000000000 --- a/src/client/datascience/jupyter/jupyterVariables.ts +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import type { JSONObject } from '@phosphor/coreutils'; -import { inject, injectable, named } from 'inversify'; - -import { Event, EventEmitter } from 'vscode'; -import { ServerStatus } from '../../../datascience-ui/interactive-common/mainState'; -import { RunByLine } from '../../common/experiments/groups'; -import { IDisposableRegistry, IExperimentsManager } from '../../common/types'; -import { captureTelemetry } from '../../telemetry'; -import { Identifiers, Telemetry } from '../constants'; -import { - IConditionalJupyterVariables, - IJupyterVariable, - IJupyterVariables, - IJupyterVariablesRequest, - IJupyterVariablesResponse, - INotebook -} from '../types'; - -/** - * This class provides variable data for showing in the interactive window or a notebook. - * It multiplexes to either one that will use the jupyter kernel or one that uses the debugger. - */ -@injectable() -export class JupyterVariables implements IJupyterVariables { - private refreshEventEmitter = new EventEmitter(); - - constructor( - @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, - @inject(IExperimentsManager) private experimentsManager: IExperimentsManager, - @inject(IJupyterVariables) @named(Identifiers.OLD_VARIABLES) private oldVariables: IJupyterVariables, - @inject(IJupyterVariables) @named(Identifiers.KERNEL_VARIABLES) private kernelVariables: IJupyterVariables, - @inject(IJupyterVariables) - @named(Identifiers.DEBUGGER_VARIABLES) - private debuggerVariables: IConditionalJupyterVariables - ) { - disposableRegistry.push(debuggerVariables.refreshRequired(this.fireRefresh.bind(this))); - disposableRegistry.push(kernelVariables.refreshRequired(this.fireRefresh.bind(this))); - disposableRegistry.push(oldVariables.refreshRequired(this.fireRefresh.bind(this))); - } - - public get refreshRequired(): Event { - return this.refreshEventEmitter.event; - } - - // IJupyterVariables implementation - @captureTelemetry(Telemetry.VariableExplorerFetchTime, undefined, true) - public async getVariables( - notebook: INotebook, - request: IJupyterVariablesRequest - ): Promise { - return this.getVariableHandler(notebook).getVariables(notebook, request); - } - - public getMatchingVariable(notebook: INotebook, name: string): Promise { - return this.getVariableHandler(notebook).getMatchingVariable(notebook, name); - } - - public async getDataFrameInfo(targetVariable: IJupyterVariable, notebook: INotebook): Promise { - return this.getVariableHandler(notebook).getDataFrameInfo(targetVariable, notebook); - } - - public async getDataFrameRows( - targetVariable: IJupyterVariable, - notebook: INotebook, - start: number, - end: number - ): Promise { - return this.getVariableHandler(notebook).getDataFrameRows(targetVariable, notebook, start, end); - } - - private getVariableHandler(notebook: INotebook): IJupyterVariables { - if (!this.experimentsManager.inExperiment(RunByLine.experiment)) { - return this.oldVariables; - } - if (this.debuggerVariables.active && notebook.status === ServerStatus.Busy) { - return this.debuggerVariables; - } - - return this.kernelVariables; - } - - private fireRefresh() { - this.refreshEventEmitter.fire(); - } -} diff --git a/src/client/datascience/jupyter/jupyterWaitForIdleError.ts b/src/client/datascience/jupyter/jupyterWaitForIdleError.ts deleted file mode 100644 index b02bab07b807..000000000000 --- a/src/client/datascience/jupyter/jupyterWaitForIdleError.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -export class JupyterWaitForIdleError extends Error { - constructor(message: string) { - super(message); - } -} diff --git a/src/client/datascience/jupyter/jupyterWebSocket.ts b/src/client/datascience/jupyter/jupyterWebSocket.ts deleted file mode 100644 index f11ed9281294..000000000000 --- a/src/client/datascience/jupyter/jupyterWebSocket.ts +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as WebSocketWS from 'ws'; -import { traceError } from '../../common/logger'; -import { noop } from '../../common/utils/misc'; -import { KernelSocketWrapper } from '../kernelSocketWrapper'; -import { IKernelSocket } from '../types'; - -// tslint:disable: no-any -export const JupyterWebSockets = new Map(); // NOSONAR - -// We need to override the websocket that jupyter lab services uses to put in our cookie information -// Do this as a function so that we can pass in variables the the socket will have local access to -export function createJupyterWebSocket(cookieString?: string, allowUnauthorized?: boolean, getAuthHeaders?: () => any) { - class JupyterWebSocket extends KernelSocketWrapper(WebSocketWS) { - private kernelId: string | undefined; - private timer: NodeJS.Timeout | number; - - constructor(url: string, protocols?: string | string[] | undefined) { - let co: WebSocketWS.ClientOptions = {}; - let co_headers: { [key: string]: string } | undefined; - - if (allowUnauthorized) { - co = { ...co, rejectUnauthorized: false }; - } - - if (cookieString) { - co_headers = { Cookie: cookieString }; - } - - // Auth headers have to be refetched every time we create a connection. They may have expired - // since the last connection. - if (getAuthHeaders) { - const authorizationHeader = getAuthHeaders(); - co_headers = co_headers ? { ...co_headers, ...authorizationHeader } : authorizationHeader; - } - if (co_headers) { - co = { ...co, headers: co_headers }; - } - - super(url, protocols, co); - - // Parse the url for the kernel id - const parsed = /.*\/kernels\/(.*)\/.*/.exec(url); - if (parsed && parsed.length > 1) { - this.kernelId = parsed[1]; - } - if (this.kernelId) { - JupyterWebSockets.set(this.kernelId, this); - this.on('close', () => { - clearInterval(this.timer as any); - JupyterWebSockets.delete(this.kernelId!); - }); - } else { - traceError('KernelId not extracted from Kernel WebSocket URL'); - } - - // Ping the websocket connection every 30 seconds to make sure it stays alive - this.timer = setInterval(() => this.ping(noop), 30_000); - } - } - return JupyterWebSocket; -} diff --git a/src/client/datascience/jupyter/jupyterZMQBinariesNotFoundError.ts b/src/client/datascience/jupyter/jupyterZMQBinariesNotFoundError.ts deleted file mode 100644 index 96a5e47107aa..000000000000 --- a/src/client/datascience/jupyter/jupyterZMQBinariesNotFoundError.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -export class JupyterZMQBinariesNotFoundError extends Error { - constructor(message: string) { - super(message); - } -} diff --git a/src/client/datascience/jupyter/kernelVariables.ts b/src/client/datascience/jupyter/kernelVariables.ts deleted file mode 100644 index 7d3d1b004bd3..000000000000 --- a/src/client/datascience/jupyter/kernelVariables.ts +++ /dev/null @@ -1,458 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import type { nbformat } from '@jupyterlab/coreutils'; -import { inject, injectable } from 'inversify'; -import stripAnsi from 'strip-ansi'; -import * as uuid from 'uuid/v4'; - -import { CancellationToken, Event, EventEmitter, Uri } from 'vscode'; -import { PYTHON_LANGUAGE } from '../../common/constants'; -import { traceError } from '../../common/logger'; -import { IConfigurationService, IDisposable } from '../../common/types'; -import * as localize from '../../common/utils/localize'; -import { DataFrameLoading, Identifiers, Settings } from '../constants'; -import { - ICell, - IJupyterVariable, - IJupyterVariables, - IJupyterVariablesRequest, - IJupyterVariablesResponse, - INotebook -} from '../types'; -import { JupyterDataRateLimitError } from './jupyterDataRateLimitError'; -import { getKernelConnectionLanguage, isPythonKernelConnection } from './kernels/helpers'; - -// tslint:disable-next-line: no-var-requires no-require-imports - -// Regexes for parsing data from Python kernel. Not sure yet if other -// kernels will add the ansi encoding. -const TypeRegex = /.*?\[.*?;31mType:.*?\[0m\s+(\w+)/; -const ValueRegex = /.*?\[.*?;31mValue:.*?\[0m\s+(.*)/; -const StringFormRegex = /.*?\[.*?;31mString form:.*?\[0m\s*?([\s\S]+?)\n(.*\[.*;31m?)/; -const DocStringRegex = /.*?\[.*?;31mDocstring:.*?\[0m\s+(.*)/; -const CountRegex = /.*?\[.*?;31mLength:.*?\[0m\s+(.*)/; -const ShapeRegex = /^\s+\[(\d+) rows x (\d+) columns\]/m; - -const DataViewableTypes: Set = new Set(['DataFrame', 'list', 'dict', 'ndarray', 'Series']); - -interface INotebookState { - currentExecutionCount: number; - variables: IJupyterVariable[]; -} - -@injectable() -export class KernelVariables implements IJupyterVariables { - private importedDataFrameScripts = new Map(); - private languageToQueryMap = new Map(); - private notebookState = new Map(); - private refreshEventEmitter = new EventEmitter(); - - constructor(@inject(IConfigurationService) private configService: IConfigurationService) {} - - public get refreshRequired(): Event { - return this.refreshEventEmitter.event; - } - - // IJupyterVariables implementation - public async getVariables( - notebook: INotebook, - request: IJupyterVariablesRequest - ): Promise { - // Run the language appropriate variable fetch - return this.getVariablesBasedOnKernel(notebook, request); - } - - public async getMatchingVariable( - notebook: INotebook, - name: string, - token?: CancellationToken - ): Promise { - // See if in the cache - const cache = this.notebookState.get(notebook.identity); - if (cache) { - let match = cache.variables.find((v) => v.name === name); - if (match && !match.value) { - match = await this.getVariableValueFromKernel(match, notebook, token); - } - return match; - } else { - // No items in the cache yet, just ask for the names - const names = await this.getVariableNamesFromKernel(notebook, token); - if (names) { - const matchName = names.find((n) => n === name); - if (matchName) { - return this.getVariableValueFromKernel( - { - name, - value: undefined, - supportsDataExplorer: false, - type: '', - size: 0, - count: 0, - shape: '', - truncated: true - }, - notebook, - token - ); - } - } - } - } - - public async getDataFrameInfo(targetVariable: IJupyterVariable, notebook: INotebook): Promise { - // Import the data frame script directory if we haven't already - await this.importDataFrameScripts(notebook); - - // Then execute a call to get the info and turn it into JSON - const results = await notebook.execute( - `print(${DataFrameLoading.DataFrameInfoFunc}(${targetVariable.name}))`, - Identifiers.EmptyFileName, - 0, - uuid(), - undefined, - true - ); - - // Combine with the original result (the call only returns the new fields) - return { - ...targetVariable, - ...this.deserializeJupyterResult(results) - }; - } - - public async getDataFrameRows( - targetVariable: IJupyterVariable, - notebook: INotebook, - start: number, - end: number - ): Promise<{}> { - // Import the data frame script directory if we haven't already - await this.importDataFrameScripts(notebook); - - if (targetVariable.rowCount) { - end = Math.min(end, targetVariable.rowCount); - } - - // Then execute a call to get the rows and turn it into JSON - const results = await notebook.execute( - `print(${DataFrameLoading.DataFrameRowFunc}(${targetVariable.name}, ${start}, ${end}))`, - Identifiers.EmptyFileName, - 0, - uuid(), - undefined, - true - ); - return this.deserializeJupyterResult(results); - } - - private async importDataFrameScripts(notebook: INotebook, token?: CancellationToken): Promise { - const key = notebook.identity.toString(); - if (!this.importedDataFrameScripts.get(key)) { - // Clear our flag if the notebook disposes or restarts - const disposables: IDisposable[] = []; - const handler = () => { - this.importedDataFrameScripts.delete(key); - disposables.forEach((d) => d.dispose()); - }; - disposables.push(notebook.onDisposed(handler)); - disposables.push(notebook.onKernelChanged(handler)); - disposables.push(notebook.onKernelRestarted(handler)); - - const fullCode = `${DataFrameLoading.DataFrameSysImport}\n${DataFrameLoading.DataFrameInfoImport}\n${DataFrameLoading.DataFrameRowImport}\n${DataFrameLoading.VariableInfoImport}`; - await notebook.execute(fullCode, Identifiers.EmptyFileName, 0, uuid(), token, true); - this.importedDataFrameScripts.set(notebook.identity.toString(), true); - } - } - - private async getFullVariable( - targetVariable: IJupyterVariable, - notebook: INotebook, - token?: CancellationToken - ): Promise { - // Import the data frame script directory if we haven't already - await this.importDataFrameScripts(notebook, token); - - // Then execute a call to get the info and turn it into JSON - const results = await notebook.execute( - `print(${DataFrameLoading.VariableInfoFunc}(${targetVariable.name}))`, - Identifiers.EmptyFileName, - 0, - uuid(), - token, - true - ); - - // Combine with the original result (the call only returns the new fields) - return { - ...targetVariable, - ...this.deserializeJupyterResult(results) - }; - } - - private extractJupyterResultText(cells: ICell[]): string { - // Verify that we have the correct cell type and outputs - if (cells.length > 0 && cells[0].data) { - const codeCell = cells[0].data as nbformat.ICodeCell; - if (codeCell.outputs.length > 0) { - const codeCellOutput = codeCell.outputs[0] as nbformat.IOutput; - if ( - codeCellOutput && - codeCellOutput.output_type === 'stream' && - codeCellOutput.name === 'stderr' && - codeCellOutput.hasOwnProperty('text') - ) { - const resultString = codeCellOutput.text as string; - // See if this the IOPUB data rate limit problem - if (resultString.includes('iopub_data_rate_limit')) { - throw new JupyterDataRateLimitError(); - } else { - const error = localize.DataScience.jupyterGetVariablesExecutionError().format(resultString); - traceError(error); - throw new Error(error); - } - } - if (codeCellOutput && codeCellOutput.output_type === 'execute_result') { - const data = codeCellOutput.data; - if (data && data.hasOwnProperty('text/plain')) { - // tslint:disable-next-line:no-any - return (data as any)['text/plain']; - } - } - if ( - codeCellOutput && - codeCellOutput.output_type === 'stream' && - codeCellOutput.hasOwnProperty('text') - ) { - return codeCellOutput.text as string; - } - if ( - codeCellOutput && - codeCellOutput.output_type === 'error' && - codeCellOutput.hasOwnProperty('traceback') - ) { - const traceback: string[] = codeCellOutput.traceback as string[]; - const stripped = traceback.map(stripAnsi).join('\r\n'); - const error = localize.DataScience.jupyterGetVariablesExecutionError().format(stripped); - traceError(error); - throw new Error(error); - } - } - } - - throw new Error(localize.DataScience.jupyterGetVariablesBadResults()); - } - - // Pull our text result out of the Jupyter cell - private deserializeJupyterResult(cells: ICell[]): T { - const text = this.extractJupyterResultText(cells); - return JSON.parse(text) as T; - } - - private getParser(notebook: INotebook) { - // Figure out kernel language - const language = getKernelConnectionLanguage(notebook?.getKernelConnection()) || PYTHON_LANGUAGE; - - // We may have cached this information - let result = this.languageToQueryMap.get(language); - if (!result) { - let query = this.configService - .getSettings(notebook.resource) - .datascience.variableQueries.find((v) => v.language === language); - if (!query && language === PYTHON_LANGUAGE) { - query = Settings.DefaultVariableQuery; - } - - // Use the query to generate our regex - if (query) { - result = { - query: query.query, - parser: new RegExp(query.parseExpr, 'g') - }; - this.languageToQueryMap.set(language, result); - } - } - - return result; - } - - private getAllMatches(regex: RegExp, text: string): string[] { - const result: string[] = []; - let m: RegExpExecArray | null = null; - // tslint:disable-next-line: no-conditional-assignment - while ((m = regex.exec(text)) !== null) { - if (m.index === regex.lastIndex) { - regex.lastIndex += 1; - } - if (m.length > 1) { - result.push(m[1]); - } - } - // Rest after searching - regex.lastIndex = -1; - return result; - } - - private async getVariablesBasedOnKernel( - notebook: INotebook, - request: IJupyterVariablesRequest - ): Promise { - // See if we already have the name list - let list = this.notebookState.get(notebook.identity); - if (!list || list.currentExecutionCount !== request.executionCount) { - // Refetch the list of names from the notebook. They might have changed. - list = { - currentExecutionCount: request.executionCount, - variables: (await this.getVariableNamesFromKernel(notebook)).map((n) => { - return { - name: n, - value: undefined, - supportsDataExplorer: false, - type: '', - size: 0, - shape: '', - count: 0, - truncated: true - }; - }) - }; - } - - const exclusionList = this.configService.getSettings(notebook.resource).datascience.variableExplorerExclude - ? this.configService.getSettings().datascience.variableExplorerExclude?.split(';') - : []; - - const result: IJupyterVariablesResponse = { - executionCount: request.executionCount, - pageStartIndex: -1, - pageResponse: [], - totalCount: 0, - refreshCount: request.refreshCount - }; - - // Use the list of names to fetch the page of data - if (list) { - const startPos = request.startIndex ? request.startIndex : 0; - const chunkSize = request.pageSize ? request.pageSize : 100; - result.pageStartIndex = startPos; - - // Do one at a time. All at once doesn't work as they all have to wait for each other anyway - for (let i = startPos; i < startPos + chunkSize && i < list.variables.length; ) { - const fullVariable = list.variables[i].value - ? list.variables[i] - : await this.getVariableValueFromKernel(list.variables[i], notebook); - - // See if this is excluded or not. - if (exclusionList && exclusionList.indexOf(fullVariable.type) >= 0) { - // Not part of our actual list. Remove from the real list too - list.variables.splice(i, 1); - } else { - list.variables[i] = fullVariable; - result.pageResponse.push(fullVariable); - i += 1; - } - } - - // Save in our cache - this.notebookState.set(notebook.identity, list); - - // Update total count (exclusions will change this as types are computed) - result.totalCount = list.variables.length; - } - - return result; - } - - private async getVariableNamesFromKernel(notebook: INotebook, token?: CancellationToken): Promise { - // Get our query and parser - const query = this.getParser(notebook); - - // Now execute the query - if (notebook && query) { - const cells = await notebook.execute(query.query, Identifiers.EmptyFileName, 0, uuid(), token, true); - const text = this.extractJupyterResultText(cells); - - // Apply the expression to it - const matches = this.getAllMatches(query.parser, text); - - // Turn each match into a value - if (matches) { - return matches; - } - } - - return []; - } - - private async getVariableValueFromKernel( - targetVariable: IJupyterVariable, - notebook: INotebook, - token?: CancellationToken - ): Promise { - let result = { ...targetVariable }; - if (notebook) { - const output = await notebook.inspect(targetVariable.name, 0, token); - - // Should be a text/plain inside of it (at least IPython does this) - if (output && output.hasOwnProperty('text/plain')) { - // tslint:disable-next-line: no-any - const text = (output as any)['text/plain'].toString(); - - // Parse into bits - const type = TypeRegex.exec(text); - const value = ValueRegex.exec(text); - const stringForm = StringFormRegex.exec(text); - const docString = DocStringRegex.exec(text); - const count = CountRegex.exec(text); - const shape = ShapeRegex.exec(text); - if (type) { - result.type = type[1]; - } - if (value) { - result.value = value[1]; - } else if (stringForm) { - result.value = stringForm[1]; - } else if (docString) { - result.value = docString[1]; - } else { - result.value = ''; - } - if (count) { - result.count = parseInt(count[1], 10); - } - if (shape) { - result.shape = `(${shape[1]}, ${shape[2]})`; - } - } - - // Otherwise look for the appropriate entries - if (output.type) { - result.type = output.type.toString(); - } - if (output.value) { - result.value = output.value.toString(); - } - - // Determine if supports viewing based on type - if (DataViewableTypes.has(result.type)) { - result.supportsDataExplorer = true; - } - } - - // For a python kernel, we might be able to get a better shape. It seems the 'inspect' request doesn't always return it. - // Do this only when necessary as this is a LOT slower than an inspect request. Like 4 or 5 times as slow - if ( - result.type && - result.count && - !result.shape && - isPythonKernelConnection(notebook.getKernelConnection()) && - result.supportsDataExplorer && - result.type !== 'list' // List count is good enough - ) { - result = await this.getFullVariable(result, notebook); - } - - return result; - } -} diff --git a/src/client/datascience/jupyter/kernels/cellExecution.ts b/src/client/datascience/jupyter/kernels/cellExecution.ts deleted file mode 100644 index 6c4babf01d37..000000000000 --- a/src/client/datascience/jupyter/kernels/cellExecution.ts +++ /dev/null @@ -1,586 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { nbformat } from '@jupyterlab/coreutils'; -import type { KernelMessage } from '@jupyterlab/services/lib/kernel/messages'; -import { CancellationToken, CellOutputKind, NotebookCell, NotebookCellRunState } from 'vscode'; -import type { CellDisplayOutput, NotebookEditor as VSCNotebookEditor } from '../../../../../types/vscode-proposed'; -import { concatMultilineString, formatStreamText } from '../../../../datascience-ui/common'; -import { IApplicationShell, IVSCodeNotebook } from '../../../common/application/types'; -import { traceInfo, traceWarning } from '../../../common/logger'; -import { RefBool } from '../../../common/refBool'; -import { IDisposable } from '../../../common/types'; -import { createDeferred } from '../../../common/utils/async'; -import { swallowExceptions } from '../../../common/utils/decorators'; -import { noop } from '../../../common/utils/misc'; -import { StopWatch } from '../../../common/utils/stopWatch'; -import { sendTelemetryEvent } from '../../../telemetry'; -import { Telemetry } from '../../constants'; -import { - handleUpdateDisplayDataMessage, - updateCellExecutionCount, - updateCellWithErrorStatus -} from '../../notebook/helpers/executionHelpers'; -import { - cellOutputToVSCCellOutput, - clearCellForExecution, - getCellStatusMessageBasedOnFirstCellErrorOutput, - updateCellExecutionTimes -} from '../../notebook/helpers/helpers'; -import { MultiCancellationTokenSource } from '../../notebook/helpers/multiCancellationToken'; -import { NotebookEditor } from '../../notebook/notebookEditor'; -import { - IDataScienceErrorHandler, - IJupyterSession, - INotebook, - INotebookEditorProvider, - INotebookExecutionLogger -} from '../../types'; -import { IKernel } from './types'; -// tslint:disable-next-line: no-var-requires no-require-imports -const vscodeNotebookEnums = require('vscode') as typeof import('vscode-proposed'); - -export class CellExecutionFactory { - constructor( - private readonly errorHandler: IDataScienceErrorHandler, - private readonly editorProvider: INotebookEditorProvider, - private readonly appShell: IApplicationShell, - private readonly vscNotebook: IVSCodeNotebook - ) {} - - public create(cell: NotebookCell) { - // tslint:disable-next-line: no-use-before-declare - return CellExecution.fromCell( - this.vscNotebook.notebookEditors.find((e) => e.document === cell.notebook)!, - cell, - this.errorHandler, - this.editorProvider, - this.appShell - ); - } -} -/** - * Responsible for execution of an individual cell and manages the state of the cell as it progresses through the execution phases. - * Execution phases include - enqueue for execution (done in ctor), start execution, completed execution with/without errors, cancel execution or dequeue. - */ -export class CellExecution { - public get result(): Promise { - return this._result.promise; - } - - public get token(): CancellationToken { - return this.source.token; - } - - public get completed() { - return this._completed; - } - - private static sentExecuteCellTelemetry?: boolean; - - private readonly oldCellRunState?: NotebookCellRunState; - - private stopWatch = new StopWatch(); - - private readonly source = new MultiCancellationTokenSource(); - - private readonly _result = createDeferred(); - - private started?: boolean; - - private _completed?: boolean; - private readonly initPromise: Promise; - /** - * This is used to chain the updates to the cells. - */ - private previousUpdatedToCellHasCompleted = Promise.resolve(); - private disposables: IDisposable[] = []; - private cancelHandled = false; - - private constructor( - public readonly editor: VSCNotebookEditor, - public readonly cell: NotebookCell, - private readonly errorHandler: IDataScienceErrorHandler, - private readonly editorProvider: INotebookEditorProvider, - private readonly applicationService: IApplicationShell - ) { - this.oldCellRunState = cell.metadata.runState; - this.initPromise = this.enqueue(); - } - - public static fromCell( - editor: VSCNotebookEditor, - cell: NotebookCell, - errorHandler: IDataScienceErrorHandler, - editorProvider: INotebookEditorProvider, - appService: IApplicationShell - ) { - return new CellExecution(editor, cell, errorHandler, editorProvider, appService); - } - - public async start(kernelPromise: Promise, notebook: INotebook) { - await this.initPromise; - this.started = true; - // Ensure we clear the cell state and trigger a change. - await clearCellForExecution(this.editor, this.cell); - await this.editor.edit((edit) => { - edit.replaceCellMetadata(this.cell.index, { - ...this.cell.metadata, - runStartTime: new Date().getTime() - }); - }); - this.stopWatch.reset(); - this.notifyCellExecution(); - - // Begin the request that will modify our cell. - kernelPromise - .then((kernel) => this.handleKernelRestart(kernel)) - .then(() => this.execute(notebook.session, notebook.getLoggers())) - .catch((e) => this.completedWithErrors(e)) - .finally(() => this.dispose()) - .catch(noop); - } - /** - * Cancel execution. - * If execution has commenced, then interrupt (via cancellation token) else dequeue from execution. - */ - public async cancel() { - if (this.cancelHandled) { - return; - } - await this.initPromise; - // We need to notify cancellation only if execution is in progress, - // coz if not, we can safely reset the states. - if (this.started && !this._completed) { - this.source.cancel(); - } - - if (!this.started) { - await this.dequeue(); - } - await this.completedDurToCancellation(); - this.dispose(); - } - private dispose() { - this.disposables.forEach((d) => d.dispose()); - } - private handleKernelRestart(kernel: IKernel) { - kernel.onRestarted(async () => this.cancel(), this, this.disposables); - } - - private async completedWithErrors(error: Partial) { - this.sendPerceivedCellExecute(); - await this.editor.edit((edit) => - edit.replaceCellMetadata(this.cell.index, { - ...this.cell.metadata, - lastRunDuration: this.stopWatch.elapsedTime - }) - ); - await updateCellWithErrorStatus(this.editor, this.cell, error); - this.errorHandler.handleError((error as unknown) as Error).ignoreErrors(); - - this._completed = true; - this._result.resolve(this.cell.metadata.runState); - } - - private async completedSuccessfully() { - this.sendPerceivedCellExecute(); - let statusMessage = ''; - // If we requested a cancellation, then assume it did not even run. - // If it did, then we'd get an interrupt error in the output. - let runState = this.token.isCancellationRequested - ? vscodeNotebookEnums.NotebookCellRunState.Idle - : vscodeNotebookEnums.NotebookCellRunState.Success; - - await updateCellExecutionTimes(this.editor, this.cell, { - startTime: this.cell.metadata.runStartTime, - lastRunDuration: this.stopWatch.elapsedTime - }); - - // If there are any errors in the cell, then change status to error. - if (this.cell.outputs.some((output) => output.outputKind === vscodeNotebookEnums.CellOutputKind.Error)) { - runState = vscodeNotebookEnums.NotebookCellRunState.Error; - statusMessage = getCellStatusMessageBasedOnFirstCellErrorOutput(this.cell.outputs); - } - - await this.editor.edit((edit) => - edit.replaceCellMetadata(this.cell.index, { - ...this.cell.metadata, - runState, - statusMessage - }) - ); - - this._completed = true; - this._result.resolve(this.cell.metadata.runState); - } - - private async completedDurToCancellation() { - await updateCellExecutionTimes(this.editor, this.cell, { - startTime: this.cell.metadata.runStartTime, - lastRunDuration: this.stopWatch.elapsedTime - }); - - await this.editor.edit((edit) => - edit.replaceCellMetadata(this.cell.index, { - ...this.cell.metadata, - runState: vscodeNotebookEnums.NotebookCellRunState.Idle, - statusMessage: '' - }) - ); - - this._completed = true; - this._result.resolve(this.cell.metadata.runState); - } - - /** - * Notify other parts of extension about the cell execution. - */ - private notifyCellExecution() { - const editor = this.editorProvider.editors.find((e) => e.file.toString() === this.cell.notebook.uri.toString()); - if (!editor) { - throw new Error('No editor for Model'); - } - if (editor && !(editor instanceof NotebookEditor)) { - throw new Error('Executing Notebook with another Editor'); - } - editor.notifyExecution(this.cell); - } - - /** - * This cell will no longer be processed for execution (even though it was meant to be). - * At this point we revert cell state & indicate that it has nto started & it is not busy. - */ - private async dequeue() { - const runState = - this.oldCellRunState === vscodeNotebookEnums.NotebookCellRunState.Running - ? vscodeNotebookEnums.NotebookCellRunState.Idle - : this.oldCellRunState; - await this.editor.edit((edit) => - edit.replaceCellMetadata(this.cell.index, { - ...this.cell.metadata, - runStartTime: undefined, - runState - }) - ); - this._completed = true; - this._result.resolve(this.cell.metadata.runState); - } - - /** - * Place in queue for execution with kernel. - * (mark it as busy). - */ - private async enqueue() { - await this.editor.edit((edit) => - edit.replaceCellMetadata(this.cell.index, { - ...this.cell.metadata, - runState: vscodeNotebookEnums.NotebookCellRunState.Running - }) - ); - } - - private sendPerceivedCellExecute() { - const props = { notebook: true }; - if (!CellExecution.sentExecuteCellTelemetry) { - CellExecution.sentExecuteCellTelemetry = true; - sendTelemetryEvent(Telemetry.ExecuteCellPerceivedCold, this.stopWatch.elapsedTime, props); - } else { - sendTelemetryEvent(Telemetry.ExecuteCellPerceivedWarm, this.stopWatch.elapsedTime, props); - } - } - - private async execute(session: IJupyterSession, loggers: INotebookExecutionLogger[]) { - // Generate metadata from our cell (some kernels expect this.) - const metadata = { - ...this.cell.metadata, - ...{ cellId: this.cell.uri.toString() } - }; - - // Create our initial request - const code = this.cell.document.getText(); - - // Skip if no code to execute - if (code.trim().length === 0) { - return this.completedSuccessfully().then(noop, noop); - } - - const request = session.requestExecute( - { - code, - silent: false, - stop_on_error: false, - allow_stdin: true, - store_history: true // Silent actually means don't output anything. Store_history is what affects execution_count - }, - false, - metadata - ); - - // Listen to messages and update our cell execution state appropriately - - // Keep track of our clear state - const clearState = new RefBool(false); - - // Listen to the reponse messages and update state as we go - if (!request) { - return this.completedWithErrors(new Error('Session cannot generate requests')).then(noop, noop); - } - - // Stop handling the request if the subscriber is canceled. - const cancelDisposable = this.token.onCancellationRequested(() => { - request.onIOPub = noop; - request.onStdin = noop; - request.onReply = noop; - }); - - // Listen to messages. - request.onIOPub = this.handleIOPub.bind(this, clearState, loggers); - request.onStdin = this.handleInputRequest.bind(this, session); - request.onReply = this.handleReply.bind(this, clearState); - - // When the request finishes we are done - try { - await request.done; - await this.completedSuccessfully(); - } catch (ex) { - // @jupyterlab/services throws a `Canceled` error when the kernel is interrupted. - // Such an error must be ignored. - if (ex && ex instanceof Error && ex.message === 'Canceled') { - await this.completedSuccessfully(); - } else { - await this.completedWithErrors(ex); - } - } finally { - cancelDisposable.dispose(); - } - } - - @swallowExceptions() - private async handleIOPub( - clearState: RefBool, - loggers: INotebookExecutionLogger[], - msg: KernelMessage.IIOPubMessage - // tslint:disable-next-line: no-any - ) { - // Wait for previous cell update to complete. - await this.previousUpdatedToCellHasCompleted.then(noop, noop); - const deferred = createDeferred(); - this.previousUpdatedToCellHasCompleted = this.previousUpdatedToCellHasCompleted.then(() => deferred.promise); - - // Let our loggers get a first crack at the message. They may change it - loggers.forEach((f) => (msg = f.preHandleIOPub ? f.preHandleIOPub(msg) : msg)); - - // tslint:disable-next-line:no-require-imports - const jupyterLab = require('@jupyterlab/services') as typeof import('@jupyterlab/services'); - - try { - if (jupyterLab.KernelMessage.isExecuteResultMsg(msg)) { - await this.handleExecuteResult(msg as KernelMessage.IExecuteResultMsg, clearState); - } else if (jupyterLab.KernelMessage.isExecuteInputMsg(msg)) { - await this.handleExecuteInput(msg as KernelMessage.IExecuteInputMsg, clearState); - } else if (jupyterLab.KernelMessage.isStatusMsg(msg)) { - // Status is handled by the result promise. While it is running we are active. Otherwise we're stopped. - // So ignore status messages. - const statusMsg = msg as KernelMessage.IStatusMsg; - this.handleStatusMessage(statusMsg, clearState); - } else if (jupyterLab.KernelMessage.isStreamMsg(msg)) { - await this.handleStreamMessage(msg as KernelMessage.IStreamMsg, clearState); - } else if (jupyterLab.KernelMessage.isDisplayDataMsg(msg)) { - await this.handleDisplayData(msg as KernelMessage.IDisplayDataMsg, clearState); - } else if (jupyterLab.KernelMessage.isUpdateDisplayDataMsg(msg)) { - await handleUpdateDisplayDataMessage(msg, this.editor); - } else if (jupyterLab.KernelMessage.isClearOutputMsg(msg)) { - await this.handleClearOutput(msg as KernelMessage.IClearOutputMsg, clearState); - } else if (jupyterLab.KernelMessage.isErrorMsg(msg)) { - await this.handleError(msg as KernelMessage.IErrorMsg, clearState); - } else if (jupyterLab.KernelMessage.isCommOpenMsg(msg)) { - // Noop. - } else if (jupyterLab.KernelMessage.isCommMsgMsg(msg)) { - // Noop. - } else if (jupyterLab.KernelMessage.isCommCloseMsg(msg)) { - // Noop. - } else { - traceWarning(`Unknown message ${msg.header.msg_type} : hasData=${'data' in msg.content}`); - } - - // Set execution count, all messages should have it - if ('execution_count' in msg.content && typeof msg.content.execution_count === 'number') { - updateCellExecutionCount(this.editor, this.cell, msg.content.execution_count).then(noop, noop); - } - } catch (err) { - // If not a restart error, then tell the subscriber - this.completedWithErrors(err).then(noop, noop); - } finally { - deferred.resolve(); - } - } - - private async addToCellData( - output: nbformat.IExecuteResult | nbformat.IDisplayData | nbformat.IStream | nbformat.IError, - clearState: RefBool - ) { - const converted = cellOutputToVSCCellOutput(output); - - await this.editor.edit((edit) => { - let existingOutput = [...this.cell.outputs]; - - // Clear if necessary - if (clearState.value) { - existingOutput = []; - clearState.update(false); - } - - // Append to the data (we would push here but VS code requires a recreation of the array) - edit.replaceCellOutput(this.cell.index, existingOutput.concat(converted)); - }); - } - - private handleInputRequest(session: IJupyterSession, msg: KernelMessage.IStdinMessage) { - // Ask the user for input - if (msg.content && 'prompt' in msg.content) { - const hasPassword = msg.content.password !== null && (msg.content.password as boolean); - this.applicationService - .showInputBox({ - prompt: msg.content.prompt ? msg.content.prompt.toString() : '', - ignoreFocusOut: true, - password: hasPassword - }) - .then((v) => { - session.sendInputReply(v || ''); - }); - } - } - - // See this for docs on the messages: - // https://jupyter-client.readthedocs.io/en/latest/messaging.html#messaging-in-jupyter - private async handleExecuteResult(msg: KernelMessage.IExecuteResultMsg, clearState: RefBool) { - await this.addToCellData( - { - output_type: 'execute_result', - data: msg.content.data, - metadata: msg.content.metadata, - // tslint:disable-next-line: no-any - transient: msg.content.transient as any, // NOSONAR - execution_count: msg.content.execution_count - }, - clearState - ); - } - - private async handleExecuteReply(msg: KernelMessage.IExecuteReplyMsg, clearState: RefBool) { - const reply = msg.content as KernelMessage.IExecuteReply; - if (reply.payload) { - await Promise.all( - reply.payload.map(async (o) => { - if (o.data && o.data.hasOwnProperty('text/plain')) { - await this.addToCellData( - { - // Mark as stream output so the text is formatted because it likely has ansi codes in it. - output_type: 'stream', - // tslint:disable-next-line: no-any - text: (o.data as any)['text/plain'].toString(), - name: 'stdout', - metadata: {}, - execution_count: reply.execution_count - }, - clearState - ); - } - }) - ); - } - } - - private async handleExecuteInput(msg: KernelMessage.IExecuteInputMsg, _clearState: RefBool) { - if (msg.content.execution_count) { - await updateCellExecutionCount(this.editor, this.cell, msg.content.execution_count); - } - } - - private handleStatusMessage(msg: KernelMessage.IStatusMsg, _clearState: RefBool) { - traceInfo(`Kernel switching to ${msg.content.execution_state}`); - } - private async handleStreamMessage(msg: KernelMessage.IStreamMsg, clearState: RefBool) { - await this.editor.edit((edit) => { - let exitingCellOutput = this.cell.outputs; - // Clear output if waiting for a clear - if (clearState.value) { - exitingCellOutput = []; - clearState.update(false); - } - - // Might already have a stream message. If so, just add on to it. - // We use Rich output for text streams (not CellStreamOutput, known VSC Issues). - // https://github.com/microsoft/vscode-python/issues/14156 - const lastOutput = - exitingCellOutput.length > 0 ? exitingCellOutput[exitingCellOutput.length - 1] : undefined; - const existing: CellDisplayOutput | undefined = - lastOutput && lastOutput.outputKind === CellOutputKind.Rich ? lastOutput : undefined; - if (existing && 'text/plain' in existing.data) { - // tslint:disable-next-line:restrict-plus-operands - existing.data['text/plain'] = formatStreamText( - concatMultilineString(`${existing.data['text/plain']}${msg.content.text}`) - ); - edit.replaceCellOutput(this.cell.index, [...exitingCellOutput]); // This is necessary to get VS code to update (for now) - } else { - const originalText = formatStreamText(concatMultilineString(msg.content.text)); - // Create a new stream entry - const output: nbformat.IStream = { - output_type: 'stream', - name: msg.content.name, - text: originalText - }; - edit.replaceCellOutput(this.cell.index, [...exitingCellOutput, cellOutputToVSCCellOutput(output)]); - } - }); - } - - private async handleDisplayData(msg: KernelMessage.IDisplayDataMsg, clearState: RefBool) { - const output: nbformat.IDisplayData = { - output_type: 'display_data', - data: msg.content.data, - metadata: msg.content.metadata, - // tslint:disable-next-line: no-any - transient: msg.content.transient as any // NOSONAR - }; - await this.addToCellData(output, clearState); - } - - private async handleClearOutput(msg: KernelMessage.IClearOutputMsg, clearState: RefBool) { - // If the message says wait, add every message type to our clear state. This will - // make us wait for this type of output before we clear it. - if (msg && msg.content.wait) { - clearState.update(true); - } else { - // Clear all outputs and start over again. - await this.editor.edit((edit) => edit.replaceCellOutput(this.cell.index, [])); - } - } - - private async handleError(msg: KernelMessage.IErrorMsg, clearState: RefBool) { - const output: nbformat.IError = { - output_type: 'error', - ename: msg.content.ename, - evalue: msg.content.evalue, - traceback: msg.content.traceback - }; - await this.addToCellData(output, clearState); - } - - private async handleReply(clearState: RefBool, msg: KernelMessage.IShellControlMessage) { - // tslint:disable-next-line:no-require-imports - const jupyterLab = require('@jupyterlab/services') as typeof import('@jupyterlab/services'); - - if (jupyterLab.KernelMessage.isExecuteReplyMsg(msg)) { - await this.handleExecuteReply(msg, clearState); - - // Set execution count, all messages should have it - if ('execution_count' in msg.content && typeof msg.content.execution_count === 'number') { - await updateCellExecutionCount(this.editor, this.cell, msg.content.execution_count); - } - } - } -} diff --git a/src/client/datascience/jupyter/kernels/helpers.ts b/src/client/datascience/jupyter/kernels/helpers.ts deleted file mode 100644 index bcbe703e4d33..000000000000 --- a/src/client/datascience/jupyter/kernels/helpers.ts +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import type { Kernel } from '@jupyterlab/services'; -import * as fastDeepEqual from 'fast-deep-equal'; -import { IJupyterKernelSpec } from '../../types'; -import { JupyterKernelSpec } from './jupyterKernelSpec'; -// tslint:disable-next-line: no-var-requires no-require-imports -const NamedRegexp = require('named-js-regexp') as typeof import('named-js-regexp'); - -// tslint:disable-next-line: no-require-imports -import cloneDeep = require('lodash/cloneDeep'); -import { PYTHON_LANGUAGE } from '../../../common/constants'; -import { ReadWrite } from '../../../common/types'; -import { PythonEnvironment } from '../../../pythonEnvironments/info'; -import { - DefaultKernelConnectionMetadata, - KernelConnectionMetadata, - KernelSpecConnectionMetadata, - LiveKernelConnectionMetadata, - LiveKernelModel, - PythonKernelConnectionMetadata -} from './types'; - -// Helper functions for dealing with kernels and kernelspecs - -// https://jupyter-client.readthedocs.io/en/stable/kernels.html -const connectionFilePlaceholder = '{connection_file}'; - -// Find the index of the connection file placeholder in a kernelspec -export function findIndexOfConnectionFile(kernelSpec: Readonly): number { - return kernelSpec.argv.indexOf(connectionFilePlaceholder); -} - -type ConnectionWithKernelSpec = - | KernelSpecConnectionMetadata - | PythonKernelConnectionMetadata - | DefaultKernelConnectionMetadata; -export function kernelConnectionMetadataHasKernelSpec( - connectionMetadata: KernelConnectionMetadata -): connectionMetadata is ConnectionWithKernelSpec { - return connectionMetadata.kind !== 'connectToLiveKernel'; -} -export function kernelConnectionMetadataHasKernelModel( - connectionMetadata: KernelConnectionMetadata -): connectionMetadata is LiveKernelConnectionMetadata { - return connectionMetadata.kind === 'connectToLiveKernel'; -} -export function getDisplayNameOrNameOfKernelConnection( - kernelConnection: KernelConnectionMetadata | undefined, - defaultValue: string = '' -) { - if (!kernelConnection) { - return defaultValue; - } - const displayName = - kernelConnection.kind === 'connectToLiveKernel' - ? kernelConnection.kernelModel.display_name - : kernelConnection.kernelSpec?.display_name; - const name = - kernelConnection.kind === 'connectToLiveKernel' - ? kernelConnection.kernelModel.name - : kernelConnection.kernelSpec?.name; - - const interpeterName = - kernelConnection.kind === 'startUsingPythonInterpreter' ? kernelConnection.interpreter.displayName : undefined; - - const defaultKernelName = kernelConnection.kind === 'startUsingDefaultKernel' ? 'Python 3' : undefined; - return displayName || name || interpeterName || defaultKernelName || defaultValue; -} - -export function getNameOfKernelConnection( - kernelConnection: KernelConnectionMetadata | undefined, - defaultValue: string = '' -) { - if (!kernelConnection) { - return defaultValue; - } - return kernelConnection.kind === 'connectToLiveKernel' - ? kernelConnection.kernelModel.name - : kernelConnection.kernelSpec?.name; -} - -export function getKernelPathFromKernelConnection(kernelConnection?: KernelConnectionMetadata): string | undefined { - if (!kernelConnection) { - return; - } - const model = kernelConnectionMetadataHasKernelModel(kernelConnection) ? kernelConnection.kernelModel : undefined; - const kernelSpec = kernelConnectionMetadataHasKernelSpec(kernelConnection) - ? kernelConnection.kernelSpec - : undefined; - return model?.path || kernelSpec?.path; -} -export function getInterpreterFromKernelConnectionMetadata( - kernelConnection?: KernelConnectionMetadata -): Partial | undefined { - if (!kernelConnection) { - return; - } - if (kernelConnection.interpreter) { - return kernelConnection.interpreter; - } - const model = kernelConnectionMetadataHasKernelModel(kernelConnection) ? kernelConnection.kernelModel : undefined; - if (model?.metadata?.interpreter) { - return model.metadata.interpreter; - } - const kernelSpec = kernelConnectionMetadataHasKernelSpec(kernelConnection) - ? kernelConnection.kernelSpec - : undefined; - return kernelSpec?.metadata?.interpreter; -} -export function isPythonKernelConnection(kernelConnection?: KernelConnectionMetadata): boolean { - if (!kernelConnection) { - return false; - } - if (kernelConnection.kind === 'startUsingPythonInterpreter') { - return true; - } - const model = kernelConnectionMetadataHasKernelModel(kernelConnection) ? kernelConnection.kernelModel : undefined; - const kernelSpec = kernelConnectionMetadataHasKernelSpec(kernelConnection) - ? kernelConnection.kernelSpec - : undefined; - return model?.language === PYTHON_LANGUAGE || kernelSpec?.language === PYTHON_LANGUAGE; -} -export function getKernelConnectionLanguage(kernelConnection?: KernelConnectionMetadata): string | undefined { - if (!kernelConnection) { - return; - } - const model = kernelConnectionMetadataHasKernelModel(kernelConnection) ? kernelConnection.kernelModel : undefined; - const kernelSpec = kernelConnectionMetadataHasKernelSpec(kernelConnection) - ? kernelConnection.kernelSpec - : undefined; - return model?.language || kernelSpec?.language; -} -// Create a default kernelspec with the given display name -export function createDefaultKernelSpec(interpreter?: PythonEnvironment): IJupyterKernelSpec { - // This creates a default kernel spec. When launched, 'python' argument will map to using the interpreter - // associated with the current resource for launching. - const defaultSpec: Kernel.ISpecModel = { - name: interpreter?.displayName || 'Python 3', - language: 'python', - display_name: interpreter?.displayName || 'Python 3', - metadata: {}, - argv: ['python', '-m', 'ipykernel_launcher', '-f', connectionFilePlaceholder], - env: {}, - resources: {} - }; - - return new JupyterKernelSpec(defaultSpec); -} - -export function areKernelConnectionsEqual( - connection1?: KernelConnectionMetadata, - connection2?: KernelConnectionMetadata -) { - if (!connection1 && !connection2) { - return true; - } - if (!connection1 && connection2) { - return false; - } - if (connection1 && !connection2) { - return false; - } - if (connection1?.kind !== connection2?.kind) { - return false; - } - if (connection1?.kind === 'connectToLiveKernel' && connection2?.kind === 'connectToLiveKernel') { - return areKernelModelsEqual(connection1.kernelModel, connection2.kernelModel); - } else if ( - connection1 && - connection1.kind !== 'connectToLiveKernel' && - connection2 && - connection2.kind !== 'connectToLiveKernel' - ) { - const kernelSpecsAreTheSame = areKernelSpecsEqual(connection1?.kernelSpec, connection2?.kernelSpec); - // If both are launching interpreters, compare interpreter paths. - const interpretersAreSame = - connection1.kind === 'startUsingPythonInterpreter' - ? connection1.interpreter.path === connection2.interpreter?.path - : true; - - return kernelSpecsAreTheSame && interpretersAreSame; - } - return false; -} -function areKernelSpecsEqual(kernelSpec1?: IJupyterKernelSpec, kernelSpec2?: IJupyterKernelSpec) { - if (kernelSpec1 && kernelSpec2) { - const spec1 = cloneDeep(kernelSpec1) as ReadWrite; - spec1.env = spec1.env || {}; - spec1.metadata = spec1.metadata || {}; - const spec2 = cloneDeep(kernelSpec2) as ReadWrite; - spec2.env = spec1.env || {}; - spec2.metadata = spec1.metadata || {}; - - return fastDeepEqual(spec1, spec2); - } else if (!kernelSpec1 && !kernelSpec2) { - return true; - } else { - return false; - } -} -function areKernelModelsEqual(kernelModel1?: LiveKernelModel, kernelModel2?: LiveKernelModel) { - if (kernelModel1 && kernelModel2) { - const model1 = cloneDeep(kernelModel1) as ReadWrite; - model1.env = model1.env || {}; - model1.metadata = model1.metadata || {}; - const model2 = cloneDeep(kernelModel2) as ReadWrite; - model2.env = model1.env || {}; - model2.metadata = model1.metadata || {}; - return fastDeepEqual(model1, model2); - } else if (!kernelModel1 && !kernelModel2) { - return true; - } else { - return false; - } -} -// Check if a name is a default python kernel name and pull the version -export function detectDefaultKernelName(name: string) { - const regEx = NamedRegexp('python\\s*(?(\\d+))', 'g'); - return regEx.exec(name.toLowerCase()); -} - -export function cleanEnvironment(spec: T): T { - // tslint:disable-next-line: no-any - const copy = cloneDeep(spec) as { env?: any }; - - if (copy.env) { - // Scrub the environment of the spec to make sure it has allowed values (they all must be strings) - // See this issue here: https://github.com/microsoft/vscode-python/issues/11749 - const keys = Object.keys(copy.env); - keys.forEach((k) => { - if (copy.env) { - const value = copy.env[k]; - if (value !== null && value !== undefined) { - copy.env[k] = value.toString(); - } - } - }); - } - - return copy as T; -} diff --git a/src/client/datascience/jupyter/kernels/jupyterKernelPromiseFailedError.ts b/src/client/datascience/jupyter/kernels/jupyterKernelPromiseFailedError.ts deleted file mode 100644 index 6ba8afa63045..000000000000 --- a/src/client/datascience/jupyter/kernels/jupyterKernelPromiseFailedError.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -export class JupyterKernelPromiseFailedError extends Error { - constructor(message: string) { - super(message); - } -} diff --git a/src/client/datascience/jupyter/kernels/jupyterKernelSpec.ts b/src/client/datascience/jupyter/kernels/jupyterKernelSpec.ts deleted file mode 100644 index 26c29d8c3ab1..000000000000 --- a/src/client/datascience/jupyter/kernels/jupyterKernelSpec.ts +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import type { Kernel } from '@jupyterlab/services'; -import * as path from 'path'; -import { CancellationToken } from 'vscode'; -import { createPromiseFromCancellation } from '../../../common/cancellation'; -import { traceInfo } from '../../../common/logger'; - -import { IPythonExecutionFactory } from '../../../common/process/types'; -import { PythonEnvironment } from '../../../pythonEnvironments/info'; -import { getRealPath } from '../../common'; -import { IDataScienceFileSystem, IJupyterKernelSpec } from '../../types'; - -export class JupyterKernelSpec implements IJupyterKernelSpec { - public name: string; - public language: string; - public path: string; - public specFile: string | undefined; - public readonly env: NodeJS.ProcessEnv | undefined; - public display_name: string; - public argv: string[]; - - // tslint:disable-next-line: no-any - public metadata?: Record & { interpreter?: Partial }; - constructor(specModel: Kernel.ISpecModel, file?: string) { - this.name = specModel.name; - this.argv = specModel.argv; - this.language = specModel.language; - this.path = specModel.argv && specModel.argv.length > 0 ? specModel.argv[0] : ''; - this.specFile = file; - this.display_name = specModel.display_name; - this.metadata = specModel.metadata; - // tslint:disable-next-line: no-any - this.env = specModel.env as any; // JSONObject, but should match - } -} - -/** - * Given the stdout contents from the command `python -m jupyter kernelspec list --json` this will parser that and build a list of kernelspecs. - * - * @export - * @param {string} stdout - * @param {IDataScienceFileSystem} fs - * @param {CancellationToken} [token] - * @returns - */ -export async function parseKernelSpecs( - stdout: string, - fs: IDataScienceFileSystem, - execFactory: IPythonExecutionFactory, - token?: CancellationToken -) { - traceInfo('Parsing kernelspecs from jupyter'); - // This should give us back a key value pair we can parse - const jsOut = JSON.parse(stdout.trim()) as { - kernelspecs: Record }>; - }; - const kernelSpecs = jsOut.kernelspecs; - - const specs = await Promise.race([ - Promise.all( - Object.keys(kernelSpecs).map(async (kernelName) => { - const spec = kernelSpecs[kernelName].spec as Kernel.ISpecModel; - // Add the missing name property. - const model = { - ...spec, - name: kernelName - }; - const specFile = await getRealPath( - fs, - execFactory, - spec.argv[0], - path.join(kernelSpecs[kernelName].resource_dir, 'kernel.json') - ); - if (specFile) { - return new JupyterKernelSpec(model as Kernel.ISpecModel, specFile); - } - }) - ), - createPromiseFromCancellation({ cancelAction: 'resolve', defaultValue: [], token }) - ]); - return specs.filter((item) => !!item).map((item) => item as JupyterKernelSpec); -} diff --git a/src/client/datascience/jupyter/kernels/kernel.ts b/src/client/datascience/jupyter/kernels/kernel.ts deleted file mode 100644 index 3bd67148755f..000000000000 --- a/src/client/datascience/jupyter/kernels/kernel.ts +++ /dev/null @@ -1,251 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { nbformat } from '@jupyterlab/coreutils'; -import { KernelMessage } from '@jupyterlab/services'; -import { Observable } from 'rxjs/Observable'; -import { Subject } from 'rxjs/Subject'; -import * as uuid from 'uuid/v4'; -import { - CancellationToken, - CancellationTokenSource, - Event, - EventEmitter, - NotebookCell, - NotebookDocument, - Uri -} from 'vscode'; -import { ServerStatus } from '../../../../datascience-ui/interactive-common/mainState'; -import { IApplicationShell, ICommandManager, IVSCodeNotebook } from '../../../common/application/types'; -import { traceError, traceWarning } from '../../../common/logger'; -import { IDisposableRegistry } from '../../../common/types'; -import { createDeferred, Deferred } from '../../../common/utils/async'; -import { noop } from '../../../common/utils/misc'; -import { CodeSnippets } from '../../constants'; -import { getDefaultNotebookContent, updateNotebookMetadata } from '../../notebookStorage/baseModel'; -import { - IDataScienceErrorHandler, - INotebook, - INotebookEditorProvider, - INotebookProvider, - INotebookProviderConnection, - InterruptResult, - KernelSocketInformation -} from '../../types'; -import { isPythonKernelConnection } from './helpers'; -import { KernelExecution } from './kernelExecution'; -import type { IKernel, IKernelProvider, IKernelSelectionUsage, KernelConnectionMetadata } from './types'; - -export class Kernel implements IKernel { - get connection(): INotebookProviderConnection | undefined { - return this.notebook?.connection; - } - get onStatusChanged(): Event { - return this._onStatusChanged.event; - } - get onRestarted(): Event { - return this._onRestarted.event; - } - get onDisposed(): Event { - return this._onDisposed.event; - } - private _info?: KernelMessage.IInfoReplyMsg['content']; - get info(): KernelMessage.IInfoReplyMsg['content'] | undefined { - return this._info; - } - get status(): ServerStatus { - return this.notebook?.status ?? ServerStatus.NotStarted; - } - get disposed(): boolean { - return this._disposed === true || this.notebook?.disposed === true; - } - get kernelSocket(): Observable { - return this._kernelSocket.asObservable(); - } - private notebook?: INotebook; - private _disposed?: boolean; - private readonly _kernelSocket = new Subject(); - private readonly _onStatusChanged = new EventEmitter(); - private readonly _onRestarted = new EventEmitter(); - private readonly _onDisposed = new EventEmitter(); - private _notebookPromise?: Promise; - private readonly hookedNotebookForEvents = new WeakSet(); - private restarting?: Deferred; - private readonly kernelValidated = new Map }>(); - private readonly kernelExecution: KernelExecution; - private startCancellation = new CancellationTokenSource(); - constructor( - public readonly uri: Uri, - public readonly metadata: Readonly, - private readonly notebookProvider: INotebookProvider, - private readonly disposables: IDisposableRegistry, - private readonly launchTimeout: number, - commandManager: ICommandManager, - private readonly errorHandler: IDataScienceErrorHandler, - editorProvider: INotebookEditorProvider, - private readonly kernelProvider: IKernelProvider, - private readonly kernelSelectionUsage: IKernelSelectionUsage, - appShell: IApplicationShell, - vscNotebook: IVSCodeNotebook - ) { - this.kernelExecution = new KernelExecution( - kernelProvider, - commandManager, - errorHandler, - editorProvider, - kernelSelectionUsage, - appShell, - vscNotebook, - metadata - ); - } - public async executeCell(cell: NotebookCell): Promise { - await this.start({ disableUI: false, token: this.startCancellation.token }); - await this.kernelExecution.executeCell(cell); - } - public async executeAllCells(document: NotebookDocument): Promise { - await this.start({ disableUI: false, token: this.startCancellation.token }); - await this.kernelExecution.executeAllCells(document); - } - public async cancelCell(cell: NotebookCell) { - this.startCancellation.cancel(); - await this.kernelExecution.cancelCell(cell); - } - public async cancelAllCells(document: NotebookDocument) { - this.startCancellation.cancel(); - this.kernelExecution.cancelAllCells(document); - } - public async start(options?: { disableUI?: boolean; token?: CancellationToken }): Promise { - if (this.restarting) { - await this.restarting.promise; - } - if (this._notebookPromise) { - await this._notebookPromise; - return; - } else { - await this.validate(this.uri); - const metadata = ((getDefaultNotebookContent().metadata || {}) as unknown) as nbformat.INotebookMetadata; - // Create a dummy notebook metadata & update the metadata before starting the notebook (required to ensure we fetch & start the right kernel). - // Lower layers of code below getOrCreateNotebook searches for kernels again using the metadata. - updateNotebookMetadata(metadata, this.metadata); - this._notebookPromise = this.notebookProvider.getOrCreateNotebook({ - identity: this.uri, - resource: this.uri, - disableUI: options?.disableUI, - getOnly: false, - metadata, - token: options?.token - }); - - this._notebookPromise - .then((nb) => (this.kernelExecution.notebook = this.notebook = nb)) - .catch((ex) => { - traceError('failed to create INotebook in kernel', ex); - this._notebookPromise = undefined; - this.startCancellation.cancel(); - this.errorHandler.handleError(ex).ignoreErrors(); // Just a notification, so don't await this - }); - await this._notebookPromise; - await this.initializeAfterStart(); - } - } - public async interrupt(): Promise { - if (this.restarting) { - await this.restarting.promise; - } - if (!this.notebook) { - throw new Error('No notebook to interrupt'); - } - return this.notebook.interruptKernel(this.launchTimeout); - } - public async dispose(): Promise { - this.restarting = undefined; - this._notebookPromise = undefined; - if (this.notebook) { - await this.notebook.dispose(); - this._disposed = true; - this._onDisposed.fire(); - this._onStatusChanged.fire(ServerStatus.Dead); - this.notebook = undefined; - this.kernelExecution.notebook = undefined; - } - this.kernelExecution.dispose(); - } - public async restart(): Promise { - if (this.restarting) { - return this.restarting.promise; - } - if (this.notebook) { - this.restarting = createDeferred(); - try { - await this.notebook.restartKernel(this.launchTimeout); - await this.initializeAfterStart(); - this.restarting.resolve(); - } catch (ex) { - this.restarting.reject(ex); - } finally { - this.restarting = undefined; - } - } - } - private async validate(uri: Uri): Promise { - const kernel = this.kernelProvider.get(uri); - if (!kernel) { - return; - } - const key = uri.toString(); - if (!this.kernelValidated.get(key)) { - const promise = new Promise((resolve) => - this.kernelSelectionUsage - .useSelectedKernel(kernel?.metadata, uri, 'raw') - .finally(() => { - // If still using the same promise, then remove the exception information. - // Basically if there's an exception, then we cannot use the kernel and a message would have been displayed. - // We don't want to cache such a promise, as its possible the user later installs the dependencies. - if (this.kernelValidated.get(key)?.kernel === kernel) { - this.kernelValidated.delete(key); - } - }) - .finally(resolve) - .catch(noop) - ); - - this.kernelValidated.set(key, { kernel, promise }); - } - await this.kernelValidated.get(key)!.promise; - } - private async initializeAfterStart() { - if (!this.notebook) { - return; - } - this.disableJedi(); - if (!this.hookedNotebookForEvents.has(this.notebook)) { - this.hookedNotebookForEvents.add(this.notebook); - this.notebook.kernelSocket.subscribe(this._kernelSocket); - this.notebook.onDisposed(() => { - this._notebookPromise = undefined; - this._onDisposed.fire(); - }); - this.notebook.onKernelRestarted(() => { - this._onRestarted.fire(); - }); - this.notebook.onSessionStatusChanged((e) => this._onStatusChanged.fire(e), this, this.disposables); - } - if (isPythonKernelConnection(this.metadata)) { - await this.notebook.setLaunchingFile(this.uri.fsPath); - } - await this.notebook - .requestKernelInfo() - .then((item) => (this._info = item.content)) - .catch(traceWarning.bind('Failed to request KernelInfo')); - await this.notebook.waitForIdle(this.launchTimeout); - } - - private disableJedi() { - if (isPythonKernelConnection(this.metadata) && this.notebook) { - this.notebook.executeObservable(CodeSnippets.disableJedi, this.uri.fsPath, 0, uuid(), true); - } - } -} diff --git a/src/client/datascience/jupyter/kernels/kernelDependencyService.ts b/src/client/datascience/jupyter/kernels/kernelDependencyService.ts deleted file mode 100644 index b405899e08a5..000000000000 --- a/src/client/datascience/jupyter/kernels/kernelDependencyService.ts +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { CancellationToken } from 'vscode'; -import { IApplicationShell } from '../../../common/application/types'; -import { createPromiseFromCancellation, wrapCancellationTokens } from '../../../common/cancellation'; -import { ProductNames } from '../../../common/installer/productNames'; -import { IInstaller, InstallerResponse, Product } from '../../../common/types'; -import { Common, DataScience } from '../../../common/utils/localize'; -import { PythonEnvironment } from '../../../pythonEnvironments/info'; -import { IKernelDependencyService, KernelInterpreterDependencyResponse } from '../../types'; - -/** - * Responsible for managing dependencies of a Python interpreter required to run as a Jupyter Kernel. - * If required modules aren't installed, will prompt user to install them. - */ -@injectable() -export class KernelDependencyService implements IKernelDependencyService { - constructor( - @inject(IApplicationShell) private readonly appShell: IApplicationShell, - @inject(IInstaller) private readonly installer: IInstaller - ) {} - /** - * Configures the python interpreter to ensure it can run a Jupyter Kernel by installing any missing dependencies. - * If user opts not to install they can opt to select another interpreter. - */ - public async installMissingDependencies( - interpreter: PythonEnvironment, - token?: CancellationToken - ): Promise { - if (await this.areDependenciesInstalled(interpreter, token)) { - return KernelInterpreterDependencyResponse.ok; - } - - const promptCancellationPromise = createPromiseFromCancellation({ - cancelAction: 'resolve', - defaultValue: undefined, - token - }); - const message = DataScience.libraryRequiredToLaunchJupyterKernelNotInstalledInterpreter().format( - interpreter.displayName || interpreter.envName || interpreter.path, - ProductNames.get(Product.ipykernel)! - ); - const installerToken = wrapCancellationTokens(token); - - const selection = await Promise.race([ - this.appShell.showErrorMessage(message, Common.install()), - promptCancellationPromise - ]); - if (installerToken.isCancellationRequested) { - return KernelInterpreterDependencyResponse.cancel; - } - - if (selection === Common.install()) { - const cancellatonPromise = createPromiseFromCancellation({ - cancelAction: 'resolve', - defaultValue: InstallerResponse.Ignore, - token - }); - // Always pass a cancellation token to `install`, to ensure it waits until the module is installed. - const response = await Promise.race([ - this.installer.install(Product.ipykernel, interpreter, installerToken), - cancellatonPromise - ]); - if (response === InstallerResponse.Installed) { - return KernelInterpreterDependencyResponse.ok; - } - } - return KernelInterpreterDependencyResponse.cancel; - } - public areDependenciesInstalled(interpreter: PythonEnvironment, _token?: CancellationToken): Promise { - return this.installer.isInstalled(Product.ipykernel, interpreter).then((installed) => installed === true); - } -} diff --git a/src/client/datascience/jupyter/kernels/kernelExecution.ts b/src/client/datascience/jupyter/kernels/kernelExecution.ts deleted file mode 100644 index 04b1e31e3708..000000000000 --- a/src/client/datascience/jupyter/kernels/kernelExecution.ts +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { NotebookCell, NotebookCellRunState, NotebookDocument } from 'vscode'; -import { IApplicationShell, ICommandManager, IVSCodeNotebook } from '../../../common/application/types'; -import { IDisposable } from '../../../common/types'; -import { noop } from '../../../common/utils/misc'; -import { captureTelemetry } from '../../../telemetry'; -import { Commands, Telemetry, VSCodeNativeTelemetry } from '../../constants'; -import { MultiCancellationTokenSource } from '../../notebook/helpers/multiCancellationToken'; -import { IDataScienceErrorHandler, INotebook, INotebookEditorProvider } from '../../types'; -import { CellExecution, CellExecutionFactory } from './cellExecution'; -import type { IKernel, IKernelProvider, IKernelSelectionUsage, KernelConnectionMetadata } from './types'; -// tslint:disable-next-line: no-var-requires no-require-imports -const vscodeNotebookEnums = require('vscode') as typeof import('vscode-proposed'); - -/** - * Separate class that deals just with kernel execution. - * Else the `Kernel` class gets very big. - */ -export class KernelExecution implements IDisposable { - public notebook?: INotebook; - - private readonly cellExecutions = new WeakMap(); - - private readonly documentExecutions = new WeakMap(); - - private readonly kernelValidated = new WeakMap }>(); - - private readonly executionFactory: CellExecutionFactory; - private readonly disposables: IDisposable[] = []; - constructor( - private readonly kernelProvider: IKernelProvider, - private readonly commandManager: ICommandManager, - errorHandler: IDataScienceErrorHandler, - editorProvider: INotebookEditorProvider, - readonly kernelSelectionUsage: IKernelSelectionUsage, - readonly appShell: IApplicationShell, - readonly vscNotebook: IVSCodeNotebook, - readonly metadata: Readonly - ) { - this.executionFactory = new CellExecutionFactory(errorHandler, editorProvider, appShell, vscNotebook); - } - - @captureTelemetry(Telemetry.ExecuteNativeCell, undefined, true) - public async executeCell(cell: NotebookCell): Promise { - if (!this.notebook) { - throw new Error('executeObservable cannot be called if kernel has not been started!'); - } - // Cannot execute empty cells. - if (this.cellExecutions.has(cell) || cell.document.getText().trim().length === 0) { - return; - } - const cellExecution = this.executionFactory.create(cell); - this.cellExecutions.set(cell, cellExecution); - - const kernel = this.getKernel(cell.notebook); - - try { - await this.executeIndividualCell(kernel, cellExecution); - } finally { - this.cellExecutions.delete(cell); - } - } - - @captureTelemetry(Telemetry.ExecuteNativeCell, undefined, true) - @captureTelemetry(VSCodeNativeTelemetry.RunAllCells, undefined, true) - public async executeAllCells(document: NotebookDocument): Promise { - if (!this.notebook) { - throw new Error('executeObservable cannot be called if kernel has not been started!'); - } - if (this.documentExecutions.has(document)) { - return; - } - const editor = this.vscNotebook.notebookEditors.find((item) => item.document === document); - if (!editor) { - return; - } - const cancelTokenSource = new MultiCancellationTokenSource(); - this.documentExecutions.set(document, cancelTokenSource); - const kernel = this.getKernel(document); - - await editor.edit((edit) => - edit.replaceMetadata({ ...document.metadata, runState: vscodeNotebookEnums.NotebookRunState.Running }) - ); - const codeCellsToExecute = document.cells - .filter((cell) => cell.cellKind === vscodeNotebookEnums.CellKind.Code) - .filter((cell) => cell.document.getText().trim().length > 0) - .map((cell) => { - const cellExecution = this.executionFactory.create(cell); - this.cellExecutions.set(cellExecution.cell, cellExecution); - return cellExecution; - }); - cancelTokenSource.token.onCancellationRequested( - () => codeCellsToExecute.forEach((cell) => cell.cancel()), - this, - this.disposables - ); - - try { - for (const cellToExecute of codeCellsToExecute) { - const result = this.executeIndividualCell(kernel, cellToExecute); - result.finally(() => this.cellExecutions.delete(cellToExecute.cell)).catch(noop); - const executionResult = await result; - // If a cell has failed or execution cancelled, the get out. - if ( - cancelTokenSource.token.isCancellationRequested || - executionResult === vscodeNotebookEnums.NotebookCellRunState.Error - ) { - await Promise.all(codeCellsToExecute.map((cell) => cell.cancel())); // Cancel pending cells. - break; - } - } - } finally { - await Promise.all(codeCellsToExecute.map((cell) => cell.cancel())); // Cancel pending cells. - this.documentExecutions.delete(document); - await editor.edit((edit) => - edit.replaceMetadata({ ...document.metadata, runState: vscodeNotebookEnums.NotebookRunState.Idle }) - ); - } - } - - public async cancelCell(cell: NotebookCell) { - if (this.cellExecutions.get(cell)) { - await this.cellExecutions.get(cell)!.cancel(); - } - } - - public cancelAllCells(document: NotebookDocument): void { - if (this.documentExecutions.get(document)) { - this.documentExecutions.get(document)!.cancel(); - } - document.cells.forEach((cell) => this.cancelCell(cell)); - } - public dispose() { - this.disposables.forEach((d) => d.dispose()); - } - private async getKernel(document: NotebookDocument): Promise { - await this.validateKernel(document); - let kernel = this.kernelProvider.get(document.uri); - if (!kernel) { - kernel = this.kernelProvider.getOrCreate(document.uri, { metadata: this.metadata }); - } - if (!kernel) { - throw new Error('Unable to create a Kernel to run cell'); - } - await kernel.start(); - return kernel; - } - - private async executeIndividualCell( - kernelPromise: Promise, - cellExecution: CellExecution - ): Promise { - if (!this.notebook) { - throw new Error('No notebook object'); - } - - cellExecution.token.onCancellationRequested( - // Interrupt kernel only if we need to cancel a cell execution. - () => this.commandManager.executeCommand(Commands.NotebookEditorInterruptKernel).then(noop, noop), - this, - this.disposables - ); - - // Start execution - await cellExecution.start(kernelPromise, this.notebook); - - // The result promise will resolve when complete. - return cellExecution.result; - } - - private async validateKernel(document: NotebookDocument): Promise { - const kernel = this.kernelProvider.get(document.uri); - if (!kernel) { - return; - } - if (!this.kernelValidated.get(document)) { - const promise = new Promise((resolve) => - this.kernelSelectionUsage - .useSelectedKernel(kernel?.metadata, document.uri, 'raw') - .finally(() => { - // If there's an exception, then we cannot use the kernel and a message would have been displayed. - // We don't want to cache such a promise, as its possible the user later installs the dependencies. - if (this.kernelValidated.get(document)?.kernel === kernel) { - this.kernelValidated.delete(document); - } - }) - .finally(resolve) - .catch(noop) - ); - - this.kernelValidated.set(document, { kernel, promise }); - } - await this.kernelValidated.get(document)!.promise; - } -} diff --git a/src/client/datascience/jupyter/kernels/kernelProvider.ts b/src/client/datascience/jupyter/kernels/kernelProvider.ts deleted file mode 100644 index 762ba98fb4b9..000000000000 --- a/src/client/datascience/jupyter/kernels/kernelProvider.ts +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as fastDeepEqual from 'fast-deep-equal'; -import { inject, injectable } from 'inversify'; -import { Uri } from 'vscode'; -import { IApplicationShell, ICommandManager, IVSCodeNotebook } from '../../../common/application/types'; -import { traceInfo, traceWarning } from '../../../common/logger'; -import { IAsyncDisposableRegistry, IConfigurationService, IDisposableRegistry } from '../../../common/types'; -import { IDataScienceErrorHandler, INotebookEditorProvider, INotebookProvider } from '../../types'; -import { Kernel } from './kernel'; -import { KernelSelector } from './kernelSelector'; -import { IKernel, IKernelProvider, IKernelSelectionUsage, KernelOptions } from './types'; - -@injectable() -export class KernelProvider implements IKernelProvider { - private readonly kernelsByUri = new Map(); - constructor( - @inject(IAsyncDisposableRegistry) private asyncDisposables: IAsyncDisposableRegistry, - @inject(IDisposableRegistry) private disposables: IDisposableRegistry, - @inject(INotebookProvider) private notebookProvider: INotebookProvider, - @inject(IConfigurationService) private configService: IConfigurationService, - @inject(ICommandManager) private readonly commandManager: ICommandManager, - @inject(IDataScienceErrorHandler) private readonly errorHandler: IDataScienceErrorHandler, - @inject(INotebookEditorProvider) private readonly editorProvider: INotebookEditorProvider, - @inject(KernelSelector) private readonly kernelSelectionUsage: IKernelSelectionUsage, - @inject(IApplicationShell) private readonly appShell: IApplicationShell, - @inject(IVSCodeNotebook) private readonly vscNotebook: IVSCodeNotebook - ) {} - public get(uri: Uri): IKernel | undefined { - return this.kernelsByUri.get(uri.toString())?.kernel; - } - public getOrCreate(uri: Uri, options: KernelOptions): IKernel | undefined { - const existingKernelInfo = this.kernelsByUri.get(uri.toString()); - if (existingKernelInfo && fastDeepEqual(existingKernelInfo.options.metadata, options.metadata)) { - return existingKernelInfo.kernel; - } - - this.disposeOldKernel(uri); - - const waitForIdleTimeout = this.configService.getSettings(uri).datascience.jupyterLaunchTimeout; - const kernel = new Kernel( - uri, - options.metadata, - this.notebookProvider, - this.disposables, - waitForIdleTimeout, - this.commandManager, - this.errorHandler, - this.editorProvider, - this, - this.kernelSelectionUsage, - this.appShell, - this.vscNotebook - ); - this.asyncDisposables.push(kernel); - this.kernelsByUri.set(uri.toString(), { options, kernel }); - this.deleteMappingIfKernelIsDisposed(uri, kernel); - return kernel; - } - /** - * If a kernel has been disposed, then remove the mapping of Uri + Kernel. - */ - private deleteMappingIfKernelIsDisposed(uri: Uri, kernel: IKernel) { - kernel.onDisposed( - () => { - // If the same kernel is associated with this document & it was disposed, then delete it. - if (this.kernelsByUri.get(uri.toString())?.kernel === kernel) { - this.kernelsByUri.delete(uri.toString()); - traceInfo( - `Kernel got disposed, hence there is no longer a kernel associated with ${uri.toString()}`, - kernel - ); - } - }, - this, - this.disposables - ); - } - private disposeOldKernel(uri: Uri) { - this.kernelsByUri - .get(uri.toString()) - ?.kernel.dispose() - .catch((ex) => traceWarning('Failed to dispose old kernel', ex)); // NOSONAR. - this.kernelsByUri.delete(uri.toString()); - } -} - -// export class KernelProvider { diff --git a/src/client/datascience/jupyter/kernels/kernelSelections.ts b/src/client/datascience/jupyter/kernels/kernelSelections.ts deleted file mode 100644 index ee24a167ddd3..000000000000 --- a/src/client/datascience/jupyter/kernels/kernelSelections.ts +++ /dev/null @@ -1,371 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { CancellationToken, EventEmitter } from 'vscode'; -import { traceError } from '../../../common/logger'; -import { IPathUtils, Resource } from '../../../common/types'; -import { createDeferredFromPromise } from '../../../common/utils/async'; -import * as localize from '../../../common/utils/localize'; -import { noop } from '../../../common/utils/misc'; -import { IInterpreterSelector } from '../../../interpreter/configuration/types'; -import { IKernelFinder } from '../../kernel-launcher/types'; -import { IDataScienceFileSystem, IJupyterKernelSpec, IJupyterSessionManager } from '../../types'; -import { detectDefaultKernelName } from './helpers'; -import { KernelService } from './kernelService'; -import { - IKernelSelectionListProvider, - IKernelSpecQuickPickItem, - KernelSpecConnectionMetadata, - LiveKernelConnectionMetadata, - LiveKernelModel, - PythonKernelConnectionMetadata -} from './types'; - -// Small classes, hence all put into one file. -// tslint:disable: max-classes-per-file - -/** - * Given a kernel spec, this will return a quick pick item with appropriate display names and the like. - * - * @param {IJupyterKernelSpec} kernelSpec - * @param {IPathUtils} pathUtils - * @returns {IKernelSpecQuickPickItem} - */ -function getQuickPickItemForKernelSpec( - kernelSpec: IJupyterKernelSpec, - pathUtils: IPathUtils -): IKernelSpecQuickPickItem { - return { - label: kernelSpec.display_name, - // If we have a matching interpreter, then display that path in the dropdown else path of the kernelspec. - detail: pathUtils.getDisplayName(kernelSpec.metadata?.interpreter?.path || kernelSpec.path), - selection: { - kernelModel: undefined, - kernelSpec: kernelSpec, - interpreter: undefined, - kind: 'startUsingKernelSpec' - } - }; -} - -/** - * Given an active kernel, this will return a quick pick item with appropriate display names and the like. - * - * @param {(LiveKernelModel)} kernel - * @param {IPathUtils} pathUtils - * @returns {IKernelSpecQuickPickItem} - */ -function getQuickPickItemForActiveKernel( - kernel: LiveKernelModel, - pathUtils: IPathUtils -): IKernelSpecQuickPickItem { - const pickPath = kernel.metadata?.interpreter?.path || kernel.path; - return { - label: kernel.display_name || kernel.name || '', - // If we have a session, use that path - detail: kernel.session.path || !pickPath ? kernel.session.path : pathUtils.getDisplayName(pickPath), - description: localize.DataScience.jupyterSelectURIRunningDetailFormat().format( - kernel.lastActivityTime.toLocaleString(), - kernel.numberOfConnections.toString() - ), - selection: { kernelModel: kernel, interpreter: undefined, kind: 'connectToLiveKernel' } - }; -} - -/** - * Provider for active kernel specs in a jupyter session. - * - * @export - * @class ActiveJupyterSessionKernelSelectionListProvider - * @implements {IKernelSelectionListProvider} - */ -export class ActiveJupyterSessionKernelSelectionListProvider - implements IKernelSelectionListProvider { - constructor(private readonly sessionManager: IJupyterSessionManager, private readonly pathUtils: IPathUtils) {} - public async getKernelSelections( - _resource: Resource, - _cancelToken?: CancellationToken | undefined - ): Promise[]> { - const [activeKernels, activeSessions, kernelSpecs] = await Promise.all([ - this.sessionManager.getRunningKernels(), - this.sessionManager.getRunningSessions(), - this.sessionManager.getKernelSpecs() - ]); - const items = activeSessions.map((item) => { - const matchingSpec: Partial = - kernelSpecs.find((spec) => spec.name === item.kernel.name) || {}; - const activeKernel = activeKernels.find((active) => active.id === item.kernel.id) || {}; - // tslint:disable-next-line: no-object-literal-type-assertion - return { - ...item.kernel, - ...matchingSpec, - ...activeKernel, - session: item - } as LiveKernelModel; - }); - return items - .filter((item) => item.display_name || item.name) - .filter((item) => 'lastActivityTime' in item && 'numberOfConnections' in item) - .map((item) => getQuickPickItemForActiveKernel(item, this.pathUtils)); - } -} - -/** - * Provider for installed kernel specs (`python -m jupyter kernelspec list`). - * - * @export - * @class InstalledJupyterKernelSelectionListProvider - * @implements {IKernelSelectionListProvider} - */ -export class InstalledJupyterKernelSelectionListProvider - implements IKernelSelectionListProvider { - constructor( - private readonly kernelService: KernelService, - private readonly pathUtils: IPathUtils, - private readonly sessionManager?: IJupyterSessionManager - ) {} - public async getKernelSelections( - _resource: Resource, - cancelToken?: CancellationToken | undefined - ): Promise[]> { - const items = await this.kernelService.getKernelSpecs(this.sessionManager, cancelToken); - return items.map((item) => getQuickPickItemForKernelSpec(item, this.pathUtils)); - } -} - -// Provider for searching for installed kernelspecs on disk without using jupyter to search -export class InstalledRawKernelSelectionListProvider - implements IKernelSelectionListProvider { - constructor(private readonly kernelFinder: IKernelFinder, private readonly pathUtils: IPathUtils) {} - public async getKernelSelections( - resource: Resource, - _cancelToken?: CancellationToken - ): Promise[]> { - const items = await this.kernelFinder.listKernelSpecs(resource); - return items - .filter((item) => { - // If we have a default kernel name and a non-absolute path just hide the item - // Otherwise we end up showing a bunch of "Python 3 - python" default items for - // other interpreters - const match = detectDefaultKernelName(item.name); - if (match) { - return path.isAbsolute(item.path); - } - return true; - }) - .map((item) => getQuickPickItemForKernelSpec(item, this.pathUtils)); - } -} - -/** - * Provider for interpreters to be treated as kernel specs. - * I.e. return interpreters that are to be treated as kernel specs, and not yet installed as kernels. - * - * @export - * @class InterpreterKernelSelectionListProvider - * @implements {IKernelSelectionListProvider} - */ -export class InterpreterKernelSelectionListProvider - implements IKernelSelectionListProvider { - constructor(private readonly interpreterSelector: IInterpreterSelector) {} - public async getKernelSelections( - resource: Resource, - _cancelToken?: CancellationToken - ): Promise[]> { - const items = await this.interpreterSelector.getSuggestions(resource); - return items.map((item) => { - return { - ...item, - // We don't want descriptions. - description: '', - selection: { - kernelModel: undefined, - interpreter: item.interpreter, - kernelSpec: undefined, - kind: 'startUsingPythonInterpreter' - } - }; - }); - } -} - -/** - * Provides a list of kernel specs for selection, for both local and remote sessions. - * - * @export - * @class KernelSelectionProviderFactory - */ -@injectable() -export class KernelSelectionProvider { - private localSuggestionsCache: IKernelSpecQuickPickItem< - KernelSpecConnectionMetadata | PythonKernelConnectionMetadata - >[] = []; - private remoteSuggestionsCache: IKernelSpecQuickPickItem< - LiveKernelConnectionMetadata | KernelSpecConnectionMetadata - >[] = []; - private _listChanged = new EventEmitter(); - public get onDidChangeSelections() { - return this._listChanged.event; - } - constructor( - @inject(KernelService) private readonly kernelService: KernelService, - @inject(IInterpreterSelector) private readonly interpreterSelector: IInterpreterSelector, - @inject(IDataScienceFileSystem) private readonly fs: IDataScienceFileSystem, - @inject(IPathUtils) private readonly pathUtils: IPathUtils, - @inject(IKernelFinder) private readonly kernelFinder: IKernelFinder - ) {} - /** - * Gets a selection of kernel specs from a remote session. - * - * @param {Resource} resource - * @param {IJupyterSessionManager} sessionManager - * @param {CancellationToken} [cancelToken] - * @returns {Promise} - * @memberof KernelSelectionProvider - */ - public async getKernelSelectionsForRemoteSession( - resource: Resource, - sessionManager: IJupyterSessionManager, - cancelToken?: CancellationToken - ): Promise[]> { - const getSelections = async () => { - const installedKernelsPromise = new InstalledJupyterKernelSelectionListProvider( - this.kernelService, - this.pathUtils, - sessionManager - ).getKernelSelections(resource, cancelToken); - const liveKernelsPromise = new ActiveJupyterSessionKernelSelectionListProvider( - sessionManager, - this.pathUtils - ).getKernelSelections(resource, cancelToken); - const [installedKernels, liveKernels] = await Promise.all([installedKernelsPromise, liveKernelsPromise]); - - // Sort by name. - installedKernels.sort((a, b) => (a.label === b.label ? 0 : a.label > b.label ? 1 : -1)); - liveKernels.sort((a, b) => (a.label === b.label ? 0 : a.label > b.label ? 1 : -1)); - return [...liveKernels!, ...installedKernels!]; - }; - - const liveItems = getSelections().then((items) => (this.remoteSuggestionsCache = items)); - // If we have something in cache, return that, while fetching in the background. - const cachedItems = - this.remoteSuggestionsCache.length > 0 ? Promise.resolve(this.remoteSuggestionsCache) : liveItems; - return Promise.race([cachedItems, liveItems]); - } - /** - * Gets a selection of kernel specs for a local session. - * - * @param {Resource} resource - * @param type - * @param {IJupyterSessionManager} [sessionManager] - * @param {CancellationToken} [cancelToken] - * @returns {Promise} - * @memberof KernelSelectionProvider - */ - public async getKernelSelectionsForLocalSession( - resource: Resource, - type: 'raw' | 'jupyter' | 'noConnection', - sessionManager?: IJupyterSessionManager, - cancelToken?: CancellationToken - ): Promise[]> { - const getSelections = async () => { - // For raw versus jupyter connections we need to use a different method for fetching installed kernelspecs - // There is a possible unknown case for if we have a guest jupyter notebook that has not yet connected - // in that case we don't use either method - let installedKernelsPromise: Promise< - IKernelSpecQuickPickItem[] - > = Promise.resolve([]); - switch (type) { - case 'raw': - installedKernelsPromise = new InstalledRawKernelSelectionListProvider( - this.kernelFinder, - this.pathUtils - ).getKernelSelections(resource, cancelToken); - break; - case 'jupyter': - installedKernelsPromise = new InstalledJupyterKernelSelectionListProvider( - this.kernelService, - this.pathUtils, - sessionManager - ).getKernelSelections(resource, cancelToken); - break; - default: - break; - } - const interpretersPromise = new InterpreterKernelSelectionListProvider( - this.interpreterSelector - ).getKernelSelections(resource, cancelToken); - - // tslint:disable-next-line: prefer-const - let [installedKernels, interpreters] = await Promise.all([installedKernelsPromise, interpretersPromise]); - - interpreters = interpreters - .filter((item) => { - // If the interpreter is registered as a kernel then don't inlcude it. - if ( - installedKernels.find( - (installedKernel) => - installedKernel.selection.kernelSpec?.display_name === - item.selection.interpreter?.displayName && - (this.fs.areLocalPathsSame( - (installedKernel.selection.kernelSpec?.argv || [])[0], - item.selection.interpreter?.path || '' - ) || - this.fs.areLocalPathsSame( - installedKernel.selection.kernelSpec?.metadata?.interpreter?.path || '', - item.selection.interpreter?.path || '' - )) - ) - ) { - return false; - } - return true; - }) - .map((item) => { - // We don't want descriptions. - return { ...item, description: '' }; - }); - - const unifiedList = [...installedKernels!, ...interpreters]; - // Sort by name. - unifiedList.sort((a, b) => (a.label === b.label ? 0 : a.label > b.label ? 1 : -1)); - - return unifiedList; - }; - - const liveItems = getSelections().then((items) => (this.localSuggestionsCache = items)); - // If we have something in cache, return that, while fetching in the background. - const cachedItems = - this.localSuggestionsCache.length > 0 ? Promise.resolve(this.localSuggestionsCache) : liveItems; - - const liveItemsDeferred = createDeferredFromPromise(liveItems); - const cachedItemsDeferred = createDeferredFromPromise(cachedItems); - Promise.race([cachedItems, liveItems]) - .then(async () => { - // If the cached items completed first, then if later the live items completes we need to notify - // others that this selection has changed (however check if the results are different). - if (cachedItemsDeferred.completed && !liveItemsDeferred.completed) { - try { - const [liveItemsList, cachedItemsList] = await Promise.all([liveItems, cachedItems]); - // If the list of live items is different from the cached list, then notify a change. - if ( - liveItemsList.length !== cachedItemsList.length && - liveItemsList.length > 0 && - JSON.stringify(liveItemsList) !== JSON.stringify(cachedItemsList) - ) { - this._listChanged.fire(resource); - } - } catch (ex) { - traceError('Error in fetching kernel selections', ex); - } - } - }) - .catch(noop); - - return Promise.race([cachedItems, liveItems]); - } -} diff --git a/src/client/datascience/jupyter/kernels/kernelSelector.ts b/src/client/datascience/jupyter/kernels/kernelSelector.ts deleted file mode 100644 index 93ff0a676d8b..000000000000 --- a/src/client/datascience/jupyter/kernels/kernelSelector.ts +++ /dev/null @@ -1,679 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import type { nbformat } from '@jupyterlab/coreutils'; -import type { Kernel } from '@jupyterlab/services'; -import { sha256 } from 'hash.js'; -import { inject, injectable } from 'inversify'; -// tslint:disable-next-line: no-require-imports -import cloneDeep = require('lodash/cloneDeep'); -import { CancellationToken } from 'vscode-jsonrpc'; -import { IApplicationShell } from '../../../common/application/types'; -import '../../../common/extensions'; -import { traceError, traceInfo, traceVerbose } from '../../../common/logger'; -import { IConfigurationService, IDisposableRegistry, Resource } from '../../../common/types'; -import * as localize from '../../../common/utils/localize'; -import { noop } from '../../../common/utils/misc'; -import { StopWatch } from '../../../common/utils/stopWatch'; -import { IInterpreterService } from '../../../interpreter/contracts'; -import { PythonEnvironment } from '../../../pythonEnvironments/info'; -import { IEventNamePropertyMapping, sendTelemetryEvent } from '../../../telemetry'; -import { Commands, KnownNotebookLanguages, Settings, Telemetry } from '../../constants'; -import { IKernelFinder } from '../../kernel-launcher/types'; -import { getInterpreterInfoStoredInMetadata } from '../../notebookStorage/baseModel'; -import { reportAction } from '../../progress/decorator'; -import { ReportableAction } from '../../progress/types'; -import { - IJupyterConnection, - IJupyterKernelSpec, - IJupyterSessionManager, - IJupyterSessionManagerFactory, - IKernelDependencyService, - INotebookMetadataLive, - INotebookProviderConnection, - KernelInterpreterDependencyResponse -} from '../../types'; -import { createDefaultKernelSpec, getDisplayNameOrNameOfKernelConnection } from './helpers'; -import { KernelSelectionProvider } from './kernelSelections'; -import { KernelService } from './kernelService'; -import { - DefaultKernelConnectionMetadata, - IKernelSelectionUsage, - IKernelSpecQuickPickItem, - KernelConnectionMetadata, - KernelSpecConnectionMetadata, - LiveKernelConnectionMetadata, - PythonKernelConnectionMetadata -} from './types'; - -/** - * All KernelConnections returned (as return values of methods) by the KernelSelector can be used in a number of ways. - * E.g. some part of the code update the `interpreter` property in the `KernelConnectionMetadata` object. - * We need to ensure such changes (i.e. updates to the `KernelConnectionMetadata`) downstream do not change the original `KernelConnectionMetadata`. - * Hence always clone the `KernelConnectionMetadata` returned by the `kernelSelector`. - */ -@injectable() -export class KernelSelector implements IKernelSelectionUsage { - /** - * List of ids of kernels that should be hidden from the kernel picker. - * - * @private - * @type {new Set} - * @memberof KernelSelector - */ - private readonly kernelIdsToHide = new Set(); - constructor( - @inject(KernelSelectionProvider) private readonly selectionProvider: KernelSelectionProvider, - @inject(IApplicationShell) private readonly applicationShell: IApplicationShell, - @inject(KernelService) private readonly kernelService: KernelService, - @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, - @inject(IKernelDependencyService) private readonly kernelDependencyService: IKernelDependencyService, - @inject(IKernelFinder) private readonly kernelFinder: IKernelFinder, - @inject(IJupyterSessionManagerFactory) private jupyterSessionManagerFactory: IJupyterSessionManagerFactory, - @inject(IConfigurationService) private configService: IConfigurationService, - @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry - ) { - disposableRegistry.push( - this.jupyterSessionManagerFactory.onRestartSessionCreated(this.addKernelToIgnoreList.bind(this)) - ); - disposableRegistry.push( - this.jupyterSessionManagerFactory.onRestartSessionUsed(this.removeKernelFromIgnoreList.bind(this)) - ); - } - - /** - * Ensure kernels such as those associated with the restart session are not displayed in the kernel picker. - * - * @param {Kernel.IKernelConnection} kernel - * @memberof KernelSelector - */ - public addKernelToIgnoreList(kernel: Kernel.IKernelConnection): void { - this.kernelIdsToHide.add(kernel.id); - this.kernelIdsToHide.add(kernel.clientId); - } - /** - * Opposite of the add counterpart. - * - * @param {Kernel.IKernelConnection} kernel - * @memberof KernelSelector - */ - public removeKernelFromIgnoreList(kernel: Kernel.IKernelConnection): void { - this.kernelIdsToHide.delete(kernel.id); - this.kernelIdsToHide.delete(kernel.clientId); - } - - /** - * Selects a kernel from a remote session. - */ - public async selectRemoteKernel( - resource: Resource, - stopWatch: StopWatch, - session: IJupyterSessionManager, - cancelToken?: CancellationToken, - currentKernelDisplayName?: string - ): Promise { - let suggestions = await this.selectionProvider.getKernelSelectionsForRemoteSession( - resource, - session, - cancelToken - ); - suggestions = suggestions.filter((item) => !this.kernelIdsToHide.has(item.selection.kernelModel?.id || '')); - const selection = await this.selectKernel( - resource, - 'jupyter', - stopWatch, - Telemetry.SelectRemoteJupyterKernel, - suggestions, - session, - cancelToken, - currentKernelDisplayName - ); - return cloneDeep(selection); - } - /** - * Select a kernel from a local session. - */ - public async selectLocalKernel( - resource: Resource, - type: 'raw' | 'jupyter' | 'noConnection', - stopWatch: StopWatch, - session?: IJupyterSessionManager, - cancelToken?: CancellationToken, - currentKernelDisplayName?: string - ): Promise { - const suggestions = await this.selectionProvider.getKernelSelectionsForLocalSession( - resource, - type, - session, - cancelToken - ); - const selection = await this.selectKernel( - resource, - type, - stopWatch, - Telemetry.SelectLocalJupyterKernel, - suggestions, - session, - cancelToken, - currentKernelDisplayName - ); - return cloneDeep(selection); - } - /** - * Gets a kernel that needs to be used with a local session. - * (will attempt to find the best matching kernel, or prompt user to use current interpreter or select one). - */ - @reportAction(ReportableAction.KernelsGetKernelForLocalConnection) - public async getPreferredKernelForLocalConnection( - resource: Resource, - type: 'raw' | 'jupyter' | 'noConnection', - sessionManager?: IJupyterSessionManager, - notebookMetadata?: nbformat.INotebookMetadata, - disableUI?: boolean, - cancelToken?: CancellationToken, - ignoreDependencyCheck?: boolean - ): Promise< - KernelSpecConnectionMetadata | PythonKernelConnectionMetadata | DefaultKernelConnectionMetadata | undefined - > { - const stopWatch = new StopWatch(); - const telemetryProps: IEventNamePropertyMapping[Telemetry.FindKernelForLocalConnection] = { - kernelSpecFound: false, - interpreterFound: false, - promptedToSelect: false - }; - // When this method is called, we know we've started a local jupyter server or are connecting raw - // Lets pre-warm the list of local kernels. - this.selectionProvider - .getKernelSelectionsForLocalSession(resource, type, sessionManager, cancelToken) - .ignoreErrors(); - - let selection: - | KernelSpecConnectionMetadata - | PythonKernelConnectionMetadata - | DefaultKernelConnectionMetadata - | undefined; - - if (type === 'jupyter') { - selection = await this.getKernelForLocalJupyterConnection( - resource, - stopWatch, - telemetryProps, - sessionManager, - notebookMetadata, - disableUI, - cancelToken - ); - } else if (type === 'raw') { - selection = await this.getKernelForLocalRawConnection( - resource, - notebookMetadata, - cancelToken, - ignoreDependencyCheck - ); - } - - // If still not found, log an error (this seems possible for some people, so use the default) - if (!selection || !selection.kernelSpec) { - traceError('Jupyter Kernel Spec not found for a local connection'); - } - - telemetryProps.kernelSpecFound = !!selection?.kernelSpec; - telemetryProps.interpreterFound = !!selection?.interpreter; - sendTelemetryEvent(Telemetry.FindKernelForLocalConnection, stopWatch.elapsedTime, telemetryProps); - const itemToReturn = cloneDeep(selection); - if (itemToReturn) { - itemToReturn.interpreter = - itemToReturn.interpreter || (await this.interpreterService.getActiveInterpreter(resource)); - } - return itemToReturn; - } - - /** - * Gets a kernel that needs to be used with a remote session. - * (will attempt to find the best matching kernel, or prompt user to use current interpreter or select one). - */ - // tslint:disable-next-line: cyclomatic-complexity - @reportAction(ReportableAction.KernelsGetKernelForRemoteConnection) - public async getPreferredKernelForRemoteConnection( - resource: Resource, - sessionManager?: IJupyterSessionManager, - notebookMetadata?: INotebookMetadataLive, - cancelToken?: CancellationToken - ): Promise { - const [interpreter, specs, sessions] = await Promise.all([ - this.interpreterService.getActiveInterpreter(resource), - this.kernelService.getKernelSpecs(sessionManager, cancelToken), - sessionManager?.getRunningSessions() - ]); - - // First check for a live active session. - if (notebookMetadata && notebookMetadata.id) { - const session = sessions?.find((s) => s.kernel.id === notebookMetadata?.id); - if (session) { - // tslint:disable-next-line: no-any - const liveKernel = session.kernel as any; - const lastActivityTime = liveKernel.last_activity - ? new Date(Date.parse(liveKernel.last_activity.toString())) - : new Date(); - const numberOfConnections = liveKernel.connections - ? parseInt(liveKernel.connections.toString(), 10) - : 0; - return cloneDeep({ - kernelModel: { ...session.kernel, lastActivityTime, numberOfConnections, session }, - interpreter: interpreter, - kind: 'connectToLiveKernel' - }); - } - } - - // No running session, try matching based on interpreter - let bestMatch: IJupyterKernelSpec | undefined; - let bestScore = -1; - for (let i = 0; specs && i < specs?.length; i = i + 1) { - const spec = specs[i]; - let score = 0; - - if (spec) { - // See if the path matches. - if (spec && spec.path && spec.path.length > 0 && interpreter && spec.path === interpreter.path) { - // Path match - score += 8; - } - - // See if the version is the same - if (interpreter && interpreter.version && spec && spec.name) { - // Search for a digit on the end of the name. It should match our major version - const match = /\D+(\d+)/.exec(spec.name); - if (match && match !== null && match.length > 0) { - // See if the version number matches - const nameVersion = parseInt(match[1][0], 10); - if (nameVersion && nameVersion === interpreter.version.major) { - score += 4; - } - } - } - - // See if the display name already matches. - if (spec.display_name && spec.display_name === notebookMetadata?.kernelspec?.display_name) { - score += 16; - } - } - - if (score > bestScore) { - bestMatch = spec; - bestScore = score; - } - } - - if (bestMatch) { - return cloneDeep({ - kernelSpec: bestMatch, - interpreter: interpreter, - kind: 'startUsingKernelSpec' - }); - } else { - // Unlikely scenario, we expect there to be at least one kernel spec. - // Either way, return so that we can start using the default kernel. - return cloneDeep({ - interpreter: interpreter, - kind: 'startUsingDefaultKernel' - }); - } - } - public async useSelectedKernel( - selection: KernelConnectionMetadata, - resource: Resource, - type: 'raw' | 'jupyter' | 'noConnection', - session?: IJupyterSessionManager, - cancelToken?: CancellationToken - ): Promise { - // Check if ipykernel is installed in this kernel. - if (selection.interpreter && type === 'jupyter') { - sendTelemetryEvent(Telemetry.SwitchToInterpreterAsKernel); - const item = await this.useInterpreterAsKernel( - resource, - selection.interpreter, - type, - undefined, - session, - false, - cancelToken - ); - return cloneDeep(item); - } else if (selection.interpreter && type === 'raw') { - const item = await this.useInterpreterAndDefaultKernel(selection.interpreter); - return cloneDeep(item); - } else if (selection.kind === 'connectToLiveKernel') { - sendTelemetryEvent(Telemetry.SwitchToExistingKernel, undefined, { - language: this.computeLanguage(selection.kernelModel.language) - }); - // tslint:disable-next-line: no-any - const interpreter = selection.kernelModel - ? await this.kernelService.findMatchingInterpreter(selection.kernelModel, cancelToken) - : undefined; - return cloneDeep({ - interpreter, - kernelModel: selection.kernelModel, - kind: 'connectToLiveKernel' - }); - } else if (selection.kernelSpec) { - sendTelemetryEvent(Telemetry.SwitchToExistingKernel, undefined, { - language: this.computeLanguage(selection.kernelSpec.language) - }); - const interpreter = selection.kernelSpec - ? await this.kernelService.findMatchingInterpreter(selection.kernelSpec, cancelToken) - : undefined; - await this.kernelService.updateKernelEnvironment(interpreter, selection.kernelSpec, cancelToken); - return cloneDeep({ kernelSpec: selection.kernelSpec, interpreter, kind: 'startUsingKernelSpec' }); - } else { - return; - } - } - public async askForLocalKernel( - resource: Resource, - type: 'raw' | 'jupyter' | 'noConnection', - kernelConnection?: KernelConnectionMetadata - ): Promise { - const displayName = getDisplayNameOrNameOfKernelConnection(kernelConnection); - const message = localize.DataScience.sessionStartFailedWithKernel().format( - displayName, - Commands.ViewJupyterOutput - ); - const selectKernel = localize.DataScience.selectDifferentKernel(); - const cancel = localize.Common.cancel(); - const selection = await this.applicationShell.showErrorMessage(message, selectKernel, cancel); - if (selection === selectKernel) { - const item = await this.selectLocalJupyterKernel(resource, type, displayName); - return cloneDeep(item); - } - } - public async selectJupyterKernel( - resource: Resource, - connection: INotebookProviderConnection | undefined, - type: 'raw' | 'jupyter', - currentKernelDisplayName: string | undefined - ): Promise { - let kernelConnection: KernelConnectionMetadata | undefined; - const settings = this.configService.getSettings(resource); - const isLocalConnection = - connection?.localLaunch ?? - settings.datascience.jupyterServerURI.toLowerCase() === Settings.JupyterServerLocalLaunch; - - if (isLocalConnection) { - kernelConnection = await this.selectLocalJupyterKernel( - resource, - connection?.type || type, - currentKernelDisplayName - ); - } else if (connection && connection.type === 'jupyter') { - kernelConnection = await this.selectRemoteJupyterKernel(resource, connection, currentKernelDisplayName); - } - return cloneDeep(kernelConnection); - } - - private async selectLocalJupyterKernel( - resource: Resource, - type: 'raw' | 'jupyter' | 'noConnection', - currentKernelDisplayName: string | undefined - ): Promise { - return this.selectLocalKernel(resource, type, new StopWatch(), undefined, undefined, currentKernelDisplayName); - } - - private async selectRemoteJupyterKernel( - resource: Resource, - connInfo: IJupyterConnection, - currentKernelDisplayName?: string - ): Promise { - const stopWatch = new StopWatch(); - const session = await this.jupyterSessionManagerFactory.create(connInfo); - return this.selectRemoteKernel(resource, stopWatch, session, undefined, currentKernelDisplayName); - } - - // Get our kernelspec and matching interpreter for a connection to a local jupyter server - private async getKernelForLocalJupyterConnection( - resource: Resource, - stopWatch: StopWatch, - telemetryProps: IEventNamePropertyMapping[Telemetry.FindKernelForLocalConnection], - sessionManager?: IJupyterSessionManager, - notebookMetadata?: nbformat.INotebookMetadata, - disableUI?: boolean, - cancelToken?: CancellationToken - ): Promise< - KernelSpecConnectionMetadata | PythonKernelConnectionMetadata | DefaultKernelConnectionMetadata | undefined - > { - if (notebookMetadata?.kernelspec) { - const kernelSpec = await this.kernelService.findMatchingKernelSpec( - notebookMetadata?.kernelspec, - sessionManager, - cancelToken - ); - if (kernelSpec) { - const interpreter = await this.kernelService.findMatchingInterpreter(kernelSpec, cancelToken); - sendTelemetryEvent(Telemetry.UseExistingKernel); - - // Make sure we update the environment in the kernel before using it - await this.kernelService.updateKernelEnvironment(interpreter, kernelSpec, cancelToken); - return { kind: 'startUsingKernelSpec', interpreter, kernelSpec }; - } else if (!cancelToken?.isCancellationRequested) { - // No kernel info, hence prompt to use current interpreter as a kernel. - const activeInterpreter = await this.interpreterService.getActiveInterpreter(resource); - if (activeInterpreter) { - return this.useInterpreterAsKernel( - resource, - activeInterpreter, - 'jupyter', - notebookMetadata.kernelspec.display_name, - sessionManager, - disableUI, - cancelToken - ); - } else { - telemetryProps.promptedToSelect = true; - return this.selectLocalKernel(resource, 'jupyter', stopWatch, sessionManager, cancelToken); - } - } - } else if (!cancelToken?.isCancellationRequested) { - // No kernel info, hence use current interpreter as a kernel. - const activeInterpreter = await this.interpreterService.getActiveInterpreter(resource); - if (activeInterpreter) { - const kernelSpec = await this.kernelService.searchAndRegisterKernel( - activeInterpreter, - disableUI, - cancelToken - ); - if (kernelSpec) { - return { kind: 'startUsingKernelSpec', kernelSpec, interpreter: activeInterpreter }; - } else { - return { kind: 'startUsingDefaultKernel', interpreter: activeInterpreter }; - } - } - } - } - private async findInterpreterStoredInNotebookMetadata( - resource: Resource, - notebookMetadata?: nbformat.INotebookMetadata - ): Promise { - const info = getInterpreterInfoStoredInMetadata(notebookMetadata); - if (!info) { - return; - } - const interpreters = await this.interpreterService.getInterpreters(resource); - return interpreters.find((item) => sha256().update(item.path).digest('hex') === info.hash); - } - - // Get our kernelspec and interpreter for a local raw connection - private async getKernelForLocalRawConnection( - resource: Resource, - notebookMetadata?: nbformat.INotebookMetadata, - cancelToken?: CancellationToken, - ignoreDependencyCheck?: boolean - ): Promise { - // If user had selected an interpreter (raw kernel), then that interpreter would be stored in the kernelspec metadata. - // Find this matching interpreter & start that using raw kernel. - const interpreterStoredInKernelSpec = await this.findInterpreterStoredInNotebookMetadata( - resource, - notebookMetadata - ); - if (interpreterStoredInKernelSpec) { - return { - kind: 'startUsingPythonInterpreter', - interpreter: interpreterStoredInKernelSpec - }; - } - - // First use our kernel finder to locate a kernelspec on disk - const kernelSpec = await this.kernelFinder.findKernelSpec( - resource, - notebookMetadata?.kernelspec, - cancelToken, - ignoreDependencyCheck - ); - const activeInterpreter = await this.interpreterService.getActiveInterpreter(resource); - if (!kernelSpec && !activeInterpreter) { - return; - } else if (!kernelSpec && activeInterpreter) { - await this.installDependenciesIntoInterpreter(activeInterpreter, ignoreDependencyCheck, cancelToken); - - // Return current interpreter. - return { - kind: 'startUsingPythonInterpreter', - interpreter: activeInterpreter - }; - } else if (kernelSpec) { - // Locate the interpreter that matches our kernelspec - const interpreter = await this.kernelService.findMatchingInterpreter(kernelSpec, cancelToken); - - if (interpreter) { - await this.installDependenciesIntoInterpreter(interpreter, ignoreDependencyCheck, cancelToken); - } - - return { kind: 'startUsingKernelSpec', kernelSpec, interpreter }; - } - } - - private async selectKernel( - resource: Resource, - type: 'raw' | 'jupyter' | 'noConnection', - stopWatch: StopWatch, - telemetryEvent: Telemetry, - suggestions: IKernelSpecQuickPickItem[], - session?: IJupyterSessionManager, - cancelToken?: CancellationToken, - currentKernelDisplayName?: string - ) { - const placeHolder = - localize.DataScience.selectKernel() + - (currentKernelDisplayName ? ` (current: ${currentKernelDisplayName})` : ''); - sendTelemetryEvent(telemetryEvent, stopWatch.elapsedTime); - const selection = await this.applicationShell.showQuickPick(suggestions, { placeHolder }, cancelToken); - if (!selection?.selection) { - return; - } - return (this.useSelectedKernel(selection.selection, resource, type, session, cancelToken) as unknown) as - | T - | undefined; - } - - // When switching to an interpreter in raw kernel mode then just create a default kernelspec for that interpreter to use - private async useInterpreterAndDefaultKernel(interpreter: PythonEnvironment): Promise { - const kernelSpec = createDefaultKernelSpec(interpreter); - return { kernelSpec, interpreter, kind: 'startUsingPythonInterpreter' }; - } - - // If we need to install our dependencies now (for non-native scenarios) - // then install ipykernel into the interpreter or throw error - private async installDependenciesIntoInterpreter( - interpreter: PythonEnvironment, - ignoreDependencyCheck?: boolean, - cancelToken?: CancellationToken - ) { - if (!ignoreDependencyCheck) { - if ( - (await this.kernelDependencyService.installMissingDependencies(interpreter, cancelToken)) !== - KernelInterpreterDependencyResponse.ok - ) { - throw new Error( - localize.DataScience.ipykernelNotInstalled().format(interpreter.displayName || interpreter.path) - ); - } - } - } - - /** - * Use the provided interpreter as a kernel. - * If `displayNameOfKernelNotFound` is provided, then display a message indicating we're using the `current interpreter`. - * This would happen when we're starting a notebook. - * Otherwise, if not provided user is changing the kernel after starting a notebook. - */ - private async useInterpreterAsKernel( - resource: Resource, - interpreter: PythonEnvironment, - type: 'raw' | 'jupyter' | 'noConnection', - displayNameOfKernelNotFound?: string, - session?: IJupyterSessionManager, - disableUI?: boolean, - cancelToken?: CancellationToken - ): Promise { - let kernelSpec: IJupyterKernelSpec | undefined; - - if (await this.kernelDependencyService.areDependenciesInstalled(interpreter, cancelToken)) { - // Find the kernel associated with this interpreter. - kernelSpec = await this.kernelService.findMatchingKernelSpec(interpreter, session, cancelToken); - - if (kernelSpec) { - traceVerbose(`ipykernel installed in ${interpreter.path}, and matching kernelspec found.`); - // Make sure the environment matches. - await this.kernelService.updateKernelEnvironment(interpreter, kernelSpec, cancelToken); - - // Notify the UI that we didn't find the initially requested kernel and are just using the active interpreter - if (displayNameOfKernelNotFound && !disableUI) { - this.applicationShell - .showInformationMessage( - localize.DataScience.fallbackToUseActiveInterpreterAsKernel().format( - displayNameOfKernelNotFound - ) - ) - .then(noop, noop); - } - - sendTelemetryEvent(Telemetry.UseInterpreterAsKernel); - return { kind: 'startUsingKernelSpec', kernelSpec, interpreter }; - } - traceInfo(`ipykernel installed in ${interpreter.path}, no matching kernel found. Will register kernel.`); - } - - // Try an install this interpreter as a kernel. - try { - kernelSpec = await this.kernelService.registerKernel(interpreter, disableUI, cancelToken); - } catch (e) { - sendTelemetryEvent(Telemetry.KernelRegisterFailed); - throw e; - } - - // If we have a display name of a kernel that could not be found, - // then notify user that we're using current interpreter instead. - if (displayNameOfKernelNotFound && !disableUI) { - this.applicationShell - .showInformationMessage( - localize.DataScience.fallBackToRegisterAndUseActiveInterpeterAsKernel().format( - displayNameOfKernelNotFound - ) - ) - .then(noop, noop); - } - - // When this method is called, we know a new kernel may have been registered. - // Lets pre-warm the list of local kernels (with the new list). - this.selectionProvider.getKernelSelectionsForLocalSession(resource, type, session, cancelToken).ignoreErrors(); - - if (kernelSpec) { - return { kind: 'startUsingKernelSpec', kernelSpec, interpreter }; - } - } - - private computeLanguage(language: string | undefined): string { - if (language && KnownNotebookLanguages.includes(language.toLowerCase())) { - return language; - } - return 'unknown'; - } -} diff --git a/src/client/datascience/jupyter/kernels/kernelService.ts b/src/client/datascience/jupyter/kernels/kernelService.ts deleted file mode 100644 index 5a5445d7765e..000000000000 --- a/src/client/datascience/jupyter/kernels/kernelService.ts +++ /dev/null @@ -1,558 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import type { nbformat } from '@jupyterlab/coreutils'; -import type { Kernel } from '@jupyterlab/services'; -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import * as uuid from 'uuid/v4'; -import { CancellationToken, CancellationTokenSource } from 'vscode'; -import { Cancellation, wrapCancellationTokens } from '../../../common/cancellation'; -import { PYTHON_LANGUAGE, PYTHON_WARNINGS } from '../../../common/constants'; -import '../../../common/extensions'; -import { traceDecorators, traceError, traceInfo, traceVerbose, traceWarning } from '../../../common/logger'; - -import { IPythonExecutionFactory } from '../../../common/process/types'; -import { ReadWrite } from '../../../common/types'; -import { sleep } from '../../../common/utils/async'; -import { noop } from '../../../common/utils/misc'; -import { IEnvironmentActivationService } from '../../../interpreter/activation/types'; -import { IInterpreterService } from '../../../interpreter/contracts'; -import { PythonEnvironment } from '../../../pythonEnvironments/info'; -import { captureTelemetry, sendTelemetryEvent } from '../../../telemetry'; -import { getRealPath } from '../../common'; -import { Telemetry } from '../../constants'; -import { reportAction } from '../../progress/decorator'; -import { ReportableAction } from '../../progress/types'; -import { - IDataScienceFileSystem, - IJupyterKernelSpec, - IJupyterSessionManager, - IJupyterSubCommandExecutionService, - IKernelDependencyService, - KernelInterpreterDependencyResponse -} from '../../types'; -import { cleanEnvironment, detectDefaultKernelName } from './helpers'; -import { JupyterKernelSpec } from './jupyterKernelSpec'; -import { LiveKernelModel } from './types'; - -// tslint:disable-next-line: no-var-requires no-require-imports -const NamedRegexp = require('named-js-regexp') as typeof import('named-js-regexp'); - -/** - * Helper to ensure we can differentiate between two types in union types, keeping typing information. - * (basically avoiding the need to case using `as`). - * We cannot use `xx in` as jupyter uses `JSONObject` which is too broad and captures anything and everything. - * - * @param {(nbformat.IKernelspecMetadata | PythonEnvironment)} item - * @returns {item is PythonEnvironment} - */ -function isInterpreter(item: nbformat.IKernelspecMetadata | PythonEnvironment): item is PythonEnvironment { - // Interpreters will not have a `display_name` property, but have `path` and `type` properties. - return ( - !!(item as PythonEnvironment).path && - !!(item as PythonEnvironment).envType && - !(item as nbformat.IKernelspecMetadata).display_name - ); -} - -/** - * Responsible for kernel management and the like. - * - * @export - * @class KernelService - */ -@injectable() -export class KernelService { - constructor( - @inject(IJupyterSubCommandExecutionService) - private readonly jupyterInterpreterExecService: IJupyterSubCommandExecutionService, - @inject(IPythonExecutionFactory) private readonly execFactory: IPythonExecutionFactory, - @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, - @inject(IKernelDependencyService) private readonly kernelDependencyService: IKernelDependencyService, - @inject(IDataScienceFileSystem) private readonly fs: IDataScienceFileSystem, - @inject(IEnvironmentActivationService) private readonly activationHelper: IEnvironmentActivationService - ) {} - /** - * Finds a kernel spec from a given session or jupyter process that matches a given spec. - * - * @param {nbformat.IKernelspecMetadata} kernelSpec The kernelspec (criteria) to be used when searching for a kernel. - * @param {IJupyterSessionManager} [sessionManager] If not provided search against the jupyter process. - * @param {CancellationToken} [cancelToken] - * @returns {(Promise)} - * @memberof KernelService - */ - public async findMatchingKernelSpec( - kernelSpec: nbformat.IKernelspecMetadata, - sessionManager?: IJupyterSessionManager, - cancelToken?: CancellationToken - ): Promise; - /** - * Finds a kernel spec from a given session or jupyter process that matches a given interpreter. - * - * @param {PythonEnvironment} interpreter The interpreter (criteria) to be used when searching for a kernel. - * @param {(IJupyterSessionManager | undefined)} sessionManager If not provided search against the jupyter process. - * @param {CancellationToken} [cancelToken] - * @returns {(Promise)} - * @memberof KernelService - */ - public async findMatchingKernelSpec( - interpreter: PythonEnvironment, - sessionManager?: IJupyterSessionManager | undefined, - cancelToken?: CancellationToken - ): Promise; - public async findMatchingKernelSpec( - option: nbformat.IKernelspecMetadata | PythonEnvironment, - sessionManager: IJupyterSessionManager | undefined, - cancelToken?: CancellationToken - ): Promise { - const specs = await this.getKernelSpecs(sessionManager, cancelToken); - if (isInterpreter(option)) { - return specs.find((item) => { - if (item.language.toLowerCase() !== PYTHON_LANGUAGE.toLowerCase()) { - return false; - } - return ( - this.fs.areLocalPathsSame(item.argv[0], option.path) || - this.fs.areLocalPathsSame(item.metadata?.interpreter?.path || '', option.path) - ); - }); - } else { - return specs.find((item) => item.display_name === option.display_name && item.name === option.name); - } - } - - /** - * Given a kernel, this will find an interpreter that matches the kernel spec. - * Note: When we create our own kernels on behalf of the user, the meta data contains the interpreter information. - * - * @param {IJupyterKernelSpec} kernelSpec - * @param {CancellationToken} [cancelToken] - * @returns {(Promise)} - * @memberof KernelService - */ - // tslint:disable-next-line: cyclomatic-complexity - public async findMatchingInterpreter( - kernelSpec: IJupyterKernelSpec | LiveKernelModel, - cancelToken?: CancellationToken - ): Promise { - const activeInterpreterPromise = this.interpreterService.getActiveInterpreter(undefined); - const allInterpretersPromise = this.interpreterService.getInterpreters(undefined); - // Ensure we handle errors if any (this is required to ensure we do not exit this function without using this promise). - // If promise is rejected and we do not use it, then ignore errors. - activeInterpreterPromise.ignoreErrors(); - // Ensure we handle errors if any (this is required to ensure we do not exit this function without using this promise). - // If promise is rejected and we do not use it, then ignore errors. - allInterpretersPromise.ignoreErrors(); - - // 1. Check if current interpreter has the same path - if (kernelSpec.metadata?.interpreter?.path) { - const interpreter = await this.interpreterService.getInterpreterDetails( - kernelSpec.metadata?.interpreter?.path - ); - if (interpreter) { - traceInfo( - `Found matching interpreter based on metadata, for the kernel ${kernelSpec.name}, ${kernelSpec.display_name}` - ); - return interpreter; - } - traceError( - `KernelSpec has interpreter information, however a matching interepter could not be found for ${kernelSpec.metadata?.interpreter?.path}` - ); - } - - // 2. Check if we have a fully qualified path in `argv` - const pathInArgv = - Array.isArray(kernelSpec.argv) && kernelSpec.argv.length > 0 ? kernelSpec.argv[0] : undefined; - if (pathInArgv && path.basename(pathInArgv) !== pathInArgv) { - const interpreter = await this.interpreterService.getInterpreterDetails(pathInArgv).catch((ex) => { - traceError( - `Failed to get interpreter information for python defined in kernel ${kernelSpec.name}, ${ - kernelSpec.display_name - } with argv: ${(kernelSpec.argv || [])?.join(',')}`, - ex - ); - return; - }); - if (interpreter) { - traceInfo( - `Found matching interpreter based on metadata, for the kernel ${kernelSpec.name}, ${kernelSpec.display_name}` - ); - return interpreter; - } - traceError( - `KernelSpec has interpreter information, however a matching interepter could not be found for ${kernelSpec.metadata?.interpreter?.path}` - ); - } - if (Cancellation.isCanceled(cancelToken)) { - return; - } - - // 3. Check if current interpreter has the same display name - const activeInterpreter = await activeInterpreterPromise; - // If the display name matches the active interpreter then use that. - if (kernelSpec.display_name === activeInterpreter?.displayName) { - return activeInterpreter; - } - - // Check if kernel is `Python2` or `Python3` or a similar generic kernel. - const match = detectDefaultKernelName(kernelSpec.name); - if (match && match.groups()) { - // 3. Look for interpreter with same major version - - const majorVersion = parseInt(match.groups()!.version, 10) || 0; - // If the major versions match, that's sufficient. - if (!majorVersion || (activeInterpreter?.version && activeInterpreter.version.major === majorVersion)) { - traceInfo(`Using current interpreter for kernel ${kernelSpec.name}, ${kernelSpec.display_name}`); - return activeInterpreter; - } - - // Find an interpreter that matches the - const allInterpreters = await allInterpretersPromise; - const found = allInterpreters.find((item) => item.version?.major === majorVersion); - - // If we cannot find a matching one, then use the current interpreter. - if (found) { - traceVerbose( - `Using interpreter ${found.path} for the kernel ${kernelSpec.name}, ${kernelSpec.display_name}` - ); - return found; - } - - traceWarning( - `Unable to find an interpreter that matches the kernel ${kernelSpec.name}, ${kernelSpec.display_name}, some features might not work.` - ); - return activeInterpreter; - } else { - // 5. Look for interpreter with same display name across all interpreters. - - // If the display name matches the active interpreter then use that. - // Look in all of our interpreters if we have somethign that matches this. - const allInterpreters = await allInterpretersPromise; - if (Cancellation.isCanceled(cancelToken)) { - return; - } - - const found = allInterpreters.find((item) => item.displayName === kernelSpec.display_name); - - if (found) { - traceVerbose( - `Found an interpreter that has the same display name as kernelspec ${kernelSpec.display_name}, matches ${found.path}` - ); - return found; - } else { - traceWarning( - `Unable to determine version of Python interpreter to use for kernel ${kernelSpec.name}, ${kernelSpec.display_name}, some features might not work.` - ); - return activeInterpreter; - } - } - } - public async searchAndRegisterKernel( - interpreter: PythonEnvironment, - disableUI?: boolean, - cancelToken?: CancellationToken - ): Promise { - // If a kernelspec already exists for this, then use that. - const found = await this.findMatchingKernelSpec(interpreter, undefined, cancelToken); - if (found) { - sendTelemetryEvent(Telemetry.UseExistingKernel); - - // Make sure the kernel is up to date with the current environment before - // we return it. - await this.updateKernelEnvironment(interpreter, found, cancelToken); - - return found; - } - return this.registerKernel(interpreter, disableUI, cancelToken); - } - - /** - * Registers an interpreter as a kernel. - * The assumption is that `ipykernel` has been installed in the interpreter. - * Kernel created will have following characteristics: - * - display_name = Display name of the interpreter. - * - metadata.interperter = Interpreter information (useful in finding a kernel that matches a given interpreter) - * - env = Will have environment variables of the activated environment. - * - * @param {PythonEnvironment} interpreter - * @param {boolean} [disableUI] - * @param {CancellationToken} [cancelToken] - * @returns {Promise} - * @memberof KernelService - */ - // tslint:disable-next-line: max-func-body-length - // tslint:disable-next-line: cyclomatic-complexity - @captureTelemetry(Telemetry.RegisterInterpreterAsKernel, undefined, true) - @traceDecorators.error('Failed to register an interpreter as a kernel') - @reportAction(ReportableAction.KernelsRegisterKernel) - // tslint:disable-next-line:max-func-body-length - public async registerKernel( - interpreter: PythonEnvironment, - disableUI?: boolean, - cancelToken?: CancellationToken - ): Promise { - if (!interpreter.displayName) { - throw new Error('Interpreter does not have a display name'); - } - - const execServicePromise = this.execFactory.createActivatedEnvironment({ - interpreter, - allowEnvironmentFetchExceptions: true, - bypassCondaExecution: true - }); - // Swallow errors if we get out of here and not resolve this. - execServicePromise.ignoreErrors(); - const name = this.generateKernelNameForIntepreter(interpreter); - // If ipykernel is not installed, prompt to install it. - if (!(await this.kernelDependencyService.areDependenciesInstalled(interpreter, cancelToken)) && !disableUI) { - // If we wish to wait for installation to complete, we must provide a cancel token. - const token = new CancellationTokenSource(); - const response = await this.kernelDependencyService.installMissingDependencies( - interpreter, - wrapCancellationTokens(cancelToken, token.token) - ); - if (response !== KernelInterpreterDependencyResponse.ok) { - traceWarning( - `Prompted to install ipykernel, however ipykernel not installed in the interpreter ${interpreter.path}. Response ${response}` - ); - return; - } - } - - if (Cancellation.isCanceled(cancelToken)) { - return; - } - - const execService = await execServicePromise; - const output = await execService.execModule( - 'ipykernel', - ['install', '--user', '--name', name, '--display-name', interpreter.displayName], - { - throwOnStdErr: true, - encoding: 'utf8', - token: cancelToken - } - ); - if (Cancellation.isCanceled(cancelToken)) { - return; - } - - let kernel = await this.findMatchingKernelSpec( - { display_name: interpreter.displayName, name }, - undefined, - cancelToken - ); - // Wait for at least 5s. We know launching a python (conda env) process on windows can sometimes take around 4s. - for (let counter = 0; counter < 10; counter += 1) { - if (Cancellation.isCanceled(cancelToken)) { - return; - } - if (kernel) { - break; - } - traceWarning('Waiting for 500ms for registered kernel to get detected'); - // Wait for jupyter server to get updated with the new kernel information. - await sleep(500); - kernel = await this.findMatchingKernelSpec( - { display_name: interpreter.displayName, name }, - undefined, - cancelToken - ); - } - if (!kernel) { - // Possible user doesn't have kernelspec installed. - kernel = await this.getKernelSpecFromStdOut(await execService.getExecutablePath(), output.stdout).catch( - (ex) => { - traceError('Failed to get kernelspec from stdout', ex); - return undefined; - } - ); - } - if (!kernel) { - const error = `Kernel not created with the name ${name}, display_name ${interpreter.displayName}. Output is ${output.stdout}`; - throw new Error(error); - } - if (!(kernel instanceof JupyterKernelSpec)) { - const error = `Kernel not registered locally, created with the name ${name}, display_name ${interpreter.displayName}. Output is ${output.stdout}`; - throw new Error(error); - } - if (!kernel.specFile) { - const error = `kernel.json not created with the name ${name}, display_name ${interpreter.displayName}. Output is ${output.stdout}`; - throw new Error(error); - } - - // Update the json with our environment. - await this.updateKernelEnvironment(interpreter, kernel, cancelToken, true); - - sendTelemetryEvent(Telemetry.RegisterAndUseInterpreterAsKernel); - traceInfo( - `Kernel successfully registered for ${interpreter.path} with the name=${name} and spec can be found here ${kernel.specFile}` - ); - return kernel; - } - public async updateKernelEnvironment( - interpreter: PythonEnvironment | undefined, - kernel: IJupyterKernelSpec, - cancelToken?: CancellationToken, - forceWrite?: boolean - ) { - const specedKernel = kernel as JupyterKernelSpec; - if (specedKernel.specFile) { - let specModel: ReadWrite = JSON.parse( - await this.fs.readLocalFile(specedKernel.specFile) - ); - let shouldUpdate = false; - - // Make sure the specmodel has an interpreter or already in the metadata or we - // may overwrite a kernel created by the user - if (interpreter && (specModel.metadata?.interpreter || forceWrite)) { - // Ensure we use a fully qualified path to the python interpreter in `argv`. - if (specModel.argv[0].toLowerCase() === 'conda') { - // If conda is the first word, its possible its a conda activation command. - traceInfo(`Spec argv[0], not updated as it is using conda.`); - } else { - traceInfo(`Spec argv[0] updated from '${specModel.argv[0]}' to '${interpreter.path}'`); - specModel.argv[0] = interpreter.path; - } - - // Get the activated environment variables (as a work around for `conda run` and similar). - // This ensures the code runs within the context of an activated environment. - specModel.env = await this.activationHelper - .getActivatedEnvironmentVariables(undefined, interpreter, true) - .catch(noop) - // tslint:disable-next-line: no-any - .then((env) => (env || {}) as any); - if (Cancellation.isCanceled(cancelToken)) { - return; - } - - // Special case, modify the PYTHONWARNINGS env to the global value. - // otherwise it's forced to 'ignore' because activated variables are cached. - if (specModel.env && process.env[PYTHON_WARNINGS]) { - // tslint:disable-next-line:no-any - specModel.env[PYTHON_WARNINGS] = process.env[PYTHON_WARNINGS] as any; - } else if (specModel.env && specModel.env[PYTHON_WARNINGS]) { - delete specModel.env[PYTHON_WARNINGS]; - } - // Ensure we update the metadata to include interpreter stuff as well (we'll use this to search kernels that match an interpreter). - // We'll need information such as interpreter type, display name, path, etc... - // Its just a JSON file, and the information is small, hence might as well store everything. - specModel.metadata = specModel.metadata || {}; - // tslint:disable-next-line: no-any - specModel.metadata.interpreter = interpreter as any; - - // Indicate we need to write - shouldUpdate = true; - } - - // Scrub the environment of the specmodel to make sure it has allowed values (they all must be strings) - // See this issue here: https://github.com/microsoft/vscode-python/issues/11749 - if (specModel.env) { - specModel = cleanEnvironment(specModel); - shouldUpdate = true; - } - - // Update the kernel.json with our new stuff. - if (shouldUpdate) { - await this.fs.writeLocalFile(specedKernel.specFile, JSON.stringify(specModel, undefined, 2)); - } - - // Always update the metadata for the original kernel. - specedKernel.metadata = specModel.metadata; - } - } - /** - * Gets a list of all kernel specs. - * - * @param {IJupyterSessionManager} [sessionManager] - * @param {CancellationToken} [cancelToken] - * @returns {Promise} - * @memberof KernelService - */ - @reportAction(ReportableAction.KernelsGetKernelSpecs) - public async getKernelSpecs( - sessionManager?: IJupyterSessionManager, - cancelToken?: CancellationToken - ): Promise { - const enumerator = sessionManager - ? sessionManager.getKernelSpecs() - : this.jupyterInterpreterExecService.getKernelSpecs(cancelToken); - if (Cancellation.isCanceled(cancelToken)) { - return []; - } - traceInfo('Enumerating kernel specs...'); - const specs: IJupyterKernelSpec[] = await enumerator; - const result = specs.filter((item) => !!item); - traceInfo(`Found ${result.length} kernelspecs`); - - // Send telemetry on this enumeration. - const anyPython = result.find((k) => k.language === 'python') !== undefined; - sendTelemetryEvent(Telemetry.KernelEnumeration, undefined, { - count: result.length, - isPython: anyPython, - source: sessionManager ? 'connection' : 'cli' - }); - - return result; - } - /** - * Not all characters are allowed in a kernel name. - * This method will generate a name for a kernel based on display name and path. - * Algorithm = + - * - * @private - * @param {PythonEnvironment} interpreter - * @memberof KernelService - */ - private generateKernelNameForIntepreter(interpreter: PythonEnvironment): string { - return `${interpreter.displayName || ''}${uuid()}`.replace(/[^A-Za-z0-9]/g, '').toLowerCase(); - } - - /** - * Will scrape kernelspec info from the output when a new kernel is created. - * - * @private - * @param {string} output - * @returns {JupyterKernelSpec} - * @memberof KernelService - */ - @traceDecorators.error('Failed to parse kernel creation stdout') - private async getKernelSpecFromStdOut(pythonPath: string, output: string): Promise { - if (!output) { - return; - } - - // Output should be of the form - // `Installed kernel in ` - const regEx = NamedRegexp('Installed\\skernelspec\\s(?\\w*)\\sin\\s(?.*)', 'g'); - const match = regEx.exec(output); - if (!match || !match.groups()) { - return; - } - - type RegExGroup = { name: string; path: string }; - const groups = match.groups() as RegExGroup | undefined; - - if (!groups || !groups.name || !groups.path) { - traceError('Kernel Output not parsed', output); - throw new Error('Unable to parse output to get the kernel info'); - } - - const specFile = await getRealPath( - this.fs, - this.execFactory, - pythonPath, - path.join(groups.path, 'kernel.json') - ); - if (!specFile) { - throw new Error('KernelSpec file not found'); - } - - const kernelModel = JSON.parse(await this.fs.readLocalFile(specFile)); - kernelModel.name = groups.name; - return new JupyterKernelSpec(kernelModel as Kernel.ISpecModel, specFile); - } -} diff --git a/src/client/datascience/jupyter/kernels/kernelSwitcher.ts b/src/client/datascience/jupyter/kernels/kernelSwitcher.ts deleted file mode 100644 index bce7b401799e..000000000000 --- a/src/client/datascience/jupyter/kernels/kernelSwitcher.ts +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { ProgressLocation, ProgressOptions } from 'vscode'; -import { IApplicationShell } from '../../../common/application/types'; -import { IConfigurationService } from '../../../common/types'; -import { DataScience } from '../../../common/utils/localize'; -import { JupyterSessionStartError } from '../../baseJupyterSession'; -import { Settings } from '../../constants'; -import { RawKernelSessionStartError } from '../../raw-kernel/rawJupyterSession'; -import { IKernelDependencyService, INotebook, KernelInterpreterDependencyResponse } from '../../types'; -import { JupyterInvalidKernelError } from '../jupyterInvalidKernelError'; -import { kernelConnectionMetadataHasKernelModel, kernelConnectionMetadataHasKernelSpec } from './helpers'; -import { KernelSelector } from './kernelSelector'; -import { KernelConnectionMetadata } from './types'; - -@injectable() -export class KernelSwitcher { - constructor( - @inject(IConfigurationService) private configService: IConfigurationService, - @inject(IApplicationShell) private appShell: IApplicationShell, - @inject(IKernelDependencyService) private readonly kernelDependencyService: IKernelDependencyService, - @inject(KernelSelector) private readonly selector: KernelSelector - ) {} - - public async switchKernelWithRetry(notebook: INotebook, kernel: KernelConnectionMetadata): Promise { - const settings = this.configService.getSettings(notebook.resource); - const isLocalConnection = - notebook.connection?.localLaunch ?? - settings.datascience.jupyterServerURI.toLowerCase() === Settings.JupyterServerLocalLaunch; - if (!isLocalConnection) { - await this.switchToKernel(notebook, kernel); - return; - } - - // Keep retrying, until it works or user cancels. - // Sometimes if a bad kernel is selected, starting a session can fail. - // In such cases we need to let the user know about this and prompt them to select another kernel. - // tslint:disable-next-line: no-constant-condition - while (true) { - try { - await this.switchToKernel(notebook, kernel); - return; - } catch (ex) { - if ( - isLocalConnection && - (ex instanceof JupyterSessionStartError || - ex instanceof JupyterInvalidKernelError || - ex instanceof RawKernelSessionStartError) - ) { - // Looks like we were unable to start a session for the local connection. - // Possibly something wrong with the kernel. - // At this point we have a valid jupyter server. - const potential = await this.selector.askForLocalKernel( - notebook.resource, - notebook.connection?.type || 'noConnection', - kernel - ); - if (potential && Object.keys(potential).length > 0) { - kernel = potential; - continue; - } - } - throw ex; - } - } - } - private async switchToKernel(notebook: INotebook, kernelConnection: KernelConnectionMetadata): Promise { - if (notebook.connection?.type === 'raw' && kernelConnection.interpreter) { - const response = await this.kernelDependencyService.installMissingDependencies( - kernelConnection.interpreter - ); - if (response === KernelInterpreterDependencyResponse.cancel) { - return; - } - } - - const switchKernel = async (newKernelConnection: KernelConnectionMetadata) => { - // Change the kernel. A status update should fire that changes our display - await notebook.setKernelConnection( - newKernelConnection, - this.configService.getSettings(notebook.resource).datascience.jupyterLaunchTimeout - ); - }; - - const kernelModel = kernelConnectionMetadataHasKernelModel(kernelConnection) ? kernelConnection : undefined; - const kernelSpec = kernelConnectionMetadataHasKernelSpec(kernelConnection) ? kernelConnection : undefined; - const kernelName = kernelSpec?.kernelSpec?.name || kernelModel?.kernelModel?.name; - // One of them is bound to be non-empty. - const displayName = - kernelConnection.kind === 'startUsingPythonInterpreter' - ? kernelConnection.interpreter.displayName || kernelConnection.interpreter.path - : kernelModel?.kernelModel?.display_name || kernelName || ''; - const options: ProgressOptions = { - location: ProgressLocation.Notification, - cancellable: false, - title: DataScience.switchingKernelProgress().format(displayName) - }; - await this.appShell.withProgress(options, async (_, __) => switchKernel(kernelConnection!)); - } -} diff --git a/src/client/datascience/jupyter/kernels/types.ts b/src/client/datascience/jupyter/kernels/types.ts deleted file mode 100644 index 9eb8eba35a43..000000000000 --- a/src/client/datascience/jupyter/kernels/types.ts +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import type { KernelMessage, Session } from '@jupyterlab/services'; -import type { Observable } from 'rxjs/Observable'; -import type { CancellationToken, Event, QuickPickItem, Uri } from 'vscode'; -import { NotebookCell, NotebookDocument } from '../../../../../types/vscode-proposed'; -import type { ServerStatus } from '../../../../datascience-ui/interactive-common/mainState'; -import type { IAsyncDisposable, Resource } from '../../../common/types'; -import type { PythonEnvironment } from '../../../pythonEnvironments/info'; -import type { - IJupyterKernel, - IJupyterKernelSpec, - IJupyterSessionManager, - InterruptResult, - KernelSocketInformation -} from '../../types'; - -export type LiveKernelModel = IJupyterKernel & Partial & { session: Session.IModel }; - -/** - * Connection metadata for Live Kernels. - * With this we are able connect to an existing kernel (instead of starting a new session). - */ -export type LiveKernelConnectionMetadata = { - kernelModel: LiveKernelModel; - /** - * Python interpreter will be used for intellisense & the like. - */ - interpreter?: PythonEnvironment; - kind: 'connectToLiveKernel'; -}; -/** - * Connection metadata for Kernels started using kernelspec (JSON). - * This could be a raw kernel (spec might have path to executable for .NET or the like). - * If the executable is not defined in kernelspec json, & it is a Python kernel, then we'll use the provided python interpreter. - */ -export type KernelSpecConnectionMetadata = { - kernelModel?: undefined; - kernelSpec: IJupyterKernelSpec; - /** - * Indicates the interpreter that may be used to start the kernel. - * If possible to start a kernel without this Python interpreter, then this Python interpreter will be used for intellisense & the like. - * This interpreter could also be the interpreter associated with the kernel spec that we are supposed to start. - */ - interpreter?: PythonEnvironment; - kind: 'startUsingKernelSpec'; -}; -/** - * Connection metadata for Kernels started using default kernel. - * Here we tell Jupyter to start a session and let it decide what kernel is to be started. - * (could apply to either local or remote sessions when dealing with Jupyter Servers). - */ -export type DefaultKernelConnectionMetadata = { - /** - * This will be empty as we do not have a kernel spec. - * Left for type compatibility with other types that have kernel spec property. - */ - kernelSpec?: IJupyterKernelSpec; - /** - * Python interpreter will be used for intellisense & the like. - */ - interpreter?: PythonEnvironment; - kind: 'startUsingDefaultKernel'; -}; -/** - * Connection metadata for Kernels started using Python interpreter. - * These are not necessarily raw (it could be plain old Jupyter Kernels, where we register Python interpreter as a kernel). - * We can have KernelSpec information here as well, however that is totally optional. - * We will always start this kernel using old Jupyter style (provided we first register this intrepreter as a kernel) or raw. - */ -export type PythonKernelConnectionMetadata = { - kernelSpec?: IJupyterKernelSpec; - interpreter: PythonEnvironment; - kind: 'startUsingPythonInterpreter'; -}; -export type KernelConnectionMetadata = - | LiveKernelConnectionMetadata - | KernelSpecConnectionMetadata - | PythonKernelConnectionMetadata - | DefaultKernelConnectionMetadata; - -/** - * Returns a string that can be used to uniquely identify a Kernel Connection. - */ -export function getKernelConnectionId(kernelConnection: KernelConnectionMetadata) { - switch (kernelConnection.kind) { - case 'connectToLiveKernel': - return `${kernelConnection.kind}#${kernelConnection.kernelModel.name}.${kernelConnection.kernelModel.session.id}.${kernelConnection.kernelModel.session.name}`; - case 'startUsingDefaultKernel': - return `${kernelConnection.kind}#${kernelConnection}`; - case 'startUsingKernelSpec': - return `${kernelConnection.kind}#${kernelConnection.kernelSpec.name}.${kernelConnection.kernelSpec.display_name}`; - case 'startUsingPythonInterpreter': - return `${kernelConnection.kind}#${kernelConnection.interpreter.path}`; - default: - throw new Error(`Unsupported Kernel Connection ${kernelConnection}`); - } -} - -export interface IKernelSpecQuickPickItem - extends QuickPickItem { - selection: T; -} -export interface IKernelSelectionListProvider { - getKernelSelections(resource: Resource, cancelToken?: CancellationToken): Promise[]>; -} - -export interface IKernelSelectionUsage { - /** - * Given a kernel selection, this method will attempt to use that kernel and return the corresponding Interpreter, Kernel Spec and the like. - * This method will also check if required dependencies are installed or not, and will install them if required. - */ - useSelectedKernel( - selection: KernelConnectionMetadata, - resource: Resource, - type: 'raw' | 'jupyter' | 'noConnection', - session?: IJupyterSessionManager, - cancelToken?: CancellationToken - ): Promise; -} - -export interface IKernel extends IAsyncDisposable { - readonly uri: Uri; - readonly metadata: Readonly; - readonly onStatusChanged: Event; - readonly onDisposed: Event; - readonly onRestarted: Event; - readonly status: ServerStatus; - readonly disposed: boolean; - /** - * Kernel information, used to save in ipynb in the metadata. - * Crucial for non-python notebooks, else we save the incorrect information. - */ - readonly info?: KernelMessage.IInfoReplyMsg['content']; - readonly kernelSocket: Observable; - start(): Promise; - interrupt(): Promise; - restart(): Promise; - executeCell(cell: NotebookCell): Promise; - executeAllCells(document: NotebookDocument): Promise; -} - -export type KernelOptions = { metadata: KernelConnectionMetadata }; -export const IKernelProvider = Symbol('IKernelProvider'); -export interface IKernelProvider { - /** - * Get hold of the active kernel for a given Uri (Notebook or other file). - */ - get(uri: Uri): IKernel | undefined; - /** - * Gets or creates a kernel for a given Uri. - * WARNING: If called with different options for same Uri, old kernel associated with the Uri will be disposed. - */ - getOrCreate(uri: Uri, options: KernelOptions): IKernel | undefined; -} diff --git a/src/client/datascience/jupyter/liveshare/guestJupyterExecution.ts b/src/client/datascience/jupyter/liveshare/guestJupyterExecution.ts deleted file mode 100644 index 3ebcf0606669..000000000000 --- a/src/client/datascience/jupyter/liveshare/guestJupyterExecution.ts +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { injectable } from 'inversify'; -import { SemVer } from 'semver'; -import * as uuid from 'uuid/v4'; -import { CancellationToken } from 'vscode'; - -import { IApplicationShell, ILiveShareApi, IWorkspaceService } from '../../../common/application/types'; - -import { - IAsyncDisposableRegistry, - IConfigurationService, - IDisposableRegistry, - IOutputChannel -} from '../../../common/types'; -import * as localize from '../../../common/utils/localize'; -import { IInterpreterService } from '../../../interpreter/contracts'; -import { IServiceContainer } from '../../../ioc/types'; -import { PythonEnvironment } from '../../../pythonEnvironments/info'; -import { LiveShare, LiveShareCommands } from '../../constants'; -import { IDataScienceFileSystem, IJupyterConnection, INotebookServer, INotebookServerOptions } from '../../types'; -import { JupyterConnectError } from '../jupyterConnectError'; -import { JupyterExecutionBase } from '../jupyterExecution'; -import { KernelSelector } from '../kernels/kernelSelector'; -import { NotebookStarter } from '../notebookStarter'; -import { LiveShareParticipantGuest } from './liveShareParticipantMixin'; -import { ServerCache } from './serverCache'; - -// This class is really just a wrapper around a jupyter execution that also provides a shared live share service -@injectable() -export class GuestJupyterExecution extends LiveShareParticipantGuest( - JupyterExecutionBase, - LiveShare.JupyterExecutionService -) { - private serverCache: ServerCache; - - constructor( - liveShare: ILiveShareApi, - interpreterService: IInterpreterService, - disposableRegistry: IDisposableRegistry, - asyncRegistry: IAsyncDisposableRegistry, - fs: IDataScienceFileSystem, - workspace: IWorkspaceService, - configuration: IConfigurationService, - kernelSelector: KernelSelector, - notebookStarter: NotebookStarter, - appShell: IApplicationShell, - jupyterOutputChannel: IOutputChannel, - serviceContainer: IServiceContainer - ) { - super( - liveShare, - interpreterService, - disposableRegistry, - workspace, - configuration, - kernelSelector, - notebookStarter, - appShell, - jupyterOutputChannel, - serviceContainer - ); - asyncRegistry.push(this); - this.serverCache = new ServerCache(configuration, workspace, fs); - } - - public async dispose(): Promise { - await super.dispose(); - - // Dispose of all of our cached servers - await this.serverCache.dispose(); - } - - public async isNotebookSupported(cancelToken?: CancellationToken): Promise { - const service = await this.waitForService(); - - // Make a remote call on the proxy - if (service) { - const result = await service.request(LiveShareCommands.isNotebookSupported, [], cancelToken); - return result as boolean; - } - - return false; - } - public async getImportPackageVersion(cancelToken?: CancellationToken): Promise { - const service = await this.waitForService(); - - // Make a remote call on the proxy - if (service) { - const result = await service.request(LiveShareCommands.getImportPackageVersion, [], cancelToken); - - if (result) { - return result as SemVer; - } - } - } - public isSpawnSupported(_cancelToken?: CancellationToken): Promise { - return Promise.resolve(false); - } - - public async guestConnectToNotebookServer( - options?: INotebookServerOptions, - cancelToken?: CancellationToken - ): Promise { - const service = await this.waitForService(); - if (service) { - const purpose = options ? options.purpose : uuid(); - const connection: IJupyterConnection = await service.request( - LiveShareCommands.connectToNotebookServer, - [options], - cancelToken - ); - - // If that works, then treat this as a remote server and connect to it - if (connection && connection.baseUrl) { - const newUri = `${connection.baseUrl}?token=${connection.token}`; - return super.connectToNotebookServer( - { - uri: newUri, - skipUsingDefaultConfig: options && options.skipUsingDefaultConfig, - workingDir: options ? options.workingDir : undefined, - purpose, - allowUI: () => false, - skipSearchingForKernel: true - }, - cancelToken - ); - } - } - } - - public async connectToNotebookServer( - options?: INotebookServerOptions, - cancelToken?: CancellationToken - ): Promise { - const result = await this.serverCache.getOrCreate( - this.guestConnectToNotebookServer.bind(this), - options, - cancelToken - ); - - if (!result) { - throw new JupyterConnectError(localize.DataScience.liveShareConnectFailure()); - } - - return result; - } - - public spawnNotebook(_file: string): Promise { - // Not supported in liveshare - throw new Error(localize.DataScience.liveShareCannotSpawnNotebooks()); - } - - public async getUsableJupyterPython(cancelToken?: CancellationToken): Promise { - const service = await this.waitForService(); - if (service) { - return service.request(LiveShareCommands.getUsableJupyterPython, [], cancelToken); - } - } - - public async getServer(options?: INotebookServerOptions): Promise { - return this.serverCache.get(options); - } -} diff --git a/src/client/datascience/jupyter/liveshare/guestJupyterNotebook.ts b/src/client/datascience/jupyter/liveshare/guestJupyterNotebook.ts deleted file mode 100644 index 1cec5d1686d9..000000000000 --- a/src/client/datascience/jupyter/liveshare/guestJupyterNotebook.ts +++ /dev/null @@ -1,364 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import type { Kernel, KernelMessage } from '@jupyterlab/services'; -import type { JSONObject } from '@phosphor/coreutils'; -import { Observable } from 'rxjs/Observable'; -import { Event, EventEmitter, Uri } from 'vscode'; -import { CancellationToken } from 'vscode-jsonrpc'; -import * as vsls from 'vsls/vscode'; -import { ServerStatus } from '../../../../datascience-ui/interactive-common/mainState'; -import { ILiveShareApi } from '../../../common/application/types'; -import { CancellationError } from '../../../common/cancellation'; -import { traceInfo } from '../../../common/logger'; -import { IConfigurationService, IDisposableRegistry, Resource } from '../../../common/types'; -import { createDeferred } from '../../../common/utils/async'; -import * as localize from '../../../common/utils/localize'; -import { noop } from '../../../common/utils/misc'; -import { PythonEnvironment } from '../../../pythonEnvironments/info'; -import { LiveShare, LiveShareCommands } from '../../constants'; -import { - ICell, - IJupyterSession, - INotebook, - INotebookCompletion, - INotebookExecutionInfo, - INotebookExecutionLogger, - INotebookProviderConnection, - InterruptResult, - KernelSocketInformation -} from '../../types'; -import { KernelConnectionMetadata } from '../kernels/types'; -import { LiveShareParticipantDefault, LiveShareParticipantGuest } from './liveShareParticipantMixin'; -import { ResponseQueue } from './responseQueue'; -import { IExecuteObservableResponse, ILiveShareParticipant, IServerResponse } from './types'; - -export class GuestJupyterNotebook - extends LiveShareParticipantGuest(LiveShareParticipantDefault, LiveShare.JupyterNotebookSharedService) - implements INotebook, ILiveShareParticipant { - private get jupyterLab(): typeof import('@jupyterlab/services') { - if (!this._jupyterLab) { - // tslint:disable-next-line:no-require-imports - this._jupyterLab = require('@jupyterlab/services') as typeof import('@jupyterlab/services'); // NOSONAR - } - return this._jupyterLab!; - } - - public get identity(): Uri { - return this._identity; - } - - public get resource(): Resource { - return this._resource; - } - - public get connection(): INotebookProviderConnection | undefined { - return this._executionInfo?.connectionInfo; - } - public kernelSocket = new Observable(); - - public get onSessionStatusChanged(): Event { - if (!this.onStatusChangedEvent) { - this.onStatusChangedEvent = new EventEmitter(); - } - return this.onStatusChangedEvent.event; - } - - public get status(): ServerStatus { - return ServerStatus.Idle; - } - - public get session(): IJupyterSession { - throw new Error('Method not implemented'); - } - public onKernelChanged = new EventEmitter().event; - public onKernelRestarted = new EventEmitter().event; - public onKernelInterrupted = new EventEmitter().event; - public onDisposed = new EventEmitter().event; - public get disposed() { - return false; - } - private _jupyterLab?: typeof import('@jupyterlab/services'); - private responseQueue: ResponseQueue = new ResponseQueue(); - private onStatusChangedEvent: EventEmitter | undefined; - - constructor( - liveShare: ILiveShareApi, - private disposableRegistry: IDisposableRegistry, - private configService: IConfigurationService, - private _resource: Resource, - private _identity: Uri, - private _executionInfo: INotebookExecutionInfo | undefined, - private startTime: number - ) { - super(liveShare); - } - - public shutdown(): Promise { - return Promise.resolve(); - } - - public dispose(): Promise { - if (this.onStatusChangedEvent) { - this.onStatusChangedEvent.dispose(); - } - return this.shutdown(); - } - - public waitForIdle(): Promise { - return Promise.resolve(); - } - - public clear(_id: string): void { - // We don't do anything as we don't cache results in this class. - noop(); - } - - public async execute( - code: string, - file: string, - line: number, - id: string, - cancelToken?: CancellationToken - ): Promise { - // Create a deferred that we'll fire when we're done - const deferred = createDeferred(); - - // Attempt to evaluate this cell in the jupyter notebook - const observable = this.executeObservable(code, file, line, id); - let output: ICell[]; - - observable.subscribe( - (cells: ICell[]) => { - output = cells; - }, - (error) => { - deferred.reject(error); - }, - () => { - deferred.resolve(output); - } - ); - - if (cancelToken) { - this.disposableRegistry.push( - cancelToken.onCancellationRequested(() => deferred.reject(new CancellationError())) - ); - } - - // Wait for the execution to finish - return deferred.promise; - } - - public async inspect(code: string): Promise { - // Send to the other side - return this.sendRequest(LiveShareCommands.inspect, [code]); - } - - public setLaunchingFile(_directory: string): Promise { - // Ignore this command on this side - return Promise.resolve(); - } - - public async setMatplotLibStyle(_useDark: boolean): Promise { - // Guest can't change the style. Maybe output a warning here? - } - - public executeObservable(code: string, file: string, line: number, id: string): Observable { - // Mimic this to the other side and then wait for a response - this.waitForService() - .then((s) => { - if (s) { - s.notify(LiveShareCommands.executeObservable, { code, file, line, id }); - } - }) - .ignoreErrors(); - return this.responseQueue.waitForObservable(code, id); - } - - public async restartKernel(): Promise { - // We need to force a restart on the host side - return this.sendRequest(LiveShareCommands.restart, []); - } - - public async interruptKernel(_timeoutMs: number): Promise { - const settings = this.configService.getSettings(this.resource); - const interruptTimeout = settings.datascience.jupyterInterruptTimeout; - - const response = await this.sendRequest(LiveShareCommands.interrupt, [interruptTimeout]); - return response as InterruptResult; - } - - public async waitForServiceName(): Promise { - // Use our base name plus our id. This means one unique server per notebook - // Live share will not accept a '.' in the name so remove any - const uriString = this.identity.toString(); - return Promise.resolve(`${LiveShare.JupyterNotebookSharedService}${uriString}`); - } - - public async getSysInfo(): Promise { - // This is a special case. Ask the shared server - const service = await this.waitForService(); - if (service) { - const result = await service.request(LiveShareCommands.getSysInfo, []); - return result as ICell; - } - } - - public async requestKernelInfo(): Promise { - // This is a special case. Ask the shared server - const service = await this.waitForService(); - if (service) { - return service.request(LiveShareCommands.getSysInfo, []); - } else { - throw new Error('No LiveShare service to get KernelInfo'); - } - } - - public async getCompletion( - _cellCode: string, - _offsetInCode: number, - _cancelToken?: CancellationToken - ): Promise { - return Promise.resolve({ - matches: [], - cursor: { - start: 0, - end: 0 - }, - metadata: {} - }); - } - - public async onAttach(api: vsls.LiveShare | null): Promise { - await super.onAttach(api); - - if (api) { - const service = await this.waitForService(); - - // Wait for sync up - const synced = service ? await service.request(LiveShareCommands.syncRequest, []) : undefined; - if (!synced && api.session && api.session.role !== vsls.Role.None) { - throw new Error(localize.DataScience.liveShareSyncFailure()); - } - - if (service) { - // Listen to responses - service.onNotify(LiveShareCommands.serverResponse, this.onServerResponse); - - // Request all of the responses since this guest was started. We likely missed a bunch - service.notify(LiveShareCommands.catchupRequest, { since: this.startTime }); - } - } - } - - public getMatchingInterpreter(): PythonEnvironment | undefined { - return; - } - - public getKernelConnection(): KernelConnectionMetadata | undefined { - return; - } - - public setKernelConnection(_spec: KernelConnectionMetadata, _timeout: number): Promise { - return Promise.resolve(); - } - public getLoggers(): INotebookExecutionLogger[] { - return []; - } - - public registerCommTarget( - _targetName: string, - _callback: (comm: Kernel.IComm, msg: KernelMessage.ICommOpenMsg) => void | PromiseLike - ) { - noop(); - } - - public sendCommMessage( - buffers: (ArrayBuffer | ArrayBufferView)[], - content: { comm_id: string; data: JSONObject; target_name: string | undefined }, - // tslint:disable-next-line: no-any - metadata: any, - // tslint:disable-next-line: no-any - msgId: any - ): Kernel.IShellFuture< - KernelMessage.IShellMessage<'comm_msg'>, - KernelMessage.IShellMessage - > { - const shellMessage = this.jupyterLab?.KernelMessage.createMessage>({ - // tslint:disable-next-line: no-any - msgType: 'comm_msg', - channel: 'shell', - buffers, - content, - metadata, - msgId, - session: '1', - username: '1' - }); - - return { - done: Promise.resolve(undefined), - msg: shellMessage!, // NOSONAR - onReply: noop, - onIOPub: noop, - onStdin: noop, - registerMessageHook: noop, - removeMessageHook: noop, - sendInputReply: noop, - isDisposed: false, - dispose: noop - }; - } - - public requestCommInfo( - _content: KernelMessage.ICommInfoRequestMsg['content'] - ): Promise { - const shellMessage = this.jupyterLab?.KernelMessage.createMessage({ - msgType: 'comm_info_reply', - channel: 'shell', - content: { - status: 'ok' - // tslint:disable-next-line: no-any - } as any, - metadata: {}, - session: '1', - username: '1' - }); - - return Promise.resolve(shellMessage); - } - public registerMessageHook( - _msgId: string, - _hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike - ): void { - noop(); - } - public removeMessageHook( - _msgId: string, - _hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike - ): void { - noop(); - } - - public registerIOPubListener(_listener: (msg: KernelMessage.IIOPubMessage, requestId: string) => void): void { - noop(); - } - - private onServerResponse = (args: Object) => { - const er = args as IExecuteObservableResponse; - traceInfo(`Guest serverResponse ${er.pos} ${er.id}`); - // Args should be of type ServerResponse. Stick in our queue if so. - if (args.hasOwnProperty('type')) { - this.responseQueue.push(args as IServerResponse); - } - }; - - // tslint:disable-next-line:no-any - private async sendRequest(command: string, args: any[]): Promise { - const service = await this.waitForService(); - if (service) { - return service.request(command, args); - } - } -} diff --git a/src/client/datascience/jupyter/liveshare/guestJupyterServer.ts b/src/client/datascience/jupyter/liveshare/guestJupyterServer.ts deleted file mode 100644 index 9e0c9a19c82f..000000000000 --- a/src/client/datascience/jupyter/liveshare/guestJupyterServer.ts +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as uuid from 'uuid/v4'; -import { Uri } from 'vscode'; -import { CancellationToken } from 'vscode-jsonrpc'; -import * as vsls from 'vsls/vscode'; -import { ILiveShareApi, IWorkspaceService } from '../../../common/application/types'; -import { IAsyncDisposableRegistry, IConfigurationService, IDisposableRegistry, Resource } from '../../../common/types'; -import { createDeferred, Deferred } from '../../../common/utils/async'; -import * as localize from '../../../common/utils/localize'; -import { IServiceContainer } from '../../../ioc/types'; -import { LiveShare, LiveShareCommands } from '../../constants'; -import { - IJupyterConnection, - IJupyterSessionManagerFactory, - INotebook, - INotebookServer, - INotebookServerLaunchInfo -} from '../../types'; -import { GuestJupyterNotebook } from './guestJupyterNotebook'; -import { LiveShareParticipantDefault, LiveShareParticipantGuest } from './liveShareParticipantMixin'; -import { ILiveShareParticipant } from './types'; - -export class GuestJupyterServer - extends LiveShareParticipantGuest(LiveShareParticipantDefault, LiveShare.JupyterServerSharedService) - implements INotebookServer, ILiveShareParticipant { - private launchInfo: INotebookServerLaunchInfo | undefined; - private connectPromise: Deferred = createDeferred(); - private _id = uuid(); - private notebooks = new Map>(); - - constructor( - private liveShare: ILiveShareApi, - private activationStartTime: number, - _asyncRegistry: IAsyncDisposableRegistry, - private disposableRegistry: IDisposableRegistry, - private configService: IConfigurationService, - _sessionManager: IJupyterSessionManagerFactory, - _workspaceService: IWorkspaceService, - _serviceContainer: IServiceContainer - ) { - super(liveShare); - } - - public get id(): string { - return this._id; - } - - public async connect(launchInfo: INotebookServerLaunchInfo, _cancelToken?: CancellationToken): Promise { - this.launchInfo = launchInfo; - this.connectPromise.resolve(launchInfo); - return Promise.resolve(); - } - - public async createNotebook(resource: Resource, identity: Uri): Promise { - // Remember we can have multiple native editors opened against the same ipynb file. - if (this.notebooks.get(identity.toString())) { - return this.notebooks.get(identity.toString())!; - } - - const deferred = createDeferred(); - this.notebooks.set(identity.toString(), deferred.promise); - // Tell the host side to generate a notebook for this uri - const service = await this.waitForService(); - if (service) { - const resourceString = resource ? resource.toString() : undefined; - const identityString = identity.toString(); - await service.request(LiveShareCommands.createNotebook, [resourceString, identityString]); - } - - // Return a new notebook to listen to - const result = new GuestJupyterNotebook( - this.liveShare, - this.disposableRegistry, - this.configService, - resource, - identity, - this.launchInfo, - this.activationStartTime - ); - deferred.resolve(result); - const oldDispose = result.dispose.bind(result); - result.dispose = () => { - this.notebooks.delete(identity.toString()); - return oldDispose(); - }; - - return result; - } - - public async onSessionChange(api: vsls.LiveShare | null): Promise { - await super.onSessionChange(api); - - this.notebooks.forEach(async (notebook) => { - const guestNotebook = (await notebook) as GuestJupyterNotebook; - if (guestNotebook) { - await guestNotebook.onSessionChange(api); - } - }); - } - - public async getNotebook(resource: Uri): Promise { - return this.notebooks.get(resource.toString()); - } - - public async shutdown(): Promise { - // Send this across to the other side. Otherwise the host server will remain running (like during an export) - const service = await this.waitForService(); - if (service) { - await service.request(LiveShareCommands.disposeServer, []); - } - } - - public dispose(): Promise { - return this.shutdown(); - } - - // Return a copy of the connection information that this server used to connect with - public getConnectionInfo(): IJupyterConnection | undefined { - if (this.launchInfo) { - return this.launchInfo.connectionInfo; - } - - return undefined; - } - - public waitForConnect(): Promise { - return this.connectPromise.promise; - } - - public async waitForServiceName(): Promise { - // First wait for connect to occur - const launchInfo = await this.waitForConnect(); - - // Use our base name plus our purpose. This means one unique server per purpose - if (!launchInfo) { - return LiveShare.JupyterServerSharedService; - } - // tslint:disable-next-line:no-suspicious-comment - // TODO: Should there be some separator in the name? - return `${LiveShare.JupyterServerSharedService}${launchInfo.purpose}`; - } - - public async onAttach(api: vsls.LiveShare | null): Promise { - await super.onAttach(api); - - if (api) { - const service = await this.waitForService(); - - // Wait for sync up - const synced = service ? await service.request(LiveShareCommands.syncRequest, []) : undefined; - if (!synced && api.session && api.session.role !== vsls.Role.None) { - throw new Error(localize.DataScience.liveShareSyncFailure()); - } - } - } -} diff --git a/src/client/datascience/jupyter/liveshare/guestJupyterSessionManager.ts b/src/client/datascience/jupyter/liveshare/guestJupyterSessionManager.ts deleted file mode 100644 index aa90b60ab082..000000000000 --- a/src/client/datascience/jupyter/liveshare/guestJupyterSessionManager.ts +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { CancellationToken } from 'vscode-jsonrpc'; - -import type { Kernel, Session } from '@jupyterlab/services'; -import { EventEmitter } from 'vscode'; -import { noop } from '../../../common/utils/misc'; -import { - IJupyterConnection, - IJupyterKernel, - IJupyterKernelSpec, - IJupyterSession, - IJupyterSessionManager -} from '../../types'; -import { KernelConnectionMetadata } from '../kernels/types'; - -export class GuestJupyterSessionManager implements IJupyterSessionManager { - private connInfo: IJupyterConnection | undefined; - - private restartSessionCreatedEvent = new EventEmitter(); - private restartSessionUsedEvent = new EventEmitter(); - - public constructor(private realSessionManager: IJupyterSessionManager) { - noop(); - } - - public get onRestartSessionCreated() { - return this.restartSessionCreatedEvent.event; - } - - public get onRestartSessionUsed() { - return this.restartSessionUsedEvent.event; - } - public startNew( - kernelConnection: KernelConnectionMetadata | undefined, - workingDirectory: string, - cancelToken?: CancellationToken - ): Promise { - return this.realSessionManager.startNew(kernelConnection, workingDirectory, cancelToken); - } - - public async getKernelSpecs(): Promise { - // Don't return any kernel specs in guest mode. They're only needed for the host side - return Promise.resolve([]); - } - - public getRunningKernels(): Promise { - return Promise.resolve([]); - } - - public getRunningSessions(): Promise { - return Promise.resolve([]); - } - - public async dispose(): Promise { - noop(); - } - - public async initialize(_connInfo: IJupyterConnection): Promise { - this.connInfo = _connInfo; - } - - public getConnInfo(): IJupyterConnection { - return this.connInfo!; - } -} diff --git a/src/client/datascience/jupyter/liveshare/guestJupyterSessionManagerFactory.ts b/src/client/datascience/jupyter/liveshare/guestJupyterSessionManagerFactory.ts deleted file mode 100644 index bac9057a4ec4..000000000000 --- a/src/client/datascience/jupyter/liveshare/guestJupyterSessionManagerFactory.ts +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import type { Kernel } from '@jupyterlab/services'; -import { EventEmitter } from 'vscode'; -import { noop } from '../../../common/utils/misc'; -import { IJupyterConnection, IJupyterSessionManager, IJupyterSessionManagerFactory } from '../../types'; -import { GuestJupyterSessionManager } from './guestJupyterSessionManager'; - -export class GuestJupyterSessionManagerFactory implements IJupyterSessionManagerFactory { - private restartSessionCreatedEvent = new EventEmitter(); - private restartSessionUsedEvent = new EventEmitter(); - public constructor(private realSessionManager: IJupyterSessionManagerFactory) { - noop(); - } - - public async create(connInfo: IJupyterConnection, failOnPassword?: boolean): Promise { - return new GuestJupyterSessionManager(await this.realSessionManager.create(connInfo, failOnPassword)); - } - - public get onRestartSessionCreated() { - return this.restartSessionCreatedEvent.event; - } - - public get onRestartSessionUsed() { - return this.restartSessionUsedEvent.event; - } -} diff --git a/src/client/datascience/jupyter/liveshare/hostJupyterExecution.ts b/src/client/datascience/jupyter/liveshare/hostJupyterExecution.ts deleted file mode 100644 index 72adb1dd8ff0..000000000000 --- a/src/client/datascience/jupyter/liveshare/hostJupyterExecution.ts +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../../common/extensions'; - -import * as uuid from 'uuid/v4'; -import { CancellationToken } from 'vscode'; -import * as vsls from 'vsls/vscode'; - -import { IApplicationShell, ILiveShareApi, IWorkspaceService } from '../../../common/application/types'; -import { traceInfo } from '../../../common/logger'; - -import { - IAsyncDisposableRegistry, - IConfigurationService, - IDisposableRegistry, - IOutputChannel -} from '../../../common/types'; -import { noop } from '../../../common/utils/misc'; -import { IInterpreterService } from '../../../interpreter/contracts'; -import { IServiceContainer } from '../../../ioc/types'; -import { LiveShare, LiveShareCommands } from '../../constants'; -import { - IDataScienceFileSystem, - IJupyterConnection, - IJupyterExecution, - INotebookServer, - INotebookServerOptions -} from '../../types'; -import { getJupyterConnectionDisplayName } from '../jupyterConnection'; -import { JupyterExecutionBase } from '../jupyterExecution'; -import { KernelSelector } from '../kernels/kernelSelector'; -import { NotebookStarter } from '../notebookStarter'; -import { LiveShareParticipantHost } from './liveShareParticipantMixin'; -import { IRoleBasedObject } from './roleBasedFactory'; -import { ServerCache } from './serverCache'; - -// tslint:disable:no-any - -// This class is really just a wrapper around a jupyter execution that also provides a shared live share service -export class HostJupyterExecution - extends LiveShareParticipantHost(JupyterExecutionBase, LiveShare.JupyterExecutionService) - implements IRoleBasedObject, IJupyterExecution { - private serverCache: ServerCache; - private _disposed = false; - private _id = uuid(); - constructor( - liveShare: ILiveShareApi, - interpreterService: IInterpreterService, - disposableRegistry: IDisposableRegistry, - asyncRegistry: IAsyncDisposableRegistry, - fs: IDataScienceFileSystem, - workspace: IWorkspaceService, - configService: IConfigurationService, - kernelSelector: KernelSelector, - notebookStarter: NotebookStarter, - appShell: IApplicationShell, - jupyterOutputChannel: IOutputChannel, - serviceContainer: IServiceContainer - ) { - super( - liveShare, - interpreterService, - disposableRegistry, - workspace, - configService, - kernelSelector, - notebookStarter, - appShell, - jupyterOutputChannel, - serviceContainer - ); - this.serverCache = new ServerCache(configService, workspace, fs); - asyncRegistry.push(this); - } - - public async dispose(): Promise { - traceInfo(`Disposing HostJupyterExecution ${this._id}`); - if (!this._disposed) { - this._disposed = true; - traceInfo(`Disposing super HostJupyterExecution ${this._id}`); - await super.dispose(); - traceInfo(`Getting live share API during dispose HostJupyterExecution ${this._id}`); - const api = await this.api; - traceInfo(`Detaching HostJupyterExecution ${this._id}`); - await this.onDetach(api); - - // Cleanup on dispose. We are going away permanently - if (this.serverCache) { - traceInfo(`Cleaning up server cache ${this._id}`); - await this.serverCache.dispose(); - } - } - traceInfo(`Finished disposing HostJupyterExecution ${this._id}`); - } - - public async hostConnectToNotebookServer( - options?: INotebookServerOptions, - cancelToken?: CancellationToken - ): Promise { - if (!this._disposed) { - return super.connectToNotebookServer(await this.serverCache.generateDefaultOptions(options), cancelToken); - } - } - - public async connectToNotebookServer( - options?: INotebookServerOptions, - cancelToken?: CancellationToken - ): Promise { - if (!this._disposed) { - return this.serverCache.getOrCreate(this.hostConnectToNotebookServer.bind(this), options, cancelToken); - } - } - - public async onAttach(api: vsls.LiveShare | null): Promise { - if (!this._disposed) { - await super.onAttach(api); - - if (api) { - const service = await this.waitForService(); - - // Register handlers for all of the supported remote calls - if (service) { - service.onRequest(LiveShareCommands.isNotebookSupported, this.onRemoteIsNotebookSupported); - service.onRequest(LiveShareCommands.getImportPackageVersion, this.onRemoteGetImportPackageVersion); - service.onRequest(LiveShareCommands.connectToNotebookServer, this.onRemoteConnectToNotebookServer); - service.onRequest(LiveShareCommands.getUsableJupyterPython, this.onRemoteGetUsableJupyterPython); - } - } - } - } - - public async onDetach(api: vsls.LiveShare | null): Promise { - await super.onDetach(api); - - // clear our cached servers if our role is no longer host or none - const newRole = - api === null || (api.session && api.session.role !== vsls.Role.Guest) ? vsls.Role.Host : vsls.Role.Guest; - if (newRole !== vsls.Role.Host) { - await this.serverCache.dispose(); - } - } - - public async getServer(options?: INotebookServerOptions): Promise { - if (!this._disposed) { - // See if we have this server or not. - return this.serverCache.get(options); - } - } - - private onRemoteIsNotebookSupported = (_args: any[], cancellation: CancellationToken): Promise => { - // Just call local - return this.isNotebookSupported(cancellation); - }; - - private onRemoteGetImportPackageVersion = (_args: any[], cancellation: CancellationToken): Promise => { - // Just call local - return this.getImportPackageVersion(cancellation); - }; - - private onRemoteConnectToNotebookServer = async ( - args: any[], - cancellation: CancellationToken - ): Promise => { - // Connect to the local server. THe local server should have started the port forwarding already - const localServer = await this.connectToNotebookServer( - args[0] as INotebookServerOptions | undefined, - cancellation - ); - - // Extract the URI and token for the other side - if (localServer) { - // The other side should be using 'localhost' for anything it's port forwarding. That should just remap - // on the guest side. However we need to eliminate the dispose method. Methods are not serializable - const connectionInfo = localServer.getConnectionInfo(); - if (connectionInfo) { - return { - type: 'jupyter', - baseUrl: connectionInfo.baseUrl, - token: connectionInfo.token, - hostName: connectionInfo.hostName, - localLaunch: false, - localProcExitCode: undefined, - valid: true, - displayName: getJupyterConnectionDisplayName(connectionInfo.token, connectionInfo.baseUrl), - disconnected: (_l) => { - return { dispose: noop }; - }, - dispose: noop, - rootDirectory: connectionInfo.rootDirectory - }; - } - } - }; - - private onRemoteGetUsableJupyterPython = (_args: any[], cancellation: CancellationToken): Promise => { - // Just call local - return this.getUsableJupyterPython(cancellation); - }; -} diff --git a/src/client/datascience/jupyter/liveshare/hostJupyterNotebook.ts b/src/client/datascience/jupyter/liveshare/hostJupyterNotebook.ts deleted file mode 100644 index 316ed151c639..000000000000 --- a/src/client/datascience/jupyter/liveshare/hostJupyterNotebook.ts +++ /dev/null @@ -1,409 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { Observable } from 'rxjs/Observable'; -import * as vscode from 'vscode'; -import { CancellationToken } from 'vscode-jsonrpc'; -import * as vsls from 'vsls/vscode'; -import { IApplicationShell, ILiveShareApi, IWorkspaceService } from '../../../common/application/types'; -import '../../../common/extensions'; -import { traceError } from '../../../common/logger'; - -import { IConfigurationService, IDisposableRegistry, Resource } from '../../../common/types'; -import { createDeferred } from '../../../common/utils/async'; -import { Identifiers, LiveShare, LiveShareCommands } from '../../constants'; -import { IExecuteInfo } from '../../interactive-common/interactiveWindowTypes'; -import { - ICell, - IDataScienceFileSystem, - IJupyterSession, - INotebook, - INotebookExecutionInfo, - INotebookExecutionLogger, - InterruptResult -} from '../../types'; -import { JupyterNotebookBase } from '../jupyterNotebook'; -import { LiveShareParticipantHost } from './liveShareParticipantMixin'; -import { ResponseQueue } from './responseQueue'; -import { IRoleBasedObject } from './roleBasedFactory'; -import { IExecuteObservableResponse, IResponseMapping, IServerResponse, ServerResponseType } from './types'; - -// tslint:disable-next-line: no-require-imports -import cloneDeep = require('lodash/cloneDeep'); - -// tslint:disable:no-any - -export class HostJupyterNotebook - extends LiveShareParticipantHost(JupyterNotebookBase, LiveShare.JupyterNotebookSharedService) - implements IRoleBasedObject, INotebook { - private catchupResponses: ResponseQueue = new ResponseQueue(); - private localResponses: ResponseQueue = new ResponseQueue(); - private requestLog: Map = new Map(); - private catchupPendingCount: number = 0; - private isDisposed = false; - constructor( - liveShare: ILiveShareApi, - session: IJupyterSession, - configService: IConfigurationService, - disposableRegistry: IDisposableRegistry, - executionInfo: INotebookExecutionInfo, - loggers: INotebookExecutionLogger[], - resource: Resource, - identity: vscode.Uri, - getDisposedError: () => Error, - workspace: IWorkspaceService, - appService: IApplicationShell, - fs: IDataScienceFileSystem - ) { - super( - liveShare, - session, - configService, - disposableRegistry, - executionInfo, - loggers, - resource, - identity, - getDisposedError, - workspace, - appService, - fs - ); - } - - public dispose = async (): Promise => { - if (!this.isDisposed) { - this.isDisposed = true; - await super.dispose(); - const api = await this.api; - return this.onDetach(api); - } - }; - - public async onAttach(api: vsls.LiveShare | null): Promise { - await super.onAttach(api); - - if (api && !this.isDisposed) { - const service = await this.waitForService(); - - // Attach event handlers to different requests - if (service) { - // Requests return arrays - service.onRequest(LiveShareCommands.syncRequest, (_args: any[], _cancellation: CancellationToken) => - this.onSync() - ); - service.onRequest(LiveShareCommands.getSysInfo, (_args: any[], cancellation: CancellationToken) => - this.onGetSysInfoRequest(cancellation) - ); - service.onRequest(LiveShareCommands.inspect, (args: any[], cancellation: CancellationToken) => - this.inspect(args[0], 0, cancellation) - ); - service.onRequest(LiveShareCommands.restart, (args: any[], cancellation: CancellationToken) => - this.onRestartRequest( - args.length > 0 ? (args[0] as number) : LiveShare.InterruptDefaultTimeout, - cancellation - ) - ); - service.onRequest(LiveShareCommands.interrupt, (args: any[], cancellation: CancellationToken) => - this.onInterruptRequest( - args.length > 0 ? (args[0] as number) : LiveShare.InterruptDefaultTimeout, - cancellation - ) - ); - service.onRequest(LiveShareCommands.disposeServer, (_args: any[], _cancellation: CancellationToken) => - this.dispose() - ); - - // Notifications are always objects. - service.onNotify(LiveShareCommands.catchupRequest, (args: object) => this.onCatchupRequest(args)); - service.onNotify(LiveShareCommands.executeObservable, (args: object) => - this.onExecuteObservableRequest(args) - ); - } - } - } - - public async waitForServiceName(): Promise { - // Use our base name plus our id. This means one unique server per notebook - // Convert to our shared URI to match the guest and remove any '.' as live share won't support them - const sharedUri = - this.identity.scheme === 'file' ? this.finishedApi!.convertLocalUriToShared(this.identity) : this.identity; - return Promise.resolve(`${LiveShare.JupyterNotebookSharedService}${sharedUri.toString()}`); - } - - public async onPeerChange(ev: vsls.PeersChangeEvent): Promise { - await super.onPeerChange(ev); - - // Keep track of the number of guests that need to do a catchup request - this.catchupPendingCount += - ev.added.filter((e) => e.role === vsls.Role.Guest).length - - ev.removed.filter((e) => e.role === vsls.Role.Guest).length; - } - - public clear(id: string): void { - this.requestLog.delete(id); - } - - public executeObservable( - code: string, - file: string, - line: number, - id: string, - silent?: boolean - ): Observable { - // See if this has already been asked for not - if (this.requestLog.has(id)) { - // This must be a local call that occurred after a guest call. Just - // use the local responses to return the results. - return this.localResponses.waitForObservable(code, id); - } else { - // Otherwise make a new request and save response in the catchup list. THis is a - // a request that came directly from the host so the host will be listening to the observable returned - // and we don't need to save the response in the local queue. - return this.makeObservableRequest(code, file, line, id, silent, [this.catchupResponses]); - } - } - - public async restartKernel(timeoutMs: number): Promise { - try { - await super.restartKernel(timeoutMs); - } catch (exc) { - this.postException(exc, []); - throw exc; - } - } - - public async interruptKernel(timeoutMs: number): Promise { - try { - return super.interruptKernel(timeoutMs); - } catch (exc) { - this.postException(exc, []); - throw exc; - } - } - - private makeRequest( - code: string, - file: string, - line: number, - id: string, - silent: boolean | undefined, - responseQueues: ResponseQueue[] - ): Promise { - // Create a deferred that we'll fire when we're done - const deferred = createDeferred(); - - // Attempt to evaluate this cell in the jupyter notebook - const observable = this.makeObservableRequest(code, file, line, id, silent, responseQueues); - let output: ICell[]; - - observable.subscribe( - (cells: ICell[]) => { - output = cells; - }, - (error) => { - deferred.reject(error); - }, - () => { - deferred.resolve(output); - } - ); - - // Wait for the execution to finish - return deferred.promise; - } - - private makeObservableRequest( - code: string, - file: string, - line: number, - id: string, - silent: boolean | undefined, - responseQueues: ResponseQueue[] - ): Observable { - try { - this.requestLog.set(id, Date.now()); - const inner = super.executeObservable(code, file, line, id, silent); - - // Cleanup old requests - const now = Date.now(); - for (const [k, val] of this.requestLog) { - if (now - val > LiveShare.ResponseLifetime) { - this.requestLog.delete(k); - } - } - - // Wrap the observable returned to send the responses to the guest(s) too. - return this.postObservableResult(code, inner, id, responseQueues); - } catch (exc) { - this.postException(exc, responseQueues); - throw exc; - } - } - - private translateCellForGuest(cell: ICell): ICell { - const copy = { ...cell }; - if (this.role === vsls.Role.Host && this.finishedApi && copy.file !== Identifiers.EmptyFileName) { - copy.file = this.finishedApi.convertLocalUriToShared(vscode.Uri.file(copy.file)).fsPath; - } - return copy; - } - - private onSync(): Promise { - return Promise.resolve(true); - } - - private onGetSysInfoRequest(_cancellation: CancellationToken): Promise { - // Get the sys info from our local server - return super.getSysInfo(); - } - - private onRestartRequest(timeout: number, _cancellation: CancellationToken): Promise { - // Just call the base - return super.restartKernel(timeout); - } - private onInterruptRequest(timeout: number, _cancellation: CancellationToken): Promise { - // Just call the base - return super.interruptKernel(timeout); - } - - private async onCatchupRequest(args: object): Promise { - if (args.hasOwnProperty('since')) { - const service = await this.waitForService(); - if (service) { - // Send results for all responses that are left. - this.catchupResponses.send(service, this.translateForGuest.bind(this)); - - // Eliminate old responses if possible. - this.catchupPendingCount -= 1; - if (this.catchupPendingCount <= 0) { - this.catchupResponses.clear(); - } - } - } - } - - private onExecuteObservableRequest(args: object) { - // See if we started this execute or not already. - if (args.hasOwnProperty('code')) { - const obj = args as IExecuteInfo; - if (!this.requestLog.has(obj.id)) { - try { - // Convert the file name if necessary - const uri = vscode.Uri.parse(`vsls:${obj.file}`); - const file = - this.finishedApi && obj.file !== Identifiers.EmptyFileName - ? this.finishedApi.convertSharedUriToLocal(uri).fsPath - : obj.file; - - // We need the results of this execute to end up in both the guest responses and the local responses - this.makeRequest(obj.code, file, obj.line, obj.id, false, [ - this.localResponses, - this.catchupResponses - ]).ignoreErrors(); - } catch (e) { - traceError(e); - } - } - } - } - - private postObservableResult( - code: string, - observable: Observable, - id: string, - responseQueues: ResponseQueue[] - ): Observable { - return new Observable((subscriber) => { - let pos = 0; - - // Listen to all of the events on the observable passed in. - observable.subscribe( - (cells) => { - // Forward to the next listener - subscriber.next(cells); - - // Send across to the guest side - try { - this.postObservableNext(code, pos, cells, id, responseQueues); - pos += 1; - } catch (e) { - subscriber.error(e); - this.postException(e, responseQueues); - } - }, - (e) => { - subscriber.error(e); - this.postException(e, responseQueues); - }, - () => { - subscriber.complete(); - this.postObservableComplete(code, pos, id, responseQueues); - } - ); - }); - } - - private translateForGuest = (r: IServerResponse): IServerResponse => { - // Remap the cell paths - const er = r as IExecuteObservableResponse; - if (er && er.cells) { - return { cells: er.cells.map(this.translateCellForGuest, this), ...er }; - } - return r; - }; - - private postObservableNext(code: string, pos: number, cells: ICell[], id: string, responseQueues: ResponseQueue[]) { - this.postResult( - ServerResponseType.ExecuteObservable, - { code, pos, type: ServerResponseType.ExecuteObservable, cells, id, time: Date.now() }, - this.translateForGuest, - responseQueues - ); - } - - private postObservableComplete(code: string, pos: number, id: string, responseQueues: ResponseQueue[]) { - this.postResult( - ServerResponseType.ExecuteObservable, - { code, pos, type: ServerResponseType.ExecuteObservable, cells: undefined, id, time: Date.now() }, - this.translateForGuest, - responseQueues - ); - } - - private postException(exc: any, responseQueues: ResponseQueue[]) { - this.postResult( - ServerResponseType.Exception, - { type: ServerResponseType.Exception, time: Date.now(), message: exc.toString() }, - (r) => r, - responseQueues - ); - } - - private postResult( - _type: T, - result: R[T], - guestTranslator: (r: IServerResponse) => IServerResponse, - responseQueues: ResponseQueue[] - ): void { - const typedResult = (result as any) as IServerResponse; - if (typedResult) { - try { - // Make a deep copy before we send. Don't want local copies being modified - const deepCopy = cloneDeep(typedResult); - this.waitForService() - .then((s) => { - if (s) { - s.notify(LiveShareCommands.serverResponse, guestTranslator(deepCopy)); - } - }) - .ignoreErrors(); - - // Need to also save in memory for those guests that are in the middle of starting up - responseQueues.forEach((r) => r.push(deepCopy)); - } catch (exc) { - traceError(exc); - } - } - } -} diff --git a/src/client/datascience/jupyter/liveshare/hostJupyterServer.ts b/src/client/datascience/jupyter/liveshare/hostJupyterServer.ts deleted file mode 100644 index 09ea53800ffb..000000000000 --- a/src/client/datascience/jupyter/liveshare/hostJupyterServer.ts +++ /dev/null @@ -1,354 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../../common/extensions'; - -import * as os from 'os'; -import * as vscode from 'vscode'; -import { CancellationToken } from 'vscode-jsonrpc'; -import * as vsls from 'vsls/vscode'; -import { IApplicationShell, ILiveShareApi, IWorkspaceService } from '../../../common/application/types'; -import { isTestExecution } from '../../../common/constants'; -import { traceInfo } from '../../../common/logger'; -import { - IAsyncDisposableRegistry, - IConfigurationService, - IDisposableRegistry, - IOutputChannel, - Resource -} from '../../../common/types'; -import { createDeferred } from '../../../common/utils/async'; -import * as localize from '../../../common/utils/localize'; -import { IInterpreterService } from '../../../interpreter/contracts'; -import { IServiceContainer } from '../../../ioc/types'; -import { Identifiers, LiveShare, LiveShareCommands, RegExpValues } from '../../constants'; -import { - IDataScienceFileSystem, - IJupyterSession, - IJupyterSessionManager, - IJupyterSessionManagerFactory, - INotebook, - INotebookExecutionLogger, - INotebookMetadataLive, - INotebookServer, - INotebookServerLaunchInfo -} from '../../types'; -import { JupyterServerBase } from '../jupyterServer'; -import { computeWorkingDirectory } from '../jupyterUtils'; -import { KernelSelector } from '../kernels/kernelSelector'; -import { HostJupyterNotebook } from './hostJupyterNotebook'; -import { LiveShareParticipantHost } from './liveShareParticipantMixin'; -import { IRoleBasedObject } from './roleBasedFactory'; -// tslint:disable:no-any - -export class HostJupyterServer extends LiveShareParticipantHost(JupyterServerBase, LiveShare.JupyterServerSharedService) - implements IRoleBasedObject, INotebookServer { - private disposed = false; - private portToForward = 0; - private sharedPort: vscode.Disposable | undefined; - constructor( - private liveShare: ILiveShareApi, - _startupTime: number, - asyncRegistry: IAsyncDisposableRegistry, - disposableRegistry: IDisposableRegistry, - configService: IConfigurationService, - sessionManager: IJupyterSessionManagerFactory, - private workspaceService: IWorkspaceService, - serviceContainer: IServiceContainer, - private appService: IApplicationShell, - private fs: IDataScienceFileSystem, - private readonly kernelSelector: KernelSelector, - private readonly interpreterService: IInterpreterService, - outputChannel: IOutputChannel - ) { - super( - liveShare, - asyncRegistry, - disposableRegistry, - configService, - sessionManager, - serviceContainer, - outputChannel - ); - } - - public async dispose(): Promise { - if (!this.disposed) { - this.disposed = true; - traceInfo(`Disposing HostJupyterServer`); - await super.dispose(); - const api = await this.api; - await this.onDetach(api); - traceInfo(`Finished disposing HostJupyterServer`); - } - } - - public async connect(launchInfo: INotebookServerLaunchInfo, cancelToken?: CancellationToken): Promise { - if (launchInfo.connectionInfo && launchInfo.connectionInfo.localLaunch) { - const portMatch = RegExpValues.ExtractPortRegex.exec(launchInfo.connectionInfo.baseUrl); - if (portMatch && portMatch.length > 1) { - const port = parseInt(portMatch[1], 10); - await this.attemptToForwardPort(this.finishedApi, port); - } - } - return super.connect(launchInfo, cancelToken); - } - - public async onAttach(api: vsls.LiveShare | null): Promise { - await super.onAttach(api); - - if (api && !this.disposed) { - const service = await this.waitForService(); - - // Attach event handlers to different requests - if (service) { - // Requests return arrays - service.onRequest(LiveShareCommands.syncRequest, (_args: any[], _cancellation: CancellationToken) => - this.onSync() - ); - service.onRequest(LiveShareCommands.disposeServer, (_args: any[], _cancellation: CancellationToken) => - this.dispose() - ); - service.onRequest( - LiveShareCommands.createNotebook, - async (args: any[], cancellation: CancellationToken) => { - const resource = this.parseUri(args[0]); - const identity = this.parseUri(args[1]); - // Don't return the notebook. We don't want it to be serialized. We just want its live share server to be started. - const notebook = (await this.createNotebook( - resource, - identity!, - undefined, - cancellation - )) as HostJupyterNotebook; - await notebook.onAttach(api); - } - ); - - // See if we need to forward the port - await this.attemptToForwardPort(api, this.portToForward); - } - } - } - - public async onSessionChange(api: vsls.LiveShare | null): Promise { - await super.onSessionChange(api); - - this.getNotebooks().forEach(async (notebook) => { - const hostNotebook = (await notebook) as HostJupyterNotebook; - if (hostNotebook) { - await hostNotebook.onSessionChange(api); - } - }); - } - - public async onDetach(api: vsls.LiveShare | null): Promise { - await super.onDetach(api); - - // Make sure to unshare our port - if (api && this.sharedPort) { - this.sharedPort.dispose(); - this.sharedPort = undefined; - } - } - - public async waitForServiceName(): Promise { - // First wait for connect to occur - const launchInfo = await this.waitForConnect(); - - // Use our base name plus our purpose. This means one unique server per purpose - if (!launchInfo) { - return LiveShare.JupyterServerSharedService; - } - // tslint:disable-next-line:no-suspicious-comment - // TODO: Should there be some separator in the name? - return `${LiveShare.JupyterServerSharedService}${launchInfo.purpose}`; - } - - protected get isDisposed() { - return this.disposed; - } - - protected async createNotebookInstance( - resource: Resource, - identity: vscode.Uri, - sessionManager: IJupyterSessionManager, - possibleSession: IJupyterSession | undefined, - disposableRegistry: IDisposableRegistry, - configService: IConfigurationService, - serviceContainer: IServiceContainer, - notebookMetadata?: INotebookMetadataLive, - cancelToken?: CancellationToken - ): Promise { - // See if already exists. - const existing = await this.getNotebook(identity); - if (existing) { - // Dispose the possible session as we don't need it - if (possibleSession) { - await possibleSession.dispose(); - } - - // Then we can return the existing notebook. - return existing; - } - - // Compute launch information from the resource and the notebook metadata - const notebookPromise = createDeferred(); - // Save the notebook - this.setNotebook(identity, notebookPromise.promise); - - const getExistingSession = async () => { - const { info, changedKernel } = await this.computeLaunchInfo( - resource, - sessionManager, - notebookMetadata, - cancelToken - ); - - // If we switched kernels, try switching the possible session - if (changedKernel && possibleSession && info.kernelConnectionMetadata) { - await possibleSession.changeKernel( - info.kernelConnectionMetadata, - this.configService.getSettings(resource).datascience.jupyterLaunchTimeout - ); - } - - // Figure out the working directory we need for our new notebook. - const workingDirectory = await computeWorkingDirectory(resource, this.workspaceService); - - // Start a session (or use the existing one if allowed) - const session = - possibleSession && this.fs.areLocalPathsSame(possibleSession.workingDirectory, workingDirectory) - ? possibleSession - : await sessionManager.startNew(info.kernelConnectionMetadata, workingDirectory, cancelToken); - traceInfo(`Started session ${this.id}`); - return { info, session }; - }; - - try { - const { info, session } = await getExistingSession(); - - if (session) { - // Create our notebook - const notebook = new HostJupyterNotebook( - this.liveShare, - session, - configService, - disposableRegistry, - info, - serviceContainer.getAll(INotebookExecutionLogger), - resource, - identity, - this.getDisposedError.bind(this), - this.workspaceService, - this.appService, - this.fs - ); - - // Wait for it to be ready - traceInfo(`Waiting for idle (session) ${this.id}`); - const idleTimeout = configService.getSettings().datascience.jupyterLaunchTimeout; - await notebook.waitForIdle(idleTimeout); - - // Run initial setup - await notebook.initialize(cancelToken); - - traceInfo(`Finished connecting ${this.id}`); - - notebookPromise.resolve(notebook); - } else { - notebookPromise.reject(this.getDisposedError()); - } - } catch (ex) { - // If there's an error, then reject the promise that is returned. - // This original promise must be rejected as it is cached (check `setNotebook`). - notebookPromise.reject(ex); - } - - return notebookPromise.promise; - } - - private async computeLaunchInfo( - resource: Resource, - sessionManager: IJupyterSessionManager, - notebookMetadata?: INotebookMetadataLive, - cancelToken?: CancellationToken - ): Promise<{ info: INotebookServerLaunchInfo; changedKernel: boolean }> { - // First we need our launch information so we can start a new session (that's what our notebook is really) - let launchInfo = await this.waitForConnect(); - if (!launchInfo) { - throw this.getDisposedError(); - } - // Create a copy of launch info, cuz we're modifying it here. - // This launch info contains the server connection info (that could be shared across other nbs). - // However the kernel info is different. The kernel info is stored as a property of this, hence create a separate instance for each nb. - launchInfo = { - ...launchInfo - }; - - // Determine the interpreter for our resource. If different, we need a different kernel. - const resourceInterpreter = await this.interpreterService.getActiveInterpreter(resource); - - // Find a kernel that can be used. - // Do this only if kernel information has been provided in the metadata, or the resource's interpreter is different. - let changedKernel = false; - if ( - notebookMetadata?.kernelspec || - notebookMetadata?.id || - resourceInterpreter?.displayName !== launchInfo.kernelConnectionMetadata?.interpreter?.displayName - ) { - const kernelInfo = await (launchInfo.connectionInfo.localLaunch - ? this.kernelSelector.getPreferredKernelForLocalConnection( - resource, - 'jupyter', - sessionManager, - notebookMetadata, - isTestExecution(), - cancelToken - ) - : this.kernelSelector.getPreferredKernelForRemoteConnection( - resource, - sessionManager, - notebookMetadata, - cancelToken - )); - - if (kernelInfo) { - launchInfo.kernelConnectionMetadata = kernelInfo; - - // For the interpreter, make sure to select the one matching the kernel. - launchInfo.kernelConnectionMetadata.interpreter = - launchInfo.kernelConnectionMetadata.interpreter || resourceInterpreter; - changedKernel = true; - } - } - - return { info: launchInfo, changedKernel }; - } - - private parseUri(uri: string | undefined): Resource { - const parsed = uri ? vscode.Uri.parse(uri) : undefined; - return parsed && - parsed.scheme && - parsed.scheme !== Identifiers.InteractiveWindowIdentityScheme && - parsed.scheme === 'vsls' - ? this.finishedApi!.convertSharedUriToLocal(parsed) - : parsed; - } - - private async attemptToForwardPort(api: vsls.LiveShare | null | undefined, port: number): Promise { - if (port !== 0 && api && api.session && api.session.role === vsls.Role.Host) { - this.portToForward = 0; - this.sharedPort = await api.shareServer({ - port, - displayName: localize.DataScience.liveShareHostFormat().format(os.hostname()) - }); - } else { - this.portToForward = port; - } - } - - private onSync(): Promise { - return Promise.resolve(true); - } -} diff --git a/src/client/datascience/jupyter/liveshare/liveShareParticipantMixin.ts b/src/client/datascience/jupyter/liveshare/liveShareParticipantMixin.ts deleted file mode 100644 index 84a1ac362b32..000000000000 --- a/src/client/datascience/jupyter/liveshare/liveShareParticipantMixin.ts +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as vsls from 'vsls/vscode'; - -import { ILiveShareApi } from '../../../common/application/types'; -import '../../../common/extensions'; -import { IAsyncDisposable } from '../../../common/types'; -import { noop } from '../../../common/utils/misc'; -import { ClassType } from '../../../ioc/types'; -import { ILiveShareParticipant } from './types'; -import { waitForGuestService, waitForHostService } from './utils'; - -// tslint:disable:no-any - -export class LiveShareParticipantDefault implements IAsyncDisposable { - constructor(..._rest: any[]) { - noop(); - } - - public async dispose(): Promise { - noop(); - } -} - -export function LiveShareParticipantGuest>(SuperClass: T, serviceName: string) { - return LiveShareParticipantMixin( - SuperClass, - vsls.Role.Guest, - serviceName, - waitForGuestService - ); -} - -export function LiveShareParticipantHost>(SuperClass: T, serviceName: string) { - return LiveShareParticipantMixin( - SuperClass, - vsls.Role.Host, - serviceName, - waitForHostService - ); -} - -/** - * This is called a mixin class in TypeScript. - * Allows us to have different base classes but inherit behavior (workaround for not allowing multiple inheritance). - * Essentially it sticks a temp class in between the base class and the class you're writing. - * Something like this: - * - * class Base { - * doStuff() { - * - * } - * } - * - * function Mixin = (SuperClass) { - * return class extends SuperClass { - * doExtraStuff() { - * super.doStuff(); - * } - * } - * } - * - * function SubClass extends Mixin(Base) { - * doBar() : { - * super.doExtraStuff(); - * } - * } - * - */ -function LiveShareParticipantMixin, S>( - SuperClass: T, - expectedRole: vsls.Role, - serviceName: string, - serviceWaiter: (api: vsls.LiveShare, name: string) => Promise -) { - return class extends SuperClass implements ILiveShareParticipant { - protected finishedApi: vsls.LiveShare | null | undefined; - protected api: Promise; - private actualRole = vsls.Role.None; - private wantedRole = expectedRole; - private servicePromise: Promise | undefined; - private serviceFullName: string | undefined; - - constructor(...rest: any[]) { - super(...rest); - // First argument should be our live share api - if (rest.length > 0) { - const liveShare = rest[0] as ILiveShareApi; - this.api = liveShare.getApi(); - this.api - .then((a) => { - this.finishedApi = a; - this.onSessionChange(a).ignoreErrors(); - }) - .ignoreErrors(); - } else { - this.api = Promise.resolve(null); - } - } - - public get role() { - return this.actualRole; - } - - public async onPeerChange(_ev: vsls.PeersChangeEvent): Promise { - noop(); - } - - public async onAttach(_api: vsls.LiveShare | null): Promise { - noop(); - } - - public waitForServiceName(): Promise { - // Default is just to return the server name - return Promise.resolve(serviceName); - } - - public onDetach(api: vsls.LiveShare | null): Promise { - if (api && this.serviceFullName && api.session && api.session.role === vsls.Role.Host) { - return api.unshareService(this.serviceFullName); - } - return Promise.resolve(); - } - - public async onSessionChange(api: vsls.LiveShare | null): Promise { - this.servicePromise = undefined; - const newRole = api !== null && api.session ? api.session.role : vsls.Role.None; - if (newRole !== this.actualRole) { - this.actualRole = newRole; - if (newRole === this.wantedRole) { - this.onAttach(api).ignoreErrors(); - } else { - this.onDetach(api).ignoreErrors(); - } - } - } - - public async waitForService(): Promise { - if (this.servicePromise) { - return this.servicePromise; - } - const api = await this.api; - if (!api || api.session.role !== this.wantedRole) { - this.servicePromise = Promise.resolve(undefined); - } else { - this.serviceFullName = this.sanitizeServiceName(await this.waitForServiceName()); - this.servicePromise = serviceWaiter(api, this.serviceFullName); - } - - return this.servicePromise; - } - - // Liveshare doesn't support '.' in service names - private sanitizeServiceName(baseServiceName: string): string { - return baseServiceName.replace('.', ''); - } - }; -} diff --git a/src/client/datascience/jupyter/liveshare/responseQueue.ts b/src/client/datascience/jupyter/liveshare/responseQueue.ts deleted file mode 100644 index 86de298eaad4..000000000000 --- a/src/client/datascience/jupyter/liveshare/responseQueue.ts +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { Observable } from 'rxjs/Observable'; -import { Subscriber } from 'rxjs/Subscriber'; -import * as vsls from 'vsls/vscode'; - -import { createDeferred, Deferred } from '../../../common/utils/async'; -import { LiveShareCommands } from '../../constants'; -import { ICell } from '../../types'; -import { IExecuteObservableResponse, IServerResponse } from './types'; - -export class ResponseQueue { - private responseQueue: IServerResponse[] = []; - private waitingQueue: { deferred: Deferred; predicate(r: IServerResponse): boolean }[] = []; - - public waitForObservable(code: string, id: string): Observable { - // Create a wrapper observable around the actual server - return new Observable((subscriber) => { - // Wait for the observable responses to come in - this.waitForResponses(subscriber, code, id).catch((e) => { - subscriber.error(e); - subscriber.complete(); - }); - }); - } - - public push(response: IServerResponse) { - this.responseQueue.push(response); - this.dispatchResponse(response); - } - - public send(service: vsls.SharedService, translator: (r: IServerResponse) => IServerResponse) { - this.responseQueue.forEach((r) => service.notify(LiveShareCommands.serverResponse, translator(r))); - } - - public clear() { - this.responseQueue = []; - } - - private async waitForResponses(subscriber: Subscriber, code: string, id: string): Promise { - let pos = 0; - let cells: ICell[] | undefined = []; - while (cells !== undefined) { - // Find all matches in order - const response = await this.waitForSpecificResponse((r) => { - return r.pos === pos && id === r.id && code === r.code; - }); - if (response.cells) { - subscriber.next(response.cells); - pos += 1; - } - cells = response.cells; - } - subscriber.complete(); - - // Clear responses after we respond to the subscriber. - this.responseQueue = this.responseQueue.filter((r) => { - const er = r as IExecuteObservableResponse; - return er.id !== id; - }); - } - - private waitForSpecificResponse(predicate: (response: T) => boolean): Promise { - // See if we have any responses right now with this type - const index = this.responseQueue.findIndex((r) => predicate(r as T)); - if (index >= 0) { - // Pull off the match - const match = this.responseQueue[index]; - - // Return this single item - return Promise.resolve(match as T); - } else { - // We have to wait for a new input to happen - const waitable = { deferred: createDeferred(), predicate }; - this.waitingQueue.push(waitable); - return waitable.deferred.promise; - } - } - - private dispatchResponse(response: IServerResponse) { - // Look through all of our responses that are queued up and see if they make a - // waiting promise resolve - const matchIndex = this.waitingQueue.findIndex((w) => w.predicate(response)); - if (matchIndex >= 0) { - this.waitingQueue[matchIndex].deferred.resolve(response); - this.waitingQueue.splice(matchIndex, 1); - } - } -} diff --git a/src/client/datascience/jupyter/liveshare/roleBasedFactory.ts b/src/client/datascience/jupyter/liveshare/roleBasedFactory.ts deleted file mode 100644 index c85f6a645018..000000000000 --- a/src/client/datascience/jupyter/liveshare/roleBasedFactory.ts +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as vscode from 'vscode'; -import * as vsls from 'vsls/vscode'; - -import { ILiveShareApi } from '../../../common/application/types'; -import { IAsyncDisposable } from '../../../common/types'; -import { ClassType } from '../../../ioc/types'; -import { ILiveShareHasRole, ILiveShareParticipant } from './types'; - -export interface IRoleBasedObject extends IAsyncDisposable, ILiveShareParticipant {} - -// tslint:disable:no-any -export class RoleBasedFactory> implements ILiveShareHasRole { - private ctorArgs: ConstructorParameters[]; - private firstTime: boolean = true; - private createPromise: Promise | undefined; - private sessionChangedEmitter = new vscode.EventEmitter(); - private _role: vsls.Role = vsls.Role.None; - - constructor( - private liveShare: ILiveShareApi, - private hostCtor: CtorType, - private guestCtor: CtorType, - ...args: ConstructorParameters - ) { - this.ctorArgs = args; - this.createPromise = this.createBasedOnRole(); // We need to start creation immediately or one side may call before we init. - } - - public get sessionChanged(): vscode.Event { - return this.sessionChangedEmitter.event; - } - - public get role(): vsls.Role { - return this._role; - } - - public get(): Promise { - // Make sure only one create happens at a time - if (this.createPromise) { - return this.createPromise; - } - this.createPromise = this.createBasedOnRole(); - return this.createPromise; - } - - private async createBasedOnRole(): Promise { - // Figure out our role to compute the object to create. Default is host. This - // allows for the host object to keep existing if we suddenly start a new session. - // For a guest, starting a new session resets the entire workspace. - const api = await this.liveShare.getApi(); - let ctor: CtorType = this.hostCtor; - let role: vsls.Role = vsls.Role.Host; - - if (api) { - // Create based on role. - if (api.session && api.session.role === vsls.Role.Host) { - ctor = this.hostCtor; - } else if (api.session && api.session.role === vsls.Role.Guest) { - ctor = this.guestCtor; - role = vsls.Role.Guest; - } - } - this._role = role; - - // Create our object - const obj = new ctor(...this.ctorArgs); - - // Rewrite the object's dispose so we can get rid of our own state. - let objDisposed = false; - const oldDispose = obj.dispose.bind(obj); - obj.dispose = () => { - objDisposed = true; - // Make sure we don't destroy the create promise. Otherwise - // dispose will end up causing the creation code to run again. - return oldDispose(); - }; - - // If the session changes, tell the listener - if (api && this.firstTime) { - this.firstTime = false; - api.onDidChangeSession((_a) => { - // Dispose the object if the role changes - const newRole = - api !== null && api.session && api.session.role === vsls.Role.Guest - ? vsls.Role.Guest - : vsls.Role.Host; - if (newRole !== role) { - // Also have to clear the create promise so we - // run the create code again. - this.createPromise = undefined; - - obj.dispose().ignoreErrors(); - } - - // Update the object with respect to the api - if (!objDisposed) { - obj.onSessionChange(api).ignoreErrors(); - } - - // Fire our event indicating old data is no longer valid. - if (newRole !== role) { - this.sessionChangedEmitter.fire(); - } - }); - api.onDidChangePeers((e) => { - if (!objDisposed) { - obj.onPeerChange(e).ignoreErrors(); - } - }); - } - - return obj; - } -} diff --git a/src/client/datascience/jupyter/liveshare/serverCache.ts b/src/client/datascience/jupyter/liveshare/serverCache.ts deleted file mode 100644 index a06f6c828ac0..000000000000 --- a/src/client/datascience/jupyter/liveshare/serverCache.ts +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../../common/extensions'; - -import * as uuid from 'uuid/v4'; -import { CancellationToken, CancellationTokenSource } from 'vscode'; - -import { IWorkspaceService } from '../../../common/application/types'; -import { traceError, traceInfo } from '../../../common/logger'; - -import { IAsyncDisposable, IConfigurationService } from '../../../common/types'; -import { sleep } from '../../../common/utils/async'; -import { IDataScienceFileSystem, INotebookServer, INotebookServerOptions } from '../../types'; -import { calculateWorkingDirectory } from '../../utils'; - -interface IServerData { - options: INotebookServerOptions; - promise: Promise; - cancelSource: CancellationTokenSource; - resolved: boolean; -} - -export class ServerCache implements IAsyncDisposable { - private cache: Map = new Map(); - private emptyKey = uuid(); - private disposed = false; - - constructor( - private configService: IConfigurationService, - private workspace: IWorkspaceService, - private fs: IDataScienceFileSystem - ) {} - - public async getOrCreate( - createFunction: ( - options?: INotebookServerOptions, - cancelToken?: CancellationToken - ) => Promise, - options?: INotebookServerOptions, - cancelToken?: CancellationToken - ): Promise { - const cancelSource = new CancellationTokenSource(); - if (cancelToken) { - cancelToken.onCancellationRequested(() => cancelSource.cancel()); - } - const fixedOptions = await this.generateDefaultOptions(options); - const key = this.generateKey(fixedOptions); - let data: IServerData | undefined; - - // Check to see if we already have a promise for this key - data = this.cache.get(key); - - if (!data) { - // Didn't find one, so start up our promise and cache it - data = { - promise: createFunction(options, cancelSource.token), - options: fixedOptions, - cancelSource, - resolved: false - }; - this.cache.set(key, data); - } - - return data.promise - .then((server: INotebookServer | undefined) => { - if (!server) { - this.cache.delete(key); - return undefined; - } - - // Change the dispose on it so we - // can detach from the server when it goes away. - const oldDispose = server.dispose.bind(server); - server.dispose = () => { - this.cache.delete(key); - return oldDispose(); - }; - - // We've resolved the promise at this point - if (data) { - data.resolved = true; - } - - return server; - }) - .catch((e) => { - this.cache.delete(key); - throw e; - }); - } - - public async get(options?: INotebookServerOptions): Promise { - const fixedOptions = await this.generateDefaultOptions(options); - const key = this.generateKey(fixedOptions); - if (this.cache.has(key)) { - return this.cache.get(key)?.promise; - } - } - - public async dispose(): Promise { - if (!this.disposed) { - this.disposed = true; - const entries = [...this.cache.values()]; - this.cache.clear(); - await Promise.all( - entries.map(async (d) => { - try { - // This should be quick. The server is either already up or will never come back. - const server = await Promise.race([d.promise, sleep(1000)]); - if (typeof server !== 'number') { - // tslint:disable-next-line: no-any - await (server as any).dispose(); - } else { - traceInfo('ServerCache Dispose, no server'); - } - } catch (e) { - traceError(`Dispose error in ServerCache: `, e); - } - }) - ); - } - } - - public async generateDefaultOptions(options?: INotebookServerOptions): Promise { - return { - uri: options ? options.uri : undefined, - skipUsingDefaultConfig: options ? options.skipUsingDefaultConfig : false, // Default for this is false - usingDarkTheme: options ? options.usingDarkTheme : undefined, - purpose: options ? options.purpose : uuid(), - workingDir: - options && options.workingDir - ? options.workingDir - : await calculateWorkingDirectory(this.configService, this.workspace, this.fs), - metadata: options?.metadata, - allowUI: options?.allowUI ? options.allowUI : () => false - }; - } - - private generateKey(options?: INotebookServerOptions): string { - if (!options) { - return this.emptyKey; - } else { - // combine all the values together to make a unique key - const uri = options.uri ? options.uri : ''; - const useFlag = options.skipUsingDefaultConfig ? 'true' : 'false'; - return `${options.purpose}${uri}${useFlag}${options.workingDir}`; - } - } -} diff --git a/src/client/datascience/jupyter/liveshare/types.ts b/src/client/datascience/jupyter/liveshare/types.ts deleted file mode 100644 index 5e662b135171..000000000000 --- a/src/client/datascience/jupyter/liveshare/types.ts +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as vsls from 'vsls/vscode'; - -import { IAsyncDisposable } from '../../../common/types'; -import { ICell } from '../../types'; - -// tslint:disable:max-classes-per-file - -export enum ServerResponseType { - ExecuteObservable, - Exception -} - -export interface IServerResponse { - type: ServerResponseType; - time: number; -} - -export interface IExecuteObservableResponse extends IServerResponse { - pos: number; - code: string; - id: string; // Unique id so guest side can tell what observable it belongs with - cells: ICell[] | undefined; -} - -export interface IExceptionResponse extends IServerResponse { - message: string; -} - -// Map all responses to their properties -export interface IResponseMapping { - [ServerResponseType.ExecuteObservable]: IExecuteObservableResponse; - [ServerResponseType.Exception]: IExceptionResponse; -} - -export interface ICatchupRequest { - since: number; -} - -export interface ILiveShareHasRole { - readonly role: vsls.Role; -} - -export interface ILiveShareParticipant extends IAsyncDisposable, ILiveShareHasRole { - onSessionChange(api: vsls.LiveShare | null): Promise; - onAttach(api: vsls.LiveShare | null): Promise; - onDetach(api: vsls.LiveShare | null): Promise; - onPeerChange(ev: vsls.PeersChangeEvent): Promise; - waitForServiceName(): Promise; -} diff --git a/src/client/datascience/jupyter/liveshare/utils.ts b/src/client/datascience/jupyter/liveshare/utils.ts deleted file mode 100644 index 9c6f39c10a31..000000000000 --- a/src/client/datascience/jupyter/liveshare/utils.ts +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { Disposable, Event } from 'vscode'; -import * as vsls from 'vsls/vscode'; - -import { createDeferred } from '../../../common/utils/async'; - -export async function waitForHostService(api: vsls.LiveShare, name: string): Promise { - const service = await api.shareService(name); - if (service && !service.isServiceAvailable) { - return waitForAvailability(service); - } - return service; -} - -export async function waitForGuestService(api: vsls.LiveShare, name: string): Promise { - const service = await api.getSharedService(name); - if (service && !service.isServiceAvailable) { - return waitForAvailability(service); - } - return service; -} - -interface IChangeWatchable { - readonly onDidChangeIsServiceAvailable: Event; -} - -async function waitForAvailability(service: T): Promise { - const deferred = createDeferred(); - let disposable: Disposable | undefined; - try { - disposable = service.onDidChangeIsServiceAvailable((e) => { - if (e) { - deferred.resolve(service); - } - }); - await deferred.promise; - } finally { - if (disposable) { - disposable.dispose(); - } - } - return service; -} diff --git a/src/client/datascience/jupyter/notebookStarter.ts b/src/client/datascience/jupyter/notebookStarter.ts deleted file mode 100644 index 1eabbd547821..000000000000 --- a/src/client/datascience/jupyter/notebookStarter.ts +++ /dev/null @@ -1,300 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as cp from 'child_process'; -import { inject, injectable, named } from 'inversify'; -import * as os from 'os'; -import * as path from 'path'; -import * as uuid from 'uuid/v4'; -import { CancellationToken, Disposable } from 'vscode'; -import { CancellationError, createPromiseFromCancellation } from '../../common/cancellation'; -import { WrappedError } from '../../common/errors/errorUtils'; -import { traceInfo } from '../../common/logger'; -import { TemporaryDirectory } from '../../common/platform/types'; -import { IDisposable, IOutputChannel } from '../../common/types'; -import * as localize from '../../common/utils/localize'; -import { StopWatch } from '../../common/utils/stopWatch'; -import { IServiceContainer } from '../../ioc/types'; -import { sendTelemetryEvent } from '../../telemetry'; -import { JUPYTER_OUTPUT_CHANNEL, Telemetry } from '../constants'; -import { reportAction } from '../progress/decorator'; -import { ReportableAction } from '../progress/types'; -import { IDataScienceFileSystem, IJupyterConnection, IJupyterSubCommandExecutionService } from '../types'; -import { JupyterConnectionWaiter } from './jupyterConnection'; -import { JupyterInstallError } from './jupyterInstallError'; - -/** - * Responsible for starting a notebook. - * Separate class as theres quite a lot of work involved in starting a notebook. - * - * @export - * @class NotebookStarter - * @implements {Disposable} - */ -@injectable() -export class NotebookStarter implements Disposable { - private readonly disposables: IDisposable[] = []; - constructor( - @inject(IJupyterSubCommandExecutionService) - private readonly jupyterInterpreterService: IJupyterSubCommandExecutionService, - @inject(IDataScienceFileSystem) private readonly fs: IDataScienceFileSystem, - @inject(IServiceContainer) private readonly serviceContainer: IServiceContainer, - @inject(IOutputChannel) @named(JUPYTER_OUTPUT_CHANNEL) private readonly jupyterOutputChannel: IOutputChannel - ) {} - public dispose() { - while (this.disposables.length > 0) { - const disposable = this.disposables.shift(); - try { - if (disposable) { - disposable.dispose(); - } - } catch { - // Nohting - } - } - } - // tslint:disable-next-line: max-func-body-length - @reportAction(ReportableAction.NotebookStart) - public async start( - useDefaultConfig: boolean, - customCommandLine: string[], - workingDirectory: string, - cancelToken?: CancellationToken - ): Promise { - traceInfo('Starting Notebook'); - // Now actually launch it - let exitCode: number | null = 0; - let starter: JupyterConnectionWaiter | undefined; - try { - // Generate a temp dir with a unique GUID, both to match up our started server and to easily clean up after - const tempDirPromise = this.generateTempDir(); - tempDirPromise.then((dir) => this.disposables.push(dir)).ignoreErrors(); - // Before starting the notebook process, make sure we generate a kernel spec - const args = await this.generateArguments( - useDefaultConfig, - customCommandLine, - tempDirPromise, - workingDirectory - ); - - // Make sure we haven't canceled already. - if (cancelToken && cancelToken.isCancellationRequested) { - throw new CancellationError(); - } - - // Then use this to launch our notebook process. - traceInfo('Starting Jupyter Notebook'); - const stopWatch = new StopWatch(); - const [launchResult, tempDir] = await Promise.all([ - this.jupyterInterpreterService.startNotebook(args || [], { - throwOnStdErr: false, - encoding: 'utf8', - token: cancelToken - }), - tempDirPromise - ]); - - // Watch for premature exits - if (launchResult.proc) { - launchResult.proc.on('exit', (c: number | null) => (exitCode = c)); - launchResult.out.subscribe((out) => this.jupyterOutputChannel.append(out.out)); - } - - // Make sure this process gets cleaned up. We might be canceled before the connection finishes. - if (launchResult && cancelToken) { - cancelToken.onCancellationRequested(() => { - launchResult.dispose(); - }); - } - - // Wait for the connection information on this result - traceInfo('Waiting for Jupyter Notebook'); - starter = new JupyterConnectionWaiter( - launchResult, - tempDir.path, - workingDirectory, - this.jupyterInterpreterService.getRunningJupyterServers.bind(this.jupyterInterpreterService), - this.serviceContainer, - cancelToken - ); - // Make sure we haven't canceled already. - if (cancelToken && cancelToken.isCancellationRequested) { - throw new CancellationError(); - } - const connection = await Promise.race([ - starter.waitForConnection(), - createPromiseFromCancellation({ - cancelAction: 'reject', - defaultValue: new CancellationError(), - token: cancelToken - }) - ]); - - if (connection instanceof CancellationError) { - throw connection; - } - - // Fire off telemetry for the process being talkable - sendTelemetryEvent(Telemetry.StartJupyterProcess, stopWatch.elapsedTime); - - return connection; - } catch (err) { - if (err instanceof CancellationError) { - throw err; - } - - // Its possible jupyter isn't installed. Check the errors. - if (!(await this.jupyterInterpreterService.isNotebookSupported())) { - throw new JupyterInstallError( - await this.jupyterInterpreterService.getReasonForJupyterNotebookNotBeingSupported(), - localize.DataScience.pythonInteractiveHelpLink() - ); - } - - // Something else went wrong. See if the local proc died or not. - if (exitCode !== 0) { - throw new Error(localize.DataScience.jupyterServerCrashed().format(exitCode?.toString())); - } else { - throw new WrappedError(localize.DataScience.jupyterNotebookFailure().format(err), err); - } - } finally { - starter?.dispose(); - } - } - - private async generateDefaultArguments( - useDefaultConfig: boolean, - tempDirPromise: Promise, - workingDirectory: string - ): Promise { - // Parallelize as much as possible. - const promisedArgs: Promise[] = []; - promisedArgs.push(Promise.resolve('--no-browser')); - promisedArgs.push(Promise.resolve(this.getNotebookDirArgument(workingDirectory))); - if (useDefaultConfig) { - promisedArgs.push(this.getConfigArgument(tempDirPromise)); - } - // Modify the data rate limit if starting locally. The default prevents large dataframes from being returned. - promisedArgs.push(Promise.resolve('--NotebookApp.iopub_data_rate_limit=10000000000.0')); - - const [args, dockerArgs] = await Promise.all([Promise.all(promisedArgs), this.getDockerArguments()]); - - // Check for the debug environment variable being set. Setting this - // causes Jupyter to output a lot more information about what it's doing - // under the covers and can be used to investigate problems with Jupyter. - const debugArgs = process.env && process.env.VSCODE_PYTHON_DEBUG_JUPYTER ? ['--debug'] : []; - - // Use this temp file and config file to generate a list of args for our command - return [...args, ...dockerArgs, ...debugArgs]; - } - - private async generateCustomArguments(customCommandLine: string[]): Promise { - // We still have a bunch of args we have to pass - const requiredArgs = ['--no-browser', '--NotebookApp.iopub_data_rate_limit=10000000000.0']; - - return [...requiredArgs, ...customCommandLine]; - } - - private async generateArguments( - useDefaultConfig: boolean, - customCommandLine: string[], - tempDirPromise: Promise, - workingDirectory: string - ): Promise { - if (!customCommandLine || customCommandLine.length === 0) { - return this.generateDefaultArguments(useDefaultConfig, tempDirPromise, workingDirectory); - } - return this.generateCustomArguments(customCommandLine); - } - - /** - * Gets the `--notebook-dir` argument. - * - * @private - * @param {Promise} tempDirectory - * @returns {Promise} - * @memberof NotebookStarter - */ - private getNotebookDirArgument(workingDirectory: string): string { - // Escape the result so Jupyter can actually read it. - return `--notebook-dir="${workingDirectory.replace(/\\/g, '\\\\')}"`; - } - - /** - * Gets the `--config` argument. - * - * @private - * @param {Promise} tempDirectory - * @returns {Promise} - * @memberof NotebookStarter - */ - private async getConfigArgument(tempDirectory: Promise): Promise { - const tempDir = await tempDirectory; - // In the temp dir, create an empty config python file. This is the same - // as starting jupyter with all of the defaults. - const configFile = path.join(tempDir.path, 'jupyter_notebook_config.py'); - await this.fs.writeLocalFile(configFile, ''); - traceInfo(`Generating custom default config at ${configFile}`); - - // Create extra args based on if we have a config or not - return `--config=${configFile}`; - } - - /** - * Adds the `--ip` and `--allow-root` arguments when in docker. - * - * @private - * @param {Promise} tempDirectory - * @returns {Promise} - * @memberof NotebookStarter - */ - private async getDockerArguments(): Promise { - const args: string[] = []; - // Check for a docker situation. - try { - const cgroup = await this.fs.readLocalFile('/proc/self/cgroup').catch(() => ''); - if (!cgroup.includes('docker') && !cgroup.includes('kubepods')) { - return args; - } - // We definitely need an ip address. - args.push('--ip'); - args.push('127.0.0.1'); - - // Now see if we need --allow-root. - return new Promise((resolve) => { - cp.exec('id', { encoding: 'utf-8' }, (_, stdout: string | Buffer) => { - if (stdout && stdout.toString().includes('(root)')) { - args.push('--allow-root'); - } - resolve(args); - }); - }); - } catch { - return args; - } - } - private async generateTempDir(): Promise { - const resultDir = path.join(os.tmpdir(), uuid()); - await this.fs.createLocalDirectory(resultDir); - - return { - path: resultDir, - dispose: async () => { - // Try ten times. Process may still be up and running. - // We don't want to do async as async dispose means it may never finish and then we don't - // delete - let count = 0; - while (count < 10) { - try { - await this.fs.deleteLocalDirectory(resultDir); - count = 10; - } catch { - count += 1; - } - } - } - }; - } -} diff --git a/src/client/datascience/jupyter/oldJupyterVariables.ts b/src/client/datascience/jupyter/oldJupyterVariables.ts deleted file mode 100644 index 7c1d77de7135..000000000000 --- a/src/client/datascience/jupyter/oldJupyterVariables.ts +++ /dev/null @@ -1,450 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import type { nbformat } from '@jupyterlab/coreutils'; -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import stripAnsi from 'strip-ansi'; -import * as uuid from 'uuid/v4'; - -import { Event, EventEmitter, Uri } from 'vscode'; -import { PYTHON_LANGUAGE } from '../../common/constants'; -import { traceError } from '../../common/logger'; - -import { IConfigurationService } from '../../common/types'; -import * as localize from '../../common/utils/localize'; -import { EXTENSION_ROOT_DIR } from '../../constants'; -import { Identifiers, Settings } from '../constants'; -import { - ICell, - IDataScienceFileSystem, - IJupyterVariable, - IJupyterVariables, - IJupyterVariablesRequest, - IJupyterVariablesResponse, - INotebook -} from '../types'; -import { JupyterDataRateLimitError } from './jupyterDataRateLimitError'; -import { getKernelConnectionLanguage, isPythonKernelConnection } from './kernels/helpers'; - -// tslint:disable-next-line: no-var-requires no-require-imports - -// Regexes for parsing data from Python kernel. Not sure yet if other -// kernels will add the ansi encoding. -const TypeRegex = /.*?\[.*?;31mType:.*?\[0m\s+(\w+)/; -const ValueRegex = /.*?\[.*?;31mValue:.*?\[0m\s+(.*)/; -const StringFormRegex = /.*?\[.*?;31mString form:.*?\[0m\s*?([\s\S]+?)\n(.*\[.*;31m?)/; -const DocStringRegex = /.*?\[.*?;31mDocstring:.*?\[0m\s+(.*)/; -const CountRegex = /.*?\[.*?;31mLength:.*?\[0m\s+(.*)/; -const ShapeRegex = /^\s+\[(\d+) rows x (\d+) columns\]/m; - -const DataViewableTypes: Set = new Set(['DataFrame', 'list', 'dict', 'ndarray', 'Series']); - -interface INotebookState { - currentExecutionCount: number; - variables: IJupyterVariable[]; -} - -@injectable() -export class OldJupyterVariables implements IJupyterVariables { - private fetchDataFrameInfoScript?: string; - private fetchDataFrameRowsScript?: string; - private fetchVariableShapeScript?: string; - private filesLoaded: boolean = false; - private languageToQueryMap = new Map(); - private notebookState = new Map(); - private refreshEventEmitter = new EventEmitter(); - - constructor( - @inject(IDataScienceFileSystem) private fs: IDataScienceFileSystem, - @inject(IConfigurationService) private configService: IConfigurationService - ) {} - - public get refreshRequired(): Event { - return this.refreshEventEmitter.event; - } - - // IJupyterVariables implementation - public async getVariables( - notebook: INotebook, - request: IJupyterVariablesRequest - ): Promise { - // Run the language appropriate variable fetch - return this.getVariablesBasedOnKernel(notebook, request); - } - - public async getMatchingVariable(_notebook: INotebook, _name: string): Promise { - // Not supported with old method. - return undefined; - } - - public async getDataFrameInfo(targetVariable: IJupyterVariable, notebook: INotebook): Promise { - // Run the get dataframe info script - return this.runScript( - notebook, - targetVariable, - targetVariable, - () => this.fetchDataFrameInfoScript, - [{ key: '_VSCode_JupyterValuesColumn', value: localize.DataScience.valuesColumn() }] - ); - } - - public async getDataFrameRows( - targetVariable: IJupyterVariable, - notebook: INotebook, - start: number, - end: number - ): Promise<{}> { - // Run the get dataframe rows script - return this.runScript<{}>(notebook, targetVariable, {}, () => this.fetchDataFrameRowsScript, [ - { key: '_VSCode_JupyterValuesColumn', value: localize.DataScience.valuesColumn() }, - { key: '_VSCode_JupyterStartRow', value: start.toString() }, - { key: '_VSCode_JupyterEndRow', value: end.toString() } - ]); - } - - // Private methods - // Load our python files for fetching variables - private async loadVariableFiles(): Promise { - let file = path.join( - EXTENSION_ROOT_DIR, - 'pythonFiles', - 'vscode_datascience_helpers', - 'getJupyterVariableDataFrameInfo.py' - ); - this.fetchDataFrameInfoScript = await this.fs.readLocalFile(file); - - file = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'vscode_datascience_helpers', 'getJupyterVariableShape.py'); - this.fetchVariableShapeScript = await this.fs.readLocalFile(file); - - file = path.join( - EXTENSION_ROOT_DIR, - 'pythonFiles', - 'vscode_datascience_helpers', - 'getJupyterVariableDataFrameRows.py' - ); - this.fetchDataFrameRowsScript = await this.fs.readLocalFile(file); - - this.filesLoaded = true; - } - - private async runScript( - notebook: INotebook, - targetVariable: IJupyterVariable | undefined, - defaultValue: T, - scriptBaseTextFetcher: () => string | undefined, - extraReplacements: { key: string; value: string }[] = [] - ): Promise { - if (!this.filesLoaded) { - await this.loadVariableFiles(); - } - - const scriptBaseText = scriptBaseTextFetcher(); - if (!notebook || !scriptBaseText) { - // No active server just return the unchanged target variable - return defaultValue; - } - - // Prep our targetVariable to send over. Remove the 'value' as it's not necessary for getting df info and can have invalid data in it - const pruned = { ...targetVariable, value: '' }; - const variableString = JSON.stringify(pruned); - - // Setup a regex - const regexPattern = - extraReplacements.length === 0 - ? '_VSCode_JupyterTestValue' - : ['_VSCode_JupyterTestValue', ...extraReplacements.map((v) => v.key)].join('|'); - const replaceRegex = new RegExp(regexPattern, 'g'); - - // Replace the test value with our current value. Replace start and end as well - const scriptText = scriptBaseText.replace(replaceRegex, (match: string) => { - if (match === '_VSCode_JupyterTestValue') { - return variableString; - } else { - const index = extraReplacements.findIndex((v) => v.key === match); - if (index >= 0) { - return extraReplacements[index].value; - } - } - - return match; - }); - - // Execute this on the notebook passed in. - const results = await notebook.execute(scriptText, Identifiers.EmptyFileName, 0, uuid(), undefined, true); - - // Results should be the updated variable. - return this.deserializeJupyterResult(results); - } - - private extractJupyterResultText(cells: ICell[]): string { - // Verify that we have the correct cell type and outputs - if (cells.length > 0 && cells[0].data) { - const codeCell = cells[0].data as nbformat.ICodeCell; - if (codeCell.outputs.length > 0) { - const codeCellOutput = codeCell.outputs[0] as nbformat.IOutput; - if ( - codeCellOutput && - codeCellOutput.output_type === 'stream' && - codeCellOutput.name === 'stderr' && - codeCellOutput.hasOwnProperty('text') - ) { - const resultString = codeCellOutput.text as string; - // See if this the IOPUB data rate limit problem - if (resultString.includes('iopub_data_rate_limit')) { - throw new JupyterDataRateLimitError(); - } else { - const error = localize.DataScience.jupyterGetVariablesExecutionError().format(resultString); - traceError(error); - throw new Error(error); - } - } - if (codeCellOutput && codeCellOutput.output_type === 'execute_result') { - const data = codeCellOutput.data; - if (data && data.hasOwnProperty('text/plain')) { - // tslint:disable-next-line:no-any - return (data as any)['text/plain']; - } - } - if ( - codeCellOutput && - codeCellOutput.output_type === 'stream' && - codeCellOutput.hasOwnProperty('text') - ) { - return codeCellOutput.text as string; - } - if ( - codeCellOutput && - codeCellOutput.output_type === 'error' && - codeCellOutput.hasOwnProperty('traceback') - ) { - const traceback: string[] = codeCellOutput.traceback as string[]; - const stripped = traceback.map(stripAnsi).join('\r\n'); - const error = localize.DataScience.jupyterGetVariablesExecutionError().format(stripped); - traceError(error); - throw new Error(error); - } - } - } - - throw new Error(localize.DataScience.jupyterGetVariablesBadResults()); - } - - // Pull our text result out of the Jupyter cell - private deserializeJupyterResult(cells: ICell[]): T { - const text = this.extractJupyterResultText(cells); - return JSON.parse(text) as T; - } - - private getParser(notebook: INotebook) { - // Figure out kernel language - const language = getKernelConnectionLanguage(notebook?.getKernelConnection()) || PYTHON_LANGUAGE; - - // We may have cached this information - let result = this.languageToQueryMap.get(language); - if (!result) { - let query = this.configService - .getSettings(notebook.resource) - .datascience.variableQueries.find((v) => v.language === language); - if (!query && language === PYTHON_LANGUAGE) { - query = Settings.DefaultVariableQuery; - } - - // Use the query to generate our regex - if (query) { - result = { - query: query.query, - parser: new RegExp(query.parseExpr, 'g') - }; - this.languageToQueryMap.set(language, result); - } - } - - return result; - } - - private getAllMatches(regex: RegExp, text: string): string[] { - const result: string[] = []; - let m: RegExpExecArray | null = null; - // tslint:disable-next-line: no-conditional-assignment - while ((m = regex.exec(text)) !== null) { - if (m.index === regex.lastIndex) { - regex.lastIndex += 1; - } - if (m.length > 1) { - result.push(m[1]); - } - } - // Rest after searching - regex.lastIndex = -1; - return result; - } - - private async getVariablesBasedOnKernel( - notebook: INotebook, - request: IJupyterVariablesRequest - ): Promise { - // See if we already have the name list - let list = this.notebookState.get(notebook.identity); - if (!list || list.currentExecutionCount !== request.executionCount) { - // Refetch the list of names from the notebook. They might have changed. - list = { - currentExecutionCount: request.executionCount, - variables: (await this.getVariableNamesFromKernel(notebook)).map((n) => { - return { - name: n, - value: undefined, - supportsDataExplorer: false, - type: '', - size: 0, - shape: '', - count: 0, - truncated: true - }; - }) - }; - } - - const exclusionList = this.configService.getSettings(notebook.resource).datascience.variableExplorerExclude - ? this.configService.getSettings().datascience.variableExplorerExclude?.split(';') - : []; - - const result: IJupyterVariablesResponse = { - executionCount: request.executionCount, - pageStartIndex: -1, - pageResponse: [], - totalCount: 0, - refreshCount: request.refreshCount - }; - - // Use the list of names to fetch the page of data - if (list) { - const startPos = request.startIndex ? request.startIndex : 0; - const chunkSize = request.pageSize ? request.pageSize : 100; - result.pageStartIndex = startPos; - - // Do one at a time. All at once doesn't work as they all have to wait for each other anyway - for (let i = startPos; i < startPos + chunkSize && i < list.variables.length; ) { - const fullVariable = list.variables[i].value - ? list.variables[i] - : await this.getVariableValueFromKernel(list.variables[i], notebook); - - // See if this is excluded or not. - if (exclusionList && exclusionList.indexOf(fullVariable.type) >= 0) { - // Not part of our actual list. Remove from the real list too - list.variables.splice(i, 1); - } else { - list.variables[i] = fullVariable; - result.pageResponse.push(fullVariable); - i += 1; - } - } - - // Save in our cache - this.notebookState.set(notebook.identity, list); - - // Update total count (exclusions will change this as types are computed) - result.totalCount = list.variables.length; - } - - return result; - } - - private async getVariableNamesFromKernel(notebook: INotebook): Promise { - // Get our query and parser - const query = this.getParser(notebook); - - // Now execute the query - if (notebook && query) { - const cells = await notebook.execute(query.query, Identifiers.EmptyFileName, 0, uuid(), undefined, true); - const text = this.extractJupyterResultText(cells); - - // Apply the expression to it - const matches = this.getAllMatches(query.parser, text); - - // Turn each match into a value - if (matches) { - return matches; - } - } - - return []; - } - - private async getVariableValueFromKernel( - targetVariable: IJupyterVariable, - notebook: INotebook - ): Promise { - let result = { ...targetVariable }; - if (notebook) { - const output = await notebook.inspect(targetVariable.name); - - // Should be a text/plain inside of it (at least IPython does this) - if (output && output.hasOwnProperty('text/plain')) { - // tslint:disable-next-line: no-any - const text = (output as any)['text/plain'].toString(); - - // Parse into bits - const type = TypeRegex.exec(text); - const value = ValueRegex.exec(text); - const stringForm = StringFormRegex.exec(text); - const docString = DocStringRegex.exec(text); - const count = CountRegex.exec(text); - const shape = ShapeRegex.exec(text); - if (type) { - result.type = type[1]; - } - if (value) { - result.value = value[1]; - } else if (stringForm) { - result.value = stringForm[1]; - } else if (docString) { - result.value = docString[1]; - } else { - result.value = ''; - } - if (count) { - result.count = parseInt(count[1], 10); - } - if (shape) { - result.shape = `(${shape[1]}, ${shape[2]})`; - } - } - - // Otherwise look for the appropriate entries - if (output.type) { - result.type = output.type.toString(); - } - if (output.value) { - result.value = output.value.toString(); - } - - // Determine if supports viewing based on type - if (DataViewableTypes.has(result.type)) { - result.supportsDataExplorer = true; - } - } - - // For a python kernel, we might be able to get a better shape. It seems the 'inspect' request doesn't always return it. - // Do this only when necessary as this is a LOT slower than an inspect request. Like 4 or 5 times as slow - if ( - result.type && - result.count && - !result.shape && - isPythonKernelConnection(notebook.getKernelConnection()) && - result.supportsDataExplorer && - result.type !== 'list' // List count is good enough - ) { - const computedShape = await this.runScript( - notebook, - result, - result, - () => this.fetchVariableShapeScript - ); - // Only want shape and count from the request. Other things might have been destroyed - result = { ...result, shape: computedShape.shape, count: computedShape.count }; - } - - return result; - } -} diff --git a/src/client/datascience/jupyter/serverPreload.ts b/src/client/datascience/jupyter/serverPreload.ts deleted file mode 100644 index 481af8e0bfee..000000000000 --- a/src/client/datascience/jupyter/serverPreload.ts +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { inject, injectable } from 'inversify'; -import { IExtensionSingleActivationService } from '../../activation/types'; -import { traceError, traceInfo } from '../../common/logger'; -import { IConfigurationService } from '../../common/types'; -import { - IInteractiveWindow, - IInteractiveWindowProvider, - INotebookAndInteractiveWindowUsageTracker, - INotebookEditorProvider, - INotebookProvider -} from '../types'; - -@injectable() -export class ServerPreload implements IExtensionSingleActivationService { - constructor( - @inject(INotebookAndInteractiveWindowUsageTracker) - private readonly tracker: INotebookAndInteractiveWindowUsageTracker, - @inject(INotebookEditorProvider) private notebookEditorProvider: INotebookEditorProvider, - @inject(IInteractiveWindowProvider) private interactiveProvider: IInteractiveWindowProvider, - @inject(IConfigurationService) private configService: IConfigurationService, - @inject(INotebookProvider) private notebookProvider: INotebookProvider - ) { - this.notebookEditorProvider.onDidOpenNotebookEditor(this.onDidOpenNotebook.bind(this)); - this.interactiveProvider.onDidChangeActiveInteractiveWindow(this.onDidOpenOrCloseInteractive.bind(this)); - } - public activate(): Promise { - // This is the list of things that should cause us to start a local server - // 1) Notebook is opened - // 2) Notebook was opened in the past 7 days - // 3) Interactive window was opened in the past 7 days - // 4) Interactive window is opened - // And the user has specified local server in their settings. - this.checkDateForServerStart(); - - // Don't hold up activation though - return Promise.resolve(); - } - - private checkDateForServerStart() { - if ( - this.shouldAutoStartStartServer(this.tracker.lastInteractiveWindowOpened) || - this.shouldAutoStartStartServer(this.tracker.lastNotebookOpened) - ) { - this.createServerIfNecessary().ignoreErrors(); - } - } - private shouldAutoStartStartServer(lastTime?: Date) { - if (!lastTime) { - return false; - } - const currentTime = new Date(); - const diff = currentTime.getTime() - lastTime.getTime(); - const diffInDays = Math.floor(diff / (24 * 3600 * 1000)); - return diffInDays <= 7; - } - - private async createServerIfNecessary() { - try { - traceInfo(`Attempting to start a server because of preload conditions ...`); - - // Check if we are already connected - let providerConnection = await this.notebookProvider.connect({ getOnly: true, disableUI: true }); - - // If it didn't start, attempt for local and if allowed. - if (!providerConnection && !this.configService.getSettings(undefined).datascience.disableJupyterAutoStart) { - // Local case, try creating one - providerConnection = await this.notebookProvider.connect({ - getOnly: false, - disableUI: true, - localOnly: true - }); - } - } catch (exc) { - traceError(`Error starting server in serverPreload: `, exc); - } - } - - private onDidOpenNotebook() { - // Automatically start a server whenever we open a notebook - this.createServerIfNecessary().ignoreErrors(); - } - - private onDidOpenOrCloseInteractive(interactive: IInteractiveWindow | undefined) { - if (interactive) { - this.createServerIfNecessary().ignoreErrors(); - } - } -} diff --git a/src/client/datascience/jupyter/serverSelector.ts b/src/client/datascience/jupyter/serverSelector.ts deleted file mode 100644 index 201c0c6eee6f..000000000000 --- a/src/client/datascience/jupyter/serverSelector.ts +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable, named } from 'inversify'; -import { isNil } from 'lodash'; -import { ConfigurationTarget, Memento, QuickPickItem, Uri } from 'vscode'; -import { IClipboard, ICommandManager } from '../../common/application/types'; -import { GLOBAL_MEMENTO, IConfigurationService, IMemento } from '../../common/types'; -import { DataScience } from '../../common/utils/localize'; -import { noop } from '../../common/utils/misc'; -import { - IMultiStepInput, - IMultiStepInputFactory, - InputFlowAction, - InputStep, - IQuickPickParameters -} from '../../common/utils/multiStepInput'; -import { captureTelemetry } from '../../telemetry'; -import { getSavedUriList } from '../common'; -import { Identifiers, Settings, Telemetry } from '../constants'; -import { IJupyterUriProvider, IJupyterUriProviderRegistration, JupyterServerUriHandle } from '../types'; - -const defaultUri = 'https://hostname:8080/?token=849d61a414abafab97bc4aab1f3547755ddc232c2b8cb7fe'; - -interface ISelectUriQuickPickItem extends QuickPickItem { - newChoice: boolean; - provider?: IJupyterUriProvider; - url?: string; -} - -@injectable() -export class JupyterServerSelector { - private readonly localLabel = `$(zap) ${DataScience.jupyterSelectURILocalLabel()}`; - private readonly newLabel = `$(server) ${DataScience.jupyterSelectURINewLabel()}`; - private readonly remoteLabel = `$(server) ${DataScience.jupyterSelectURIRemoteLabel()}`; - constructor( - @inject(IMemento) @named(GLOBAL_MEMENTO) private globalState: Memento, - @inject(IClipboard) private readonly clipboard: IClipboard, - @inject(IMultiStepInputFactory) private readonly multiStepFactory: IMultiStepInputFactory, - @inject(IConfigurationService) private configuration: IConfigurationService, - @inject(ICommandManager) private cmdManager: ICommandManager, - @inject(IJupyterUriProviderRegistration) - private extraUriProviders: IJupyterUriProviderRegistration - ) {} - - @captureTelemetry(Telemetry.SelectJupyterURI) - public selectJupyterURI(allowLocal: boolean): Promise { - const multiStep = this.multiStepFactory.create<{}>(); - return multiStep.run(this.startSelectingURI.bind(this, allowLocal), {}); - } - - private async startSelectingURI( - allowLocal: boolean, - input: IMultiStepInput<{}>, - _state: {} - ): Promise | void> { - // First step, show a quick pick to choose either the remote or the local. - // newChoice element will be set if the user picked 'enter a new server' - const item = await input.showQuickPick>({ - placeholder: DataScience.jupyterSelectURIQuickPickPlaceholder(), - items: await this.getUriPickList(allowLocal), - title: allowLocal - ? DataScience.jupyterSelectURIQuickPickTitle() - : DataScience.jupyterSelectURIQuickPickTitleRemoteOnly() - }); - if (item.label === this.localLabel) { - await this.setJupyterURIToLocal(); - } else if (!item.newChoice && !item.provider) { - await this.setJupyterURIToRemote(!isNil(item.url) ? item.url : item.label); - } else if (!item.provider) { - return this.selectRemoteURI.bind(this); - } else { - return this.selectProviderURI.bind(this, item.provider, item); - } - } - - private async selectProviderURI( - provider: IJupyterUriProvider, - item: ISelectUriQuickPickItem, - _input: IMultiStepInput<{}>, - _state: {} - ): Promise | void> { - const result = await provider.handleQuickPick(item, true); - if (result === 'back') { - throw InputFlowAction.back; - } - if (result) { - await this.handleProviderQuickPick(provider.id, result); - } - } - private async handleProviderQuickPick(id: string, result: JupyterServerUriHandle | undefined) { - if (result) { - const uri = this.generateUriFromRemoteProvider(id, result); - await this.setJupyterURIToRemote(uri); - } - } - - private generateUriFromRemoteProvider(id: string, result: JupyterServerUriHandle) { - // tslint:disable-next-line: no-http-string - return `${Identifiers.REMOTE_URI}?${Identifiers.REMOTE_URI_ID_PARAM}=${id}&${ - Identifiers.REMOTE_URI_HANDLE_PARAM - }=${encodeURI(result)}`; - } - - private async selectRemoteURI(input: IMultiStepInput<{}>, _state: {}): Promise | void> { - let initialValue = defaultUri; - try { - const text = await this.clipboard.readText().catch(() => ''); - const parsedUri = Uri.parse(text.trim(), true); - // Only display http/https uris. - initialValue = text && parsedUri && parsedUri.scheme.toLowerCase().startsWith('http') ? text : defaultUri; - } catch { - // We can ignore errors. - } - // Ask the user to enter a URI to connect to. - const uri = await input.showInputBox({ - title: DataScience.jupyterSelectURIPrompt(), - value: initialValue || defaultUri, - validate: this.validateSelectJupyterURI, - prompt: '' - }); - - if (uri) { - await this.setJupyterURIToRemote(uri); - } - } - - @captureTelemetry(Telemetry.SetJupyterURIToLocal) - private async setJupyterURIToLocal(): Promise { - const previousValue = this.configuration.getSettings(undefined).datascience.jupyterServerURI; - await this.configuration.updateSetting( - 'dataScience.jupyterServerURI', - Settings.JupyterServerLocalLaunch, - undefined, - ConfigurationTarget.Workspace - ); - - // Reload if there's a change - if (previousValue !== Settings.JupyterServerLocalLaunch) { - this.cmdManager - .executeCommand('python.reloadVSCode', DataScience.reloadAfterChangingJupyterServerConnection()) - .then(noop, noop); - } - } - - @captureTelemetry(Telemetry.SetJupyterURIToUserSpecified) - private async setJupyterURIToRemote(userURI: string): Promise { - const previousValue = this.configuration.getSettings(undefined).datascience.jupyterServerURI; - await this.configuration.updateSetting( - 'dataScience.jupyterServerURI', - userURI, - undefined, - ConfigurationTarget.Workspace - ); - - // Reload if there's a change - if (previousValue !== userURI) { - this.cmdManager - .executeCommand('python.reloadVSCode', DataScience.reloadAfterChangingJupyterServerConnection()) - .then(noop, noop); - } - } - private validateSelectJupyterURI = async (inputText: string): Promise => { - try { - // tslint:disable-next-line:no-unused-expression - new URL(inputText); - - // Double check http - if (!inputText.toLowerCase().includes('http')) { - throw new Error('Has to be http'); - } - } catch { - return DataScience.jupyterSelectURIInvalidURI(); - } - }; - - private async getUriPickList(allowLocal: boolean): Promise { - // Ask our providers to stick on items - let providerItems: ISelectUriQuickPickItem[] = []; - const providers = await this.extraUriProviders.getProviders(); - if (providers) { - providers.forEach((p) => { - const newproviderItems = p.getQuickPickEntryItems().map((i) => { - return { ...i, newChoice: false, provider: p }; - }); - providerItems = providerItems.concat(newproviderItems); - }); - } - - // Always have 'local' and 'add new' - let items: ISelectUriQuickPickItem[] = []; - if (allowLocal) { - items.push({ label: this.localLabel, detail: DataScience.jupyterSelectURILocalDetail(), newChoice: false }); - items = items.concat(providerItems); - items.push({ label: this.newLabel, detail: DataScience.jupyterSelectURINewDetail(), newChoice: true }); - } else { - items = items.concat(providerItems); - items.push({ - label: this.remoteLabel, - detail: DataScience.jupyterSelectURIRemoteDetail(), - newChoice: true - }); - } - - // Get our list of recent server connections and display that as well - const savedURIList = getSavedUriList(this.globalState); - savedURIList.forEach((uriItem) => { - if (uriItem.uri) { - const uriDate = new Date(uriItem.time); - items.push({ - label: !isNil(uriItem.displayName) ? uriItem.displayName : uriItem.uri, - detail: DataScience.jupyterSelectURIMRUDetail().format(uriDate.toLocaleString()), - newChoice: false, - url: uriItem.uri - }); - } - }); - - return items; - } -} diff --git a/src/client/datascience/jupyter/variableScriptLoader.ts b/src/client/datascience/jupyter/variableScriptLoader.ts deleted file mode 100644 index 8e75093f820f..000000000000 --- a/src/client/datascience/jupyter/variableScriptLoader.ts +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as path from 'path'; - -import { EXTENSION_ROOT_DIR } from '../../constants'; -import { IDataScienceFileSystem, IJupyterVariable } from '../types'; - -export class VariableScriptLoader { - private fetchVariableShapeScript?: string; - private filesLoaded: boolean = false; - - constructor(private fs: IDataScienceFileSystem) {} - - public readShapeScript(targetVariable: IJupyterVariable): Promise { - return this.readScript(targetVariable, () => this.fetchVariableShapeScript); - } - - private async readScript( - targetVariable: IJupyterVariable | undefined, - scriptBaseTextFetcher: () => string | undefined, - extraReplacements: { key: string; value: string }[] = [] - ): Promise { - if (!this.filesLoaded) { - await this.loadVariableFiles(); - } - - const scriptBaseText = scriptBaseTextFetcher(); - - // Prep our targetVariable to send over. Remove the 'value' as it's not necessary for getting df info and can have invalid data in it - const pruned = { ...targetVariable, value: '' }; - const variableString = JSON.stringify(pruned); - - // Setup a regex - const regexPattern = - extraReplacements.length === 0 - ? '_VSCode_JupyterTestValue' - : ['_VSCode_JupyterTestValue', ...extraReplacements.map((v) => v.key)].join('|'); - const replaceRegex = new RegExp(regexPattern, 'g'); - - // Replace the test value with our current value. Replace start and end as well - return scriptBaseText - ? scriptBaseText.replace(replaceRegex, (match: string) => { - if (match === '_VSCode_JupyterTestValue') { - return variableString; - } else { - const index = extraReplacements.findIndex((v) => v.key === match); - if (index >= 0) { - return extraReplacements[index].value; - } - } - - return match; - }) - : undefined; - } - - // Load our python files for fetching variables - private async loadVariableFiles(): Promise { - const file = path.join( - EXTENSION_ROOT_DIR, - 'pythonFiles', - 'vscode_datascience_helpers', - 'getJupyterVariableShape.py' - ); - this.fetchVariableShapeScript = await this.fs.readLocalFile(file); - this.filesLoaded = true; - } -} diff --git a/src/client/datascience/jupyterDebugService.ts b/src/client/datascience/jupyterDebugService.ts deleted file mode 100644 index ca774a2c041f..000000000000 --- a/src/client/datascience/jupyterDebugService.ts +++ /dev/null @@ -1,415 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import { inject, injectable } from 'inversify'; -import * as net from 'net'; -import * as path from 'path'; -import * as uuid from 'uuid/v4'; -import { - Breakpoint, - BreakpointsChangeEvent, - DebugAdapterTracker, - DebugAdapterTrackerFactory, - DebugConfiguration, - DebugConfigurationProvider, - DebugConsole, - DebugSession, - DebugSessionCustomEvent, - Disposable, - Event, - EventEmitter, - SourceBreakpoint, - WorkspaceFolder -} from 'vscode'; -import { DebugProtocol } from 'vscode-debugprotocol'; -import { traceError, traceInfo } from '../common/logger'; -import { IDisposable, IDisposableRegistry } from '../common/types'; -import { createDeferred } from '../common/utils/async'; -import { noop } from '../common/utils/misc'; -import { EXTENSION_ROOT_DIR } from '../constants'; -import { DebugAdapterDescriptorFactory } from '../debugger/extension/adapter/factory'; -import { IProtocolParser } from '../debugger/extension/types'; -import { IJupyterDebugService } from './types'; - -// tslint:disable:no-any - -// For debugging set these environment variables -// PYDEVD_DEBUG=True -// DEBUGPY_LOG_DIR=

-// PYDEVD_DEBUG_FILE= -class JupyterDebugSession implements DebugSession { - private _name = 'JupyterDebugSession'; - constructor( - private _id: string, - private _configuration: DebugConfiguration, - private customRequestHandler: (command: string, args?: any) => Thenable - ) { - noop(); - } - public get id(): string { - return this._id; - } - public get type(): string { - return 'python'; - } - public get name(): string { - return this._name; - } - public get workspaceFolder(): WorkspaceFolder | undefined { - return undefined; - } - public get configuration(): DebugConfiguration { - return this._configuration; - } - public customRequest(command: string, args?: any): Thenable { - return this.customRequestHandler(command, args); - } -} - -//tslint:disable:trailing-comma no-any no-multiline-string -/** - * IJupyterDebugService that talks directly to the debugger. Supports both run by line and - * regular debugging (regular is used in tests). - */ -@injectable() -export class JupyterDebugService implements IJupyterDebugService, IDisposable { - private socket: net.Socket | undefined; - private session: DebugSession | undefined; - private sequence: number = 1; - private breakpointEmitter: EventEmitter = new EventEmitter(); - private debugAdapterTrackerFactories: DebugAdapterTrackerFactory[] = []; - private debugAdapterTrackers: DebugAdapterTracker[] = []; - private sessionChangedEvent: EventEmitter = new EventEmitter(); - private sessionStartedEvent: EventEmitter = new EventEmitter(); - private sessionTerminatedEvent: EventEmitter = new EventEmitter(); - private sessionCustomEvent: EventEmitter = new EventEmitter(); - private breakpointsChangedEvent: EventEmitter = new EventEmitter(); - private _breakpoints: Breakpoint[] = []; - private _stoppedThreadId: number | undefined; - private _topFrameId: number | undefined; - constructor( - @inject(IProtocolParser) private protocolParser: IProtocolParser, - @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry - ) { - disposableRegistry.push(this); - } - - public dispose(): void { - if (this.socket) { - this.socket.end(); - this.socket = undefined; - } - } - - public get activeDebugSession(): DebugSession | undefined { - return this.session; - } - public get activeDebugConsole(): DebugConsole { - return { - append(_value: string): void { - noop(); - }, - appendLine(_value: string): void { - noop(); - } - }; - } - public get breakpoints(): Breakpoint[] { - return this._breakpoints; - } - public get onDidChangeActiveDebugSession(): Event { - return this.sessionChangedEvent.event; - } - public get onDidStartDebugSession(): Event { - return this.sessionStartedEvent.event; - } - public get onDidReceiveDebugSessionCustomEvent(): Event { - return this.sessionCustomEvent.event; - } - public get onDidTerminateDebugSession(): Event { - return this.sessionTerminatedEvent.event; - } - public get onDidChangeBreakpoints(): Event { - return this.breakpointsChangedEvent.event; - } - public registerDebugConfigurationProvider(_debugType: string, _provider: DebugConfigurationProvider): Disposable { - return { - dispose: () => { - noop(); - } - }; - } - - public registerDebugAdapterDescriptorFactory( - _debugType: string, - _factory: DebugAdapterDescriptorFactory - ): Disposable { - return { - dispose: () => { - noop(); - } - }; - } - public registerDebugAdapterTrackerFactory(_debugType: string, provider: DebugAdapterTrackerFactory): Disposable { - this.debugAdapterTrackerFactories.push(provider); - return { - dispose: () => { - this.debugAdapterTrackerFactories = this.debugAdapterTrackerFactories.filter((f) => f !== provider); - } - }; - } - - public startRunByLine(config: DebugConfiguration): Thenable { - // This is the same as normal debugging. Just a convenient entry point - // in case we need to make it different. - return this.startDebugging(undefined, config); - } - - public startDebugging( - _folder: WorkspaceFolder | undefined, - nameOrConfiguration: string | DebugConfiguration, - _parentSession?: DebugSession | undefined - ): Thenable { - // Should have a port number. We'll assume it's local - const config = nameOrConfiguration as DebugConfiguration; // NOSONAR - if (config.port) { - this.session = new JupyterDebugSession(uuid(), config, this.sendCustomRequest.bind(this)); - this.sessionChangedEvent.fire(this.session); - - // Create our debug adapter trackers at session start - this.debugAdapterTrackers = this.debugAdapterTrackerFactories.map( - (f) => f.createDebugAdapterTracker(this.session!) as DebugAdapterTracker // NOSONAR - ); - - this.socket = net.createConnection(config.port); - this.protocolParser.connect(this.socket); - this.protocolParser.on('event_stopped', this.onBreakpoint.bind(this)); - this.protocolParser.on('event_output', this.onOutput.bind(this)); - this.protocolParser.on('event_terminated', this.sendToTrackers.bind(this)); - this.socket.on('error', this.onError.bind(this)); - this.socket.on('close', this.onClose.bind(this)); - return this.sendStartSequence(config, this.session.id).then(() => true); - } - return Promise.resolve(true); - } - public addBreakpoints(breakpoints: Breakpoint[]): void { - this._breakpoints = this._breakpoints.concat(breakpoints); - } - public removeBreakpoints(_breakpoints: Breakpoint[]): void { - noop(); - } - public get onBreakpointHit(): Event { - return this.breakpointEmitter.event; - } - - public async continue(): Promise { - await this.sendMessage('continue', { threadId: 0 }); - this.sendToTrackers({ type: 'event', event: 'continue' }); - } - - public async step(): Promise { - await this.sendMessage('stepIn', { threadId: this._stoppedThreadId ? this._stoppedThreadId : 1 }); - this.sendToTrackers({ type: 'event', event: 'stepIn' }); - } - - public async getStack(): Promise { - const deferred = createDeferred(); - this.protocolParser.once('response_stackTrace', (args: any) => { - this.sendToTrackers(args); - const response = args as DebugProtocol.StackTraceResponse; - const frames = response.body.stackFrames ? response.body.stackFrames : []; - deferred.resolve(frames); - this._topFrameId = frames[0]?.id; - }); - await this.emitMessage('stackTrace', { - threadId: this._stoppedThreadId ? this._stoppedThreadId : 1, - startFrame: 0, - levels: 1 - }); - return deferred.promise; - } - - public async requestVariables(): Promise { - // Need a stack trace so we have a topmost frame id - await this.getStack(); - const deferred = createDeferred(); - let variablesReference = 0; - this.protocolParser.once('response_scopes', (args: any) => { - this.sendToTrackers(args); - // Get locals variables reference - const response = args as DebugProtocol.ScopesResponse; - if (response) { - variablesReference = response.body.scopes[0].variablesReference; - } - this.emitMessage('variables', { - threadId: this._stoppedThreadId ? this._stoppedThreadId : 1, - variablesReference - }).ignoreErrors(); - }); - this.protocolParser.once('response_variables', (args: any) => { - this.sendToTrackers(args); - deferred.resolve(); - }); - await this.emitMessage('scopes', { - frameId: this._topFrameId ? this._topFrameId : 1 - }); - return deferred.promise; - } - - public stop(): void { - this.onClose(); - } - - private sendToTrackers(args: any) { - this.debugAdapterTrackers.forEach((d) => d.onDidSendMessage!(args)); - } - - private sendCustomRequest(command: string, args?: any): Promise { - return this.sendMessage(command, args); - } - - private async sendStartSequence(config: DebugConfiguration, sessionId: string): Promise { - traceInfo('Sending debugger initialize...'); - await this.sendInitialize(); - if (this._breakpoints.length > 0) { - traceInfo('Sending breakpoints'); - await this.sendBreakpoints(); - } - traceInfo('Sending debugger attach...'); - const attachPromise = this.sendAttach(config, sessionId); - traceInfo('Sending configuration done'); - await this.sendConfigurationDone(); - traceInfo('Session started.'); - return attachPromise.then(() => { - this.sessionStartedEvent.fire(this.session!); - }); - } - - private sendBreakpoints(): Promise { - // Only supporting a single file now - const sbs = this._breakpoints.map((b) => b as SourceBreakpoint); // NOSONAR - const file = sbs[0].location.uri.fsPath; - return this.sendMessage('setBreakpoints', { - source: { - name: path.basename(file), - path: file - }, - lines: sbs.map((sb) => sb.location.range.start.line), - breakpoints: sbs.map((sb) => { - return { line: sb.location.range.start.line }; - }), - sourceModified: true - }); - } - - private sendAttach(config: DebugConfiguration, sessionId: string): Promise { - // Send our attach request - return this.sendMessage('attach', { - debugOptions: ['RedirectOutput', 'FixFilePathCase', 'WindowsClient', 'ShowReturnValue'], - workspaceFolder: EXTENSION_ROOT_DIR, - __sessionId: sessionId, - ...config - }); - } - - private sendConfigurationDone(): Promise { - return this.sendMessage('configurationDone'); - } - - private sendInitialize(): Promise { - // Send our initialize request. (Got this by dumping debugAdapter output during real run. Set logToFile to true to generate) - return this.sendMessage('initialize', { - clientID: 'vscode', - clientName: 'Visual Studio Code', - adapterID: 'python', - pathFormat: 'path', - linesStartAt1: true, - columnsStartAt1: true, - supportsVariableType: true, - supportsVariablePaging: true, - supportsRunInTerminalRequest: true, - locale: 'en-us' - }); - } - - private sendDisconnect(): Promise { - return this.sendMessage('disconnect', {}); - } - - private sendMessage(command: string, args?: any): Promise { - const response = createDeferred(); - const disposable = this.sessionTerminatedEvent.event(() => { - response.resolve({ body: {} }); - }); - const sequenceNumber = this.sequence; - this.protocolParser.on(`response_${command}`, (resp: any) => { - if (resp.request_seq === sequenceNumber) { - this.sendToTrackers(resp); - traceInfo(`Received response from debugger: ${JSON.stringify(args)}`); - disposable.dispose(); - response.resolve(resp.body); - } - }); - this.socket?.on('error', (err) => response.reject(err)); // NOSONAR - this.emitMessage(command, args).catch((exc) => { - traceError(`Exception attempting to emit ${command} to debugger: `, exc); - }); - return response.promise; - } - - private emitMessage(command: string, args?: any): Promise { - return new Promise((resolve, reject) => { - try { - if (this.socket) { - const obj = { - command, - arguments: args, - type: 'request', - seq: this.sequence - }; - this.sequence += 1; - const objString = JSON.stringify(obj); - traceInfo(`Sending request to debugger: ${objString}`); - const message = `Content-Length: ${objString.length}\r\n\r\n${objString}`; - this.socket.write(message, (_a: any) => { - this.sendToTrackers(obj); - resolve(); - }); - } - } catch (e) { - reject(e); - } - }); - } - - private onBreakpoint(args: DebugProtocol.StoppedEvent): void { - // Save the current thread id. We use this in our stack trace request - this._stoppedThreadId = args.body.threadId; - this.sendToTrackers(args); - - // Indicate we stopped at a breakpoint - this.breakpointEmitter.fire(); - } - - private onOutput(args: any): void { - this.sendToTrackers(args); - traceInfo(JSON.stringify(args)); - } - - private onError(args: any): void { - this.sendToTrackers(args); - traceInfo(JSON.stringify(args)); - } - - private onClose(): void { - if (this.socket) { - this.sessionTerminatedEvent.fire(this.activeDebugSession!); - this.session = undefined; - this.sessionChangedEvent.fire(undefined); - this.debugAdapterTrackers.forEach((d) => (d.onExit ? d.onExit(0, undefined) : noop())); - this.debugAdapterTrackers = []; - this.sendDisconnect().ignoreErrors(); - this.socket.destroy(); - this.socket = undefined; - } - } -} diff --git a/src/client/datascience/jupyterUriProviderRegistration.ts b/src/client/datascience/jupyterUriProviderRegistration.ts deleted file mode 100644 index 9e01b206dece..000000000000 --- a/src/client/datascience/jupyterUriProviderRegistration.ts +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import { inject, injectable } from 'inversify'; -import * as path from 'path'; - -import { IExtensions } from '../common/types'; -import * as localize from '../common/utils/localize'; -import { EXTENSION_ROOT_DIR } from '../constants'; -import { JupyterUriProviderWrapper } from './jupyterUriProviderWrapper'; -import { - IDataScienceFileSystem, - IJupyterServerUri, - IJupyterUriProvider, - IJupyterUriProviderRegistration, - JupyterServerUriHandle -} from './types'; - -@injectable() -export class JupyterUriProviderRegistration implements IJupyterUriProviderRegistration { - private loadedOtherExtensionsPromise: Promise | undefined; - private providers = new Map>(); - - constructor( - @inject(IExtensions) private readonly extensions: IExtensions, - @inject(IDataScienceFileSystem) private readonly fs: IDataScienceFileSystem - ) {} - - public async getProviders(): Promise> { - await this.checkOtherExtensions(); - - // Other extensions should have registered in their activate callback - return Promise.all([...this.providers.values()]); - } - - public registerProvider(provider: IJupyterUriProvider) { - if (!this.providers.has(provider.id)) { - this.providers.set(provider.id, this.createProvider(provider)); - } else { - throw new Error(`IJupyterUriProvider already exists with id ${provider.id}`); - } - } - - public async getJupyterServerUri(id: string, handle: JupyterServerUriHandle): Promise { - await this.checkOtherExtensions(); - - const providerPromise = this.providers.get(id); - if (providerPromise) { - const provider = await providerPromise; - return provider.getServerUri(handle); - } - throw new Error(localize.DataScience.unknownServerUri()); - } - - private checkOtherExtensions(): Promise { - if (!this.loadedOtherExtensionsPromise) { - this.loadedOtherExtensionsPromise = this.loadOtherExtensions(); - } - return this.loadedOtherExtensionsPromise; - } - - private async loadOtherExtensions(): Promise { - const list = this.extensions.all - .filter((e) => e.packageJSON?.contributes?.pythonRemoteServerProvider) - .map((e) => (e.isActive ? Promise.resolve() : e.activate())); - await Promise.all(list); - } - - private async createProvider(provider: IJupyterUriProvider): Promise { - const packageName = await this.determineExtensionFromCallstack(); - return new JupyterUriProviderWrapper(provider, packageName); - } - - private async determineExtensionFromCallstack(): Promise { - const stack = new Error().stack; - if (stack) { - const root = EXTENSION_ROOT_DIR.toLowerCase(); - const frames = stack.split('\n').map((f) => { - const result = /\((.*)\)/.exec(f); - if (result) { - return result[1].toLowerCase(); - } - }); - for (const frame of frames) { - if (frame && !frame.startsWith(root)) { - // This file is from a different extension. Try to find its package.json - let dirName = path.dirname(frame); - let last = frame; - while (dirName && dirName.length < last.length) { - const possiblePackageJson = path.join(dirName, 'package.json'); - if (await this.fs.localFileExists(possiblePackageJson)) { - const text = await this.fs.readLocalFile(possiblePackageJson); - try { - const json = JSON.parse(text); - return `${json.publisher}.${json.name}`; - } catch { - // If parse fails, then not the extension - } - } - last = dirName; - dirName = path.dirname(dirName); - } - } - } - } - return localize.DataScience.unknownPackage(); - } -} diff --git a/src/client/datascience/jupyterUriProviderWrapper.ts b/src/client/datascience/jupyterUriProviderWrapper.ts deleted file mode 100644 index 7c1019bb62ac..000000000000 --- a/src/client/datascience/jupyterUriProviderWrapper.ts +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import * as vscode from 'vscode'; -import * as localize from '../common/utils/localize'; -import { IJupyterServerUri, IJupyterUriProvider, JupyterServerUriHandle } from './types'; - -/** - * This class wraps an IJupyterUriProvider provided by another extension. It allows us to show - * extra data on the other extension's UI. - */ -export class JupyterUriProviderWrapper implements IJupyterUriProvider { - constructor(private readonly provider: IJupyterUriProvider, private packageName: string) {} - public get id() { - return this.provider.id; - } - public getQuickPickEntryItems(): vscode.QuickPickItem[] { - return this.provider.getQuickPickEntryItems().map((q) => { - return { - ...q, - // Add the package name onto the description - description: localize.DataScience.uriProviderDescriptionFormat().format( - q.description || '', - this.packageName - ), - original: q - }; - }); - } - public handleQuickPick( - item: vscode.QuickPickItem, - back: boolean - ): Promise { - // tslint:disable-next-line: no-any - if ((item as any).original) { - // tslint:disable-next-line: no-any - return this.provider.handleQuickPick((item as any).original, back); - } - return this.provider.handleQuickPick(item, back); - } - - public getServerUri(handle: JupyterServerUriHandle): Promise { - return this.provider.getServerUri(handle); - } -} diff --git a/src/client/datascience/kernel-launcher/helpers.ts b/src/client/datascience/kernel-launcher/helpers.ts deleted file mode 100644 index ddab7b2d2d25..000000000000 --- a/src/client/datascience/kernel-launcher/helpers.ts +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import { IInterpreterService } from '../../interpreter/contracts'; -import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { IJupyterKernelSpec } from '../types'; - -// For a given IJupyterKernelSpec return the interpreter associated with it or error -export async function getKernelInterpreter( - kernelSpec: IJupyterKernelSpec, - interpreterService: IInterpreterService -): Promise { - // First part of argument is always the executable. - const args = [...kernelSpec.argv]; - const pythonPath = kernelSpec.metadata?.interpreter?.path || args[0]; - - // Use that to find the matching interpeter. - const matchingInterpreter = await interpreterService.getInterpreterDetails(pythonPath); - - if (!matchingInterpreter) { - throw new Error(`Failed to find interpreter for kernelspec ${kernelSpec.display_name}`); - } - - return matchingInterpreter; -} diff --git a/src/client/datascience/kernel-launcher/kernelDaemon.ts b/src/client/datascience/kernel-launcher/kernelDaemon.ts deleted file mode 100644 index e4e5594f620c..000000000000 --- a/src/client/datascience/kernel-launcher/kernelDaemon.ts +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { ChildProcess } from 'child_process'; -import { Subject } from 'rxjs/Subject'; -import { MessageConnection, NotificationType, RequestType, RequestType0 } from 'vscode-jsonrpc'; -import { IPlatformService } from '../../common/platform/types'; -import { BasePythonDaemon, ExecResponse } from '../../common/process/baseDaemon'; -import { - IPythonExecutionService, - ObservableExecutionResult, - Output, - SpawnOptions, - StdErrError -} from '../../common/process/types'; -import { IPythonKernelDaemon, PythonKernelDiedError } from './types'; - -export class PythonKernelDaemon extends BasePythonDaemon implements IPythonKernelDaemon { - private started?: boolean; - private killed?: boolean; - private preWarmed?: boolean; - private outputHooked?: boolean; - private readonly subject = new Subject>(); - constructor( - pythonExecutionService: IPythonExecutionService, - platformService: IPlatformService, - pythonPath: string, - proc: ChildProcess, - connection: MessageConnection - ) { - super(pythonExecutionService, platformService, pythonPath, proc, connection); - } - public async interrupt() { - const request = new RequestType0('interrupt_kernel'); - await this.sendRequestWithoutArgs(request); - } - public async kill() { - if (this.killed) { - return; - } - this.killed = true; - const request = new RequestType0('kill_kernel'); - await this.sendRequestWithoutArgs(request); - } - public async preWarm() { - if (this.started) { - return; - } - this.preWarmed = true; - this.monitorOutput(); - const request = new RequestType0('prewarm_kernel'); - - await this.sendRequestWithoutArgs(request); - } - - public async start( - moduleName: string, - args: string[], - options: SpawnOptions - ): Promise> { - if (this.killed) { - throw new Error('Restarting a dead daemon'); - } - if (options.throwOnStdErr) { - throw new Error("'throwOnStdErr' not supported in spawnOptions for KernelDaemon.start"); - } - if (options.mergeStdOutErr) { - throw new Error("'mergeStdOutErr' not supported in spawnOptions for KernelDaemon.start"); - } - if (this.started) { - throw new Error('Kernel has already been started in daemon'); - } - this.started = true; - this.monitorOutput(); - - if (this.preWarmed) { - const request = new RequestType<{ args: string[] }, ExecResponse, void, void>('start_prewarmed_kernel'); - await this.sendRequest(request, { args: [moduleName].concat(args) }); - } else { - // No need of the output here, we'll tap into the output coming from daemon `this.outputObservale`. - // This is required because execModule will never end. - // We cannot use `execModuleObservable` as that only works where the daemon is busy seeerving on request and we wait for it to finish. - // In this case we're never going to wait for the module to run to end. Cuz when we run `pytohn -m ipykernel`, it never ends. - // It only ends when the kernel dies, meaning the kernel process is dead. - // What we need is to be able to run the module and keep getting a stream of stdout/stderr. - // & also be able to execute other python code. I.e. we need a daemon. - // For this we run the `ipykernel` code in a separate thread. - // This is why when we run `execModule` in the Kernel daemon, it finishes (comes back) quickly. - // However in reality it is running in the background. - // See `m_exec_module_observable` in `kernel_launcher_daemon.py`. - await this.execModule(moduleName, args, options); - } - - return { - proc: this.proc, - dispose: () => this.dispose(), - out: this.subject - }; - } - private monitorOutput() { - if (this.outputHooked) { - return; - } - this.outputHooked = true; - // Message from daemon when kernel dies. - const KernelDiedNotification = new NotificationType<{ exit_code: string; reason?: string }, void>( - 'kernel_died' - ); - this.connection.onNotification(KernelDiedNotification, (output) => { - this.subject.error( - new PythonKernelDiedError({ exitCode: parseInt(output.exit_code, 10), reason: output.reason }) - ); - }); - - // All output messages from daemon from here on are considered to be coming from the kernel. - // This is because the kernel is a long running process and that will be the only code in the daemon - // sptting stuff into stdout/stderr. - this.outputObservale.subscribe( - (out) => { - if (out.source === 'stderr') { - this.subject.error(new StdErrError(out.out)); - } else { - this.subject.next(out); - } - }, - this.subject.error.bind(this.subject), - this.subject.complete.bind(this.subject) - ); - - // If the daemon dies, then kernel is also dead. - this.closed.catch((error) => this.subject.error(new PythonKernelDiedError({ error }))); - } -} diff --git a/src/client/datascience/kernel-launcher/kernelDaemonPool.ts b/src/client/datascience/kernel-launcher/kernelDaemonPool.ts deleted file mode 100644 index de1c40b6c9b8..000000000000 --- a/src/client/datascience/kernel-launcher/kernelDaemonPool.ts +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IWorkspaceService } from '../../common/application/types'; -import { traceError } from '../../common/logger'; - -import { IPythonExecutionFactory, IPythonExecutionService } from '../../common/process/types'; -import { IDisposable, Resource } from '../../common/types'; -import { noop } from '../../common/utils/misc'; -import { IEnvironmentVariablesProvider } from '../../common/variables/types'; -import { IInterpreterService } from '../../interpreter/contracts'; -import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { KernelLauncherDaemonModule } from '../constants'; -import { IDataScienceFileSystem, IJupyterKernelSpec, IKernelDependencyService } from '../types'; -import { PythonKernelDaemon } from './kernelDaemon'; -import { IPythonKernelDaemon } from './types'; - -type IKernelDaemonInfo = { - key: string; - workspaceResource: Resource; - workspaceFolderIdentifier: string; - interpreterPath: string; - daemon: Promise; -}; - -@injectable() -export class KernelDaemonPool implements IDisposable { - private readonly disposables: IDisposable[] = []; - private daemonPool: IKernelDaemonInfo[] = []; - private initialized?: boolean; - - public get daemons() { - return this.daemonPool.length; - } - - constructor( - @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, - @inject(IEnvironmentVariablesProvider) private readonly envVars: IEnvironmentVariablesProvider, - @inject(IDataScienceFileSystem) private readonly fs: IDataScienceFileSystem, - @inject(IInterpreterService) private readonly interrpeterService: IInterpreterService, - @inject(IPythonExecutionFactory) private readonly pythonExecutionFactory: IPythonExecutionFactory, - @inject(IKernelDependencyService) private readonly kernelDependencyService: IKernelDependencyService - ) {} - public async preWarmKernelDaemons() { - if (this.initialized) { - return; - } - this.initialized = true; - this.envVars.onDidEnvironmentVariablesChange(this.onDidEnvironmentVariablesChange.bind(this)); - this.interrpeterService.onDidChangeInterpreter(this.onDidChangeInterpreter.bind(this)); - const promises: Promise[] = []; - if (this.workspaceService.hasWorkspaceFolders) { - promises.push( - ...(this.workspaceService.workspaceFolders || []).map((item) => this.preWarmKernelDaemon(item.uri)) - ); - } else { - promises.push(this.preWarmKernelDaemon(undefined)); - } - await Promise.all(promises); - } - public dispose() { - this.disposables.forEach((item) => item.dispose()); - } - public async get( - resource: Resource, - kernelSpec: IJupyterKernelSpec, - interpreter?: PythonEnvironment - ): Promise { - const pythonPath = interpreter?.path || kernelSpec.argv[0]; - // If we have environment variables in the kernel.json, then its not we support. - // Cuz there's no way to know before hand what kernelspec can be used, hence no way to know what envs are required. - if (kernelSpec.env && Object.keys(kernelSpec.env).length > 0) { - return this.createDaemon(resource, pythonPath); - } - - const key = this.getDaemonKey(resource, pythonPath); - const index = this.daemonPool.findIndex((item) => item.key === key); - try { - if (index >= 0) { - const daemon = this.daemonPool[index].daemon; - this.daemonPool.splice(index, 1); - return daemon; - } - return this.createDaemon(resource, pythonPath); - } finally { - // If we removed a daemon from the pool, rehydrate it. - if (index >= 0) { - this.preWarmKernelDaemon(resource).ignoreErrors(); - } - } - } - - private getDaemonKey(resource: Resource, pythonPath: string): string { - return `${this.workspaceService.getWorkspaceFolderIdentifier(resource)}#${pythonPath}`; - } - private createDaemon(resource: Resource, pythonPath: string) { - const daemon = this.pythonExecutionFactory.createDaemon({ - daemonModule: KernelLauncherDaemonModule, - pythonPath, - daemonClass: PythonKernelDaemon, - dedicated: true, - resource - }); - daemon - .then((d) => { - if ('dispose' in d) { - this.disposables.push(d); - } - }) - .catch(noop); - return daemon; - } - private async onDidEnvironmentVariablesChange(affectedResoruce: Resource) { - const workspaceFolderIdentifier = this.workspaceService.getWorkspaceFolderIdentifier(affectedResoruce); - this.daemonPool = this.daemonPool.filter((item) => { - if (item.workspaceFolderIdentifier === workspaceFolderIdentifier) { - item.daemon - .then((d) => { - if ('dispose' in d) { - d.dispose(); - } - }) - .catch(noop); - return false; - } - return true; - }); - } - private async preWarmKernelDaemon(resource: Resource) { - const interpreter = await this.interrpeterService.getActiveInterpreter(resource); - if (!interpreter || !(await this.kernelDependencyService.areDependenciesInstalled(interpreter))) { - return; - } - const key = this.getDaemonKey(resource, interpreter.path); - // If we have already created one in the interim, then get out. - if (this.daemonPool.some((item) => item.key === key)) { - return; - } - - const workspaceFolderIdentifier = this.workspaceService.getWorkspaceFolderIdentifier(resource); - const daemon = this.createDaemon(resource, interpreter.path); - // Once a daemon is created ensure we pre-warm it (will load ipykernel and start the kernker process waiting to start the actual kernel code). - // I.e. we'll start python process thats the kernel, but will not start the kernel module (`python -m ipykernel`). - daemon - .then((d) => { - // Prewarm if we support prewarming - if ('preWarm' in d) { - d.preWarm().catch(traceError.bind(`Failed to prewarm kernel daemon ${interpreter.path}`)); - } - }) - .catch((e) => { - traceError(`Failed to load deamon: ${e}`); - }); - this.daemonPool.push({ - daemon, - interpreterPath: interpreter.path, - key, - workspaceFolderIdentifier, - workspaceResource: resource - }); - } - private async onDidChangeInterpreter() { - // Get a list of all unique workspaces - const uniqueResourcesWithKernels = new Map(); - this.daemonPool.forEach((item) => { - uniqueResourcesWithKernels.set(item.workspaceFolderIdentifier, item); - }); - - // Key = workspace identifier, and value is interpreter path. - const currentInterpreterInEachWorksapce = new Map(); - // Get interpreters for each workspace. - await Promise.all( - Array.from(uniqueResourcesWithKernels.entries()).map(async (item) => { - const resource = item[1].workspaceResource; - try { - const interpreter = await this.interrpeterService.getActiveInterpreter(resource); - if (!interpreter) { - return; - } - currentInterpreterInEachWorksapce.set(item[1].key, interpreter.path); - } catch (ex) { - traceError(`Failed to get interpreter information for workspace ${resource?.fsPath}`); - } - }) - ); - - // Go through all interpreters for each workspace. - // If we have a daemon with an interpreter thats not the same as the current interpreter for that workspace - // then kill that daemon, as its no longer valid. - this.daemonPool = this.daemonPool.filter((item) => { - const interpreterForWorkspace = currentInterpreterInEachWorksapce.get(item.key); - if (!interpreterForWorkspace || !this.fs.areLocalPathsSame(interpreterForWorkspace, item.interpreterPath)) { - item.daemon - .then((d) => { - if ('dispose' in d) { - d.dispose(); - } - }) - .catch(noop); - return false; - } - - return true; - }); - } -} diff --git a/src/client/datascience/kernel-launcher/kernelDaemonPreWarmer.ts b/src/client/datascience/kernel-launcher/kernelDaemonPreWarmer.ts deleted file mode 100644 index 1e19f400a4ff..000000000000 --- a/src/client/datascience/kernel-launcher/kernelDaemonPreWarmer.ts +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IExtensionActivationService } from '../../activation/types'; -import '../../common/extensions'; -import { IConfigurationService, IDisposableRegistry, Resource } from '../../common/types'; -import { swallowExceptions } from '../../common/utils/decorators'; -import { - IInteractiveWindowProvider, - INotebookAndInteractiveWindowUsageTracker, - INotebookEditorProvider, - IRawNotebookSupportedService -} from '../types'; -import { KernelDaemonPool } from './kernelDaemonPool'; - -@injectable() -export class KernelDaemonPreWarmer implements IExtensionActivationService { - constructor( - @inject(INotebookEditorProvider) private readonly notebookEditorProvider: INotebookEditorProvider, - @inject(IInteractiveWindowProvider) private interactiveProvider: IInteractiveWindowProvider, - @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry, - @inject(INotebookAndInteractiveWindowUsageTracker) - private readonly usageTracker: INotebookAndInteractiveWindowUsageTracker, - @inject(KernelDaemonPool) private readonly kernelDaemonPool: KernelDaemonPool, - @inject(IRawNotebookSupportedService) private readonly rawNotebookSupported: IRawNotebookSupportedService, - @inject(IConfigurationService) private readonly configService: IConfigurationService - ) {} - public async activate(_resource: Resource): Promise { - // Check to see if raw notebooks are supported - // If not, don't bother with prewarming - // Also respect the disable autostart setting to not do any prewarming for the user - if ( - !(await this.rawNotebookSupported.supported()) || - this.configService.getSettings().datascience.disableJupyterAutoStart - ) { - return; - } - - this.disposables.push(this.notebookEditorProvider.onDidOpenNotebookEditor(this.preWarmKernelDaemonPool, this)); - this.disposables.push( - this.interactiveProvider.onDidChangeActiveInteractiveWindow(this.preWarmKernelDaemonPool, this) - ); - if (this.notebookEditorProvider.editors.length > 0 || this.interactiveProvider.windows.length > 0) { - await this.preWarmKernelDaemonPool(); - } - await this.preWarmDaemonPoolIfNecesary(); - } - private async preWarmDaemonPoolIfNecesary() { - if ( - this.shouldPreWarmDaemonPool(this.usageTracker.lastInteractiveWindowOpened) || - this.shouldPreWarmDaemonPool(this.usageTracker.lastNotebookOpened) - ) { - await this.preWarmKernelDaemonPool(); - } - } - @swallowExceptions('PreWarmKernelDaemon') - private async preWarmKernelDaemonPool() { - await this.kernelDaemonPool.preWarmKernelDaemons(); - } - private shouldPreWarmDaemonPool(lastTime?: Date) { - if (!lastTime) { - return false; - } - const currentTime = new Date(); - const diff = currentTime.getTime() - lastTime.getTime(); - const diffInDays = Math.floor(diff / (24 * 3600 * 1000)); - return diffInDays <= 7; - } -} diff --git a/src/client/datascience/kernel-launcher/kernelFinder.ts b/src/client/datascience/kernel-launcher/kernelFinder.ts deleted file mode 100644 index 69e34934a0a2..000000000000 --- a/src/client/datascience/kernel-launcher/kernelFinder.ts +++ /dev/null @@ -1,393 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import type { nbformat } from '@jupyterlab/coreutils'; -import { inject, injectable, named } from 'inversify'; -import * as path from 'path'; -import { CancellationToken } from 'vscode'; -import { IWorkspaceService } from '../../common/application/types'; -import { traceError, traceInfo } from '../../common/logger'; -import { IPlatformService } from '../../common/platform/types'; -import { IPythonExecutionFactory } from '../../common/process/types'; -import { IExtensionContext, IPathUtils, Resource } from '../../common/types'; -import { IEnvironmentVariablesProvider } from '../../common/variables/types'; -import { IInterpreterLocatorService, IInterpreterService, KNOWN_PATH_SERVICE } from '../../interpreter/contracts'; -import { captureTelemetry } from '../../telemetry'; -import { getRealPath } from '../common'; -import { Telemetry } from '../constants'; -import { JupyterKernelSpec } from '../jupyter/kernels/jupyterKernelSpec'; -import { IDataScienceFileSystem, IJupyterKernelSpec } from '../types'; -import { IKernelFinder } from './types'; -// tslint:disable-next-line:no-require-imports no-var-requires -const flatten = require('lodash/flatten') as typeof import('lodash/flatten'); - -const winJupyterPath = path.join('AppData', 'Roaming', 'jupyter', 'kernels'); -const linuxJupyterPath = path.join('.local', 'share', 'jupyter', 'kernels'); -const macJupyterPath = path.join('Library', 'Jupyter', 'kernels'); -const baseKernelPath = path.join('share', 'jupyter', 'kernels'); - -const cacheFile = 'kernelSpecPathCache.json'; - -// This class searches for a kernel that matches the given kernel name. -// First it searches on a global persistent state, then on the installed python interpreters, -// and finally on the default locations that jupyter installs kernels on. -// If a kernel name is not given, it returns a default IJupyterKernelSpec created from the current interpreter. -// Before returning the IJupyterKernelSpec it makes sure that ipykernel is installed into the kernel spec interpreter -@injectable() -export class KernelFinder implements IKernelFinder { - private cache?: string[]; - private cacheDirty = false; - - // Store our results when listing all possible kernelspecs for a resource - private workspaceToKernels = new Map>(); - - // Store any json file that we have loaded from disk before - private pathToKernelSpec = new Map>(); - - constructor( - @inject(IInterpreterService) private interpreterService: IInterpreterService, - @inject(IInterpreterLocatorService) - @named(KNOWN_PATH_SERVICE) - private readonly interpreterLocator: IInterpreterLocatorService, - @inject(IPlatformService) private platformService: IPlatformService, - @inject(IDataScienceFileSystem) private fs: IDataScienceFileSystem, - @inject(IPathUtils) private readonly pathUtils: IPathUtils, - @inject(IExtensionContext) private readonly context: IExtensionContext, - @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, - @inject(IPythonExecutionFactory) private readonly exeFactory: IPythonExecutionFactory, - @inject(IEnvironmentVariablesProvider) private readonly envVarsProvider: IEnvironmentVariablesProvider - ) {} - @captureTelemetry(Telemetry.KernelFinderPerf) - public async findKernelSpec( - resource: Resource, - kernelSpecMetadata?: nbformat.IKernelspecMetadata - ): Promise { - await this.readCache(); - let foundKernel: IJupyterKernelSpec | undefined; - - const kernelName = kernelSpecMetadata?.name; - - if (kernelSpecMetadata && kernelName) { - let kernelSpec = await this.searchCache(kernelName); - - if (kernelSpec) { - return kernelSpec; - } - - // Check in active interpreter first - kernelSpec = await this.getKernelSpecFromActiveInterpreter(kernelName, resource); - - if (kernelSpec) { - this.writeCache().ignoreErrors(); - return kernelSpec; - } - - const diskSearch = this.findDiskPath(kernelName); - const interpreterSearch = this.getInterpreterPaths(resource).then((interpreterPaths) => { - return this.findInterpreterPath(interpreterPaths, kernelName); - }); - - let result = await Promise.race([diskSearch, interpreterSearch]); - if (!result) { - const both = await Promise.all([diskSearch, interpreterSearch]); - result = both[0] ? both[0] : both[1]; - } - - foundKernel = result; - } - - this.writeCache().ignoreErrors(); - - return foundKernel; - } - - // Search all our local file system locations for installed kernel specs and return them - public async listKernelSpecs(resource: Resource): Promise { - if (!resource) { - // We need a resource to search for related kernel specs - return []; - } - - // Get an id for the workspace folder, if we don't have one, use the fsPath of the resource - const workspaceFolderId = this.workspaceService.getWorkspaceFolderIdentifier(resource, resource.fsPath); - - // If we have not already searched for this resource, then generate the search - if (!this.workspaceToKernels.has(workspaceFolderId)) { - this.workspaceToKernels.set(workspaceFolderId, this.findResourceKernelSpecs(resource)); - } - - this.writeCache().ignoreErrors(); - - // ! as the has and set above verify that we have a return here - return this.workspaceToKernels.get(workspaceFolderId)!; - } - - private async findResourceKernelSpecs(resource: Resource): Promise { - const results: IJupyterKernelSpec[] = []; - - // Find all the possible places to look for this resource - const paths = await this.findAllResourcePossibleKernelPaths(resource); - - const searchResults = await this.kernelGlobSearch(paths); - - await Promise.all( - searchResults.map(async (resultPath) => { - // Add these into our path cache to speed up later finds - this.updateCache(resultPath); - const kernelspec = await this.getKernelSpec(resultPath); - - if (kernelspec) { - results.push(kernelspec); - } - }) - ); - - return results; - } - - // Load the IJupyterKernelSpec for a given spec path, check the ones that we have already loaded first - private async getKernelSpec(specPath: string): Promise { - // If we have not already loaded this kernel spec, then load it - if (!this.pathToKernelSpec.has(specPath)) { - this.pathToKernelSpec.set(specPath, this.loadKernelSpec(specPath)); - } - - // ! as the has and set above verify that we have a return here - return this.pathToKernelSpec.get(specPath)!.then((value) => { - if (value) { - return value; - } - - // If we failed to get a kernelspec pull path from our cache and loaded list - this.pathToKernelSpec.delete(specPath); - this.cache = this.cache?.filter((itempath) => itempath !== specPath); - return undefined; - }); - } - - // Load kernelspec json from disk - private async loadKernelSpec(specPath: string): Promise { - let kernelJson; - try { - kernelJson = JSON.parse(await this.fs.readLocalFile(specPath)); - } catch { - traceError(`Failed to parse kernelspec ${specPath}`); - return undefined; - } - const kernelSpec: IJupyterKernelSpec = new JupyterKernelSpec(kernelJson, specPath); - - // Some registered kernel specs do not have a name, in this case use the last part of the path - kernelSpec.name = kernelJson?.name || path.basename(path.dirname(specPath)); - return kernelSpec; - } - - // For the given resource, find atll the file paths for kernel specs that wewant to associate with this - private async findAllResourcePossibleKernelPaths( - resource: Resource, - _cancelToken?: CancellationToken - ): Promise { - const [activePath, interpreterPaths, diskPaths] = await Promise.all([ - this.getActiveInterpreterPath(resource), - this.getInterpreterPaths(resource), - this.getDiskPaths() - ]); - - return [...activePath, ...interpreterPaths, ...diskPaths]; - } - - private async getActiveInterpreterPath(resource: Resource): Promise { - const activeInterpreter = await this.interpreterService.getActiveInterpreter(resource); - - if (activeInterpreter) { - return [path.join(activeInterpreter.sysPrefix, 'share', 'jupyter', 'kernels')]; - } - - return []; - } - - private async getInterpreterPaths(resource: Resource): Promise { - const interpreters = await this.interpreterLocator.getInterpreters(resource, { ignoreCache: false }); - const interpreterPrefixPaths = interpreters.map((interpreter) => interpreter.sysPrefix); - // We can get many duplicates here, so de-dupe the list - const uniqueInterpreterPrefixPaths = [...new Set(interpreterPrefixPaths)]; - return uniqueInterpreterPrefixPaths.map((prefixPath) => path.join(prefixPath, baseKernelPath)); - } - - // Find any paths associated with the JUPYTER_PATH env var. Can be a list of dirs. - // We need to look at the 'kernels' sub-directory and these paths are supposed to come first in the searching - // https://jupyter.readthedocs.io/en/latest/projects/jupyter-directories.html#envvar-JUPYTER_PATH - private async getJupyterPathPaths(): Promise { - const paths: string[] = []; - const vars = await this.envVarsProvider.getEnvironmentVariables(); - const jupyterPathVars = vars.JUPYTER_PATH - ? vars.JUPYTER_PATH.split(path.delimiter).map((jupyterPath) => { - return path.join(jupyterPath, 'kernels'); - }) - : []; - - if (jupyterPathVars.length > 0) { - if (this.platformService.isWindows) { - const activeInterpreter = await this.interpreterService.getActiveInterpreter(); - if (activeInterpreter) { - jupyterPathVars.forEach(async (jupyterPath) => { - const jupyterWinPath = await getRealPath( - this.fs, - this.exeFactory, - activeInterpreter.path, - jupyterPath - ); - - if (jupyterWinPath) { - paths.push(jupyterWinPath); - } - }); - } else { - paths.push(...jupyterPathVars); - } - } else { - // Unix based - paths.push(...jupyterPathVars); - } - } - - return paths; - } - - private async getDiskPaths(): Promise { - // Paths specified in JUPYTER_PATH are supposed to come first in searching - const paths: string[] = await this.getJupyterPathPaths(); - - if (this.platformService.isWindows) { - const activeInterpreter = await this.interpreterService.getActiveInterpreter(); - if (activeInterpreter) { - const winPath = await getRealPath( - this.fs, - this.exeFactory, - activeInterpreter.path, - path.join(this.pathUtils.home, winJupyterPath) - ); - if (winPath) { - paths.push(winPath); - } - } else { - paths.push(path.join(this.pathUtils.home, winJupyterPath)); - } - - if (process.env.ALLUSERSPROFILE) { - paths.push(path.join(process.env.ALLUSERSPROFILE, 'jupyter', 'kernels')); - } - } else { - // Unix based - const secondPart = this.platformService.isMac ? macJupyterPath : linuxJupyterPath; - - paths.push( - path.join('usr', 'share', 'jupyter', 'kernels'), - path.join('usr', 'local', 'share', 'jupyter', 'kernels'), - path.join(this.pathUtils.home, secondPart) - ); - } - - return paths; - } - - // Given a set of paths, search for kernel.json files and return back the full paths of all of them that we find - private async kernelGlobSearch(paths: string[]): Promise { - const promises = paths.map((kernelPath) => this.fs.searchLocal(`**/kernel.json`, kernelPath, true)); - const searchResults = await Promise.all(promises); - - // Append back on the start of each path so we have the full path in the results - const fullPathResults = searchResults - .filter((f) => f) - .map((result, index) => { - return result.map((partialSpecPath) => { - return path.join(paths[index], partialSpecPath); - }); - }); - - return flatten(fullPathResults); - } - - private async getKernelSpecFromActiveInterpreter( - kernelName: string, - resource: Resource - ): Promise { - const activePath = await this.getActiveInterpreterPath(resource); - return this.getKernelSpecFromDisk(activePath, kernelName); - } - - private async findInterpreterPath( - interpreterPaths: string[], - kernelName: string - ): Promise { - const promises = interpreterPaths.map((intPath) => this.getKernelSpecFromDisk([intPath], kernelName)); - - const specs = await Promise.all(promises); - return specs.find((sp) => sp !== undefined); - } - - // Jupyter looks for kernels in these paths: - // https://jupyter-client.readthedocs.io/en/stable/kernels.html#kernel-specs - private async findDiskPath(kernelName: string): Promise { - const paths = await this.getDiskPaths(); - - return this.getKernelSpecFromDisk(paths, kernelName); - } - - private async getKernelSpecFromDisk(paths: string[], kernelName: string): Promise { - const searchResults = await this.kernelGlobSearch(paths); - searchResults.forEach((specPath) => { - this.updateCache(specPath); - }); - - return this.searchCache(kernelName); - } - - private async readCache(): Promise { - try { - if (Array.isArray(this.cache) && this.cache.length > 0) { - return; - } - this.cache = JSON.parse( - await this.fs.readLocalFile(path.join(this.context.globalStoragePath, cacheFile)) - ) as string[]; - } catch { - traceInfo('No kernelSpec cache found.'); - } - } - - private updateCache(newPath: string) { - this.cache = Array.isArray(this.cache) ? this.cache : []; - if (!this.cache.includes(newPath)) { - this.cache.push(newPath); - this.cacheDirty = true; - } - } - - private async writeCache() { - if (this.cacheDirty && Array.isArray(this.cache)) { - await this.fs.writeLocalFile( - path.join(this.context.globalStoragePath, cacheFile), - JSON.stringify(this.cache) - ); - this.cacheDirty = false; - } - } - - private async searchCache(kernelName: string): Promise { - const kernelJsonFile = this.cache?.find((kernelPath) => { - try { - return path.basename(path.dirname(kernelPath)) === kernelName; - } catch (e) { - traceInfo('KernelSpec path in cache is not a string.', e); - return false; - } - }); - - if (kernelJsonFile) { - return this.getKernelSpec(kernelJsonFile); - } - - return undefined; - } -} diff --git a/src/client/datascience/kernel-launcher/kernelLauncher.ts b/src/client/datascience/kernel-launcher/kernelLauncher.ts deleted file mode 100644 index 50894ab93a10..000000000000 --- a/src/client/datascience/kernel-launcher/kernelLauncher.ts +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import { inject, injectable } from 'inversify'; -import * as portfinder from 'portfinder'; -import { promisify } from 'util'; -import * as uuid from 'uuid/v4'; -import { IProcessServiceFactory } from '../../common/process/types'; -import { Resource } from '../../common/types'; -import { captureTelemetry } from '../../telemetry'; -import { Telemetry } from '../constants'; -import { KernelSpecConnectionMetadata, PythonKernelConnectionMetadata } from '../jupyter/kernels/types'; -import { IDataScienceFileSystem } from '../types'; -import { KernelDaemonPool } from './kernelDaemonPool'; -import { KernelProcess } from './kernelProcess'; -import { IKernelConnection, IKernelLauncher, IKernelProcess } from './types'; - -const PortToStartFrom = 9_000; - -// Launches and returns a kernel process given a resource or python interpreter. -// If the given interpreter is undefined, it will try to use the selected interpreter. -// If the selected interpreter doesn't have a kernel, it will find a kernel on disk and use that. -@injectable() -export class KernelLauncher implements IKernelLauncher { - private static nextFreePortToTryAndUse = PortToStartFrom; - constructor( - @inject(IProcessServiceFactory) private processExecutionFactory: IProcessServiceFactory, - @inject(IDataScienceFileSystem) private readonly fs: IDataScienceFileSystem, - @inject(KernelDaemonPool) private readonly daemonPool: KernelDaemonPool - ) {} - - @captureTelemetry(Telemetry.KernelLauncherPerf) - public async launch( - kernelConnectionMetadata: KernelSpecConnectionMetadata | PythonKernelConnectionMetadata, - resource: Resource, - workingDirectory: string - ): Promise { - const connection = await this.getKernelConnection(); - const kernelProcess = new KernelProcess( - this.processExecutionFactory, - this.daemonPool, - connection, - kernelConnectionMetadata, - this.fs, - resource - ); - await kernelProcess.launch(workingDirectory); - return kernelProcess; - } - - private async getKernelConnection(): Promise { - const getPorts = promisify(portfinder.getPorts); - // Ports may have been freed, hence start from begining. - const port = - KernelLauncher.nextFreePortToTryAndUse > PortToStartFrom + 1_000 - ? PortToStartFrom - : KernelLauncher.nextFreePortToTryAndUse; - const ports = await getPorts(5, { host: '127.0.0.1', port }); - // We launch restart kernels in the background, its possible other session hasn't started. - // Ensure we do not use same ports. - KernelLauncher.nextFreePortToTryAndUse = Math.max(...ports) + 1; - - return { - version: 1, - key: uuid(), - signature_scheme: 'hmac-sha256', - transport: 'tcp', - ip: '127.0.0.1', - hb_port: ports[0], - control_port: ports[1], - shell_port: ports[2], - stdin_port: ports[3], - iopub_port: ports[4] - }; - } -} diff --git a/src/client/datascience/kernel-launcher/kernelLauncherDaemon.ts b/src/client/datascience/kernel-launcher/kernelLauncherDaemon.ts deleted file mode 100644 index e79a699aaf66..000000000000 --- a/src/client/datascience/kernel-launcher/kernelLauncherDaemon.ts +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { ChildProcess } from 'child_process'; -import * as fs from 'fs-extra'; -import { inject, injectable } from 'inversify'; -import { IDisposable } from 'monaco-editor'; -import { ObservableExecutionResult } from '../../common/process/types'; -import { Resource } from '../../common/types'; -import { noop } from '../../common/utils/misc'; -import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { IJupyterKernelSpec } from '../types'; -import { KernelDaemonPool } from './kernelDaemonPool'; -import { IPythonKernelDaemon } from './types'; - -/** - * Launches a Python kernel in a daemon. - * We need a daemon for the sole purposes of being able to interrupt kernels in Windows. - * (Else we don't need a kernel). - */ -@injectable() -export class PythonKernelLauncherDaemon implements IDisposable { - private readonly processesToDispose: ChildProcess[] = []; - constructor(@inject(KernelDaemonPool) private readonly daemonPool: KernelDaemonPool) {} - public async launch( - resource: Resource, - workingDirectory: string, - kernelSpec: IJupyterKernelSpec, - interpreter?: PythonEnvironment - ): Promise<{ observableOutput: ObservableExecutionResult; daemon: IPythonKernelDaemon | undefined }> { - const [daemon, wdExists] = await Promise.all([ - this.daemonPool.get(resource, kernelSpec, interpreter), - fs.pathExists(workingDirectory) - ]); - - // Check to see if we have the type of kernelspec that we expect - const args = kernelSpec.argv.slice(); - const modulePrefixIndex = args.findIndex((item) => item === '-m'); - if (modulePrefixIndex === -1) { - throw new Error( - `Unsupported KernelSpec file. args must be [, '-m', , arg1, arg2, ..]. Provied ${args.join( - ' ' - )}` - ); - } - const moduleName = args[modulePrefixIndex + 1]; - const moduleArgs = args.slice(modulePrefixIndex + 2); - const env = kernelSpec.env && Object.keys(kernelSpec.env).length > 0 ? kernelSpec.env : undefined; - - // The daemon pool can return back a non-IPythonKernelDaemon if daemon service is not supported or for Python 2. - // Use a check for the daemon.start function here before we call it. - if (!('start' in daemon)) { - // If we don't have a KernelDaemon here then we have an execution service and should use that to launch - const observableOutput = daemon.execModuleObservable(moduleName, moduleArgs, { - env, - cwd: wdExists ? workingDirectory : process.cwd() - }); - - return { observableOutput, daemon: undefined }; - } else { - // In the case that we do have a kernel deamon, just return it - const observableOutput = await daemon.start(moduleName, moduleArgs, { env, cwd: workingDirectory }); - if (observableOutput.proc) { - this.processesToDispose.push(observableOutput.proc); - } - return { observableOutput, daemon }; - } - } - public dispose() { - while (this.processesToDispose.length) { - try { - this.processesToDispose.shift()!.kill(); - } catch { - noop(); - } - } - } -} diff --git a/src/client/datascience/kernel-launcher/kernelProcess.ts b/src/client/datascience/kernel-launcher/kernelProcess.ts deleted file mode 100644 index 46b79635dc90..000000000000 --- a/src/client/datascience/kernel-launcher/kernelProcess.ts +++ /dev/null @@ -1,294 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import { ChildProcess } from 'child_process'; -import * as tcpPortUsed from 'tcp-port-used'; -import * as tmp from 'tmp'; -import { Event, EventEmitter } from 'vscode'; -import { traceError, traceInfo, traceWarning } from '../../common/logger'; -import { IProcessServiceFactory, ObservableExecutionResult } from '../../common/process/types'; -import { Resource } from '../../common/types'; -import { noop, swallowExceptions } from '../../common/utils/misc'; -import { captureTelemetry } from '../../telemetry'; -import { Telemetry } from '../constants'; -import { - createDefaultKernelSpec, - findIndexOfConnectionFile, - isPythonKernelConnection -} from '../jupyter/kernels/helpers'; -import { KernelSpecConnectionMetadata, PythonKernelConnectionMetadata } from '../jupyter/kernels/types'; -import { IDataScienceFileSystem, IJupyterKernelSpec } from '../types'; -import { KernelDaemonPool } from './kernelDaemonPool'; -import { PythonKernelLauncherDaemon } from './kernelLauncherDaemon'; -import { IKernelConnection, IKernelProcess, IPythonKernelDaemon, PythonKernelDiedError } from './types'; - -// Launches and disposes a kernel process given a kernelspec and a resource or python interpreter. -// Exposes connection information and the process itself. -export class KernelProcess implements IKernelProcess { - public get exited(): Event<{ exitCode?: number; reason?: string }> { - return this.exitEvent.event; - } - public get kernelConnectionMetadata(): Readonly { - return this._kernelConnectionMetadata; - } - public get connection(): Readonly { - return this._connection; - } - private get isPythonKernel(): boolean { - return isPythonKernelConnection(this.kernelConnectionMetadata); - } - private _process?: ChildProcess; - private exitEvent = new EventEmitter<{ exitCode?: number; reason?: string }>(); - private pythonKernelLauncher?: PythonKernelLauncherDaemon; - private launchedOnce?: boolean; - private disposed?: boolean; - private kernelDaemon?: IPythonKernelDaemon; - private connectionFile?: string; - private _launchKernelSpec?: IJupyterKernelSpec; - private readonly _kernelConnectionMetadata: Readonly; - constructor( - private readonly processExecutionFactory: IProcessServiceFactory, - private readonly daemonPool: KernelDaemonPool, - private readonly _connection: IKernelConnection, - kernelConnectionMetadata: KernelSpecConnectionMetadata | PythonKernelConnectionMetadata, - private readonly fs: IDataScienceFileSystem, - private readonly resource: Resource - ) { - this._kernelConnectionMetadata = kernelConnectionMetadata; - } - public async interrupt(): Promise { - if (this.kernelDaemon) { - await this.kernelDaemon?.interrupt(); - } - } - - @captureTelemetry(Telemetry.RawKernelProcessLaunch, undefined, true) - public async launch(workingDirectory: string): Promise { - if (this.launchedOnce) { - throw new Error('Kernel has already been launched.'); - } - this.launchedOnce = true; - - // Update our connection arguments in the kernel spec - await this.updateConnectionArgs(); - - const exeObs = await this.launchAsObservable(workingDirectory); - - let stdout = ''; - let stderr = ''; - exeObs.out.subscribe( - (output) => { - if (output.source === 'stderr') { - // Capture stderr, incase kernel doesn't start. - stderr += output.out; - traceWarning(`StdErr from Kernel Process ${output.out}`); - } else { - stdout += output.out; - traceInfo(`Kernel Output: ${stdout}`); - } - }, - (error) => { - if (this.disposed) { - traceInfo('Kernel died', error, stderr); - return; - } - traceError('Kernel died', error, stderr); - if (error instanceof PythonKernelDiedError) { - if (this.disposed) { - traceInfo('KernelProcess Exit', `Exit - ${error.exitCode}, ${error.reason}`, error); - } else { - traceError('KernelProcess Exit', `Exit - ${error.exitCode}, ${error.reason}`, error); - } - if (this.disposed) { - return; - } - this.exitEvent.fire({ exitCode: error.exitCode, reason: error.reason || error.message }); - } - } - ); - - // Don't return until our heartbeat channel is open for connections - return this.waitForHeartbeat(); - } - - public async dispose(): Promise { - if (this.disposed) { - return; - } - this.disposed = true; - if (this.kernelDaemon) { - await this.kernelDaemon.kill().catch(noop); - swallowExceptions(() => this.kernelDaemon?.dispose()); - } - swallowExceptions(() => { - this._process?.kill(); // NOSONAR - this.exitEvent.fire({}); - }); - swallowExceptions(() => this.pythonKernelLauncher?.dispose()); - swallowExceptions(async () => (this.connectionFile ? this.fs.deleteLocalFile(this.connectionFile) : noop())); - } - - // Make sure that the heartbeat channel is open for connections - private async waitForHeartbeat() { - try { - // Wait until the port is open for connection - // First parameter is wait between retries, second parameter is total wait before error - await tcpPortUsed.waitUntilUsed(this.connection.hb_port, 200, 30_000); - } catch (error) { - // Make sure to dispose if we never get a heartbeat - this.dispose().ignoreErrors(); - traceError('Timed out waiting to get a heartbeat from kernel process.'); - throw new Error('Timed out waiting to get a heartbeat from kernel process.'); - } - } - - private get launchKernelSpec(): IJupyterKernelSpec { - if (this._launchKernelSpec) { - return this._launchKernelSpec; - } - - let kernelSpec = this._kernelConnectionMetadata.kernelSpec; - // If there is no kernelspec & when launching a Python process, generate a dummy `kernelSpec` - if (!kernelSpec && this._kernelConnectionMetadata.kind === 'startUsingPythonInterpreter') { - kernelSpec = createDefaultKernelSpec(this._kernelConnectionMetadata.interpreter); - } - // We always expect a kernel spec. - if (!kernelSpec) { - throw new Error('KernelSpec cannot be empty in KernelProcess.ts'); - } - if (!Array.isArray(kernelSpec.argv)) { - traceError('KernelSpec.argv in KernelPrcess is undefined'); - // tslint:disable-next-line: no-any - this._launchKernelSpec = undefined; - } else { - // Copy our kernelspec and assign a new argv array - this._launchKernelSpec = { ...kernelSpec, argv: [...kernelSpec.argv] }; - } - return this._launchKernelSpec!; - } - - // Instead of having to use a connection file update our local copy of the kernelspec to launch - // directly with command line arguments - private async updateConnectionArgs() { - // First check to see if we have a kernelspec that expects a connection file, - // Error if we don't have one. We expect '-f', '{connectionfile}' in our launch args - const indexOfConnectionFile = findIndexOfConnectionFile(this.launchKernelSpec); - - // Technically if we don't have a kernelspec then index should already be -1, but the check here lets us avoid ? on the type - if (indexOfConnectionFile === -1) { - throw new Error( - `Connection file not found in kernelspec json args, ${this.launchKernelSpec.argv.join(' ')}` - ); - } - - if ( - this.isPythonKernel && - indexOfConnectionFile === 0 && - this.launchKernelSpec.argv[indexOfConnectionFile - 1] !== '-f' - ) { - throw new Error( - `Connection file not found in kernelspec json args, ${this.launchKernelSpec.argv.join(' ')}` - ); - } - - // Python kernels are special. Handle the extra arguments. - if (this.isPythonKernel) { - // Slice out -f and the connection file from the args - this.launchKernelSpec.argv.splice(indexOfConnectionFile - 1, 2); - - // Add in our connection command line args - this.launchKernelSpec.argv.push(...this.addPythonConnectionArgs()); - } else { - // For other kernels, just write to the connection file. - // Note: We have to dispose the temp file and recreate it because otherwise the file - // system will hold onto the file with an open handle. THis doesn't work so well when - // a different process tries to open it. - const tempFile = await this.fs.createTemporaryLocalFile('.json'); - this.connectionFile = tempFile.filePath; - await tempFile.dispose(); - await this.fs.writeLocalFile(this.connectionFile, JSON.stringify(this._connection)); - - // Then replace the connection file argument with this file - this.launchKernelSpec.argv[indexOfConnectionFile] = this.connectionFile; - } - } - - // Add the command line arguments - private addPythonConnectionArgs(): string[] { - const newConnectionArgs: string[] = []; - - newConnectionArgs.push(`--ip=${this._connection.ip}`); - newConnectionArgs.push(`--stdin=${this._connection.stdin_port}`); - newConnectionArgs.push(`--control=${this._connection.control_port}`); - newConnectionArgs.push(`--hb=${this._connection.hb_port}`); - newConnectionArgs.push(`--Session.signature_scheme="${this._connection.signature_scheme}"`); - newConnectionArgs.push(`--Session.key=b"${this._connection.key}"`); // Note we need the 'b here at the start for a byte string - newConnectionArgs.push(`--shell=${this._connection.shell_port}`); - newConnectionArgs.push(`--transport="${this._connection.transport}"`); - newConnectionArgs.push(`--iopub=${this._connection.iopub_port}`); - - // Turn this on if you get desparate. It can cause crashes though as the - // logging code isn't that robust. - // if (isTestExecution()) { - // // Extra logging for tests - // newConnectionArgs.push(`--log-level=10`); - // } - - // We still put in the tmp name to make sure the kernel picks a valid connection file name. It won't read it as - // we passed in the arguments, but it will use it as the file name so it doesn't clash with other kernels. - newConnectionArgs.push(`--f=${tmp.tmpNameSync({ postfix: '.json' })}`); - - return newConnectionArgs; - } - - private async launchAsObservable(workingDirectory: string) { - let exeObs: ObservableExecutionResult | undefined; - if (this.isPythonKernel) { - this.pythonKernelLauncher = new PythonKernelLauncherDaemon(this.daemonPool); - const kernelDaemonLaunch = await this.pythonKernelLauncher.launch( - this.resource, - workingDirectory, - this.launchKernelSpec, - this._kernelConnectionMetadata.interpreter - ); - - this.kernelDaemon = kernelDaemonLaunch.daemon; - exeObs = kernelDaemonLaunch.observableOutput; - } - - // If we are not python just use the ProcessExecutionFactory - if (!exeObs) { - // First part of argument is always the executable. - const executable = this.launchKernelSpec.argv[0]; - const executionService = await this.processExecutionFactory.create(this.resource); - exeObs = executionService.execObservable(executable, this.launchKernelSpec.argv.slice(1), { - env: this._kernelConnectionMetadata.kernelSpec?.env, - cwd: workingDirectory - }); - } - - if (exeObs && exeObs.proc) { - exeObs.proc.on('exit', (exitCode) => { - traceInfo('KernelProcess Exit', `Exit - ${exitCode}`); - if (this.disposed) { - return; - } - this.exitEvent.fire({ exitCode: exitCode || undefined }); - }); - // tslint:disable-next-line: no-any - exeObs.proc.stdout.on('data', (data: any) => { - traceInfo(`KernelProcess output: ${data}`); - }); - // tslint:disable-next-line: no-any - exeObs.proc.stderr.on('data', (data: any) => { - traceInfo(`KernelProcess error: ${data}`); - }); - } else { - throw new Error('KernelProcess failed to launch'); - } - - this._process = exeObs.proc; - return exeObs; - } -} diff --git a/src/client/datascience/kernel-launcher/types.ts b/src/client/datascience/kernel-launcher/types.ts deleted file mode 100644 index 625d3226af47..000000000000 --- a/src/client/datascience/kernel-launcher/types.ts +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import type { nbformat } from '@jupyterlab/coreutils'; -import { SpawnOptions } from 'child_process'; -import { CancellationToken, Event } from 'vscode'; -import { InterpreterUri } from '../../common/installer/types'; -import { ObservableExecutionResult } from '../../common/process/types'; -import { IAsyncDisposable, IDisposable, Resource } from '../../common/types'; -import { KernelSpecConnectionMetadata, PythonKernelConnectionMetadata } from '../jupyter/kernels/types'; -import { IJupyterKernelSpec } from '../types'; - -export const IKernelLauncher = Symbol('IKernelLauncher'); -export interface IKernelLauncher { - launch( - kernelConnectionMetadata: KernelSpecConnectionMetadata | PythonKernelConnectionMetadata, - resource: Resource, - workingDirectory: string - ): Promise; -} - -export interface IKernelConnection { - version: number; - iopub_port: number; - shell_port: number; - stdin_port: number; - control_port: number; - signature_scheme: 'hmac-sha256'; - hb_port: number; - ip: string; - key: string; - transport: 'tcp' | 'ipc'; -} - -export interface IKernelProcess extends IAsyncDisposable { - readonly connection: Readonly; - readonly kernelConnectionMetadata: Readonly; - /** - * This event is triggered if the process is exited - */ - readonly exited: Event<{ exitCode?: number; reason?: string }>; - interrupt(): Promise; -} - -export const IKernelFinder = Symbol('IKernelFinder'); -export interface IKernelFinder { - findKernelSpec( - interpreterUri: InterpreterUri, - kernelSpecMetadata?: nbformat.IKernelspecMetadata, - cancelToken?: CancellationToken, - ignoreDependencyCheck?: boolean - ): Promise; - listKernelSpecs(resource: Resource): Promise; -} - -/** - * The daemon responsible for the Python Kernel. - */ -export interface IPythonKernelDaemon extends IDisposable { - interrupt(): Promise; - kill(): Promise; - preWarm(): Promise; - start(moduleName: string, args: string[], options: SpawnOptions): Promise>; -} - -export class PythonKernelDiedError extends Error { - public readonly exitCode: number; - public readonly reason?: string; - constructor(options: { exitCode: number; reason?: string } | { error: Error }) { - const message = - 'exitCode' in options - ? `Kernel died with exit code ${options.exitCode}. ${options.reason}` - : `Kernel died ${options.error.message}`; - super(message); - if ('exitCode' in options) { - this.exitCode = options.exitCode; - this.reason = options.reason; - } else { - this.exitCode = -1; - this.reason = options.error.message; - this.stack = options.error.stack; - this.name = options.error.name; - } - } -} diff --git a/src/client/datascience/kernelSocketWrapper.ts b/src/client/datascience/kernelSocketWrapper.ts deleted file mode 100644 index ca87b32723f4..000000000000 --- a/src/client/datascience/kernelSocketWrapper.ts +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import * as WebSocketWS from 'ws'; -import { ClassType } from '../ioc/types'; -import { IKernelSocket } from './types'; - -// tslint:disable: no-any prefer-method-signature -export type IWebSocketLike = { - onopen: (event: { target: any }) => void; - onerror: (event: { error: any; message: string; type: string; target: any }) => void; - onclose: (event: { wasClean: boolean; code: number; reason: string; target: any }) => void; - onmessage: (event: { data: WebSocketWS.Data; type: string; target: any }) => void; - emit(event: string | symbol, ...args: any[]): boolean; - send(data: any, a2: any): void; - close(): void; -}; - -/** - * This is called a mixin class in TypeScript. - * Allows us to have different base classes but inherit behavior (workaround for not allowing multiple inheritance). - * Essentially it sticks a temp class in between the base class and the class you're writing. - * Something like this: - * - * class Base { - * doStuff() { - * - * } - * } - * - * function Mixin = (SuperClass) { - * return class extends SuperClass { - * doExtraStuff() { - * super.doStuff(); - * } - * } - * } - * - * function SubClass extends Mixin(Base) { - * doBar() : { - * super.doExtraStuff(); - * } - * } - * - */ - -export function KernelSocketWrapper>(SuperClass: T) { - return class BaseKernelSocket extends SuperClass implements IKernelSocket { - private receiveHooks: ((data: WebSocketWS.Data) => Promise)[]; - private sendHooks: ((data: any, cb?: (err?: Error) => void) => Promise)[]; - private msgChain: Promise; - private sendChain: Promise; - - constructor(...rest: any[]) { - super(...rest); - // Make sure the message chain is initialized - this.msgChain = Promise.resolve(); - this.sendChain = Promise.resolve(); - this.receiveHooks = []; - this.sendHooks = []; - } - - public sendToRealKernel(data: any, a2: any) { - // This will skip the send hooks. It's coming from - // the UI side. - super.send(data, a2); - } - - public send(data: any, a2: any): void { - if (this.sendHooks) { - // Stick the send hooks into the send chain. We use chain - // to ensure that: - // a) Hooks finish before we fire the event for real - // b) Event fires - // c) Next message happens after this one (so the UI can handle the message before another event goes through) - this.sendChain = this.sendChain - .then(() => Promise.all(this.sendHooks.map((s) => s(data, a2)))) - .then(() => super.send(data, a2)); - } else { - super.send(data, a2); - } - } - - public emit(event: string | symbol, ...args: any[]): boolean { - if (event === 'message' && this.receiveHooks.length) { - // Stick the receive hooks into the message chain. We use chain - // to ensure that: - // a) Hooks finish before we fire the event for real - // b) Event fires - // c) Next message happens after this one (so this side can handle the message before another event goes through) - this.msgChain = this.msgChain - .then(() => Promise.all(this.receiveHooks.map((p) => p(args[0])))) - .then(() => super.emit(event, ...args)); - // True value indicates there were handlers. We definitely have 'message' handlers. - return true; - } else { - return super.emit(event, ...args); - } - } - - public addReceiveHook(hook: (data: WebSocketWS.Data) => Promise) { - this.receiveHooks.push(hook); - } - public removeReceiveHook(hook: (data: WebSocketWS.Data) => Promise) { - this.receiveHooks = this.receiveHooks.filter((l) => l !== hook); - } - - // tslint:disable-next-line: no-any - public addSendHook(patch: (data: any, cb?: (err?: Error) => void) => Promise): void { - this.sendHooks.push(patch); - } - - // tslint:disable-next-line: no-any - public removeSendHook(patch: (data: any, cb?: (err?: Error) => void) => Promise): void { - this.sendHooks = this.sendHooks.filter((p) => p !== patch); - } - }; -} diff --git a/src/client/datascience/liveshare/liveshare.ts b/src/client/datascience/liveshare/liveshare.ts deleted file mode 100644 index c7ecb30ec1d8..000000000000 --- a/src/client/datascience/liveshare/liveshare.ts +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { inject, injectable } from 'inversify'; -import * as vsls from 'vsls/vscode'; - -import { IApplicationShell, ILiveShareApi, IWorkspaceService } from '../../common/application/types'; -import { IConfigurationService, IDisposableRegistry } from '../../common/types'; -import { LiveShareProxy } from './liveshareProxy'; - -// tslint:disable:no-any unified-signatures - -@injectable() -export class LiveShareApi implements ILiveShareApi { - private supported: boolean = false; - private apiPromise: Promise | undefined; - private disposed: boolean = false; - - constructor( - @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, - @inject(IWorkspaceService) workspace: IWorkspaceService, - @inject(IConfigurationService) private configService: IConfigurationService, - @inject(IApplicationShell) private appShell: IApplicationShell - ) { - const disposable = workspace.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration('python.dataScience', undefined)) { - // When config changes happen, recreate our commands. - this.onSettingsChanged(); - } - }); - disposableRegistry.push(disposable); - disposableRegistry.push(this); - this.onSettingsChanged(); - } - - public dispose(): void { - this.disposed = true; - } - - public getApi(): Promise { - if (this.disposed) { - return Promise.resolve(null); - } - return this.apiPromise!; - } - - private onSettingsChanged() { - const supported = this.configService.getSettings().datascience.allowLiveShare; - if (supported !== this.supported) { - this.supported = supported ? true : false; - const liveShareTimeout = this.configService.getSettings().datascience.liveShareConnectionTimeout; - this.apiPromise = supported - ? vsls - .getApi() - .then((a) => (a ? new LiveShareProxy(this.appShell, liveShareTimeout, a) : a)) - .catch((_e) => null) - : Promise.resolve(null); - } else if (!this.apiPromise) { - this.apiPromise = Promise.resolve(null); - } - } -} diff --git a/src/client/datascience/liveshare/liveshareProxy.ts b/src/client/datascience/liveshare/liveshareProxy.ts deleted file mode 100644 index 4a4b2f10b5d3..000000000000 --- a/src/client/datascience/liveshare/liveshareProxy.ts +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { Disposable, Event, TreeDataProvider, Uri } from 'vscode'; -import * as vsls from 'vsls/vscode'; - -import { IApplicationShell } from '../../common/application/types'; -import { createDeferred, Deferred } from '../../common/utils/async'; -import * as localize from '../../common/utils/localize'; -import { LiveShare, LiveShareCommands } from '../constants'; -import { ServiceProxy } from './serviceProxy'; - -// tslint:disable:no-any unified-signatures -export class LiveShareProxy implements vsls.LiveShare { - private currentRole: vsls.Role = vsls.Role.None; - private guestChecker: vsls.SharedService | vsls.SharedServiceProxy | null = null; - private pendingGuestCheckCount = 0; - private peerCheckPromise: Deferred | undefined; - constructor( - private applicationShell: IApplicationShell, - private peerTimeout: number | undefined, - private realApi: vsls.LiveShare - ) { - this.realApi.onDidChangePeers(this.onPeersChanged, this); - this.realApi.onDidChangeSession(this.onSessionChanged, this); - this.onSessionChanged({ session: this.realApi.session }).ignoreErrors(); - } - public get session(): vsls.Session { - return this.realApi.session; - } - public get onDidChangeSession(): Event { - return this.realApi.onDidChangeSession; - } - public get peers(): vsls.Peer[] { - return this.realApi.peers; - } - public get onDidChangePeers(): Event { - return this.realApi.onDidChangePeers; - } - public share(options?: vsls.ShareOptions | undefined): Promise { - return this.realApi.share(options); - } - public join(link: Uri, options?: vsls.JoinOptions | undefined): Promise { - return this.realApi.join(link, options); - } - public end(): Promise { - return this.realApi.end(); - } - public async shareService(name: string): Promise { - // Create the real shared service. - const realService = await this.realApi.shareService(name); - - // Create a proxy for the shared service. This allows us to wait for the next request/response - // on the shared service to cause a failure when the guest doesn't have the python extension installed. - if (realService) { - return new ServiceProxy( - realService, - () => this.peersAreOkay(), - () => this.forceShutdown() - ); - } - - return realService; - } - public unshareService(name: string): Promise { - return this.realApi.unshareService(name); - } - public getSharedService(name: string): Promise { - return this.realApi.getSharedService(name); - } - public convertLocalUriToShared(localUri: Uri): Uri { - return this.realApi.convertLocalUriToShared(localUri); - } - public convertSharedUriToLocal(sharedUri: Uri): Uri { - return this.realApi.convertSharedUriToLocal(sharedUri); - } - public registerCommand(command: string, isEnabled?: (() => boolean) | undefined, thisArg?: any): Disposable | null { - return this.realApi.registerCommand(command, isEnabled, thisArg); - } - public registerTreeDataProvider(viewId: vsls.View, treeDataProvider: TreeDataProvider): Disposable | null { - return this.realApi.registerTreeDataProvider(viewId, treeDataProvider); - } - public registerContactServiceProvider( - name: string, - contactServiceProvider: vsls.ContactServiceProvider - ): Disposable | null { - return this.realApi.registerContactServiceProvider(name, contactServiceProvider); - } - public shareServer(server: vsls.Server): Promise { - return this.realApi.shareServer(server); - } - public getContacts(emails: string[]): Promise { - return this.realApi.getContacts(emails); - } - - private async onSessionChanged(ev: vsls.SessionChangeEvent): Promise { - const newRole = ev.session ? ev.session.role : vsls.Role.None; - if (this.currentRole !== newRole) { - // Setup our guest checker service. - if (this.currentRole === vsls.Role.Host) { - await this.realApi.unshareService(LiveShare.GuestCheckerService); - } - this.currentRole = newRole; - - // If host, we need to listen for responses - if (this.currentRole === vsls.Role.Host) { - this.guestChecker = await this.realApi.shareService(LiveShare.GuestCheckerService); - if (this.guestChecker) { - this.guestChecker.onNotify(LiveShareCommands.guestCheck, (_args: object) => this.onGuestResponse()); - } - - // If guest, we need to list for requests. - } else if (this.currentRole === vsls.Role.Guest) { - this.guestChecker = await this.realApi.getSharedService(LiveShare.GuestCheckerService); - if (this.guestChecker) { - this.guestChecker.onNotify(LiveShareCommands.guestCheck, (_args: object) => this.onHostRequest()); - } - } - } - } - - private onPeersChanged(_ev: vsls.PeersChangeEvent) { - if (this.currentRole === vsls.Role.Host && this.guestChecker) { - // Update our pending count. This means we need to ask again if positive. - this.pendingGuestCheckCount = this.realApi.peers.length; - this.peerCheckPromise = undefined; - } - } - - private peersAreOkay(): Promise { - // If already asking, just use that promise - if (this.peerCheckPromise) { - return this.peerCheckPromise.promise; - } - - // Shortcut if we don't need to ask. - if (!this.guestChecker || this.currentRole !== vsls.Role.Host || this.pendingGuestCheckCount <= 0) { - return Promise.resolve(true); - } - - // We need to ask each guest then. - this.peerCheckPromise = createDeferred(); - this.guestChecker.notify(LiveShareCommands.guestCheck, {}); - - // Wait for a second and then check - setTimeout(this.validatePendingGuests.bind(this), this.peerTimeout ? this.peerTimeout : 1000); - return this.peerCheckPromise.promise; - } - - private validatePendingGuests() { - if (this.peerCheckPromise && !this.peerCheckPromise.resolved) { - this.peerCheckPromise.resolve(this.pendingGuestCheckCount <= 0); - } - } - - private onGuestResponse() { - // Guest has responded to a guest check. Update our pending count - this.pendingGuestCheckCount -= 1; - if (this.pendingGuestCheckCount <= 0 && this.peerCheckPromise) { - this.peerCheckPromise.resolve(true); - } - } - - private onHostRequest() { - // Host is asking us to respond - if (this.guestChecker && this.currentRole === vsls.Role.Guest) { - this.guestChecker.notify(LiveShareCommands.guestCheck, {}); - } - } - - private forceShutdown() { - // One or more guests doesn't have the python extension installed. Force our live share session to disconnect - this.realApi - .end() - .then(() => { - this.pendingGuestCheckCount = 0; - this.peerCheckPromise = undefined; - this.applicationShell.showErrorMessage(localize.DataScience.liveShareInvalid()); - }) - .ignoreErrors(); - } -} diff --git a/src/client/datascience/liveshare/postOffice.ts b/src/client/datascience/liveshare/postOffice.ts deleted file mode 100644 index ac255edd586b..000000000000 --- a/src/client/datascience/liveshare/postOffice.ts +++ /dev/null @@ -1,284 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import type { JSONArray } from '@phosphor/coreutils'; -import * as vscode from 'vscode'; -import * as vsls from 'vsls/vscode'; - -import { ILiveShareApi } from '../../common/application/types'; -import { traceInfo } from '../../common/logger'; -import { IAsyncDisposable } from '../../common/types'; -import { createDeferred, Deferred } from '../../common/utils/async'; -import { LiveShare } from '../constants'; - -// tslint:disable:no-any - -interface IMessageArgs { - args: string; -} - -// This class is used to register two communication between a host and all of its guests -export class PostOffice implements IAsyncDisposable { - private name: string; - private startedPromise: Deferred | undefined; - private hostServer: vsls.SharedService | null = null; - private guestServer: vsls.SharedServiceProxy | null = null; - private currentRole: vsls.Role = vsls.Role.None; - private currentPeerCount: number = 0; - private peerCountChangedEmitter: vscode.EventEmitter = new vscode.EventEmitter(); - private commandMap: { [key: string]: { thisArg: any; callback(...args: any[]): void } } = {}; - - constructor( - name: string, - private liveShareApi: ILiveShareApi, - private hostArgsTranslator?: (api: vsls.LiveShare | null, command: string, role: vsls.Role, args: any[]) => void - ) { - this.name = name; - - // Note to self, could the callbacks be keeping things alive that we don't want to be alive? - } - - public get peerCount() { - return this.currentPeerCount; - } - - public get peerCountChanged(): vscode.Event { - return this.peerCountChangedEmitter.event; - } - - public get role() { - return this.currentRole; - } - - public async dispose() { - this.peerCountChangedEmitter.fire(0); - this.peerCountChangedEmitter.dispose(); - if (this.hostServer) { - traceInfo(`Shutting down live share api`); - const s = await this.getApi(); - if (s !== null) { - await s.unshareService(this.name); - } - this.hostServer = null; - } - this.guestServer = null; - } - - public async postCommand(command: string, ...args: any[]): Promise { - // Make sure startup finished - const api = await this.getApi(); - let skipDefault = false; - - if (api && api.session) { - switch (this.currentRole) { - case vsls.Role.Guest: - // Ask host to broadcast - if (this.guestServer) { - this.guestServer.notify( - LiveShare.LiveShareBroadcastRequest, - this.createBroadcastArgs(command, ...args) - ); - } - skipDefault = true; - break; - case vsls.Role.Host: - // Notify everybody and call our local callback (by falling through) - if (this.hostServer) { - this.hostServer.notify( - this.escapeCommandName(command), - this.translateArgs(api, command, ...args) - ); - } - break; - default: - break; - } - } - - if (!skipDefault) { - // Default when not connected is to just call the registered callback - this.callCallback(command, ...args); - } - } - - public async registerCallback(command: string, callback: (...args: any[]) => void, thisArg?: any): Promise { - const api = await this.getApi(); - - // For a guest, make sure to register the notification - if (api && api.session && api.session.role === vsls.Role.Guest && this.guestServer) { - this.guestServer.onNotify(this.escapeCommandName(command), (a) => - this.onGuestNotify(command, a as IMessageArgs) - ); - } - - // Always stick in the command map so that if we switch roles, we reregister - this.commandMap[command] = { callback, thisArg }; - } - - private createBroadcastArgs(command: string, ...args: any[]): IMessageArgs { - return { args: JSON.stringify([command, ...args]) }; - } - - private translateArgs(api: vsls.LiveShare, command: string, ...args: any[]): IMessageArgs { - // Make sure to eliminate all .toJSON functions on our arguments. Otherwise they're stringified incorrectly - for (let a = 0; a <= args.length; a += 1) { - // Eliminate this on only object types (https://stackoverflow.com/questions/8511281/check-if-a-value-is-an-object-in-javascript) - if (args[a] === Object(args[a])) { - args[a].toJSON = undefined; - } - } - - // Copy our args so we don't affect callers. - const copyArgs = JSON.parse(JSON.stringify(args)); - - // Some file path args need to have their values translated to guest - // uri format for use on a guest. Try to find any file arguments - const callback = this.commandMap.hasOwnProperty(command) ? this.commandMap[command].callback : undefined; - if (callback) { - // Give the passed in args translator a chance to attempt a translation - if (this.hostArgsTranslator) { - this.hostArgsTranslator(api, command, vsls.Role.Host, copyArgs); - } - } - - // Then wrap them all up in a string. - return { args: JSON.stringify(copyArgs) }; - } - - private escapeCommandName(command: string): string { - // Replace . with $ instead. - return command.replace(/\./g, '$'); - } - - private unescapeCommandName(command: string): string { - // Turn $ back into . - return command.replace(/\$/g, '.'); - } - - private onGuestNotify = (command: string, m: IMessageArgs) => { - const unescaped = this.unescapeCommandName(command); - const args = JSON.parse(m.args) as JSONArray; - this.callCallback(unescaped, ...args); - }; - - private callCallback(command: string, ...args: any[]) { - const callback = this.getCallback(command); - if (callback) { - callback(...args); - } - } - - private getCallback(command: string): ((...args: any[]) => void) | undefined { - let callback = this.commandMap.hasOwnProperty(command) ? this.commandMap[command].callback : undefined; - if (callback) { - // Bind the this arg if necessary - const thisArg = this.commandMap[command].thisArg; - if (thisArg) { - callback = callback.bind(thisArg); - } - } - - return callback; - } - - private getApi(): Promise { - if (!this.startedPromise) { - this.startedPromise = createDeferred(); - this.startCommandServer() - .then((v) => this.startedPromise!.resolve(v)) - .catch((e) => this.startedPromise!.reject(e)); - } - - return this.startedPromise.promise; - } - - private async startCommandServer(): Promise { - const api = await this.liveShareApi.getApi(); - if (api !== null) { - api.onDidChangeSession(() => this.onChangeSession(api).ignoreErrors()); - api.onDidChangePeers(() => this.onChangePeers(api).ignoreErrors()); - await this.onChangeSession(api); - await this.onChangePeers(api); - } - return api; - } - - private async onChangeSession(api: vsls.LiveShare): Promise { - // Startup or shutdown our connection to the other side - if (api.session) { - if (this.currentRole !== api.session.role) { - this.currentRole = api.session.role; - // We're changing our role. - if (this.hostServer) { - await api.unshareService(this.name); - this.hostServer = null; - } - if (this.guestServer) { - this.guestServer = null; - } - } - - // Startup our proxy or server - if (api.session.role === vsls.Role.Host) { - this.hostServer = await api.shareService(this.name); - - // When we start the host, listen for the broadcast message - if (this.hostServer !== null) { - this.hostServer.onNotify(LiveShare.LiveShareBroadcastRequest, (a) => - this.onBroadcastRequest(api, a as IMessageArgs) - ); - } - } else if (api.session.role === vsls.Role.Guest) { - this.guestServer = await api.getSharedService(this.name); - - // When we switch to guest mode, we may have to reregister all of our commands. - this.registerGuestCommands(api); - } - } - } - - private async onChangePeers(api: vsls.LiveShare): Promise { - let newPeerCount = 0; - if (api.session) { - newPeerCount = api.peers.length; - } - if (newPeerCount !== this.currentPeerCount) { - this.peerCountChangedEmitter.fire(newPeerCount); - this.currentPeerCount = newPeerCount; - } - } - - private onBroadcastRequest = (api: vsls.LiveShare, a: IMessageArgs) => { - // This means we need to rebroadcast a request. We should also handle this request ourselves (as this means - // a guest is trying to tell everybody about a command) - if (a.args.length > 0) { - const jsonArray = JSON.parse(a.args) as JSONArray; - if (jsonArray !== null && jsonArray.length >= 2) { - const firstArg = jsonArray[0]!; // More stupid hygiene problems. - const command = firstArg !== null ? firstArg.toString() : ''; - - // Args need to be translated from guest to host - const rest = jsonArray.slice(1); - if (this.hostArgsTranslator) { - this.hostArgsTranslator(api, command, vsls.Role.Guest, rest); - } - - this.postCommand(command, ...rest).ignoreErrors(); - } - } - }; - - private registerGuestCommands(api: vsls.LiveShare) { - if (api && api.session && api.session.role === vsls.Role.Guest && this.guestServer !== null) { - const keys = Object.keys(this.commandMap); - keys.forEach((k) => { - if (this.guestServer !== null) { - // Hygiene is too dumb to recognize the if above - this.guestServer.onNotify(this.escapeCommandName(k), (a) => - this.onGuestNotify(k, a as IMessageArgs) - ); - } - }); - } - } -} diff --git a/src/client/datascience/liveshare/serviceProxy.ts b/src/client/datascience/liveshare/serviceProxy.ts deleted file mode 100644 index 31d94cb26433..000000000000 --- a/src/client/datascience/liveshare/serviceProxy.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { Event } from 'vscode'; -import * as vsls from 'vsls/vscode'; - -// tslint:disable:no-any unified-signatures -export class ServiceProxy implements vsls.SharedService { - constructor( - private realService: vsls.SharedService, - private guestsResponding: () => Promise, - private forceShutdown: () => void - ) {} - public get isServiceAvailable(): boolean { - return this.realService.isServiceAvailable; - } - public get onDidChangeIsServiceAvailable(): Event { - return this.realService.onDidChangeIsServiceAvailable; - } - - public onRequest(name: string, handler: vsls.RequestHandler): void { - return this.realService.onRequest(name, handler); - } - public onNotify(name: string, handler: vsls.NotifyHandler): void { - return this.realService.onNotify(name, handler); - } - public async notify(name: string, args: object): Promise { - if (await this.guestsResponding()) { - return this.realService.notify(name, args); - } else { - this.forceShutdown(); - } - } -} diff --git a/src/client/datascience/messages.ts b/src/client/datascience/messages.ts deleted file mode 100644 index c7e7ca14dcf8..000000000000 --- a/src/client/datascience/messages.ts +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -export enum CssMessages { - GetCssRequest = 'get_css_request', - GetCssResponse = 'get_css_response', - GetMonacoThemeRequest = 'get_monaco_theme_request', - GetMonacoThemeResponse = 'get_monaco_theme_response' -} - -export enum SharedMessages { - UpdateSettings = 'update_settings', - Started = 'started', - LocInit = 'loc_init', - StyleUpdate = 'style_update' -} - -export interface IGetCssRequest { - isDark: boolean; -} - -export interface IGetMonacoThemeRequest { - isDark: boolean; -} - -export interface IGetCssResponse { - css: string; - theme: string; - knownDark?: boolean; -} diff --git a/src/client/datascience/monacoMessages.ts b/src/client/datascience/monacoMessages.ts deleted file mode 100644 index fcd8fcd4620f..000000000000 --- a/src/client/datascience/monacoMessages.ts +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; - -export interface IGetMonacoThemeResponse { - theme: monacoEditor.editor.IStandaloneThemeData; -} diff --git a/src/client/datascience/multiplexingDebugService.ts b/src/client/datascience/multiplexingDebugService.ts deleted file mode 100644 index f4801a0d4c06..000000000000 --- a/src/client/datascience/multiplexingDebugService.ts +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import { inject, injectable, named } from 'inversify'; -import { - Breakpoint, - BreakpointsChangeEvent, - DebugAdapterDescriptorFactory, - DebugAdapterTrackerFactory, - DebugConfiguration, - DebugConfigurationProvider, - DebugConsole, - DebugSession, - DebugSessionCustomEvent, - Disposable, - Event, - EventEmitter, - WorkspaceFolder -} from 'vscode'; -import { DebugProtocol } from 'vscode-debugprotocol'; -import { ICommandManager, IDebugService } from '../common/application/types'; -import { IDisposableRegistry } from '../common/types'; -import { Identifiers } from './constants'; -import { IJupyterDebugService } from './types'; - -/** - * IJupyterDebugService that will pick the correct debugger based on if doing run by line or normal debugging. - * RunByLine will use the JupyterDebugService, Normal debugging will use the VS code debug service. - */ -@injectable() -export class MultiplexingDebugService implements IJupyterDebugService { - private lastStartedService: IDebugService | undefined; - private sessionChangedEvent: EventEmitter = new EventEmitter(); - private sessionStartedEvent: EventEmitter = new EventEmitter(); - private sessionTerminatedEvent: EventEmitter = new EventEmitter(); - private sessionCustomEvent: EventEmitter = new EventEmitter(); - - constructor( - @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, - @inject(ICommandManager) private commandManager: ICommandManager, - @inject(IDebugService) private vscodeDebugService: IDebugService, - @inject(IJupyterDebugService) - @named(Identifiers.RUN_BY_LINE_DEBUGSERVICE) - private jupyterDebugService: IJupyterDebugService - ) { - disposableRegistry.push(vscodeDebugService.onDidTerminateDebugSession(this.endedDebugSession.bind(this))); - disposableRegistry.push(jupyterDebugService.onDidTerminateDebugSession(this.endedDebugSession.bind(this))); - disposableRegistry.push(vscodeDebugService.onDidStartDebugSession(this.startedDebugSession.bind(this))); - disposableRegistry.push(jupyterDebugService.onDidStartDebugSession(this.startedDebugSession.bind(this))); - disposableRegistry.push(vscodeDebugService.onDidChangeActiveDebugSession(this.changedDebugSession.bind(this))); - disposableRegistry.push(jupyterDebugService.onDidChangeActiveDebugSession(this.changedDebugSession.bind(this))); - disposableRegistry.push(vscodeDebugService.onDidReceiveDebugSessionCustomEvent(this.gotCustomEvent.bind(this))); - disposableRegistry.push( - jupyterDebugService.onDidReceiveDebugSessionCustomEvent(this.gotCustomEvent.bind(this)) - ); - } - public get activeDebugSession(): DebugSession | undefined { - return this.activeService.activeDebugSession; - } - - public get activeDebugConsole(): DebugConsole { - return this.activeService.activeDebugConsole; - } - public get breakpoints(): Breakpoint[] { - return this.activeService.breakpoints; - } - public get onDidChangeActiveDebugSession(): Event { - return this.sessionChangedEvent.event; - } - public get onDidStartDebugSession(): Event { - return this.sessionStartedEvent.event; - } - public get onDidReceiveDebugSessionCustomEvent(): Event { - return this.sessionCustomEvent.event; - } - public get onDidTerminateDebugSession(): Event { - return this.sessionTerminatedEvent.event; - } - public get onDidChangeBreakpoints(): Event { - return this.activeService.onDidChangeBreakpoints; - } - public get onBreakpointHit(): Event { - return this.jupyterDebugService.onBreakpointHit; - } - public startRunByLine(config: DebugConfiguration): Thenable { - this.lastStartedService = this.jupyterDebugService; - return this.jupyterDebugService.startRunByLine(config); - } - public registerDebugConfigurationProvider(debugType: string, provider: DebugConfigurationProvider): Disposable { - const d1 = this.vscodeDebugService.registerDebugConfigurationProvider(debugType, provider); - const d2 = this.jupyterDebugService.registerDebugConfigurationProvider(debugType, provider); - return this.combineDisposables(d1, d2); - } - public registerDebugAdapterDescriptorFactory( - debugType: string, - factory: DebugAdapterDescriptorFactory - ): Disposable { - const d1 = this.vscodeDebugService.registerDebugAdapterDescriptorFactory(debugType, factory); - const d2 = this.jupyterDebugService.registerDebugAdapterDescriptorFactory(debugType, factory); - return this.combineDisposables(d1, d2); - } - public registerDebugAdapterTrackerFactory(debugType: string, factory: DebugAdapterTrackerFactory): Disposable { - const d1 = this.vscodeDebugService.registerDebugAdapterTrackerFactory(debugType, factory); - const d2 = this.jupyterDebugService.registerDebugAdapterTrackerFactory(debugType, factory); - return this.combineDisposables(d1, d2); - } - public startDebugging( - folder: WorkspaceFolder | undefined, - nameOrConfiguration: string | DebugConfiguration, - parentSession?: DebugSession | undefined - ): Thenable { - this.lastStartedService = this.vscodeDebugService; - return this.vscodeDebugService.startDebugging(folder, nameOrConfiguration, parentSession); - } - public addBreakpoints(breakpoints: Breakpoint[]): void { - return this.activeService.addBreakpoints(breakpoints); - } - public removeBreakpoints(breakpoints: Breakpoint[]): void { - return this.activeService.removeBreakpoints(breakpoints); - } - - public getStack(): Promise { - if (this.lastStartedService === this.jupyterDebugService) { - return this.jupyterDebugService.getStack(); - } - throw new Error('Requesting jupyter specific stack when not debugging.'); - } - public step(): Promise { - if (this.lastStartedService === this.jupyterDebugService) { - return this.jupyterDebugService.step(); - } - throw new Error('Requesting jupyter specific step when not debugging.'); - } - public continue(): Promise { - if (this.lastStartedService === this.jupyterDebugService) { - return this.jupyterDebugService.continue(); - } - throw new Error('Requesting jupyter specific step when not debugging.'); - } - public requestVariables(): Promise { - if (this.lastStartedService === this.jupyterDebugService) { - return this.jupyterDebugService.requestVariables(); - } - throw new Error('Requesting jupyter specific variables when not debugging.'); - } - - public stop(): void { - if (this.lastStartedService === this.jupyterDebugService) { - this.jupyterDebugService.stop(); - } else { - // Stop our debugging UI session, no await as we just want it stopped - this.commandManager.executeCommand('workbench.action.debug.stop'); - } - } - - private get activeService(): IDebugService { - if (this.lastStartedService) { - return this.lastStartedService; - } else { - return this.vscodeDebugService; - } - } - - private combineDisposables(d1: Disposable, d2: Disposable): Disposable { - return { - dispose: () => { - d1.dispose(); - d2.dispose(); - } - }; - } - - private endedDebugSession(session: DebugSession) { - this.sessionTerminatedEvent.fire(session); - this.lastStartedService = undefined; - } - - private startedDebugSession(session: DebugSession) { - this.sessionStartedEvent.fire(session); - } - - private changedDebugSession(session: DebugSession | undefined) { - this.sessionChangedEvent.fire(session); - } - - private gotCustomEvent(e: DebugSessionCustomEvent) { - this.sessionCustomEvent.fire(e); - } -} diff --git a/src/client/datascience/notebook/constants.ts b/src/client/datascience/notebook/constants.ts deleted file mode 100644 index 4cf273010809..000000000000 --- a/src/client/datascience/notebook/constants.ts +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -export const JupyterNotebookView = 'jupyter-notebook'; -export const JupyterNotebookRenderer = 'jupyter-notebook-renderer'; -export const RendererExtensionId = 'ms-ai-tools.notebook-renderers'; -export const RendererExtensionDownloadUri = 'https://aka.ms/NotebookRendererDownloadLink'; diff --git a/src/client/datascience/notebook/contentProvider.ts b/src/client/datascience/notebook/contentProvider.ts deleted file mode 100644 index 770eb8971463..000000000000 --- a/src/client/datascience/notebook/contentProvider.ts +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { CancellationToken, EventEmitter, Uri } from 'vscode'; -import type { - NotebookCommunication, - NotebookContentProvider as VSCNotebookContentProvider, - NotebookData, - NotebookDocument, - NotebookDocumentBackup, - NotebookDocumentBackupContext, - NotebookDocumentContentChangeEvent, - NotebookDocumentOpenContext -} from 'vscode-proposed'; -import { MARKDOWN_LANGUAGE } from '../../common/constants'; -import { DataScience } from '../../common/utils/localize'; -import { captureTelemetry, sendTelemetryEvent, setSharedProperty } from '../../telemetry'; -import { Telemetry } from '../constants'; -import { INotebookStorageProvider } from '../notebookStorage/notebookStorageProvider'; -import { VSCodeNotebookModel } from '../notebookStorage/vscNotebookModel'; -import { notebookModelToVSCNotebookData } from './helpers/helpers'; -import { NotebookEditorCompatibilitySupport } from './notebookEditorCompatibilitySupport'; -// tslint:disable-next-line: no-var-requires no-require-imports -const vscodeNotebookEnums = require('vscode') as typeof import('vscode-proposed'); -/** - * This class is responsible for reading a notebook file (ipynb or other files) and returning VS Code with the NotebookData. - * Its up to extension authors to read the files and return it in a format that VSCode understands. - * Same with the cells and cell output. - * - * Also responsible for saving of notebooks. - * When saving, VSC will provide their model and we need to take that and merge it with an existing ipynb json (if any, to preserve metadata). - */ -@injectable() -export class NotebookContentProvider implements VSCNotebookContentProvider { - private notebookChanged = new EventEmitter(); - public get onDidChangeNotebook() { - return this.notebookChanged.event; - } - constructor( - @inject(INotebookStorageProvider) private readonly notebookStorage: INotebookStorageProvider, - @inject(NotebookEditorCompatibilitySupport) - private readonly compatibilitySupport: NotebookEditorCompatibilitySupport - ) {} - public async resolveNotebook(_document: NotebookDocument, _webview: NotebookCommunication): Promise { - // Later - } - public async openNotebook(uri: Uri, openContext: NotebookDocumentOpenContext): Promise { - if (!this.compatibilitySupport.canOpenWithVSCodeNotebookEditor(uri)) { - // If not supported, return a notebook with error displayed. - // We cannot, not display a notebook. - return { - cells: [ - { - cellKind: vscodeNotebookEnums.CellKind.Markdown, - language: MARKDOWN_LANGUAGE, - source: `# ${DataScience.usingPreviewNotebookWithOtherNotebookWarning()}`, - metadata: { editable: false, runnable: false }, - outputs: [] - } - ], - languages: [], - metadata: { cellEditable: false, editable: false, runnable: false } - }; - } - // If there's no backup id, then skip loading dirty contents. - const model = await this.notebookStorage.getOrCreateModel({ - file: uri, - backupId: openContext.backupId, - isNative: true, - skipLoadingDirtyContents: openContext.backupId === undefined - }); - if (!(model instanceof VSCodeNotebookModel)) { - throw new Error('Incorrect NotebookModel, expected VSCodeNotebookModel'); - } - setSharedProperty('ds_notebookeditor', 'native'); - sendTelemetryEvent(Telemetry.CellCount, undefined, { count: model.cells.length }); - return notebookModelToVSCNotebookData(model); - } - @captureTelemetry(Telemetry.Save, undefined, true) - public async saveNotebook(document: NotebookDocument, cancellation: CancellationToken) { - const model = await this.notebookStorage.getOrCreateModel({ file: document.uri, isNative: true }); - if (cancellation.isCancellationRequested) { - return; - } - await this.notebookStorage.save(model, cancellation); - } - - public async saveNotebookAs( - targetResource: Uri, - document: NotebookDocument, - cancellation: CancellationToken - ): Promise { - const model = await this.notebookStorage.getOrCreateModel({ file: document.uri, isNative: true }); - if (!cancellation.isCancellationRequested) { - await this.notebookStorage.saveAs(model, targetResource); - } - } - public async backupNotebook( - document: NotebookDocument, - _context: NotebookDocumentBackupContext, - cancellation: CancellationToken - ): Promise { - const model = await this.notebookStorage.getOrCreateModel({ file: document.uri, isNative: true }); - const id = this.notebookStorage.generateBackupId(model); - await this.notebookStorage.backup(model, cancellation, id); - return { - id, - delete: () => this.notebookStorage.deleteBackup(model, id).ignoreErrors() - }; - } -} diff --git a/src/client/datascience/notebook/helpers/executionHelpers.ts b/src/client/datascience/notebook/helpers/executionHelpers.ts deleted file mode 100644 index 6e351ca45f2a..000000000000 --- a/src/client/datascience/notebook/helpers/executionHelpers.ts +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import type { nbformat } from '@jupyterlab/coreutils'; -import type { KernelMessage } from '@jupyterlab/services'; -import * as fastDeepEqual from 'fast-deep-equal'; -import type { NotebookCell, NotebookEditor } from '../../../../../types/vscode-proposed'; -import { createErrorOutput } from '../../../../datascience-ui/common/cellFactory'; -import { createIOutputFromCellOutputs, createVSCCellOutputsFromOutputs, translateErrorOutput } from './helpers'; -// tslint:disable-next-line: no-var-requires no-require-imports -const vscodeNotebookEnums = require('vscode') as typeof import('vscode-proposed'); - -/** - * Updates the cell in notebook model as well as the notebook document. - * Update notebook document so UI is updated accordingly. - * Notebook model is what we use to update/track changes to ipynb. - * @returns {boolean} Returns `true` if output has changed. - */ -export async function handleUpdateDisplayDataMessage( - msg: KernelMessage.IUpdateDisplayDataMsg, - editor: NotebookEditor -): Promise { - const document = editor.document; - // Find any cells that have this same display_id - for (const cell of document.cells) { - if (cell.cellKind !== vscodeNotebookEnums.CellKind.Code) { - continue; - } - let updated = false; - - const outputs = createIOutputFromCellOutputs(cell.outputs); - const changedOutputs = outputs.map((output) => { - if ( - (output.output_type === 'display_data' || output.output_type === 'execute_result') && - output.transient && - // tslint:disable-next-line: no-any - (output.transient as any).display_id === msg.content.transient.display_id - ) { - // Remember we have updated output for this cell. - updated = true; - - return { - ...output, - data: msg.content.data, - metadata: msg.content.metadata - }; - } else { - return output; - } - }); - - if (updated) { - await updateCellOutput(editor, cell, changedOutputs); - } - } -} - -/** - * Updates the VSC cell with the error output. - */ -export async function updateCellWithErrorStatus( - notebookEditor: NotebookEditor, - cell: NotebookCell, - ex: Partial -) { - await notebookEditor.edit((edit) => { - edit.replaceCellMetadata(cell.index, { - ...cell.metadata, - runState: vscodeNotebookEnums.NotebookCellRunState.Error - }); - edit.replaceCellOutput(cell.index, [translateErrorOutput(createErrorOutput(ex))]); - }); -} - -/** - * @returns {boolean} Returns `true` if execution count has changed. - */ -export async function updateCellExecutionCount( - editor: NotebookEditor, - cell: NotebookCell, - executionCount: number -): Promise { - if (cell.metadata.executionOrder !== executionCount && executionCount) { - await editor.edit((edit) => - edit.replaceCellMetadata(cell.index, { - ...cell.metadata, - executionOrder: executionCount - }) - ); - } -} - -/** - * Updates our Cell Model with the cell output. - * As we execute a cell we get output from jupyter. This code will ensure the cell is updated with the output. - */ -export async function updateCellOutput(editor: NotebookEditor, cell: NotebookCell, outputs: nbformat.IOutput[]) { - const newOutput = createVSCCellOutputsFromOutputs(outputs); - // If there was no output and still no output, then nothing to do. - if (cell.outputs.length === 0 && newOutput.length === 0) { - return; - } - // Compare outputs (at the end of the day everything is serializable). - // Hence this is a safe comparison. - if (cell.outputs.length === newOutput.length && fastDeepEqual(cell.outputs, newOutput)) { - return; - } - await editor.edit((edit) => edit.replaceCellOutput(cell.index, newOutput)); -} diff --git a/src/client/datascience/notebook/helpers/helpers.ts b/src/client/datascience/notebook/helpers/helpers.ts deleted file mode 100644 index bdd656b35d59..000000000000 --- a/src/client/datascience/notebook/helpers/helpers.ts +++ /dev/null @@ -1,745 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { nbformat } from '@jupyterlab/coreutils'; -import * as uuid from 'uuid/v4'; -import type { - CellDisplayOutput, - CellErrorOutput, - CellOutput, - NotebookCell, - NotebookCellData, - NotebookCellMetadata, - NotebookData, - NotebookDocument, - NotebookEditor -} from 'vscode-proposed'; -import { NotebookCellRunState } from '../../../../../typings/vscode-proposed'; -import { concatMultilineString, splitMultilineString } from '../../../../datascience-ui/common'; -import { MARKDOWN_LANGUAGE, PYTHON_LANGUAGE } from '../../../common/constants'; -import { traceError, traceWarning } from '../../../common/logger'; -import { sendTelemetryEvent } from '../../../telemetry'; -import { Telemetry } from '../../constants'; -import { CellState, ICell, INotebookModel } from '../../types'; -import { JupyterNotebookView } from '../constants'; -// tslint:disable-next-line: no-var-requires no-require-imports -const vscodeNotebookEnums = require('vscode') as typeof import('vscode-proposed'); -// tslint:disable-next-line: no-require-imports -import { KernelMessage } from '@jupyterlab/services'; -// tslint:disable-next-line: no-require-imports -import cloneDeep = require('lodash/cloneDeep'); -import { isUntitledFile } from '../../../common/utils/misc'; -import { KernelConnectionMetadata } from '../../jupyter/kernels/types'; -import { updateNotebookMetadata } from '../../notebookStorage/baseModel'; -import { VSCodeNotebookModel } from '../../notebookStorage/vscNotebookModel'; - -// This is the custom type we are adding into nbformat.IBaseCellMetadata -export interface IBaseCellVSCodeMetadata { - end_execution_time?: string; - start_execution_time?: string; -} - -/** - * Whether this is a Notebook we created/manage/use. - * Remember, there could be other notebooks such as GitHub Issues nb by VS Code. - */ -export function isJupyterNotebook(document: NotebookDocument): boolean; -// tslint:disable-next-line: unified-signatures -export function isJupyterNotebook(viewType: string): boolean; -export function isJupyterNotebook(option: NotebookDocument | string) { - if (typeof option === 'string') { - return option === JupyterNotebookView; - } else { - return option.viewType === JupyterNotebookView; - } -} - -const kernelInformationForNotebooks = new WeakMap< - NotebookDocument, - { metadata?: KernelConnectionMetadata | undefined; kernelInfo?: KernelMessage.IInfoReplyMsg['content'] } ->(); - -export function getNotebookMetadata(document: NotebookDocument): nbformat.INotebookMetadata | undefined { - // tslint:disable-next-line: no-any - let notebookContent: Partial = document.metadata.custom as any; - - // If language isn't specified in the metadata, at least specify that - if (!notebookContent?.metadata?.language_info?.name) { - const content = notebookContent || {}; - const metadata = content.metadata || { orig_nbformat: 3, language_info: {} }; - const language_info = { ...metadata.language_info, name: document.languages[0] }; - // Fix nyc compiler not working. - // tslint:disable-next-line: no-any - notebookContent = { ...content, metadata: { ...metadata, language_info } } as any; - } - notebookContent = cloneDeep(notebookContent); - const data = kernelInformationForNotebooks.get(document); - if (data && data.metadata) { - updateNotebookMetadata(notebookContent.metadata, data.metadata, data.kernelInfo); - } - - return notebookContent.metadata; -} - -/** - * No need to update the notebook metadata just yet. - * When users open a blank notebook and a kernel is auto selected, document is marked as dirty. Hence as soon as you create a blank notebook it is dr ity. - * Similarly, if you open an existing notebook, it is marked as dirty. - * - * Solution: Store the metadata in some place, when saving, take the metadata & store in the file. - */ -export function updateKernelInNotebookMetadata( - document: NotebookDocument, - kernelConnection: KernelConnectionMetadata | undefined -) { - const data = { ...(kernelInformationForNotebooks.get(document) || {}) }; - data.metadata = kernelConnection; - kernelInformationForNotebooks.set(document, data); -} -export function updateKernelInfoInNotebookMetadata( - document: NotebookDocument, - kernelInfo: KernelMessage.IInfoReplyMsg['content'] -) { - if (kernelInformationForNotebooks.get(document)?.kernelInfo === kernelInfo) { - return; - } - const data = { ...(kernelInformationForNotebooks.get(document) || {}) }; - data.kernelInfo = kernelInfo; - kernelInformationForNotebooks.set(document, data); -} -/** - * Converts a NotebookModel into VSCode friendly format. - */ -export function notebookModelToVSCNotebookData(model: VSCodeNotebookModel): NotebookData { - const cells = model.cells - .map(createVSCNotebookCellDataFromCell.bind(undefined, model)) - .filter((item) => !!item) - .map((item) => item!); - - const defaultLanguage = getDefaultCodeLanguage(model); - if (cells.length === 0 && isUntitledFile(model.file)) { - cells.push({ - cellKind: vscodeNotebookEnums.CellKind.Code, - language: defaultLanguage, - metadata: {}, - outputs: [], - source: '' - }); - } - return { - cells, - languages: ['*'], - metadata: { - custom: model.notebookContentWithoutCells, - cellEditable: model.isTrusted, - cellRunnable: model.isTrusted, - editable: model.isTrusted, - cellHasExecutionOrder: true, - runnable: model.isTrusted, - displayOrder: [ - 'application/vnd.*', - 'application/vdom.*', - 'application/geo+json', - 'application/x-nteract-model-debug+json', - 'text/html', - 'application/javascript', - 'image/gif', - 'text/latex', - 'text/markdown', - 'image/svg+xml', - 'image/png', - 'image/jpeg', - 'application/json', - 'text/plain' - ] - } - }; -} -export function createCellFromVSCNotebookCell(vscCell: NotebookCell, model: INotebookModel): ICell { - let cell: ICell; - if (vscCell.cellKind === vscodeNotebookEnums.CellKind.Markdown) { - const data = createMarkdownCellFromVSCNotebookCell(vscCell); - cell = { - data, - file: model.file.toString(), - id: uuid(), - line: 0, - state: CellState.init - }; - } else if (vscCell.language === 'raw') { - const data = createRawCellFromVSCNotebookCell(vscCell); - cell = { - data, - file: model.file.toString(), - id: uuid(), - line: 0, - state: CellState.init - }; - } else { - const data = createCodeCellFromVSCNotebookCell(vscCell); - cell = { - data, - file: model.file.toString(), - id: uuid(), - line: 0, - state: CellState.init - }; - } - // Delete the `metadata.custom.vscode` property we added. - if ('vscode' in cell.data.metadata) { - const metadata = { ...cell.data.metadata }; - delete metadata.vscode; - cell.data.metadata = metadata; - } - return cell; -} - -/** - * Identifies Jupyter Cell metadata that are to be stored in VSCode Cells. - * This is used to facilitate: - * 1. When a user copies and pastes a cell, then the corresponding metadata is also copied across. - * 2. Diffing (VSC knows about metadata & stuff that contributes changes to a cell). - */ -export function getCustomNotebookCellMetadata(cell: ICell): Record { - // We put this only for VSC to display in diff view. - // Else we don't use this. - const propertiesToClone = ['metadata', 'attachments']; - // tslint:disable-next-line: no-any - const custom: Record = {}; - propertiesToClone.forEach((propertyToClone) => { - if (cell.data[propertyToClone]) { - custom[propertyToClone] = cloneDeep(cell.data[propertyToClone]); - } - }); - return custom; -} - -export function getDefaultCodeLanguage(model: INotebookModel) { - return model.metadata?.language_info?.name && - model.metadata?.language_info?.name.toLowerCase() !== PYTHON_LANGUAGE.toLowerCase() - ? model.metadata?.language_info?.name - : PYTHON_LANGUAGE; -} -function createRawCellFromVSCNotebookCell(cell: NotebookCell): nbformat.IRawCell { - const rawCell: nbformat.IRawCell = { - cell_type: 'raw', - source: splitMultilineString(cell.document.getText()), - metadata: cell.metadata.custom?.metadata || {} - }; - if (cell.metadata.custom?.attachments) { - rawCell.attachments = cell.metadata.custom?.attachments; - } - return rawCell; -} - -function createVSCNotebookCellDataFromRawCell(model: INotebookModel, cell: ICell): NotebookCellData { - const notebookCellMetadata: NotebookCellMetadata = { - editable: model.isTrusted, - executionOrder: undefined, - hasExecutionOrder: false, - runnable: false, - custom: getCustomNotebookCellMetadata(cell) - }; - return { - cellKind: vscodeNotebookEnums.CellKind.Code, - language: 'raw', - metadata: notebookCellMetadata, - outputs: [], - source: concatMultilineString(cell.data.source) - }; -} -function createMarkdownCellFromVSCNotebookCell(cell: NotebookCell): nbformat.IMarkdownCell { - const markdownCell: nbformat.IMarkdownCell = { - cell_type: 'markdown', - source: splitMultilineString(cell.document.getText()), - metadata: cell.metadata.custom?.metadata || {} - }; - if (cell.metadata.custom?.attachments) { - markdownCell.attachments = cell.metadata.custom?.attachments; - } - return markdownCell; -} -function createVSCNotebookCellDataFromMarkdownCell(model: INotebookModel, cell: ICell): NotebookCellData { - const notebookCellMetadata: NotebookCellMetadata = { - editable: model.isTrusted, - executionOrder: undefined, - hasExecutionOrder: false, - runnable: false, - custom: getCustomNotebookCellMetadata(cell) - }; - return { - cellKind: vscodeNotebookEnums.CellKind.Markdown, - language: MARKDOWN_LANGUAGE, - metadata: notebookCellMetadata, - source: concatMultilineString(cell.data.source), - outputs: [] - }; -} -function createVSCNotebookCellDataFromCodeCell(model: INotebookModel, cell: ICell): NotebookCellData { - // tslint:disable-next-line: no-any - const outputs = createVSCCellOutputsFromOutputs(cell.data.outputs as any); - const defaultCodeLanguage = getDefaultCodeLanguage(model); - // If we have an execution count & no errors, then success state. - // If we have an execution count & errors, then error state. - // Else idle state. - const hasErrors = outputs.some((output) => output.outputKind === vscodeNotebookEnums.CellOutputKind.Error); - const hasExecutionCount = typeof cell.data.execution_count === 'number' && cell.data.execution_count > 0; - let runState: NotebookCellRunState; - let statusMessage: string | undefined; - if (!hasExecutionCount) { - runState = vscodeNotebookEnums.NotebookCellRunState.Idle; - } else if (hasErrors) { - runState = vscodeNotebookEnums.NotebookCellRunState.Error; - // Error details are stripped from the output, get raw output. - // tslint:disable-next-line: no-any - statusMessage = getCellStatusMessageBasedOnFirstErrorOutput(cell.data.outputs as any); - } else { - runState = vscodeNotebookEnums.NotebookCellRunState.Success; - } - - const vscodeMetadata = (cell.data.metadata.vscode as unknown) as IBaseCellVSCodeMetadata | undefined; - const startExecutionTime = vscodeMetadata?.start_execution_time - ? new Date(Date.parse(vscodeMetadata.start_execution_time)).getTime() - : undefined; - const endExecutionTime = vscodeMetadata?.end_execution_time - ? new Date(Date.parse(vscodeMetadata.end_execution_time)).getTime() - : undefined; - - let runStartTime: undefined | number; - let lastRunDuration: undefined | number; - if (startExecutionTime && typeof endExecutionTime === 'number') { - runStartTime = startExecutionTime; - lastRunDuration = endExecutionTime - startExecutionTime; - } - - const notebookCellMetadata: NotebookCellMetadata = { - editable: model.isTrusted, - executionOrder: typeof cell.data.execution_count === 'number' ? cell.data.execution_count : undefined, - hasExecutionOrder: true, - runState, - runnable: model.isTrusted, - statusMessage, - runStartTime, - lastRunDuration, - custom: getCustomNotebookCellMetadata(cell) - }; - - // If not trusted, then clear the output in VSC Cell. - // At this point we have the original output in the ICell. - if (!model.isTrusted) { - while (outputs.length) { - outputs.shift(); - } - } - return { - cellKind: vscodeNotebookEnums.CellKind.Code, - language: defaultCodeLanguage, - metadata: notebookCellMetadata, - source: concatMultilineString(cell.data.source), - outputs - }; -} - -export function createIOutputFromCellOutputs(cellOutputs: CellOutput[]): nbformat.IOutput[] { - return cellOutputs - .map((output) => { - switch (output.outputKind) { - case vscodeNotebookEnums.CellOutputKind.Error: - return translateCellErrorOutput(output); - case vscodeNotebookEnums.CellOutputKind.Rich: - return translateCellDisplayOutput(output); - case vscodeNotebookEnums.CellOutputKind.Text: - // We do not generate text output. - return; - default: - return; - } - }) - .filter((output) => !!output) - .map((output) => output!!); -} - -export async function clearCellForExecution(editor: NotebookEditor, cell: NotebookCell) { - await editor.edit((edit) => { - edit.replaceCellMetadata(cell.index, { - ...cell.metadata, - statusMessage: undefined, - executionOrder: undefined, - lastRunDuration: undefined, - runStartTime: undefined - }); - edit.replaceCellOutput(cell.index, []); - }); - await updateCellExecutionTimes(editor, cell); -} - -/** - * Store execution start and end times. - * Stored as ISO for portability. - */ -export async function updateCellExecutionTimes( - editor: NotebookEditor, - cell: NotebookCell, - times?: { startTime?: number; lastRunDuration?: number } -) { - if (!times || !times.lastRunDuration || !times.startTime) { - // Based on feedback from VSC, its best to clone these objects when updating them. - const cellMetadata = cloneDeep(cell.metadata); - let updated = false; - if (cellMetadata.custom?.metadata?.vscode?.start_execution_time) { - delete cellMetadata.custom.metadata.vscode.start_execution_time; - updated = true; - } - if (cellMetadata.custom?.metadata?.vscode?.end_execution_time) { - delete cellMetadata.custom.metadata.vscode.end_execution_time; - updated = true; - } - if (updated) { - await editor.edit((edit) => - edit.replaceCellMetadata(cell.index, { - ...cellMetadata - }) - ); - } - return; - } - - const startTimeISO = new Date(times.startTime).toISOString(); - const endTimeISO = new Date(times.startTime + times.lastRunDuration).toISOString(); - // Based on feedback from VSC, its best to clone these objects when updating them. - const customMetadata = cloneDeep(cell.metadata.custom || {}); - customMetadata.metadata = customMetadata.metadata || {}; - customMetadata.metadata.vscode = customMetadata.metadata.vscode || {}; - // We store it in the metadata so we can display this when user opens a notebook again. - customMetadata.metadata.vscode.end_execution_time = endTimeISO; - customMetadata.metadata.vscode.start_execution_time = startTimeISO; - const lastRunDuration = times.lastRunDuration ?? cell.metadata.lastRunDuration; - await editor.edit((edit) => - edit.replaceCellMetadata(cell.index, { - ...cell.metadata, - custom: customMetadata, - lastRunDuration - }) - ); -} - -function createCodeCellFromVSCNotebookCell(cell: NotebookCell): nbformat.ICodeCell { - const metadata = cell.metadata.custom?.metadata || {}; - return { - cell_type: 'code', - execution_count: cell.metadata.executionOrder ?? null, - source: splitMultilineString(cell.document.getText()), - outputs: createIOutputFromCellOutputs(cell.outputs), - metadata - }; -} -export function createVSCNotebookCellDataFromCell(model: INotebookModel, cell: ICell): NotebookCellData | undefined { - switch (cell.data.cell_type) { - case 'raw': { - return createVSCNotebookCellDataFromRawCell(model, cell); - } - case 'markdown': { - return createVSCNotebookCellDataFromMarkdownCell(model, cell); - } - case 'code': { - return createVSCNotebookCellDataFromCodeCell(model, cell); - } - default: { - traceError(`Conversion of Cell into VS Code NotebookCell not supported ${cell.data.cell_type}`); - } - } -} - -export function createVSCCellOutputsFromOutputs(outputs?: nbformat.IOutput[]): CellOutput[] { - const cellOutputs: nbformat.IOutput[] = Array.isArray(outputs) ? (outputs as []) : []; - return cellOutputs.map(cellOutputToVSCCellOutput); -} -const cellOutputMappers = new Map< - nbformat.OutputType, - (output: nbformat.IOutput, outputType: nbformat.OutputType) => CellOutput ->(); -// tslint:disable-next-line: no-any -cellOutputMappers.set('display_data', translateDisplayDataOutput as any); -// tslint:disable-next-line: no-any -cellOutputMappers.set('error', translateErrorOutput as any); -// tslint:disable-next-line: no-any -cellOutputMappers.set('execute_result', translateDisplayDataOutput as any); -// tslint:disable-next-line: no-any -cellOutputMappers.set('stream', translateStreamOutput as any); -// tslint:disable-next-line: no-any -cellOutputMappers.set('update_display_data', translateDisplayDataOutput as any); -export function cellOutputToVSCCellOutput(output: nbformat.IOutput): CellOutput { - const fn = cellOutputMappers.get(output.output_type as nbformat.OutputType); - let result: CellOutput; - if (fn) { - result = fn(output, (output.output_type as unknown) as nbformat.OutputType); - } else { - traceWarning(`Unable to translate cell from ${output.output_type} to NotebookCellData for VS Code.`); - result = { - outputKind: vscodeNotebookEnums.CellOutputKind.Rich, - // tslint:disable-next-line: no-any - data: output.data as any, - metadata: { custom: { vscode: { outputType: output.output_type } } } - }; - } - - // Add on transient data if we have any. This should be removed by our save functions elsewhere. - if ( - output.transient && - result && - result.outputKind === vscodeNotebookEnums.CellOutputKind.Rich && - result.metadata - ) { - // tslint:disable-next-line: no-any - result.metadata.custom = { ...result.metadata.custom, transient: output.transient }; - } - return result; -} - -export function vscCellOutputToCellOutput(output: CellOutput): nbformat.IOutput | undefined { - switch (output.outputKind) { - case vscodeNotebookEnums.CellOutputKind.Error: { - return translateCellErrorOutput(output); - } - case vscodeNotebookEnums.CellOutputKind.Rich: { - return translateCellDisplayOutput(output); - } - case vscodeNotebookEnums.CellOutputKind.Text: { - // We do not return such output. - return; - } - default: { - return; - } - } -} - -/** - * Converts a Jupyter display cell output into a VSCode cell output format. - * Handles sizing, adding backgrounds to images and the like. - * E.g. Jupyter cell output contains metadata to add backgrounds to images, here we generate the necessary HTML. - * - * @export - * @param {nbformat.IDisplayData} output - * @returns {(CellDisplayOutput | undefined)} - */ -function translateDisplayDataOutput( - output: nbformat.IDisplayData | nbformat.IDisplayUpdate | nbformat.IExecuteResult, - outputType: nbformat.OutputType -): CellDisplayOutput | undefined { - const data = { ...output.data }; - // tslint:disable-next-line: no-any - const metadata = output.metadata ? ({ custom: output.metadata } as any) : { custom: {} }; - metadata.custom.vscode = { outputType }; - if (output.execution_count) { - metadata.execution_order = output.execution_count; - } - return { - outputKind: vscodeNotebookEnums.CellOutputKind.Rich, - data, - metadata // Used be renderers & VS Code for diffing (it knows what has changed). - }; -} - -function translateStreamOutput(output: nbformat.IStream, outputType: nbformat.OutputType): CellDisplayOutput { - // Do not return as `CellOutputKind.Text`. VSC will not translate ascii output correctly. - // Instead format the output as rich. - return { - outputKind: vscodeNotebookEnums.CellOutputKind.Rich, - data: { - ['text/plain']: concatMultilineString(output.text, true) - }, - metadata: { - custom: { vscode: { outputType, name: output.name } } - } - }; -} - -// tslint:disable-next-line: no-any -function getSanitizedCellMetadata(metadata?: { [key: string]: any }) { - const cloned = { ...metadata }; - if ('vscode' in cloned) { - delete cloned.vscode; - } - return cloned; -} - -type JupyterOutput = - | nbformat.IUnrecognizedOutput - | nbformat.IExecuteResult - | nbformat.IDisplayData - | nbformat.IStream - | nbformat.IError; - -function translateCellDisplayOutput(output: CellDisplayOutput): JupyterOutput { - const outputType: nbformat.OutputType = output.metadata?.custom?.vscode?.outputType; - let result: JupyterOutput; - switch (outputType) { - case 'stream': - { - result = { - output_type: 'stream', - name: output.metadata?.custom?.vscode?.name, - text: splitMultilineString(output.data['text/plain']) - }; - } - break; - case 'display_data': - { - const metadata = getSanitizedCellMetadata(output.metadata?.custom); - result = { - output_type: 'display_data', - data: output.data, - metadata - }; - } - break; - case 'execute_result': - { - const metadata = getSanitizedCellMetadata(output.metadata?.custom); - result = { - output_type: 'execute_result', - data: output.data, - metadata, - execution_count: output.metadata?.custom?.vscode?.execution_count - }; - } - break; - case 'update_display_data': - { - const metadata = getSanitizedCellMetadata(output.metadata?.custom); - result = { - output_type: 'update_display_data', - data: output.data, - metadata - }; - } - break; - default: - { - sendTelemetryEvent(Telemetry.VSCNotebookCellTranslationFailed, undefined, { - isErrorOutput: outputType === 'error' - }); - const metadata = getSanitizedCellMetadata(output.metadata?.custom); - const unknownOutput: nbformat.IUnrecognizedOutput = { output_type: outputType }; - if (Object.keys(metadata).length > 0) { - unknownOutput.metadata = metadata; - } - if (Object.keys(output.data).length > 0) { - unknownOutput.data = output.data; - } - result = unknownOutput; - } - break; - } - - // Account for transient data as well - if (result && output.metadata && output.metadata.custom?.transient) { - result.transient = { ...output.metadata.custom?.transient }; - } - return result; -} - -/** - * We will display the error message in the status of the cell. - * The `ename` & `evalue` is displayed at the top of the output by VS Code. - * As we're displaying the error in the statusbar, we don't want this dup error in output. - * Hence remove this. - */ -export function translateErrorOutput(output: nbformat.IError): CellErrorOutput { - return { - ename: output.ename, - evalue: output.evalue, - outputKind: vscodeNotebookEnums.CellOutputKind.Error, - traceback: output.traceback - }; -} -export function translateCellErrorOutput(output: CellErrorOutput): nbformat.IError { - return { - output_type: 'error', - ename: output.ename, - evalue: output.evalue, - traceback: output.traceback - }; -} - -export function getCellStatusMessageBasedOnFirstErrorOutput(outputs?: nbformat.IOutput[]): string { - if (!Array.isArray(outputs)) { - return ''; - } - const errorOutput = (outputs.find((output) => output.output_type === 'error') as unknown) as - | nbformat.IError - | undefined; - if (!errorOutput) { - return ''; - } - return `${errorOutput.ename}${errorOutput.evalue ? ': ' : ''}${errorOutput.evalue}`; -} -export function getCellStatusMessageBasedOnFirstCellErrorOutput(outputs?: CellOutput[]): string { - if (!Array.isArray(outputs)) { - return ''; - } - const errorOutput = outputs.find((output) => output.outputKind === vscodeNotebookEnums.CellOutputKind.Error) as - | CellErrorOutput - | undefined; - if (!errorOutput) { - return ''; - } - return `${errorOutput.ename}${errorOutput.evalue ? ': ' : ''}${errorOutput.evalue}`; -} - -/** - * Updates a notebook document as a result of trusting it. - */ -export async function updateVSCNotebookAfterTrustingNotebook( - editor: NotebookEditor, - document: NotebookDocument, - originalCells: ICell[] -) { - const areAllCellsEditableAndRunnable = document.cells.every((cell) => { - if (cell.cellKind === vscodeNotebookEnums.CellKind.Markdown) { - return cell.metadata.editable; - } else { - return cell.metadata.editable && cell.metadata.runnable; - } - }); - const isDocumentEditableAndRunnable = - document.metadata.cellEditable && - document.metadata.cellRunnable && - document.metadata.editable && - document.metadata.runnable; - - // If already trusted, then nothing to do. - if (isDocumentEditableAndRunnable && areAllCellsEditableAndRunnable) { - return; - } - - await editor.edit((edit) => { - edit.replaceMetadata({ - ...document.metadata, - cellEditable: true, - cellRunnable: true, - editable: true, - runnable: true - }); - document.cells.forEach((cell, index) => { - if (cell.cellKind === vscodeNotebookEnums.CellKind.Markdown) { - edit.replaceCellMetadata(index, { ...cell.metadata, editable: true }); - } else { - edit.replaceCellMetadata(index, { - ...cell.metadata, - editable: true, - runnable: true - }); - // Restore the output once we trust the notebook. - edit.replaceCellOutput( - index, - // tslint:disable-next-line: no-any - createVSCCellOutputsFromOutputs(originalCells[index].data.outputs as any) - ); - } - }); - }); -} diff --git a/src/client/datascience/notebook/helpers/multiCancellationToken.ts b/src/client/datascience/notebook/helpers/multiCancellationToken.ts deleted file mode 100644 index 172dff5489fc..000000000000 --- a/src/client/datascience/notebook/helpers/multiCancellationToken.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { CancellationToken, EventEmitter } from 'vscode'; - -/** - * Cancellation token source that can be cancelled multiple times. - */ -export class MultiCancellationTokenSource { - /** - * The cancellation token of this source. - */ - public readonly token: CancellationToken; - private readonly eventEmitter = new EventEmitter(); - constructor() { - this.token = { - isCancellationRequested: false, - onCancellationRequested: this.eventEmitter.event.bind(this.eventEmitter) - }; - } - public cancel(): void { - this.token.isCancellationRequested = true; - this.eventEmitter.fire(); - } - - /** - * Dispose object and free resources. - */ - public dispose(): void { - this.eventEmitter.dispose(); - } -} diff --git a/src/client/datascience/notebook/integration.ts b/src/client/datascience/notebook/integration.ts deleted file mode 100644 index 15ca58d6a664..000000000000 --- a/src/client/datascience/notebook/integration.ts +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { inject, injectable } from 'inversify'; -import { ConfigurationTarget } from 'vscode'; -import { NotebookContentProvider as VSCNotebookContentProvider } from '../../../../types/vscode-proposed'; -import { IExtensionSingleActivationService } from '../../activation/types'; -import { - IApplicationEnvironment, - IApplicationShell, - IVSCodeNotebook, - IWorkspaceService -} from '../../common/application/types'; -import { NotebookEditorSupport } from '../../common/experiments/groups'; -import { traceError } from '../../common/logger'; -import { IDisposableRegistry, IExperimentsManager, IExtensionContext } from '../../common/types'; -import { DataScience } from '../../common/utils/localize'; -import { noop } from '../../common/utils/misc'; -import { JupyterNotebookView } from './constants'; -import { isJupyterNotebook } from './helpers/helpers'; -import { VSCodeKernelPickerProvider } from './kernelProvider'; -import { INotebookContentProvider } from './types'; - -const EditorAssociationUpdatedKey = 'EditorAssociationUpdatedToUseNotebooks'; - -/** - * This class basically registers the necessary providers and the like with VSC. - * I.e. this is where we integrate our stuff with VS Code via their extension endpoints. - */ - -@injectable() -export class NotebookIntegration implements IExtensionSingleActivationService { - constructor( - @inject(IVSCodeNotebook) private readonly vscNotebook: IVSCodeNotebook, - @inject(IExperimentsManager) private readonly experiment: IExperimentsManager, - @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry, - @inject(INotebookContentProvider) private readonly notebookContentProvider: VSCNotebookContentProvider, - @inject(VSCodeKernelPickerProvider) private readonly kernelProvider: VSCodeKernelPickerProvider, - @inject(IApplicationEnvironment) private readonly env: IApplicationEnvironment, - @inject(IApplicationShell) private readonly shell: IApplicationShell, - @inject(IWorkspaceService) private readonly workspace: IWorkspaceService, - @inject(IExtensionContext) private readonly extensionContext: IExtensionContext - ) {} - public async activate(): Promise { - // This condition is temporary. - // If user belongs to the experiment, then make the necessary changes to package.json. - // Once the API is final, we won't need to modify the package.json. - if (this.experiment.inExperiment(NotebookEditorSupport.nativeNotebookExperiment)) { - await this.enableNotebooks(); - } else { - // Possible user was in experiment, then they opted out. In this case we need to revert the changes made to the settings file. - // Again, this is temporary code. - await this.disableNotebooks(); - } - if (this.env.channel !== 'insiders') { - return; - } - try { - this.disposables.push( - this.vscNotebook.registerNotebookContentProvider(JupyterNotebookView, this.notebookContentProvider, { - transientOutputs: false, - transientMetadata: { - breakpointMargin: true, - editable: true, - hasExecutionOrder: true, - inputCollapsed: true, - lastRunDuration: true, - outputCollapsed: true, - runStartTime: true, - runnable: true, - executionOrder: false, - custom: false, - runState: false, - statusMessage: false - } - }) - ); - this.disposables.push( - this.vscNotebook.registerNotebookKernelProvider( - { filenamePattern: '**/*.ipynb', viewType: JupyterNotebookView }, - this.kernelProvider - ) - ); - } catch (ex) { - // If something goes wrong, and we're not in Insiders & not using the NativeEditor experiment, then swallow errors. - traceError('Failed to register VS Code Notebook API', ex); - if (this.experiment.inExperiment(NotebookEditorSupport.nativeNotebookExperiment)) { - throw ex; - } - } - } - private async enableNotebooks() { - if (this.env.channel === 'stable') { - this.shell.showErrorMessage(DataScience.previewNotebookOnlySupportedInVSCInsiders()).then(noop, noop); - return; - } - - await this.enableDisableEditorAssociation(true); - } - private async enableDisableEditorAssociation(enable: boolean) { - // This code is temporary. - const settings = this.workspace.getConfiguration('workbench', undefined); - const editorAssociations = settings.get('editorAssociations') as { - viewType: string; - filenamePattern: string; - }[]; - - // Update the settings. - if ( - enable && - (!Array.isArray(editorAssociations) || - editorAssociations.length === 0 || - !editorAssociations.find((item) => isJupyterNotebook(item.viewType))) - ) { - editorAssociations.push({ - viewType: 'jupyter-notebook', - filenamePattern: '*.ipynb' - }); - await Promise.all([ - this.extensionContext.globalState.update(EditorAssociationUpdatedKey, true), - settings.update('editorAssociations', editorAssociations, ConfigurationTarget.Global) - ]); - } - - // Revert the settings. - if ( - !enable && - this.extensionContext.globalState.get(EditorAssociationUpdatedKey, false) && - Array.isArray(editorAssociations) && - editorAssociations.find((item) => isJupyterNotebook(item.viewType)) - ) { - const updatedSettings = editorAssociations.filter((item) => !isJupyterNotebook(item.viewType)); - await Promise.all([ - this.extensionContext.globalState.update(EditorAssociationUpdatedKey, false), - settings.update('editorAssociations', updatedSettings, ConfigurationTarget.Global) - ]); - } - } - private async disableNotebooks() { - if (this.env.channel === 'stable') { - return; - } - // If we never modified the settings, then nothing to do. - if (!this.extensionContext.globalState.get(EditorAssociationUpdatedKey, false)) { - return; - } - await this.enableDisableEditorAssociation(false); - } -} diff --git a/src/client/datascience/notebook/interpreterStatusBarVisbility.ts b/src/client/datascience/notebook/interpreterStatusBarVisbility.ts deleted file mode 100644 index 44b5695e7ddc..000000000000 --- a/src/client/datascience/notebook/interpreterStatusBarVisbility.ts +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -import { Event, EventEmitter } from 'vscode'; -import { IVSCodeNotebook } from '../../common/application/types'; -import { IDisposableRegistry } from '../../common/types'; -import { IInterpreterStatusbarVisibilityFilter } from '../../interpreter/contracts'; -import { isJupyterNotebook } from './helpers/helpers'; - -@injectable() -export class InterpreterStatusBarVisibility implements IInterpreterStatusbarVisibilityFilter { - private _changed = new EventEmitter(); - - constructor( - @inject(IVSCodeNotebook) private readonly vscNotebook: IVSCodeNotebook, - @inject(IDisposableRegistry) disposables: IDisposableRegistry - ) { - vscNotebook.onDidChangeActiveNotebookEditor( - () => { - this._changed.fire(); - }, - this, - disposables - ); - } - public get changed(): Event { - return this._changed.event; - } - public get hidden() { - return this.vscNotebook.activeNotebookEditor && - isJupyterNotebook(this.vscNotebook.activeNotebookEditor.document) - ? true - : false; - } -} diff --git a/src/client/datascience/notebook/kernelProvider.ts b/src/client/datascience/notebook/kernelProvider.ts deleted file mode 100644 index fe20a3789d08..000000000000 --- a/src/client/datascience/notebook/kernelProvider.ts +++ /dev/null @@ -1,314 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -// tslint:disable-next-line: no-require-imports -import cloneDeep = require('lodash/cloneDeep'); -import { CancellationToken, Event, EventEmitter, Uri } from 'vscode'; -import { - NotebookCell, - NotebookDocument, - NotebookKernel as VSCNotebookKernel, - NotebookKernelProvider -} from '../../../../types/vscode-proposed'; -import { IVSCodeNotebook } from '../../common/application/types'; -import { IDisposableRegistry } from '../../common/types'; -import { noop } from '../../common/utils/misc'; -import { IInterpreterService } from '../../interpreter/contracts'; -import { areKernelConnectionsEqual } from '../jupyter/kernels/helpers'; -import { KernelSelectionProvider } from '../jupyter/kernels/kernelSelections'; -import { KernelSelector } from '../jupyter/kernels/kernelSelector'; -import { KernelSwitcher } from '../jupyter/kernels/kernelSwitcher'; -import { getKernelConnectionId, IKernel, IKernelProvider, KernelConnectionMetadata } from '../jupyter/kernels/types'; -import { INotebookStorageProvider } from '../notebookStorage/notebookStorageProvider'; -import { INotebook, INotebookProvider } from '../types'; -import { - getNotebookMetadata, - isJupyterNotebook, - updateKernelInfoInNotebookMetadata, - updateKernelInNotebookMetadata -} from './helpers/helpers'; - -class VSCodeNotebookKernelMetadata implements VSCNotebookKernel { - get preloads(): Uri[] { - return []; - } - get id() { - return getKernelConnectionId(this.selection); - } - constructor( - public readonly label: string, - public readonly description: string, - public readonly detail: string, - public readonly selection: Readonly, - public readonly isPreferred: boolean, - private readonly kernelProvider: IKernelProvider, - private readonly notebook: IVSCodeNotebook - ) {} - public executeCell(doc: NotebookDocument, cell: NotebookCell) { - const kernel = this.kernelProvider.getOrCreate(cell.notebook.uri, { metadata: this.selection }); - if (kernel) { - this.updateKernelInfoInNotebookWhenAvailable(kernel, doc); - kernel.executeCell(cell).catch(noop); - } - } - public executeAllCells(document: NotebookDocument) { - const kernel = this.kernelProvider.getOrCreate(document.uri, { metadata: this.selection }); - if (kernel) { - this.updateKernelInfoInNotebookWhenAvailable(kernel, document); - kernel.executeAllCells(document).catch(noop); - } - } - public cancelCellExecution(_: NotebookDocument, cell: NotebookCell) { - this.kernelProvider.get(cell.notebook.uri)?.interrupt(); // NOSONAR - } - public cancelAllCellsExecution(document: NotebookDocument) { - this.kernelProvider.get(document.uri)?.interrupt(); // NOSONAR - } - private updateKernelInfoInNotebookWhenAvailable(kernel: IKernel, doc: NotebookDocument) { - const disposable = kernel.onStatusChanged(() => { - if (!kernel.info) { - return; - } - const editor = this.notebook.notebookEditors.find((item) => item.document === doc); - if (!editor || editor.kernel?.id !== this.id) { - return; - } - disposable.dispose(); - updateKernelInfoInNotebookMetadata(doc, kernel.info); - }); - } -} - -@injectable() -export class VSCodeKernelPickerProvider implements NotebookKernelProvider { - public get onDidChangeKernels(): Event { - return this._onDidChangeKernels.event; - } - private readonly _onDidChangeKernels = new EventEmitter(); - private notebookKernelChangeHandled = new WeakSet(); - constructor( - @inject(KernelSelectionProvider) private readonly kernelSelectionProvider: KernelSelectionProvider, - @inject(KernelSelector) private readonly kernelSelector: KernelSelector, - @inject(IKernelProvider) private readonly kernelProvider: IKernelProvider, - @inject(IVSCodeNotebook) private readonly notebook: IVSCodeNotebook, - @inject(INotebookStorageProvider) private readonly storageProvider: INotebookStorageProvider, - @inject(INotebookProvider) private readonly notebookProvider: INotebookProvider, - @inject(KernelSwitcher) private readonly kernelSwitcher: KernelSwitcher, - @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry, - @inject(IInterpreterService) private readonly interpreterService: IInterpreterService - ) { - this.kernelSelectionProvider.onDidChangeSelections( - (e) => { - if (e) { - const doc = this.notebook.notebookDocuments.find((d) => d.uri.fsPath === e.fsPath); - if (doc) { - return this._onDidChangeKernels.fire(doc); - } - } - this._onDidChangeKernels.fire(undefined); - }, - this, - disposables - ); - this.notebook.onDidChangeActiveNotebookKernel(this.onDidChangeActiveNotebookKernel, this, disposables); - } - public async provideKernels( - document: NotebookDocument, - token: CancellationToken - ): Promise { - const [preferredKernel, kernels, activeInterpreter] = await Promise.all([ - this.getPreferredKernel(document, token), - this.kernelSelectionProvider.getKernelSelectionsForLocalSession(document.uri, 'raw', undefined, token), - this.interpreterService.getActiveInterpreter(document.uri) - ]); - if (token.isCancellationRequested) { - return []; - } - - // Default the interpreter to the local interpreter (if none is provided). - const withInterpreter = kernels.map((kernel) => { - const selection = cloneDeep(kernel.selection); // Always clone, so we can make changes to this. - selection.interpreter = selection.interpreter || activeInterpreter; - return { ...kernel, selection }; - }); - - // Turn this into our preferred list. - const mapped = withInterpreter.map((kernel) => { - return new VSCodeNotebookKernelMetadata( - kernel.label, - kernel.description || '', - kernel.detail || '', - kernel.selection, - areKernelConnectionsEqual(kernel.selection, preferredKernel), - this.kernelProvider, - this.notebook - ); - }); - - // If no preferred kernel set but we have a language, use that to set preferred instead. - if (!mapped.find((v) => v.isPreferred)) { - const languages = document.cells.map((c) => c.language); - // Find the first that matches on language - const indexOfKernelMatchingDocumentLanguage = kernels.findIndex((k) => - languages.find((l) => l === k.selection.kernelSpec?.language) - ); - - // If we have a preferred kernel, then add that to the list, & put it on top of the list. - const preferredKernelMetadata = this.createNotebookKernelMetadataFromPreferredKernel(preferredKernel); - if (preferredKernelMetadata) { - mapped.splice(0, 0, preferredKernelMetadata); - } else if (indexOfKernelMatchingDocumentLanguage >= 0) { - const kernel = kernels[indexOfKernelMatchingDocumentLanguage]; - mapped.splice( - indexOfKernelMatchingDocumentLanguage, - 1, - new VSCodeNotebookKernelMetadata( - kernel.label, - kernel.description || '', - kernel.detail || '', - kernel.selection, - true, - this.kernelProvider, - this.notebook - ) - ); - } - } - mapped.sort((a, b) => { - if (a.label > b.label) { - return 1; - } else if (a.label === b.label) { - return 0; - } else { - return -1; - } - }); - return mapped; - } - private createNotebookKernelMetadataFromPreferredKernel( - preferredKernel?: KernelConnectionMetadata - ): VSCodeNotebookKernelMetadata | undefined { - if (!preferredKernel) { - return; - } else if (preferredKernel.kind === 'startUsingDefaultKernel') { - return; - } else if (preferredKernel.kind === 'startUsingPythonInterpreter') { - return new VSCodeNotebookKernelMetadata( - preferredKernel.interpreter.displayName || preferredKernel.interpreter.path, - '', - preferredKernel.interpreter.path, - preferredKernel, - true, - this.kernelProvider, - this.notebook - ); - } else if (preferredKernel.kind === 'connectToLiveKernel') { - return new VSCodeNotebookKernelMetadata( - preferredKernel.kernelModel.display_name || preferredKernel.kernelModel.name, - '', - preferredKernel.kernelModel.name, - preferredKernel, - true, - this.kernelProvider, - this.notebook - ); - } else { - return new VSCodeNotebookKernelMetadata( - preferredKernel.kernelSpec.display_name, - '', - preferredKernel.kernelSpec.name, - preferredKernel, - true, - this.kernelProvider, - this.notebook - ); - } - } - private async getPreferredKernel(document: NotebookDocument, token: CancellationToken) { - // If we already have a kernel selected, then return that. - const editor = - this.notebook.notebookEditors.find((e) => e.document === document) || - (this.notebook.activeNotebookEditor?.document === document - ? this.notebook.activeNotebookEditor - : undefined); - if (editor && editor.kernel && editor.kernel instanceof VSCodeNotebookKernelMetadata) { - return editor.kernel.selection; - } - return this.kernelSelector.getPreferredKernelForLocalConnection( - document.uri, - 'raw', - undefined, - getNotebookMetadata(document), - true, - token, - true - ); - } - private async onDidChangeActiveNotebookKernel({ - document, - kernel - }: { - document: NotebookDocument; - kernel: VSCNotebookKernel | undefined; - }) { - // We're only interested in our Jupyter Notebooks & our kernels. - if (!kernel || !(kernel instanceof VSCodeNotebookKernelMetadata) || !isJupyterNotebook(document)) { - return; - } - const selectedKernelConnectionMetadata = kernel.selection; - - const model = this.storageProvider.get(document.uri); - if (!model || !model.isTrusted) { - // tslint:disable-next-line: no-suspicious-comment - // TODO: https://github.com/microsoft/vscode-python/issues/13476 - // If a model is not trusted, we cannot change the kernel (this results in changes to notebook metadata). - // This is because we store selected kernel in the notebook metadata. - return; - } - - const existingKernel = this.kernelProvider.get(document.uri); - if (existingKernel && areKernelConnectionsEqual(existingKernel.metadata, selectedKernelConnectionMetadata)) { - return; - } - - // Make this the new kernel (calling this method will associate the new kernel with this Uri). - // Calling `getOrCreate` will ensure a kernel is created and it is mapped to the Uri provided. - // This way other parts of extension have access to this kernel immediately after event is handled. - this.kernelProvider.getOrCreate(document.uri, { - metadata: selectedKernelConnectionMetadata - }); - - // Change kernel and update metadata. - const notebook = await this.notebookProvider.getOrCreateNotebook({ - resource: document.uri, - identity: document.uri, - getOnly: true - }); - - // If we have a notebook, change its kernel now - if (notebook) { - if (!this.notebookKernelChangeHandled.has(notebook)) { - this.notebookKernelChangeHandled.add(notebook); - notebook.onKernelChanged( - (e) => { - if (notebook.disposed) { - return; - } - updateKernelInNotebookMetadata(document, e); - }, - this, - this.disposables - ); - } - // tslint:disable-next-line: no-suspicious-comment - // TODO: https://github.com/microsoft/vscode-python/issues/13514 - // We need to handle these exceptions in `siwthKernelWithRetry`. - // We shouldn't handle them here, as we're already handling some errors in the `siwthKernelWithRetry` method. - // Adding comment here, so we have context for the requirement. - this.kernelSwitcher.switchKernelWithRetry(notebook, selectedKernelConnectionMetadata).catch(noop); - } else { - updateKernelInNotebookMetadata(document, selectedKernelConnectionMetadata); - } - } -} diff --git a/src/client/datascience/notebook/notebookDisposeService.ts b/src/client/datascience/notebook/notebookDisposeService.ts deleted file mode 100644 index a699bd75b663..000000000000 --- a/src/client/datascience/notebook/notebookDisposeService.ts +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { NotebookDocument } from '../../../../typings/vscode-proposed'; -import { IExtensionSingleActivationService } from '../../activation/types'; -import { IApplicationEnvironment, IVSCodeNotebook } from '../../common/application/types'; -import { IDisposableRegistry } from '../../common/types'; -import { noop } from '../../common/utils/misc'; -import { IKernelProvider } from '../jupyter/kernels/types'; -import { INotebookProvider } from '../types'; - -@injectable() -export class NotebookDisposeService implements IExtensionSingleActivationService { - constructor( - @inject(IApplicationEnvironment) private readonly env: IApplicationEnvironment, - @inject(IVSCodeNotebook) private readonly vscNotebook: IVSCodeNotebook, - @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry, - @inject(INotebookProvider) private readonly notebookProvider: INotebookProvider, - @inject(IKernelProvider) private readonly kernelProvider: IKernelProvider - ) {} - public async activate(): Promise { - if (this.env.channel !== 'insiders') { - return; - } - - this.vscNotebook.onDidCloseNotebookDocument(this.onDidCloseNotebookDocument, this, this.disposables); - } - private onDidCloseNotebookDocument(document: NotebookDocument) { - const kernel = this.kernelProvider.get(document.uri); - if (kernel) { - kernel.dispose().catch(noop); - } - this.notebookProvider.disposeAssociatedNotebook({ identity: document.uri }); - } -} diff --git a/src/client/datascience/notebook/notebookEditor.ts b/src/client/datascience/notebook/notebookEditor.ts deleted file mode 100644 index 4ff1439eef42..000000000000 --- a/src/client/datascience/notebook/notebookEditor.ts +++ /dev/null @@ -1,307 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { ConfigurationTarget, Event, EventEmitter, Uri, WebviewPanel } from 'vscode'; -import type { NotebookCell, NotebookDocument } from 'vscode-proposed'; -import { IApplicationShell, ICommandManager, IVSCodeNotebook } from '../../common/application/types'; -import { traceError } from '../../common/logger'; -import { IConfigurationService, IDisposableRegistry } from '../../common/types'; -import { DataScience } from '../../common/utils/localize'; -import { noop } from '../../common/utils/misc'; -import { sendTelemetryEvent } from '../../telemetry'; -import { Telemetry } from '../constants'; -import { JupyterKernelPromiseFailedError } from '../jupyter/kernels/jupyterKernelPromiseFailedError'; -import { IKernel, IKernelProvider } from '../jupyter/kernels/types'; -import { - INotebook, - INotebookEditor, - INotebookExtensibility, - INotebookModel, - INotebookProvider, - InterruptResult, - IStatusProvider -} from '../types'; -import { getDefaultCodeLanguage } from './helpers/helpers'; -// tslint:disable-next-line: no-var-requires no-require-imports -const vscodeNotebookEnums = require('vscode') as typeof import('vscode-proposed'); - -export class NotebookEditor implements INotebookEditor { - public readonly type = 'native'; - public get onDidChangeViewState(): Event { - return this.changedViewState.event; - } - public get closed(): Event { - return this._closed.event; - } - public get modified(): Event { - return this._modified.event; - } - - public get executed(): Event { - return this._executed.event; - } - public get saved(): Event { - return this._saved.event; - } - public get isUntitled(): boolean { - return this.model.isUntitled; - } - public get isDirty(): boolean { - return this.document.isDirty; - } - public get file(): Uri { - return this.model.file; - } - public get visible(): boolean { - return !this.model.isDisposed; - } - public get active(): boolean { - return this.vscodeNotebook.activeNotebookEditor?.document.uri.toString() === this.model.file.toString(); - } - public get onExecutedCode(): Event { - return this.executedCode.event; - } - public get notebookExtensibility(): INotebookExtensibility { - return this.nbExtensibility; - } - public notebook?: INotebook | undefined; - - private changedViewState = new EventEmitter(); - private _closed = new EventEmitter(); - private _saved = new EventEmitter(); - private _executed = new EventEmitter(); - private _modified = new EventEmitter(); - private executedCode = new EventEmitter(); - private restartingKernel?: boolean; - constructor( - public readonly model: INotebookModel, - public readonly document: NotebookDocument, - private readonly vscodeNotebook: IVSCodeNotebook, - private readonly commandManager: ICommandManager, - private readonly notebookProvider: INotebookProvider, - private readonly kernelProvider: IKernelProvider, - private readonly statusProvider: IStatusProvider, - private readonly applicationShell: IApplicationShell, - private readonly configurationService: IConfigurationService, - disposables: IDisposableRegistry, - private readonly nbExtensibility: INotebookExtensibility - ) { - disposables.push(model.onDidEdit(() => this._modified.fire(this))); - disposables.push( - model.changed((e) => { - if (e.kind === 'save') { - this._saved.fire(this); - } - }) - ); - disposables.push(model.onDidDispose(this._closed.fire.bind(this._closed, this))); - } - public async load(_storage: INotebookModel, _webViewPanel?: WebviewPanel): Promise { - // Not used. - } - public runAllCells(): void { - this.commandManager.executeCommand('notebook.execute').then(noop, noop); - } - public runSelectedCell(): void { - this.commandManager.executeCommand('notebook.cell.execute').then(noop, noop); - } - public addCellBelow(): void { - this.commandManager.executeCommand('notebook.cell.insertCodeCellBelow').then(noop, noop); - } - public show(): Promise { - throw new Error('Method not implemented.'); - } - public startProgress(): void { - throw new Error('Method not implemented.'); - } - public stopProgress(): void { - throw new Error('Method not implemented.'); - } - public undoCells(): void { - this.commandManager.executeCommand('notebook.undo').then(noop, noop); - } - public redoCells(): void { - this.commandManager.executeCommand('notebook.redo').then(noop, noop); - } - public async hasCell(id: string): Promise { - return this.model.cells.find((c) => c.id === id) ? true : false; - } - public removeAllCells(): void { - if (!this.vscodeNotebook.activeNotebookEditor) { - return; - } - const defaultLanguage = getDefaultCodeLanguage(this.model); - const editor = this.vscodeNotebook.notebookEditors.find((item) => item.document === this.document); - if (editor) { - editor - .edit((edit) => - edit.replaceCells(0, this.document.cells.length, [ - { - cellKind: vscodeNotebookEnums.CellKind.Code, - language: defaultLanguage, - metadata: {}, - outputs: [], - source: '' - } - ]) - ) - .then(noop, noop); - } - } - public expandAllCells(): void { - if (!this.vscodeNotebook.activeNotebookEditor) { - return; - } - const notebook = this.vscodeNotebook.activeNotebookEditor.document; - const editor = this.vscodeNotebook.notebookEditors.find((item) => item.document === this.document); - if (editor) { - editor - .edit((edit) => { - notebook.cells.forEach((cell, index) => { - edit.replaceCellMetadata(index, { - ...cell.metadata, - inputCollapsed: false, - outputCollapsed: false - }); - }); - }) - .then(noop, noop); - } - } - public collapseAllCells(): void { - if (!this.vscodeNotebook.activeNotebookEditor) { - return; - } - const notebook = this.vscodeNotebook.activeNotebookEditor.document; - const editor = this.vscodeNotebook.notebookEditors.find((item) => item.document === this.document); - if (editor) { - editor - .edit((edit) => { - notebook.cells.forEach((cell, index) => { - edit.replaceCellMetadata(index, { - ...cell.metadata, - inputCollapsed: true, - outputCollapsed: true - }); - }); - }) - .then(noop, noop); - } - } - public notifyExecution(cell: NotebookCell) { - this._executed.fire(this); - this.executedCode.fire(cell.document.getText()); - this.nbExtensibility.fireKernelPostExecute(cell); - } - public async interruptKernel(): Promise { - if (this.restartingKernel) { - return; - } - const kernel = this.kernelProvider.get(this.file); - if (!kernel || this.restartingKernel) { - return; - } - const status = this.statusProvider.set(DataScience.interruptKernelStatus(), true, undefined, undefined); - - try { - const result = await kernel.interrupt(); - status.dispose(); - - // We timed out, ask the user if they want to restart instead. - if (result === InterruptResult.TimedOut) { - const message = DataScience.restartKernelAfterInterruptMessage(); - const yes = DataScience.restartKernelMessageYes(); - const no = DataScience.restartKernelMessageNo(); - const v = await this.applicationShell.showInformationMessage(message, yes, no); - if (v === yes) { - this.restartingKernel = false; - await this.restartKernel(); - } - } - } catch (err) { - status.dispose(); - traceError(err); - this.applicationShell.showErrorMessage(err); - } - } - - public async restartKernel(): Promise { - sendTelemetryEvent(Telemetry.RestartKernelCommand); - if (this.restartingKernel) { - return; - } - const kernel = this.kernelProvider.get(this.file); - - if (kernel && !this.restartingKernel) { - if (await this.shouldAskForRestart()) { - // Ask the user if they want us to restart or not. - const message = DataScience.restartKernelMessage(); - const yes = DataScience.restartKernelMessageYes(); - const dontAskAgain = DataScience.restartKernelMessageDontAskAgain(); - const no = DataScience.restartKernelMessageNo(); - - const response = await this.applicationShell.showInformationMessage(message, yes, dontAskAgain, no); - if (response === dontAskAgain) { - await this.disableAskForRestart(); - await this.restartKernelInternal(kernel); - } else if (response === yes) { - await this.restartKernelInternal(kernel); - } - } else { - await this.restartKernelInternal(kernel); - } - } - } - public dispose() { - this._closed.fire(this); - } - private async restartKernelInternal(kernel: IKernel): Promise { - this.restartingKernel = true; - - // Set our status - const status = this.statusProvider.set(DataScience.restartingKernelStatus(), true, undefined, undefined); - - try { - await kernel.restart(); - this.nbExtensibility.fireKernelRestart(); - } catch (exc) { - // If we get a kernel promise failure, then restarting timed out. Just shutdown and restart the entire server. - // Note, this code might not be necessary, as such an error is thrown only when interrupting a kernel times out. - if (exc instanceof JupyterKernelPromiseFailedError && kernel) { - // Old approach (INotebook is not exposed in IKernel, and INotebook will eventually go away). - const notebook = await this.notebookProvider.getOrCreateNotebook({ - resource: this.file, - identity: this.file, - getOnly: true - }); - if (notebook) { - await notebook.dispose(); - } - await this.notebookProvider.connect({ getOnly: false, disableUI: false }); - } else { - // Show the error message - this.applicationShell.showErrorMessage(exc); - traceError(exc); - } - } finally { - status.dispose(); - this.restartingKernel = false; - } - } - private async shouldAskForRestart(): Promise { - const settings = this.configurationService.getSettings(this.file); - return settings && settings.datascience && settings.datascience.askForKernelRestart === true; - } - - private async disableAskForRestart(): Promise { - const settings = this.configurationService.getSettings(this.file); - if (settings && settings.datascience) { - settings.datascience.askForKernelRestart = false; - this.configurationService - .updateSetting('dataScience.askForKernelRestart', false, undefined, ConfigurationTarget.Global) - .ignoreErrors(); - } - } -} diff --git a/src/client/datascience/notebook/notebookEditorCompatibilitySupport.ts b/src/client/datascience/notebook/notebookEditorCompatibilitySupport.ts deleted file mode 100644 index 4ac70ab6823d..000000000000 --- a/src/client/datascience/notebook/notebookEditorCompatibilitySupport.ts +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { Uri } from 'vscode'; -import { IExtensionSingleActivationService } from '../../activation/types'; -import { IApplicationShell } from '../../common/application/types'; -import { UseVSCodeNotebookEditorApi } from '../../common/constants'; - -import { DataScience } from '../../common/utils/localize'; -import { noop } from '../../common/utils/misc'; -import { IServiceContainer } from '../../ioc/types'; -import { OurNotebookProvider, VSCodeNotebookProvider } from '../constants'; -import { IDataScienceFileSystem, INotebookEditorProvider } from '../types'; - -@injectable() -export class NotebookEditorCompatibilitySupport implements IExtensionSingleActivationService { - private ourCustomNotebookEditorProvider?: INotebookEditorProvider; - private vscodeNotebookEditorProvider!: INotebookEditorProvider; - private initialized?: boolean; - constructor( - @inject(IApplicationShell) private readonly appShell: IApplicationShell, - @inject(UseVSCodeNotebookEditorApi) private readonly useVSCodeNotebookEditorApi: boolean, - - @inject(IDataScienceFileSystem) private readonly fs: IDataScienceFileSystem, - @inject(IServiceContainer) private readonly serviceContainer: IServiceContainer - ) {} - public async activate(): Promise { - this.initialize(); - } - public canOpenWithVSCodeNotebookEditor(uri: Uri) { - this.initialize(); - if (!this.ourCustomNotebookEditorProvider) { - return true; - } - // If user has a normal notebook opened for the same document, let them know things can go wonky. - if ( - this.ourCustomNotebookEditorProvider.editors.some((editor) => - this.fs.areLocalPathsSame(editor.file.fsPath, uri.fsPath) - ) - ) { - this.showWarning(false); - return false; - } - return true; - } - public canOpenWithOurNotebookEditor(uri: Uri, throwException = false) { - this.initialize(); - // If user has a VS Code notebook opened for the same document, let them know things can go wonky. - if ( - this.vscodeNotebookEditorProvider.editors.some((editor) => - this.fs.areLocalPathsSame(editor.file.fsPath, uri.fsPath) - ) - ) { - this.showWarning(throwException); - return false; - } - return true; - } - private initialize() { - if (this.initialized) { - return; - } - this.initialized = true; - if (!this.useVSCodeNotebookEditorApi) { - this.ourCustomNotebookEditorProvider = this.serviceContainer.get( - OurNotebookProvider - ); - } - this.vscodeNotebookEditorProvider = this.serviceContainer.get(VSCodeNotebookProvider); - - if (this.ourCustomNotebookEditorProvider) { - this.ourCustomNotebookEditorProvider.onDidOpenNotebookEditor((e) => - this.canOpenWithOurNotebookEditor(e.file) - ); - } - this.vscodeNotebookEditorProvider.onDidOpenNotebookEditor((e) => this.canOpenWithVSCodeNotebookEditor(e.file)); - } - private showWarning(throwException: boolean) { - if (throwException) { - throw new Error(DataScience.usingPreviewNotebookWithOtherNotebookWarning()); - } - this.appShell.showErrorMessage(DataScience.usingPreviewNotebookWithOtherNotebookWarning()).then(noop, noop); - } -} diff --git a/src/client/datascience/notebook/notebookEditorProvider.ts b/src/client/datascience/notebook/notebookEditorProvider.ts deleted file mode 100644 index 1cc4b962ff96..000000000000 --- a/src/client/datascience/notebook/notebookEditorProvider.ts +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { Event, EventEmitter, Uri } from 'vscode'; -import type { NotebookDocument, NotebookEditor as VSCodeNotebookEditor } from 'vscode-proposed'; -import { IApplicationShell, ICommandManager, IVSCodeNotebook } from '../../common/application/types'; -import '../../common/extensions'; - -import { IConfigurationService, IDisposableRegistry } from '../../common/types'; -import { createDeferred, Deferred } from '../../common/utils/async'; -import { IServiceContainer } from '../../ioc/types'; -import { captureTelemetry, setSharedProperty } from '../../telemetry'; -import { Commands, Telemetry } from '../constants'; -import { IKernelProvider } from '../jupyter/kernels/types'; -import { INotebookStorageProvider } from '../notebookStorage/notebookStorageProvider'; -import { VSCodeNotebookModel } from '../notebookStorage/vscNotebookModel'; -import { - IDataScienceFileSystem, - INotebookEditor, - INotebookEditorProvider, - INotebookExtensibility, - INotebookProvider, - IStatusProvider -} from '../types'; -import { JupyterNotebookView } from './constants'; -import { isJupyterNotebook } from './helpers/helpers'; -import { NotebookEditor } from './notebookEditor'; - -/** - * Notebook Editor provider used by other parts of DS code. - * This is an adapter, that takes the VSCode api for editors (did notebook editors open, close save, etc) and - * then exposes them in a manner we expect - i.e. INotebookEditorProvider. - * This is also responsible for tracking all notebooks that open and then keeping the VS Code notebook models updated with changes we made to our underlying model. - * E.g. when cells are executed the results in our model is updated, this tracks those changes and syncs VSC cells with those updates. - */ -@injectable() -export class NotebookEditorProvider implements INotebookEditorProvider { - public get onDidChangeActiveNotebookEditor(): Event { - return this._onDidChangeActiveNotebookEditor.event; - } - public get onDidCloseNotebookEditor(): Event { - return this._onDidCloseNotebookEditor.event; - } - public get onDidOpenNotebookEditor(): Event { - return this._onDidOpenNotebookEditor.event; - } - public get activeEditor(): INotebookEditor | undefined { - return this.editors.find((e) => e.visible && e.active); - } - public get editors(): INotebookEditor[] { - return [...this.openedEditors]; - } - protected readonly _onDidChangeActiveNotebookEditor = new EventEmitter(); - protected readonly _onDidOpenNotebookEditor = new EventEmitter(); - private readonly _onDidCloseNotebookEditor = new EventEmitter(); - private readonly openedEditors = new Set(); - private readonly trackedVSCodeNotebookEditors = new Set(); - private readonly notebookEditorsByUri = new Map(); - private readonly notebooksWaitingToBeOpenedByUri = new Map>(); - constructor( - @inject(IVSCodeNotebook) private readonly vscodeNotebook: IVSCodeNotebook, - @inject(INotebookStorageProvider) private readonly storage: INotebookStorageProvider, - @inject(ICommandManager) private readonly commandManager: ICommandManager, - @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry, - @inject(IConfigurationService) private readonly configurationService: IConfigurationService, - @inject(IApplicationShell) private readonly appShell: IApplicationShell, - @inject(IStatusProvider) private readonly statusProvider: IStatusProvider, - @inject(IServiceContainer) private readonly serviceContainer: IServiceContainer, - @inject(IDataScienceFileSystem) private readonly fs: IDataScienceFileSystem, - @inject(INotebookExtensibility) private readonly notebookExtensibility: INotebookExtensibility - ) { - this.disposables.push(this.vscodeNotebook.onDidOpenNotebookDocument(this.onDidOpenNotebookDocument, this)); - this.disposables.push(this.vscodeNotebook.onDidCloseNotebookDocument(this.onDidCloseNotebookDocument, this)); - this.disposables.push( - this.vscodeNotebook.onDidChangeActiveNotebookEditor(this.onDidChangeActiveVsCodeNotebookEditor, this) - ); - this.disposables.push( - this.commandManager.registerCommand(Commands.OpenNotebookInPreviewEditor, async (uri?: Uri) => { - if (uri) { - setSharedProperty('ds_notebookeditor', 'native'); - captureTelemetry(Telemetry.OpenNotebook, { scope: 'command' }, false); - this.open(uri).ignoreErrors(); - } - }) - ); - } - - public async open(file: Uri): Promise { - setSharedProperty('ds_notebookeditor', 'native'); - if (this.notebooksWaitingToBeOpenedByUri.get(file.toString())) { - return this.notebooksWaitingToBeOpenedByUri.get(file.toString())!.promise; - } - - // Wait for editor to get opened up, vscode will notify when it is opened. - // Further below. - this.notebooksWaitingToBeOpenedByUri.set(file.toString(), createDeferred()); - const deferred = this.notebooksWaitingToBeOpenedByUri.get(file.toString())!; - - // Tell VSC to open the notebook, at which point it will fire a callback when a notebook document has been opened. - // Then our promise will get resolved. - await this.commandManager.executeCommand('vscode.openWith', file, JupyterNotebookView); - - // This gets resolved when we have handled the opening of the notebook. - return deferred.promise; - } - public async show(_file: Uri): Promise { - // We do not need this. - return; - } - @captureTelemetry(Telemetry.CreateNewNotebook, undefined, false) - public async createNew(contents?: string): Promise { - setSharedProperty('ds_notebookeditor', 'native'); - const model = await this.storage.createNew(contents, true); - return this.open(model.file); - } - private onEditorOpened(editor: INotebookEditor): void { - this.openedEditors.add(editor); - editor.closed(this.closedEditor, this, this.disposables); - this._onDidOpenNotebookEditor.fire(editor); - this._onDidChangeActiveNotebookEditor.fire(editor); - } - - private closedEditor(editor: INotebookEditor): void { - if (this.openedEditors.has(editor)) { - this.openedEditors.delete(editor); - this._onDidCloseNotebookEditor.fire(editor); - - // Find all notebooks associated with this editor (ipynb file). - const otherEditors = this.editors.filter( - (e) => this.fs.areLocalPathsSame(e.file.fsPath, editor.file.fsPath) && e !== editor - ); - - // If we have no editors for this file, then dispose the notebook. - if (otherEditors.length === 0) { - editor.notebook?.dispose(); - } - } - } - - private async onDidOpenNotebookDocument(doc: NotebookDocument): Promise { - if (!isJupyterNotebook(doc)) { - return; - } - const uri = doc.uri; - const model = await this.storage.getOrCreateModel({ file: uri, isNative: true }); - if (model instanceof VSCodeNotebookModel) { - model.associateNotebookDocument(doc); - } - // In open method we might be waiting. - let editor = this.notebookEditorsByUri.get(uri.toString()); - if (!editor) { - const notebookProvider = this.serviceContainer.get(INotebookProvider); - const kernelProvider = this.serviceContainer.get(IKernelProvider); - editor = new NotebookEditor( - model, - doc, - this.vscodeNotebook, - this.commandManager, - notebookProvider, - kernelProvider, - this.statusProvider, - this.appShell, - this.configurationService, - this.disposables, - this.notebookExtensibility - ); - this.onEditorOpened(editor); - } - if (!this.notebooksWaitingToBeOpenedByUri.get(uri.toString())) { - this.notebooksWaitingToBeOpenedByUri.set(uri.toString(), createDeferred()); - } - const deferred = this.notebooksWaitingToBeOpenedByUri.get(uri.toString())!; - deferred.resolve(editor); - this.notebookEditorsByUri.set(uri.toString(), editor); - if (!model.isTrusted) { - await this.commandManager.executeCommand(Commands.TrustNotebook, model.file); - } - } - private onDidChangeActiveVsCodeNotebookEditor(editor: VSCodeNotebookEditor | undefined) { - if (!editor) { - this._onDidChangeActiveNotebookEditor.fire(undefined); - return; - } - if (this.trackedVSCodeNotebookEditors.has(editor)) { - const ourEditor = this.editors.find((item) => item.file.toString() === editor.document.uri.toString()); - this._onDidChangeActiveNotebookEditor.fire(ourEditor); - return; - } - this.trackedVSCodeNotebookEditors.add(editor); - this.disposables.push(editor.onDidDispose(() => this.onDidDisposeVSCodeNotebookEditor(editor))); - } - private async onDidCloseNotebookDocument(document: NotebookDocument) { - this.disposeResourceRelatedToNotebookEditor(document.uri); - } - private disposeResourceRelatedToNotebookEditor(uri: Uri) { - // Ok, dispose all of the resources associated with this document. - // In our case, we only have one editor. - const editor = this.notebookEditorsByUri.get(uri.toString()); - if (editor) { - this.closedEditor(editor); - editor.dispose(); - if (editor.model) { - editor.model.dispose(); - } - } - this.notebookEditorsByUri.delete(uri.toString()); - this.notebooksWaitingToBeOpenedByUri.delete(uri.toString()); - } - /** - * We know a notebook editor has been closed. - * We need to close/dispose all of our resources related to this notebook document. - * However we also need to check if there are other notebooks opened, that are associated with this same notebook. - * I.e. we may have closed a duplicate editor. - */ - private async onDidDisposeVSCodeNotebookEditor(closedEditor: VSCodeNotebookEditor) { - const uri = closedEditor.document.uri; - if ( - this.vscodeNotebook.notebookEditors.some( - (item) => item !== closedEditor && item.document.uri.toString() === uri.toString() - ) - ) { - return; - } - this.disposeResourceRelatedToNotebookEditor(closedEditor.document.uri); - } -} diff --git a/src/client/datascience/notebook/notebookEditorProviderWrapper.ts b/src/client/datascience/notebook/notebookEditorProviderWrapper.ts deleted file mode 100644 index 6889d7305327..000000000000 --- a/src/client/datascience/notebook/notebookEditorProviderWrapper.ts +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { Event, EventEmitter, Uri } from 'vscode'; -import { UseVSCodeNotebookEditorApi } from '../../common/constants'; -import '../../common/extensions'; -import { IDisposableRegistry } from '../../common/types'; -import { IServiceContainer } from '../../ioc/types'; -import { OurNotebookProvider, VSCodeNotebookProvider } from '../constants'; -import { INotebookEditor, INotebookEditorProvider } from '../types'; -import { NotebookEditorCompatibilitySupport } from './notebookEditorCompatibilitySupport'; - -/** - * Notebook Editor provider used by other parts of DS code. - * This is an adapter, that takes the VSCode api for editors (did notebook editors open, close save, etc) and - * then exposes them in a manner we expect - i.e. INotebookEditorProvider. - * This is also responsible for tracking all notebooks that open and then keeping the VS Code notebook models updated with changes we made to our underlying model. - * E.g. when cells are executed the results in our model is updated, this tracks those changes and syncs VSC cells with those updates. - */ -@injectable() -export class NotebookEditorProviderWrapper implements INotebookEditorProvider { - public get onDidChangeActiveNotebookEditor(): Event { - if (this.useVSCodeNotebookEditorApi) { - return this.vscodeNotebookEditorProvider.onDidChangeActiveNotebookEditor; - } - return this._onDidChangeActiveNotebookEditor.event; - } - public get onDidCloseNotebookEditor(): Event { - if (this.useVSCodeNotebookEditorApi) { - return this.vscodeNotebookEditorProvider.onDidCloseNotebookEditor; - } - return this._onDidCloseNotebookEditor.event; - } - public get onDidOpenNotebookEditor(): Event { - if (this.useVSCodeNotebookEditorApi) { - return this.vscodeNotebookEditorProvider.onDidOpenNotebookEditor; - } - return this._onDidOpenNotebookEditor.event; - } - public get activeEditor(): INotebookEditor | undefined { - if (this.useVSCodeNotebookEditorApi) { - return this.vscodeNotebookEditorProvider.activeEditor; - } - return ( - this.vscodeNotebookEditorProvider.activeEditor || this.ourCustomOrOldNotebookEditorProvider?.activeEditor - ); - } - public get editors(): INotebookEditor[] { - if (this.useVSCodeNotebookEditorApi) { - return this.vscodeNotebookEditorProvider.editors; - } - // If a VS Code notebook is opened, then user vscode notebooks provider. - if (this.vscodeNotebookEditorProvider.activeEditor) { - return this.vscodeNotebookEditorProvider.editors; - } - const provider = this.ourCustomOrOldNotebookEditorProvider || this.vscodeNotebookEditorProvider; - return provider.editors; - } - protected readonly _onDidChangeActiveNotebookEditor = new EventEmitter(); - protected readonly _onDidOpenNotebookEditor = new EventEmitter(); - private readonly _onDidCloseNotebookEditor = new EventEmitter(); - private readonly ourCustomOrOldNotebookEditorProvider?: INotebookEditorProvider; - private hasNotebookOpenedUsingVSCodeNotebook?: boolean; - constructor( - @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry, - @inject(UseVSCodeNotebookEditorApi) private readonly useVSCodeNotebookEditorApi: boolean, - @inject(VSCodeNotebookProvider) private readonly vscodeNotebookEditorProvider: INotebookEditorProvider, - @inject(IServiceContainer) serviceContainer: IServiceContainer, - @inject(NotebookEditorCompatibilitySupport) - private readonly compatibilitySupport: NotebookEditorCompatibilitySupport - ) { - // If user doesn't belong to Notebooks experiment, then use old notebook editor API. - if (!this.useVSCodeNotebookEditorApi) { - const ourCustomOrOldNotebookEditorProvider = serviceContainer.get( - OurNotebookProvider - ); - this.ourCustomOrOldNotebookEditorProvider = ourCustomOrOldNotebookEditorProvider; - ourCustomOrOldNotebookEditorProvider.onDidChangeActiveNotebookEditor( - this._onDidChangeActiveNotebookEditor.fire, - this._onDidChangeActiveNotebookEditor, - this.disposables - ); - ourCustomOrOldNotebookEditorProvider.onDidCloseNotebookEditor( - this._onDidCloseNotebookEditor.fire, - this._onDidCloseNotebookEditor, - this.disposables - ); - ourCustomOrOldNotebookEditorProvider.onDidOpenNotebookEditor( - this._onDidOpenNotebookEditor.fire, - this._onDidOpenNotebookEditor, - this.disposables - ); - } - - // Even if user doesn't belong to notebook experiment, they can open a notebook using the new vsc Notebook ui. - this.vscodeNotebookEditorProvider.onDidChangeActiveNotebookEditor( - (e) => { - if (e) { - // Keep track of the fact that we opened something using VS Code notebooks. - this.hasNotebookOpenedUsingVSCodeNotebook = true; - this._onDidChangeActiveNotebookEditor.fire(e); - } else if (this.hasNotebookOpenedUsingVSCodeNotebook) { - // We are only interested in events fired when we had already used a VS Code notebook. - this._onDidChangeActiveNotebookEditor.fire(e); - // Check if we have other VSC Notebooks opened. - if (!this.vscodeNotebookEditorProvider.editors.length) { - this.hasNotebookOpenedUsingVSCodeNotebook = false; - } - } - }, - this, - this.disposables - ); - // This can be done blindly, as th VSCodeNotebook API would trigger these events only if it was explicitly used. - this.vscodeNotebookEditorProvider.onDidCloseNotebookEditor( - this._onDidCloseNotebookEditor.fire, - this._onDidCloseNotebookEditor, - this.disposables - ); - // This can be done blindly, as th VSCodeNotebook API would trigger these events only if it was explicitly used. - this.vscodeNotebookEditorProvider.onDidOpenNotebookEditor( - this._onDidOpenNotebookEditor.fire, - this._onDidOpenNotebookEditor, - this.disposables - ); - } - - public async open(file: Uri): Promise { - if (this.ourCustomOrOldNotebookEditorProvider) { - this.compatibilitySupport.canOpenWithOurNotebookEditor(file, true); - } - - return (this.ourCustomOrOldNotebookEditorProvider || this.vscodeNotebookEditorProvider).open(file); - } - public async show(file: Uri): Promise { - return (this.ourCustomOrOldNotebookEditorProvider || this.vscodeNotebookEditorProvider).show(file); - } - public async createNew(contents?: string): Promise { - return (this.ourCustomOrOldNotebookEditorProvider || this.vscodeNotebookEditorProvider).createNew(contents); - } -} diff --git a/src/client/datascience/notebook/rendererExtension.ts b/src/client/datascience/notebook/rendererExtension.ts deleted file mode 100644 index 0488cb9aa68d..000000000000 --- a/src/client/datascience/notebook/rendererExtension.ts +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -import { NotebookDocument } from '../../../../types/vscode-proposed'; -import { IExtensionSingleActivationService } from '../../activation/types'; -import { IApplicationEnvironment, IVSCodeNotebook } from '../../common/application/types'; -import { IDisposableRegistry, IExtensions } from '../../common/types'; -import { noop } from '../../common/utils/misc'; -import { RendererExtensionId } from './constants'; -import { isJupyterNotebook } from './helpers/helpers'; -import { RendererExtensionDownloader } from './rendererExtensionDownloader'; - -@injectable() -export class RendererExtension implements IExtensionSingleActivationService { - constructor( - @inject(IVSCodeNotebook) private readonly notebook: IVSCodeNotebook, - @inject(RendererExtensionDownloader) private readonly downloader: RendererExtensionDownloader, - @inject(IExtensions) private readonly extensions: IExtensions, - @inject(IApplicationEnvironment) private readonly env: IApplicationEnvironment, - @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry - ) {} - public async activate() { - if (this.env.channel === 'stable') { - return; - } - this.notebook.onDidOpenNotebookDocument(this.onDidOpenNotebook, this, this.disposables); - this.notebook.notebookDocuments.forEach((doc) => this.onDidOpenNotebook(doc)); - } - - private onDidOpenNotebook(e: NotebookDocument) { - if (!isJupyterNotebook(e)) { - return; - } - - // Download and install the extension if not already found. - if (!this.extensions.getExtension(RendererExtensionId)) { - this.downloader.downloadAndInstall().catch(noop); - } - } -} diff --git a/src/client/datascience/notebook/rendererExtensionDownloader.ts b/src/client/datascience/notebook/rendererExtensionDownloader.ts deleted file mode 100644 index d3956a71f7db..000000000000 --- a/src/client/datascience/notebook/rendererExtensionDownloader.ts +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable, named } from 'inversify'; -import { Uri } from 'vscode'; -import { IApplicationShell, ICommandManager } from '../../common/application/types'; -import { Octicons, STANDARD_OUTPUT_CHANNEL } from '../../common/constants'; -import { vsixFileExtension } from '../../common/installer/extensionBuildInstaller'; - -import { IFileDownloader, IOutputChannel } from '../../common/types'; -import { DataScienceRendererExtension } from '../../common/utils/localize'; -import { traceDecorators } from '../../logging'; -import { IDataScienceFileSystem } from '../types'; -import { RendererExtensionDownloadUri } from './constants'; - -@injectable() -export class RendererExtensionDownloader { - private installed?: boolean; - constructor( - @inject(IOutputChannel) @named(STANDARD_OUTPUT_CHANNEL) private readonly output: IOutputChannel, - @inject(IApplicationShell) private readonly appShell: IApplicationShell, - @inject(ICommandManager) private readonly cmdManager: ICommandManager, - @inject(IFileDownloader) private readonly fileDownloader: IFileDownloader, - @inject(IDataScienceFileSystem) private readonly fs: IDataScienceFileSystem - ) {} - @traceDecorators.error('Installing Notebook Renderer extension failed') - public async downloadAndInstall(): Promise { - if (this.installed) { - return; - } - this.installed = true; - const vsixFilePath = await this.download(); - try { - this.output.append(DataScienceRendererExtension.installingExtension()); - await this.appShell.withProgressCustomIcon(Octicons.Installing, async (progress) => { - progress.report({ message: DataScienceRendererExtension.installingExtension() }); - return this.cmdManager.executeCommand('workbench.extensions.installExtension', Uri.file(vsixFilePath)); - }); - this.output.appendLine(DataScienceRendererExtension.installationCompleteMessage()); - } finally { - await this.fs.deleteLocalFile(vsixFilePath); - } - } - - @traceDecorators.error('Downloading Notebook Renderer extension failed') - private async download(): Promise { - this.output.appendLine(DataScienceRendererExtension.startingDownloadOutputMessage()); - const downloadOptions = { - extension: vsixFileExtension, - outputChannel: this.output, - progressMessagePrefix: DataScienceRendererExtension.downloadingMessage() - }; - return this.fileDownloader.downloadFile(RendererExtensionDownloadUri, downloadOptions).then((file) => { - this.output.appendLine(DataScienceRendererExtension.downloadCompletedOutputMessage()); - return file; - }); - } -} diff --git a/src/client/datascience/notebook/serviceRegistry.ts b/src/client/datascience/notebook/serviceRegistry.ts deleted file mode 100644 index 069fd280bfa2..000000000000 --- a/src/client/datascience/notebook/serviceRegistry.ts +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { NotebookContentProvider as VSCNotebookContentProvider } from '../../../../types/vscode-proposed'; -import { IExtensionSingleActivationService } from '../../activation/types'; -import { IInterpreterStatusbarVisibilityFilter } from '../../interpreter/contracts'; -import { IServiceManager } from '../../ioc/types'; -import { KernelProvider } from '../jupyter/kernels/kernelProvider'; -import { IKernelProvider } from '../jupyter/kernels/types'; -import { NotebookContentProvider } from './contentProvider'; -import { NotebookIntegration } from './integration'; -import { InterpreterStatusBarVisibility } from './interpreterStatusBarVisbility'; -import { VSCodeKernelPickerProvider } from './kernelProvider'; -import { NotebookDisposeService } from './notebookDisposeService'; -import { NotebookSurveyBanner, NotebookSurveyDataLogger } from './survey'; -import { INotebookContentProvider } from './types'; - -export function registerTypes(serviceManager: IServiceManager) { - serviceManager.addSingleton(INotebookContentProvider, NotebookContentProvider); - serviceManager.addSingleton( - IExtensionSingleActivationService, - NotebookIntegration - ); - serviceManager.addSingleton( - IExtensionSingleActivationService, - NotebookDisposeService - ); - serviceManager.addSingleton(NotebookIntegration, NotebookIntegration); - serviceManager.addSingleton(IKernelProvider, KernelProvider); - serviceManager.addSingleton(NotebookSurveyBanner, NotebookSurveyBanner); - serviceManager.addSingleton(VSCodeKernelPickerProvider, VSCodeKernelPickerProvider); - serviceManager.addSingleton( - IInterpreterStatusbarVisibilityFilter, - InterpreterStatusBarVisibility - ); - serviceManager.addSingleton( - IExtensionSingleActivationService, - NotebookSurveyDataLogger - ); -} diff --git a/src/client/datascience/notebook/survey.ts b/src/client/datascience/notebook/survey.ts deleted file mode 100644 index aabac9de00b6..000000000000 --- a/src/client/datascience/notebook/survey.ts +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IExtensionSingleActivationService } from '../../activation/types'; -import { IApplicationShell, IVSCodeNotebook } from '../../common/application/types'; -import { traceError } from '../../common/logger'; -import { IBrowserService, IDisposableRegistry, IPersistentStateFactory } from '../../common/types'; -import * as localize from '../../common/utils/localize'; -import { MillisecondsInADay } from '../../constants'; -import { INotebookEditorProvider } from '../types'; - -const surveyLink = 'https://aka.ms/pyaivscnbsurvey'; -const storageKey = 'NotebookSurveyUsageData'; - -export type NotebookSurveyUsageData = { - numberOfExecutionsInCurrentSession?: number; - numberOfCellActionsInCurrentSession?: number; - numberOfExecutionsInPreviousSessions?: number; - numberOfCellActionsInPreviousSessions?: number; - surveyDisabled?: boolean; - lastUsedDateTime?: number; -}; - -@injectable() -export class NotebookSurveyBanner { - public get enabled(): boolean { - return !this.persistentState.createGlobalPersistentState(storageKey, {}).value - .surveyDisabled; - } - private disabledInCurrentSession: boolean = false; - constructor( - @inject(IApplicationShell) private appShell: IApplicationShell, - @inject(IPersistentStateFactory) private persistentState: IPersistentStateFactory, - @inject(IBrowserService) private browserService: IBrowserService - ) {} - - public async showBanner(): Promise { - if (!this.enabled || this.disabledInCurrentSession) { - return; - } - - const show = await this.shouldShowBanner(); - if (!show || this.disabledInCurrentSession) { - return; - } - - this.disabledInCurrentSession = true; - const response = await this.appShell.showInformationMessage( - localize.DataScienceNotebookSurveyBanner.bannerMessage(), - localize.CommonSurvey.yesLabel(), - localize.CommonSurvey.noLabel(), - localize.CommonSurvey.remindMeLaterLabel() - ); - switch (response) { - case localize.CommonSurvey.yesLabel(): { - this.browserService.launch(surveyLink); - await this.disable(); - break; - } - case localize.CommonSurvey.noLabel(): { - await this.disable(); - break; - } - default: { - // Disable for the current session. - this.disabledInCurrentSession = true; - } - } - } - - private async disable(): Promise { - await this.persistentState - .createGlobalPersistentState(storageKey, {}) - .updateValue({ surveyDisabled: true }); - } - - private async shouldShowBanner(): Promise { - if (!this.enabled || this.disabledInCurrentSession) { - return false; - } - const currentDate = new Date(); - if (currentDate.getMonth() < 7 && currentDate.getFullYear() <= 2020) { - return false; - } - - const data = this.persistentState.createGlobalPersistentState(storageKey, {}); - - const totalActionsInPreviousSessions = - (data.value.numberOfCellActionsInPreviousSessions || 0) + - (data.value.numberOfExecutionsInPreviousSessions || 0); - // If user barely tried nb in a previous session, then possible it wasn't a great experience. - if (totalActionsInPreviousSessions > 0 && totalActionsInPreviousSessions < 5) { - return true; - } - - const totalActionsInCurrentSessions = - (data.value.numberOfCellActionsInCurrentSession || 0) + - (data.value.numberOfExecutionsInCurrentSession || 0); - // If more than 100 actions in total then get feedback. - if (totalActionsInPreviousSessions + totalActionsInCurrentSessions > 100) { - return true; - } - - // If more than 5 actions and not used for 5 days since then. - // Geed feedback, possible it wasn't what they expected, as they have stopped using it. - if (totalActionsInPreviousSessions > 5 && data.value.lastUsedDateTime) { - const daysSinceLastUsage = (new Date().getTime() - data.value.lastUsedDateTime) / MillisecondsInADay; - if (daysSinceLastUsage > 5) { - return true; - } - } - - return false; - } -} - -/* -Survey after > 100 actions in notebooks (+remind me later) -Survey after > 5 & not used for 5 days (+remind me later) -Survey after < 5 operations in notebooks & closed it (+remind me later) -*/ - -@injectable() -export class NotebookSurveyDataLogger implements IExtensionSingleActivationService { - constructor( - @inject(IPersistentStateFactory) private readonly persistentState: IPersistentStateFactory, - @inject(IVSCodeNotebook) private readonly vscNotebook: IVSCodeNotebook, - @inject(INotebookEditorProvider) private readonly notebookEditorProvider: INotebookEditorProvider, - @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry, - // tslint:disable-next-line: no-use-before-declare - @inject(NotebookSurveyBanner) private readonly survey: NotebookSurveyBanner - ) {} - public async activate() { - if (!this.survey.enabled) { - return; - } - - this.notebookEditorProvider.onDidOpenNotebookEditor( - (e) => { - if (e.type !== 'native') { - return; - } - e.onExecutedCode(() => this.incrementCellExecution(), this, this.disposables); - }, - this, - this.disposables - ); - this.vscNotebook.onDidChangeNotebookDocument( - (e) => { - if (e.type === 'changeCells' || e.type === 'changeCellLanguage') { - this.incrementCellAction().catch(traceError.bind(undefined, 'Failed to update survey data')); - } - }, - this, - this.disposables - ); - - this.migrateDataAndDisplayBanner().catch(traceError.bind(undefined, 'Failed to migrate survey data')); - } - private async migrateDataAndDisplayBanner() { - const data = this.persistentState.createGlobalPersistentState(storageKey, {}); - // The user has loaded a new instance of VSC, and we need to move numbers from previous session into the respective storage props. - if (data.value.numberOfCellActionsInCurrentSession || data.value.numberOfExecutionsInCurrentSession) { - data.value.numberOfCellActionsInPreviousSessions = data.value.numberOfCellActionsInPreviousSessions || 0; - data.value.numberOfCellActionsInPreviousSessions += data.value.numberOfCellActionsInCurrentSession || 0; - data.value.numberOfCellActionsInCurrentSession = 0; // Reset for new session. - - data.value.numberOfExecutionsInPreviousSessions = data.value.numberOfExecutionsInPreviousSessions || 0; - data.value.numberOfExecutionsInPreviousSessions += data.value.numberOfExecutionsInCurrentSession || 0; - data.value.numberOfExecutionsInCurrentSession = 0; // Reset for new session. - - data.value.lastUsedDateTime = new Date().getTime(); - await data.updateValue(data.value); - } - - await this.survey.showBanner(); - } - private async incrementCellAction() { - const data = this.persistentState.createGlobalPersistentState(storageKey, {}); - - data.value.numberOfCellActionsInCurrentSession = (data.value.numberOfCellActionsInCurrentSession || 0) + 1; - data.value.lastUsedDateTime = new Date().getTime(); - await data.updateValue(data.value); - await this.survey.showBanner(); - } - private async incrementCellExecution() { - const data = this.persistentState.createGlobalPersistentState(storageKey, {}); - data.value.numberOfExecutionsInCurrentSession = (data.value.numberOfExecutionsInCurrentSession || 0) + 1; - data.value.lastUsedDateTime = new Date().getTime(); - await data.updateValue(data.value); - await this.survey.showBanner(); - } -} diff --git a/src/client/datascience/notebook/types.ts b/src/client/datascience/notebook/types.ts deleted file mode 100644 index 0e38da6cd92a..000000000000 --- a/src/client/datascience/notebook/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -export const INotebookContentProvider = Symbol('INotebookContentProvider'); diff --git a/src/client/datascience/notebookAndInteractiveTracker.ts b/src/client/datascience/notebookAndInteractiveTracker.ts deleted file mode 100644 index b888b7312be4..000000000000 --- a/src/client/datascience/notebookAndInteractiveTracker.ts +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable, named } from 'inversify'; -import { Memento } from 'vscode'; -import { IDisposableRegistry, IMemento, WORKSPACE_MEMENTO } from '../common/types'; -import { - IInteractiveWindowProvider, - INotebookAndInteractiveWindowUsageTracker, - INotebookEditorProvider -} from './types'; - -const LastNotebookOpenedTimeKey = 'last-notebook-start-time'; -const LastInteractiveWindowStartTimeKey = 'last-interactive-window-start-time'; - -@injectable() -export class NotebookAndInteractiveWindowUsageTracker implements INotebookAndInteractiveWindowUsageTracker { - public get lastNotebookOpened() { - const time = this.mementoStorage.get(LastNotebookOpenedTimeKey); - return time ? new Date(time) : undefined; - } - public get lastInteractiveWindowOpened() { - const time = this.mementoStorage.get(LastInteractiveWindowStartTimeKey); - return time ? new Date(time) : undefined; - } - constructor( - @inject(IMemento) @named(WORKSPACE_MEMENTO) private mementoStorage: Memento, - @inject(INotebookEditorProvider) private readonly notebookEditorProvider: INotebookEditorProvider, - @inject(IInteractiveWindowProvider) private readonly interactiveWindowProvider: IInteractiveWindowProvider, - @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry - ) {} - public async startTracking(): Promise { - this.disposables.push( - this.notebookEditorProvider.onDidOpenNotebookEditor(() => - this.mementoStorage.update(LastNotebookOpenedTimeKey, Date.now()) - ) - ); - this.disposables.push( - this.interactiveWindowProvider.onDidChangeActiveInteractiveWindow(() => - this.mementoStorage.update(LastInteractiveWindowStartTimeKey, Date.now()) - ) - ); - } -} diff --git a/src/client/datascience/notebookExtensibility.ts b/src/client/datascience/notebookExtensibility.ts deleted file mode 100644 index 3a9f698c530f..000000000000 --- a/src/client/datascience/notebookExtensibility.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { injectable } from 'inversify'; -import { Event, EventEmitter } from 'vscode'; -import type { NotebookCell } from 'vscode-proposed'; -import { INotebookExtensibility } from './types'; - -@injectable() -export class NotebookExtensibility implements INotebookExtensibility { - private kernelExecute = new EventEmitter(); - - private kernelRestart = new EventEmitter(); - - public get onKernelPostExecute(): Event { - return this.kernelExecute.event; - } - - public get onKernelRestart(): Event { - return this.kernelRestart.event; - } - - public fireKernelRestart(): void { - this.kernelRestart.fire(); - } - - public fireKernelPostExecute(cell: NotebookCell): void { - this.kernelExecute.fire(cell); - } -} diff --git a/src/client/datascience/notebookStorage/baseModel.ts b/src/client/datascience/notebookStorage/baseModel.ts deleted file mode 100644 index 8cefbb1b959d..000000000000 --- a/src/client/datascience/notebookStorage/baseModel.ts +++ /dev/null @@ -1,336 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { nbformat } from '@jupyterlab/coreutils/lib/nbformat'; -import { KernelMessage } from '@jupyterlab/services'; -import * as fastDeepEqual from 'fast-deep-equal'; -import { sha256 } from 'hash.js'; -import { cloneDeep } from 'lodash'; -import { Event, EventEmitter, Memento, Uri } from 'vscode'; -import { ICryptoUtils } from '../../common/types'; -import { isUntitledFile } from '../../common/utils/misc'; -import { pruneCell } from '../common'; -import { NotebookModelChange } from '../interactive-common/interactiveWindowTypes'; -import { - getInterpreterFromKernelConnectionMetadata, - isPythonKernelConnection, - kernelConnectionMetadataHasKernelModel -} from '../jupyter/kernels/helpers'; -import { KernelConnectionMetadata } from '../jupyter/kernels/types'; -import { ICell, INotebookMetadataLive, INotebookModel } from '../types'; - -export const ActiveKernelIdList = `Active_Kernel_Id_List`; -// This is the number of kernel ids that will be remembered between opening and closing VS code -export const MaximumKernelIdListSize = 40; -type KernelIdListEntry = { - fileHash: string; - kernelId: string | undefined; -}; - -export function getInterpreterInfoStoredInMetadata( - metadata?: nbformat.INotebookMetadata -): { displayName: string; hash: string } | undefined { - if (!metadata || !metadata.kernelspec || !metadata.kernelspec.name) { - return; - } - // See `updateNotebookMetadata` to determine how & where exactly interpreter hash is stored. - // tslint:disable-next-line: no-any - const kernelSpecMetadata: undefined | any = metadata.kernelspec.metadata as any; - const interpreterHash = kernelSpecMetadata?.interpreter?.hash; - return interpreterHash ? { displayName: metadata.kernelspec.name, hash: interpreterHash } : undefined; -} - -// tslint:disable-next-line: cyclomatic-complexity -export function updateNotebookMetadata( - metadata?: nbformat.INotebookMetadata, - kernelConnection?: KernelConnectionMetadata, - kernelInfo?: KernelMessage.IInfoReplyMsg['content'] -) { - let changed = false; - let kernelId: string | undefined; - if (!metadata) { - return { changed, kernelId }; - } - - if (kernelInfo && kernelInfo.status === 'ok') { - if (!fastDeepEqual(metadata.language_info, kernelInfo.language_info)) { - metadata.language_info = cloneDeep(kernelInfo.language_info); - changed = true; - } - } else { - // Get our kernel_info and language_info from the current notebook - const isPythonConnection = isPythonKernelConnection(kernelConnection); - const interpreter = isPythonConnection - ? getInterpreterFromKernelConnectionMetadata(kernelConnection) - : undefined; - if ( - interpreter && - interpreter.version && - metadata && - metadata.language_info && - metadata.language_info.version !== interpreter.version.raw - ) { - metadata.language_info.version = interpreter.version.raw; - changed = true; - } else if (!interpreter && metadata?.language_info && isPythonConnection) { - // It's possible, such as with raw kernel and a default kernelspec to not have interpreter info - // for this case clear out old invalid language_info entries as they are related to the previous execution - // However we should clear previous language info only if language is python, else just leave it as is. - metadata.language_info = undefined; - changed = true; - } - } - - const kernelSpecOrModel = - kernelConnection && kernelConnectionMetadataHasKernelModel(kernelConnection) - ? kernelConnection.kernelModel - : kernelConnection?.kernelSpec; - if (kernelConnection?.kind === 'startUsingPythonInterpreter') { - // Store interpreter name, we expect the kernel finder will find the corresponding interpreter based on this name. - const name = kernelConnection.interpreter.displayName || ''; - if (metadata.kernelspec?.name !== name || metadata.kernelspec?.display_name !== name) { - changed = true; - metadata.kernelspec = { - name, - display_name: name, - metadata: { - interpreter: { - hash: sha256().update(kernelConnection.interpreter.path).digest('hex') - } - } - }; - } - } else if (kernelSpecOrModel && !metadata.kernelspec) { - // Add a new spec in this case - metadata.kernelspec = { - name: kernelSpecOrModel.name || kernelSpecOrModel.display_name || '', - display_name: kernelSpecOrModel.display_name || kernelSpecOrModel.name || '' - }; - kernelId = kernelSpecOrModel.id; - changed = true; - } else if (kernelSpecOrModel && metadata.kernelspec) { - // Spec exists, just update name and display_name - const name = kernelSpecOrModel.name || kernelSpecOrModel.display_name || ''; - const displayName = kernelSpecOrModel.display_name || kernelSpecOrModel.name || ''; - if ( - metadata.kernelspec.name !== name || - metadata.kernelspec.display_name !== displayName || - kernelId !== kernelSpecOrModel.id - ) { - changed = true; - metadata.kernelspec.name = name; - metadata.kernelspec.display_name = displayName; - kernelId = kernelSpecOrModel.id; - } - try { - // This is set only for when we select an interpreter. - // tslint:disable-next-line: no-any - delete (metadata.kernelspec as any).metadata; - } catch { - // Noop. - } - } - return { changed, kernelId }; -} - -export function getDefaultNotebookContent(pythonNumber: number = 3): Partial { - // Use this to build our metadata object - // Use these as the defaults unless we have been given some in the options. - const metadata: nbformat.INotebookMetadata = { - language_info: { - codemirror_mode: { - name: 'ipython', - version: pythonNumber - }, - file_extension: '.py', - mimetype: 'text/x-python', - name: 'python', - nbconvert_exporter: 'python', - pygments_lexer: `ipython${pythonNumber}`, - version: pythonNumber - }, - orig_nbformat: 2 - }; - - // Default notebook data. - return { - metadata: metadata, - nbformat: 4, - nbformat_minor: 2 - }; -} -export abstract class BaseNotebookModel implements INotebookModel { - public get onDidDispose() { - return this._disposed.event; - } - public get isDisposed() { - return this._isDisposed === true; - } - public get isDirty(): boolean { - return false; - } - public get changed(): Event { - return this._changedEmitter.event; - } - public get file(): Uri { - return this._file; - } - - public get isUntitled(): boolean { - return isUntitledFile(this.file); - } - public get cells(): ICell[] { - return this._cells; - } - public get onDidEdit(): Event { - return this._editEventEmitter.event; - } - public get metadata(): INotebookMetadataLive | undefined { - return this.kernelId && this.notebookJson.metadata - ? { - ...this.notebookJson.metadata, - id: this.kernelId - } - : // Fix nyc compiler problem - // tslint:disable-next-line: no-any - (this.notebookJson.metadata as any); - } - public get isTrusted() { - return this._isTrusted; - } - - protected _disposed = new EventEmitter(); - protected _isDisposed?: boolean; - protected _changedEmitter = new EventEmitter(); - protected _editEventEmitter = new EventEmitter(); - private kernelId: string | undefined; - constructor( - protected _isTrusted: boolean, - protected _file: Uri, - protected _cells: ICell[], - protected globalMemento: Memento, - private crypto: ICryptoUtils, - protected notebookJson: Partial = {}, - public readonly indentAmount: string = ' ', - private readonly pythonNumber: number = 3 - ) { - this.ensureNotebookJson(); - this.kernelId = this.getStoredKernelId(); - } - public dispose() { - this._isDisposed = true; - this._disposed.fire(); - } - public update(change: NotebookModelChange): void { - this.handleModelChange(change); - } - - public getContent(): string { - return this.generateNotebookContent(); - } - public trust() { - this._isTrusted = true; - } - protected handleUndo(_change: NotebookModelChange): boolean { - return false; - } - protected handleRedo(change: NotebookModelChange): boolean { - let changed = false; - switch (change.kind) { - case 'version': - changed = this.updateVersionInfo(change.kernelConnection); - break; - default: - break; - } - - return changed; - } - protected generateNotebookJson() { - // Make sure we have some - this.ensureNotebookJson(); - - // Reuse our original json except for the cells. - const json = { ...this.notebookJson }; - json.cells = this.cells.map((c) => pruneCell(c.data)); - return json; - } - - private handleModelChange(change: NotebookModelChange) { - const oldDirty = this.isDirty; - let changed = false; - - switch (change.source) { - case 'redo': - case 'user': - changed = this.handleRedo(change); - break; - case 'undo': - changed = this.handleUndo(change); - break; - default: - break; - } - - // Forward onto our listeners if necessary - if (changed || this.isDirty !== oldDirty) { - this._changedEmitter.fire({ ...change, newDirty: this.isDirty, oldDirty, model: this }); - } - // Slightly different for the event we send to VS code. Skip version and file changes. Only send user events. - if ((changed || this.isDirty !== oldDirty) && change.kind !== 'version' && change.source === 'user') { - this._editEventEmitter.fire(change); - } - } - // tslint:disable-next-line: cyclomatic-complexity - private updateVersionInfo(kernelConnection: KernelConnectionMetadata | undefined): boolean { - const { changed, kernelId } = updateNotebookMetadata(this.notebookJson.metadata, kernelConnection); - if (kernelId) { - this.kernelId = kernelId; - } - // Update our kernel id in our global storage too - this.setStoredKernelId(kernelId); - - return changed; - } - - private ensureNotebookJson() { - if (!this.notebookJson || !this.notebookJson.metadata) { - this.notebookJson = getDefaultNotebookContent(this.pythonNumber); - } - } - - private generateNotebookContent(): string { - const json = this.generateNotebookJson(); - return JSON.stringify(json, null, this.indentAmount); - } - private getStoredKernelId(): string | undefined { - // Stored as a list so we don't take up too much space - const list: KernelIdListEntry[] = this.globalMemento.get(ActiveKernelIdList, []); - if (list) { - // Not using a map as we're only going to store the last 40 items. - const fileHash = this.crypto.createHash(this._file.toString(), 'string'); - const entry = list.find((l) => l.fileHash === fileHash); - return entry?.kernelId; - } - } - private setStoredKernelId(id: string | undefined) { - const list: KernelIdListEntry[] = this.globalMemento.get(ActiveKernelIdList, []); - const fileHash = this.crypto.createHash(this._file.toString(), 'string'); - const index = list.findIndex((l) => l.fileHash === fileHash); - // Always remove old spot (we'll push on the back for new ones) - if (index >= 0) { - list.splice(index, 1); - } - - // If adding a new one, push - if (id) { - list.push({ fileHash, kernelId: id }); - } - - // Prune list if too big - while (list.length > MaximumKernelIdListSize) { - list.shift(); - } - return this.globalMemento.update(ActiveKernelIdList, list); - } -} diff --git a/src/client/datascience/notebookStorage/factory.ts b/src/client/datascience/notebookStorage/factory.ts deleted file mode 100644 index b440b40709f9..000000000000 --- a/src/client/datascience/notebookStorage/factory.ts +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { nbformat } from '@jupyterlab/coreutils/lib/nbformat'; -import { inject, injectable } from 'inversify'; -import { Memento, Uri } from 'vscode'; -import { UseVSCodeNotebookEditorApi } from '../../common/constants'; -import { ICryptoUtils } from '../../common/types'; -import { ICell, INotebookModel } from '../types'; -import { NativeEditorNotebookModel } from './notebookModel'; -import { INotebookModelFactory } from './types'; -import { VSCodeNotebookModel } from './vscNotebookModel'; - -@injectable() -export class NotebookModelFactory implements INotebookModelFactory { - constructor(@inject(UseVSCodeNotebookEditorApi) private readonly useVSCodeNotebookEditorApi: boolean) {} - public createModel( - options: { - trusted: boolean; - file: Uri; - cells: ICell[]; - notebookJson?: Partial; - globalMemento: Memento; - crypto: ICryptoUtils; - indentAmount?: string; - pythonNumber?: number; - initiallyDirty?: boolean; - }, - forVSCodeNotebook?: boolean - ): INotebookModel { - if (forVSCodeNotebook || this.useVSCodeNotebookEditorApi) { - return new VSCodeNotebookModel( - options.trusted, - options.file, - options.cells, - options.globalMemento, - options.crypto, - options.notebookJson, - options.indentAmount, - options.pythonNumber - ); - } - return new NativeEditorNotebookModel( - options.trusted, - options.file, - options.cells, - options.globalMemento, - options.crypto, - options.notebookJson, - options.indentAmount, - options.pythonNumber, - options.initiallyDirty - ); - } -} diff --git a/src/client/datascience/notebookStorage/nativeEditorProvider.ts b/src/client/datascience/notebookStorage/nativeEditorProvider.ts deleted file mode 100644 index f2015fd9eff0..000000000000 --- a/src/client/datascience/notebookStorage/nativeEditorProvider.ts +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { inject, injectable } from 'inversify'; -import * as uuid from 'uuid/v4'; -import { Disposable, Event, EventEmitter, Memento, Uri, WebviewPanel } from 'vscode'; -import { CancellationToken } from 'vscode-languageclient/node'; -import { arePathsSame } from '../../../datascience-ui/react-common/arePathsSame'; -import { - CustomDocument, - CustomDocumentBackup, - CustomDocumentBackupContext, - CustomDocumentEditEvent, - CustomDocumentOpenContext, - CustomEditorProvider, - IApplicationShell, - ICommandManager, - ICustomEditorService, - IDocumentManager, - ILiveShareApi, - IWebviewPanelProvider, - IWorkspaceService -} from '../../common/application/types'; -import { UseCustomEditorApi } from '../../common/constants'; -import { traceInfo } from '../../common/logger'; - -import { - GLOBAL_MEMENTO, - IAsyncDisposableRegistry, - IConfigurationService, - IDisposableRegistry, - IExperimentsManager, - IMemento, - WORKSPACE_MEMENTO -} from '../../common/types'; -import { createDeferred } from '../../common/utils/async'; -import { IServiceContainer } from '../../ioc/types'; -import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; -import { generateNewNotebookUri } from '../common'; -import { Identifiers, Telemetry } from '../constants'; -import { IDataViewerFactory } from '../data-viewing/types'; -import { NotebookModelChange } from '../interactive-common/interactiveWindowTypes'; -import { NativeEditor } from '../interactive-ipynb/nativeEditor'; -import { NativeEditorSynchronizer } from '../interactive-ipynb/nativeEditorSynchronizer'; -import { KernelSelector } from '../jupyter/kernels/kernelSelector'; -import { - ICodeCssGenerator, - IDataScienceErrorHandler, - IDataScienceFileSystem, - IInteractiveWindowListener, - IJupyterDebugger, - IJupyterVariableDataProviderFactory, - IJupyterVariables, - IModelLoadOptions, - INotebookEditor, - INotebookEditorProvider, - INotebookExporter, - INotebookExtensibility, - INotebookImporter, - INotebookModel, - INotebookProvider, - IStatusProvider, - IThemeFinder, - ITrustService -} from '../types'; -import { getNextUntitledCounter } from './nativeEditorStorage'; -import { NotebookModelEditEvent } from './notebookModelEditEvent'; -import { INotebookStorageProvider } from './notebookStorageProvider'; - -// Class that is registered as the custom editor provider for notebooks. VS code will call into this class when -// opening an ipynb file. This class then creates a backing storage, model, and opens a view for the file. -@injectable() -export class NativeEditorProvider implements INotebookEditorProvider, CustomEditorProvider { - public get onDidChangeActiveNotebookEditor(): Event { - return this._onDidChangeActiveNotebookEditor.event; - } - public get onDidCloseNotebookEditor(): Event { - return this._onDidCloseNotebookEditor.event; - } - public get onDidOpenNotebookEditor(): Event { - return this._onDidOpenNotebookEditor.event; - } - public get activeEditor(): INotebookEditor | undefined { - return this.editors.find((e) => e.visible && e.active); - } - public get onDidChangeCustomDocument(): Event { - return this._onDidEdit.event; - } - - public get editors(): INotebookEditor[] { - return [...this.openedEditors]; - } - // Note, this constant has to match the value used in the package.json to register the webview custom editor. - public static readonly customEditorViewType = 'ms-python.python.notebook.ipynb'; - protected readonly _onDidChangeActiveNotebookEditor = new EventEmitter(); - protected readonly _onDidOpenNotebookEditor = new EventEmitter(); - protected readonly _onDidEdit = new EventEmitter(); - protected customDocuments = new Map(); - private readonly _onDidCloseNotebookEditor = new EventEmitter(); - private openedEditors: Set = new Set(); - private models = new Set(); - private _id = uuid(); - private untitledCounter = 1; - constructor( - @inject(IServiceContainer) protected readonly serviceContainer: IServiceContainer, - @inject(IAsyncDisposableRegistry) protected readonly asyncRegistry: IAsyncDisposableRegistry, - @inject(IDisposableRegistry) protected readonly disposables: IDisposableRegistry, - @inject(IWorkspaceService) protected readonly workspace: IWorkspaceService, - @inject(IConfigurationService) protected readonly configuration: IConfigurationService, - @inject(ICustomEditorService) private customEditorService: ICustomEditorService, - @inject(INotebookStorageProvider) protected readonly storage: INotebookStorageProvider, - @inject(INotebookProvider) private readonly notebookProvider: INotebookProvider, - @inject(IDataScienceFileSystem) protected readonly fs: IDataScienceFileSystem - ) { - traceInfo(`id is ${this._id}`); - - // Register for the custom editor service. - customEditorService.registerCustomEditorProvider(NativeEditorProvider.customEditorViewType, this, { - webviewOptions: { - enableFindWidget: true, - retainContextWhenHidden: true - }, - supportsMultipleEditorsPerDocument: false - }); - } - - public async openCustomDocument( - uri: Uri, - context: CustomDocumentOpenContext, // This has info about backups. right now we use our own data. - _cancellation: CancellationToken - ): Promise { - const model = await this.loadModel({ - file: uri, - backupId: context.backupId, - skipLoadingDirtyContents: context.backupId === undefined - }); - return { - uri, - dispose: () => model.dispose() - }; - } - public async saveCustomDocument(document: CustomDocument, cancellation: CancellationToken): Promise { - const model = await this.loadModel({ file: document.uri }); - return this.storage.save(model, cancellation); - } - public async saveCustomDocumentAs(document: CustomDocument, targetResource: Uri): Promise { - const model = await this.loadModel({ file: document.uri }); - return this.storage.saveAs(model, targetResource); - } - public async revertCustomDocument(document: CustomDocument, cancellation: CancellationToken): Promise { - const model = await this.loadModel({ file: document.uri }); - return this.storage.revert(model, cancellation); - } - public async backupCustomDocument( - document: CustomDocument, - _context: CustomDocumentBackupContext, - cancellation: CancellationToken - ): Promise { - const model = await this.loadModel({ file: document.uri }); - const id = this.storage.generateBackupId(model); - await this.storage.backup(model, cancellation, id); - return { - id, - delete: () => this.storage.deleteBackup(model, id).ignoreErrors() // This cleans up after save has happened. - }; - } - - public async resolveCustomEditor(document: CustomDocument, panel: WebviewPanel) { - this.customDocuments.set(document.uri.fsPath, document); - await this.loadNotebookEditor(document.uri, panel); - } - - public async resolveCustomDocument(document: CustomDocument): Promise { - this.customDocuments.set(document.uri.fsPath, document); - await this.loadModel({ file: document.uri }); - } - - public async open(file: Uri): Promise { - // Create a deferred promise that will fire when the notebook - // actually opens - const deferred = createDeferred(); - - // Sign up for open event once it does open - let disposable: Disposable | undefined; - const handler = (e: INotebookEditor) => { - if (arePathsSame(e.file.fsPath, file.fsPath)) { - if (disposable) { - disposable.dispose(); - } - deferred.resolve(e); - } - }; - disposable = this._onDidOpenNotebookEditor.event(handler); - - // Send an open command. - this.customEditorService.openEditor(file, NativeEditorProvider.customEditorViewType).ignoreErrors(); - - // Promise should resolve when the file opens. - return deferred.promise; - } - - public async show(file: Uri): Promise { - return this.open(file); - } - - @captureTelemetry(Telemetry.CreateNewNotebook, undefined, false) - public async createNew(possibleContents?: string, title?: string): Promise { - // Create a new URI for the dummy file using our root workspace path - const uri = this.getNextNewNotebookUri(title); - - // Set these contents into the storage before the file opens. Make sure not - // load from the memento storage though as this is an entirely brand new file. - await this.loadModel({ file: uri, possibleContents, skipLoadingDirtyContents: true }); - - return this.open(uri); - } - - public async loadModel(options: IModelLoadOptions): Promise { - // Get the model that may match this file - let model = [...this.models.values()].find((m) => this.fs.arePathsSame(m.file, options.file)); - if (!model) { - // Every time we load a new untitled file, up the counter past the max value for this counter - this.untitledCounter = getNextUntitledCounter(options.file, this.untitledCounter); - - // Load our model from our storage object. - model = await this.storage.getOrCreateModel(options); - - // Make sure to listen to events on the model - this.trackModel(model); - } - return model; - } - - protected createNotebookEditor(model: INotebookModel, panel?: WebviewPanel): NativeEditor { - const editor = new NativeEditor( - this.serviceContainer.getAll(IInteractiveWindowListener), - this.serviceContainer.get(ILiveShareApi), - this.serviceContainer.get(IApplicationShell), - this.serviceContainer.get(IDocumentManager), - this.serviceContainer.get(IWebviewPanelProvider), - this.serviceContainer.get(IDisposableRegistry), - this.serviceContainer.get(ICodeCssGenerator), - this.serviceContainer.get(IThemeFinder), - this.serviceContainer.get(IStatusProvider), - this.serviceContainer.get(IDataScienceFileSystem), - this.serviceContainer.get(IConfigurationService), - this.serviceContainer.get(ICommandManager), - this.serviceContainer.get(INotebookExporter), - this.serviceContainer.get(IWorkspaceService), - this.serviceContainer.get(NativeEditorSynchronizer), - this.serviceContainer.get(INotebookEditorProvider), - this.serviceContainer.get(IDataViewerFactory), - this.serviceContainer.get(IJupyterVariableDataProviderFactory), - this.serviceContainer.get(IJupyterVariables, Identifiers.ALL_VARIABLES), - this.serviceContainer.get(IJupyterDebugger), - this.serviceContainer.get(INotebookImporter), - this.serviceContainer.get(IDataScienceErrorHandler), - this.serviceContainer.get(IMemento, GLOBAL_MEMENTO), - this.serviceContainer.get(IMemento, WORKSPACE_MEMENTO), - this.serviceContainer.get(IExperimentsManager), - this.serviceContainer.get(IAsyncDisposableRegistry), - this.serviceContainer.get(INotebookProvider), - this.serviceContainer.get(UseCustomEditorApi), - this.serviceContainer.get(ITrustService), - model, - panel, - this.serviceContainer.get(KernelSelector), - this.serviceContainer.get(INotebookExtensibility) - ); - this.openedEditor(editor); - return editor; - } - - protected async loadNotebookEditor(resource: Uri, panel?: WebviewPanel) { - try { - // Get the model - const model = await this.loadModel({ file: resource }); - - // Load it - return this.createNotebookEditor(model, panel); - } catch (exc) { - // Send telemetry indicating a failure - sendTelemetryEvent(Telemetry.OpenNotebookFailure); - throw exc; - } - } - - protected openedEditor(editor: INotebookEditor): void { - this.disposables.push(editor.onDidChangeViewState(this.onChangedViewState, this)); - this.openedEditors.add(editor); - editor.closed(this.closedEditor, this, this.disposables); - this._onDidOpenNotebookEditor.fire(editor); - } - - protected async modelEdited(model: INotebookModel, change: NotebookModelChange) { - // Find the document associated with this edit. - const document = this.customDocuments.get(model.file.fsPath); - - // Tell VS code about model changes if not caused by vs code itself - if (document && change.kind !== 'save' && change.kind !== 'saveAs' && change.source === 'user') { - this._onDidEdit.fire(new NotebookModelEditEvent(document, model, change)); - } - } - - private closedEditor(editor: INotebookEditor): void { - this.openedEditors.delete(editor); - this._onDidCloseNotebookEditor.fire(editor); - } - private trackModel(model: INotebookModel) { - if (!this.models.has(model)) { - this.models.add(model); - this.disposables.push(model.onDidDispose(this.onDisposedModel.bind(this, model))); - this.disposables.push(model.onDidEdit(this.modelEdited.bind(this, model))); - } - } - - private onDisposedModel(model: INotebookModel) { - // When model goes away, dispose of the associated notebook (as all of the editors have closed down) - this.notebookProvider - .getOrCreateNotebook({ identity: model.file, getOnly: true }) - .then((n) => n?.dispose()) - .ignoreErrors(); - this.models.delete(model); - } - - private onChangedViewState(): void { - this._onDidChangeActiveNotebookEditor.fire(this.activeEditor); - } - - private getNextNewNotebookUri(title?: string): Uri { - return generateNewNotebookUri(this.untitledCounter, this.workspace.rootPath, title); - } -} diff --git a/src/client/datascience/notebookStorage/nativeEditorStorage.ts b/src/client/datascience/notebookStorage/nativeEditorStorage.ts deleted file mode 100644 index f3e71c597de0..000000000000 --- a/src/client/datascience/notebookStorage/nativeEditorStorage.ts +++ /dev/null @@ -1,481 +0,0 @@ -import type { nbformat } from '@jupyterlab/coreutils'; -import { inject, injectable, named } from 'inversify'; -import * as path from 'path'; -import * as uuid from 'uuid/v4'; -import { CancellationToken, Memento, Uri } from 'vscode'; -import { createCodeCell } from '../../../datascience-ui/common/cellFactory'; -import { traceError } from '../../common/logger'; -import { isFileNotFoundError } from '../../common/platform/errors'; - -import { GLOBAL_MEMENTO, ICryptoUtils, IExtensionContext, IMemento, WORKSPACE_MEMENTO } from '../../common/types'; -import { isUntitledFile, noop } from '../../common/utils/misc'; -import { sendTelemetryEvent } from '../../telemetry'; -import { Identifiers, KnownNotebookLanguages, Telemetry } from '../constants'; -import { InvalidNotebookFileError } from '../jupyter/invalidNotebookFileError'; -import { INotebookModelFactory } from '../notebookStorage/types'; -import { - CellState, - IDataScienceFileSystem, - IJupyterExecution, - IModelLoadOptions, - INotebookModel, - INotebookStorage, - ITrustService -} from '../types'; - -// tslint:disable-next-line:no-require-imports no-var-requires -import detectIndent = require('detect-indent'); -import { VSCodeNotebookModel } from './vscNotebookModel'; - -export const KeyPrefix = 'notebook-storage-'; -const NotebookTransferKey = 'notebook-transfered'; - -export function getNextUntitledCounter(file: Uri | undefined, currentValue: number): number { - if (file && isUntitledFile(file)) { - const basename = path.basename(file.fsPath, 'ipynb'); - const extname = path.extname(file.fsPath); - if (extname.toLowerCase() === '.ipynb') { - // See if ends with - - const match = /.*-(\d+)/.exec(basename); - if (match && match[1]) { - const fileValue = parseInt(match[1], 10); - if (fileValue) { - return Math.max(currentValue, fileValue + 1); - } - } - } - } - - return currentValue; -} - -@injectable() -export class NativeEditorStorage implements INotebookStorage { - // Keep track of if we are backing up our file already - private backingUp = false; - // If backup requests come in while we are already backing up save the most recent one here - private backupRequested: { model: INotebookModel; cancellation: CancellationToken } | undefined; - - constructor( - @inject(IJupyterExecution) private jupyterExecution: IJupyterExecution, - @inject(IDataScienceFileSystem) private fs: IDataScienceFileSystem, - @inject(ICryptoUtils) private crypto: ICryptoUtils, - @inject(IExtensionContext) private context: IExtensionContext, - @inject(IMemento) @named(GLOBAL_MEMENTO) private globalStorage: Memento, - @inject(IMemento) @named(WORKSPACE_MEMENTO) private localStorage: Memento, - @inject(ITrustService) private trustService: ITrustService, - @inject(INotebookModelFactory) private readonly factory: INotebookModelFactory - ) {} - private static isUntitledFile(file: Uri) { - return isUntitledFile(file); - } - - public generateBackupId(model: INotebookModel): string { - return `${path.basename(model.file.fsPath)}-${uuid()}`; - } - public get(_file: Uri): INotebookModel | undefined { - return undefined; - } - public getOrCreateModel(options: IModelLoadOptions): Promise { - return this.loadFromFile(options); - } - public async save(model: INotebookModel, _cancellation: CancellationToken): Promise { - const contents = model.getContent(); - const parallelize = [this.fs.writeFile(model.file, contents)]; - if (model.isTrusted) { - parallelize.push(this.trustService.trustNotebook(model.file, contents)); - } - await Promise.all(parallelize); - model.update({ - source: 'user', - kind: 'save', - oldDirty: model.isDirty, - newDirty: false - }); - } - - public async saveAs(model: INotebookModel, file: Uri): Promise { - const contents = model.getContent(); - const parallelize = [this.fs.writeFile(file, contents)]; - if (model.isTrusted) { - parallelize.push(this.trustService.trustNotebook(file, contents)); - } - await Promise.all(parallelize); - if (model instanceof VSCodeNotebookModel) { - return; - } - model.update({ - source: 'user', - kind: 'saveAs', - oldDirty: model.isDirty, - newDirty: false, - target: file, - sourceUri: model.file - }); - } - public async backup(model: INotebookModel, cancellation: CancellationToken, backupId?: string): Promise { - // If we are already backing up, save this request replacing any other previous requests - if (this.backingUp) { - this.backupRequested = { model, cancellation }; - return; - } - this.backingUp = true; - // Should send to extension context storage path - return this.storeContentsInHotExitFile(model, cancellation, backupId).finally(() => { - this.backingUp = false; - - // If there is a backup request waiting, then clear and start it - if (this.backupRequested) { - const requested = this.backupRequested; - this.backupRequested = undefined; - this.backup(requested.model, requested.cancellation).catch((error) => { - traceError(`Error in backing up NativeEditor Storage: ${error}`); - }); - } - }); - } - - public async revert(model: INotebookModel, _cancellation: CancellationToken): Promise { - // Revert to what is in the real file. This is only used for the custom editor - await this.loadFromFile({ file: model.file, skipLoadingDirtyContents: true }); - } - - public async deleteBackup(model: INotebookModel, backupId: string): Promise { - return this.clearHotExit(model.file, backupId); - } - /** - * Stores the uncommitted notebook changes into a temporary location. - * Also keep track of the current time. This way we can check whether changes were - * made to the file since the last time uncommitted changes were stored. - */ - private async storeContentsInHotExitFile( - model: INotebookModel, - cancelToken?: CancellationToken, - backupId?: string - ): Promise { - const contents = model.getContent(); - const key = backupId || this.getStaticStorageKey(model.file); - const filePath = this.getHashedFileName(key); - - // Keep track of the time when this data was saved. - // This way when we retrieve the data we can compare it against last modified date of the file. - const specialContents = contents ? JSON.stringify({ contents, lastModifiedTimeMs: Date.now() }) : undefined; - return this.writeToStorage(model.file, filePath, specialContents, cancelToken); - } - - private async clearHotExit(file: Uri, backupId?: string): Promise { - const key = backupId || this.getStaticStorageKey(file); - const filePath = this.getHashedFileName(key); - await this.writeToStorage(undefined, filePath); - } - - private async writeToStorage( - owningFile: Uri | undefined, - filePath: string, - contents?: string, - cancelToken?: CancellationToken - ): Promise { - try { - if (!cancelToken?.isCancellationRequested) { - if (contents) { - await this.fs.createLocalDirectory(path.dirname(filePath)); - if (!cancelToken?.isCancellationRequested) { - if (owningFile) { - this.trustService.trustNotebook(owningFile, contents).ignoreErrors(); - } - await this.fs.writeLocalFile(filePath, contents); - } - } else { - await this.fs.deleteLocalFile(filePath).catch((ex) => { - // No need to log error if file doesn't exist. - if (!isFileNotFoundError(ex)) { - traceError('Failed to delete hotExit file. Possible it does not exist', ex); - } - }); - } - } - } catch (exc) { - traceError(`Error writing storage for ${filePath}: `, exc); - } - } - private async extractPythonMainVersion(notebookData: Partial): Promise { - if ( - notebookData && - notebookData.metadata && - notebookData.metadata.language_info && - notebookData.metadata.language_info.codemirror_mode && - // tslint:disable-next-line: no-any - typeof (notebookData.metadata.language_info.codemirror_mode as any).version === 'number' - ) { - // tslint:disable-next-line: no-any - return (notebookData.metadata.language_info.codemirror_mode as any).version; - } - // Use the active interpreter - const usableInterpreter = await this.jupyterExecution.getUsableJupyterPython(); - return usableInterpreter && usableInterpreter.version ? usableInterpreter.version.major : 3; - } - - private sendLanguageTelemetry(notebookJson: Partial) { - try { - // See if we have a language - let language = ''; - if (notebookJson.metadata?.language_info?.name) { - language = notebookJson.metadata?.language_info?.name; - } else if (notebookJson.metadata?.kernelspec?.language) { - language = notebookJson.metadata?.kernelspec?.language.toString(); - } - if (language && !KnownNotebookLanguages.includes(language.toLowerCase())) { - language = 'unknown'; - } - if (language) { - sendTelemetryEvent(Telemetry.NotebookLanguage, undefined, { language }); - } - } catch { - // If this fails, doesn't really matter - noop(); - } - } - private async loadFromFile(options: IModelLoadOptions): Promise { - try { - // Attempt to read the contents if a viable file - const contents = NativeEditorStorage.isUntitledFile(options.file) - ? options.possibleContents - : await this.fs.readFile(options.file); - - // Get backup id from the options if available. - const backupId = options.backupId ? options.backupId : this.getStaticStorageKey(options.file); - - // If skipping dirty contents, delete the dirty hot exit file now - if (options.skipLoadingDirtyContents) { - await this.clearHotExit(options.file, backupId); - } - - // See if this file was stored in storage prior to shutdown - const dirtyContents = options.skipLoadingDirtyContents - ? undefined - : await this.getStoredContents(options.file, backupId); - if (dirtyContents) { - // This means we're dirty. Indicate dirty and load from this content - return this.loadContents(options.file, dirtyContents, true, options.isNative); - } else { - // Load without setting dirty - return this.loadContents(options.file, contents, undefined, options.isNative); - } - } catch (ex) { - // May not exist at this time. Should always have a single cell though - traceError(`Failed to load notebook file ${options.file.toString()}`, ex); - return this.factory.createModel( - { - trusted: true, - file: options.file, - cells: [], - crypto: this.crypto, - globalMemento: this.globalStorage - }, - options.isNative - ); - } - } - - private createEmptyCell(id: string) { - return { - id, - line: 0, - file: Identifiers.EmptyFileName, - state: CellState.finished, - data: createCodeCell() - }; - } - - private async loadContents( - file: Uri, - contents: string | undefined, - isInitiallyDirty = false, - forVSCodeNotebook?: boolean - ) { - // tslint:disable-next-line: no-any - const json = contents ? (JSON.parse(contents) as Partial) : undefined; - - // Double check json (if we have any) - if (json && !json.cells) { - throw new InvalidNotebookFileError(file.fsPath); - } - - // Then compute indent. It's computed from the contents - const indentAmount = contents ? detectIndent(contents).indent : undefined; - - // Then save the contents. We'll stick our cells back into this format when we save - if (json) { - // Log language or kernel telemetry - this.sendLanguageTelemetry(json); - } - - // Extract cells from the json - const cells = json ? (json.cells as (nbformat.ICodeCell | nbformat.IRawCell | nbformat.IMarkdownCell)[]) : []; - - // Remap the ids - const remapped = cells.map((c, index) => { - return { - id: `NotebookImport#${index}`, - file: Identifiers.EmptyFileName, - line: 0, - state: CellState.finished, - data: c - }; - }); - - if (!forVSCodeNotebook) { - // Make sure at least one - if (remapped.length === 0) { - remapped.splice(0, 0, this.createEmptyCell(uuid())); - } - } - const pythonNumber = json ? await this.extractPythonMainVersion(json) : 3; - - const model = this.factory.createModel( - { - trusted: isUntitledFile(file) || json === undefined, - file, - cells: remapped, - notebookJson: json, - indentAmount, - pythonNumber, - initiallyDirty: isInitiallyDirty, - crypto: this.crypto, - globalMemento: this.globalStorage - }, - forVSCodeNotebook - ); - - // If no contents or untitled, this is a newly created file - // If dirty, that means it's been edited before in our extension - if (contents !== undefined && !isUntitledFile(file) && !isInitiallyDirty && !model.isTrusted) { - const isNotebookTrusted = await this.trustService.isNotebookTrusted(file, model.getContent()); - if (isNotebookTrusted) { - model.trust(); - } - } else { - model.trust(); - } - - return model; - } - - private getStaticStorageKey(file: Uri): string { - return `${KeyPrefix}${file.toString()}`; - } - - /** - * Gets any unsaved changes to the notebook file from the old locations. - * If the file has been modified since the uncommitted changes were stored, then ignore the uncommitted changes. - * - * @private - * @returns {(Promise)} - * @memberof NativeEditor - */ - private async getStoredContents(file: Uri, backupId?: string): Promise { - const key = backupId || this.getStaticStorageKey(file); - - // First look in the global storage file location - let result = await this.getStoredContentsFromFile(file, key); - if (!result) { - result = await this.getStoredContentsFromGlobalStorage(file, key); - if (!result) { - result = await this.getStoredContentsFromLocalStorage(file, key); - } - } - - return result; - } - - private async getStoredContentsFromFile(file: Uri, key: string): Promise { - const filePath = this.getHashedFileName(key); - try { - // Use this to read from the extension global location - const contents = await this.fs.readLocalFile(filePath); - const data = JSON.parse(contents); - // Check whether the file has been modified since the last time the contents were saved. - if (data && data.lastModifiedTimeMs && file.scheme === 'file') { - const stat = await this.fs.stat(file); - if (stat.mtime > data.lastModifiedTimeMs) { - return; - } - } - if (data && data.contents) { - return data.contents; - } - } catch (exc) { - // No need to log error if file doesn't exist. - if (!isFileNotFoundError(exc)) { - traceError(`Exception reading from temporary storage for ${key}`, exc); - } - } - } - - private async getStoredContentsFromGlobalStorage(file: Uri, key: string): Promise { - try { - const data = this.globalStorage.get<{ contents?: string; lastModifiedTimeMs?: number }>(key); - - // If we have data here, make sure we eliminate any remnants of storage - if (data) { - await this.transferStorage(); - } - - // Check whether the file has been modified since the last time the contents were saved. - if (data && data.lastModifiedTimeMs && file.scheme === 'file') { - const stat = await this.fs.stat(file); - if (stat.mtime > data.lastModifiedTimeMs) { - return; - } - } - if (data && data.contents) { - return data.contents; - } - } catch { - noop(); - } - } - - private async getStoredContentsFromLocalStorage(_file: Uri, key: string): Promise { - const workspaceData = this.localStorage.get(key); - if (workspaceData) { - // Make sure to clear so we don't use this again. - this.localStorage.update(key, undefined); - - return workspaceData; - } - } - - // VS code recommended we use the hidden '_values' to iterate over all of the entries in - // the global storage map and delete the ones we own. - private async transferStorage(): Promise { - const promises: Thenable[] = []; - - // Indicate we ran this function - await this.globalStorage.update(NotebookTransferKey, true); - - try { - // tslint:disable-next-line: no-any - if ((this.globalStorage as any)._value) { - // tslint:disable-next-line: no-any - const keys = Object.keys((this.globalStorage as any)._value); - [...keys].forEach((k: string) => { - if (k.startsWith(KeyPrefix)) { - // Remove from the map so that global storage does not have this anymore. - // Use the real API here as we don't know how the map really gets updated. - promises.push(this.globalStorage.update(k, undefined)); - } - }); - } - } catch (e) { - traceError('Exception eliminating global storage parts:', e); - } - - return Promise.all(promises); - } - - private getHashedFileName(key: string): string { - const file = `${this.crypto.createHash(key, 'string')}.ipynb`; - return path.join(this.context.globalStoragePath, file); - } -} diff --git a/src/client/datascience/notebookStorage/notebookModel.ts b/src/client/datascience/notebookStorage/notebookModel.ts deleted file mode 100644 index d5dc511fadcb..000000000000 --- a/src/client/datascience/notebookStorage/notebookModel.ts +++ /dev/null @@ -1,261 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { nbformat } from '@jupyterlab/coreutils/lib/nbformat'; -import * as fastDeepEqual from 'fast-deep-equal'; -import * as uuid from 'uuid/v4'; -import { Memento, Uri } from 'vscode'; -import { concatMultilineString, splitMultilineString } from '../../../datascience-ui/common'; -import { createCodeCell } from '../../../datascience-ui/common/cellFactory'; -import { ICryptoUtils } from '../../common/types'; -import { Identifiers } from '../constants'; -import { IEditorContentChange, NotebookModelChange } from '../interactive-common/interactiveWindowTypes'; -import { CellState, ICell } from '../types'; -import { BaseNotebookModel } from './baseModel'; - -export class NativeEditorNotebookModel extends BaseNotebookModel { - public get id() { - return this._id; - } - private _id = uuid(); - private saveChangeCount: number = 0; - private changeCount: number = 0; - public get isDirty(): boolean { - return this.changeCount !== this.saveChangeCount; - } - constructor( - isTrusted: boolean, - file: Uri, - cells: ICell[], - globalMemento: Memento, - crypto: ICryptoUtils, - json: Partial = {}, - indentAmount: string = ' ', - pythonNumber: number = 3, - isInitiallyDirty: boolean = false - ) { - super(isTrusted, file, cells, globalMemento, crypto, json, indentAmount, pythonNumber); - if (isInitiallyDirty) { - // This means we're dirty. Indicate dirty and load from this content - this.saveChangeCount = -1; - } - } - - public async applyEdits(edits: readonly NotebookModelChange[]): Promise { - edits.forEach((e) => this.update({ ...e, source: 'redo' })); - } - public async undoEdits(edits: readonly NotebookModelChange[]): Promise { - edits.forEach((e) => this.update({ ...e, source: 'undo' })); - } - - protected handleRedo(change: NotebookModelChange): boolean { - let changed = false; - switch (change.kind) { - case 'clear': - changed = this.clearOutputs(); - break; - case 'edit': - changed = this.editCell(change.forward, change.id); - break; - case 'insert': - changed = this.insertCell(change.cell, change.index); - break; - case 'changeCellType': - changed = this.changeCellType(change.cell); - break; - case 'modify': - changed = this.modifyCells(change.newCells); - break; - case 'remove': - changed = this.removeCell(change.cell); - break; - case 'remove_all': - changed = this.removeAllCells(change.newCellId); - break; - case 'swap': - changed = this.swapCells(change.firstCellId, change.secondCellId); - break; - case 'updateCellExecutionCount': - changed = this.updateCellExecutionCount(change.cellId, change.executionCount); - break; - case 'save': - this.saveChangeCount = this.changeCount; - break; - case 'saveAs': - this.saveChangeCount = this.changeCount; - this.changeCount = this.saveChangeCount = 0; - this._file = change.target; - break; - default: - changed = super.handleRedo(change); - break; - } - - // Dirty state comes from undo. At least VS code will track it that way. However - // skip file changes as we don't forward those to VS code - if (change.kind !== 'save' && change.kind !== 'saveAs') { - this.changeCount += 1; - } - - return changed; - } - - protected handleUndo(change: NotebookModelChange): boolean { - let changed = false; - switch (change.kind) { - case 'clear': - changed = !fastDeepEqual(this.cells, change.oldCells); - this._cells = change.oldCells; - break; - case 'edit': - this.editCell(change.reverse, change.id); - changed = true; - break; - case 'changeCellType': - this.changeCellType(change.cell); - changed = true; - break; - case 'insert': - changed = this.removeCell(change.cell); - break; - case 'modify': - changed = this.modifyCells(change.oldCells); - break; - case 'remove': - changed = this.insertCell(change.cell, change.index); - break; - case 'remove_all': - this._cells = change.oldCells; - changed = true; - break; - case 'swap': - changed = this.swapCells(change.firstCellId, change.secondCellId); - break; - default: - break; - } - - // Dirty state comes from undo. At least VS code will track it that way. - // Note unlike redo, 'file' and 'version' are not possible on undo as - // we don't send them to VS code. - this.changeCount -= 1; - - return changed; - } - - private removeAllCells(newCellId: string) { - this._cells = []; - this._cells.push(this.createEmptyCell(newCellId)); - return true; - } - - private applyCellContentChange(change: IEditorContentChange, id: string): boolean { - const normalized = change.text.replace(/\r/g, ''); - - // Figure out which cell we're editing. - const index = this.cells.findIndex((c) => c.id === id); - if (index >= 0) { - // This is an actual edit. - const contents = concatMultilineString(this.cells[index].data.source); - const before = contents.substr(0, change.rangeOffset); - const after = contents.substr(change.rangeOffset + change.rangeLength); - const newContents = `${before}${normalized}${after}`; - if (contents !== newContents) { - const newCell = { - ...this.cells[index], - data: { ...this.cells[index].data, source: splitMultilineString(newContents) } - }; - this._cells[index] = this.asCell(newCell); - return true; - } - } - return false; - } - - private editCell(changes: IEditorContentChange[], id: string): boolean { - // Apply the changes to the visible cell list - if (changes && changes.length) { - return changes.map((c) => this.applyCellContentChange(c, id)).reduce((p, c) => p || c, false); - } - - return false; - } - - private swapCells(firstCellId: string, secondCellId: string) { - const first = this.cells.findIndex((v) => v.id === firstCellId); - const second = this.cells.findIndex((v) => v.id === secondCellId); - if (first >= 0 && second >= 0 && first !== second) { - const temp = { ...this.cells[first] }; - this._cells[first] = this.asCell(this.cells[second]); - this._cells[second] = this.asCell(temp); - return true; - } - return false; - } - - private updateCellExecutionCount(cellId: string, executionCount?: number) { - const index = this.cells.findIndex((v) => v.id === cellId); - if (index >= 0) { - this._cells[index].data.execution_count = - typeof executionCount === 'number' && executionCount > 0 ? executionCount : null; - return true; - } - return false; - } - - private modifyCells(cells: ICell[]): boolean { - // Update these cells in our list - cells.forEach((c) => { - const index = this.cells.findIndex((v) => v.id === c.id); - this._cells[index] = this.asCell(c); - }); - return true; - } - - private changeCellType(cell: ICell): boolean { - // Update the cell in our list. - const index = this.cells.findIndex((v) => v.id === cell.id); - this._cells[index] = this.asCell(cell); - return true; - } - - private removeCell(cell: ICell): boolean { - const index = this.cells.findIndex((c) => c.id === cell.id); - if (index >= 0) { - this.cells.splice(index, 1); - return true; - } - return false; - } - - private clearOutputs(): boolean { - const newCells = this.cells.map((c) => - this.asCell({ ...c, data: { ...c.data, execution_count: null, outputs: [] } }) - ); - const result = !fastDeepEqual(newCells, this.cells); - this._cells = newCells; - return result; - } - - private insertCell(cell: ICell, index: number): boolean { - // Insert a cell into our visible list based on the index. They should be in sync - this._cells.splice(index, 0, cell); - return true; - } - - // tslint:disable-next-line: no-any - private asCell(cell: any): ICell { - // Works around problems with setting a cell to another one in the nyc compiler. - return cell as ICell; - } - - private createEmptyCell(id: string) { - return { - id, - line: 0, - file: Identifiers.EmptyFileName, - state: CellState.finished, - data: createCodeCell() - }; - } -} diff --git a/src/client/datascience/notebookStorage/notebookModelEditEvent.ts b/src/client/datascience/notebookStorage/notebookModelEditEvent.ts deleted file mode 100644 index 9fbbcc17b281..000000000000 --- a/src/client/datascience/notebookStorage/notebookModelEditEvent.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { CustomDocument, CustomDocumentEditEvent } from '../../common/application/types'; -import { NotebookModelChange } from '../interactive-common/interactiveWindowTypes'; -import { INotebookModel } from '../types'; -import { NativeEditorNotebookModel } from './notebookModel'; -export class NotebookModelEditEvent implements CustomDocumentEditEvent { - public label?: string | undefined; - constructor( - public readonly document: CustomDocument, - private readonly model: INotebookModel, - private readonly change: NotebookModelChange - ) { - this.label = change.kind; - } - public undo(): void | Thenable { - return (this.model as NativeEditorNotebookModel).undoEdits([{ ...this.change, source: 'undo' }]); - } - public redo(): void | Thenable { - return (this.model as NativeEditorNotebookModel).applyEdits([{ ...this.change, source: 'redo' }]); - } -} diff --git a/src/client/datascience/notebookStorage/notebookStorageProvider.ts b/src/client/datascience/notebookStorage/notebookStorageProvider.ts deleted file mode 100644 index 702adfb9e1a4..000000000000 --- a/src/client/datascience/notebookStorage/notebookStorageProvider.ts +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { EventEmitter, Uri } from 'vscode'; -import { CancellationToken } from 'vscode-jsonrpc'; -import { IWorkspaceService } from '../../common/application/types'; -import { IDisposable, IDisposableRegistry } from '../../common/types'; -import { generateNewNotebookUri } from '../common'; -import { IModelLoadOptions, INotebookModel, INotebookStorage } from '../types'; -import { getNextUntitledCounter } from './nativeEditorStorage'; -import { VSCodeNotebookModel } from './vscNotebookModel'; - -// tslint:disable-next-line:no-require-imports no-var-requires - -export const INotebookStorageProvider = Symbol.for('INotebookStorageProvider'); -export interface INotebookStorageProvider extends INotebookStorage { - createNew(contents?: string, forVSCodeNotebook?: boolean): Promise; -} -@injectable() -export class NotebookStorageProvider implements INotebookStorageProvider { - public get onSavedAs() { - return this._savedAs.event; - } - private static untitledCounter = 1; - private readonly _savedAs = new EventEmitter<{ new: Uri; old: Uri }>(); - private readonly storageAndModels = new Map>(); - private readonly resolvedStorageAndModels = new Map(); - private models = new Set(); - private readonly disposables: IDisposable[] = []; - constructor( - @inject(INotebookStorage) private readonly storage: INotebookStorage, - @inject(IDisposableRegistry) disposables: IDisposableRegistry, - @inject(IWorkspaceService) private readonly workspace: IWorkspaceService - ) { - disposables.push(this); - } - public async save(model: INotebookModel, cancellation: CancellationToken) { - await this.storage.save(model, cancellation); - } - public async saveAs(model: INotebookModel, targetResource: Uri) { - const oldUri = model.file; - await this.storage.saveAs(model, targetResource); - if (model instanceof VSCodeNotebookModel) { - return; - } - this.trackModel(model); - this.storageAndModels.delete(oldUri.toString()); - this.storageAndModels.set(targetResource.toString(), Promise.resolve(model)); - } - public generateBackupId(model: INotebookModel): string { - return this.storage.generateBackupId(model); - } - public backup(model: INotebookModel, cancellation: CancellationToken, backupId?: string) { - return this.storage.backup(model, cancellation, backupId); - } - public revert(model: INotebookModel, cancellation: CancellationToken) { - return this.storage.revert(model, cancellation); - } - public deleteBackup(model: INotebookModel, backupId?: string) { - return this.storage.deleteBackup(model, backupId); - } - public get(file: Uri): INotebookModel | undefined { - return this.resolvedStorageAndModels.get(file.toString()); - } - - public getOrCreateModel(options: IModelLoadOptions): Promise { - const key = options.file.toString(); - if (!this.storageAndModels.has(key)) { - // Every time we load a new untitled file, up the counter past the max value for this counter - NotebookStorageProvider.untitledCounter = getNextUntitledCounter( - options.file, - NotebookStorageProvider.untitledCounter - ); - const promise = this.storage.getOrCreateModel(options); - this.storageAndModels.set(key, promise.then(this.trackModel.bind(this))); - } - return this.storageAndModels.get(key)!; - } - public dispose() { - while (this.disposables.length) { - this.disposables.shift()?.dispose(); // NOSONAR - } - } - - public async createNew(possibleContents?: string, forVSCodeNotebooks?: boolean): Promise { - // Create a new URI for the dummy file using our root workspace path - const uri = this.getNextNewNotebookUri(forVSCodeNotebooks); - - // Always skip loading from the hot exit file. When creating a new file we want a new file. - return this.getOrCreateModel({ - file: uri, - possibleContents, - skipLoadingDirtyContents: true, - isNative: forVSCodeNotebooks - }); - } - - private getNextNewNotebookUri(forVSCodeNotebooks?: boolean): Uri { - return generateNewNotebookUri( - NotebookStorageProvider.untitledCounter, - this.workspace.rootPath, - undefined, - forVSCodeNotebooks - ); - } - - private trackModel(model: INotebookModel): INotebookModel { - this.disposables.push(model); - this.models.add(model); - this.resolvedStorageAndModels.set(model.file.toString(), model); - // When a model is no longer used, ensure we remove it from the cache. - model.onDidDispose( - () => { - this.models.delete(model); - this.storageAndModels.delete(model.file.toString()); - this.resolvedStorageAndModels.delete(model.file.toString()); - }, - this, - this.disposables - ); - return model; - } -} diff --git a/src/client/datascience/notebookStorage/types.ts b/src/client/datascience/notebookStorage/types.ts deleted file mode 100644 index f26f2c5aafe0..000000000000 --- a/src/client/datascience/notebookStorage/types.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { nbformat } from '@jupyterlab/coreutils/lib/nbformat'; -import { Memento, Uri } from 'vscode'; -import { ICryptoUtils } from '../../common/types'; -import { ICell, INotebookModel } from '../types'; - -export const INotebookModelFactory = Symbol('INotebookModelFactory'); -export interface INotebookModelFactory { - createModel( - options: { - trusted: boolean; - file: Uri; - cells: ICell[]; - notebookJson?: Partial; - indentAmount?: string; - pythonNumber?: number; - initiallyDirty?: boolean; - crypto: ICryptoUtils; - globalMemento: Memento; - }, - forVSCodeNotebook?: boolean - ): INotebookModel; -} diff --git a/src/client/datascience/notebookStorage/vscNotebookModel.ts b/src/client/datascience/notebookStorage/vscNotebookModel.ts deleted file mode 100644 index d30947ebf435..000000000000 --- a/src/client/datascience/notebookStorage/vscNotebookModel.ts +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import type { nbformat } from '@jupyterlab/coreutils'; -import { Memento, Uri } from 'vscode'; -import { NotebookDocument } from '../../../../types/vscode-proposed'; -import { IVSCodeNotebook } from '../../common/application/types'; -import { ICryptoUtils } from '../../common/types'; -import { noop } from '../../common/utils/misc'; -import { NotebookModelChange } from '../interactive-common/interactiveWindowTypes'; -import { - createCellFromVSCNotebookCell, - getNotebookMetadata, - updateVSCNotebookAfterTrustingNotebook -} from '../notebook/helpers/helpers'; -import { ICell } from '../types'; -import { BaseNotebookModel } from './baseModel'; - -// https://github.com/microsoft/vscode-python/issues/13155 -// tslint:disable-next-line: no-any -function sortObjectPropertiesRecursively(obj: any): any { - if (Array.isArray(obj)) { - return obj.map(sortObjectPropertiesRecursively); - } - if (obj !== undefined && obj !== null && typeof obj === 'object' && Object.keys(obj).length > 0) { - return ( - Object.keys(obj) - .sort() - // tslint:disable-next-line: no-any - .reduce>((sortedObj, prop) => { - sortedObj[prop] = sortObjectPropertiesRecursively(obj[prop]); - return sortedObj; - // tslint:disable-next-line: no-any - }, {}) as any - ); - } - return obj; -} - -// Exported for test mocks -export class VSCodeNotebookModel extends BaseNotebookModel { - public get isDirty(): boolean { - return this.document?.isDirty === true; - } - public get cells(): ICell[] { - // Possible the document has been closed/disposed - if (this.isDisposed) { - return []; - } - - // When a notebook is not trusted, return original cells. - // This is because the VSCode NotebookDocument object will not have any output in the cells. - return this.document && this.isTrusted - ? this.document.cells.map((cell) => createCellFromVSCNotebookCell(cell, this)) - : this._cells; - } - public get isDisposed() { - // Possible the document has been closed/disposed - if ( - this.document && - this.vscodeNotebook && - !this.vscodeNotebook?.notebookDocuments.find((doc) => doc === this.document) - ) { - return true; - } - return this._isDisposed === true; - } - - private document?: NotebookDocument; - public get notebookContentWithoutCells(): Partial { - return { - ...this.notebookJson, - cells: [] - }; - } - public get isUntitled(): boolean { - return this.document ? this.document.isUntitled : super.isUntitled; - } - - constructor( - isTrusted: boolean, - file: Uri, - cells: ICell[], - globalMemento: Memento, - crypto: ICryptoUtils, - json: Partial = {}, - indentAmount: string = ' ', - pythonNumber: number = 3, - private readonly vscodeNotebook?: IVSCodeNotebook - ) { - super(isTrusted, file, cells, globalMemento, crypto, json, indentAmount, pythonNumber); - } - /** - * Unfortunately Notebook models are created early, well before a VSC Notebook Document is created. - * We can associate an INotebookModel with a VSC Notebook, only after the Notebook has been opened. - */ - public associateNotebookDocument(document: NotebookDocument) { - this.document = document; - } - public trust() { - super.trust(); - if (this.document) { - const editor = this.vscodeNotebook?.notebookEditors.find((item) => item.document === this.document); - if (editor) { - updateVSCNotebookAfterTrustingNotebook(editor, this.document, this._cells).then(noop, noop); - } - // We don't need old cells. - this._cells = []; - } - } - protected generateNotebookJson() { - const json = super.generateNotebookJson(); - if (this.document && this.isTrusted) { - // The metadata will be in the notebook document. - const metadata = getNotebookMetadata(this.document); - if (metadata) { - json.metadata = metadata; - } - } - if (this.document && !this.isTrusted && Array.isArray(json.cells)) { - // The output can contain custom metadata, we need to remove that. - json.cells = json.cells.map((cell) => { - const metadata = { ...cell.metadata }; - if ('vscode' in metadata) { - delete metadata.vscode; - } - return { - ...cell, - metadata - // tslint:disable-next-line: no-any - } as any; - }); - } - - // https://github.com/microsoft/vscode-python/issues/13155 - // Object keys in metadata, cells and the like need to be sorted alphabetically. - // Jupyter (Python) seems to sort them alphabetically. - // We should do the same to minimize changes to content when saving ipynb. - return sortObjectPropertiesRecursively(json); - } - - protected handleRedo(change: NotebookModelChange): boolean { - super.handleRedo(change); - return true; - } -} diff --git a/src/client/datascience/plotting/plotViewer.ts b/src/client/datascience/plotting/plotViewer.ts deleted file mode 100644 index 81713366ad9e..000000000000 --- a/src/client/datascience/plotting/plotViewer.ts +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { Event, EventEmitter, ViewColumn } from 'vscode'; - -import { traceInfo } from '../../../client/common/logger'; -import { createDeferred } from '../../../client/common/utils/async'; -import { IApplicationShell, IWebviewPanelProvider, IWorkspaceService } from '../../common/application/types'; -import { EXTENSION_ROOT_DIR, UseCustomEditorApi } from '../../common/constants'; -import { traceError } from '../../common/logger'; - -import { IConfigurationService, IDisposable } from '../../common/types'; -import * as localize from '../../common/utils/localize'; -import { ICodeCssGenerator, IDataScienceFileSystem, IPlotViewer, IThemeFinder } from '../types'; -import { WebviewPanelHost } from '../webviews/webviewPanelHost'; -import { PlotViewerMessageListener } from './plotViewerMessageListener'; -import { IExportPlotRequest, IPlotViewerMapping, PlotViewerMessages } from './types'; - -const plotDir = path.join(EXTENSION_ROOT_DIR, 'out', 'datascience-ui', 'viewers'); -@injectable() -export class PlotViewer extends WebviewPanelHost implements IPlotViewer, IDisposable { - private closedEvent: EventEmitter = new EventEmitter(); - private removedEvent: EventEmitter = new EventEmitter(); - - constructor( - @inject(IWebviewPanelProvider) provider: IWebviewPanelProvider, - @inject(IConfigurationService) configuration: IConfigurationService, - @inject(ICodeCssGenerator) cssGenerator: ICodeCssGenerator, - @inject(IThemeFinder) themeFinder: IThemeFinder, - @inject(IWorkspaceService) workspaceService: IWorkspaceService, - @inject(IApplicationShell) private applicationShell: IApplicationShell, - @inject(IDataScienceFileSystem) private fs: IDataScienceFileSystem, - @inject(UseCustomEditorApi) useCustomEditorApi: boolean - ) { - super( - configuration, - provider, - cssGenerator, - themeFinder, - workspaceService, - (c, v, d) => new PlotViewerMessageListener(c, v, d), - plotDir, - [path.join(plotDir, 'commons.initial.bundle.js'), path.join(plotDir, 'plotViewer.js')], - localize.DataScience.plotViewerTitle(), - ViewColumn.One, - useCustomEditorApi, - false - ); - // Load the web panel using our current directory as we don't expect to load any other files - super.loadWebPanel(process.cwd()).catch(traceError); - } - - public get closed(): Event { - return this.closedEvent.event; - } - - public get removed(): Event { - return this.removedEvent.event; - } - - public async show(): Promise { - if (!this.isDisposed) { - // Then show our web panel. - return super.show(true); - } - } - - public addPlot = async (imageHtml: string): Promise => { - if (!this.isDisposed) { - // Make sure we're shown - await super.show(false); - - // Send a message with our data - this.postMessage(PlotViewerMessages.SendPlot, imageHtml).ignoreErrors(); - } - }; - - public dispose() { - super.dispose(); - if (this.closedEvent) { - this.closedEvent.fire(this); - } - } - - protected get owningResource() { - return undefined; - } - - //tslint:disable-next-line:no-any - protected onMessage(message: string, payload: any) { - switch (message) { - case PlotViewerMessages.CopyPlot: - this.copyPlot(payload.toString()).ignoreErrors(); - break; - - case PlotViewerMessages.ExportPlot: - this.exportPlot(payload).ignoreErrors(); - break; - - case PlotViewerMessages.RemovePlot: - this.removePlot(payload); - break; - - default: - break; - } - - super.onMessage(message, payload); - } - - private removePlot(payload: number) { - this.removedEvent.fire(payload); - } - - private copyPlot(_svg: string): Promise { - // This should be handled actually in the web view. Leaving - // this here for now in case need node to handle it. - return Promise.resolve(); - } - - private async exportPlot(payload: IExportPlotRequest): Promise { - traceInfo('exporting plot...'); - const filtersObject: Record = {}; - filtersObject[localize.DataScience.pdfFilter()] = ['pdf']; - filtersObject[localize.DataScience.pngFilter()] = ['png']; - filtersObject[localize.DataScience.svgFilter()] = ['svg']; - - // Ask the user what file to save to - const file = await this.applicationShell.showSaveDialog({ - saveLabel: localize.DataScience.exportPlotTitle(), - filters: filtersObject - }); - try { - if (file) { - const ext = path.extname(file.fsPath); - switch (ext.toLowerCase()) { - case '.pdf': - traceInfo('Attempting pdf write...'); - // Import here since pdfkit is so huge. - // tslint:disable-next-line: no-require-imports - const SVGtoPDF = require('svg-to-pdfkit'); - const deferred = createDeferred(); - // tslint:disable-next-line: no-require-imports - const pdfkit = require('pdfkit/js/pdfkit.standalone') as typeof import('pdfkit'); - const doc = new pdfkit(); - const ws = this.fs.createLocalWriteStream(file.fsPath); - traceInfo(`Writing pdf to ${file.fsPath}`); - ws.on('finish', () => deferred.resolve); - // See docs or demo from source https://cdn.statically.io/gh/alafr/SVG-to-PDFKit/master/examples/demo.htm - // How to resize to fit (fit within the height & width of page). - SVGtoPDF(doc, payload.svg, 0, 0, { preserveAspectRatio: 'xMinYMin meet' }); - doc.pipe(ws); - doc.end(); - traceInfo(`Finishing pdf to ${file.fsPath}`); - await deferred.promise; - traceInfo(`Completed pdf to ${file.fsPath}`); - break; - - case '.png': - const buffer = new Buffer(payload.png.replace('data:image/png;base64', ''), 'base64'); - await this.fs.writeLocalFile(file.fsPath, buffer); - break; - - default: - case '.svg': - // This is the easy one: - await this.fs.writeLocalFile(file.fsPath, payload.svg); - break; - } - } - } catch (e) { - traceError(e); - this.applicationShell.showErrorMessage(localize.DataScience.exportImageFailed().format(e)); - } - } -} diff --git a/src/client/datascience/plotting/plotViewerMessageListener.ts b/src/client/datascience/plotting/plotViewerMessageListener.ts deleted file mode 100644 index 2e4affa392cf..000000000000 --- a/src/client/datascience/plotting/plotViewerMessageListener.ts +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; - -import { IWebviewPanel, IWebviewPanelMessageListener } from '../../common/application/types'; - -// tslint:disable:no-any - -// This class listens to messages that come from the local Plot Viewer window -export class PlotViewerMessageListener implements IWebviewPanelMessageListener { - private disposedCallback: () => void; - private callback: (message: string, payload: any) => void; - private viewChanged: (panel: IWebviewPanel) => void; - - constructor( - callback: (message: string, payload: any) => void, - viewChanged: (panel: IWebviewPanel) => void, - disposed: () => void - ) { - // Save our dispose callback so we remove our history window - this.disposedCallback = disposed; - - // Save our local callback so we can handle the non broadcast case(s) - this.callback = callback; - - // Save view changed so we can forward view change events. - this.viewChanged = viewChanged; - } - - public async dispose() { - this.disposedCallback(); - } - - public onMessage(message: string, payload: any) { - // Send to just our local callback. - this.callback(message, payload); - } - - public onChangeViewState(panel: IWebviewPanel) { - // Forward this onto our callback - if (this.viewChanged) { - this.viewChanged(panel); - } - } -} diff --git a/src/client/datascience/plotting/plotViewerProvider.ts b/src/client/datascience/plotting/plotViewerProvider.ts deleted file mode 100644 index 57cd21195976..000000000000 --- a/src/client/datascience/plotting/plotViewerProvider.ts +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../common/extensions'; - -import { inject, injectable } from 'inversify'; - -import { IAsyncDisposable, IAsyncDisposableRegistry, IDisposable } from '../../common/types'; -import { IServiceContainer } from '../../ioc/types'; -import { sendTelemetryEvent } from '../../telemetry'; -import { Telemetry } from '../constants'; -import { IPlotViewer, IPlotViewerProvider } from '../types'; - -@injectable() -export class PlotViewerProvider implements IPlotViewerProvider, IAsyncDisposable { - private currentViewer: IPlotViewer | undefined; - private currentViewerClosed: IDisposable | undefined; - private imageList: string[] = []; - constructor( - @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(IAsyncDisposableRegistry) asyncRegistry: IAsyncDisposableRegistry - ) { - asyncRegistry.push(this); - } - - public async dispose() { - if (this.currentViewer) { - this.currentViewer.dispose(); - } - } - - public async showPlot(imageHtml: string): Promise { - this.imageList.push(imageHtml); - // If the viewer closed, send it all of the old images - const imagesToSend = this.currentViewer ? [imageHtml] : this.imageList; - const viewer = await this.getOrCreate(); - await Promise.all(imagesToSend.map(viewer.addPlot)); - } - - private async getOrCreate(): Promise { - // Get or create a new plot viwer - if (!this.currentViewer) { - this.currentViewer = this.serviceContainer.get(IPlotViewer); - this.currentViewerClosed = this.currentViewer.closed(this.closedViewer); - this.currentViewer.removed(this.removedPlot); - sendTelemetryEvent(Telemetry.OpenPlotViewer); - await this.currentViewer.show(); - } - - return this.currentViewer; - } - - private closedViewer = () => { - if (this.currentViewer) { - this.currentViewer = undefined; - } - if (this.currentViewerClosed) { - this.currentViewerClosed.dispose(); - this.currentViewerClosed = undefined; - } - }; - - private removedPlot = (index: number) => { - this.imageList.splice(index, 1); - }; -} diff --git a/src/client/datascience/plotting/types.ts b/src/client/datascience/plotting/types.ts deleted file mode 100644 index d10491da37e5..000000000000 --- a/src/client/datascience/plotting/types.ts +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import { CssMessages, IGetCssRequest, IGetCssResponse, SharedMessages } from '../messages'; - -export namespace PlotViewerMessages { - export const Started = SharedMessages.Started; - export const UpdateSettings = SharedMessages.UpdateSettings; - export const SendPlot = 'send_plot'; - export const CopyPlot = 'copy_plot'; - export const ExportPlot = 'export_plot'; - export const RemovePlot = 'remove_plot'; -} - -export interface IExportPlotRequest { - svg: string; - png: string; -} - -// Map all messages to specific payloads -export class IPlotViewerMapping { - public [PlotViewerMessages.Started]: never | undefined; - public [PlotViewerMessages.UpdateSettings]: string; - public [PlotViewerMessages.SendPlot]: string; - public [PlotViewerMessages.CopyPlot]: string; - public [PlotViewerMessages.ExportPlot]: IExportPlotRequest; - public [PlotViewerMessages.RemovePlot]: number; - public [CssMessages.GetCssRequest]: IGetCssRequest; - public [CssMessages.GetCssResponse]: IGetCssResponse; -} diff --git a/src/client/datascience/preWarmVariables.ts b/src/client/datascience/preWarmVariables.ts deleted file mode 100644 index 9eaad2da74d2..000000000000 --- a/src/client/datascience/preWarmVariables.ts +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IExtensionSingleActivationService } from '../activation/types'; -import '../common/extensions'; -import { IDisposableRegistry } from '../common/types'; -import { noop } from '../common/utils/misc'; -import { IEnvironmentActivationService } from '../interpreter/activation/types'; -import { JupyterInterpreterService } from './jupyter/interpreter/jupyterInterpreterService'; - -@injectable() -export class PreWarmActivatedJupyterEnvironmentVariables implements IExtensionSingleActivationService { - constructor( - @inject(IEnvironmentActivationService) private readonly activationService: IEnvironmentActivationService, - @inject(JupyterInterpreterService) private readonly jupyterInterpreterService: JupyterInterpreterService, - @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry - ) {} - public async activate(): Promise { - this.disposables.push( - this.jupyterInterpreterService.onDidChangeInterpreter(() => this.preWarmInterpreterVariables().catch(noop)) - ); - this.preWarmInterpreterVariables().ignoreErrors(); - } - - private async preWarmInterpreterVariables() { - const interpreter = await this.jupyterInterpreterService.getSelectedInterpreter(); - if (!interpreter) { - return; - } - this.activationService.getActivatedEnvironmentVariables(undefined, interpreter).ignoreErrors(); - } -} diff --git a/src/client/datascience/progress/decorator.ts b/src/client/datascience/progress/decorator.ts deleted file mode 100644 index 5f0215d08736..000000000000 --- a/src/client/datascience/progress/decorator.ts +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { traceError } from '../../common/logger'; -import { PromiseFunction } from '../types'; -import { IProgressReporter, Progress, ReportableAction } from './types'; - -const _reporters = new Set(); - -export function registerReporter(reporter: IProgressReporter) { - _reporters.add(reporter); -} - -export function disposeRegisteredReporters() { - _reporters.clear(); -} - -function report(progress: Progress) { - try { - _reporters.forEach((item) => item.report(progress)); - } catch (ex) { - traceError('Failed to report progress', ex); - } -} - -/** - * Reports a user reportable action. - * Action may be logged or displayed to the user depending on the registered listeners. - * - * @export - * @param {ReportableAction} action - * @returns - */ -export function reportAction(action: ReportableAction) { - return function (_target: Object, _propertyName: string, descriptor: TypedPropertyDescriptor) { - const originalMethod = descriptor.value!; - // tslint:disable-next-line:no-any no-function-expression - descriptor.value = async function (...args: any[]) { - report({ action, phase: 'started' }); - // tslint:disable-next-line:no-invalid-this - return originalMethod.apply(this, args).finally(() => { - report({ action, phase: 'completed' }); - }); - }; - }; -} diff --git a/src/client/datascience/progress/messages.ts b/src/client/datascience/progress/messages.ts deleted file mode 100644 index 89abfaecaf6f..000000000000 --- a/src/client/datascience/progress/messages.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { DataScience } from '../../common/utils/localize'; -import { ReportableAction } from './types'; - -const progressMessages = { - [ReportableAction.JupyterSessionWaitForIdleSession]: DataScience.waitingForJupyterSessionToBeIdle(), - [ReportableAction.KernelsGetKernelForLocalConnection]: DataScience.gettingListOfKernelsForLocalConnection(), - [ReportableAction.KernelsGetKernelForRemoteConnection]: DataScience.gettingListOfKernelsForRemoteConnection(), - [ReportableAction.KernelsGetKernelSpecs]: DataScience.gettingListOfKernelSpecs(), - [ReportableAction.KernelsRegisterKernel]: DataScience.registeringKernel(), - [ReportableAction.NotebookConnect]: DataScience.connectingToJupyter(), - [ReportableAction.NotebookStart]: DataScience.startingJupyterNotebook(), - [ReportableAction.RawKernelConnecting]: DataScience.rawKernelConnectingSession(), - [ReportableAction.CheckingIfImportIsSupported]: DataScience.checkingIfImportIsSupported(), // Localize these later - [ReportableAction.InstallingMissingDependencies]: DataScience.installingMissingDependencies(), - [ReportableAction.ExportNotebookToPython]: DataScience.exportNotebookToPython(), - [ReportableAction.PerformingExport]: DataScience.performingExport(), - [ReportableAction.ConvertingToPDF]: DataScience.convertingToPDF() -}; - -/** - * Given a reportable action, this will return the user friendly message. - * - * @export - * @param {ReportableAction} action - * @returns {(string | undefined)} - */ -export function getUserMessageForAction(action: ReportableAction): string | undefined { - return progressMessages[action]; -} diff --git a/src/client/datascience/progress/progressReporter.ts b/src/client/datascience/progress/progressReporter.ts deleted file mode 100644 index 87ccadf9f450..000000000000 --- a/src/client/datascience/progress/progressReporter.ts +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { CancellationToken, CancellationTokenSource, Progress as VSCProgress, ProgressLocation } from 'vscode'; -import { IApplicationShell } from '../../common/application/types'; -import { IDisposable } from '../../common/types'; -import { createDeferred } from '../../common/utils/async'; -import { noop } from '../../common/utils/misc'; -import { registerReporter } from './decorator'; -import { getUserMessageForAction } from './messages'; -import { IProgressReporter, Progress, ReportableAction } from './types'; - -@injectable() -export class ProgressReporter implements IProgressReporter { - private progressReporters: VSCProgress<{ message?: string | undefined; increment?: number | undefined }>[] = []; - private actionPhases = new Map(); - private currentActions: ReportableAction[] = []; - private get currentAction(): ReportableAction | undefined { - return this.currentActions.length === 0 ? undefined : this.currentActions[this.currentActions.length - 1]; - } - - constructor(@inject(IApplicationShell) private readonly appShell: IApplicationShell) { - registerReporter(this); - } - - /** - * Create and display a progress indicator for starting of Jupyter Notebooks. - * - * @param {string} message - * @returns {(IDisposable & { token: CancellationToken })} - * @memberof ProgressReporter - */ - public createProgressIndicator(message: string, cancellable = false): IDisposable & { token: CancellationToken } { - const cancellation = new CancellationTokenSource(); - const deferred = createDeferred(); - const options = { location: ProgressLocation.Notification, cancellable: cancellable, title: message }; - - this.appShell - .withProgress(options, async (progress, cancelToken) => { - cancelToken.onCancellationRequested(() => { - if (cancelToken.isCancellationRequested) { - cancellation.cancel(); - } - deferred.resolve(); - }); - cancellation.token.onCancellationRequested(() => { - deferred.resolve(); - }); - this.progressReporters.push(progress); - await deferred.promise; - }) - .then(noop, noop); - - return { - token: cancellation.token, - dispose: () => deferred.resolve() - }; - } - - /** - * Reports progress to the user. - * Keep messages in a stack. As we have new actions taking place place them in a stack and notify progress. - * As they finish pop them from the stack (if currently displayed). - * We need a stack, as we have multiple async actions taking place, and each stat & can complete at different times. - * - * @param {Progress} progress - * @returns {void} - * @memberof JupyterStartupProgressReporter - */ - public report(progress: Progress): void { - if (this.progressReporters.length === 0) { - return; - } - this.actionPhases.set(progress.action, progress.phase); - - if (progress.phase === 'started') { - this.currentActions.push(progress.action); - } - - if (!this.currentAction) { - return; - } - - // If current action has been completed, then pop that item. - // Until we have an action that is still in progress - while (this.actionPhases.get(this.currentAction) && this.actionPhases.get(this.currentAction) !== 'started') { - this.actionPhases.delete(this.currentAction); - this.currentActions.pop(); - } - - this.updateProgressMessage(); - } - - private updateProgressMessage() { - if (!this.currentAction || this.progressReporters.length === 0) { - return; - } - const message = getUserMessageForAction(this.currentAction); - if (message) { - this.progressReporters.forEach((item) => item.report({ message })); - } - } -} diff --git a/src/client/datascience/progress/types.ts b/src/client/datascience/progress/types.ts deleted file mode 100644 index 12f51a14f121..000000000000 --- a/src/client/datascience/progress/types.ts +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -export type Progress = { action: ReportableAction; phase: 'started' | 'completed' }; -export interface IProgressReporter { - report(progress: Progress): void; -} - -/** - * Actions performed by extension that can be (potentially) reported to the user. - * - * @export - * @enum {number} - */ -export enum ReportableAction { - /** - * Getting kernels for a local connection. - * If not found, user may have to select or we might register a kernel. - */ - KernelsGetKernelForLocalConnection = 'KernelsStartGetKernelForLocalConnection', - /** - * Getting kernels for a remote connection. - * If not found, user may have to select. - */ - KernelsGetKernelForRemoteConnection = 'KernelsGetKernelForRemoteConnection', - /** - * Registering kernel. - */ - KernelsRegisterKernel = 'KernelsRegisterKernel', - /** - * Retrieving kernel specs. - */ - KernelsGetKernelSpecs = 'KernelsGetKernelSpecs', - /** - * Starting Jupyter Notebook & waiting to get connection information. - */ - NotebookStart = 'NotebookStart', - /** - * Connecting to the Jupyter Notebook. - */ - NotebookConnect = 'NotebookConnect', - /** - * Wait for session to go idle. - */ - JupyterSessionWaitForIdleSession = 'JupyterSessionWaitForIdleSession', - /** - * Connecting a raw kernel session - */ - RawKernelConnecting = 'RawKernelConnecting', - CheckingIfImportIsSupported = 'CheckingIfImportIsSupported', - InstallingMissingDependencies = 'InstallingMissingDependencies', - ExportNotebookToPython = 'ExportNotebookToPython', - PerformingExport = 'PerformingExport', - ConvertingToPDF = 'ConvertingToPDF' -} diff --git a/src/client/datascience/raw-kernel/liveshare/guestRawNotebookProvider.ts b/src/client/datascience/raw-kernel/liveshare/guestRawNotebookProvider.ts deleted file mode 100644 index 61c1fe758b07..000000000000 --- a/src/client/datascience/raw-kernel/liveshare/guestRawNotebookProvider.ts +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import type { nbformat } from '@jupyterlab/coreutils'; -import { Uri } from 'vscode'; -import { CancellationToken } from 'vscode-jsonrpc'; -import * as vsls from 'vsls/vscode'; -import { IApplicationShell, ILiveShareApi, IWorkspaceService } from '../../../common/application/types'; - -import { IAsyncDisposableRegistry, IConfigurationService, IDisposableRegistry, Resource } from '../../../common/types'; -import { createDeferred } from '../../../common/utils/async'; -import * as localize from '../../../common/utils/localize'; -import { IServiceContainer } from '../../../ioc/types'; -import { LiveShare, LiveShareCommands } from '../../constants'; -import { GuestJupyterNotebook } from '../../jupyter/liveshare/guestJupyterNotebook'; -import { - LiveShareParticipantDefault, - LiveShareParticipantGuest -} from '../../jupyter/liveshare/liveShareParticipantMixin'; -import { ILiveShareParticipant } from '../../jupyter/liveshare/types'; -import { IDataScienceFileSystem, INotebook, IRawConnection, IRawNotebookProvider } from '../../types'; -import { RawConnection } from '../rawNotebookProvider'; - -export class GuestRawNotebookProvider - extends LiveShareParticipantGuest(LiveShareParticipantDefault, LiveShare.RawNotebookProviderService) - implements IRawNotebookProvider, ILiveShareParticipant { - // Keep track of guest notebooks on this side - private notebooks = new Map>(); - private rawConnection = new RawConnection(); - - constructor( - private readonly liveShare: ILiveShareApi, - private readonly startupTime: number, - private readonly disposableRegistry: IDisposableRegistry, - _asyncRegistry: IAsyncDisposableRegistry, - private readonly configService: IConfigurationService, - _workspaceService: IWorkspaceService, - _appShell: IApplicationShell, - _fs: IDataScienceFileSystem, - _serviceContainer: IServiceContainer - ) { - super(liveShare); - } - - public async supported(): Promise { - // Query the host to see if liveshare is supported - const service = await this.waitForService(); - let result = false; - if (service) { - result = await service.request(LiveShareCommands.rawKernelSupported, []); - } - - return result; - } - - public async createNotebook( - identity: Uri, - resource: Resource, - _disableUI: boolean, - notebookMetadata: nbformat.INotebookMetadata, - _cancelToken: CancellationToken - ): Promise { - // Remember we can have multiple native editors opened against the same ipynb file. - if (this.notebooks.get(identity.toString())) { - return this.notebooks.get(identity.toString())!; - } - - const deferred = createDeferred(); - this.notebooks.set(identity.toString(), deferred.promise); - // Tell the host side to generate a notebook for this uri - const service = await this.waitForService(); - if (service) { - const resourceString = resource ? resource.toString() : undefined; - const identityString = identity.toString(); - const notebookMetadataString = JSON.stringify(notebookMetadata); - await service.request(LiveShareCommands.createRawNotebook, [ - resourceString, - identityString, - notebookMetadataString - ]); - } - - // Return a new notebook to listen to - const result = new GuestJupyterNotebook( - this.liveShare, - this.disposableRegistry, - this.configService, - resource, - identity, - undefined, - this.startupTime - ); - deferred.resolve(result); - const oldDispose = result.dispose.bind(result); - result.dispose = () => { - this.notebooks.delete(identity.toString()); - return oldDispose(); - }; - - return result; - } - - public async connect(): Promise { - return Promise.resolve(this.rawConnection); - } - - public async onSessionChange(api: vsls.LiveShare | null): Promise { - await super.onSessionChange(api); - - this.notebooks.forEach(async (notebook) => { - const guestNotebook = (await notebook) as GuestJupyterNotebook; - if (guestNotebook) { - await guestNotebook.onSessionChange(api); - } - }); - } - - public async getNotebook(resource: Uri): Promise { - return this.notebooks.get(resource.toString()); - } - - public async onAttach(api: vsls.LiveShare | null): Promise { - await super.onAttach(api); - - if (api) { - const service = await this.waitForService(); - - // Wait for sync up - const synced = service ? await service.request(LiveShareCommands.syncRequest, []) : undefined; - if (!synced && api.session && api.session.role !== vsls.Role.None) { - throw new Error(localize.DataScience.liveShareSyncFailure()); - } - } - } - - public async waitForServiceName(): Promise { - return LiveShare.RawNotebookProviderService; - } -} diff --git a/src/client/datascience/raw-kernel/liveshare/hostRawNotebookProvider.ts b/src/client/datascience/raw-kernel/liveshare/hostRawNotebookProvider.ts deleted file mode 100644 index d26553cf8695..000000000000 --- a/src/client/datascience/raw-kernel/liveshare/hostRawNotebookProvider.ts +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../../common/extensions'; - -import * as vscode from 'vscode'; -import { CancellationToken } from 'vscode-jsonrpc'; -import * as vsls from 'vsls/vscode'; - -import type { nbformat } from '@jupyterlab/coreutils'; -import { IApplicationShell, ILiveShareApi, IWorkspaceService } from '../../../common/application/types'; -import { traceError, traceInfo } from '../../../common/logger'; - -import { - IAsyncDisposableRegistry, - IConfigurationService, - IDisposableRegistry, - IOutputChannel, - Resource -} from '../../../common/types'; -import { createDeferred } from '../../../common/utils/async'; -import * as localize from '../../../common/utils/localize'; -import { noop } from '../../../common/utils/misc'; -import { IServiceContainer } from '../../../ioc/types'; -import { Identifiers, LiveShare, LiveShareCommands, Settings } from '../../constants'; -import { computeWorkingDirectory } from '../../jupyter/jupyterUtils'; -import { KernelSelector } from '../../jupyter/kernels/kernelSelector'; -import { KernelConnectionMetadata } from '../../jupyter/kernels/types'; -import { HostJupyterNotebook } from '../../jupyter/liveshare/hostJupyterNotebook'; -import { LiveShareParticipantHost } from '../../jupyter/liveshare/liveShareParticipantMixin'; -import { IRoleBasedObject } from '../../jupyter/liveshare/roleBasedFactory'; -import { IKernelLauncher } from '../../kernel-launcher/types'; -import { ProgressReporter } from '../../progress/progressReporter'; -import { - IDataScienceFileSystem, - INotebook, - INotebookExecutionInfo, - INotebookExecutionLogger, - IRawNotebookProvider, - IRawNotebookSupportedService -} from '../../types'; -import { calculateWorkingDirectory } from '../../utils'; -import { RawJupyterSession } from '../rawJupyterSession'; -import { RawNotebookProviderBase } from '../rawNotebookProvider'; - -// tslint:disable-next-line: no-require-imports -// tslint:disable:no-any - -export class HostRawNotebookProvider - extends LiveShareParticipantHost(RawNotebookProviderBase, LiveShare.RawNotebookProviderService) - implements IRoleBasedObject, IRawNotebookProvider { - private disposed = false; - constructor( - private liveShare: ILiveShareApi, - _t: number, - private disposableRegistry: IDisposableRegistry, - asyncRegistry: IAsyncDisposableRegistry, - private configService: IConfigurationService, - private workspaceService: IWorkspaceService, - private appShell: IApplicationShell, - private fs: IDataScienceFileSystem, - private serviceContainer: IServiceContainer, - private kernelLauncher: IKernelLauncher, - private kernelSelector: KernelSelector, - private progressReporter: ProgressReporter, - private outputChannel: IOutputChannel, - rawNotebookSupported: IRawNotebookSupportedService - ) { - super(liveShare, asyncRegistry, rawNotebookSupported); - } - - public async dispose(): Promise { - if (!this.disposed) { - this.disposed = true; - await super.dispose(); - } - } - - public async onAttach(api: vsls.LiveShare | null): Promise { - await super.onAttach(api); - if (api && !this.disposed) { - const service = await this.waitForService(); - // Attach event handlers to different requests - if (service) { - service.onRequest(LiveShareCommands.syncRequest, (_args: any[], _cancellation: CancellationToken) => - this.onSync() - ); - service.onRequest( - LiveShareCommands.rawKernelSupported, - (_args: any[], _cancellation: CancellationToken) => this.supported() - ); - service.onRequest( - LiveShareCommands.createRawNotebook, - async (args: any[], _cancellation: CancellationToken) => { - const resource = this.parseUri(args[0]); - const identity = this.parseUri(args[1]); - const notebookMetadata = JSON.parse(args[2]) as nbformat.INotebookMetadata; - // Don't return the notebook. We don't want it to be serialized. We just want its live share server to be started. - const notebook = (await this.createNotebook( - identity!, - resource, - true, // Disable UI for this creation - notebookMetadata, - undefined - )) as HostJupyterNotebook; - await notebook.onAttach(api); - } - ); - } - } - } - - public async onSessionChange(api: vsls.LiveShare | null): Promise { - await super.onSessionChange(api); - - this.getNotebooks().forEach(async (notebook) => { - const hostNotebook = (await notebook) as HostJupyterNotebook; - if (hostNotebook) { - await hostNotebook.onSessionChange(api); - } - }); - } - - public async onDetach(api: vsls.LiveShare | null): Promise { - await super.onDetach(api); - } - - public async waitForServiceName(): Promise { - return LiveShare.RawNotebookProviderService; - } - - protected async createNotebookInstance( - resource: Resource, - identity: vscode.Uri, - disableUI?: boolean, - notebookMetadata?: nbformat.INotebookMetadata, - cancelToken?: CancellationToken - ): Promise { - const notebookPromise = createDeferred(); - this.setNotebook(identity, notebookPromise.promise); - - const progressReporter = !disableUI - ? this.progressReporter.createProgressIndicator(localize.DataScience.connectingIPyKernel()) - : undefined; - - const workingDirectory = await computeWorkingDirectory(resource, this.workspaceService); - - const rawSession = new RawJupyterSession( - this.kernelLauncher, - resource, - this.outputChannel, - noop, - noop, - workingDirectory - ); - try { - const launchTimeout = this.configService.getSettings().datascience.jupyterLaunchTimeout; - - // We need to locate kernelspec and possible interpreter for this launch based on resource and notebook metadata - const kernelConnectionMetadata = await this.kernelSelector.getPreferredKernelForLocalConnection( - resource, - 'raw', - undefined, - notebookMetadata, - disableUI, - cancelToken - ); - - // Interpreter is optional, but we must have a kernel spec for a raw launch if using a kernelspec - if ( - !kernelConnectionMetadata || - (!kernelConnectionMetadata?.kernelSpec && kernelConnectionMetadata?.kind === 'startUsingKernelSpec') - ) { - notebookPromise.reject('Failed to find a kernelspec to use for ipykernel launch'); - } else { - await rawSession.connect(kernelConnectionMetadata, launchTimeout, cancelToken); - - // Get the execution info for our notebook - const info = await this.getExecutionInfo(kernelConnectionMetadata); - - if (rawSession.isConnected) { - // Create our notebook - const notebook = new HostJupyterNotebook( - this.liveShare, - rawSession, - this.configService, - this.disposableRegistry, - info, - this.serviceContainer.getAll(INotebookExecutionLogger), - resource, - identity, - this.getDisposedError.bind(this), - this.workspaceService, - this.appShell, - this.fs - ); - - // Run initial setup - await notebook.initialize(cancelToken); - - traceInfo(`Finished connecting ${this.id}`); - - notebookPromise.resolve(notebook); - } else { - notebookPromise.reject(this.getDisposedError()); - } - } - } catch (ex) { - // Make sure we shut down our session in case we started a process - rawSession.dispose().catch((error) => { - traceError(`Failed to dispose of raw session on launch error: ${error} `); - }); - // If there's an error, then reject the promise that is returned. - // This original promise must be rejected as it is cached (check `setNotebook`). - notebookPromise.reject(ex); - } finally { - progressReporter?.dispose(); // NOSONAR - } - - return notebookPromise.promise; - } - - // Get the notebook execution info for this raw session instance - private async getExecutionInfo( - kernelConnectionMetadata: KernelConnectionMetadata - ): Promise { - return { - connectionInfo: this.getConnection(), - uri: Settings.JupyterServerLocalLaunch, - kernelConnectionMetadata, - workingDir: await calculateWorkingDirectory(this.configService, this.workspaceService, this.fs), - purpose: Identifiers.RawPurpose - }; - } - - private parseUri(uri: string | undefined): Resource { - const parsed = uri ? vscode.Uri.parse(uri) : undefined; - return parsed && - parsed.scheme && - parsed.scheme !== Identifiers.InteractiveWindowIdentityScheme && - parsed.scheme === 'vsls' - ? this.finishedApi!.convertSharedUriToLocal(parsed) - : parsed; - } - - private onSync(): Promise { - return Promise.resolve(true); - } -} diff --git a/src/client/datascience/raw-kernel/rawJupyterSession.ts b/src/client/datascience/raw-kernel/rawJupyterSession.ts deleted file mode 100644 index 865403fbf245..000000000000 --- a/src/client/datascience/raw-kernel/rawJupyterSession.ts +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import type { Kernel } from '@jupyterlab/services'; -import type { Slot } from '@phosphor/signaling'; -import { CancellationToken } from 'vscode-jsonrpc'; -import { CancellationError, createPromiseFromCancellation } from '../../common/cancellation'; -import { traceError, traceInfo } from '../../common/logger'; -import { IDisposable, IOutputChannel, Resource } from '../../common/types'; -import { waitForPromise } from '../../common/utils/async'; -import * as localize from '../../common/utils/localize'; -import { noop } from '../../common/utils/misc'; -import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; -import { BaseJupyterSession } from '../baseJupyterSession'; -import { Identifiers, Telemetry } from '../constants'; -import { getDisplayNameOrNameOfKernelConnection } from '../jupyter/kernels/helpers'; -import { KernelConnectionMetadata } from '../jupyter/kernels/types'; -import { IKernelLauncher } from '../kernel-launcher/types'; -import { reportAction } from '../progress/decorator'; -import { ReportableAction } from '../progress/types'; -import { RawSession } from '../raw-kernel/rawSession'; -import { ISessionWithSocket } from '../types'; - -// Error thrown when we are unable to start a raw kernel session -export class RawKernelSessionStartError extends Error { - constructor(kernelConnection: KernelConnectionMetadata) { - super( - localize.DataScience.rawKernelSessionFailed().format( - getDisplayNameOrNameOfKernelConnection(kernelConnection) - ) - ); - } -} - -/* -RawJupyterSession is the implementation of IJupyterSession that instead of -connecting to JupyterLab services it instead connects to a kernel directly -through ZMQ. -It's responsible for translating our IJupyterSession interface into the -jupyterlabs interface as well as starting up and connecting to a raw session -*/ -export class RawJupyterSession extends BaseJupyterSession { - private processExitHandler: IDisposable | undefined; - private _disposables: IDisposable[] = []; - constructor( - private readonly kernelLauncher: IKernelLauncher, - private readonly resource: Resource, - private readonly outputChannel: IOutputChannel, - private readonly restartSessionCreated: (id: Kernel.IKernelConnection) => void, - restartSessionUsed: (id: Kernel.IKernelConnection) => void, - workingDirectory: string - ) { - super(restartSessionUsed, workingDirectory); - } - - @reportAction(ReportableAction.JupyterSessionWaitForIdleSession) - public async waitForIdle(timeout: number): Promise { - // Wait until status says idle. - if (this.session) { - return this.waitForIdleOnSession(this.session, timeout); - } - return Promise.resolve(); - } - public async dispose(): Promise { - this._disposables.forEach((d) => d.dispose()); - await super.dispose(); - } - - public shutdown(): Promise { - if (this.processExitHandler) { - this.processExitHandler.dispose(); - this.processExitHandler = undefined; - } - return super.shutdown(); - } - - // Connect to the given kernelspec, which should already have ipykernel installed into its interpreter - @captureTelemetry(Telemetry.RawKernelSessionConnect, undefined, true) - @reportAction(ReportableAction.RawKernelConnecting) - public async connect( - kernelConnection: KernelConnectionMetadata, - timeout: number, - cancelToken?: CancellationToken - ): Promise { - // Save the resource that we connect with - let newSession: RawSession | null | CancellationError = null; - try { - // Try to start up our raw session, allow for cancellation or timeout - // Notebook Provider level will handle the thrown error - newSession = await waitForPromise( - Promise.race([ - this.startRawSession(kernelConnection, cancelToken), - createPromiseFromCancellation({ - cancelAction: 'reject', - defaultValue: new CancellationError(), - token: cancelToken - }) - ]), - timeout - ); - - // Only connect our session if we didn't cancel or timeout - if (newSession instanceof CancellationError) { - sendTelemetryEvent(Telemetry.RawKernelSessionStartUserCancel); - traceInfo('Starting of raw session cancelled by user'); - throw newSession; - } else if (newSession === null) { - sendTelemetryEvent(Telemetry.RawKernelSessionStartTimeout); - traceError('Raw session failed to start in given timeout'); - throw new RawKernelSessionStartError(kernelConnection); - } else { - sendTelemetryEvent(Telemetry.RawKernelSessionStartSuccess); - traceInfo('Raw session started and connected'); - this.setSession(newSession); - - // Listen for session status changes - this.session?.statusChanged.connect(this.statusHandler); // NOSONAR - - // Update kernelspec and interpreter - this.kernelConnectionMetadata = newSession.kernelProcess?.kernelConnectionMetadata; - - this.outputChannel.appendLine( - localize.DataScience.kernelStarted().format( - getDisplayNameOrNameOfKernelConnection(this.kernelConnectionMetadata) - ) - ); - } - } catch (error) { - // Send our telemetry event with the error included - sendTelemetryEvent(Telemetry.RawKernelSessionStartException, undefined, undefined, error); - traceError(`Failed to connect raw kernel session: ${error}`); - this.connected = false; - throw error; - } - - this.connected = true; - return (newSession as RawSession).kernelProcess.kernelConnectionMetadata; - } - - public async createNewKernelSession( - kernelConnection: KernelConnectionMetadata, - timeoutMS: number, - _cancelToken?: CancellationToken - ): Promise { - if (!kernelConnection || 'session' in kernelConnection) { - // Don't allow for connecting to a LiveKernelModel - throw new Error(localize.DataScience.sessionDisposed()); - } - - const displayName = getDisplayNameOrNameOfKernelConnection(kernelConnection); - this.outputChannel.appendLine(localize.DataScience.kernelStarted().format(displayName)); - - const newSession = await waitForPromise(this.startRawSession(kernelConnection), timeoutMS); - - if (!newSession) { - throw new RawKernelSessionStartError(kernelConnection); - } - - return newSession; - } - - protected shutdownSession( - session: ISessionWithSocket | undefined, - statusHandler: Slot | undefined - ): Promise { - return super.shutdownSession(session, statusHandler).then(() => { - if (session) { - return (session as RawSession).kernelProcess.dispose(); - } - }); - } - - protected setSession(session: ISessionWithSocket | undefined) { - super.setSession(session); - - // When setting the session clear our current exit handler and hook up to the - // new session process - if (this.processExitHandler) { - this.processExitHandler.dispose(); - this.processExitHandler = undefined; - } - if (session && (session as RawSession).kernelProcess) { - // Watch to see if our process exits - this.processExitHandler = (session as RawSession).kernelProcess.exited((exitCode) => { - traceError(`Raw kernel process exited code: ${exitCode}`); - this.shutdown().catch((reason) => { - traceError(`Error shutting down jupyter session: ${reason}`); - }); - // Next code the user executes will show a session disposed message - }); - } - } - - protected startRestartSession() { - if (!this.restartSessionPromise && this.session) { - this.restartSessionPromise = this.createRestartSession(this.kernelConnectionMetadata, this.session); - } - } - protected async createRestartSession( - kernelConnection: KernelConnectionMetadata | undefined, - _session: ISessionWithSocket, - cancelToken?: CancellationToken - ): Promise { - if (!kernelConnection || kernelConnection.kind === 'connectToLiveKernel') { - // Need to have connected before restarting and can't use a LiveKernelModel - throw new Error(localize.DataScience.sessionDisposed()); - } - const startPromise = this.startRawSession(kernelConnection, cancelToken); - return startPromise.then((session) => { - this.restartSessionCreated(session.kernel); - return session; - }); - } - - @captureTelemetry(Telemetry.RawKernelStartRawSession, undefined, true) - private async startRawSession( - kernelConnection: KernelConnectionMetadata, - cancelToken?: CancellationToken - ): Promise { - if ( - kernelConnection.kind !== 'startUsingKernelSpec' && - kernelConnection.kind !== 'startUsingPythonInterpreter' - ) { - throw new Error(`Unable to start Raw Kernels for Kernel Connection of type ${kernelConnection.kind}`); - } - const cancellationPromise = createPromiseFromCancellation({ - cancelAction: 'reject', - defaultValue: undefined, - token: cancelToken - }) as Promise; - cancellationPromise.catch(noop); - - const process = await Promise.race([ - this.kernelLauncher.launch(kernelConnection, this.resource, this.workingDirectory), - cancellationPromise - ]); - - // Create our raw session, it will own the process lifetime - const result = new RawSession(process); - - // When our kernel connects and gets a status message it triggers the ready promise - await result.kernel.ready; - - // So that we don't have problems with ipywidgets, always register the default ipywidgets comm target. - // Restart sessions and retries might make this hard to do correctly otherwise. - result.kernel.registerCommTarget(Identifiers.DefaultCommTarget, noop); - - return result; - } -} diff --git a/src/client/datascience/raw-kernel/rawKernel.ts b/src/client/datascience/raw-kernel/rawKernel.ts deleted file mode 100644 index b80c81646b81..000000000000 --- a/src/client/datascience/raw-kernel/rawKernel.ts +++ /dev/null @@ -1,275 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import type { Kernel, KernelMessage, ServerConnection } from '@jupyterlab/services'; -import * as uuid from 'uuid/v4'; -import { isTestExecution } from '../../common/constants'; -import { IDisposable } from '../../common/types'; -import { swallowExceptions } from '../../common/utils/misc'; -import { getNameOfKernelConnection } from '../jupyter/kernels/helpers'; -import { IKernelProcess } from '../kernel-launcher/types'; -import { IWebSocketLike } from '../kernelSocketWrapper'; -import { IKernelSocket } from '../types'; -import { RawSocket } from './rawSocket'; -// tslint:disable: no-any no-require-imports - -export function suppressShutdownErrors(realKernel: any) { - // When running under a test, mark all futures as done so we - // don't hit this problem: - // https://github.com/jupyterlab/jupyterlab/issues/4252 - // tslint:disable:no-any - if (isTestExecution()) { - const defaultKernel = realKernel as any; // NOSONAR - if (defaultKernel && defaultKernel._futures) { - const futures = defaultKernel._futures as Map; // NOSONAR - if (futures) { - futures.forEach((f) => { - if (f._status !== undefined) { - f._status |= 4; - } - }); - } - } - if (defaultKernel && defaultKernel._reconnectLimit) { - defaultKernel._reconnectLimit = 0; - } - } -} - -/* -RawKernel class represents the mapping from the JupyterLab services IKernel interface -to a raw IPython kernel running on the local machine. RawKernel is in charge of taking -input request, translating them, sending them to an IPython kernel over ZMQ, then passing back the messages -*/ -export class RawKernel implements Kernel.IKernel { - public socket: IKernelSocket & IDisposable; - public get terminated() { - return this.realKernel.terminated as any; // NOSONAR - } - public get statusChanged() { - return this.realKernel.statusChanged as any; // NOSONAR - } - public get iopubMessage() { - return this.realKernel.iopubMessage as any; // NOSONAR - } - public get unhandledMessage() { - return this.realKernel.unhandledMessage as any; // NOSONAR - } - public get anyMessage() { - return this.realKernel.anyMessage as any; // NOSONAR - } - public get serverSettings(): ServerConnection.ISettings { - return this.realKernel.serverSettings; - } - public get id(): string { - return this.realKernel.id; - } - public get name(): string { - return this.realKernel.name; - } - public get model(): Kernel.IModel { - return this.realKernel.model; - } - public get username(): string { - return this.realKernel.username; - } - public get clientId(): string { - return this.realKernel.clientId; - } - public get status(): Kernel.Status { - return this.realKernel.status; - } - public get info(): KernelMessage.IInfoReply | null { - return this.realKernel.info; - } - public get isReady(): boolean { - return this.realKernel.isReady; - } - public get ready(): Promise { - return this.realKernel.ready; - } - public get handleComms(): boolean { - return this.realKernel.handleComms; - } - public get isDisposed(): boolean { - return this.realKernel.isDisposed; - } - constructor( - private realKernel: Kernel.IKernel, - socket: IKernelSocket & IWebSocketLike & IDisposable, - private kernelProcess: IKernelProcess - ) { - // Save this raw socket as our kernel socket. It will be - // used to watch and respond to kernel messages. - this.socket = socket; - - // Pretend like an open occurred. This will prime the real kernel to be connected - socket.emit('open'); - } - - public async shutdown(): Promise { - suppressShutdownErrors(this.realKernel); - await this.kernelProcess.dispose(); - this.socket.dispose(); - } - public getSpec(): Promise { - return this.realKernel.getSpec(); - } - public sendShellMessage( - msg: KernelMessage.IShellMessage, - expectReply?: boolean, - disposeOnDone?: boolean - ): Kernel.IShellFuture< - KernelMessage.IShellMessage, - KernelMessage.IShellMessage - > { - return this.realKernel.sendShellMessage(msg, expectReply, disposeOnDone); - } - public sendControlMessage( - msg: KernelMessage.IControlMessage, - expectReply?: boolean, - disposeOnDone?: boolean - ): Kernel.IControlFuture< - KernelMessage.IControlMessage, - KernelMessage.IControlMessage - > { - return this.realKernel.sendControlMessage(msg, expectReply, disposeOnDone); - } - public reconnect(): Promise { - throw new Error('Reconnect is not supported.'); - } - public interrupt(): Promise { - // Send this directly to our kernel process. Don't send it through the real kernel. The - // real kernel will send a goofy API request to the websocket. - return this.kernelProcess.interrupt(); - } - public restart(): Promise { - throw new Error('This method should not be called. Restart is implemented at a higher level'); - } - public requestKernelInfo(): Promise { - return this.realKernel.requestKernelInfo(); - } - public requestComplete(content: { code: string; cursor_pos: number }): Promise { - return this.realKernel.requestComplete(content); - } - public requestInspect(content: { - code: string; - cursor_pos: number; - detail_level: 0 | 1; - }): Promise { - return this.realKernel.requestInspect(content); - } - public requestHistory( - content: - | KernelMessage.IHistoryRequestRange - | KernelMessage.IHistoryRequestSearch - | KernelMessage.IHistoryRequestTail - ): Promise { - return this.realKernel.requestHistory(content); - } - public requestExecute( - content: { - code: string; - silent?: boolean; - store_history?: boolean; - user_expressions?: import('@phosphor/coreutils').JSONObject; - allow_stdin?: boolean; - stop_on_error?: boolean; - }, - disposeOnDone?: boolean, - metadata?: import('@phosphor/coreutils').JSONObject - ): Kernel.IShellFuture { - return this.realKernel.requestExecute(content, disposeOnDone, metadata); - } - public requestDebug( - // tslint:disable-next-line: no-banned-terms - content: { seq: number; type: 'request'; command: string; arguments?: any }, - disposeOnDone?: boolean - ): Kernel.IControlFuture { - return this.realKernel.requestDebug(content, disposeOnDone); - } - public requestIsComplete(content: { code: string }): Promise { - return this.realKernel.requestIsComplete(content); - } - public requestCommInfo(content: { - target_name?: string; - target?: string; - }): Promise { - return this.realKernel.requestCommInfo(content); - } - public sendInputReply(content: KernelMessage.ReplyContent): void { - return this.realKernel.sendInputReply(content); - } - public connectToComm(targetName: string, commId?: string): Kernel.IComm { - return this.realKernel.connectToComm(targetName, commId); - } - public registerCommTarget( - targetName: string, - callback: (comm: Kernel.IComm, msg: KernelMessage.ICommOpenMsg) => void | PromiseLike - ): void { - return this.realKernel.registerCommTarget(targetName, callback); - } - public removeCommTarget( - targetName: string, - callback: (comm: Kernel.IComm, msg: KernelMessage.ICommOpenMsg) => void | PromiseLike - ): void { - return this.realKernel.removeCommTarget(targetName, callback); - } - public dispose(): void { - swallowExceptions(() => this.realKernel.dispose()); - swallowExceptions(() => this.socket.dispose()); - } - public registerMessageHook( - msgId: string, - hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike - ): void { - this.realKernel.registerMessageHook(msgId, hook); - } - public removeMessageHook( - msgId: string, - hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike - ): void { - this.realKernel.removeMessageHook(msgId, hook); - } -} - -let nonSerializingKernel: any; - -export function createRawKernel(kernelProcess: IKernelProcess, clientId: string): RawKernel { - const jupyterLab = require('@jupyterlab/services') as typeof import('@jupyterlab/services'); // NOSONAR - const jupyterLabSerialize = require('@jupyterlab/services/lib/kernel/serialize') as typeof import('@jupyterlab/services/lib/kernel/serialize'); // NOSONAR - - // Dummy websocket we give to the underlying real kernel - let socketInstance: any; - class RawSocketWrapper extends RawSocket { - constructor() { - super(kernelProcess.connection, jupyterLabSerialize.serialize, jupyterLabSerialize.deserialize); - socketInstance = this; - } - } - - // Remap the server settings for the real kernel to use our dummy websocket - const settings = jupyterLab.ServerConnection.makeSettings({ - WebSocket: RawSocketWrapper as any, // NOSONAR - wsUrl: 'RAW' - }); - - // Then create the real kernel. We will remap its serialize/deserialize functions - // to do nothing so that we can control serialization at our socket layer. - if (!nonSerializingKernel) { - // Note, this is done with a postInstall step (found in build\ci\postInstall.js). In that post install step - // we eliminate the serialize import from the default kernel and remap it to do nothing. - nonSerializingKernel = require('@jupyterlab/services/lib/kernel/nonSerializingKernel') as typeof import('@jupyterlab/services/lib/kernel/default'); // NOSONAR - } - const realKernel = new nonSerializingKernel.DefaultKernel( - { - name: getNameOfKernelConnection(kernelProcess.kernelConnectionMetadata), - serverSettings: settings, - clientId, - handleComms: true - }, - uuid() - ); - - // Use this real kernel in result. - return new RawKernel(realKernel, socketInstance, kernelProcess); -} diff --git a/src/client/datascience/raw-kernel/rawNotebookProvider.ts b/src/client/datascience/raw-kernel/rawNotebookProvider.ts deleted file mode 100644 index 559f6cc8bed7..000000000000 --- a/src/client/datascience/raw-kernel/rawNotebookProvider.ts +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as uuid from 'uuid/v4'; -import { Event, EventEmitter, Uri } from 'vscode'; -import { CancellationToken } from 'vscode-jsonrpc'; -import { ILiveShareApi } from '../../common/application/types'; -import '../../common/extensions'; -import { traceInfo } from '../../common/logger'; -import { IAsyncDisposableRegistry, Resource } from '../../common/types'; -import * as localize from '../../common/utils/localize'; -import { noop } from '../../common/utils/misc'; -import { captureTelemetry } from '../../telemetry'; -import { Telemetry } from '../constants'; -import { - ConnectNotebookProviderOptions, - INotebook, - INotebookMetadataLive, - IRawConnection, - IRawNotebookProvider, - IRawNotebookSupportedService -} from '../types'; - -export class RawConnection implements IRawConnection { - public readonly type = 'raw'; - public readonly localLaunch = true; - public readonly valid = true; - public readonly displayName = localize.DataScience.rawConnectionDisplayName(); - private eventEmitter: EventEmitter = new EventEmitter(); - - public dispose() { - noop(); - } - public get disconnected(): Event { - return this.eventEmitter.event; - } -} - -export class RawNotebookProviderBase implements IRawNotebookProvider { - public get id(): string { - return this._id; - } - // Keep track of the notebooks that we have provided - private notebooks = new Map>(); - private rawConnection: IRawConnection | undefined; - private _id = uuid(); - - constructor( - _liveShare: ILiveShareApi, - private asyncRegistry: IAsyncDisposableRegistry, - private rawNotebookSupportedService: IRawNotebookSupportedService - ) { - this.asyncRegistry.push(this); - } - - public connect(options: ConnectNotebookProviderOptions): Promise { - // For getOnly, we don't want to create a connection, even though we don't have a server - // here we only want to be "connected" when requested to mimic jupyter server function - if (options.getOnly) { - return Promise.resolve(this.rawConnection); - } - - // If not get only, create if needed and return - if (!this.rawConnection) { - this.rawConnection = new RawConnection(); - - // Fire our optional event that we have created a connection - if (options.onConnectionMade) { - options.onConnectionMade(); - } - } - return Promise.resolve(this.rawConnection); - } - - // Check to see if we have all that we need for supporting raw kernel launch - public async supported(): Promise { - return this.rawNotebookSupportedService.supported(); - } - - @captureTelemetry(Telemetry.RawKernelCreatingNotebook, undefined, true) - public async createNotebook( - identity: Uri, - resource: Resource, - disableUI: boolean, - notebookMetadata: INotebookMetadataLive, - cancelToken?: CancellationToken - ): Promise { - return this.createNotebookInstance(resource, identity, disableUI, notebookMetadata, cancelToken); - } - - public async getNotebook(identity: Uri): Promise { - return this.notebooks.get(identity.toString()); - } - - public async dispose(): Promise { - traceInfo(`Shutting down notebooks for ${this.id}`); - const notebooks = await Promise.all([...this.notebooks.values()]); - await Promise.all(notebooks.map((n) => n?.dispose())); - } - - // This may be a bit of a noop in the raw case - public getDisposedError(): Error { - return new Error(localize.DataScience.rawConnectionBrokenError()); - } - - protected getNotebooks(): Promise[] { - return [...this.notebooks.values()]; - } - - protected getConnection(): IRawConnection { - // At the time of getConnection force a connection if not created already - // should always have happened already, but the check here lets us avoid returning undefined option - if (!this.rawConnection) { - this.rawConnection = new RawConnection(); - } - return this.rawConnection; - } - - protected setNotebook(identity: Uri, notebook: Promise) { - const removeNotebook = () => { - if (this.notebooks.get(identity.toString()) === notebook) { - this.notebooks.delete(identity.toString()); - } - }; - - notebook - .then((nb) => { - const oldDispose = nb.dispose; - nb.dispose = () => { - this.notebooks.delete(identity.toString()); - return oldDispose(); - }; - }) - .catch(removeNotebook); - - // Save the notebook - this.notebooks.set(identity.toString(), notebook); - } - - protected createNotebookInstance( - _resource: Resource, - _identity: Uri, - _disableUI?: boolean, - _notebookMetadata?: INotebookMetadataLive, - _cancelToken?: CancellationToken - ): Promise { - throw new Error('You forgot to override createNotebookInstance'); - } -} diff --git a/src/client/datascience/raw-kernel/rawNotebookProviderWrapper.ts b/src/client/datascience/raw-kernel/rawNotebookProviderWrapper.ts deleted file mode 100644 index e37591626fc7..000000000000 --- a/src/client/datascience/raw-kernel/rawNotebookProviderWrapper.ts +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import type { nbformat } from '@jupyterlab/coreutils'; -import { inject, injectable, named } from 'inversify'; -import { Uri } from 'vscode'; -import { CancellationToken } from 'vscode-jsonrpc'; -import * as vsls from 'vsls/vscode'; -import { IApplicationShell, ILiveShareApi, IWorkspaceService } from '../../common/application/types'; -import '../../common/extensions'; - -import { - IAsyncDisposableRegistry, - IConfigurationService, - IDisposableRegistry, - IOutputChannel, - Resource -} from '../../common/types'; -import { IServiceContainer } from '../../ioc/types'; -import { DataScienceStartupTime, JUPYTER_OUTPUT_CHANNEL } from '../constants'; -import { KernelSelector } from '../jupyter/kernels/kernelSelector'; -import { IRoleBasedObject, RoleBasedFactory } from '../jupyter/liveshare/roleBasedFactory'; -import { ILiveShareHasRole } from '../jupyter/liveshare/types'; -import { IKernelLauncher } from '../kernel-launcher/types'; -import { ProgressReporter } from '../progress/progressReporter'; -import { - ConnectNotebookProviderOptions, - IDataScienceFileSystem, - INotebook, - IRawConnection, - IRawNotebookProvider, - IRawNotebookSupportedService -} from '../types'; -import { GuestRawNotebookProvider } from './liveshare/guestRawNotebookProvider'; -import { HostRawNotebookProvider } from './liveshare/hostRawNotebookProvider'; - -interface IRawNotebookProviderInterface extends IRoleBasedObject, IRawNotebookProvider {} - -// tslint:disable:callable-types -type RawNotebookProviderClassType = { - new ( - liveShare: ILiveShareApi, - startupTime: number, - disposableRegistry: IDisposableRegistry, - asyncRegistry: IAsyncDisposableRegistry, - configService: IConfigurationService, - workspaceService: IWorkspaceService, - appShell: IApplicationShell, - fs: IDataScienceFileSystem, - serviceContainer: IServiceContainer, - kernelLauncher: IKernelLauncher, - kernelSelector: KernelSelector, - progressReporter: ProgressReporter, - outputChannel: IOutputChannel, - rawKernelSupported: IRawNotebookSupportedService - ): IRawNotebookProviderInterface; -}; -// tslint:enable:callable-types - -// This class wraps either a HostRawNotebookProvider or a GuestRawNotebookProvider based on the liveshare state. It abstracts -// out the live share specific parts. -@injectable() -export class RawNotebookProviderWrapper implements IRawNotebookProvider, ILiveShareHasRole { - private serverFactory: RoleBasedFactory; - - constructor( - @inject(ILiveShareApi) liveShare: ILiveShareApi, - @inject(DataScienceStartupTime) startupTime: number, - @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, - @inject(IAsyncDisposableRegistry) asyncRegistry: IAsyncDisposableRegistry, - @inject(IConfigurationService) configService: IConfigurationService, - @inject(IWorkspaceService) workspaceService: IWorkspaceService, - @inject(IApplicationShell) appShell: IApplicationShell, - @inject(IDataScienceFileSystem) fs: IDataScienceFileSystem, - @inject(IServiceContainer) serviceContainer: IServiceContainer, - @inject(IKernelLauncher) kernelLauncher: IKernelLauncher, - @inject(KernelSelector) kernelSelector: KernelSelector, - @inject(ProgressReporter) progressReporter: ProgressReporter, - @inject(IOutputChannel) @named(JUPYTER_OUTPUT_CHANNEL) outputChannel: IOutputChannel, - @inject(IRawNotebookSupportedService) rawNotebookSupported: IRawNotebookSupportedService - ) { - // The server factory will create the appropriate HostRawNotebookProvider or GuestRawNotebookProvider based on - // the liveshare state. - this.serverFactory = new RoleBasedFactory( - liveShare, - HostRawNotebookProvider, - GuestRawNotebookProvider, - liveShare, - startupTime, - disposableRegistry, - asyncRegistry, - configService, - workspaceService, - appShell, - fs, - serviceContainer, - kernelLauncher, - kernelSelector, - progressReporter, - outputChannel, - rawNotebookSupported - ); - } - - public get role(): vsls.Role { - return this.serverFactory.role; - } - - public async supported(): Promise { - const notebookProvider = await this.serverFactory.get(); - return notebookProvider.supported(); - } - - public async connect(options: ConnectNotebookProviderOptions): Promise { - const notebookProvider = await this.serverFactory.get(); - return notebookProvider.connect(options); - } - - public async createNotebook( - identity: Uri, - resource: Resource, - disableUI: boolean, - notebookMetadata: nbformat.INotebookMetadata, - cancelToken: CancellationToken - ): Promise { - const notebookProvider = await this.serverFactory.get(); - return notebookProvider.createNotebook(identity, resource, disableUI, notebookMetadata, cancelToken); - } - - public async getNotebook(identity: Uri): Promise { - const notebookProvider = await this.serverFactory.get(); - return notebookProvider.getNotebook(identity); - } - - public async dispose(): Promise { - const server = await this.serverFactory.get(); - return server.dispose(); - } -} diff --git a/src/client/datascience/raw-kernel/rawNotebookSupportedService.ts b/src/client/datascience/raw-kernel/rawNotebookSupportedService.ts deleted file mode 100644 index 5c9cd416f156..000000000000 --- a/src/client/datascience/raw-kernel/rawNotebookSupportedService.ts +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { inject, injectable } from 'inversify'; -import { LocalZMQKernel } from '../../common/experiments/groups'; -import { traceError, traceInfo } from '../../common/logger'; -import { IConfigurationService, IExperimentsManager } from '../../common/types'; -import { sendTelemetryEvent } from '../../telemetry'; -import { Settings, Telemetry } from '../constants'; -import { IRawNotebookSupportedService } from '../types'; - -// This class check to see if we have everything in place to support a raw kernel launch on the machine -@injectable() -export class RawNotebookSupportedService implements IRawNotebookSupportedService { - // Keep track of our ZMQ import check, this doesn't change with settings so we only want to do this once - private _zmqSupportedPromise: Promise | undefined; - - constructor( - @inject(IConfigurationService) private readonly configuration: IConfigurationService, - @inject(IExperimentsManager) private readonly experimentsManager: IExperimentsManager - ) {} - - // Check to see if we have all that we need for supporting raw kernel launch - public async supported(): Promise { - // Save the ZMQ support for last, since it's probably the slowest part - return this.localLaunch() && this.experimentEnabled() && (await this.zmqSupported()) ? true : false; - } - - private localLaunch(): boolean { - const settings = this.configuration.getSettings(undefined); - const serverURI: string | undefined = settings.datascience.jupyterServerURI; - - if (!serverURI || serverURI.toLowerCase() === Settings.JupyterServerLocalLaunch) { - return true; - } - - return false; - } - - // Enable if we are in our experiment or in the insiders channel - private experimentEnabled(): boolean { - return ( - this.experimentsManager.inExperiment(LocalZMQKernel.experiment) || - (this.configuration.getSettings().insidersChannel && - this.configuration.getSettings().insidersChannel !== 'off') - ); - } - - // Check to see if this machine supports our local ZMQ launching - private async zmqSupported(): Promise { - if (!this._zmqSupportedPromise) { - this._zmqSupportedPromise = this.zmqSupportedImpl(); - } - - return this._zmqSupportedPromise; - } - - private async zmqSupportedImpl(): Promise { - try { - await import('zeromq'); - traceInfo(`ZMQ install verified.`); - sendTelemetryEvent(Telemetry.ZMQSupported); - } catch (e) { - traceError(`Exception while attempting zmq :`, e); - sendTelemetryEvent(Telemetry.ZMQNotSupported); - return false; - } - - return true; - } -} diff --git a/src/client/datascience/raw-kernel/rawSession.ts b/src/client/datascience/raw-kernel/rawSession.ts deleted file mode 100644 index 5f0a79d2c0ec..000000000000 --- a/src/client/datascience/raw-kernel/rawSession.ts +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import type { Kernel, KernelMessage, ServerConnection, Session } from '@jupyterlab/services'; -import type { ISignal, Signal } from '@phosphor/signaling'; -import * as uuid from 'uuid/v4'; -import '../../common/extensions'; -import { traceError } from '../../common/logger'; -import { IDisposable } from '../../common/types'; -import { IKernelProcess } from '../kernel-launcher/types'; -import { ISessionWithSocket, KernelSocketInformation } from '../types'; -import { createRawKernel, RawKernel } from './rawKernel'; - -/* -RawSession class implements a jupyterlab ISession object -This provides enough of the ISession interface so that our direct -ZMQ Kernel connection can pretend to be a jupyterlab Session -*/ -export class RawSession implements ISessionWithSocket { - public isDisposed: boolean = false; - private isDisposing?: boolean; - - // Note, ID is the ID of this session - // ClientID is the ID that we pass in messages to the kernel - // and is also the clientID of the active kernel - private _id: string; - private _clientID: string; - private _kernel: RawKernel; - private readonly _statusChanged: Signal; - private readonly _kernelChanged: Signal; - private readonly _ioPubMessage: Signal; - private readonly exitHandler: IDisposable; - - // RawSession owns the lifetime of the kernel process and will dispose it - constructor(public kernelProcess: IKernelProcess) { - // tslint:disable-next-line: no-require-imports - const signaling = require('@phosphor/signaling') as typeof import('@phosphor/signaling'); - this._statusChanged = new signaling.Signal(this); - this._kernelChanged = new signaling.Signal(this); - this._ioPubMessage = new signaling.Signal(this); - // Unique ID for this session instance - this._id = uuid(); - - // ID for our client JMP connection - this._clientID = uuid(); - - // Connect our kernel and hook up status changes - this._kernel = createRawKernel(kernelProcess, this._clientID); - this._kernel.statusChanged.connect(this.onKernelStatus, this); - this._kernel.iopubMessage.connect(this.onIOPubMessage, this); - this.exitHandler = kernelProcess.exited(this.handleUnhandledExitingOfKernelProcess, this); - } - - public async dispose() { - this.isDisposing = true; - if (!this.isDisposed) { - this.exitHandler.dispose(); - await this._kernel.shutdown(); - this._kernel.dispose(); - this.kernelProcess.dispose().ignoreErrors(); - } - this.isDisposed = true; - } - - // Return the ID, this is session's ID, not clientID for messages - get id(): string { - return this._id; - } - - // Return the current kernel for this session - get kernel(): Kernel.IKernelConnection { - return this._kernel; - } - - get kernelSocketInformation(): KernelSocketInformation | undefined { - return { - socket: this._kernel.socket, - options: { - id: this._kernel.id, - clientId: this._clientID, - userName: '', - model: this._kernel.model - } - }; - } - - // Provide status changes for the attached kernel - get statusChanged(): ISignal { - return this._statusChanged; - } - - // Shutdown our session and kernel - public shutdown(): Promise { - return this.dispose(); - } - - // Not Implemented ISession - get terminated(): ISignal { - throw new Error('Not yet implemented'); - } - get kernelChanged(): ISignal { - return this._kernelChanged; - } - get propertyChanged(): ISignal { - throw new Error('Not yet implemented'); - } - get iopubMessage(): ISignal { - return this._ioPubMessage; - } - get unhandledMessage(): ISignal { - throw new Error('Not yet implemented'); - } - get anyMessage(): ISignal { - throw new Error('Not yet implemented'); - } - get path(): string { - throw new Error('Not yet implemented'); - } - get name(): string { - throw new Error('Not yet implemented'); - } - get type(): string { - throw new Error('Not yet implemented'); - } - get serverSettings(): ServerConnection.ISettings { - throw new Error('Not yet implemented'); - } - get model(): Session.IModel { - throw new Error('Not yet implemented'); - } - get status(): Kernel.Status { - throw new Error('Not yet implemented'); - } - public setPath(_path: string): Promise { - throw new Error('Not yet implemented'); - } - public setName(_name: string): Promise { - throw new Error('Not yet implemented'); - } - public setType(_type: string): Promise { - throw new Error('Not yet implemented'); - } - public changeKernel(_options: Partial): Promise { - throw new Error('Not yet implemented'); - } - - // Private - // Send out a message when our kernel changes state - private onKernelStatus(_sender: Kernel.IKernelConnection, state: Kernel.Status) { - this._statusChanged.emit(state); - } - private onIOPubMessage(_sender: Kernel.IKernelConnection, msg: KernelMessage.IIOPubMessage) { - this._ioPubMessage.emit(msg); - } - private handleUnhandledExitingOfKernelProcess(e: { exitCode?: number | undefined; reason?: string | undefined }) { - if (this.isDisposing) { - return; - } - traceError(`Disposing session as kernel process died ExitCode: ${e.exitCode}, Reason: ${e.reason}`); - // Just kill the session. - this.dispose().ignoreErrors(); - } -} diff --git a/src/client/datascience/raw-kernel/rawSocket.ts b/src/client/datascience/raw-kernel/rawSocket.ts deleted file mode 100644 index c592f35bcade..000000000000 --- a/src/client/datascience/raw-kernel/rawSocket.ts +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import type { KernelMessage } from '@jupyterlab/services'; -import * as wireProtocol from '@nteract/messaging/lib/wire-protocol'; -import { IDisposable } from 'monaco-editor'; -import * as uuid from 'uuid/v4'; -import * as WebSocketWS from 'ws'; -import type { Dealer, Subscriber } from 'zeromq'; -import { traceError } from '../../common/logger'; -import { noop } from '../../common/utils/misc'; -import { IKernelConnection } from '../kernel-launcher/types'; -import { IWebSocketLike } from '../kernelSocketWrapper'; -import { IKernelSocket } from '../types'; - -function formConnectionString(config: IKernelConnection, channel: string) { - const portDelimiter = config.transport === 'tcp' ? ':' : '-'; - const port = config[`${channel}_port` as keyof IKernelConnection]; - if (!port) { - throw new Error(`Port not found for channel "${channel}"`); - } - return `${config.transport}://${config.ip}${portDelimiter}${port}`; -} -interface IChannels { - shell: Dealer; - control: Dealer; - stdin: Dealer; - iopub: Subscriber; -} - -// tslint:disable: no-any -/** - * This class creates a WebSocket front end on a ZMQ set of connections. It is special in that - * it does all serialization/deserialization itself. - */ -export class RawSocket implements IWebSocketLike, IKernelSocket, IDisposable { - public onopen: (event: { target: any }) => void = noop; - public onerror: (event: { error: any; message: string; type: string; target: any }) => void = noop; - public onclose: (event: { wasClean: boolean; code: number; reason: string; target: any }) => void = noop; - public onmessage: (event: { data: WebSocketWS.Data; type: string; target: any }) => void = noop; - private receiveHooks: ((data: WebSocketWS.Data) => Promise)[] = []; - private sendHooks: ((data: any, cb?: (err?: Error) => void) => Promise)[] = []; - private msgChain: Promise = Promise.resolve(); - private sendChain: Promise = Promise.resolve(); - private channels: IChannels; - private closed = false; - - constructor( - private connection: IKernelConnection, - private serialize: (msg: KernelMessage.IMessage) => string | ArrayBuffer, - private deserialize: (data: ArrayBuffer | string) => KernelMessage.IMessage - ) { - // Setup our ZMQ channels now - this.channels = this.generateChannels(connection); - } - - public dispose() { - if (!this.closed) { - this.close(); - } - } - - public close(): void { - this.closed = true; - // When the socket is completed / disposed, close all the event - // listeners and shutdown the socket - const closer = (closable: { close(): void }) => { - try { - closable.close(); - } catch (ex) { - traceError(`Error during socket shutdown`, ex); - } - }; - closer(this.channels.control); - closer(this.channels.iopub); - closer(this.channels.shell); - closer(this.channels.stdin); - } - - public emit(event: string | symbol, ...args: any[]): boolean { - switch (event) { - case 'message': - this.onmessage({ data: args[0], type: 'message', target: this }); - break; - case 'close': - this.onclose({ wasClean: true, code: 0, reason: '', target: this }); - break; - case 'error': - this.onerror({ error: '', message: 'to do', type: 'error', target: this }); - break; - case 'open': - this.onopen({ target: this }); - break; - default: - break; - } - return true; - } - public sendToRealKernel(data: any, _callback: any): void { - // If from ipywidgets, this will be serialized already, so turn it back into a message so - // we can add the special hash to it. - const message = this.deserialize(data); - - // Send this directly (don't call back into the hooks) - this.sendMessage(message, true); - } - - public send(data: any, _callback: any): void { - // This comes directly from the jupyter lab kernel. It should be a message already - this.sendMessage(data as KernelMessage.IMessage, false); - } - - public addReceiveHook(hook: (data: WebSocketWS.Data) => Promise): void { - this.receiveHooks.push(hook); - } - public removeReceiveHook(hook: (data: WebSocketWS.Data) => Promise): void { - this.receiveHooks = this.receiveHooks.filter((l) => l !== hook); - } - public addSendHook(hook: (data: any, cb?: ((err?: Error | undefined) => void) | undefined) => Promise): void { - this.sendHooks.push(hook); - } - public removeSendHook( - hook: (data: any, cb?: ((err?: Error | undefined) => void) | undefined) => Promise - ): void { - this.sendHooks = this.sendHooks.filter((p) => p !== hook); - } - private generateChannel( - connection: IKernelConnection, - channel: 'iopub' | 'shell' | 'control' | 'stdin', - ctor: () => T - ): T { - const result = ctor(); - result.connect(formConnectionString(connection, channel)); - this.processSocketMessages(channel, result).catch( - traceError.bind(`Failed to read messages from channel ${channel}`) - ); - return result; - } - private async processSocketMessages( - channel: 'iopub' | 'shell' | 'control' | 'stdin', - readable: Subscriber | Dealer - ) { - // tslint:disable-next-line: await-promise - for await (const msg of readable) { - // Make sure to quit if we are disposed. - if (this.closed) { - break; - } else { - this.onIncomingMessage(channel, msg); - } - } - } - - private generateChannels(connection: IKernelConnection): IChannels { - // tslint:disable-next-line: no-require-imports - const zmq = require('zeromq') as typeof import('zeromq'); - - // Need a routing id for them to share. - const routingId = uuid(); - - // Wire up all of the different channels. - const result: IChannels = { - iopub: this.generateChannel(connection, 'iopub', () => new zmq.Subscriber()), - shell: this.generateChannel(connection, 'shell', () => new zmq.Dealer({ routingId })), - control: this.generateChannel(connection, 'control', () => new zmq.Dealer({ routingId })), - stdin: this.generateChannel(connection, 'stdin', () => new zmq.Dealer({ routingId })) - }; - // What about hb port? Enchannel didn't use this one. - - // Make sure to subscribe to general iopub messages (this is stuff like status changes) - result.iopub.subscribe(); - - return result; - } - - private onIncomingMessage(channel: string, data: any) { - // Decode the message if still possible. - const message = this.closed - ? {} - : (wireProtocol.decode(data, this.connection.key, this.connection.signature_scheme) as any); - - // Make sure it has a channel on it - message.channel = channel; - - if (this.receiveHooks.length) { - // Stick the receive hooks into the message chain. We use chain - // to ensure that: - // a) Hooks finish before we fire the event for real - // b) Event fires - // c) Next message happens after this one (so this side can handle the message before another event goes through) - this.msgChain = this.msgChain - .then(() => { - // Hooks expect serialized data as this normally comes from a WebSocket - const serialized = this.serialize(message); - return Promise.all(this.receiveHooks.map((p) => p(serialized))); - }) - .then(() => this.fireOnMessage(message)); - } else { - this.msgChain = this.msgChain.then(() => this.fireOnMessage(message)); - } - } - - private fireOnMessage(message: any) { - if (!this.closed) { - this.onmessage({ data: message, type: 'message', target: this }); - } - } - - private sendMessage(msg: KernelMessage.IMessage, bypassHooking: boolean) { - // First encode the message. - const data = wireProtocol.encode(msg as any, this.connection.key, this.connection.signature_scheme); - - // Then send through our hooks, and then post to the real zmq socket - if (!bypassHooking && this.sendHooks.length) { - // Separate encoding for ipywidgets. It expects the same result a WebSocket would generate. - const hookData = this.serialize(msg); - - this.sendChain = this.sendChain - .then(() => Promise.all(this.sendHooks.map((s) => s(hookData, noop)))) - .then(() => this.postToSocket(msg.channel, data)); - } else { - this.sendChain = this.sendChain.then(() => { - this.postToSocket(msg.channel, data); - }); - } - } - - private postToSocket(channel: string, data: any) { - const socket = (this.channels as any)[channel]; - if (socket) { - (socket as Dealer).send(data).catch((exc) => { - traceError(`Error communicating with the kernel`, exc); - }); - } else { - traceError(`Attempting to send message on invalid channel: ${channel}`); - } - } -} diff --git a/src/client/datascience/serviceRegistry.ts b/src/client/datascience/serviceRegistry.ts deleted file mode 100644 index 05b44495fa04..000000000000 --- a/src/client/datascience/serviceRegistry.ts +++ /dev/null @@ -1,329 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as vscode from 'vscode'; -import { IExtensionSingleActivationService } from '../activation/types'; -import { UseCustomEditorApi, UseVSCodeNotebookEditorApi } from '../common/constants'; -import { NotebookEditorSupport } from '../common/experiments/groups'; -import { FileSystemPathUtils } from '../common/platform/fs-paths'; -import { IFileSystemPathUtils } from '../common/platform/types'; -import { StartPage } from '../common/startPage/startPage'; -import { IStartPage } from '../common/startPage/types'; -import { IExperimentsManager } from '../common/types'; -import { ProtocolParser } from '../debugger/extension/helpers/protocolParser'; -import { IProtocolParser } from '../debugger/extension/types'; -import { IServiceManager } from '../ioc/types'; -import { Activation } from './activation'; -import { CodeCssGenerator } from './codeCssGenerator'; -import { JupyterCommandLineSelectorCommand } from './commands/commandLineSelector'; -import { CommandRegistry } from './commands/commandRegistry'; -import { ExportCommands } from './commands/exportCommands'; -import { NotebookCommands } from './commands/notebookCommands'; -import { JupyterServerSelectorCommand } from './commands/serverSelector'; -import { DataScienceStartupTime, Identifiers, OurNotebookProvider, VSCodeNotebookProvider } from './constants'; -import { ActiveEditorContextService } from './context/activeEditorContext'; -import { DataViewer } from './data-viewing/dataViewer'; -import { DataViewerDependencyService } from './data-viewing/dataViewerDependencyService'; -import { DataViewerFactory } from './data-viewing/dataViewerFactory'; -import { JupyterVariableDataProvider } from './data-viewing/jupyterVariableDataProvider'; -import { JupyterVariableDataProviderFactory } from './data-viewing/jupyterVariableDataProviderFactory'; -import { IDataViewer, IDataViewerFactory } from './data-viewing/types'; -import { DataScience } from './datascience'; -import { DataScienceFileSystem } from './dataScienceFileSystem'; -import { DataScienceSurveyBannerLogger } from './dataScienceSurveyBanner'; -import { DebugLocationTrackerFactory } from './debugLocationTrackerFactory'; -import { CellHashProvider } from './editor-integration/cellhashprovider'; -import { CodeLensFactory } from './editor-integration/codeLensFactory'; -import { DataScienceCodeLensProvider } from './editor-integration/codelensprovider'; -import { CodeWatcher } from './editor-integration/codewatcher'; -import { Decorator } from './editor-integration/decorator'; -import { HoverProvider } from './editor-integration/hoverProvider'; -import { DataScienceErrorHandler } from './errorHandler/errorHandler'; -import { ExportBase } from './export/exportBase'; -import { ExportDependencyChecker } from './export/exportDependencyChecker'; -import { ExportFileOpener } from './export/exportFileOpener'; -import { ExportManager } from './export/exportManager'; -import { ExportManagerFilePicker } from './export/exportManagerFilePicker'; -import { ExportToHTML } from './export/exportToHTML'; -import { ExportToPDF } from './export/exportToPDF'; -import { ExportToPython } from './export/exportToPython'; -import { ExportUtil } from './export/exportUtil'; -import { ExportFormat, IExport, IExportManager, IExportManagerFilePicker } from './export/types'; -import { GatherListener } from './gather/gatherListener'; -import { GatherLogger } from './gather/gatherLogger'; -import { DebugListener } from './interactive-common/debugListener'; -import { IntellisenseProvider } from './interactive-common/intellisense/intellisenseProvider'; -import { LinkProvider } from './interactive-common/linkProvider'; -import { NotebookProvider } from './interactive-common/notebookProvider'; -import { NotebookServerProvider } from './interactive-common/notebookServerProvider'; -import { NotebookUsageTracker } from './interactive-common/notebookUsageTracker'; -import { ShowPlotListener } from './interactive-common/showPlotListener'; -import { AutoSaveService } from './interactive-ipynb/autoSaveService'; -import { DigestStorage } from './interactive-ipynb/digestStorage'; -import { NativeEditor } from './interactive-ipynb/nativeEditor'; -import { NativeEditorCommandListener } from './interactive-ipynb/nativeEditorCommandListener'; -import { NativeEditorOldWebView } from './interactive-ipynb/nativeEditorOldWebView'; -import { NativeEditorProviderOld } from './interactive-ipynb/nativeEditorProviderOld'; -import { NativeEditorRunByLineListener } from './interactive-ipynb/nativeEditorRunByLineListener'; -import { NativeEditorSynchronizer } from './interactive-ipynb/nativeEditorSynchronizer'; -import { NativeEditorViewTracker } from './interactive-ipynb/nativeEditorViewTracker'; -import { TrustCommandHandler } from './interactive-ipynb/trustCommandHandler'; -import { TrustService } from './interactive-ipynb/trustService'; -import { InteractiveWindow } from './interactive-window/interactiveWindow'; -import { InteractiveWindowCommandListener } from './interactive-window/interactiveWindowCommandListener'; -import { InteractiveWindowProvider } from './interactive-window/interactiveWindowProvider'; -import { IPyWidgetHandler } from './ipywidgets/ipywidgetHandler'; -import { IPyWidgetMessageDispatcherFactory } from './ipywidgets/ipyWidgetMessageDispatcherFactory'; -import { IPyWidgetScriptSource } from './ipywidgets/ipyWidgetScriptSource'; -import { JupyterCommandLineSelector } from './jupyter/commandLineSelector'; -import { DebuggerVariableRegistration } from './jupyter/debuggerVariableRegistration'; -import { DebuggerVariables } from './jupyter/debuggerVariables'; -import { JupyterCommandFactory } from './jupyter/interpreter/jupyterCommand'; -import { JupyterInterpreterDependencyService } from './jupyter/interpreter/jupyterInterpreterDependencyService'; -import { JupyterInterpreterOldCacheStateStore } from './jupyter/interpreter/jupyterInterpreterOldCacheStateStore'; -import { JupyterInterpreterSelectionCommand } from './jupyter/interpreter/jupyterInterpreterSelectionCommand'; -import { JupyterInterpreterSelector } from './jupyter/interpreter/jupyterInterpreterSelector'; -import { JupyterInterpreterService } from './jupyter/interpreter/jupyterInterpreterService'; -import { JupyterInterpreterStateStore } from './jupyter/interpreter/jupyterInterpreterStateStore'; -import { JupyterInterpreterSubCommandExecutionService } from './jupyter/interpreter/jupyterInterpreterSubCommandExecutionService'; -import { CellOutputMimeTypeTracker } from './jupyter/jupyterCellOutputMimeTypeTracker'; -import { JupyterDebugger } from './jupyter/jupyterDebugger'; -import { JupyterExecutionFactory } from './jupyter/jupyterExecutionFactory'; -import { JupyterExporter } from './jupyter/jupyterExporter'; -import { JupyterImporter } from './jupyter/jupyterImporter'; -import { JupyterNotebookProvider } from './jupyter/jupyterNotebookProvider'; -import { JupyterPasswordConnect } from './jupyter/jupyterPasswordConnect'; -import { JupyterServerWrapper } from './jupyter/jupyterServerWrapper'; -import { JupyterSessionManagerFactory } from './jupyter/jupyterSessionManagerFactory'; -import { JupyterVariables } from './jupyter/jupyterVariables'; -import { KernelDependencyService } from './jupyter/kernels/kernelDependencyService'; -import { KernelSelectionProvider } from './jupyter/kernels/kernelSelections'; -import { KernelSelector } from './jupyter/kernels/kernelSelector'; -import { KernelService } from './jupyter/kernels/kernelService'; -import { KernelSwitcher } from './jupyter/kernels/kernelSwitcher'; -import { KernelVariables } from './jupyter/kernelVariables'; -import { NotebookStarter } from './jupyter/notebookStarter'; -import { OldJupyterVariables } from './jupyter/oldJupyterVariables'; -import { ServerPreload } from './jupyter/serverPreload'; -import { JupyterServerSelector } from './jupyter/serverSelector'; -import { JupyterDebugService } from './jupyterDebugService'; -import { JupyterUriProviderRegistration } from './jupyterUriProviderRegistration'; -import { KernelDaemonPool } from './kernel-launcher/kernelDaemonPool'; -import { KernelDaemonPreWarmer } from './kernel-launcher/kernelDaemonPreWarmer'; -import { KernelFinder } from './kernel-launcher/kernelFinder'; -import { KernelLauncher } from './kernel-launcher/kernelLauncher'; -import { IKernelFinder, IKernelLauncher } from './kernel-launcher/types'; -import { MultiplexingDebugService } from './multiplexingDebugService'; -import { NotebookEditorCompatibilitySupport } from './notebook/notebookEditorCompatibilitySupport'; -import { NotebookEditorProvider } from './notebook/notebookEditorProvider'; -import { NotebookEditorProviderWrapper } from './notebook/notebookEditorProviderWrapper'; -import { registerTypes as registerNotebookTypes } from './notebook/serviceRegistry'; -import { NotebookAndInteractiveWindowUsageTracker } from './notebookAndInteractiveTracker'; -import { NotebookExtensibility } from './notebookExtensibility'; -import { NotebookModelFactory } from './notebookStorage/factory'; -import { NativeEditorProvider } from './notebookStorage/nativeEditorProvider'; -import { NativeEditorStorage } from './notebookStorage/nativeEditorStorage'; -import { INotebookStorageProvider, NotebookStorageProvider } from './notebookStorage/notebookStorageProvider'; -import { INotebookModelFactory } from './notebookStorage/types'; -import { PlotViewer } from './plotting/plotViewer'; -import { PlotViewerProvider } from './plotting/plotViewerProvider'; -import { PreWarmActivatedJupyterEnvironmentVariables } from './preWarmVariables'; -import { ProgressReporter } from './progress/progressReporter'; -import { RawNotebookProviderWrapper } from './raw-kernel/rawNotebookProviderWrapper'; -import { RawNotebookSupportedService } from './raw-kernel/rawNotebookSupportedService'; -import { StatusProvider } from './statusProvider'; -import { ThemeFinder } from './themeFinder'; -import { - ICellHashListener, - ICellHashProvider, - ICodeCssGenerator, - ICodeLensFactory, - ICodeWatcher, - IDataScience, - IDataScienceCodeLensProvider, - IDataScienceCommandListener, - IDataScienceErrorHandler, - IDataScienceFileSystem, - IDebugLocationTracker, - IDigestStorage, - IGatherLogger, - IInteractiveWindow, - IInteractiveWindowListener, - IInteractiveWindowProvider, - IJupyterCommandFactory, - IJupyterDebugger, - IJupyterDebugService, - IJupyterExecution, - IJupyterInterpreterDependencyManager, - IJupyterNotebookProvider, - IJupyterPasswordConnect, - IJupyterServerProvider, - IJupyterSessionManagerFactory, - IJupyterSubCommandExecutionService, - IJupyterUriProviderRegistration, - IJupyterVariableDataProvider, - IJupyterVariableDataProviderFactory, - IJupyterVariables, - IKernelDependencyService, - INotebookAndInteractiveWindowUsageTracker, - INotebookEditor, - INotebookEditorProvider, - INotebookExecutionLogger, - INotebookExporter, - INotebookExtensibility, - INotebookImporter, - INotebookProvider, - INotebookServer, - INotebookStorage, - IPlotViewer, - IPlotViewerProvider, - IRawNotebookProvider, - IRawNotebookSupportedService, - IStatusProvider, - IThemeFinder, - ITrustService -} from './types'; - -// README: Did you make sure "dataScienceIocContainer.ts" has also been updated appropriately? - -// tslint:disable-next-line: max-func-body-length -export function registerTypes(serviceManager: IServiceManager) { - const experiments = serviceManager.get(IExperimentsManager); - const inCustomEditorApiExperiment = experiments.inExperiment(NotebookEditorSupport.customEditorExperiment); - const usingCustomEditor = inCustomEditorApiExperiment && !vscode.env.appName.includes('Insider'); // Don't use app manager as it's not available yet. - const useVSCodeNotebookAPI = experiments.inExperiment(NotebookEditorSupport.nativeNotebookExperiment) && !usingCustomEditor; - serviceManager.addSingletonInstance(UseCustomEditorApi, usingCustomEditor); - serviceManager.addSingletonInstance(UseVSCodeNotebookEditorApi, useVSCodeNotebookAPI); - serviceManager.addSingletonInstance(DataScienceStartupTime, Date.now()); - - // This condition is temporary. - serviceManager.addSingleton(VSCodeNotebookProvider, NotebookEditorProvider); - serviceManager.addSingleton(OurNotebookProvider, usingCustomEditor ? NativeEditorProvider : NativeEditorProviderOld); - serviceManager.addSingleton(INotebookEditorProvider, NotebookEditorProviderWrapper); - serviceManager.add(IExtensionSingleActivationService, NotebookEditorCompatibilitySupport); - serviceManager.add(NotebookEditorCompatibilitySupport, NotebookEditorCompatibilitySupport); - if (!useVSCodeNotebookAPI) { - serviceManager.add(INotebookEditor, usingCustomEditor ? NativeEditor : NativeEditorOldWebView); - // These are never going to be required for new VSC NB. - if (!usingCustomEditor) { - serviceManager.add(IInteractiveWindowListener, AutoSaveService); - } - serviceManager.addSingleton(NativeEditorSynchronizer, NativeEditorSynchronizer); - } - - serviceManager.add(ICellHashProvider, CellHashProvider, undefined, [INotebookExecutionLogger]); - serviceManager.addSingleton(INotebookModelFactory, NotebookModelFactory); - serviceManager.addSingleton(INotebookExecutionLogger, HoverProvider); - serviceManager.add(ICodeWatcher, CodeWatcher); - serviceManager.addSingleton(IDataScienceErrorHandler, DataScienceErrorHandler); - serviceManager.add(IDataViewer, DataViewer); - serviceManager.add(IInteractiveWindow, InteractiveWindow); - serviceManager.add(IInteractiveWindowListener, DebugListener); - serviceManager.add(IInteractiveWindowListener, GatherListener); - serviceManager.add(IInteractiveWindowListener, IntellisenseProvider); - serviceManager.add(IInteractiveWindowListener, LinkProvider); - serviceManager.add(IInteractiveWindowListener, ShowPlotListener); - serviceManager.add(IInteractiveWindowListener, IPyWidgetHandler); - serviceManager.add(IInteractiveWindowListener, IPyWidgetScriptSource); - serviceManager.add(IInteractiveWindowListener, NativeEditorRunByLineListener); - serviceManager.add(IJupyterCommandFactory, JupyterCommandFactory); - serviceManager.add(INotebookExporter, JupyterExporter); - serviceManager.add(INotebookImporter, JupyterImporter); - serviceManager.add(INotebookServer, JupyterServerWrapper); - serviceManager.addSingleton(INotebookStorage, NativeEditorStorage); - serviceManager.addSingleton(INotebookStorageProvider, NotebookStorageProvider); - serviceManager.addSingleton(IRawNotebookProvider, RawNotebookProviderWrapper); - serviceManager.addSingleton(IRawNotebookSupportedService, RawNotebookSupportedService); - serviceManager.addSingleton(IJupyterNotebookProvider, JupyterNotebookProvider); - serviceManager.add(IPlotViewer, PlotViewer); - serviceManager.addSingleton(IKernelLauncher, KernelLauncher); - serviceManager.addSingleton(IKernelFinder, KernelFinder); - serviceManager.addSingleton(ActiveEditorContextService, ActiveEditorContextService); - serviceManager.addSingleton(CellOutputMimeTypeTracker, CellOutputMimeTypeTracker, undefined, [IExtensionSingleActivationService, INotebookExecutionLogger]); - serviceManager.addSingleton(CommandRegistry, CommandRegistry); - serviceManager.addSingleton(DataViewerDependencyService, DataViewerDependencyService); - serviceManager.addSingleton(ICodeCssGenerator, CodeCssGenerator); - serviceManager.addSingleton(ICodeLensFactory, CodeLensFactory, undefined, [IInteractiveWindowListener]); - serviceManager.addSingleton(IDataScience, DataScience); - serviceManager.addSingleton(IDataScienceCodeLensProvider, DataScienceCodeLensProvider); - serviceManager.addSingleton(IDataScienceCommandListener, InteractiveWindowCommandListener); - serviceManager.addSingleton(IDataScienceCommandListener, NativeEditorCommandListener); - serviceManager.addSingleton(IDataViewerFactory, DataViewerFactory); - serviceManager.addSingleton(IDebugLocationTracker, DebugLocationTrackerFactory); - serviceManager.addSingleton(IExtensionSingleActivationService, Activation); - serviceManager.addSingleton(IExtensionSingleActivationService, Decorator); - serviceManager.addSingleton(IExtensionSingleActivationService, JupyterInterpreterSelectionCommand); - serviceManager.addSingleton(IExtensionSingleActivationService, PreWarmActivatedJupyterEnvironmentVariables); - serviceManager.addSingleton(IExtensionSingleActivationService, ServerPreload); - serviceManager.addSingleton(IExtensionSingleActivationService, NativeEditorViewTracker); - serviceManager.addSingleton(IExtensionSingleActivationService, NotebookUsageTracker); - serviceManager.addSingleton(IExtensionSingleActivationService, TrustCommandHandler); - serviceManager.addSingleton(IInteractiveWindowListener, DataScienceSurveyBannerLogger); - serviceManager.addSingleton(IInteractiveWindowProvider, InteractiveWindowProvider); - serviceManager.addSingleton(IJupyterDebugger, JupyterDebugger, undefined, [ICellHashListener]); - serviceManager.addSingleton(IJupyterExecution, JupyterExecutionFactory); - serviceManager.addSingleton(IJupyterPasswordConnect, JupyterPasswordConnect); - serviceManager.addSingleton(IJupyterSessionManagerFactory, JupyterSessionManagerFactory); - serviceManager.addSingleton(IExtensionSingleActivationService, DebuggerVariableRegistration); - serviceManager.addSingleton(IJupyterVariables, JupyterVariables, Identifiers.ALL_VARIABLES); - serviceManager.addSingleton(IJupyterVariables, OldJupyterVariables, Identifiers.OLD_VARIABLES); - serviceManager.addSingleton(IJupyterVariables, KernelVariables, Identifiers.KERNEL_VARIABLES); - serviceManager.addSingleton(IJupyterVariables, DebuggerVariables, Identifiers.DEBUGGER_VARIABLES); - serviceManager.addSingleton(IPlotViewerProvider, PlotViewerProvider); - serviceManager.addSingleton(IStatusProvider, StatusProvider); - serviceManager.addSingleton(IThemeFinder, ThemeFinder); - serviceManager.addSingleton(JupyterCommandLineSelector, JupyterCommandLineSelector); - serviceManager.addSingleton(JupyterCommandLineSelectorCommand, JupyterCommandLineSelectorCommand); - serviceManager.addSingleton(JupyterInterpreterDependencyService, JupyterInterpreterDependencyService); - serviceManager.addSingleton(JupyterInterpreterOldCacheStateStore, JupyterInterpreterOldCacheStateStore); - serviceManager.addSingleton(JupyterInterpreterSelector, JupyterInterpreterSelector); - serviceManager.addSingleton(JupyterInterpreterService, JupyterInterpreterService); - serviceManager.addSingleton(JupyterInterpreterStateStore, JupyterInterpreterStateStore); - serviceManager.addSingleton(JupyterServerSelector, JupyterServerSelector); - serviceManager.addSingleton(JupyterServerSelectorCommand, JupyterServerSelectorCommand); - serviceManager.addSingleton(KernelSelectionProvider, KernelSelectionProvider); - serviceManager.addSingleton(KernelSelector, KernelSelector); - serviceManager.addSingleton(KernelService, KernelService); - serviceManager.addSingleton(KernelSwitcher, KernelSwitcher); - serviceManager.addSingleton(NotebookCommands, NotebookCommands); - serviceManager.addSingleton(NotebookStarter, NotebookStarter); - serviceManager.addSingleton(ProgressReporter, ProgressReporter); - serviceManager.addSingleton(INotebookProvider, NotebookProvider); - serviceManager.addSingleton(IJupyterServerProvider, NotebookServerProvider); - serviceManager.addSingleton(IPyWidgetMessageDispatcherFactory, IPyWidgetMessageDispatcherFactory); - serviceManager.addSingleton(IJupyterInterpreterDependencyManager, JupyterInterpreterSubCommandExecutionService); - serviceManager.addSingleton(IJupyterSubCommandExecutionService, JupyterInterpreterSubCommandExecutionService); - serviceManager.addSingleton(KernelDaemonPool, KernelDaemonPool); - serviceManager.addSingleton(IKernelDependencyService, KernelDependencyService); - serviceManager.addSingleton(INotebookAndInteractiveWindowUsageTracker, NotebookAndInteractiveWindowUsageTracker); - serviceManager.addSingleton(KernelDaemonPreWarmer, KernelDaemonPreWarmer); - serviceManager.addSingleton(IStartPage, StartPage, undefined, [IExtensionSingleActivationService]); - serviceManager.add(IProtocolParser, ProtocolParser); - serviceManager.addSingleton(IJupyterDebugService, MultiplexingDebugService, Identifiers.MULTIPLEXING_DEBUGSERVICE); - serviceManager.addSingleton(IJupyterDebugService, JupyterDebugService, Identifiers.RUN_BY_LINE_DEBUGSERVICE); - serviceManager.add(IJupyterVariableDataProvider, JupyterVariableDataProvider); - serviceManager.addSingleton(IJupyterVariableDataProviderFactory, JupyterVariableDataProviderFactory); - serviceManager.addSingleton(IExportManager, ExportManager); - serviceManager.addSingleton(ExportDependencyChecker, ExportDependencyChecker); - serviceManager.addSingleton(ExportFileOpener, ExportFileOpener); - serviceManager.addSingleton(IExport, ExportToPDF, ExportFormat.pdf); - serviceManager.addSingleton(IExport, ExportToHTML, ExportFormat.html); - serviceManager.addSingleton(IExport, ExportToPython, ExportFormat.python); - serviceManager.addSingleton(IExport, ExportBase, 'Export Base'); - serviceManager.addSingleton(ExportUtil, ExportUtil); - serviceManager.addSingleton(ExportCommands, ExportCommands); - serviceManager.addSingleton(IExportManagerFilePicker, ExportManagerFilePicker); - serviceManager.addSingleton(IJupyterUriProviderRegistration, JupyterUriProviderRegistration); - serviceManager.addSingleton(IDigestStorage, DigestStorage); - serviceManager.addSingleton(ITrustService, TrustService); - serviceManager.addSingleton(IDataScienceFileSystem, DataScienceFileSystem); - serviceManager.addSingleton(IFileSystemPathUtils, FileSystemPathUtils); - serviceManager.addSingleton(INotebookExtensibility, NotebookExtensibility); - - registerGatherTypes(serviceManager); - registerNotebookTypes(serviceManager); -} - -export function registerGatherTypes(serviceManager: IServiceManager) { - serviceManager.add(IGatherLogger, GatherLogger, undefined, [INotebookExecutionLogger]); -} diff --git a/src/client/datascience/shiftEnterBanner.ts b/src/client/datascience/shiftEnterBanner.ts deleted file mode 100644 index 7fadde77a3a6..000000000000 --- a/src/client/datascience/shiftEnterBanner.ts +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { ConfigurationTarget } from 'vscode'; -import { IApplicationShell } from '../common/application/types'; -import '../common/extensions'; -import { IConfigurationService, IPersistentStateFactory, IPythonExtensionBanner } from '../common/types'; -import * as localize from '../common/utils/localize'; -import { captureTelemetry, sendTelemetryEvent } from '../telemetry'; -import { Telemetry } from './constants'; -import { IJupyterExecution } from './types'; - -export enum InteractiveShiftEnterStateKeys { - ShowBanner = 'InteractiveShiftEnterBanner' -} - -enum InteractiveShiftEnterLabelIndex { - Yes, - No -} - -// Create a banner to ask users if they want to send shift-enter to the interactive window or not -@injectable() -export class InteractiveShiftEnterBanner implements IPythonExtensionBanner { - private initialized?: boolean; - private disabledInCurrentSession: boolean = false; - private bannerMessage: string = localize.InteractiveShiftEnterBanner.bannerMessage(); - private bannerLabels: string[] = [localize.Common.bannerLabelYes(), localize.Common.bannerLabelNo()]; - - constructor( - @inject(IApplicationShell) private appShell: IApplicationShell, - @inject(IPersistentStateFactory) private persistentState: IPersistentStateFactory, - @inject(IJupyterExecution) private jupyterExecution: IJupyterExecution, - @inject(IConfigurationService) private configuration: IConfigurationService - ) { - this.initialize(); - } - - public initialize() { - if (this.initialized) { - return; - } - this.initialized = true; - - if (!this.enabled) { - return; - } - } - - public get enabled(): boolean { - return this.persistentState.createGlobalPersistentState( - InteractiveShiftEnterStateKeys.ShowBanner, - true - ).value; - } - - public async showBanner(): Promise { - if (!this.enabled) { - return; - } - - const show = await this.shouldShowBanner(); - if (!show) { - return; - } - - // This check is independent from shouldShowBanner, that just checks the persistent state. - // The Jupyter check should only happen once and should disable the banner if it fails (don't reprompt and don't recheck) - const jupyterFound = await this.jupyterExecution.isNotebookSupported(); - if (!jupyterFound) { - await this.disableBanner(); - return; - } - - sendTelemetryEvent(Telemetry.ShiftEnterBannerShown); - const response = await this.appShell.showInformationMessage(this.bannerMessage, ...this.bannerLabels); - switch (response) { - case this.bannerLabels[InteractiveShiftEnterLabelIndex.Yes]: { - await this.enableInteractiveShiftEnter(); - break; - } - case this.bannerLabels[InteractiveShiftEnterLabelIndex.No]: { - await this.disableInteractiveShiftEnter(); - break; - } - default: { - // Disable for the current session. - this.disabledInCurrentSession = true; - } - } - } - - public async shouldShowBanner(): Promise { - const settings = this.configuration.getSettings(); - return Promise.resolve( - this.enabled && - !this.disabledInCurrentSession && - !settings.datascience.sendSelectionToInteractiveWindow && - settings.datascience.enabled - ); - } - - @captureTelemetry(Telemetry.DisableInteractiveShiftEnter) - public async disableInteractiveShiftEnter(): Promise { - await this.configuration.updateSetting( - 'dataScience.sendSelectionToInteractiveWindow', - false, - undefined, - ConfigurationTarget.Global - ); - await this.disableBanner(); - } - - @captureTelemetry(Telemetry.EnableInteractiveShiftEnter) - public async enableInteractiveShiftEnter(): Promise { - await this.configuration.updateSetting( - 'dataScience.sendSelectionToInteractiveWindow', - true, - undefined, - ConfigurationTarget.Global - ); - await this.disableBanner(); - } - - private async disableBanner(): Promise { - await this.persistentState - .createGlobalPersistentState(InteractiveShiftEnterStateKeys.ShowBanner, false) - .updateValue(false); - } -} diff --git a/src/client/datascience/statusProvider.ts b/src/client/datascience/statusProvider.ts deleted file mode 100644 index c82ad4bad9dc..000000000000 --- a/src/client/datascience/statusProvider.ts +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { inject, injectable } from 'inversify'; -import { Disposable, ProgressLocation, ProgressOptions } from 'vscode'; - -import { IApplicationShell } from '../common/application/types'; -import { createDeferred, Deferred } from '../common/utils/async'; -import { IInteractiveBase, IStatusProvider } from './types'; - -class StatusItem implements Disposable { - private deferred: Deferred; - private disposed: boolean = false; - private timeout: NodeJS.Timer | number | undefined; - private disposeCallback: () => void; - - constructor(_title: string, disposeCallback: () => void, timeout?: number) { - this.deferred = createDeferred(); - this.disposeCallback = disposeCallback; - - // A timeout is possible too. Auto dispose if that's the case - if (timeout) { - this.timeout = setTimeout(this.dispose, timeout); - } - } - - public dispose = () => { - if (!this.disposed) { - this.disposed = true; - if (this.timeout) { - // tslint:disable-next-line: no-any - clearTimeout(this.timeout as any); - this.timeout = undefined; - } - this.disposeCallback(); - if (!this.deferred.completed) { - this.deferred.resolve(); - } - } - }; - - public promise = (): Promise => { - return this.deferred.promise; - }; - - public reject = () => { - this.deferred.reject(); - this.dispose(); - }; -} - -@injectable() -export class StatusProvider implements IStatusProvider { - private statusCount: number = 0; - - constructor(@inject(IApplicationShell) private applicationShell: IApplicationShell) {} - - public set( - message: string, - showInWebView: boolean, - timeout?: number, - cancel?: () => void, - panel?: IInteractiveBase - ): Disposable { - // Start our progress - this.incrementCount(showInWebView, panel); - - // Create a StatusItem that will return our promise - const statusItem = new StatusItem(message, () => this.decrementCount(panel), timeout); - - const progressOptions: ProgressOptions = { - location: cancel ? ProgressLocation.Notification : ProgressLocation.Window, - title: message, - cancellable: cancel !== undefined - }; - - // Set our application shell status with a busy icon - this.applicationShell.withProgress(progressOptions, (_p, c) => { - if (c && cancel) { - c.onCancellationRequested(() => { - cancel(); - statusItem.reject(); - }); - } - return statusItem.promise(); - }); - - return statusItem; - } - - public async waitWithStatus( - promise: () => Promise, - message: string, - showInWebView: boolean, - timeout?: number, - cancel?: () => void, - panel?: IInteractiveBase - ): Promise { - // Create a status item and wait for our promise to either finish or reject - const status = this.set(message, showInWebView, timeout, cancel, panel); - let result: T; - try { - result = await promise(); - } finally { - status.dispose(); - } - return result; - } - - private incrementCount = (showInWebView: boolean, panel?: IInteractiveBase) => { - if (this.statusCount === 0) { - if (panel && showInWebView) { - panel.startProgress(); - } - } - this.statusCount += 1; - }; - - private decrementCount = (panel?: IInteractiveBase) => { - const updatedCount = this.statusCount - 1; - if (updatedCount === 0) { - if (panel) { - panel.stopProgress(); - } - } - this.statusCount = Math.max(updatedCount, 0); - }; -} diff --git a/src/client/datascience/themeFinder.ts b/src/client/datascience/themeFinder.ts deleted file mode 100644 index c0deeb79e47c..000000000000 --- a/src/client/datascience/themeFinder.ts +++ /dev/null @@ -1,293 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { inject, injectable } from 'inversify'; -import * as path from 'path'; - -import { LanguageConfiguration } from 'vscode'; -import { EXTENSION_ROOT_DIR, PYTHON_LANGUAGE } from '../common/constants'; -import { traceError } from '../common/logger'; - -import { ICurrentProcess, IExtensions } from '../common/types'; -import { getLanguageConfiguration } from '../language/languageConfiguration'; -import { IDataScienceFileSystem, IThemeFinder } from './types'; - -// tslint:disable:no-any - -interface IThemeData { - rootFile: string; - isDark: boolean; -} - -@injectable() -export class ThemeFinder implements IThemeFinder { - private themeCache: { [key: string]: IThemeData | undefined } = {}; - private languageCache: { [key: string]: string | undefined } = {}; - - constructor( - @inject(IExtensions) private extensions: IExtensions, - @inject(ICurrentProcess) private currentProcess: ICurrentProcess, - @inject(IDataScienceFileSystem) private fs: IDataScienceFileSystem - ) {} - - public async findThemeRootJson(themeName: string): Promise { - // find our data - const themeData = await this.findThemeData(themeName); - - // Use that data if it worked - if (themeData) { - return themeData.rootFile; - } - } - - public async findTmLanguage(language: string): Promise { - // See if already found it or not - if (!this.themeCache.hasOwnProperty(language)) { - try { - this.languageCache[language] = await this.findMatchingLanguage(language); - } catch (exc) { - traceError(exc); - } - } - return this.languageCache[language]; - } - - public async findLanguageConfiguration(language: string): Promise { - if (language === PYTHON_LANGUAGE) { - // Custom for python. Some of these are required by monaco. - return { - comments: { - lineComment: '#', - blockComment: ['"""', '"""'] - }, - brackets: [ - ['{', '}'], - ['[', ']'], - ['(', ')'] - ], - autoClosingPairs: [ - { open: '{', close: '}' }, - { open: '[', close: ']' }, - { open: '(', close: ')' }, - { open: '"', close: '"', notIn: ['string'] }, - { open: "'", close: "'", notIn: ['string', 'comment'] } - ], - surroundingPairs: [ - { open: '{', close: '}' }, - { open: '[', close: ']' }, - { open: '(', close: ')' }, - { open: '"', close: '"' }, - { open: "'", close: "'" } - ], - folding: { - offSide: true, - markers: { - start: new RegExp('^\\s*#region\\b'), - end: new RegExp('^\\s*#endregion\\b') - } - }, - ...getLanguageConfiguration() - } as any; // NOSONAR - } - return this.findMatchingLanguageConfiguration(language); - } - - public async isThemeDark(themeName: string): Promise { - // find our data - const themeData = await this.findThemeData(themeName); - - // Use that data if it worked - if (themeData) { - return themeData.isDark; - } - } - - private async findThemeData(themeName: string): Promise { - // See if already found it or not - if (!this.themeCache.hasOwnProperty(themeName)) { - try { - this.themeCache[themeName] = await this.findMatchingTheme(themeName); - } catch (exc) { - traceError(exc); - } - } - return this.themeCache[themeName]; - } - - private async findMatchingLanguage(language: string): Promise { - const currentExe = this.currentProcess.execPath; - let currentPath = path.dirname(currentExe); - - // Should be somewhere under currentPath/resources/app/extensions inside of a json file - let extensionsPath = path.join(currentPath, 'resources', 'app', 'extensions'); - if (!(await this.fs.localDirectoryExists(extensionsPath))) { - // Might be on mac or linux. try a different path - currentPath = path.resolve(currentPath, '../../../..'); - extensionsPath = path.join(currentPath, 'resources', 'app', 'extensions'); - } - - // Search through all of the files in this folder - let results = await this.findMatchingLanguages(language, extensionsPath); - - // If that didn't work, see if it's our MagicPython predefined tmLanguage - if (!results && language === PYTHON_LANGUAGE) { - results = await this.fs.readLocalFile( - path.join(EXTENSION_ROOT_DIR, 'resources', 'MagicPython.tmLanguage.json') - ); - } - - return results; - } - - private async findMatchingLanguageConfiguration(language: string): Promise { - try { - const currentExe = this.currentProcess.execPath; - let currentPath = path.dirname(currentExe); - - // Should be somewhere under currentPath/resources/app/extensions inside of a json file - let extensionsPath = path.join(currentPath, 'resources', 'app', 'extensions', language); - if (!(await this.fs.localDirectoryExists(extensionsPath))) { - // Might be on mac or linux. try a different path - currentPath = path.resolve(currentPath, '../../../..'); - extensionsPath = path.join(currentPath, 'resources', 'app', 'extensions', language); - } - - // See if the 'language-configuration.json' file exists - const filePath = path.join(extensionsPath, 'language-configuration.json'); - if (await this.fs.localFileExists(filePath)) { - const contents = await this.fs.readLocalFile(filePath); - return JSON.parse(contents) as LanguageConfiguration; - } - } catch { - // Do nothing if an error - } - - return {}; - } - - private async findMatchingLanguages(language: string, rootPath: string): Promise { - // Environment variable to mimic missing json problem - if (process.env.VSC_PYTHON_MIMIC_REMOTE) { - return undefined; - } - - // Search through all package.json files in the directory and below, looking - // for the themeName in them. - const foundPackages = await this.fs.searchLocal('**/package.json', rootPath); - if (foundPackages && foundPackages.length > 0) { - // For each one, open it up and look for the theme name. - for (const f of foundPackages) { - const fpath = path.join(rootPath, f); - const data = await this.findMatchingLanguageFromJson(fpath, language); - if (data) { - return data; - } - } - } - } - - private async findMatchingTheme(themeName: string): Promise { - // Environment variable to mimic missing json problem - if (process.env.VSC_PYTHON_MIMIC_REMOTE) { - return undefined; - } - - // Look through all extensions to find the theme. This will search - // the default extensions folder and our installed extensions. - const extensions = this.extensions.all; - for (const e of extensions) { - const result = await this.findMatchingThemeFromJson(path.join(e.extensionPath, 'package.json'), themeName); - if (result) { - return result; - } - } - - // If didn't find in the extensions folder, then try searching manually. This shouldn't happen, but - // this is our backup plan in case vscode changes stuff. - const currentExe = this.currentProcess.execPath; - let currentPath = path.dirname(currentExe); - - // Should be somewhere under currentPath/resources/app/extensions inside of a json file - let extensionsPath = path.join(currentPath, 'resources', 'app', 'extensions'); - if (!(await this.fs.localDirectoryExists(extensionsPath))) { - // Might be on mac or linux. try a different path - currentPath = path.resolve(currentPath, '../../../..'); - extensionsPath = path.join(currentPath, 'resources', 'app', 'extensions'); - } - const other = await this.findMatchingThemes(extensionsPath, themeName); - if (other) { - return other; - } - } - - private async findMatchingThemes(rootPath: string, themeName: string): Promise { - // Search through all package.json files in the directory and below, looking - // for the themeName in them. - const foundPackages = await this.fs.searchLocal('**/package.json', rootPath); - if (foundPackages && foundPackages.length > 0) { - // For each one, open it up and look for the theme name. - for (const f of foundPackages) { - const fpath = path.join(rootPath, f); - const data = await this.findMatchingThemeFromJson(fpath, themeName); - if (data) { - return data; - } - } - } - } - - private async findMatchingLanguageFromJson(packageJson: string, language: string): Promise { - // Read the contents of the json file - const text = await this.fs.readLocalFile(packageJson); - const json = JSON.parse(text); - - // Should have a name entry and a contributes entry - if (json.hasOwnProperty('name') && json.hasOwnProperty('contributes')) { - // See if contributes has a grammars - const contributes = json.contributes; - if (contributes.hasOwnProperty('grammars')) { - const grammars = contributes.grammars as any[]; - // Go through each theme, seeing if the label matches our theme name - for (const t of grammars) { - if (t.hasOwnProperty('language') && t.language === language) { - // Path is relative to the package.json file. - const rootFile = t.hasOwnProperty('path') - ? path.join(path.dirname(packageJson), t.path.toString()) - : ''; - return this.fs.readLocalFile(rootFile); - } - } - } - } - } - - private async findMatchingThemeFromJson(packageJson: string, themeName: string): Promise { - // Read the contents of the json file - const text = await this.fs.readLocalFile(packageJson); - const json = JSON.parse(text); - - // Should have a name entry and a contributes entry - if (json.hasOwnProperty('name') && json.hasOwnProperty('contributes')) { - // See if contributes has a theme - const contributes = json.contributes; - if (contributes.hasOwnProperty('themes')) { - const themes = contributes.themes as any[]; - // Go through each theme, seeing if the label matches our theme name - for (const t of themes) { - if ( - (t.hasOwnProperty('label') && t.label === themeName) || - (t.hasOwnProperty('id') && t.id === themeName) - ) { - const isDark = t.hasOwnProperty('uiTheme') && t.uiTheme === 'vs-dark'; - // Path is relative to the package.json file. - const rootFile = t.hasOwnProperty('path') - ? path.join(path.dirname(packageJson), t.path.toString()) - : ''; - - return { isDark, rootFile }; - } - } - } - } - } -} diff --git a/src/client/datascience/types.ts b/src/client/datascience/types.ts deleted file mode 100644 index 70a0e1270651..000000000000 --- a/src/client/datascience/types.ts +++ /dev/null @@ -1,1404 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import type { nbformat } from '@jupyterlab/coreutils'; -import type { Session } from '@jupyterlab/services'; -import type { Kernel, KernelMessage } from '@jupyterlab/services/lib/kernel'; -import type { JSONObject } from '@phosphor/coreutils'; -import { WriteStream } from 'fs-extra'; -import { Observable } from 'rxjs/Observable'; -import { SemVer } from 'semver'; -import { - CancellationToken, - CodeLens, - CodeLensProvider, - DebugConfiguration, - DebugSession, - Disposable, - Event, - LanguageConfiguration, - QuickPickItem, - Range, - TextDocument, - TextEditor, - Uri -} from 'vscode'; -import { DebugProtocol } from 'vscode-debugprotocol'; -import type { NotebookCell } from 'vscode-proposed'; -import type { Data as WebSocketData } from 'ws'; -import { ServerStatus } from '../../datascience-ui/interactive-common/mainState'; -import { ICommandManager, IDebugService } from '../common/application/types'; -import { FileStat, TemporaryFile } from '../common/platform/types'; -import { ExecutionResult, ObservableExecutionResult, SpawnOptions } from '../common/process/types'; -import { IAsyncDisposable, IDataScienceSettings, IDisposable, InteractiveWindowMode, Resource } from '../common/types'; -import { StopWatch } from '../common/utils/stopWatch'; -import { PythonEnvironment } from '../pythonEnvironments/info'; -import { JupyterCommands } from './constants'; -import { IDataViewerDataProvider } from './data-viewing/types'; -import { NotebookModelChange } from './interactive-common/interactiveWindowTypes'; -import { JupyterServerInfo } from './jupyter/jupyterConnection'; -import { JupyterInstallError } from './jupyter/jupyterInstallError'; -import { JupyterKernelSpec } from './jupyter/kernels/jupyterKernelSpec'; -import { KernelConnectionMetadata } from './jupyter/kernels/types'; - -// tslint:disable-next-line:no-any -export type PromiseFunction = (...any: any[]) => Promise; - -// Main interface -export const IDataScience = Symbol('IDataScience'); -export interface IDataScience extends Disposable { - activate(): Promise; -} - -export const IDataScienceCommandListener = Symbol('IDataScienceCommandListener'); -export interface IDataScienceCommandListener { - register(commandManager: ICommandManager): void; -} - -export interface IRawConnection extends Disposable { - readonly type: 'raw'; - readonly localLaunch: true; - readonly valid: boolean; - readonly displayName: string; - disconnected: Event; -} - -export interface IJupyterConnection extends Disposable { - readonly type: 'jupyter'; - readonly localLaunch: boolean; - readonly valid: boolean; - readonly displayName: string; - disconnected: Event; - - // Jupyter specific members - readonly baseUrl: string; - readonly token: string; - readonly hostName: string; - localProcExitCode: number | undefined; - readonly rootDirectory: string; // Directory where the notebook server was started. - readonly url?: string; - // tslint:disable-next-line: no-any - getAuthHeader?(): any; // Snould be a json object -} - -export type INotebookProviderConnection = IRawConnection | IJupyterConnection; - -export enum InterruptResult { - Success = 0, - TimedOut = 1, - Restarted = 2 -} - -// Information used to execute a notebook -export interface INotebookExecutionInfo { - // Connection to what has provided our notebook, such as a jupyter - // server or a raw ZMQ kernel - connectionInfo: INotebookProviderConnection; - uri: string | undefined; // Different from the connectionInfo as this is the setting used, not the result - kernelConnectionMetadata?: KernelConnectionMetadata; - workingDir: string | undefined; - purpose: string | undefined; // Purpose this server is for -} - -// Information used to launch a jupyter notebook server - -// Information used to launch a notebook server -export interface INotebookServerLaunchInfo { - connectionInfo: IJupyterConnection; - uri: string | undefined; // Different from the connectionInfo as this is the setting used, not the result - kernelConnectionMetadata?: KernelConnectionMetadata; - workingDir: string | undefined; - purpose: string | undefined; // Purpose this server is for -} - -export interface INotebookCompletion { - matches: ReadonlyArray; - cursor: { - start: number; - end: number; - }; - metadata: {}; -} - -export type INotebookMetadataLive = nbformat.INotebookMetadata & { id?: string }; - -// Talks to a jupyter ipython kernel to retrieve data for cells -export const INotebookServer = Symbol('INotebookServer'); -export interface INotebookServer extends IAsyncDisposable { - readonly id: string; - createNotebook( - resource: Resource, - identity: Uri, - notebookMetadata?: INotebookMetadataLive, - cancelToken?: CancellationToken - ): Promise; - getNotebook(identity: Uri, cancelToken?: CancellationToken): Promise; - connect(launchInfo: INotebookServerLaunchInfo, cancelToken?: CancellationToken): Promise; - getConnectionInfo(): IJupyterConnection | undefined; - waitForConnect(): Promise; - shutdown(): Promise; -} - -// Provides a service to determine if raw notebook is supported or not -export const IRawNotebookSupportedService = Symbol('IRawNotebookSupportedService'); -export interface IRawNotebookSupportedService { - supported(): Promise; -} - -// Provides notebooks that talk directly to kernels as opposed to a jupyter server -export const IRawNotebookProvider = Symbol('IRawNotebookProvider'); -export interface IRawNotebookProvider extends IAsyncDisposable { - supported(): Promise; - connect(connect: ConnectNotebookProviderOptions): Promise; - createNotebook( - identity: Uri, - resource: Resource, - disableUI?: boolean, - notebookMetadata?: nbformat.INotebookMetadata, - cancelToken?: CancellationToken - ): Promise; - getNotebook(identity: Uri, token?: CancellationToken): Promise; -} - -// Provides notebooks that talk to jupyter servers -export const IJupyterNotebookProvider = Symbol('IJupyterNotebookProvider'); -export interface IJupyterNotebookProvider { - connect(options: ConnectNotebookProviderOptions): Promise; - createNotebook(options: GetNotebookOptions): Promise; - getNotebook(options: GetNotebookOptions): Promise; - disconnect(options: ConnectNotebookProviderOptions): Promise; -} - -export interface INotebook extends IAsyncDisposable { - readonly resource: Resource; - readonly connection: INotebookProviderConnection | undefined; - kernelSocket: Observable; - readonly identity: Uri; - readonly status: ServerStatus; - readonly disposed: boolean; - readonly session: IJupyterSession; // Temporary. This just makes it easier to write a notebook that works with VS code types. - onSessionStatusChanged: Event; - onDisposed: Event; - onKernelChanged: Event; - onKernelRestarted: Event; - onKernelInterrupted: Event; - clear(id: string): void; - executeObservable(code: string, file: string, line: number, id: string, silent: boolean): Observable; - execute( - code: string, - file: string, - line: number, - id: string, - cancelToken?: CancellationToken, - silent?: boolean - ): Promise; - inspect(code: string, offsetInCode?: number, cancelToken?: CancellationToken): Promise; - getCompletion( - cellCode: string, - offsetInCode: number, - cancelToken?: CancellationToken - ): Promise; - restartKernel(timeoutInMs: number): Promise; - waitForIdle(timeoutInMs: number): Promise; - interruptKernel(timeoutInMs: number): Promise; - setLaunchingFile(file: string): Promise; - getSysInfo(): Promise; - requestKernelInfo(): Promise; - setMatplotLibStyle(useDark: boolean): Promise; - getMatchingInterpreter(): PythonEnvironment | undefined; - /** - * Gets the metadata that's used to start/connect to a Kernel. - */ - getKernelConnection(): KernelConnectionMetadata | undefined; - /** - * Sets the metadata that's used to start/connect to a Kernel. - * Doing so results in a new kernel being started (i.e. a change in the kernel). - */ - setKernelConnection(connectionMetadata: KernelConnectionMetadata, timeoutMS: number): Promise; - getLoggers(): INotebookExecutionLogger[]; - registerIOPubListener(listener: (msg: KernelMessage.IIOPubMessage, requestId: string) => void): void; - registerCommTarget( - targetName: string, - callback: (comm: Kernel.IComm, msg: KernelMessage.ICommOpenMsg) => void | PromiseLike - ): void; - sendCommMessage( - buffers: (ArrayBuffer | ArrayBufferView)[], - content: { comm_id: string; data: JSONObject; target_name: string | undefined }, - // tslint:disable-next-line: no-any - metadata: any, - // tslint:disable-next-line: no-any - msgId: any - ): Kernel.IShellFuture< - KernelMessage.IShellMessage<'comm_msg'>, - KernelMessage.IShellMessage - >; - requestCommInfo(content: KernelMessage.ICommInfoRequestMsg['content']): Promise; - registerMessageHook( - msgId: string, - hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike - ): void; - removeMessageHook(msgId: string, hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike): void; -} - -// Options for connecting to a notebook provider -export type ConnectNotebookProviderOptions = { - getOnly?: boolean; - disableUI?: boolean; - localOnly?: boolean; - token?: CancellationToken; - onConnectionMade?(): void; // Optional callback for when the first connection is made -}; - -export interface INotebookServerOptions { - uri?: string; - usingDarkTheme?: boolean; - skipUsingDefaultConfig?: boolean; - workingDir?: string; - purpose: string; - metadata?: INotebookMetadataLive; - disableUI?: boolean; - skipSearchingForKernel?: boolean; - allowUI(): boolean; -} - -export const INotebookExecutionLogger = Symbol('INotebookExecutionLogger'); -export interface INotebookExecutionLogger extends IDisposable { - preExecute(cell: ICell, silent: boolean): Promise; - postExecute(cell: ICell, silent: boolean): Promise; - onKernelRestarted(): void; - preHandleIOPub?(msg: KernelMessage.IIOPubMessage): KernelMessage.IIOPubMessage; -} - -export interface IGatherProvider { - logExecution(vscCell: ICell): void; - gatherCode(vscCell: ICell): string; - resetLog(): void; -} - -export const IGatherLogger = Symbol('IGatherLogger'); -export interface IGatherLogger extends INotebookExecutionLogger { - getGatherProvider(): IGatherProvider | undefined; -} - -export const IJupyterExecution = Symbol('IJupyterExecution'); -export interface IJupyterExecution extends IAsyncDisposable { - serverStarted: Event; - isNotebookSupported(cancelToken?: CancellationToken): Promise; - getImportPackageVersion(cancelToken?: CancellationToken): Promise; - isSpawnSupported(cancelToken?: CancellationToken): Promise; - connectToNotebookServer( - options?: INotebookServerOptions, - cancelToken?: CancellationToken - ): Promise; - spawnNotebook(file: string): Promise; - importNotebook(file: Uri, template: string | undefined): Promise; - getUsableJupyterPython(cancelToken?: CancellationToken): Promise; - getServer(options?: INotebookServerOptions): Promise; - getNotebookError(): Promise; - refreshCommands(): Promise; -} - -export const IJupyterDebugger = Symbol('IJupyterDebugger'); -export interface IJupyterDebugger { - readonly isRunningByLine: boolean; - startRunByLine(notebook: INotebook, cellHashFileName: string): Promise; - startDebugging(notebook: INotebook): Promise; - stopDebugging(notebook: INotebook): Promise; - onRestart(notebook: INotebook): void; -} - -export interface IJupyterPasswordConnectInfo { - requestHeaders?: HeadersInit; - remappedBaseUrl?: string; - remappedToken?: string; -} - -export const IJupyterPasswordConnect = Symbol('IJupyterPasswordConnect'); -export interface IJupyterPasswordConnect { - getPasswordConnectionInfo(url: string): Promise; -} - -export const IJupyterSession = Symbol('IJupyterSession'); -export interface IJupyterSession extends IAsyncDisposable { - onSessionStatusChanged: Event; - readonly status: ServerStatus; - readonly workingDirectory: string; - readonly kernelSocket: Observable; - restart(timeout: number): Promise; - interrupt(timeout: number): Promise; - waitForIdle(timeout: number): Promise; - requestExecute( - content: KernelMessage.IExecuteRequestMsg['content'], - disposeOnDone?: boolean, - metadata?: JSONObject - ): Kernel.IShellFuture | undefined; - requestComplete( - content: KernelMessage.ICompleteRequestMsg['content'] - ): Promise; - requestInspect( - content: KernelMessage.IInspectRequestMsg['content'] - ): Promise; - sendInputReply(content: string): void; - changeKernel(kernelConnection: KernelConnectionMetadata, timeoutMS: number): Promise; - registerCommTarget( - targetName: string, - callback: (comm: Kernel.IComm, msg: KernelMessage.ICommOpenMsg) => void | PromiseLike - ): void; - sendCommMessage( - buffers: (ArrayBuffer | ArrayBufferView)[], - content: { comm_id: string; data: JSONObject; target_name: string | undefined }, - // tslint:disable-next-line: no-any - metadata: any, - // tslint:disable-next-line: no-any - msgId: any - ): Kernel.IShellFuture< - KernelMessage.IShellMessage<'comm_msg'>, - KernelMessage.IShellMessage - >; - requestCommInfo(content: KernelMessage.ICommInfoRequestMsg['content']): Promise; - registerMessageHook( - msgId: string, - hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike - ): void; - removeMessageHook(msgId: string, hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike): void; - requestKernelInfo(): Promise; -} - -export type ISessionWithSocket = Session.ISession & { - // Whether this is a remote session that we attached to. - isRemoteSession?: boolean; - // Socket information used for hooking messages to the kernel - kernelSocketInformation?: KernelSocketInformation; -}; - -export const IJupyterSessionManagerFactory = Symbol('IJupyterSessionManagerFactory'); -export interface IJupyterSessionManagerFactory { - readonly onRestartSessionCreated: Event; - readonly onRestartSessionUsed: Event; - create(connInfo: IJupyterConnection, failOnPassword?: boolean): Promise; -} - -export interface IJupyterSessionManager extends IAsyncDisposable { - readonly onRestartSessionCreated: Event; - readonly onRestartSessionUsed: Event; - startNew( - kernelConnection: KernelConnectionMetadata | undefined, - workingDirectory: string, - cancelToken?: CancellationToken - ): Promise; - getKernelSpecs(): Promise; - getConnInfo(): IJupyterConnection; - getRunningKernels(): Promise; - getRunningSessions(): Promise; -} - -export interface IJupyterKernel { - /** - * Id of an existing (active) Kernel from an active session. - * - * @type {string} - * @memberof IJupyterKernel - */ - id?: string; - name: string; - lastActivityTime: Date; - numberOfConnections: number; -} - -export interface IJupyterKernelSpec { - /** - * Id of an existing (active) Kernel from an active session. - * - * @type {string} - * @memberof IJupyterKernel - */ - id?: string; - name: string; - language: string; - path: string; - env: NodeJS.ProcessEnv | undefined; - /** - * Kernel display name. - * - * @type {string} - * @memberof IJupyterKernelSpec - */ - readonly display_name: string; - /** - * A dictionary of additional attributes about this kernel; used by clients to aid in kernel selection. - * Optionally storing the interpreter information in the metadata (helping extension search for kernels that match an interpereter). - */ - // tslint:disable-next-line: no-any - readonly metadata?: Record & { interpreter?: Partial }; - readonly argv: string[]; -} - -export const INotebookImporter = Symbol('INotebookImporter'); -export interface INotebookImporter extends Disposable { - importFromFile(contentsFile: Uri): Promise; -} - -export const INotebookExporter = Symbol('INotebookExporter'); -export interface INotebookExporter extends Disposable { - translateToNotebook( - cells: ICell[], - directoryChange?: string, - kernelSpec?: nbformat.IKernelspecMetadata - ): Promise; - exportToFile(cells: ICell[], file: string, showOpenPrompt?: boolean): Promise; -} - -export const IInteractiveWindowProvider = Symbol('IInteractiveWindowProvider'); -export interface IInteractiveWindowProvider { - /** - * The active interactive window if it has the focus. - */ - readonly activeWindow: IInteractiveWindow | undefined; - /** - * List of open interactive windows - */ - readonly windows: ReadonlyArray; - /** - * Event fired when the active interactive window changes - */ - readonly onDidChangeActiveInteractiveWindow: Event; - /** - * Gets or creates a new interactive window and associates it with the owner. If no owner, marks as a non associated. - * @param owner file that started this interactive window - */ - getOrCreate(owner: Resource): Promise; - /** - * Synchronizes with the other peers in a live share connection to make sure it has the same window open - * @param window window on this side - */ - synchronize(window: IInteractiveWindow): Promise; -} - -export const IDataScienceErrorHandler = Symbol('IDataScienceErrorHandler'); -export interface IDataScienceErrorHandler { - handleError(err: Error): Promise; -} - -/** - * Given a local resource this will convert the Uri into a form such that it can be used in a WebView. - */ -export interface ILocalResourceUriConverter { - /** - * Root folder that scripts should be copied to. - */ - readonly rootScriptFolder: Uri; - /** - * Convert a uri for the local file system to one that can be used inside webviews. - * - * Webviews cannot directly load resources from the workspace or local file system using `file:` uris. The - * `asWebviewUri` function takes a local `file:` uri and converts it into a uri that can be used inside of - * a webview to load the same resource: - * - * ```ts - * webview.html = `` - * ``` - */ - asWebviewUri(localResource: Uri): Promise; -} - -export interface IInteractiveBase extends Disposable { - onExecutedCode: Event; - notebook?: INotebook; - startProgress(): void; - stopProgress(): void; - undoCells(): void; - redoCells(): void; - removeAllCells(): void; - interruptKernel(): Promise; - restartKernel(): Promise; - hasCell(id: string): Promise; -} - -export const IInteractiveWindow = Symbol('IInteractiveWindow'); -export interface IInteractiveWindow extends IInteractiveBase { - readonly onDidChangeViewState: Event; - readonly visible: boolean; - readonly active: boolean; - readonly owner: Resource; - readonly submitters: Uri[]; - readonly identity: Uri; - readonly title: string; - closed: Event; - addCode(code: string, file: Uri, line: number, editor?: TextEditor, runningStopWatch?: StopWatch): Promise; - addMessage(message: string): Promise; - debugCode( - code: string, - file: Uri, - line: number, - editor?: TextEditor, - runningStopWatch?: StopWatch - ): Promise; - expandAllCells(): void; - collapseAllCells(): void; - exportCells(): void; - scrollToCell(id: string): void; -} - -export interface IInteractiveWindowLoadable extends IInteractiveWindow { - changeMode(newMode: InteractiveWindowMode): void; -} - -// For native editing, the provider acts like the IDocumentManager for normal docs -export const INotebookEditorProvider = Symbol('INotebookEditorProvider'); -export interface INotebookEditorProvider { - readonly activeEditor: INotebookEditor | undefined; - readonly editors: INotebookEditor[]; - readonly onDidOpenNotebookEditor: Event; - readonly onDidChangeActiveNotebookEditor: Event; - readonly onDidCloseNotebookEditor: Event; - open(file: Uri): Promise; - show(file: Uri): Promise; - createNew(contents?: string, title?: string): Promise; -} - -// For native editing, the INotebookEditor acts like a TextEditor and a TextDocument together -export const INotebookEditor = Symbol('INotebookEditor'); -export interface INotebookEditor extends Disposable { - /** - * Type of editor, whether it is the old, custom or native notebook editor. - * Once VSC Notebook is stable, this property can be removed. - */ - readonly type: 'old' | 'custom' | 'native'; - readonly onDidChangeViewState: Event; - readonly closed: Event; - readonly executed: Event; - readonly modified: Event; - readonly saved: Event; - readonly notebookExtensibility: INotebookExtensibility; - /** - * Is this notebook representing an untitled file which has never been saved yet. - */ - readonly isUntitled: boolean; - /** - * `true` if there are unpersisted changes. - */ - readonly isDirty: boolean; - readonly file: Uri; - readonly visible: boolean; - readonly active: boolean; - readonly model: INotebookModel; - onExecutedCode: Event; - notebook?: INotebook; - show(): Promise; - runAllCells(): void; - runSelectedCell(): void; - addCellBelow(): void; - undoCells(): void; - redoCells(): void; - removeAllCells(): void; - expandAllCells(): void; - collapseAllCells(): void; - interruptKernel(): Promise; - restartKernel(): Promise; -} - -export const INotebookExtensibility = Symbol('INotebookExtensibility'); - -export interface INotebookExtensibility { - readonly onKernelPostExecute: Event; - readonly onKernelRestart: Event; - fireKernelRestart(): void; - fireKernelPostExecute(cell: NotebookCell): void; -} - -export const IInteractiveWindowListener = Symbol('IInteractiveWindowListener'); - -/** - * Listens to history messages to provide extra functionality - */ -export interface IInteractiveWindowListener extends IDisposable { - /** - * Fires this event when posting a response message - */ - // tslint:disable-next-line: no-any - postMessage: Event<{ message: string; payload: any }>; - /** - * Fires this event when posting a message to the interactive base. - */ - // tslint:disable-next-line: no-any - postInternalMessage?: Event<{ message: string; payload: any }>; - /** - * Handles messages that the interactive window receives - * @param message message type - * @param payload message payload - */ - // tslint:disable-next-line: no-any - onMessage(message: string, payload?: any): void; - /** - * Fired when the view state of the interactive window changes - * @param args - */ - onViewStateChanged?(args: WebViewViewChangeEventArgs): void; -} - -// Wraps the vscode API in order to send messages back and forth from a webview -export const IPostOffice = Symbol('IPostOffice'); -export interface IPostOffice { - // tslint:disable-next-line:no-any - post(message: string, params: any[] | undefined): void; - // tslint:disable-next-line:no-any - listen(message: string, listener: (args: any[] | undefined) => void): void; -} - -// Wraps the vscode CodeLensProvider base class -export const IDataScienceCodeLensProvider = Symbol('IDataScienceCodeLensProvider'); -export interface IDataScienceCodeLensProvider extends CodeLensProvider { - getCodeWatcher(document: TextDocument): ICodeWatcher | undefined; -} - -// Wraps the Code Watcher API -export const ICodeWatcher = Symbol('ICodeWatcher'); -export interface ICodeWatcher { - readonly uri: Uri | undefined; - codeLensUpdated: Event; - setDocument(document: TextDocument): void; - getVersion(): number; - getCodeLenses(): CodeLens[]; - getCachedSettings(): IDataScienceSettings | undefined; - runAllCells(): Promise; - runCell(range: Range): Promise; - debugCell(range: Range): Promise; - runCurrentCell(): Promise; - runCurrentCellAndAdvance(): Promise; - runSelectionOrLine(activeEditor: TextEditor | undefined): Promise; - runToLine(targetLine: number): Promise; - runFromLine(targetLine: number): Promise; - runAllCellsAbove(stopLine: number, stopCharacter: number): Promise; - runCellAndAllBelow(startLine: number, startCharacter: number): Promise; - runFileInteractive(): Promise; - debugFileInteractive(): Promise; - addEmptyCellToBottom(): Promise; - runCurrentCellAndAddBelow(): Promise; - insertCellBelowPosition(): void; - insertCellBelow(): void; - insertCellAbove(): void; - deleteCells(): void; - selectCell(): void; - selectCellContents(): void; - extendSelectionByCellAbove(): void; - extendSelectionByCellBelow(): void; - moveCellsUp(): Promise; - moveCellsDown(): Promise; - changeCellToMarkdown(): void; - changeCellToCode(): void; - debugCurrentCell(): Promise; - gotoNextCell(): void; - gotoPreviousCell(): void; -} - -export const ICodeLensFactory = Symbol('ICodeLensFactory'); -export interface ICodeLensFactory { - updateRequired: Event; - createCodeLenses(document: TextDocument): CodeLens[]; - getCellRanges(document: TextDocument): ICellRange[]; -} - -export enum CellState { - editing = -1, - init = 0, - executing = 1, - finished = 2, - error = 3 -} - -// Basic structure for a cell from a notebook -export interface ICell { - id: string; // This value isn't unique. File and line are needed too. - file: string; - line: number; - state: CellState; - data: nbformat.ICodeCell | nbformat.IRawCell | nbformat.IMarkdownCell | IMessageCell; - extraLines?: number[]; -} - -// CellRange is used as the basis for creating new ICells. -// Was only intended to aggregate together ranges to create an ICell -// However the "range" aspect is useful when working with plain text document -// Ultimately, it would probably be ideal to be ICell and change line to range. -// Specificially see how this is being used for the ICodeLensFactory to -// provide cells for the CodeWatcher to use. -export interface ICellRange { - range: Range; - title: string; - cell_type: string; -} - -export interface IInteractiveWindowInfo { - cellCount: number; - undoCount: number; - redoCount: number; - selectedCell: string | undefined; -} - -export interface IMessageCell extends nbformat.IBaseCell { - cell_type: 'messages'; - messages: string[]; -} - -export const ICodeCssGenerator = Symbol('ICodeCssGenerator'); -export interface ICodeCssGenerator { - generateThemeCss(resource: Resource, isDark: boolean, theme: string): Promise; - generateMonacoTheme(resource: Resource, isDark: boolean, theme: string): Promise; -} - -export const IThemeFinder = Symbol('IThemeFinder'); -export interface IThemeFinder { - findThemeRootJson(themeName: string): Promise; - findTmLanguage(language: string): Promise; - findLanguageConfiguration(language: string): Promise; - isThemeDark(themeName: string): Promise; -} - -export const IStatusProvider = Symbol('IStatusProvider'); -export interface IStatusProvider { - // call this function to set the new status on the active - // interactive window. Dispose of the returned object when done. - set( - message: string, - showInWebView: boolean, - timeout?: number, - canceled?: () => void, - interactivePanel?: IInteractiveBase - ): Disposable; - - // call this function to wait for a promise while displaying status - waitWithStatus( - promise: () => Promise, - message: string, - showInWebView: boolean, - timeout?: number, - canceled?: () => void, - interactivePanel?: IInteractiveBase - ): Promise; -} - -export interface IJupyterCommand { - interpreter(): Promise; - execObservable(args: string[], options: SpawnOptions): Promise>; - exec(args: string[], options: SpawnOptions): Promise>; -} - -export const IJupyterCommandFactory = Symbol('IJupyterCommandFactory'); -export interface IJupyterCommandFactory { - createInterpreterCommand( - command: JupyterCommands, - moduleName: string, - args: string[], - interpreter: PythonEnvironment, - isActiveInterpreter: boolean - ): IJupyterCommand; - createProcessCommand(exe: string, args: string[]): IJupyterCommand; -} - -// Config settings we pass to our react code -export type FileSettings = { - autoSaveDelay: number; - autoSave: 'afterDelay' | 'off' | 'onFocusChange' | 'onWindowChange'; -}; - -export interface IDataScienceExtraSettings extends IDataScienceSettings { - extraSettings: { - editor: { - cursor: string; - cursorBlink: string; - fontLigatures: boolean; - autoClosingBrackets: string; - autoClosingQuotes: string; - autoSurround: string; - autoIndent: boolean; - scrollBeyondLastLine: boolean; - horizontalScrollbarSize: number; - verticalScrollbarSize: number; - fontSize: number; - fontFamily: string; - }; - theme: string; - useCustomEditorApi: boolean; - }; - intellisenseOptions: { - quickSuggestions: { - other: boolean; - comments: boolean; - strings: boolean; - }; - acceptSuggestionOnEnter: boolean | 'on' | 'smart' | 'off'; - quickSuggestionsDelay: number; - suggestOnTriggerCharacters: boolean; - tabCompletion: boolean | 'on' | 'off' | 'onlySnippets'; - suggestLocalityBonus: boolean; - suggestSelection: 'first' | 'recentlyUsed' | 'recentlyUsedByPrefix'; - wordBasedSuggestions: boolean; - parameterHintsEnabled: boolean; - }; - variableOptions: { - enableDuringDebugger: boolean; - }; - - gatherIsInstalled: boolean; -} - -// Get variables from the currently running active Jupyter server -// Note: This definition is used implicitly by getJupyterVariableValue.py file -// Changes here may need to be reflected there as well -export interface IJupyterVariable { - name: string; - value: string | undefined; - executionCount?: number; - supportsDataExplorer: boolean; - type: string; - size: number; - shape: string; - count: number; - truncated: boolean; - columns?: { key: string; type: string }[]; - rowCount?: number; - indexColumn?: string; -} - -export const IJupyterVariableDataProvider = Symbol('IJupyterVariableDataProvider'); -export interface IJupyterVariableDataProvider extends IDataViewerDataProvider { - setDependencies(variable: IJupyterVariable, notebook: INotebook): void; -} - -export const IJupyterVariableDataProviderFactory = Symbol('IJupyterVariableDataProviderFactory'); -export interface IJupyterVariableDataProviderFactory { - create(variable: IJupyterVariable, notebook: INotebook): Promise; -} - -export const IJupyterVariables = Symbol('IJupyterVariables'); -export interface IJupyterVariables { - readonly refreshRequired: Event; - getVariables(notebook: INotebook, request: IJupyterVariablesRequest): Promise; - getDataFrameInfo(targetVariable: IJupyterVariable, notebook: INotebook): Promise; - getDataFrameRows( - targetVariable: IJupyterVariable, - notebook: INotebook, - start: number, - end: number - ): Promise; - getMatchingVariable( - notebook: INotebook, - name: string, - cancelToken?: CancellationToken - ): Promise; -} - -export interface IConditionalJupyterVariables extends IJupyterVariables { - readonly active: boolean; -} - -// Request for variables -export interface IJupyterVariablesRequest { - executionCount: number; - refreshCount: number; - sortColumn: string; - sortAscending: boolean; - startIndex: number; - pageSize: number; -} - -// Response to a request -export interface IJupyterVariablesResponse { - executionCount: number; - totalCount: number; - pageStartIndex: number; - pageResponse: IJupyterVariable[]; - refreshCount: number; -} - -export const IPlotViewerProvider = Symbol('IPlotViewerProvider'); -export interface IPlotViewerProvider { - showPlot(imageHtml: string): Promise; -} -export const IPlotViewer = Symbol('IPlotViewer'); - -export interface IPlotViewer extends IDisposable { - closed: Event; - removed: Event; - addPlot(imageHtml: string): Promise; - show(): Promise; -} - -export interface ISourceMapMapping { - line: number; - endLine: number; - runtimeSource: { path: string }; - runtimeLine: number; -} - -export interface ISourceMapRequest { - source: { path: string }; - pydevdSourceMaps: ISourceMapMapping[]; -} - -export interface ICellHash { - line: number; // 1 based - endLine: number; // 1 based and inclusive - runtimeLine: number; // Line in the jupyter source to start at - hash: string; - executionCount: number; - id: string; // Cell id as sent to jupyter - timestamp: number; -} - -export interface IFileHashes { - file: string; - hashes: ICellHash[]; -} - -export const ICellHashListener = Symbol('ICellHashListener'); -export interface ICellHashListener { - hashesUpdated(hashes: IFileHashes[]): Promise; -} - -export const ICellHashProvider = Symbol('ICellHashProvider'); -export interface ICellHashProvider { - updated: Event; - getHashes(): IFileHashes[]; - getExecutionCount(): number; - incExecutionCount(): void; - generateHashFileName(cell: ICell, executionCount: number): string; -} - -export interface IDebugLocation { - fileName: string; - lineNumber: number; - column: number; -} - -export const IDebugLocationTracker = Symbol('IDebugLocationTracker'); -export interface IDebugLocationTracker { - updated: Event; - getLocation(debugSession: DebugSession): IDebugLocation | undefined; -} - -export const IJupyterSubCommandExecutionService = Symbol('IJupyterSubCommandExecutionService'); -/** - * Responsible for execution of jupyter subcommands such as `notebook`, `nbconvert`, etc. - * The executed code is as follows `python -m jupyter `. - * - * @export - * @interface IJupyterSubCommandExecutionService - */ -export interface IJupyterSubCommandExecutionService { - /** - * Checks whether notebook is supported. - * - * @param {CancellationToken} [cancelToken] - * @returns {Promise} - * @memberof IJupyterSubCommandExecutionService - */ - isNotebookSupported(cancelToken?: CancellationToken): Promise; - /** - * If exporting is supported return the version of nbconvert available - * otherwise undefined. - */ - getExportPackageVersion(cancelToken?: CancellationToken): Promise; - /** - * Error message indicating why jupyter notebook isn't supported. - * - * @returns {Promise} - * @memberof IJupyterSubCommandExecutionService - */ - getReasonForJupyterNotebookNotBeingSupported(): Promise; - /** - * Used to refresh the command finder. - * - * @returns {Promise} - * @memberof IJupyterSubCommandExecutionService - */ - refreshCommands(): Promise; - /** - * Gets the interpreter to be used for starting of jupyter server. - * - * @param {CancellationToken} [token] - * @returns {(Promise)} - * @memberof IJupyterInterpreterService - */ - getSelectedInterpreter(token?: CancellationToken): Promise; - /** - * Starts the jupyter notebook server - * - * @param {string[]} notebookArgs - * @param {SpawnOptions} options - * @returns {Promise>} - * @memberof IJupyterSubCommandExecutionService - */ - startNotebook(notebookArgs: string[], options: SpawnOptions): Promise>; - /** - * Gets a list of all locally running jupyter notebook servers. - * - * @param {CancellationToken} [token] - * @returns {(Promise)} - * @memberof IJupyterSubCommandExecutionService - */ - getRunningJupyterServers(token?: CancellationToken): Promise; - /** - * Exports a given notebook into a python file. - * - * @param {string} file - * @param {string} [template] - * @param {CancellationToken} [token] - * @returns {Promise} - * @memberof IJupyterSubCommandExecutionService - */ - exportNotebookToPython(file: Uri, template?: string, token?: CancellationToken): Promise; - /** - * Opens an ipynb file in a new instance of a jupyter notebook server. - * - * @param {string} notebookFile - * @returns {Promise} - * @memberof IJupyterSubCommandExecutionService - */ - openNotebook(notebookFile: string): Promise; - /** - * Gets the kernelspecs. - * - * @param {CancellationToken} [token] - * @returns {Promise} - * @memberof IJupyterSubCommandExecutionService - */ - getKernelSpecs(token?: CancellationToken): Promise; -} - -export const IJupyterInterpreterDependencyManager = Symbol('IJupyterInterpreterDependencyManager'); -export interface IJupyterInterpreterDependencyManager { - /** - * Installs the dependencies required to launch jupyter. - * - * @param {JupyterInstallError} [err] - * @returns {Promise} - * @memberof IJupyterInterpreterDependencyManager - */ - installMissingDependencies(err?: JupyterInstallError): Promise; -} - -export interface INotebookModel { - readonly indentAmount: string; - readonly onDidDispose: Event; - readonly file: Uri; - readonly isDirty: boolean; - readonly isUntitled: boolean; - readonly changed: Event; - readonly cells: readonly Readonly[]; - readonly onDidEdit: Event; - readonly isDisposed: boolean; - readonly metadata: INotebookMetadataLive | undefined; - readonly isTrusted: boolean; - getContent(): string; - update(change: NotebookModelChange): void; - /** - * Dispose of the Notebook model. - * - * This is invoked when there are no more references to a given `NotebookModel` (for example when - * all editors associated with the document have been closed.) - */ - dispose(): void; - /** - * Trusts a notebook document. - */ - trust(): void; -} - -export interface IModelLoadOptions { - isNative?: boolean; - file: Uri; - possibleContents?: string; - backupId?: string; - skipLoadingDirtyContents?: boolean; -} - -export const INotebookStorage = Symbol('INotebookStorage'); - -export interface INotebookStorage { - generateBackupId(model: INotebookModel): string; - save(model: INotebookModel, cancellation: CancellationToken): Promise; - saveAs(model: INotebookModel, targetResource: Uri): Promise; - backup(model: INotebookModel, cancellation: CancellationToken, backupId?: string): Promise; - get(file: Uri): INotebookModel | undefined; - getOrCreateModel(options: IModelLoadOptions): Promise; - revert(model: INotebookModel, cancellation: CancellationToken): Promise; - deleteBackup(model: INotebookModel, backupId?: string): Promise; -} -type WebViewViewState = { - readonly visible: boolean; - readonly active: boolean; -}; -export type WebViewViewChangeEventArgs = { current: WebViewViewState; previous: WebViewViewState }; - -export type GetServerOptions = { - getOnly?: boolean; - disableUI?: boolean; - localOnly?: boolean; - token?: CancellationToken; - onConnectionMade?(): void; // Optional callback for when the first connection is made -}; - -/** - * Options for getting a notebook - */ -export type GetNotebookOptions = { - resource?: Uri; - identity: Uri; - getOnly?: boolean; - disableUI?: boolean; - metadata?: nbformat.INotebookMetadata & { id?: string }; - token?: CancellationToken; -}; - -export const INotebookProvider = Symbol('INotebookProvider'); -export interface INotebookProvider { - readonly type: 'raw' | 'jupyter'; - /** - * Fired when a notebook has been created for a given Uri/Identity - */ - onNotebookCreated: Event<{ identity: Uri; notebook: INotebook }>; - onSessionStatusChanged: Event<{ status: ServerStatus; notebook: INotebook }>; - - /** - * Fired just the first time that this provider connects - */ - onConnectionMade: Event; - /** - * Fired when a kernel would have been changed if a notebook had existed. - */ - onPotentialKernelChanged: Event<{ identity: Uri; kernelConnection: KernelConnectionMetadata }>; - - /** - * List of all notebooks (active and ones that are being constructed). - */ - activeNotebooks: Promise[]; - /** - * Disposes notebook associated with the given identity. - * Using `getOrCreateNotebook` would be incorrect as thats async, and its possible a document has been opened in the interim (meaning we could end up disposing something that is required). - */ - disposeAssociatedNotebook(options: { identity: Uri }): void; - /** - * Gets or creates a notebook, and manages the lifetime of notebooks. - */ - getOrCreateNotebook(options: GetNotebookOptions): Promise; - /** - * Connect to a notebook provider to prepare its connection and to get connection information - */ - connect(options: ConnectNotebookProviderOptions): Promise; - - /** - * Disconnect from a notebook provider connection - */ - disconnect(options: ConnectNotebookProviderOptions, cancelToken?: CancellationToken): Promise; - /** - * Fires the potentialKernelChanged event for a notebook that doesn't exist. - * @param identity identity notebook would have - * @param kernel kernel that it was changed to. - */ - firePotentialKernelChanged(identity: Uri, kernel: KernelConnectionMetadata): void; -} - -export const IJupyterServerProvider = Symbol('IJupyterServerProvider'); -export interface IJupyterServerProvider { - /** - * Gets the server used for starting notebooks - */ - getOrCreateServer(options: GetServerOptions): Promise; -} - -export interface IKernelSocket { - // tslint:disable-next-line: no-any - sendToRealKernel(data: any, cb?: (err?: Error) => void): void; - /** - * Adds a listener to a socket that will be called before the socket's onMessage is called. This - * allows waiting for a callback before processing messages - * @param listener - */ - addReceiveHook(hook: (data: WebSocketData) => Promise): void; - /** - * Removes a listener for the socket. When no listeners are present, the socket no longer blocks - * @param listener - */ - removeReceiveHook(hook: (data: WebSocketData) => Promise): void; - /** - * Adds a hook to the sending of data from a websocket. Hooks can block sending so be careful. - * @param patch - */ - // tslint:disable-next-line: no-any - addSendHook(hook: (data: any, cb?: (err?: Error) => void) => Promise): void; - /** - * Removes a send hook from the socket. - * @param hook - */ - // tslint:disable-next-line: no-any - removeSendHook(hook: (data: any, cb?: (err?: Error) => void) => Promise): void; -} - -export type KernelSocketOptions = { - /** - * Kernel Id. - */ - readonly id: string; - /** - * Kernel ClientId. - */ - readonly clientId: string; - /** - * Kernel UserName. - */ - readonly userName: string; - /** - * Kernel model. - */ - readonly model: { - /** - * Unique identifier of the kernel server session. - */ - readonly id: string; - /** - * The name of the kernel. - */ - readonly name: string; - }; -}; -export type KernelSocketInformation = { - /** - * Underlying socket used by jupyterlab/services to communicate with kernel. - * See jupyterlab/services/kernel/default.ts - */ - readonly socket?: IKernelSocket; - /** - * Options used to clone a kernel. - */ - readonly options: KernelSocketOptions; -}; - -export enum KernelInterpreterDependencyResponse { - ok, - cancel -} - -export const IKernelDependencyService = Symbol('IKernelDependencyService'); -export interface IKernelDependencyService { - installMissingDependencies( - interpreter: PythonEnvironment, - token?: CancellationToken - ): Promise; - areDependenciesInstalled(interpreter: PythonEnvironment, _token?: CancellationToken): Promise; -} - -export const INotebookAndInteractiveWindowUsageTracker = Symbol('INotebookAndInteractiveWindowUsageTracker'); -export interface INotebookAndInteractiveWindowUsageTracker { - readonly lastNotebookOpened?: Date; - readonly lastInteractiveWindowOpened?: Date; - startTracking(): void; -} - -export const IJupyterDebugService = Symbol('IJupyterDebugService'); -export interface IJupyterDebugService extends IDebugService { - /** - * Event fired when a breakpoint is hit (debugger has stopped) - */ - readonly onBreakpointHit: Event; - /** - * Start debugging a notebook cell. - * @param nameOrConfiguration Either the name of a debug or compound configuration or a [DebugConfiguration](#DebugConfiguration) object. - * @return A thenable that resolves when debugging could be successfully started. - */ - startRunByLine(config: DebugConfiguration): Thenable; - /** - * Gets the current stack frame for the current thread - */ - getStack(): Promise; - /** - * Steps the current thread. Returns after the request is sent. Wait for onBreakpointHit or onDidTerminateDebugSession to determine when done. - */ - step(): Promise; - /** - * Runs the current thread. Will keep running until a breakpoint or end of session. - */ - continue(): Promise; - /** - * Force a request for variables. DebugAdapterTrackers can listen for the results. - */ - requestVariables(): Promise; - /** - * Stop debugging - */ - stop(): void; -} - -export interface IJupyterServerUri { - baseUrl: string; - token: string; - // tslint:disable-next-line: no-any - authorizationHeader: any; // JSON object for authorization header. - expiration?: Date; // Date/time when header expires and should be refreshed. - displayName: string; -} - -export type JupyterServerUriHandle = string; - -export interface IJupyterUriProvider { - readonly id: string; // Should be a unique string (like a guid) - getQuickPickEntryItems(): QuickPickItem[]; - handleQuickPick(item: QuickPickItem, backEnabled: boolean): Promise; - getServerUri(handle: JupyterServerUriHandle): Promise; -} - -export const IJupyterUriProviderRegistration = Symbol('IJupyterUriProviderRegistration'); - -export interface IJupyterUriProviderRegistration { - getProviders(): Promise>; - registerProvider(picker: IJupyterUriProvider): void; - getJupyterServerUri(id: string, handle: JupyterServerUriHandle): Promise; -} -export const IDigestStorage = Symbol('IDigestStorage'); -export interface IDigestStorage { - readonly key: Promise; - saveDigest(uri: Uri, digest: string): Promise; - containsDigest(uri: Uri, digest: string): Promise; -} - -export const ITrustService = Symbol('ITrustService'); -export interface ITrustService { - readonly onDidSetNotebookTrust: Event; - isNotebookTrusted(uri: Uri, notebookContents: string): Promise; - trustNotebook(uri: Uri, notebookContents: string): Promise; -} - -export const IDataScienceFileSystem = Symbol('IDataScienceFileSystem'); -export interface IDataScienceFileSystem { - // Local-only filesystem utilities - appendLocalFile(path: string, text: string): Promise; - areLocalPathsSame(path1: string, path2: string): boolean; - createLocalDirectory(path: string): Promise; - createLocalWriteStream(path: string): WriteStream; - copyLocal(source: string, destination: string): Promise; - createTemporaryLocalFile(fileExtension: string, mode?: number): Promise; - deleteLocalDirectory(dirname: string): Promise; - deleteLocalFile(path: string): Promise; - getDisplayName(path: string): string; - getFileHash(path: string): Promise; - localDirectoryExists(dirname: string): Promise; - localFileExists(filename: string): Promise; - readLocalData(path: string): Promise; - readLocalFile(path: string): Promise; - searchLocal(globPattern: string, cwd?: string, dot?: boolean): Promise; - writeLocalFile(path: string, text: string | Buffer): Promise; - - // URI-based filesystem utilities wrapping the VS Code filesystem API - arePathsSame(path1: Uri, path2: Uri): boolean; - copy(source: Uri, destination: Uri): Promise; - createDirectory(uri: Uri): Promise; - delete(uri: Uri): Promise; - readFile(uri: Uri): Promise; - stat(uri: Uri): Promise; - writeFile(uri: Uri, text: string | Buffer): Promise; -} -export interface ISwitchKernelOptions { - identity: Resource; - resource: Resource; - currentKernelDisplayName: string | undefined; -} diff --git a/src/client/datascience/utils.ts b/src/client/datascience/utils.ts deleted file mode 100644 index f91b905b9f02..000000000000 --- a/src/client/datascience/utils.ts +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import * as path from 'path'; - -import { IWorkspaceService } from '../common/application/types'; - -import { IConfigurationService } from '../common/types'; -import { IDataScienceFileSystem } from './types'; - -export async function calculateWorkingDirectory( - configService: IConfigurationService, - workspace: IWorkspaceService, - fs: IDataScienceFileSystem -): Promise { - let workingDir: string | undefined; - // For a local launch calculate the working directory that we should switch into - const settings = configService.getSettings(undefined); - const fileRoot = settings.datascience.notebookFileRoot; - - // If we don't have a workspace open the notebookFileRoot seems to often have a random location in it (we use ${workspaceRoot} as default) - // so only do this setting if we actually have a valid workspace open - if (fileRoot && workspace.hasWorkspaceFolders) { - const workspaceFolderPath = workspace.workspaceFolders![0].uri.fsPath; - if (path.isAbsolute(fileRoot)) { - if (await fs.localDirectoryExists(fileRoot)) { - // User setting is absolute and exists, use it - workingDir = fileRoot; - } else { - // User setting is absolute and doesn't exist, use workspace - workingDir = workspaceFolderPath; - } - } else if (!fileRoot.includes('${')) { - // fileRoot is a relative path, combine it with the workspace folder - const combinedPath = path.join(workspaceFolderPath, fileRoot); - if (await fs.localDirectoryExists(combinedPath)) { - // combined path exists, use it - workingDir = combinedPath; - } else { - // Combined path doesn't exist, use workspace - workingDir = workspaceFolderPath; - } - } else { - // fileRoot is a variable that hasn't been expanded - workingDir = fileRoot; - } - } - return workingDir; -} diff --git a/src/client/datascience/webviews/webviewHost.ts b/src/client/datascience/webviews/webviewHost.ts deleted file mode 100644 index 8c8a55424fdf..000000000000 --- a/src/client/datascience/webviews/webviewHost.ts +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import '../../common/extensions'; - -import { injectable, unmanaged } from 'inversify'; -import { ConfigurationChangeEvent, extensions, Uri, WorkspaceConfiguration } from 'vscode'; - -import { IWebview, IWorkspaceService } from '../../common/application/types'; -import { isTestExecution } from '../../common/constants'; -import { IConfigurationService, IDisposable, Resource } from '../../common/types'; -import { createDeferred, Deferred } from '../../common/utils/async'; -import * as localize from '../../common/utils/localize'; -import { captureTelemetry } from '../../telemetry'; -import { DefaultTheme, GatherExtension, Telemetry } from '../constants'; -import { CssMessages, IGetCssRequest, IGetMonacoThemeRequest, SharedMessages } from '../messages'; -import { ICodeCssGenerator, IDataScienceExtraSettings, IThemeFinder } from '../types'; - -@injectable() // For some reason this is necessary to get the class hierarchy to work. -export abstract class WebviewHost implements IDisposable { - protected webview?: IWebview; - - protected disposed = false; - - protected themeIsDarkPromise: Deferred | undefined = createDeferred(); - - protected webviewInit: Deferred | undefined = createDeferred(); - - protected readonly _disposables: IDisposable[] = []; - - constructor( - @unmanaged() protected configService: IConfigurationService, - @unmanaged() private cssGenerator: ICodeCssGenerator, - @unmanaged() protected themeFinder: IThemeFinder, - @unmanaged() protected workspaceService: IWorkspaceService, - @unmanaged() protected readonly useCustomEditorApi: boolean, - @unmanaged() private readonly enableVariablesDuringDebugging: boolean - ) { - // Listen for settings changes from vscode. - this._disposables.push(this.workspaceService.onDidChangeConfiguration(this.onPossibleSettingsChange, this)); - - // Listen for settings changes - this._disposables.push( - this.configService.getSettings(undefined).onDidChange(this.onDataScienceSettingsChanged.bind(this)) - ); - } - - public dispose() { - if (!this.disposed) { - this.disposed = true; - this.themeIsDarkPromise = undefined; - this._disposables.forEach((item) => item.dispose()); - } - - this.webviewInit = undefined; - } - - public setTheme(isDark: boolean) { - if (this.themeIsDarkPromise && !this.themeIsDarkPromise.resolved) { - this.themeIsDarkPromise.resolve(isDark); - } else { - this.themeIsDarkPromise = createDeferred(); - this.themeIsDarkPromise.resolve(isDark); - } - } - - // Post a message to our webview and update our new datascience settings - protected onDataScienceSettingsChanged = async () => { - // Stringify our settings to send over to the panel - const dsSettings = JSON.stringify(await this.generateDataScienceExtraSettings()); - this.postMessageInternal(SharedMessages.UpdateSettings, dsSettings).ignoreErrors(); - }; - - protected asWebviewUri(localResource: Uri) { - if (!this.webview) { - throw new Error('asWebViewUri called too early'); - } - return this.webview?.asWebviewUri(localResource); - } - - protected abstract get owningResource(): Resource; - - protected postMessage(type: T, payload?: M[T]): Promise { - // Then send it the message - return this.postMessageInternal(type.toString(), payload); - } - - // tslint:disable-next-line:no-any - protected onMessage(message: string, payload: any) { - switch (message) { - case CssMessages.GetCssRequest: - this.handleCssRequest(payload as IGetCssRequest).ignoreErrors(); - break; - - case CssMessages.GetMonacoThemeRequest: - this.handleMonacoThemeRequest(payload as IGetMonacoThemeRequest).ignoreErrors(); - break; - - default: - break; - } - } - - protected async generateDataScienceExtraSettings(): Promise { - const resource = this.owningResource; - const editor = this.workspaceService.getConfiguration('editor'); - const workbench = this.workspaceService.getConfiguration('workbench'); - const theme = !workbench ? DefaultTheme : workbench.get('colorTheme', DefaultTheme); - const ext = extensions.getExtension(GatherExtension); - - return { - ...this.configService.getSettings(resource).datascience, - extraSettings: { - editor: { - cursor: this.getValue(editor, 'cursorStyle', 'line'), - cursorBlink: this.getValue(editor, 'cursorBlinking', 'blink'), - autoClosingBrackets: this.getValue(editor, 'autoClosingBrackets', 'languageDefined'), - autoClosingQuotes: this.getValue(editor, 'autoClosingQuotes', 'languageDefined'), - autoSurround: this.getValue(editor, 'autoSurround', 'languageDefined'), - autoIndent: this.getValue(editor, 'autoIndent', false), - fontLigatures: this.getValue(editor, 'fontLigatures', false), - scrollBeyondLastLine: this.getValue(editor, 'scrollBeyondLastLine', true), - // VS Code puts a value for this, but it's 10 (the explorer bar size) not 14 the editor size for vert - verticalScrollbarSize: this.getValue(editor, 'scrollbar.verticalScrollbarSize', 14), - horizontalScrollbarSize: this.getValue(editor, 'scrollbar.horizontalScrollbarSize', 10), - fontSize: this.getValue(editor, 'fontSize', 14), - fontFamily: this.getValue(editor, 'fontFamily', "Consolas, 'Courier New', monospace") - }, - theme, - useCustomEditorApi: this.useCustomEditorApi - }, - intellisenseOptions: { - quickSuggestions: { - other: this.getValue(editor, 'quickSuggestions.other', true), - comments: this.getValue(editor, 'quickSuggestions.comments', false), - strings: this.getValue(editor, 'quickSuggestions.strings', false) - }, - acceptSuggestionOnEnter: this.getValue(editor, 'acceptSuggestionOnEnter', 'on'), - quickSuggestionsDelay: this.getValue(editor, 'quickSuggestionsDelay', 10), - suggestOnTriggerCharacters: this.getValue(editor, 'suggestOnTriggerCharacters', true), - tabCompletion: this.getValue(editor, 'tabCompletion', 'on'), - suggestLocalityBonus: this.getValue(editor, 'suggest.localityBonus', true), - suggestSelection: this.getValue(editor, 'suggestSelection', 'recentlyUsed'), - wordBasedSuggestions: this.getValue(editor, 'wordBasedSuggestions', true), - parameterHintsEnabled: this.getValue(editor, 'parameterHints.enabled', true) - }, - variableOptions: { - enableDuringDebugger: this.enableVariablesDuringDebugging - }, - gatherIsInstalled: !!ext - }; - } - - protected async sendLocStrings() { - const locStrings = isTestExecution() ? '{}' : localize.getCollectionJSON(); - this.postMessageInternal(SharedMessages.LocInit, locStrings).ignoreErrors(); - } - - // tslint:disable-next-line:no-any - protected async postMessageInternal(type: string, payload?: any): Promise { - if (this.webviewInit) { - // Make sure the webpanel is up before we send it anything. - await this.webviewInit.promise; - - // Then send it the message - this.webview?.postMessage({ type: type.toString(), payload }); - } - } - - protected isDark(): Promise { - return this.themeIsDarkPromise ? this.themeIsDarkPromise.promise : Promise.resolve(false); - } - - @captureTelemetry(Telemetry.WebviewStyleUpdate) - private async handleCssRequest(request: IGetCssRequest): Promise { - const settings = await this.generateDataScienceExtraSettings(); - const requestIsDark = settings.ignoreVscodeTheme ? false : request?.isDark; - this.setTheme(requestIsDark); - const isDark = settings.ignoreVscodeTheme - ? false - : await this.themeFinder.isThemeDark(settings.extraSettings.theme); - const resource = this.owningResource; - const css = await this.cssGenerator.generateThemeCss(resource, requestIsDark, settings.extraSettings.theme); - return this.postMessageInternal(CssMessages.GetCssResponse, { - css, - theme: settings.extraSettings.theme, - knownDark: isDark - }); - } - - @captureTelemetry(Telemetry.WebviewMonacoStyleUpdate) - private async handleMonacoThemeRequest(request: IGetMonacoThemeRequest): Promise { - const settings = await this.generateDataScienceExtraSettings(); - const isDark = settings.ignoreVscodeTheme ? false : request?.isDark; - this.setTheme(isDark); - const resource = this.owningResource; - const monacoTheme = await this.cssGenerator.generateMonacoTheme(resource, isDark, settings.extraSettings.theme); - return this.postMessageInternal(CssMessages.GetMonacoThemeResponse, { theme: monacoTheme }); - } - - private getValue(workspaceConfig: WorkspaceConfiguration, section: string, defaultValue: T): T { - if (workspaceConfig) { - return workspaceConfig.get(section, defaultValue); - } - return defaultValue; - } - - // Post a message to our webpanel and update our new datascience settings - private onPossibleSettingsChange = async (event: ConfigurationChangeEvent) => { - if ( - event.affectsConfiguration('workbench.colorTheme') || - event.affectsConfiguration('editor.fontSize') || - event.affectsConfiguration('editor.fontFamily') || - event.affectsConfiguration('editor.cursorStyle') || - event.affectsConfiguration('editor.cursorBlinking') || - event.affectsConfiguration('editor.autoClosingBrackets') || - event.affectsConfiguration('editor.autoClosingQuotes') || - event.affectsConfiguration('editor.autoSurround') || - event.affectsConfiguration('editor.autoIndent') || - event.affectsConfiguration('editor.scrollBeyondLastLine') || - event.affectsConfiguration('editor.fontLigatures') || - event.affectsConfiguration('editor.scrollbar.verticalScrollbarSize') || - event.affectsConfiguration('editor.scrollbar.horizontalScrollbarSize') || - event.affectsConfiguration('files.autoSave') || - event.affectsConfiguration('files.autoSaveDelay') || - event.affectsConfiguration('python.dataScience.widgetScriptSources') - ) { - // See if the theme changed - const newSettings = await this.generateDataScienceExtraSettings(); - if (newSettings) { - const dsSettings = JSON.stringify(newSettings); - this.postMessageInternal(SharedMessages.UpdateSettings, dsSettings).ignoreErrors(); - } - } - }; -} diff --git a/src/client/datascience/webviews/webviewPanelHost.ts b/src/client/datascience/webviews/webviewPanelHost.ts deleted file mode 100644 index d4dd09166191..000000000000 --- a/src/client/datascience/webviews/webviewPanelHost.ts +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import '../../common/extensions'; - -import { injectable, unmanaged } from 'inversify'; -import { Uri, ViewColumn, WebviewPanel } from 'vscode'; - -import { - IWebviewPanel, - IWebviewPanelMessageListener, - IWebviewPanelProvider, - IWorkspaceService -} from '../../common/application/types'; -import { traceInfo } from '../../common/logger'; -import { IConfigurationService, IDisposable } from '../../common/types'; -import { createDeferred } from '../../common/utils/async'; -import { noop } from '../../common/utils/misc'; -import { StopWatch } from '../../common/utils/stopWatch'; -import { sendTelemetryEvent } from '../../telemetry'; -import { Telemetry } from '../constants'; -import { SharedMessages } from '../messages'; -import { ICodeCssGenerator, IThemeFinder, WebViewViewChangeEventArgs } from '../types'; -import { WebviewHost } from './webviewHost'; - -@injectable() // For some reason this is necessary to get the class hierarchy to work. -export abstract class WebviewPanelHost extends WebviewHost implements IDisposable { - protected get isDisposed(): boolean { - return this.disposed; - } - - protected viewState: { visible: boolean; active: boolean } = { visible: false, active: false }; - - private webPanel: IWebviewPanel | undefined; - - private messageListener: IWebviewPanelMessageListener; - - private startupStopwatch = new StopWatch(); - - constructor( - @unmanaged() protected configService: IConfigurationService, - @unmanaged() private provider: IWebviewPanelProvider, - @unmanaged() cssGenerator: ICodeCssGenerator, - @unmanaged() protected themeFinder: IThemeFinder, - @unmanaged() protected workspaceService: IWorkspaceService, - @unmanaged() - messageListenerCtor: ( - callback: (message: string, payload: {}) => void, - viewChanged: (panel: IWebviewPanel) => void, - disposed: () => void - ) => IWebviewPanelMessageListener, - @unmanaged() private rootPath: string, - @unmanaged() private scripts: string[], - @unmanaged() private _title: string, - @unmanaged() private viewColumn: ViewColumn, - @unmanaged() protected readonly useCustomEditorApi: boolean, - @unmanaged() enableVariablesDuringDebugging: boolean - ) { - super( - configService, - cssGenerator, - themeFinder, - workspaceService, - useCustomEditorApi, - enableVariablesDuringDebugging - ); - - // Create our message listener for our web panel. - this.messageListener = messageListenerCtor( - this.onMessage.bind(this), - this.webPanelViewStateChanged.bind(this), - this.dispose.bind(this) - ); - } - - public async show(preserveFocus: boolean): Promise { - if (!this.isDisposed) { - // Then show our web panel. - if (this.webPanel) { - await this.webPanel.show(preserveFocus); - } - } - } - - public updateCwd(cwd: string): void { - if (this.webPanel) { - this.webPanel.updateCwd(cwd); - } - } - - public dispose() { - if (!this.isDisposed) { - this.disposed = true; - if (this.webPanel) { - this.webPanel.close(); - this.webPanel = undefined; - } - } - - super.dispose(); - } - - public get title() { - return this._title; - } - - public setTitle(newTitle: string) { - this._title = newTitle; - if (!this.isDisposed && this.webPanel) { - this.webPanel.setTitle(newTitle); - } - } - - // tslint:disable-next-line:no-any - protected onMessage(message: string, payload: any) { - switch (message) { - case SharedMessages.Started: - this.webPanelRendered(); - break; - - default: - // Forward unhandled messages to the base class - super.onMessage(message, payload); - break; - } - } - - protected shareMessage(type: T, payload?: M[T]) { - // Send our remote message. - this.messageListener.onMessage(type.toString(), payload); - } - - protected onViewStateChanged(_args: WebViewViewChangeEventArgs) { - noop(); - } - - protected async loadWebPanel(cwd: string, webViewPanel?: WebviewPanel) { - // Make not disposed anymore - this.disposed = false; - - // Setup our init promise for the web panel. We use this to make sure we're in sync with our - // react control. - this.webviewInit = this.webviewInit || createDeferred(); - - // Setup a promise that will wait until the webview passes back - // a message telling us what them is in use - this.themeIsDarkPromise = this.themeIsDarkPromise ? this.themeIsDarkPromise : createDeferred(); - - // Load our actual web panel - - traceInfo(`Loading web panel. Panel is ${this.webPanel ? 'set' : 'notset'}`); - - // Create our web panel (it's the UI that shows up for the history) - if (this.webPanel === undefined) { - // Get our settings to pass along to the react control - const settings = await this.generateDataScienceExtraSettings(); - - traceInfo('Loading web view...'); - - const workspaceFolder = this.workspaceService.getWorkspaceFolder(Uri.file(cwd))?.uri; - - // Use this script to create our web view panel. It should contain all of the necessary - // script to communicate with this class. - this.webPanel = await this.provider.create({ - viewColumn: this.viewColumn, - listener: this.messageListener, - title: this.title, - rootPath: this.rootPath, - scripts: this.scripts, - settings, - cwd, - webViewPanel, - additionalPaths: workspaceFolder ? [workspaceFolder.fsPath] : [] - }); - - // Set our webview after load - this.webview = this.webPanel; - - // Track to seee if our web panel fails to load - this._disposables.push(this.webPanel.loadFailed(this.onWebPanelLoadFailed, this)); - - traceInfo('Web view created.'); - } - - // Send the first settings message - this.onDataScienceSettingsChanged().ignoreErrors(); - - // Send the loc strings (skip during testing as it takes up a lot of memory) - this.sendLocStrings().ignoreErrors(); - } - - // If our webpanel fails to load then just dispose ourselves - private onWebPanelLoadFailed = async () => { - this.dispose(); - }; - - private webPanelViewStateChanged = (webPanel: IWebviewPanel) => { - const visible = webPanel.isVisible(); - const active = webPanel.isActive(); - const current = { visible, active }; - const previous = { visible: this.viewState.visible, active: this.viewState.active }; - this.viewState.visible = visible; - this.viewState.active = active; - this.onViewStateChanged({ current, previous }); - }; - - // tslint:disable-next-line:no-any - private webPanelRendered() { - if (this.webviewInit && !this.webviewInit.resolved) { - // Send telemetry for startup - sendTelemetryEvent(Telemetry.WebviewStartup, this.startupStopwatch.elapsedTime, { type: this.title }); - - // Resolve our started promise. This means the webpanel is ready to go. - this.webviewInit.resolve(); - - traceInfo('Web view react rendered'); - } - - // On started, resend our init data. - this.sendLocStrings().ignoreErrors(); - this.onDataScienceSettingsChanged().ignoreErrors(); - } -} diff --git a/src/client/debugger/constants.ts b/src/client/debugger/constants.ts index 6d4af08019be..a2ac198a597d 100644 --- a/src/client/debugger/constants.ts +++ b/src/client/debugger/constants.ts @@ -3,8 +3,5 @@ 'use strict'; -import * as path from 'path'; -import { EXTENSION_ROOT_DIR } from '../common/constants'; - -export const DEBUGGER_PATH = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'lib', 'python', 'debugpy'); export const DebuggerTypeName = 'python'; +export const PythonDebuggerTypeName = 'debugpy'; diff --git a/src/client/debugger/extension/adapter/activator.ts b/src/client/debugger/extension/adapter/activator.ts index be64226946c5..999c00366ed6 100644 --- a/src/client/debugger/extension/adapter/activator.ts +++ b/src/client/debugger/extension/adapter/activator.ts @@ -2,37 +2,52 @@ // Licensed under the MIT License. 'use strict'; - +import { Uri } from 'vscode'; import { inject, injectable } from 'inversify'; import { IExtensionSingleActivationService } from '../../../activation/types'; import { IDebugService } from '../../../common/application/types'; -import { IDisposableRegistry } from '../../../common/types'; +import { IConfigurationService, IDisposableRegistry } from '../../../common/types'; +import { ICommandManager } from '../../../common/application/types'; import { DebuggerTypeName } from '../../constants'; import { IAttachProcessProviderFactory } from '../attachQuickPick/types'; import { IDebugAdapterDescriptorFactory, IDebugSessionLoggingFactory, IOutdatedDebuggerPromptFactory } from '../types'; @injectable() export class DebugAdapterActivator implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; constructor( @inject(IDebugService) private readonly debugService: IDebugService, + @inject(IConfigurationService) private readonly configSettings: IConfigurationService, + @inject(ICommandManager) private commandManager: ICommandManager, @inject(IDebugAdapterDescriptorFactory) private descriptorFactory: IDebugAdapterDescriptorFactory, @inject(IDebugSessionLoggingFactory) private debugSessionLoggingFactory: IDebugSessionLoggingFactory, @inject(IOutdatedDebuggerPromptFactory) private debuggerPromptFactory: IOutdatedDebuggerPromptFactory, @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry, @inject(IAttachProcessProviderFactory) - private readonly attachProcessProviderFactory: IAttachProcessProviderFactory + private readonly attachProcessProviderFactory: IAttachProcessProviderFactory, ) {} public async activate(): Promise { this.attachProcessProviderFactory.registerCommands(); this.disposables.push( - this.debugService.registerDebugAdapterTrackerFactory(DebuggerTypeName, this.debugSessionLoggingFactory) + this.debugService.registerDebugAdapterTrackerFactory(DebuggerTypeName, this.debugSessionLoggingFactory), + ); + this.disposables.push( + this.debugService.registerDebugAdapterTrackerFactory(DebuggerTypeName, this.debuggerPromptFactory), ); + this.disposables.push( - this.debugService.registerDebugAdapterTrackerFactory(DebuggerTypeName, this.debuggerPromptFactory) + this.debugService.registerDebugAdapterDescriptorFactory(DebuggerTypeName, this.descriptorFactory), ); this.disposables.push( - this.debugService.registerDebugAdapterDescriptorFactory(DebuggerTypeName, this.descriptorFactory) + this.debugService.onDidStartDebugSession((debugSession) => { + if (this.shouldTerminalFocusOnStart(debugSession.workspaceFolder?.uri)) + this.commandManager.executeCommand('workbench.action.terminal.focus'); + }), ); } + + private shouldTerminalFocusOnStart(uri: Uri | undefined): boolean { + return this.configSettings.getSettings(uri)?.terminal.focusAfterLaunch; + } } diff --git a/src/client/debugger/extension/adapter/factory.ts b/src/client/debugger/extension/adapter/factory.ts index 6379874d8e47..edef16368dc0 100644 --- a/src/client/debugger/extension/adapter/factory.ts +++ b/src/client/debugger/extension/adapter/factory.ts @@ -10,26 +10,38 @@ import { DebugAdapterExecutable, DebugAdapterServer, DebugSession, - WorkspaceFolder + l10n, + WorkspaceFolder, } from 'vscode'; -import { IApplicationShell } from '../../../common/application/types'; -import { traceVerbose } from '../../../common/logger'; import { EXTENSION_ROOT_DIR } from '../../../constants'; import { IInterpreterService } from '../../../interpreter/contracts'; -import { sendTelemetryEvent } from '../../../telemetry'; -import { EventName } from '../../../telemetry/constants'; +import { traceError, traceLog, traceVerbose } from '../../../logging'; +import { PythonEnvironment } from '../../../pythonEnvironments/info'; import { AttachRequestArguments, LaunchRequestArguments } from '../../types'; import { IDebugAdapterDescriptorFactory } from '../types'; +import { showErrorMessage } from '../../../common/vscodeApis/windowApis'; +import { Common, Interpreters } from '../../../common/utils/localize'; +import { IPersistentStateFactory } from '../../../common/types'; +import { Commands } from '../../../common/constants'; +import { ICommandManager } from '../../../common/application/types'; +import { getDebugpyPath } from '../../pythonDebugger'; + +// persistent state names, exported to make use of in testing +export enum debugStateKeys { + doNotShowAgain = 'doNotShowPython36DebugDeprecatedAgain', +} @injectable() export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFactory { constructor( + @inject(ICommandManager) private readonly commandManager: ICommandManager, @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, - @inject(IApplicationShell) private readonly appShell: IApplicationShell + @inject(IPersistentStateFactory) private persistentState: IPersistentStateFactory, ) {} + public async createDebugAdapterDescriptor( session: DebugSession, - _executable: DebugAdapterExecutable | undefined + _executable: DebugAdapterExecutable | undefined, ): Promise { const configuration = session.configuration as LaunchRequestArguments | AttachRequestArguments; @@ -46,39 +58,43 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac if (configuration.request === 'attach') { if (configuration.connect !== undefined) { + traceLog( + `Connecting to DAP Server at: ${configuration.connect.host ?? '127.0.0.1'}:${ + configuration.connect.port + }`, + ); return new DebugAdapterServer(configuration.connect.port, configuration.connect.host ?? '127.0.0.1'); } else if (configuration.port !== undefined) { + traceLog(`Connecting to DAP Server at: ${configuration.host ?? '127.0.0.1'}:${configuration.port}`); return new DebugAdapterServer(configuration.port, configuration.host ?? '127.0.0.1'); } else if (configuration.listen === undefined && configuration.processId === undefined) { throw new Error('"request":"attach" requires either "connect", "listen", or "processId"'); } } - const pythonPath = await this.getPythonPath(configuration, session.workspaceFolder); - if (pythonPath.length !== 0) { - if (configuration.request === 'attach' && configuration.processId !== undefined) { - sendTelemetryEvent(EventName.DEBUGGER_ATTACH_TO_LOCAL_PROCESS); - } + const command = await this.getDebugAdapterPython(configuration, session.workspaceFolder); + if (command.length !== 0) { + const executable = command.shift() ?? 'python'; // "logToFile" is not handled directly by the adapter - instead, we need to pass // the corresponding CLI switch when spawning it. const logArgs = configuration.logToFile ? ['--log-dir', EXTENSION_ROOT_DIR] : []; if (configuration.debugAdapterPath !== undefined) { - return new DebugAdapterExecutable(pythonPath, [configuration.debugAdapterPath, ...logArgs]); + const args = command.concat([configuration.debugAdapterPath, ...logArgs]); + traceLog(`DAP Server launched with command: ${executable} ${args.join(' ')}`); + return new DebugAdapterExecutable(executable, args); } + const debugpyPath = await getDebugpyPath(); + if (!debugpyPath) { + traceError('Could not find debugpy path.'); + throw new Error('Could not find debugpy path.'); + } + const debuggerAdapterPathToUse = path.join(debugpyPath, 'adapter'); - const debuggerAdapterPathToUse = path.join( - EXTENSION_ROOT_DIR, - 'pythonFiles', - 'lib', - 'python', - 'debugpy', - 'adapter' - ); - - sendTelemetryEvent(EventName.DEBUG_ADAPTER_USING_WHEELS_PATH, undefined, { usingWheels: true }); - return new DebugAdapterExecutable(pythonPath, [debuggerAdapterPathToUse, ...logArgs]); + const args = command.concat([debuggerAdapterPathToUse, ...logArgs]); + traceLog(`DAP Server launched with command: ${executable} ${args.join(' ')}`); + return new DebugAdapterExecutable(executable, args); } // Unlikely scenario. @@ -96,28 +112,77 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac * @returns {Promise} Path to the python interpreter for this workspace. * @memberof DebugAdapterDescriptorFactory */ - private async getPythonPath( + private async getDebugAdapterPython( configuration: LaunchRequestArguments | AttachRequestArguments, - workspaceFolder?: WorkspaceFolder - ): Promise { - if (configuration.pythonPath) { - return configuration.pythonPath; + workspaceFolder?: WorkspaceFolder, + ): Promise { + if (configuration.debugAdapterPython !== undefined) { + return this.getExecutableCommand( + await this.interpreterService.getInterpreterDetails(configuration.debugAdapterPython), + ); + } else if (configuration.pythonPath) { + return this.getExecutableCommand( + await this.interpreterService.getInterpreterDetails(configuration.pythonPath), + ); } + const resourceUri = workspaceFolder ? workspaceFolder.uri : undefined; const interpreter = await this.interpreterService.getActiveInterpreter(resourceUri); if (interpreter) { traceVerbose(`Selecting active interpreter as Python Executable for DA '${interpreter.path}'`); - return interpreter.path; + return this.getExecutableCommand(interpreter); } - const interpreters = await this.interpreterService.getInterpreters(resourceUri); + await this.interpreterService.hasInterpreters(); // Wait until we know whether we have an interpreter + const interpreters = this.interpreterService.getInterpreters(resourceUri); if (interpreters.length === 0) { this.notifySelectInterpreter().ignoreErrors(); - return ''; + return []; } traceVerbose(`Picking first available interpreter to launch the DA '${interpreters[0].path}'`); - return interpreters[0].path; + return this.getExecutableCommand(interpreters[0]); + } + + private async showDeprecatedPythonMessage() { + const notificationPromptEnabled = this.persistentState.createGlobalPersistentState( + debugStateKeys.doNotShowAgain, + false, + ); + if (notificationPromptEnabled.value) { + return; + } + const prompts = [Interpreters.changePythonInterpreter, Common.doNotShowAgain]; + const selection = await showErrorMessage( + l10n.t('The debugger in the python extension no longer supports python versions minor than 3.7.'), + { modal: true }, + ...prompts, + ); + if (!selection) { + return; + } + if (selection == Interpreters.changePythonInterpreter) { + await this.commandManager.executeCommand(Commands.Set_Interpreter); + } + if (selection == Common.doNotShowAgain) { + // Never show the message again + await this.persistentState + .createGlobalPersistentState(debugStateKeys.doNotShowAgain, false) + .updateValue(true); + } + } + + private async getExecutableCommand(interpreter: PythonEnvironment | undefined): Promise { + if (interpreter) { + if ( + (interpreter.version?.major ?? 0) < 3 || + ((interpreter.version?.major ?? 0) <= 3 && (interpreter.version?.minor ?? 0) <= 6) + ) { + this.showDeprecatedPythonMessage(); + } + return interpreter.path.length > 0 ? [interpreter.path] : []; + } + return []; } /** @@ -129,9 +194,6 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac * @memberof DebugAdapterDescriptorFactory */ private async notifySelectInterpreter() { - await this.appShell.showErrorMessage( - // tslint:disable-next-line: messages-must-be-localized - 'Please install Python or select a Python Interpreter to use the debugger.' - ); + await showErrorMessage(l10n.t('Install Python or select a Python Interpreter to use the debugger.')); } } diff --git a/src/client/debugger/extension/adapter/logging.ts b/src/client/debugger/extension/adapter/logging.ts index 58b2f7ff4358..907b895170c6 100644 --- a/src/client/debugger/extension/adapter/logging.ts +++ b/src/client/debugger/extension/adapter/logging.ts @@ -9,7 +9,7 @@ import { DebugAdapterTrackerFactory, DebugConfiguration, DebugSession, - ProviderResult + ProviderResult, } from 'vscode'; import { DebugProtocol } from 'vscode-debugprotocol'; @@ -58,7 +58,7 @@ class DebugSessionLoggingTracker implements DebugAdapterTracker { private log(message: string) { if (this.enabled) { - this.stream!.write(`${this.timer.elapsedTime} ${message}`); + this.stream!.write(`${this.timer.elapsedTime} ${message}`); // NOSONAR } } diff --git a/src/client/debugger/extension/adapter/outdatedDebuggerPrompt.ts b/src/client/debugger/extension/adapter/outdatedDebuggerPrompt.ts index b9aa0d14542b..04117e9838d1 100644 --- a/src/client/debugger/extension/adapter/outdatedDebuggerPrompt.ts +++ b/src/client/debugger/extension/adapter/outdatedDebuggerPrompt.ts @@ -2,34 +2,27 @@ // Licensed under the MIT License. 'use strict'; - -import { inject, injectable } from 'inversify'; +import { injectable } from 'inversify'; import { DebugAdapterTracker, DebugAdapterTrackerFactory, DebugSession, ProviderResult } from 'vscode'; import { DebugProtocol } from 'vscode-debugprotocol'; -import { IApplicationShell } from '../../../common/application/types'; -import { IBrowserService } from '../../../common/types'; import { Common, OutdatedDebugger } from '../../../common/utils/localize'; +import { launch } from '../../../common/vscodeApis/browserApis'; +import { showInformationMessage } from '../../../common/vscodeApis/windowApis'; import { IPromptShowState } from './types'; // This situation occurs when user connects to old containers or server where // the debugger they had installed was ptvsd. We should show a prompt to ask them to update. class OutdatedDebuggerPrompt implements DebugAdapterTracker { - constructor( - private promptCheck: IPromptShowState, - private appShell: IApplicationShell, - private browserService: IBrowserService - ) {} + constructor(private promptCheck: IPromptShowState) {} public onDidSendMessage(message: DebugProtocol.ProtocolMessage) { if (this.promptCheck.shouldShowPrompt() && this.isPtvsd(message)) { - const prompts = [Common.moreInfo()]; - this.appShell - .showInformationMessage(OutdatedDebugger.outdatedDebuggerMessage(), ...prompts) - .then((selection) => { - if (selection === prompts[0]) { - this.browserService.launch('https://aka.ms/migrateToDebugpy'); - } - }); + const prompts = [Common.moreInfo]; + showInformationMessage(OutdatedDebugger.outdatedDebuggerMessage, ...prompts).then((selection) => { + if (selection === prompts[0]) { + launch('https://aka.ms/migrateToDebugpy'); + } + }); } } @@ -71,13 +64,10 @@ class OutdatedDebuggerPromptState implements IPromptShowState { @injectable() export class OutdatedDebuggerPromptFactory implements DebugAdapterTrackerFactory { private readonly promptCheck: OutdatedDebuggerPromptState; - constructor( - @inject(IApplicationShell) private readonly appShell: IApplicationShell, - @inject(IBrowserService) private browserService: IBrowserService - ) { + constructor() { this.promptCheck = new OutdatedDebuggerPromptState(); } public createDebugAdapterTracker(_session: DebugSession): ProviderResult { - return new OutdatedDebuggerPrompt(this.promptCheck, this.appShell, this.browserService); + return new OutdatedDebuggerPrompt(this.promptCheck); } } diff --git a/src/client/debugger/extension/adapter/remoteLaunchers.ts b/src/client/debugger/extension/adapter/remoteLaunchers.ts index 8f1eab6f9b51..f68f747a8a8c 100644 --- a/src/client/debugger/extension/adapter/remoteLaunchers.ts +++ b/src/client/debugger/extension/adapter/remoteLaunchers.ts @@ -3,24 +3,25 @@ 'use strict'; -import * as path from 'path'; -import { EXTENSION_ROOT_DIR } from '../../../common/constants'; import '../../../common/extensions'; +import { getDebugpyPath } from '../../pythonDebugger'; -const pathToPythonLibDir = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'lib', 'python'); -const pathToDebugger = path.join(pathToPythonLibDir, 'debugpy'); - -export type RemoteDebugOptions = { +type RemoteDebugOptions = { host: string; port: number; waitUntilDebuggerAttaches: boolean; }; -export function getDebugpyLauncherArgs(options: RemoteDebugOptions, debuggerPath: string = pathToDebugger) { - const waitArgs = options.waitUntilDebuggerAttaches ? ['--wait-for-client'] : []; - return [debuggerPath.fileToCommandArgument(), '--listen', `${options.host}:${options.port}`, ...waitArgs]; -} +export async function getDebugpyLauncherArgs(options: RemoteDebugOptions, debuggerPath?: string) { + if (!debuggerPath) { + debuggerPath = await getDebugpyPath(); + } -export function getDebugpyPackagePath(): string { - return pathToDebugger; + const waitArgs = options.waitUntilDebuggerAttaches ? ['--wait-for-client'] : []; + return [ + debuggerPath.fileToCommandArgumentForPythonExt(), + '--listen', + `${options.host}:${options.port}`, + ...waitArgs, + ]; } diff --git a/src/client/debugger/extension/attachQuickPick/factory.ts b/src/client/debugger/extension/attachQuickPick/factory.ts index f36c45183965..627962106e88 100644 --- a/src/client/debugger/extension/attachQuickPick/factory.ts +++ b/src/client/debugger/extension/attachQuickPick/factory.ts @@ -20,7 +20,7 @@ export class AttachProcessProviderFactory implements IAttachProcessProviderFacto @inject(ICommandManager) private readonly commandManager: ICommandManager, @inject(IPlatformService) private readonly platformService: IPlatformService, @inject(IProcessServiceFactory) private readonly processServiceFactory: IProcessServiceFactory, - @inject(IDisposableRegistry) private readonly disposableRegistry: IDisposableRegistry + @inject(IDisposableRegistry) private readonly disposableRegistry: IDisposableRegistry, ) {} public registerCommands() { @@ -29,7 +29,7 @@ export class AttachProcessProviderFactory implements IAttachProcessProviderFacto const disposable = this.commandManager.registerCommand( Commands.PickLocalProcess, () => picker.showQuickPick(), - this + this, ); this.disposableRegistry.push(disposable); } diff --git a/src/client/debugger/extension/attachQuickPick/picker.ts b/src/client/debugger/extension/attachQuickPick/picker.ts index db1d4a8a8a74..a296a9b3163a 100644 --- a/src/client/debugger/extension/attachQuickPick/picker.ts +++ b/src/client/debugger/extension/attachQuickPick/picker.ts @@ -14,7 +14,7 @@ import { IAttachItem, IAttachPicker, IAttachProcessProvider, REFRESH_BUTTON_ICON export class AttachPicker implements IAttachPicker { constructor( @inject(IApplicationShell) private readonly applicationShell: IApplicationShell, - private readonly attachItemsProvider: IAttachProcessProvider + private readonly attachItemsProvider: IAttachProcessProvider, ) {} public showQuickPick(): Promise { @@ -23,12 +23,12 @@ export class AttachPicker implements IAttachPicker { const refreshButton = { iconPath: getIcon(REFRESH_BUTTON_ICON), - tooltip: AttachProcess.refreshList() + tooltip: AttachProcess.refreshList, }; const quickPick = this.applicationShell.createQuickPick(); - quickPick.title = AttachProcess.attachTitle(); - quickPick.placeholder = AttachProcess.selectProcessPlaceholder(); + quickPick.title = AttachProcess.attachTitle; + quickPick.placeholder = AttachProcess.selectProcessPlaceholder; quickPick.canSelectMany = false; quickPick.matchOnDescription = true; quickPick.matchOnDetail = true; @@ -39,17 +39,19 @@ export class AttachPicker implements IAttachPicker { quickPick.onDidTriggerButton( async () => { + quickPick.busy = true; const attachItems = await this.attachItemsProvider.getAttachItems(); quickPick.items = attachItems; + quickPick.busy = false; }, this, - disposables + disposables, ); quickPick.onDidAccept( () => { if (quickPick.selectedItems.length !== 1) { - reject(new Error(AttachProcess.noProcessSelected())); + reject(new Error(AttachProcess.noProcessSelected)); } const selectedId = quickPick.selectedItems[0].id; @@ -60,7 +62,7 @@ export class AttachPicker implements IAttachPicker { resolve(selectedId); }, undefined, - disposables + disposables, ); quickPick.onDidHide( @@ -68,10 +70,10 @@ export class AttachPicker implements IAttachPicker { disposables.forEach((item) => item.dispose()); quickPick.dispose(); - reject(new Error(AttachProcess.noProcessSelected())); + reject(new Error(AttachProcess.noProcessSelected)); }, undefined, - disposables + disposables, ); quickPick.show(); diff --git a/src/client/debugger/extension/attachQuickPick/provider.ts b/src/client/debugger/extension/attachQuickPick/provider.ts index fbeb3eee34ac..3626d8dfb8ce 100644 --- a/src/client/debugger/extension/attachQuickPick/provider.ts +++ b/src/client/debugger/extension/attachQuickPick/provider.ts @@ -4,9 +4,9 @@ 'use strict'; import { inject, injectable } from 'inversify'; +import { l10n } from 'vscode'; import { IPlatformService } from '../../../common/platform/types'; import { IProcessServiceFactory } from '../../../common/process/types'; -import { AttachProcess as AttachProcessLocalization } from '../../../common/utils/localize'; import { PsProcessParser } from './psProcessParser'; import { IAttachItem, IAttachProcessProvider, ProcessListCommand } from './types'; import { WmicProcessParser } from './wmicProcessParser'; @@ -15,7 +15,7 @@ import { WmicProcessParser } from './wmicProcessParser'; export class AttachProcessProvider implements IAttachProcessProvider { constructor( @inject(IPlatformService) private readonly platformService: IPlatformService, - @inject(IProcessServiceFactory) private readonly processServiceFactory: IProcessServiceFactory + @inject(IProcessServiceFactory) private readonly processServiceFactory: IProcessServiceFactory, ) {} public getAttachItems(): Promise { @@ -23,7 +23,7 @@ export class AttachProcessProvider implements IAttachProcessProvider { processEntries.sort( ( { processName: aprocessName, commandLine: aCommandLine }, - { processName: bProcessName, commandLine: bCommandLine } + { processName: bProcessName, commandLine: bCommandLine }, ) => { const compare = (aString: string, bString: string): number => { // localeCompare is significantly slower than < and > (2000 ms vs 80 ms for 10,000 elements) @@ -53,7 +53,7 @@ export class AttachProcessProvider implements IAttachProcessProvider { } return compare(aprocessName, bProcessName); - } + }, ); return processEntries; @@ -69,7 +69,7 @@ export class AttachProcessProvider implements IAttachProcessProvider { } else if (this.platformService.isWindows) { processCmd = WmicProcessParser.wmicCommand; } else { - throw new Error(AttachProcessLocalization.unsupportedOS().format(this.platformService.osType)); + throw new Error(l10n.t("Operating system '{0}' not supported.", this.platformService.osType)); } const processService = await this.processServiceFactory.create(); diff --git a/src/client/debugger/extension/attachQuickPick/psProcessParser.ts b/src/client/debugger/extension/attachQuickPick/psProcessParser.ts index e5ab6f404a74..843369bd00c7 100644 --- a/src/client/debugger/extension/attachQuickPick/psProcessParser.ts +++ b/src/client/debugger/extension/attachQuickPick/psProcessParser.ts @@ -40,11 +40,11 @@ export namespace PsProcessParser { // Since 'args' contains the full path to the executable, even if truncated, searching will work as desired. export const psLinuxCommand: ProcessListCommand = { command: 'ps', - args: ['axww', '-o', `pid=,comm=${commColumnTitle},args=`] + args: ['axww', '-o', `pid=,comm=${commColumnTitle},args=`], }; export const psDarwinCommand: ProcessListCommand = { command: 'ps', - args: ['axww', '-o', `pid=,comm=${commColumnTitle},args=`, '-c'] + args: ['axww', '-o', `pid=,comm=${commColumnTitle},args=`, '-c'], }; export function parseProcesses(processes: string): IAttachItem[] { @@ -94,7 +94,7 @@ export namespace PsProcessParser { detail: cmdline, id: pid, processName: executable, - commandLine: cmdline + commandLine: cmdline, }; } } diff --git a/src/client/debugger/extension/attachQuickPick/types.ts b/src/client/debugger/extension/attachQuickPick/types.ts index c668231b29e2..5e26c1354f9e 100644 --- a/src/client/debugger/extension/attachQuickPick/types.ts +++ b/src/client/debugger/extension/attachQuickPick/types.ts @@ -3,7 +3,7 @@ 'use strict'; -import { QuickPickItem, Uri } from 'vscode'; +import { QuickPickItem } from 'vscode'; export type ProcessListCommand = { command: string; args: string[] }; @@ -26,9 +26,4 @@ export interface IAttachPicker { showQuickPick(): Promise; } -export interface IRefreshButton { - iconPath: { light: string | Uri; dark: string | Uri }; - tooltip: string; -} - export const REFRESH_BUTTON_ICON = 'refresh.svg'; diff --git a/src/client/debugger/extension/attachQuickPick/wmicProcessParser.ts b/src/client/debugger/extension/attachQuickPick/wmicProcessParser.ts index d7da2b98ace3..e1faed50fc2e 100644 --- a/src/client/debugger/extension/attachQuickPick/wmicProcessParser.ts +++ b/src/client/debugger/extension/attachQuickPick/wmicProcessParser.ts @@ -15,7 +15,7 @@ export namespace WmicProcessParser { detail: '', id: '', processName: '', - commandLine: '' + commandLine: '', }; // Perf numbers on Win10: @@ -27,7 +27,7 @@ export namespace WmicProcessParser { // | 1308 | 1132 | export const wmicCommand: ProcessListCommand = { command: 'wmic', - args: ['process', 'get', 'Name,ProcessId,CommandLine', '/FORMAT:list'] + args: ['process', 'get', 'Name,ProcessId,CommandLine', '/FORMAT:list'], }; export function parseProcesses(processes: string): IAttachItem[] { diff --git a/src/client/debugger/extension/banner.ts b/src/client/debugger/extension/banner.ts deleted file mode 100644 index f2cea5c64fd3..000000000000 --- a/src/client/debugger/extension/banner.ts +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { Disposable } from 'vscode'; -import { IApplicationShell, IDebugService } from '../../common/application/types'; -import '../../common/extensions'; -import { traceError } from '../../common/logger'; -import { IBrowserService, IDisposableRegistry, IPersistentStateFactory, IRandom } from '../../common/types'; -import { IServiceContainer } from '../../ioc/types'; -import { DebuggerTypeName } from '../constants'; -import { IDebuggerBanner } from './types'; - -const SAMPLE_SIZE_PER_HUNDRED = 10; - -export enum PersistentStateKeys { - ShowBanner = 'ShowBanner', - DebuggerLaunchCounter = 'DebuggerLaunchCounter', - DebuggerLaunchThresholdCounter = 'DebuggerLaunchThresholdCounter', - UserSelected = 'DebuggerUserSelected' -} - -@injectable() -export class DebuggerBanner implements IDebuggerBanner { - private initialized?: boolean; - private disabledInCurrentSession?: boolean; - private userSelected?: boolean; - - constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {} - - public initialize() { - if (this.initialized) { - return; - } - this.initialized = true; - - // Don't even bother adding handlers if banner has been turned off. - if (!this.isEnabled()) { - return; - } - - this.addCallback(); - } - - // "enabled" state - - public isEnabled(): boolean { - const factory = this.serviceContainer.get(IPersistentStateFactory); - const key = PersistentStateKeys.ShowBanner; - const state = factory.createGlobalPersistentState(key, true); - return state.value; - } - - public async disable(): Promise { - const factory = this.serviceContainer.get(IPersistentStateFactory); - const key = PersistentStateKeys.ShowBanner; - const state = factory.createGlobalPersistentState(key, false); - await state.updateValue(false); - } - - // showing banner - - public async shouldShow(): Promise { - if (!this.isEnabled() || this.disabledInCurrentSession) { - return false; - } - if (!(await this.passedThreshold())) { - return false; - } - return this.isUserSelected(); - } - - public async show(): Promise { - const appShell = this.serviceContainer.get(IApplicationShell); - const msg = 'Can you please take 2 minutes to tell us how the debugger is working for you?'; - const yes = 'Yes, take survey now'; - const no = 'No thanks'; - const later = 'Remind me later'; - const response = await appShell.showInformationMessage(msg, yes, no, later); - switch (response) { - case yes: { - await this.action(); - await this.disable(); - break; - } - case no: { - await this.disable(); - break; - } - default: { - // Disable for the current session. - this.disabledInCurrentSession = true; - } - } - } - - private async action(): Promise { - const debuggerLaunchCounter = await this.getGetDebuggerLaunchCounter(); - const browser = this.serviceContainer.get(IBrowserService); - browser.launch(`https://www.research.net/r/N7B25RV?n=${debuggerLaunchCounter}`); - } - - // user selection - - private async isUserSelected(): Promise { - if (this.userSelected !== undefined) { - return this.userSelected; - } - - const factory = this.serviceContainer.get(IPersistentStateFactory); - const key = PersistentStateKeys.UserSelected; - const state = factory.createGlobalPersistentState(key, undefined); - let selected = state.value; - if (selected === undefined) { - const runtime = this.serviceContainer.get(IRandom); - const randomSample = runtime.getRandomInt(0, 100); - selected = randomSample < SAMPLE_SIZE_PER_HUNDRED; - state.updateValue(selected).ignoreErrors(); - } - this.userSelected = selected; - return selected; - } - - // persistent counter - - private async passedThreshold(): Promise { - const [threshold, debuggerCounter] = await Promise.all([ - this.getDebuggerLaunchThresholdCounter(), - this.getGetDebuggerLaunchCounter() - ]); - return debuggerCounter >= threshold; - } - - private async incrementDebuggerLaunchCounter(): Promise { - const factory = this.serviceContainer.get(IPersistentStateFactory); - const key = PersistentStateKeys.DebuggerLaunchCounter; - const state = factory.createGlobalPersistentState(key, 0); - await state.updateValue(state.value + 1); - } - - private async getGetDebuggerLaunchCounter(): Promise { - const factory = this.serviceContainer.get(IPersistentStateFactory); - const key = PersistentStateKeys.DebuggerLaunchCounter; - const state = factory.createGlobalPersistentState(key, 0); - return state.value; - } - - private async getDebuggerLaunchThresholdCounter(): Promise { - const factory = this.serviceContainer.get(IPersistentStateFactory); - const key = PersistentStateKeys.DebuggerLaunchThresholdCounter; - const state = factory.createGlobalPersistentState(key, undefined); - if (state.value === undefined) { - const runtime = this.serviceContainer.get(IRandom); - const randomNumber = runtime.getRandomInt(1, 11); - await state.updateValue(randomNumber); - } - return state.value!; - } - - // debugger-specific functionality - - private addCallback() { - const debuggerService = this.serviceContainer.get(IDebugService); - const disposable = debuggerService.onDidTerminateDebugSession(async (e) => { - if (e.type === DebuggerTypeName) { - await this.onDidTerminateDebugSession().catch((ex) => traceError('Error in debugger Banner', ex)); - } - }); - this.serviceContainer.get(IDisposableRegistry).push(disposable); - } - - private async onDidTerminateDebugSession(): Promise { - if (!this.isEnabled()) { - return; - } - await this.incrementDebuggerLaunchCounter(); - const show = await this.shouldShow(); - if (!show) { - return; - } - - await this.show(); - } -} diff --git a/src/client/debugger/extension/configuration/debugConfigurationService.ts b/src/client/debugger/extension/configuration/debugConfigurationService.ts index 655de8b6a5df..9997fb4f0509 100644 --- a/src/client/debugger/extension/configuration/debugConfigurationService.ts +++ b/src/client/debugger/extension/configuration/debugConfigurationService.ts @@ -4,17 +4,10 @@ 'use strict'; import { inject, injectable, named } from 'inversify'; -import { CancellationToken, DebugConfiguration, QuickPickItem, WorkspaceFolder } from 'vscode'; -import { DebugConfigStrings } from '../../../common/utils/localize'; -import { - IMultiStepInput, - IMultiStepInputFactory, - InputStep, - IQuickPickParameters -} from '../../../common/utils/multiStepInput'; -import { AttachRequestArguments, DebugConfigurationArguments, LaunchRequestArguments } from '../../types'; -import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationService } from '../types'; -import { IDebugConfigurationProviderFactory, IDebugConfigurationResolver } from './types'; +import { CancellationToken, DebugConfiguration, WorkspaceFolder } from 'vscode'; +import { AttachRequestArguments, LaunchRequestArguments } from '../../types'; +import { IDebugConfigurationService } from '../types'; +import { IDebugConfigurationResolver } from './types'; @injectable() export class PythonDebugConfigurationService implements IDebugConfigurationService { @@ -25,112 +18,46 @@ export class PythonDebugConfigurationService implements IDebugConfigurationServi @inject(IDebugConfigurationResolver) @named('launch') private readonly launchResolver: IDebugConfigurationResolver, - @inject(IDebugConfigurationProviderFactory) - private readonly providerFactory: IDebugConfigurationProviderFactory, - @inject(IMultiStepInputFactory) private readonly multiStepFactory: IMultiStepInputFactory ) {} - public async provideDebugConfigurations( - folder: WorkspaceFolder | undefined, - token?: CancellationToken - ): Promise { - const config: Partial = {}; - const state = { config, folder, token }; - - // Disabled until configuration issues are addressed by VS Code. See #4007 - const multiStep = this.multiStepFactory.create(); - await multiStep.run((input, s) => this.pickDebugConfiguration(input, s), state); - if (Object.keys(state.config).length === 0) { - return; - } else { - return [state.config as DebugConfiguration]; - } - } public async resolveDebugConfiguration( folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, - token?: CancellationToken + token?: CancellationToken, ): Promise { if (debugConfiguration.request === 'attach') { return this.attachResolver.resolveDebugConfiguration( folder, debugConfiguration as AttachRequestArguments, - token + token, + ); + } + if (debugConfiguration.request === 'test') { + // `"request": "test"` is now deprecated. But some users might have it in their + // launch config. We get here if they triggered it using F5 or start with debugger. + throw Error( + 'This configuration can only be used by the test debugging commands. `"request": "test"` is deprecated, please keep as `"request": "launch"` and add `"purpose": ["debug-test"]` instead.', ); - } else if (debugConfiguration.request === 'test') { - throw Error("Please use the command 'Python: Debug Unit Tests'"); } else { if (Object.keys(debugConfiguration).length === 0) { - const configs = await this.provideDebugConfigurations(folder, token); - if (configs === undefined) { - return; - } - if (Array.isArray(configs) && configs.length === 1) { - debugConfiguration = configs[0]; - } + return undefined; } return this.launchResolver.resolveDebugConfiguration( folder, debugConfiguration as LaunchRequestArguments, - token + token, ); } } - protected async pickDebugConfiguration( - input: IMultiStepInput, - state: DebugConfigurationState - ): Promise | void> { - type DebugConfigurationQuickPickItem = QuickPickItem & { type: DebugConfigurationType }; - const items: DebugConfigurationQuickPickItem[] = [ - { - label: DebugConfigStrings.file.selectConfiguration.label(), - type: DebugConfigurationType.launchFile, - description: DebugConfigStrings.file.selectConfiguration.description() - }, - { - label: DebugConfigStrings.module.selectConfiguration.label(), - type: DebugConfigurationType.launchModule, - description: DebugConfigStrings.module.selectConfiguration.description() - }, - { - label: DebugConfigStrings.attach.selectConfiguration.label(), - type: DebugConfigurationType.remoteAttach, - description: DebugConfigStrings.attach.selectConfiguration.description() - }, - { - label: DebugConfigStrings.attachPid.selectConfiguration.label(), - type: DebugConfigurationType.pidAttach, - description: DebugConfigStrings.attachPid.selectConfiguration.description() - }, - { - label: DebugConfigStrings.django.selectConfiguration.label(), - type: DebugConfigurationType.launchDjango, - description: DebugConfigStrings.django.selectConfiguration.description() - }, - { - label: DebugConfigStrings.flask.selectConfiguration.label(), - type: DebugConfigurationType.launchFlask, - description: DebugConfigStrings.flask.selectConfiguration.description() - }, - { - label: DebugConfigStrings.pyramid.selectConfiguration.label(), - type: DebugConfigurationType.launchPyramid, - description: DebugConfigStrings.pyramid.selectConfiguration.description() - } - ]; - state.config = {}; - const pick = await input.showQuickPick< - DebugConfigurationQuickPickItem, - IQuickPickParameters - >({ - title: DebugConfigStrings.selectConfiguration.title(), - placeholder: DebugConfigStrings.selectConfiguration.placeholder(), - activeItem: items[0], - items: items - }); - if (pick) { - const provider = this.providerFactory.create(pick.type); - return provider.buildConfiguration.bind(provider); + + public async resolveDebugConfigurationWithSubstitutedVariables( + folder: WorkspaceFolder | undefined, + debugConfiguration: DebugConfiguration, + token?: CancellationToken, + ): Promise { + function resolve(resolver: IDebugConfigurationResolver) { + return resolver.resolveDebugConfigurationWithSubstitutedVariables(folder, debugConfiguration as T, token); } + return debugConfiguration.request === 'attach' ? resolve(this.attachResolver) : resolve(this.launchResolver); } } diff --git a/src/client/debugger/extension/configuration/launch.json/completionProvider.ts b/src/client/debugger/extension/configuration/launch.json/completionProvider.ts deleted file mode 100644 index 16d5c14608c8..000000000000 --- a/src/client/debugger/extension/configuration/launch.json/completionProvider.ts +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { getLocation } from 'jsonc-parser'; -import * as path from 'path'; -import { - CancellationToken, - CompletionItem, - CompletionItemKind, - CompletionItemProvider, - Position, - SnippetString, - TextDocument -} from 'vscode'; -import { IExtensionSingleActivationService } from '../../../../activation/types'; -import { ILanguageService } from '../../../../common/application/types'; -import { IDisposableRegistry } from '../../../../common/types'; -import { DebugConfigStrings } from '../../../../common/utils/localize'; - -const configurationNodeName = 'configurations'; -enum JsonLanguages { - json = 'json', - jsonWithComments = 'jsonc' -} - -@injectable() -export class LaunchJsonCompletionProvider implements CompletionItemProvider, IExtensionSingleActivationService { - constructor( - @inject(ILanguageService) private readonly languageService: ILanguageService, - @inject(IDisposableRegistry) private readonly disposableRegistry: IDisposableRegistry - ) {} - public async activate(): Promise { - this.disposableRegistry.push( - this.languageService.registerCompletionItemProvider({ language: JsonLanguages.json }, this) - ); - this.disposableRegistry.push( - this.languageService.registerCompletionItemProvider({ language: JsonLanguages.jsonWithComments }, this) - ); - } - public async provideCompletionItems( - document: TextDocument, - position: Position, - token: CancellationToken - ): Promise { - if (!this.canProvideCompletions(document, position)) { - return []; - } - - return [ - { - command: { - command: 'python.SelectAndInsertDebugConfiguration', - title: DebugConfigStrings.launchJsonCompletions.description(), - arguments: [document, position, token] - }, - documentation: DebugConfigStrings.launchJsonCompletions.description(), - sortText: 'AAAA', - preselect: true, - kind: CompletionItemKind.Enum, - label: DebugConfigStrings.launchJsonCompletions.label(), - insertText: new SnippetString() - } - ]; - } - public canProvideCompletions(document: TextDocument, position: Position) { - if (path.basename(document.uri.fsPath) !== 'launch.json') { - return false; - } - const location = getLocation(document.getText(), document.offsetAt(position)); - // Cursor must be inside the configurations array and not in any nested items. - // Hence path[0] = array, path[1] = array element index. - return location.path[0] === configurationNodeName && location.path.length === 2; - } -} diff --git a/src/client/debugger/extension/configuration/launch.json/interpreterPathCommand.ts b/src/client/debugger/extension/configuration/launch.json/interpreterPathCommand.ts deleted file mode 100644 index 6ba5ce3a9942..000000000000 --- a/src/client/debugger/extension/configuration/launch.json/interpreterPathCommand.ts +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { Uri } from 'vscode'; -import { IExtensionSingleActivationService } from '../../../../activation/types'; -import { ICommandManager } from '../../../../common/application/types'; -import { Commands } from '../../../../common/constants'; -import { IConfigurationService, IDisposable, IDisposableRegistry } from '../../../../common/types'; - -@injectable() -export class InterpreterPathCommand implements IExtensionSingleActivationService { - constructor( - @inject(ICommandManager) private readonly commandManager: ICommandManager, - @inject(IConfigurationService) private readonly configurationService: IConfigurationService, - @inject(IDisposableRegistry) private readonly disposables: IDisposable[] - ) {} - - public async activate() { - this.disposables.push( - this.commandManager.registerCommand(Commands.GetSelectedInterpreterPath, (args) => { - return this._getSelectedInterpreterPath(args); - }) - ); - } - - public _getSelectedInterpreterPath(args: { workspaceFolder: string } | string[]): string { - // If `launch.json` is launching this command, `args.workspaceFolder` carries the workspaceFolder - // If `tasks.json` is launching this command, `args[1]` carries the workspaceFolder - const workspaceFolder = 'workspaceFolder' in args ? args.workspaceFolder : args[1] ? args[1] : undefined; - return this.configurationService.getSettings(workspaceFolder ? Uri.parse(workspaceFolder) : undefined) - .pythonPath; - } -} diff --git a/src/client/debugger/extension/configuration/launch.json/launchJsonReader.ts b/src/client/debugger/extension/configuration/launch.json/launchJsonReader.ts new file mode 100644 index 000000000000..d5857638821a --- /dev/null +++ b/src/client/debugger/extension/configuration/launch.json/launchJsonReader.ts @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { parse } from 'jsonc-parser'; +import { DebugConfiguration, Uri, WorkspaceFolder } from 'vscode'; +import * as fs from '../../../../common/platform/fs-paths'; +import { getConfiguration, getWorkspaceFolder } from '../../../../common/vscodeApis/workspaceApis'; +import { traceLog } from '../../../../logging'; + +export async function getConfigurationsForWorkspace(workspace: WorkspaceFolder): Promise { + const filename = path.join(workspace.uri.fsPath, '.vscode', 'launch.json'); + if (!(await fs.pathExists(filename))) { + // Check launch config in the workspace file + const codeWorkspaceConfig = getConfiguration('launch', workspace); + if (!codeWorkspaceConfig.configurations || !Array.isArray(codeWorkspaceConfig.configurations)) { + return []; + } + traceLog('Using configuration in workspace'); + return codeWorkspaceConfig.configurations; + } + + const text = await fs.readFile(filename, 'utf-8'); + const parsed = parse(text, [], { allowTrailingComma: true, disallowComments: false }); + if (!parsed.configurations || !Array.isArray(parsed.configurations)) { + throw Error('Missing field in launch.json: configurations'); + } + if (!parsed.version) { + throw Error('Missing field in launch.json: version'); + } + // We do not bother ensuring each item is a DebugConfiguration... + traceLog('Using configuration in launch.json'); + return parsed.configurations; +} + +export async function getConfigurationsByUri(uri?: Uri): Promise { + if (uri) { + const workspace = getWorkspaceFolder(uri); + if (workspace) { + return getConfigurationsForWorkspace(workspace); + } + } + return []; +} diff --git a/src/client/debugger/extension/configuration/launch.json/updaterService.ts b/src/client/debugger/extension/configuration/launch.json/updaterService.ts deleted file mode 100644 index 199cd0ecffa1..000000000000 --- a/src/client/debugger/extension/configuration/launch.json/updaterService.ts +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { createScanner, parse, SyntaxKind } from 'jsonc-parser'; -import { CancellationToken, DebugConfiguration, Position, Range, TextDocument, WorkspaceEdit } from 'vscode'; -import { IExtensionSingleActivationService } from '../../../../activation/types'; -import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../../../common/application/types'; -import { IDisposableRegistry } from '../../../../common/types'; -import { noop } from '../../../../common/utils/misc'; -import { captureTelemetry } from '../../../../telemetry'; -import { EventName } from '../../../../telemetry/constants'; -import { IDebugConfigurationService } from '../../types'; - -type PositionOfCursor = 'InsideEmptyArray' | 'BeforeItem' | 'AfterItem'; -type PositionOfComma = 'BeforeCursor'; - -export class LaunchJsonUpdaterServiceHelper { - constructor( - private readonly commandManager: ICommandManager, - private readonly workspace: IWorkspaceService, - private readonly documentManager: IDocumentManager, - private readonly configurationProvider: IDebugConfigurationService - ) {} - @captureTelemetry(EventName.DEBUGGER_CONFIGURATION_PROMPTS_IN_LAUNCH_JSON) - public async selectAndInsertDebugConfig( - document: TextDocument, - position: Position, - token: CancellationToken - ): Promise { - if (this.documentManager.activeTextEditor && this.documentManager.activeTextEditor.document === document) { - const folder = this.workspace.getWorkspaceFolder(document.uri); - const configs = await this.configurationProvider.provideDebugConfigurations!(folder, token); - - if (!token.isCancellationRequested && Array.isArray(configs) && configs.length > 0) { - // Always use the first available debug configuration. - await this.insertDebugConfiguration(document, position, configs[0]); - } - } - } - /** - * Inserts the debug configuration into the document. - * Invokes the document formatter to ensure JSON is formatted nicely. - * @param {TextDocument} document - * @param {Position} position - * @param {DebugConfiguration} config - * @returns {Promise} - * @memberof LaunchJsonCompletionItemProvider - */ - public async insertDebugConfiguration( - document: TextDocument, - position: Position, - config: DebugConfiguration - ): Promise { - const cursorPosition = this.getCursorPositionInConfigurationsArray(document, position); - if (!cursorPosition) { - return; - } - const commaPosition = this.isCommaImmediatelyBeforeCursor(document, position) ? 'BeforeCursor' : undefined; - const formattedJson = this.getTextForInsertion(config, cursorPosition, commaPosition); - const workspaceEdit = new WorkspaceEdit(); - workspaceEdit.insert(document.uri, position, formattedJson); - await this.documentManager.applyEdit(workspaceEdit); - this.commandManager.executeCommand('editor.action.formatDocument').then(noop, noop); - } - /** - * Gets the string representation of the debug config for insertion in the document. - * Adds necessary leading or trailing commas (remember the text is added into an array). - * @param {DebugConfiguration} config - * @param {PositionOfCursor} cursorPosition - * @param {PositionOfComma} [commaPosition] - * @returns - * @memberof LaunchJsonCompletionItemProvider - */ - public getTextForInsertion( - config: DebugConfiguration, - cursorPosition: PositionOfCursor, - commaPosition?: PositionOfComma - ) { - const json = JSON.stringify(config); - if (cursorPosition === 'AfterItem') { - // If we already have a comma immediatley before the cursor, then no need of adding a comma. - return commaPosition === 'BeforeCursor' ? json : `,${json}`; - } - if (cursorPosition === 'BeforeItem') { - return `${json},`; - } - return json; - } - public getCursorPositionInConfigurationsArray( - document: TextDocument, - position: Position - ): PositionOfCursor | undefined { - if (this.isConfigurationArrayEmpty(document)) { - return 'InsideEmptyArray'; - } - const scanner = createScanner(document.getText(), true); - scanner.setPosition(document.offsetAt(position)); - const nextToken = scanner.scan(); - if (nextToken === SyntaxKind.CommaToken || nextToken === SyntaxKind.CloseBracketToken) { - return 'AfterItem'; - } - if (nextToken === SyntaxKind.OpenBraceToken) { - return 'BeforeItem'; - } - } - public isConfigurationArrayEmpty(document: TextDocument): boolean { - const configuration = parse(document.getText(), [], { allowTrailingComma: true, disallowComments: false }) as { - configurations: []; - }; - return ( - !configuration || !Array.isArray(configuration.configurations) || configuration.configurations.length === 0 - ); - } - public isCommaImmediatelyBeforeCursor(document: TextDocument, position: Position) { - const line = document.lineAt(position.line); - // Get text from start of line until the cursor. - const currentLine = document.getText(new Range(line.range.start, position)); - if (currentLine.trim().endsWith(',')) { - return true; - } - // If there are other characters, then don't bother. - if (currentLine.trim().length !== 0) { - return false; - } - - // Keep walking backwards until we hit a non-comma character or a comm character. - let startLineNumber = position.line - 1; - while (startLineNumber > 0) { - const lineText = document.lineAt(startLineNumber).text; - if (lineText.trim().endsWith(',')) { - return true; - } - // If there are other characters, then don't bother. - if (lineText.trim().length !== 0) { - return false; - } - startLineNumber -= 1; - continue; - } - return false; - } -} - -@injectable() -export class LaunchJsonUpdaterService implements IExtensionSingleActivationService { - constructor( - @inject(ICommandManager) private readonly commandManager: ICommandManager, - @inject(IDisposableRegistry) private readonly disposableRegistry: IDisposableRegistry, - @inject(IWorkspaceService) private readonly workspace: IWorkspaceService, - @inject(IDocumentManager) private readonly documentManager: IDocumentManager, - @inject(IDebugConfigurationService) private readonly configurationProvider: IDebugConfigurationService - ) {} - public async activate(): Promise { - const handler = new LaunchJsonUpdaterServiceHelper( - this.commandManager, - this.workspace, - this.documentManager, - this.configurationProvider - ); - this.disposableRegistry.push( - this.commandManager.registerCommand( - 'python.SelectAndInsertDebugConfiguration', - handler.selectAndInsertDebugConfig, - handler - ) - ); - } -} diff --git a/src/client/debugger/extension/configuration/providers/djangoLaunch.ts b/src/client/debugger/extension/configuration/providers/djangoLaunch.ts deleted file mode 100644 index 78839f495750..000000000000 --- a/src/client/debugger/extension/configuration/providers/djangoLaunch.ts +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { Uri, WorkspaceFolder } from 'vscode'; -import { IWorkspaceService } from '../../../../common/application/types'; -import { IFileSystem } from '../../../../common/platform/types'; -import { IPathUtils } from '../../../../common/types'; -import { DebugConfigStrings } from '../../../../common/utils/localize'; -import { MultiStepInput } from '../../../../common/utils/multiStepInput'; -import { SystemVariables } from '../../../../common/variables/systemVariables'; -import { sendTelemetryEvent } from '../../../../telemetry'; -import { EventName } from '../../../../telemetry/constants'; -import { DebuggerTypeName } from '../../../constants'; -import { LaunchRequestArguments } from '../../../types'; -import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; - -// tslint:disable-next-line:no-invalid-template-strings -const workspaceFolderToken = '${workspaceFolder}'; - -@injectable() -export class DjangoLaunchDebugConfigurationProvider implements IDebugConfigurationProvider { - constructor( - @inject(IFileSystem) private fs: IFileSystem, - @inject(IWorkspaceService) private readonly workspace: IWorkspaceService, - @inject(IPathUtils) private pathUtils: IPathUtils - ) {} - public async buildConfiguration(input: MultiStepInput, state: DebugConfigurationState) { - const program = await this.getManagePyPath(state.folder); - let manuallyEnteredAValue: boolean | undefined; - const defaultProgram = `${workspaceFolderToken}${this.pathUtils.separator}manage.py`; - const config: Partial = { - name: DebugConfigStrings.django.snippet.name(), - type: DebuggerTypeName, - request: 'launch', - program: program || defaultProgram, - args: ['runserver'], - django: true - }; - if (!program) { - const selectedProgram = await input.showInputBox({ - title: DebugConfigStrings.django.enterManagePyPath.title(), - value: defaultProgram, - prompt: DebugConfigStrings.django.enterManagePyPath.prompt(), - validate: (value) => this.validateManagePy(state.folder, defaultProgram, value) - }); - if (selectedProgram) { - manuallyEnteredAValue = true; - config.program = selectedProgram; - } - } - - sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { - configurationType: DebugConfigurationType.launchDjango, - autoDetectedDjangoManagePyPath: !!program, - manuallyEnteredAValue - }); - Object.assign(state.config, config); - } - public async validateManagePy( - folder: WorkspaceFolder | undefined, - defaultValue: string, - selected?: string - ): Promise { - const error = DebugConfigStrings.django.enterManagePyPath.invalid(); - if (!selected || selected.trim().length === 0) { - return error; - } - const resolvedPath = this.resolveVariables(selected, folder ? folder.uri : undefined); - if (selected !== defaultValue && !(await this.fs.fileExists(resolvedPath))) { - return error; - } - if (!resolvedPath.trim().toLowerCase().endsWith('.py')) { - return error; - } - return; - } - protected resolveVariables(pythonPath: string, resource: Uri | undefined): string { - const systemVariables = new SystemVariables(resource, undefined, this.workspace); - return systemVariables.resolveAny(pythonPath); - } - - protected async getManagePyPath(folder: WorkspaceFolder | undefined): Promise { - if (!folder) { - return; - } - const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'manage.py'); - if (await this.fs.fileExists(defaultLocationOfManagePy)) { - return `${workspaceFolderToken}${this.pathUtils.separator}manage.py`; - } - } -} diff --git a/src/client/debugger/extension/configuration/providers/fileLaunch.ts b/src/client/debugger/extension/configuration/providers/fileLaunch.ts deleted file mode 100644 index 1762709e4554..000000000000 --- a/src/client/debugger/extension/configuration/providers/fileLaunch.ts +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { injectable } from 'inversify'; -import { DebugConfigStrings } from '../../../../common/utils/localize'; -import { MultiStepInput } from '../../../../common/utils/multiStepInput'; -import { captureTelemetry } from '../../../../telemetry'; -import { EventName } from '../../../../telemetry/constants'; -import { DebuggerTypeName } from '../../../constants'; -import { LaunchRequestArguments } from '../../../types'; -import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; - -@injectable() -export class FileLaunchDebugConfigurationProvider implements IDebugConfigurationProvider { - @captureTelemetry( - EventName.DEBUGGER_CONFIGURATION_PROMPTS, - { configurationType: DebugConfigurationType.launchFile }, - false - ) - public async buildConfiguration(_input: MultiStepInput, state: DebugConfigurationState) { - const config: Partial = { - name: DebugConfigStrings.file.snippet.name(), - type: DebuggerTypeName, - request: 'launch', - // tslint:disable-next-line:no-invalid-template-strings - program: '${file}', - console: 'integratedTerminal' - }; - Object.assign(state.config, config); - } -} diff --git a/src/client/debugger/extension/configuration/providers/flaskLaunch.ts b/src/client/debugger/extension/configuration/providers/flaskLaunch.ts deleted file mode 100644 index 7ac3ad6455ff..000000000000 --- a/src/client/debugger/extension/configuration/providers/flaskLaunch.ts +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { WorkspaceFolder } from 'vscode'; -import { IFileSystem } from '../../../../common/platform/types'; -import { DebugConfigStrings } from '../../../../common/utils/localize'; -import { MultiStepInput } from '../../../../common/utils/multiStepInput'; -import { sendTelemetryEvent } from '../../../../telemetry'; -import { EventName } from '../../../../telemetry/constants'; -import { DebuggerTypeName } from '../../../constants'; -import { LaunchRequestArguments } from '../../../types'; -import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; - -@injectable() -export class FlaskLaunchDebugConfigurationProvider implements IDebugConfigurationProvider { - constructor(@inject(IFileSystem) private fs: IFileSystem) {} - public isSupported(debugConfigurationType: DebugConfigurationType): boolean { - return debugConfigurationType === DebugConfigurationType.launchFlask; - } - public async buildConfiguration(input: MultiStepInput, state: DebugConfigurationState) { - const application = await this.getApplicationPath(state.folder); - let manuallyEnteredAValue: boolean | undefined; - const config: Partial = { - name: DebugConfigStrings.flask.snippet.name(), - type: DebuggerTypeName, - request: 'launch', - module: 'flask', - env: { - FLASK_APP: application || 'app.py', - FLASK_ENV: 'development', - FLASK_DEBUG: '0' - }, - args: ['run', '--no-debugger'], - jinja: true - }; - - if (!application) { - const selectedApp = await input.showInputBox({ - title: DebugConfigStrings.flask.enterAppPathOrNamePath.title(), - value: 'app.py', - prompt: DebugConfigStrings.flask.enterAppPathOrNamePath.prompt(), - validate: (value) => - Promise.resolve( - value && value.trim().length > 0 - ? undefined - : DebugConfigStrings.flask.enterAppPathOrNamePath.invalid() - ) - }); - if (selectedApp) { - manuallyEnteredAValue = true; - config.env!.FLASK_APP = selectedApp; - } - } - - sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { - configurationType: DebugConfigurationType.launchFlask, - autoDetectedFlaskAppPyPath: !!application, - manuallyEnteredAValue - }); - Object.assign(state.config, config); - } - protected async getApplicationPath(folder: WorkspaceFolder | undefined): Promise { - if (!folder) { - return; - } - const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'app.py'); - if (await this.fs.fileExists(defaultLocationOfManagePy)) { - return 'app.py'; - } - } -} diff --git a/src/client/debugger/extension/configuration/providers/moduleLaunch.ts b/src/client/debugger/extension/configuration/providers/moduleLaunch.ts deleted file mode 100644 index e2d6db959d7d..000000000000 --- a/src/client/debugger/extension/configuration/providers/moduleLaunch.ts +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { injectable } from 'inversify'; -import { DebugConfigStrings } from '../../../../common/utils/localize'; -import { MultiStepInput } from '../../../../common/utils/multiStepInput'; -import { sendTelemetryEvent } from '../../../../telemetry'; -import { EventName } from '../../../../telemetry/constants'; -import { DebuggerTypeName } from '../../../constants'; -import { LaunchRequestArguments } from '../../../types'; -import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; - -@injectable() -export class ModuleLaunchDebugConfigurationProvider implements IDebugConfigurationProvider { - public async buildConfiguration(input: MultiStepInput, state: DebugConfigurationState) { - let manuallyEnteredAValue: boolean | undefined; - const config: Partial = { - name: DebugConfigStrings.module.snippet.name(), - type: DebuggerTypeName, - request: 'launch', - module: DebugConfigStrings.module.snippet.default() - }; - const selectedModule = await input.showInputBox({ - title: DebugConfigStrings.module.enterModule.title(), - value: config.module || DebugConfigStrings.module.enterModule.default(), - prompt: DebugConfigStrings.module.enterModule.prompt(), - validate: (value) => - Promise.resolve( - value && value.trim().length > 0 ? undefined : DebugConfigStrings.module.enterModule.invalid() - ) - }); - if (selectedModule) { - manuallyEnteredAValue = true; - config.module = selectedModule; - } - - sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { - configurationType: DebugConfigurationType.launchModule, - manuallyEnteredAValue - }); - Object.assign(state.config, config); - } -} diff --git a/src/client/debugger/extension/configuration/providers/pidAttach.ts b/src/client/debugger/extension/configuration/providers/pidAttach.ts deleted file mode 100644 index fe95e82b654c..000000000000 --- a/src/client/debugger/extension/configuration/providers/pidAttach.ts +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { injectable } from 'inversify'; -import { DebugConfigStrings } from '../../../../common/utils/localize'; -import { MultiStepInput } from '../../../../common/utils/multiStepInput'; -import { captureTelemetry } from '../../../../telemetry'; -import { EventName } from '../../../../telemetry/constants'; -import { DebuggerTypeName } from '../../../constants'; -import { AttachRequestArguments } from '../../../types'; -import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; - -@injectable() -export class PidAttachDebugConfigurationProvider implements IDebugConfigurationProvider { - @captureTelemetry( - EventName.DEBUGGER_CONFIGURATION_PROMPTS, - { configurationType: DebugConfigurationType.pidAttach }, - false - ) - public async buildConfiguration(_input: MultiStepInput, state: DebugConfigurationState) { - const config: Partial = { - name: DebugConfigStrings.attachPid.snippet.name(), - type: DebuggerTypeName, - request: 'attach', - // tslint:disable-next-line:no-invalid-template-strings - processId: '${command:pickProcess}' - }; - Object.assign(state.config, config); - } -} diff --git a/src/client/debugger/extension/configuration/providers/providerFactory.ts b/src/client/debugger/extension/configuration/providers/providerFactory.ts deleted file mode 100644 index 0504ca398fb0..000000000000 --- a/src/client/debugger/extension/configuration/providers/providerFactory.ts +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable, named } from 'inversify'; -import { DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; -import { IDebugConfigurationProviderFactory } from '../types'; - -@injectable() -export class DebugConfigurationProviderFactory implements IDebugConfigurationProviderFactory { - private readonly providers: Map; - constructor( - @inject(IDebugConfigurationProvider) - @named(DebugConfigurationType.launchFlask) - flaskProvider: IDebugConfigurationProvider, - @inject(IDebugConfigurationProvider) - @named(DebugConfigurationType.launchDjango) - djangoProvider: IDebugConfigurationProvider, - @inject(IDebugConfigurationProvider) - @named(DebugConfigurationType.launchModule) - moduleProvider: IDebugConfigurationProvider, - @inject(IDebugConfigurationProvider) - @named(DebugConfigurationType.launchFile) - fileProvider: IDebugConfigurationProvider, - @inject(IDebugConfigurationProvider) - @named(DebugConfigurationType.launchPyramid) - pyramidProvider: IDebugConfigurationProvider, - @inject(IDebugConfigurationProvider) - @named(DebugConfigurationType.remoteAttach) - remoteAttachProvider: IDebugConfigurationProvider, - @inject(IDebugConfigurationProvider) - @named(DebugConfigurationType.pidAttach) - pidAttachProvider: IDebugConfigurationProvider - ) { - this.providers = new Map(); - this.providers.set(DebugConfigurationType.launchDjango, djangoProvider); - this.providers.set(DebugConfigurationType.launchFlask, flaskProvider); - this.providers.set(DebugConfigurationType.launchFile, fileProvider); - this.providers.set(DebugConfigurationType.launchModule, moduleProvider); - this.providers.set(DebugConfigurationType.launchPyramid, pyramidProvider); - this.providers.set(DebugConfigurationType.remoteAttach, remoteAttachProvider); - this.providers.set(DebugConfigurationType.pidAttach, pidAttachProvider); - } - public create(configurationType: DebugConfigurationType): IDebugConfigurationProvider { - return this.providers.get(configurationType)!; - } -} diff --git a/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts b/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts deleted file mode 100644 index 9aab8b77c583..000000000000 --- a/src/client/debugger/extension/configuration/providers/pyramidLaunch.ts +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { Uri, WorkspaceFolder } from 'vscode'; -import { IWorkspaceService } from '../../../../common/application/types'; -import { IFileSystem } from '../../../../common/platform/types'; -import { IPathUtils } from '../../../../common/types'; -import { DebugConfigStrings } from '../../../../common/utils/localize'; -import { MultiStepInput } from '../../../../common/utils/multiStepInput'; -import { SystemVariables } from '../../../../common/variables/systemVariables'; -import { sendTelemetryEvent } from '../../../../telemetry'; -import { EventName } from '../../../../telemetry/constants'; -import { DebuggerTypeName } from '../../../constants'; -import { LaunchRequestArguments } from '../../../types'; -import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; - -// tslint:disable-next-line:no-invalid-template-strings -const workspaceFolderToken = '${workspaceFolder}'; - -@injectable() -export class PyramidLaunchDebugConfigurationProvider implements IDebugConfigurationProvider { - constructor( - @inject(IFileSystem) private fs: IFileSystem, - @inject(IWorkspaceService) private readonly workspace: IWorkspaceService, - @inject(IPathUtils) private pathUtils: IPathUtils - ) {} - public async buildConfiguration(input: MultiStepInput, state: DebugConfigurationState) { - const iniPath = await this.getDevelopmentIniPath(state.folder); - const defaultIni = `${workspaceFolderToken}${this.pathUtils.separator}development.ini`; - let manuallyEnteredAValue: boolean | undefined; - - const config: Partial = { - name: DebugConfigStrings.pyramid.snippet.name(), - type: DebuggerTypeName, - request: 'launch', - module: 'pyramid.scripts.pserve', - args: [iniPath || defaultIni], - pyramid: true, - jinja: true - }; - - if (!iniPath) { - const selectedIniPath = await input.showInputBox({ - title: DebugConfigStrings.pyramid.enterDevelopmentIniPath.title(), - value: defaultIni, - prompt: DebugConfigStrings.pyramid.enterDevelopmentIniPath.prompt(), - validate: (value) => this.validateIniPath(state ? state.folder : undefined, defaultIni, value) - }); - if (selectedIniPath) { - manuallyEnteredAValue = true; - config.args = [selectedIniPath]; - } - } - - sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { - configurationType: DebugConfigurationType.launchPyramid, - autoDetectedPyramidIniPath: !!iniPath, - manuallyEnteredAValue - }); - Object.assign(state.config, config); - } - public async validateIniPath( - folder: WorkspaceFolder | undefined, - defaultValue: string, - selected?: string - ): Promise { - if (!folder) { - return; - } - const error = DebugConfigStrings.pyramid.enterDevelopmentIniPath.invalid(); - if (!selected || selected.trim().length === 0) { - return error; - } - const resolvedPath = this.resolveVariables(selected, folder.uri); - if (selected !== defaultValue && !(await this.fs.fileExists(resolvedPath))) { - return error; - } - if (!resolvedPath.trim().toLowerCase().endsWith('.ini')) { - return error; - } - } - protected resolveVariables(pythonPath: string, resource: Uri | undefined): string { - const systemVariables = new SystemVariables(resource, undefined, this.workspace); - return systemVariables.resolveAny(pythonPath); - } - - protected async getDevelopmentIniPath(folder: WorkspaceFolder | undefined): Promise { - if (!folder) { - return; - } - const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'development.ini'); - if (await this.fs.fileExists(defaultLocationOfManagePy)) { - return `${workspaceFolderToken}${this.pathUtils.separator}development.ini`; - } - } -} diff --git a/src/client/debugger/extension/configuration/providers/remoteAttach.ts b/src/client/debugger/extension/configuration/providers/remoteAttach.ts deleted file mode 100644 index 51189093ebc2..000000000000 --- a/src/client/debugger/extension/configuration/providers/remoteAttach.ts +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { injectable } from 'inversify'; -import { DebugConfigStrings } from '../../../../common/utils/localize'; -import { InputStep, MultiStepInput } from '../../../../common/utils/multiStepInput'; -import { sendTelemetryEvent } from '../../../../telemetry'; -import { EventName } from '../../../../telemetry/constants'; -import { DebuggerTypeName } from '../../../constants'; -import { AttachRequestArguments } from '../../../types'; -import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationProvider } from '../../types'; - -const defaultHost = 'localhost'; -const defaultPort = 5678; - -@injectable() -export class RemoteAttachDebugConfigurationProvider implements IDebugConfigurationProvider { - public async buildConfiguration( - input: MultiStepInput, - state: DebugConfigurationState - ): Promise | void> { - const config: Partial = { - name: DebugConfigStrings.attach.snippet.name(), - type: DebuggerTypeName, - request: 'attach', - connect: { - host: defaultHost, - port: defaultPort - }, - pathMappings: [ - { - // tslint:disable-next-line:no-invalid-template-strings - localRoot: '${workspaceFolder}', - remoteRoot: '.' - } - ] - }; - - const connect = config.connect!; - connect.host = await input.showInputBox({ - title: DebugConfigStrings.attach.enterRemoteHost.title(), - step: 1, - totalSteps: 2, - value: connect.host || defaultHost, - prompt: DebugConfigStrings.attach.enterRemoteHost.prompt(), - validate: (value) => - Promise.resolve( - value && value.trim().length > 0 ? undefined : DebugConfigStrings.attach.enterRemoteHost.invalid() - ) - }); - if (!connect.host) { - connect.host = defaultHost; - } - - sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { - configurationType: DebugConfigurationType.remoteAttach, - manuallyEnteredAValue: connect.host !== defaultHost - }); - Object.assign(state.config, config); - return (_) => this.configurePort(input, state.config); - } - - protected async configurePort( - input: MultiStepInput, - config: Partial - ) { - const connect = config.connect || (config.connect = {}); - const port = await input.showInputBox({ - title: DebugConfigStrings.attach.enterRemotePort.title(), - step: 2, - totalSteps: 2, - value: (connect.port || defaultPort).toString(), - prompt: DebugConfigStrings.attach.enterRemotePort.prompt(), - validate: (value) => - Promise.resolve( - value && /^\d+$/.test(value.trim()) - ? undefined - : DebugConfigStrings.attach.enterRemotePort.invalid() - ) - }); - if (port && /^\d+$/.test(port.trim())) { - connect.port = parseInt(port, 10); - } - if (!connect.port) { - connect.port = defaultPort; - } - sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { - configurationType: DebugConfigurationType.remoteAttach, - manuallyEnteredAValue: connect.port !== defaultPort - }); - } -} diff --git a/src/client/debugger/extension/configuration/resolvers/attach.ts b/src/client/debugger/extension/configuration/resolvers/attach.ts index bb8fdf913bed..1c232f261d03 100644 --- a/src/client/debugger/extension/configuration/resolvers/attach.ts +++ b/src/client/debugger/extension/configuration/resolvers/attach.ts @@ -3,45 +3,38 @@ 'use strict'; -import { inject, injectable } from 'inversify'; +import { injectable } from 'inversify'; import { CancellationToken, Uri, WorkspaceFolder } from 'vscode'; -import { IDocumentManager, IWorkspaceService } from '../../../../common/application/types'; -import { IPlatformService } from '../../../../common/platform/types'; -import { IConfigurationService } from '../../../../common/types'; +import { getOSType, OSType } from '../../../../common/utils/platform'; import { AttachRequestArguments, DebugOptions, PathMapping } from '../../../types'; import { BaseConfigurationResolver } from './base'; @injectable() export class AttachConfigurationResolver extends BaseConfigurationResolver { - constructor( - @inject(IWorkspaceService) workspaceService: IWorkspaceService, - @inject(IDocumentManager) documentManager: IDocumentManager, - @inject(IPlatformService) platformService: IPlatformService, - @inject(IConfigurationService) configurationService: IConfigurationService - ) { - super(workspaceService, documentManager, platformService, configurationService); - } - public async resolveDebugConfiguration( + public async resolveDebugConfigurationWithSubstitutedVariables( folder: WorkspaceFolder | undefined, debugConfiguration: AttachRequestArguments, - _token?: CancellationToken + _token?: CancellationToken, ): Promise { - const workspaceFolder = this.getWorkspaceFolder(folder); + const workspaceFolder = AttachConfigurationResolver.getWorkspaceFolder(folder); await this.provideAttachDefaults(workspaceFolder, debugConfiguration as AttachRequestArguments); const dbgConfig = debugConfiguration; if (Array.isArray(dbgConfig.debugOptions)) { dbgConfig.debugOptions = dbgConfig.debugOptions!.filter( - (item, pos) => dbgConfig.debugOptions!.indexOf(item) === pos + (item, pos) => dbgConfig.debugOptions!.indexOf(item) === pos, ); } + if (debugConfiguration.clientOS === undefined) { + debugConfiguration.clientOS = getOSType() === OSType.Windows ? 'windows' : 'unix'; + } return debugConfiguration; } - // tslint:disable-next-line:cyclomatic-complexity + protected async provideAttachDefaults( workspaceFolder: Uri | undefined, - debugConfiguration: AttachRequestArguments + debugConfiguration: AttachRequestArguments, ): Promise { if (!Array.isArray(debugConfiguration.debugOptions)) { debugConfiguration.debugOptions = []; @@ -50,50 +43,41 @@ export class AttachConfigurationResolver extends BaseConfigurationResolver 0 ? pathMappings : undefined; } diff --git a/src/client/debugger/extension/configuration/resolvers/base.ts b/src/client/debugger/extension/configuration/resolvers/base.ts index 1132928fbd99..fde55ad8d5ea 100644 --- a/src/client/debugger/extension/configuration/resolvers/base.ts +++ b/src/client/debugger/extension/configuration/resolvers/base.ts @@ -3,116 +3,174 @@ 'use strict'; -// tslint:disable:no-invalid-template-strings no-suspicious-comment - import { injectable } from 'inversify'; import * as path from 'path'; import { CancellationToken, DebugConfiguration, Uri, WorkspaceFolder } from 'vscode'; -import { IDocumentManager, IWorkspaceService } from '../../../../common/application/types'; -import { PYTHON_LANGUAGE } from '../../../../common/constants'; -import { IPlatformService } from '../../../../common/platform/types'; import { IConfigurationService } from '../../../../common/types'; -import { SystemVariables } from '../../../../common/variables/systemVariables'; -import { sendTelemetryEvent } from '../../../../telemetry'; -import { EventName } from '../../../../telemetry/constants'; -import { DebuggerTelemetry } from '../../../../telemetry/types'; +import { getOSType, OSType } from '../../../../common/utils/platform'; +import { + getWorkspaceFolder as getVSCodeWorkspaceFolder, + getWorkspaceFolders, +} from '../../../../common/vscodeApis/workspaceApis'; +import { IInterpreterService } from '../../../../interpreter/contracts'; import { AttachRequestArguments, DebugOptions, LaunchRequestArguments, PathMapping } from '../../../types'; import { PythonPathSource } from '../../types'; import { IDebugConfigurationResolver } from '../types'; +import { resolveVariables } from '../utils/common'; +import { getProgram } from './helper'; @injectable() export abstract class BaseConfigurationResolver implements IDebugConfigurationResolver { protected pythonPathSource: PythonPathSource = PythonPathSource.launchJson; + constructor( - protected readonly workspaceService: IWorkspaceService, - protected readonly documentManager: IDocumentManager, - protected readonly platformService: IPlatformService, - protected readonly configurationService: IConfigurationService + protected readonly configurationService: IConfigurationService, + protected readonly interpreterService: IInterpreterService, ) {} - public abstract resolveDebugConfiguration( + + // This is a legacy hook used solely for backwards-compatible manual substitution + // of ${command:python.interpreterPath} in "pythonPath", for the sake of other + // existing implementations of resolveDebugConfiguration() that may rely on it. + // + // For all future config variables, expansion should be performed by VSCode itself, + // and validation of debug configuration in derived classes should be performed in + // resolveDebugConfigurationWithSubstitutedVariables() instead, where all variables + // are already substituted. + // eslint-disable-next-line class-methods-use-this + public async resolveDebugConfiguration( + _folder: WorkspaceFolder | undefined, + debugConfiguration: DebugConfiguration, + _token?: CancellationToken, + ): Promise { + if (debugConfiguration.clientOS === undefined) { + debugConfiguration.clientOS = getOSType() === OSType.Windows ? 'windows' : 'unix'; + } + return debugConfiguration as T; + } + + public abstract resolveDebugConfigurationWithSubstitutedVariables( folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, - token?: CancellationToken + token?: CancellationToken, ): Promise; - protected getWorkspaceFolder(folder: WorkspaceFolder | undefined): Uri | undefined { + + protected static getWorkspaceFolder(folder: WorkspaceFolder | undefined): Uri | undefined { if (folder) { return folder.uri; } - const program = this.getProgram(); - if ( - !Array.isArray(this.workspaceService.workspaceFolders) || - this.workspaceService.workspaceFolders.length === 0 - ) { + const program = getProgram(); + const workspaceFolders = getWorkspaceFolders(); + + if (!Array.isArray(workspaceFolders) || workspaceFolders.length === 0) { return program ? Uri.file(path.dirname(program)) : undefined; } - if (this.workspaceService.workspaceFolders.length === 1) { - return this.workspaceService.workspaceFolders[0].uri; + if (workspaceFolders.length === 1) { + return workspaceFolders[0].uri; } if (program) { - const workspaceFolder = this.workspaceService.getWorkspaceFolder(Uri.file(program)); + const workspaceFolder = getVSCodeWorkspaceFolder(Uri.file(program)); if (workspaceFolder) { return workspaceFolder.uri; } } + return undefined; } - protected getProgram(): string | undefined { - const editor = this.documentManager.activeTextEditor; - if (editor && editor.document.languageId === PYTHON_LANGUAGE) { - return editor.document.fileName; - } - } - protected resolveAndUpdatePaths( + + protected async resolveAndUpdatePaths( workspaceFolder: Uri | undefined, - debugConfiguration: LaunchRequestArguments - ): void { - this.resolveAndUpdateEnvFilePath(workspaceFolder, debugConfiguration); - this.resolveAndUpdatePythonPath(workspaceFolder, debugConfiguration); + debugConfiguration: LaunchRequestArguments, + ): Promise { + BaseConfigurationResolver.resolveAndUpdateEnvFilePath(workspaceFolder, debugConfiguration); + await this.resolveAndUpdatePythonPath(workspaceFolder, debugConfiguration); } - protected resolveAndUpdateEnvFilePath( + + protected static resolveAndUpdateEnvFilePath( workspaceFolder: Uri | undefined, - debugConfiguration: LaunchRequestArguments + debugConfiguration: LaunchRequestArguments, ): void { if (!debugConfiguration) { return; } if (debugConfiguration.envFile && (workspaceFolder || debugConfiguration.cwd)) { - const systemVariables = new SystemVariables( + debugConfiguration.envFile = resolveVariables( + debugConfiguration.envFile, + (workspaceFolder ? workspaceFolder.fsPath : undefined) || debugConfiguration.cwd, undefined, - (workspaceFolder ? workspaceFolder.fsPath : undefined) || debugConfiguration.cwd ); - debugConfiguration.envFile = systemVariables.resolveAny(debugConfiguration.envFile); } } - protected resolveAndUpdatePythonPath( + + protected async resolveAndUpdatePythonPath( workspaceFolder: Uri | undefined, - debugConfiguration: LaunchRequestArguments - ): void { + debugConfiguration: LaunchRequestArguments, + ): Promise { if (!debugConfiguration) { return; } if (debugConfiguration.pythonPath === '${command:python.interpreterPath}' || !debugConfiguration.pythonPath) { - const pythonPath = this.configurationService.getSettings(workspaceFolder).pythonPath; - debugConfiguration.pythonPath = pythonPath; + const interpreterPath = + (await this.interpreterService.getActiveInterpreter(workspaceFolder))?.path ?? + this.configurationService.getSettings(workspaceFolder).pythonPath; + debugConfiguration.pythonPath = interpreterPath; + } else { + debugConfiguration.pythonPath = resolveVariables( + debugConfiguration.pythonPath ? debugConfiguration.pythonPath : undefined, + workspaceFolder?.fsPath, + undefined, + ); + } + + if (debugConfiguration.python === '${command:python.interpreterPath}') { + this.pythonPathSource = PythonPathSource.settingsJson; + const interpreterPath = + (await this.interpreterService.getActiveInterpreter(workspaceFolder))?.path ?? + this.configurationService.getSettings(workspaceFolder).pythonPath; + debugConfiguration.python = interpreterPath; + } else if (debugConfiguration.python === undefined) { this.pythonPathSource = PythonPathSource.settingsJson; + debugConfiguration.python = debugConfiguration.pythonPath; } else { this.pythonPathSource = PythonPathSource.launchJson; + debugConfiguration.python = resolveVariables( + debugConfiguration.python ?? debugConfiguration.pythonPath, + workspaceFolder?.fsPath, + undefined, + ); + } + + if ( + debugConfiguration.debugAdapterPython === '${command:python.interpreterPath}' || + debugConfiguration.debugAdapterPython === undefined + ) { + debugConfiguration.debugAdapterPython = debugConfiguration.pythonPath ?? debugConfiguration.python; + } + if ( + debugConfiguration.debugLauncherPython === '${command:python.interpreterPath}' || + debugConfiguration.debugLauncherPython === undefined + ) { + debugConfiguration.debugLauncherPython = debugConfiguration.pythonPath ?? debugConfiguration.python; } + + delete debugConfiguration.pythonPath; } - protected debugOption(debugOptions: DebugOptions[], debugOption: DebugOptions) { + + protected static debugOption(debugOptions: DebugOptions[], debugOption: DebugOptions): void { if (debugOptions.indexOf(debugOption) >= 0) { return; } debugOptions.push(debugOption); } - protected isLocalHost(hostName?: string) { + + protected static isLocalHost(hostName?: string): boolean { const LocalHosts = ['localhost', '127.0.0.1', '::1']; - return hostName && LocalHosts.indexOf(hostName.toLowerCase()) >= 0 ? true : false; + return !!(hostName && LocalHosts.indexOf(hostName.toLowerCase()) >= 0); } - protected fixUpPathMappings( + + protected static fixUpPathMappings( pathMappings: PathMapping[], defaultLocalRoot?: string, - defaultRemoteRoot?: string + defaultRemoteRoot?: string, ): PathMapping[] { if (!defaultLocalRoot) { return []; @@ -125,22 +183,24 @@ export abstract class BaseConfigurationResolver pathMappings = [ { localRoot: defaultLocalRoot, - remoteRoot: defaultRemoteRoot - } + remoteRoot: defaultRemoteRoot, + }, ]; } else { // Expand ${workspaceFolder} variable first if necessary. - const systemVariables = new SystemVariables(undefined, defaultLocalRoot); - pathMappings = pathMappings.map(({ localRoot: mappedLocalRoot, remoteRoot }) => ({ - localRoot: systemVariables.resolveAny(mappedLocalRoot), - // TODO: Apply to remoteRoot too? - remoteRoot - })); + pathMappings = pathMappings.map(({ localRoot: mappedLocalRoot, remoteRoot }) => { + const resolvedLocalRoot = resolveVariables(mappedLocalRoot, defaultLocalRoot, undefined); + return { + localRoot: resolvedLocalRoot || '', + // TODO: Apply to remoteRoot too? + remoteRoot, + }; + }); } // If on Windows, lowercase the drive letter for path mappings. // TODO: Apply even if no localRoot? - if (this.platformService.isWindows) { + if (getOSType() === OSType.Windows) { // TODO: Apply to remoteRoot too? pathMappings = pathMappings.map(({ localRoot: windowsLocalRoot, remoteRoot }) => { let localRoot = windowsLocalRoot; @@ -153,35 +213,16 @@ export abstract class BaseConfigurationResolver return pathMappings; } - protected isDebuggingFlask(debugConfiguration: Partial) { - return debugConfiguration.module && debugConfiguration.module.toUpperCase() === 'FLASK' ? true : false; + + protected static isDebuggingFastAPI( + debugConfiguration: Partial, + ): boolean { + return !!(debugConfiguration.module && debugConfiguration.module.toUpperCase() === 'FASTAPI'); } - protected sendTelemetry( - trigger: 'launch' | 'attach' | 'test', - debugConfiguration: Partial - ) { - const name = debugConfiguration.name || ''; - const moduleName = debugConfiguration.module || ''; - const telemetryProps: DebuggerTelemetry = { - trigger, - console: debugConfiguration.console, - hasEnvVars: typeof debugConfiguration.env === 'object' && Object.keys(debugConfiguration.env).length > 0, - django: !!debugConfiguration.django, - flask: this.isDebuggingFlask(debugConfiguration), - hasArgs: Array.isArray(debugConfiguration.args) && debugConfiguration.args.length > 0, - isLocalhost: this.isLocalHost(debugConfiguration.host), - isModule: moduleName.length > 0, - isSudo: !!debugConfiguration.sudo, - jinja: !!debugConfiguration.jinja, - pyramid: !!debugConfiguration.pyramid, - stopOnEntry: !!debugConfiguration.stopOnEntry, - showReturnValue: !!debugConfiguration.showReturnValue, - subProcess: !!debugConfiguration.subProcess, - watson: name.toLowerCase().indexOf('watson') >= 0, - pyspark: name.toLowerCase().indexOf('pyspark') >= 0, - gevent: name.toLowerCase().indexOf('gevent') >= 0, - scrapy: moduleName.toLowerCase() === 'scrapy' - }; - sendTelemetryEvent(EventName.DEBUGGER, undefined, telemetryProps); + + protected static isDebuggingFlask( + debugConfiguration: Partial, + ): boolean { + return !!(debugConfiguration.module && debugConfiguration.module.toUpperCase() === 'FLASK'); } } diff --git a/src/client/debugger/extension/configuration/resolvers/helper.ts b/src/client/debugger/extension/configuration/resolvers/helper.ts index e0e31b3210c3..15be5f97538e 100644 --- a/src/client/debugger/extension/configuration/resolvers/helper.ts +++ b/src/client/debugger/extension/configuration/resolvers/helper.ts @@ -4,35 +4,54 @@ 'use strict'; import { inject, injectable } from 'inversify'; -import { ICurrentProcess, IPathUtils } from '../../../../common/types'; +import { ICurrentProcess } from '../../../../common/types'; import { EnvironmentVariables, IEnvironmentVariablesService } from '../../../../common/variables/types'; import { LaunchRequestArguments } from '../../../types'; +import { PYTHON_LANGUAGE } from '../../../../common/constants'; +import { getActiveTextEditor } from '../../../../common/vscodeApis/windowApis'; +import { getSearchPathEnvVarNames } from '../../../../common/utils/exec'; export const IDebugEnvironmentVariablesService = Symbol('IDebugEnvironmentVariablesService'); export interface IDebugEnvironmentVariablesService { - getEnvironmentVariables(args: LaunchRequestArguments): Promise; + getEnvironmentVariables( + args: LaunchRequestArguments, + baseVars?: EnvironmentVariables, + ): Promise; } @injectable() export class DebugEnvironmentVariablesHelper implements IDebugEnvironmentVariablesService { constructor( @inject(IEnvironmentVariablesService) private envParser: IEnvironmentVariablesService, - @inject(IPathUtils) private pathUtils: IPathUtils, - @inject(ICurrentProcess) private process: ICurrentProcess + @inject(ICurrentProcess) private process: ICurrentProcess, ) {} - public async getEnvironmentVariables(args: LaunchRequestArguments): Promise { - const pathVariableName = this.pathUtils.getPathVariableName(); + + public async getEnvironmentVariables( + args: LaunchRequestArguments, + baseVars?: EnvironmentVariables, + ): Promise { + const pathVariableName = getSearchPathEnvVarNames()[0]; // Merge variables from both .env file and env json variables. const debugLaunchEnvVars: Record = - // tslint:disable-next-line:no-any - args.env && Object.keys(args.env).length > 0 ? ({ ...args.env } as any) : ({} as any); + args.env && Object.keys(args.env).length > 0 + ? ({ ...args.env } as Record) + : ({} as Record); const envFileVars = await this.envParser.parseFile(args.envFile, debugLaunchEnvVars); - const env = envFileVars ? { ...envFileVars! } : {}; - this.envParser.mergeVariables(debugLaunchEnvVars, env); + const env = envFileVars ? { ...envFileVars } : {}; + + // "overwrite: true" to ensure that debug-configuration env variable values + // take precedence over env file. + this.envParser.mergeVariables(debugLaunchEnvVars, env, { overwrite: true }); + if (baseVars) { + this.envParser.mergeVariables(baseVars, env, { mergeAll: true }); + } // Append the PYTHONPATH and PATH variables. - this.envParser.appendPath(env, debugLaunchEnvVars[pathVariableName]); + this.envParser.appendPath( + env, + debugLaunchEnvVars[pathVariableName] ?? debugLaunchEnvVars[pathVariableName.toUpperCase()], + ); this.envParser.appendPythonPath(env, debugLaunchEnvVars.PYTHONPATH); if (typeof env[pathVariableName] === 'string' && env[pathVariableName]!.length > 0) { @@ -75,3 +94,11 @@ export class DebugEnvironmentVariablesHelper implements IDebugEnvironmentVariabl return env; } } + +export function getProgram(): string | undefined { + const activeTextEditor = getActiveTextEditor(); + if (activeTextEditor && activeTextEditor.document.languageId === PYTHON_LANGUAGE) { + return activeTextEditor.document.fileName; + } + return undefined; +} diff --git a/src/client/debugger/extension/configuration/resolvers/launch.ts b/src/client/debugger/extension/configuration/resolvers/launch.ts index 9d78188207df..3ca38fb0f710 100644 --- a/src/client/debugger/extension/configuration/resolvers/launch.ts +++ b/src/client/debugger/extension/configuration/resolvers/launch.ts @@ -7,69 +7,109 @@ import { inject, injectable, named } from 'inversify'; import { CancellationToken, Uri, WorkspaceFolder } from 'vscode'; import { InvalidPythonPathInDebuggerServiceId } from '../../../../application/diagnostics/checks/invalidPythonPathInDebugger'; import { IDiagnosticsService, IInvalidPythonPathInDebuggerService } from '../../../../application/diagnostics/types'; -import { IDocumentManager, IWorkspaceService } from '../../../../common/application/types'; -import { IPlatformService } from '../../../../common/platform/types'; import { IConfigurationService } from '../../../../common/types'; +import { getOSType, OSType } from '../../../../common/utils/platform'; +import { EnvironmentVariables } from '../../../../common/variables/types'; +import { IEnvironmentActivationService } from '../../../../interpreter/activation/types'; +import { IInterpreterService } from '../../../../interpreter/contracts'; import { DebuggerTypeName } from '../../../constants'; import { DebugOptions, LaunchRequestArguments } from '../../../types'; import { BaseConfigurationResolver } from './base'; -import { IDebugEnvironmentVariablesService } from './helper'; +import { getProgram, IDebugEnvironmentVariablesService } from './helper'; +import { + CreateEnvironmentCheckKind, + triggerCreateEnvironmentCheckNonBlocking, +} from '../../../../pythonEnvironments/creation/createEnvironmentTrigger'; +import { sendTelemetryEvent } from '../../../../telemetry'; +import { EventName } from '../../../../telemetry/constants'; @injectable() export class LaunchConfigurationResolver extends BaseConfigurationResolver { + private isCustomPythonSet = false; + constructor( - @inject(IWorkspaceService) workspaceService: IWorkspaceService, - @inject(IDocumentManager) documentManager: IDocumentManager, @inject(IDiagnosticsService) @named(InvalidPythonPathInDebuggerServiceId) private readonly invalidPythonPathInDebuggerService: IInvalidPythonPathInDebuggerService, - @inject(IPlatformService) platformService: IPlatformService, @inject(IConfigurationService) configurationService: IConfigurationService, - @inject(IDebugEnvironmentVariablesService) private readonly debugEnvHelper: IDebugEnvironmentVariablesService + @inject(IDebugEnvironmentVariablesService) private readonly debugEnvHelper: IDebugEnvironmentVariablesService, + @inject(IInterpreterService) interpreterService: IInterpreterService, + @inject(IEnvironmentActivationService) private environmentActivationService: IEnvironmentActivationService, ) { - super(workspaceService, documentManager, platformService, configurationService); + super(configurationService, interpreterService); } + public async resolveDebugConfiguration( folder: WorkspaceFolder | undefined, debugConfiguration: LaunchRequestArguments, - _token?: CancellationToken + _token?: CancellationToken, ): Promise { - const workspaceFolder = this.getWorkspaceFolder(folder); - - const config = debugConfiguration as LaunchRequestArguments; - const numberOfSettings = Object.keys(config); - - if ((config.noDebug === true && numberOfSettings.length === 1) || numberOfSettings.length === 0) { - const defaultProgram = this.getProgram(); + this.isCustomPythonSet = debugConfiguration.python !== undefined; + if ( + debugConfiguration.name === undefined && + debugConfiguration.type === undefined && + debugConfiguration.request === undefined && + debugConfiguration.program === undefined && + debugConfiguration.env === undefined + ) { + const defaultProgram = getProgram(); + debugConfiguration.name = 'Launch'; + debugConfiguration.type = DebuggerTypeName; + debugConfiguration.request = 'launch'; + debugConfiguration.program = defaultProgram ?? ''; + debugConfiguration.env = {}; + } - config.name = 'Launch'; - config.type = DebuggerTypeName; - config.request = 'launch'; - config.program = defaultProgram ? defaultProgram : ''; - config.env = {}; + const workspaceFolder = LaunchConfigurationResolver.getWorkspaceFolder(folder); + // Pass workspace folder so we can get this when we get debug events firing. + // Do it here itself instead of `resolveDebugConfigurationWithSubstitutedVariables` which is called after + // this method, as in order to calculate substituted variables, this might be needed. + debugConfiguration.workspaceFolder = workspaceFolder?.fsPath; + await this.resolveAndUpdatePaths(workspaceFolder, debugConfiguration); + if (debugConfiguration.clientOS === undefined) { + debugConfiguration.clientOS = getOSType() === OSType.Windows ? 'windows' : 'unix'; } + return debugConfiguration; + } - await this.provideLaunchDefaults(workspaceFolder, config); + public async resolveDebugConfigurationWithSubstitutedVariables( + folder: WorkspaceFolder | undefined, + debugConfiguration: LaunchRequestArguments, + _token?: CancellationToken, + ): Promise { + const workspaceFolder = LaunchConfigurationResolver.getWorkspaceFolder(folder); + await this.provideLaunchDefaults(workspaceFolder, debugConfiguration); - const isValid = await this.validateLaunchConfiguration(folder, config); + const isValid = await this.validateLaunchConfiguration(folder, debugConfiguration); if (!isValid) { - return; + return undefined; } - const dbgConfig = debugConfiguration; - if (Array.isArray(dbgConfig.debugOptions)) { - dbgConfig.debugOptions = dbgConfig.debugOptions!.filter( - (item, pos) => dbgConfig.debugOptions!.indexOf(item) === pos + if (Array.isArray(debugConfiguration.debugOptions)) { + debugConfiguration.debugOptions = debugConfiguration.debugOptions!.filter( + (item, pos) => debugConfiguration.debugOptions!.indexOf(item) === pos, ); } + sendTelemetryEvent(EventName.ENVIRONMENT_CHECK_TRIGGER, undefined, { trigger: 'debug' }); + triggerCreateEnvironmentCheckNonBlocking(CreateEnvironmentCheckKind.Workspace, workspaceFolder); return debugConfiguration; } - // tslint:disable-next-line:cyclomatic-complexity + protected async provideLaunchDefaults( workspaceFolder: Uri | undefined, - debugConfiguration: LaunchRequestArguments + debugConfiguration: LaunchRequestArguments, ): Promise { - this.resolveAndUpdatePaths(workspaceFolder, debugConfiguration); + if (debugConfiguration.python === undefined) { + debugConfiguration.python = debugConfiguration.pythonPath; + } + if (debugConfiguration.debugAdapterPython === undefined) { + debugConfiguration.debugAdapterPython = debugConfiguration.pythonPath; + } + if (debugConfiguration.debugLauncherPython === undefined) { + debugConfiguration.debugLauncherPython = debugConfiguration.pythonPath; + } + delete debugConfiguration.pythonPath; + if (typeof debugConfiguration.cwd !== 'string' && workspaceFolder) { debugConfiguration.cwd = workspaceFolder.fsPath; } @@ -77,10 +117,20 @@ export class LaunchConfigurationResolver extends BaseConfigurationResolver 0) { - pathMappings = this.fixUpPathMappings( + pathMappings = LaunchConfigurationResolver.fixUpPathMappings( pathMappings || [], - workspaceFolder ? workspaceFolder.fsPath : '' + workspaceFolder ? workspaceFolder.fsPath : '', ); } debugConfiguration.pathMappings = pathMappings.length > 0 ? pathMappings : undefined; } - this.sendTelemetry(debugConfiguration.request as 'launch' | 'test', debugConfiguration); } protected async validateLaunchConfiguration( folder: WorkspaceFolder | undefined, - debugConfiguration: LaunchRequestArguments + debugConfiguration: LaunchRequestArguments, ): Promise { const diagnosticService = this.invalidPythonPathInDebuggerService; - return diagnosticService.validatePythonPath( - debugConfiguration.pythonPath, - this.pythonPathSource, - folder ? folder.uri : undefined - ); + for (const executable of [ + debugConfiguration.python, + debugConfiguration.debugAdapterPython, + debugConfiguration.debugLauncherPython, + ]) { + if (!(await diagnosticService.validatePythonPath(executable, this.pythonPathSource, folder?.uri))) { + return false; + } + } + return true; } } diff --git a/src/client/debugger/extension/configuration/types.ts b/src/client/debugger/extension/configuration/types.ts index 1331ea39551a..eaebf6d435c4 100644 --- a/src/client/debugger/extension/configuration/types.ts +++ b/src/client/debugger/extension/configuration/types.ts @@ -4,18 +4,18 @@ 'use strict'; import { CancellationToken, DebugConfiguration, WorkspaceFolder } from 'vscode'; -import { DebugConfigurationType, IDebugConfigurationProvider } from '../types'; export const IDebugConfigurationResolver = Symbol('IDebugConfigurationResolver'); export interface IDebugConfigurationResolver { resolveDebugConfiguration( folder: WorkspaceFolder | undefined, debugConfiguration: T, - token?: CancellationToken + token?: CancellationToken, ): Promise; -} -export const IDebugConfigurationProviderFactory = Symbol('IDebugConfigurationProviderFactory'); -export interface IDebugConfigurationProviderFactory { - create(configurationType: DebugConfigurationType): IDebugConfigurationProvider; + resolveDebugConfigurationWithSubstitutedVariables( + folder: WorkspaceFolder | undefined, + debugConfiguration: T, + token?: CancellationToken, + ): Promise; } diff --git a/src/client/debugger/extension/configuration/utils/common.ts b/src/client/debugger/extension/configuration/utils/common.ts new file mode 100644 index 000000000000..3643a0c49c5d --- /dev/null +++ b/src/client/debugger/extension/configuration/utils/common.ts @@ -0,0 +1,43 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { WorkspaceFolder } from 'vscode'; +import { getWorkspaceFolder } from '../../../../common/vscodeApis/workspaceApis'; + +/** + * @returns whether the provided parameter is a JavaScript String or not. + */ +function isString(str: any): str is string { + if (typeof str === 'string' || str instanceof String) { + return true; + } + + return false; +} + +export function resolveVariables( + value: string | undefined, + rootFolder: string | undefined, + folder: WorkspaceFolder | undefined, +): string | undefined { + if (value) { + const workspaceFolder = folder ? getWorkspaceFolder(folder.uri) : undefined; + const variablesObject: { [key: string]: any } = {}; + variablesObject.workspaceFolder = workspaceFolder ? workspaceFolder.uri.fsPath : rootFolder; + + const regexp = /\$\{(.*?)\}/g; + return value.replace(regexp, (match: string, name: string) => { + const newValue = variablesObject[name]; + if (isString(newValue)) { + return newValue; + } + return match && (match.indexOf('env.') > 0 || match.indexOf('env:') > 0) ? '' : match; + }); + } + return value; +} diff --git a/src/client/debugger/extension/debugCommands.ts b/src/client/debugger/extension/debugCommands.ts new file mode 100644 index 000000000000..629f8616a6d6 --- /dev/null +++ b/src/client/debugger/extension/debugCommands.ts @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { inject, injectable } from 'inversify'; +import { DebugConfiguration, Uri } from 'vscode'; +import { IExtensionSingleActivationService } from '../../activation/types'; +import { ICommandManager, IDebugService } from '../../common/application/types'; +import { Commands } from '../../common/constants'; +import { IDisposableRegistry } from '../../common/types'; +import { sendTelemetryEvent } from '../../telemetry'; +import { EventName } from '../../telemetry/constants'; +import { DebugPurpose, LaunchRequestArguments } from '../types'; +import { IInterpreterService } from '../../interpreter/contracts'; +import { noop } from '../../common/utils/misc'; +import { getConfigurationsByUri } from './configuration/launch.json/launchJsonReader'; +import { + CreateEnvironmentCheckKind, + triggerCreateEnvironmentCheckNonBlocking, +} from '../../pythonEnvironments/creation/createEnvironmentTrigger'; + +@injectable() +export class DebugCommands implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; + + constructor( + @inject(ICommandManager) private readonly commandManager: ICommandManager, + @inject(IDebugService) private readonly debugService: IDebugService, + @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry, + @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, + ) {} + + public activate(): Promise { + this.disposables.push( + this.commandManager.registerCommand(Commands.Debug_In_Terminal, async (file?: Uri) => { + const interpreter = await this.interpreterService.getActiveInterpreter(file); + if (!interpreter) { + this.commandManager.executeCommand(Commands.TriggerEnvironmentSelection, file).then(noop, noop); + return; + } + sendTelemetryEvent(EventName.ENVIRONMENT_CHECK_TRIGGER, undefined, { trigger: 'debug-in-terminal' }); + triggerCreateEnvironmentCheckNonBlocking(CreateEnvironmentCheckKind.File, file); + const config = await DebugCommands.getDebugConfiguration(file); + this.debugService.startDebugging(undefined, config); + }), + ); + return Promise.resolve(); + } + + private static async getDebugConfiguration(uri?: Uri): Promise { + const configs = (await getConfigurationsByUri(uri)).filter((c) => c.request === 'launch'); + for (const config of configs) { + if ((config as LaunchRequestArguments).purpose?.includes(DebugPurpose.DebugInTerminal)) { + if (!config.program && !config.module && !config.code) { + // This is only needed if people reuse debug-test for debug-in-terminal + config.program = uri?.fsPath ?? '${file}'; + } + // Ensure that the purpose is cleared, this is so we can track if people accidentally + // trigger this via F5 or Start with debugger. + config.purpose = []; + return config; + } + } + return { + name: `Debug ${uri ? path.basename(uri.fsPath) : 'File'}`, + type: 'python', + request: 'launch', + program: uri?.fsPath ?? '${file}', + console: 'integratedTerminal', + }; + } +} diff --git a/src/client/debugger/extension/helpers/protocolParser.ts b/src/client/debugger/extension/helpers/protocolParser.ts deleted file mode 100644 index 86a8374d51a5..000000000000 --- a/src/client/debugger/extension/helpers/protocolParser.ts +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// tslint:disable:no-constant-condition no-typeof-undefined - -import { EventEmitter } from 'events'; -import { injectable } from 'inversify'; -import { Readable } from 'stream'; -import { DebugProtocol } from 'vscode-debugprotocol'; -import { IProtocolParser } from '../types'; - -const PROTOCOL_START_INDENTIFIER = '\r\n\r\n'; - -// tslint:disable-next-line: no-any -type Listener = (...args: any[]) => void; - -/** - * Parsers the debugger Protocol messages and raises the following events: - * 1. 'data', message (for all protocol messages) - * 1. 'event_', message (for all protocol events) - * 1. 'request_', message (for all protocol requests) - * 1. 'response_', message (for all protocol responses) - * 1. '', message (for all protocol messages that are not events, requests nor responses) - * @export - * @class ProtocolParser - * @extends {EventEmitter} - * @implements {IProtocolParser} - */ -@injectable() -export class ProtocolParser implements IProtocolParser { - private rawData = new Buffer(0); - private contentLength: number = -1; - private disposed: boolean = false; - private stream?: Readable; - private events: EventEmitter; - constructor() { - this.events = new EventEmitter(); - } - public dispose() { - if (this.stream) { - this.stream.removeListener('data', this.dataCallbackHandler); - this.stream = undefined; - } - } - public connect(stream: Readable) { - this.stream = stream; - stream.addListener('data', this.dataCallbackHandler); - } - public on(event: string | symbol, listener: Listener): this { - this.events.on(event, listener); - return this; - } - public once(event: string | symbol, listener: Listener): this { - this.events.once(event, listener); - return this; - } - private dataCallbackHandler = (data: string | Buffer) => { - this.handleData(data as Buffer); - }; - private dispatch(body: string): void { - const message = JSON.parse(body) as DebugProtocol.ProtocolMessage; - - switch (message.type) { - case 'event': { - const event = message as DebugProtocol.Event; - if (typeof event.event === 'string') { - this.events.emit(`${message.type}_${event.event}`, event); - } - break; - } - case 'request': { - const request = message as DebugProtocol.Request; - if (typeof request.command === 'string') { - this.events.emit(`${message.type}_${request.command}`, request); - } - break; - } - case 'response': { - const reponse = message as DebugProtocol.Response; - if (typeof reponse.command === 'string') { - this.events.emit(`${message.type}_${reponse.command}`, reponse); - } - break; - } - default: { - this.events.emit(`${message.type}`, message); - } - } - - this.events.emit('data', message); - } - private handleData(data: Buffer): void { - if (this.disposed) { - return; - } - this.rawData = Buffer.concat([this.rawData, data]); - - while (true) { - if (this.contentLength >= 0) { - if (this.rawData.length >= this.contentLength) { - const message = this.rawData.toString('utf8', 0, this.contentLength); - this.rawData = this.rawData.slice(this.contentLength); - this.contentLength = -1; - if (message.length > 0) { - this.dispatch(message); - } - // there may be more complete messages to process. - continue; - } - } else { - const idx = this.rawData.indexOf(PROTOCOL_START_INDENTIFIER); - if (idx !== -1) { - const header = this.rawData.toString('utf8', 0, idx); - const lines = header.split('\r\n'); - for (const line of lines) { - const pair = line.split(/: +/); - if (pair[0] === 'Content-Length') { - this.contentLength = +pair[1]; - } - } - this.rawData = this.rawData.slice(idx + PROTOCOL_START_INDENTIFIER.length); - continue; - } - } - break; - } - } -} diff --git a/src/client/debugger/extension/hooks/childProcessAttachHandler.ts b/src/client/debugger/extension/hooks/childProcessAttachHandler.ts index 9a3a7bda4935..233818e00aaf 100644 --- a/src/client/debugger/extension/hooks/childProcessAttachHandler.ts +++ b/src/client/debugger/extension/hooks/childProcessAttachHandler.ts @@ -9,23 +9,21 @@ import { swallowExceptions } from '../../../common/utils/decorators'; import { AttachRequestArguments } from '../../types'; import { DebuggerEvents } from './constants'; import { IChildProcessAttachService, IDebugSessionEventHandlers } from './types'; +import { DebuggerTypeName } from '../../constants'; /** * This class is responsible for automatically attaching the debugger to any * child processes launched. I.e. this is the class responsible for multi-proc debugging. - * @export - * @class ChildProcessAttachEventHandler - * @implements {IDebugSessionEventHandlers} */ @injectable() export class ChildProcessAttachEventHandler implements IDebugSessionEventHandlers { constructor( - @inject(IChildProcessAttachService) private readonly childProcessAttachService: IChildProcessAttachService + @inject(IChildProcessAttachService) private readonly childProcessAttachService: IChildProcessAttachService, ) {} @swallowExceptions('Handle child process launch') public async handleCustomEvent(event: DebugSessionCustomEvent): Promise { - if (!event) { + if (!event || event.session.configuration.type !== DebuggerTypeName) { return; } @@ -34,7 +32,7 @@ export class ChildProcessAttachEventHandler implements IDebugSessionEventHandler event.event === DebuggerEvents.PtvsdAttachToSubprocess || event.event === DebuggerEvents.DebugpyAttachToSubprocess ) { - data = event.body! as AttachRequestArguments & DebugConfiguration; + data = event.body as AttachRequestArguments & DebugConfiguration; } else { return; } diff --git a/src/client/debugger/extension/hooks/childProcessAttachService.ts b/src/client/debugger/extension/hooks/childProcessAttachService.ts index c7771b845340..39556f94c87c 100644 --- a/src/client/debugger/extension/hooks/childProcessAttachService.ts +++ b/src/client/debugger/extension/hooks/childProcessAttachService.ts @@ -4,47 +4,47 @@ 'use strict'; import { inject, injectable } from 'inversify'; -import { DebugConfiguration, DebugSession, WorkspaceFolder } from 'vscode'; -import { IApplicationShell, IDebugService, IWorkspaceService } from '../../../common/application/types'; +import { IDebugService } from '../../../common/application/types'; +import { DebugConfiguration, DebugSession, l10n, WorkspaceFolder, DebugSessionOptions } from 'vscode'; import { noop } from '../../../common/utils/misc'; -import { captureTelemetry } from '../../../telemetry'; -import { EventName } from '../../../telemetry/constants'; import { AttachRequestArguments } from '../../types'; import { IChildProcessAttachService } from './types'; +import { showErrorMessage } from '../../../common/vscodeApis/windowApis'; +import { getWorkspaceFolders } from '../../../common/vscodeApis/workspaceApis'; /** * This class is responsible for attaching the debugger to any * child processes launched. I.e. this is the class responsible for multi-proc debugging. - * @export - * @class ChildProcessAttachEventHandler - * @implements {IChildProcessAttachService} */ @injectable() export class ChildProcessAttachService implements IChildProcessAttachService { - constructor( - @inject(IApplicationShell) private readonly appShell: IApplicationShell, - @inject(IDebugService) private readonly debugService: IDebugService, - @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService - ) {} + constructor(@inject(IDebugService) private readonly debugService: IDebugService) {} - @captureTelemetry(EventName.DEBUGGER_ATTACH_TO_CHILD_PROCESS) public async attach(data: AttachRequestArguments & DebugConfiguration, parentSession: DebugSession): Promise { const debugConfig: AttachRequestArguments & DebugConfiguration = data; - const processId = debugConfig.subProcessId!; const folder = this.getRelatedWorkspaceFolder(debugConfig); - const launched = await this.debugService.startDebugging(folder, debugConfig, parentSession); + const debugSessionOption: DebugSessionOptions = { + parentSession: parentSession, + lifecycleManagedByParent: true, + }; + const launched = await this.debugService.startDebugging(folder, debugConfig, debugSessionOption); if (!launched) { - this.appShell.showErrorMessage(`Failed to launch debugger for child process ${processId}`).then(noop, noop); + showErrorMessage(l10n.t('Failed to launch debugger for child process {0}', debugConfig.subProcessId!)).then( + noop, + noop, + ); } } private getRelatedWorkspaceFolder( - config: AttachRequestArguments & DebugConfiguration + config: AttachRequestArguments & DebugConfiguration, ): WorkspaceFolder | undefined { const workspaceFolder = config.workspaceFolder; - if (!this.workspaceService.hasWorkspaceFolders || !workspaceFolder) { + + const hasWorkspaceFolders = (getWorkspaceFolders()?.length || 0) > 0; + if (!hasWorkspaceFolders || !workspaceFolder) { return; } - return this.workspaceService.workspaceFolders!.find((ws) => ws.uri.fsPath === workspaceFolder); + return getWorkspaceFolders()!.find((ws) => ws.uri.fsPath === workspaceFolder); } } diff --git a/src/client/debugger/extension/hooks/constants.ts b/src/client/debugger/extension/hooks/constants.ts index b2a2e0a8ec52..3bd0b657281e 100644 --- a/src/client/debugger/extension/hooks/constants.ts +++ b/src/client/debugger/extension/hooks/constants.ts @@ -6,5 +6,5 @@ export enum DebuggerEvents { // Event sent by PTVSD when a child process is launched and ready to be attached to for multi-proc debugging. PtvsdAttachToSubprocess = 'ptvsd_attach', - DebugpyAttachToSubprocess = 'debugpyAttach' + DebugpyAttachToSubprocess = 'debugpyAttach', } diff --git a/src/client/debugger/extension/hooks/eventHandlerDispatcher.ts b/src/client/debugger/extension/hooks/eventHandlerDispatcher.ts index 3aac7df6663f..7b1dd1516abd 100644 --- a/src/client/debugger/extension/hooks/eventHandlerDispatcher.ts +++ b/src/client/debugger/extension/hooks/eventHandlerDispatcher.ts @@ -12,22 +12,22 @@ export class DebugSessionEventDispatcher { constructor( @multiInject(IDebugSessionEventHandlers) private readonly eventHandlers: IDebugSessionEventHandlers[], @inject(IDebugService) private readonly debugService: IDebugService, - @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry + @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry, ) {} public registerEventHandlers() { this.disposables.push( this.debugService.onDidReceiveDebugSessionCustomEvent((e) => { this.eventHandlers.forEach((handler) => - handler.handleCustomEvent ? handler.handleCustomEvent(e).ignoreErrors() : undefined + handler.handleCustomEvent ? handler.handleCustomEvent(e).ignoreErrors() : undefined, ); - }) + }), ); this.disposables.push( this.debugService.onDidTerminateDebugSession((e) => { this.eventHandlers.forEach((handler) => - handler.handleTerminateEvent ? handler.handleTerminateEvent(e).ignoreErrors() : undefined + handler.handleTerminateEvent ? handler.handleTerminateEvent(e).ignoreErrors() : undefined, ); - }) + }), ); } } diff --git a/src/client/debugger/extension/serviceRegistry.ts b/src/client/debugger/extension/serviceRegistry.ts index 4e785a82f0cd..7734e87124cd 100644 --- a/src/client/debugger/extension/serviceRegistry.ts +++ b/src/client/debugger/extension/serviceRegistry.ts @@ -12,124 +12,59 @@ import { DebugSessionLoggingFactory } from './adapter/logging'; import { OutdatedDebuggerPromptFactory } from './adapter/outdatedDebuggerPrompt'; import { AttachProcessProviderFactory } from './attachQuickPick/factory'; import { IAttachProcessProviderFactory } from './attachQuickPick/types'; -import { DebuggerBanner } from './banner'; import { PythonDebugConfigurationService } from './configuration/debugConfigurationService'; -import { LaunchJsonCompletionProvider } from './configuration/launch.json/completionProvider'; -import { InterpreterPathCommand } from './configuration/launch.json/interpreterPathCommand'; -import { LaunchJsonUpdaterService } from './configuration/launch.json/updaterService'; -import { DjangoLaunchDebugConfigurationProvider } from './configuration/providers/djangoLaunch'; -import { FileLaunchDebugConfigurationProvider } from './configuration/providers/fileLaunch'; -import { FlaskLaunchDebugConfigurationProvider } from './configuration/providers/flaskLaunch'; -import { ModuleLaunchDebugConfigurationProvider } from './configuration/providers/moduleLaunch'; -import { PidAttachDebugConfigurationProvider } from './configuration/providers/pidAttach'; -import { DebugConfigurationProviderFactory } from './configuration/providers/providerFactory'; -import { PyramidLaunchDebugConfigurationProvider } from './configuration/providers/pyramidLaunch'; -import { RemoteAttachDebugConfigurationProvider } from './configuration/providers/remoteAttach'; import { AttachConfigurationResolver } from './configuration/resolvers/attach'; import { DebugEnvironmentVariablesHelper, IDebugEnvironmentVariablesService } from './configuration/resolvers/helper'; import { LaunchConfigurationResolver } from './configuration/resolvers/launch'; -import { IDebugConfigurationProviderFactory, IDebugConfigurationResolver } from './configuration/types'; +import { IDebugConfigurationResolver } from './configuration/types'; +import { DebugCommands } from './debugCommands'; import { ChildProcessAttachEventHandler } from './hooks/childProcessAttachHandler'; import { ChildProcessAttachService } from './hooks/childProcessAttachService'; import { IChildProcessAttachService, IDebugSessionEventHandlers } from './hooks/types'; import { - DebugConfigurationType, IDebugAdapterDescriptorFactory, - IDebugConfigurationProvider, IDebugConfigurationService, - IDebuggerBanner, IDebugSessionLoggingFactory, - IOutdatedDebuggerPromptFactory + IOutdatedDebuggerPromptFactory, } from './types'; -export function registerTypes(serviceManager: IServiceManager) { - serviceManager.addSingleton( - IExtensionSingleActivationService, - LaunchJsonCompletionProvider - ); - serviceManager.addSingleton( - IExtensionSingleActivationService, - InterpreterPathCommand - ); - serviceManager.addSingleton( - IExtensionSingleActivationService, - LaunchJsonUpdaterService - ); +export function registerTypes(serviceManager: IServiceManager): void { serviceManager.addSingleton( IDebugConfigurationService, - PythonDebugConfigurationService + PythonDebugConfigurationService, ); - serviceManager.addSingleton(IDebuggerBanner, DebuggerBanner); serviceManager.addSingleton(IChildProcessAttachService, ChildProcessAttachService); serviceManager.addSingleton(IDebugSessionEventHandlers, ChildProcessAttachEventHandler); serviceManager.addSingleton>( IDebugConfigurationResolver, LaunchConfigurationResolver, - 'launch' + 'launch', ); serviceManager.addSingleton>( IDebugConfigurationResolver, AttachConfigurationResolver, - 'attach' - ); - serviceManager.addSingleton( - IDebugConfigurationProviderFactory, - DebugConfigurationProviderFactory - ); - serviceManager.addSingleton( - IDebugConfigurationProvider, - FileLaunchDebugConfigurationProvider, - DebugConfigurationType.launchFile - ); - serviceManager.addSingleton( - IDebugConfigurationProvider, - DjangoLaunchDebugConfigurationProvider, - DebugConfigurationType.launchDjango - ); - serviceManager.addSingleton( - IDebugConfigurationProvider, - FlaskLaunchDebugConfigurationProvider, - DebugConfigurationType.launchFlask - ); - serviceManager.addSingleton( - IDebugConfigurationProvider, - RemoteAttachDebugConfigurationProvider, - DebugConfigurationType.remoteAttach - ); - serviceManager.addSingleton( - IDebugConfigurationProvider, - ModuleLaunchDebugConfigurationProvider, - DebugConfigurationType.launchModule - ); - serviceManager.addSingleton( - IDebugConfigurationProvider, - PyramidLaunchDebugConfigurationProvider, - DebugConfigurationType.launchPyramid - ); - serviceManager.addSingleton( - IDebugConfigurationProvider, - PidAttachDebugConfigurationProvider, - DebugConfigurationType.pidAttach + 'attach', ); serviceManager.addSingleton( IDebugEnvironmentVariablesService, - DebugEnvironmentVariablesHelper + DebugEnvironmentVariablesHelper, ); serviceManager.addSingleton( IExtensionSingleActivationService, - DebugAdapterActivator + DebugAdapterActivator, ); serviceManager.addSingleton( IDebugAdapterDescriptorFactory, - DebugAdapterDescriptorFactory + DebugAdapterDescriptorFactory, ); serviceManager.addSingleton(IDebugSessionLoggingFactory, DebugSessionLoggingFactory); serviceManager.addSingleton( IOutdatedDebuggerPromptFactory, - OutdatedDebuggerPromptFactory + OutdatedDebuggerPromptFactory, ); serviceManager.addSingleton( IAttachProcessProviderFactory, - AttachProcessProviderFactory + AttachProcessProviderFactory, ); + serviceManager.addSingleton(IExtensionSingleActivationService, DebugCommands); } diff --git a/src/client/debugger/extension/types.ts b/src/client/debugger/extension/types.ts index b5e6826b3b7e..4a8f35e2b808 100644 --- a/src/client/debugger/extension/types.ts +++ b/src/client/debugger/extension/types.ts @@ -3,53 +3,10 @@ 'use strict'; -import { Readable } from 'stream'; -import { - CancellationToken, - DebugAdapterDescriptorFactory, - DebugAdapterTrackerFactory, - DebugConfigurationProvider, - Disposable, - WorkspaceFolder -} from 'vscode'; - -import { InputStep, MultiStepInput } from '../../common/utils/multiStepInput'; -import { DebugConfigurationArguments } from '../types'; +import { DebugAdapterDescriptorFactory, DebugAdapterTrackerFactory, DebugConfigurationProvider } from 'vscode'; export const IDebugConfigurationService = Symbol('IDebugConfigurationService'); export interface IDebugConfigurationService extends DebugConfigurationProvider {} -export const IDebuggerBanner = Symbol('IDebuggerBanner'); -export interface IDebuggerBanner { - initialize(): void; -} - -export const IDebugConfigurationProvider = Symbol('IDebugConfigurationProvider'); -export type DebugConfigurationState = { - config: Partial; - folder?: WorkspaceFolder; - token?: CancellationToken; -}; -export interface IDebugConfigurationProvider { - buildConfiguration( - input: MultiStepInput, - state: DebugConfigurationState - ): Promise | void>; -} - -export enum DebugConfigurationType { - launchFile = 'launchFile', - remoteAttach = 'remoteAttach', - launchDjango = 'launchDjango', - launchFlask = 'launchFlask', - launchModule = 'launchModule', - launchPyramid = 'launchPyramid', - pidAttach = 'pidAttach' -} - -export enum PythonPathSource { - launchJson = 'launch.json', - settingsJson = 'settings.json' -} export const IDebugAdapterDescriptorFactory = Symbol('IDebugAdapterDescriptorFactory'); export interface IDebugAdapterDescriptorFactory extends DebugAdapterDescriptorFactory {} @@ -62,9 +19,7 @@ export const IOutdatedDebuggerPromptFactory = Symbol('IOutdatedDebuggerPromptFac export interface IOutdatedDebuggerPromptFactory extends DebugAdapterTrackerFactory {} -export const IProtocolParser = Symbol('IProtocolParser'); -export interface IProtocolParser extends Disposable { - connect(stream: Readable): void; - once(event: string | symbol, listener: Function): this; - on(event: string | symbol, listener: Function): this; +export enum PythonPathSource { + launchJson = 'launch.json', + settingsJson = 'settings.json', } diff --git a/src/client/debugger/pythonDebugger.ts b/src/client/debugger/pythonDebugger.ts new file mode 100644 index 000000000000..3450e95f3cee --- /dev/null +++ b/src/client/debugger/pythonDebugger.ts @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { extensions } from 'vscode'; + +interface IPythonDebuggerExtensionApi { + debug: { + getDebuggerPackagePath(): Promise; + }; +} + +async function activateExtension() { + const extension = extensions.getExtension('ms-python.debugpy'); + if (extension) { + if (!extension.isActive) { + await extension.activate(); + } + } + return extension; +} + +async function getPythonDebuggerExtensionAPI(): Promise { + const extension = await activateExtension(); + return extension?.exports as IPythonDebuggerExtensionApi; +} + +export async function getDebugpyPath(): Promise { + const api = await getPythonDebuggerExtensionAPI(); + return api?.debug.getDebuggerPackagePath() ?? ''; +} diff --git a/src/client/debugger/types.ts b/src/client/debugger/types.ts index 68ad0e49a149..1422f1aa75ab 100644 --- a/src/client/debugger/types.ts +++ b/src/client/debugger/types.ts @@ -5,13 +5,12 @@ import { DebugConfiguration } from 'vscode'; import { DebugProtocol } from 'vscode-debugprotocol/lib/debugProtocol'; -import { DebuggerTypeName } from './constants'; +import { DebuggerTypeName, PythonDebuggerTypeName } from './constants'; export enum DebugOptions { RedirectOutput = 'RedirectOutput', Django = 'Django', Jinja = 'Jinja', - DebugStdLib = 'DebugStdLib', Sudo = 'Sudo', Pyramid = 'Pyramid', FixFilePathCase = 'FixFilePathCase', @@ -19,18 +18,30 @@ export enum DebugOptions { UnixClient = 'UnixClient', StopOnEntry = 'StopOnEntry', ShowReturnValue = 'ShowReturnValue', - SubProcess = 'Multiprocess' + SubProcess = 'Multiprocess', +} + +export enum DebugPurpose { + DebugTest = 'debug-test', + DebugInTerminal = 'debug-in-terminal', } export type PathMapping = { localRoot: string; remoteRoot: string; }; -export type Connection = { +type Connection = { host?: string; port?: number; }; +export interface IAutomaticCodeReload { + enable?: boolean; + exclude?: string[]; + include?: string[]; + pollingInterval?: number; +} + interface ICommonDebugArguments { redirectOutput?: boolean; django?: boolean; @@ -47,8 +58,10 @@ interface ICommonDebugArguments { subProcess?: boolean; // An absolute path to local directory with source. pathMappings?: PathMapping[]; + clientOS?: 'windows' | 'unix'; } -export interface IKnownAttachDebugArguments extends ICommonDebugArguments { + +interface IKnownAttachDebugArguments extends ICommonDebugArguments { workspaceFolder?: string; customDebugger?: boolean; // localRoot and remoteRoot are deprecated (replaced by pathMappings). @@ -63,43 +76,62 @@ export interface IKnownAttachDebugArguments extends ICommonDebugArguments { listen?: Connection; } -export interface IKnownLaunchRequestArguments extends ICommonDebugArguments { +interface IKnownLaunchRequestArguments extends ICommonDebugArguments { sudo?: boolean; pyramid?: boolean; workspaceFolder?: string; // An absolute path to the program to debug. module?: string; program?: string; - pythonPath: string; + python?: string; // Automatically stop target after launch. If not specified, target does not stop. stopOnEntry?: boolean; - args: string[]; + args?: string[]; cwd?: string; debugOptions?: DebugOptions[]; env?: Record; - envFile: string; + envFile?: string; console?: ConsoleType; - // Internal field used to set custom python debug adapter (for testing) + // The following are all internal properties that are not publicly documented or + // exposed in launch.json schema for the extension. + + // Python interpreter used by the extension to spawn the debug adapter. + debugAdapterPython?: string; + + // Debug adapter to use in lieu of the one bundled with the extension. + // This must be a full path that is executable with "python "; + // for debugpy, this is ".../src/debugpy/adapter". debugAdapterPath?: string; + + // Python interpreter used by the debug adapter to spawn the debug launcher. + debugLauncherPython?: string; + + // Legacy interpreter setting. Equivalent to setting "python", "debugAdapterPython", + // and "debugLauncherPython" all at once. + pythonPath?: string; + + // Configures automatic code reloading. + autoReload?: IAutomaticCodeReload; + + // Defines where the purpose where the config should be used. + purpose?: DebugPurpose[]; } -// tslint:disable-next-line:interface-name + export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments, IKnownLaunchRequestArguments, DebugConfiguration { - type: typeof DebuggerTypeName; + type: typeof DebuggerTypeName | typeof PythonDebuggerTypeName; } -// tslint:disable-next-line:interface-name export interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments, IKnownAttachDebugArguments, DebugConfiguration { - type: typeof DebuggerTypeName; + type: typeof DebuggerTypeName | typeof PythonDebuggerTypeName; } -// tslint:disable-next-line:interface-name export interface DebugConfigurationArguments extends LaunchRequestArguments, AttachRequestArguments {} export type ConsoleType = 'internalConsole' | 'integratedTerminal' | 'externalTerminal'; diff --git a/src/client/deprecatedProposedApi.ts b/src/client/deprecatedProposedApi.ts new file mode 100644 index 000000000000..d0003c895517 --- /dev/null +++ b/src/client/deprecatedProposedApi.ts @@ -0,0 +1,166 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { ConfigurationTarget, EventEmitter } from 'vscode'; +import { arePathsSame } from './common/platform/fs-paths'; +import { IExtensions, IInterpreterPathService, Resource } from './common/types'; +import { + EnvironmentsChangedParams, + ActiveEnvironmentChangedParams, + EnvironmentDetailsOptions, + EnvironmentDetails, + DeprecatedProposedAPI, +} from './deprecatedProposedApiTypes'; +import { IInterpreterService } from './interpreter/contracts'; +import { IServiceContainer } from './ioc/types'; +import { traceVerbose, traceWarn } from './logging'; +import { PythonEnvInfo } from './pythonEnvironments/base/info'; +import { getEnvPath } from './pythonEnvironments/base/info/env'; +import { GetRefreshEnvironmentsOptions, IDiscoveryAPI } from './pythonEnvironments/base/locator'; +import { sendTelemetryEvent } from './telemetry'; +import { EventName } from './telemetry/constants'; + +const onDidInterpretersChangedEvent = new EventEmitter(); +/** + * @deprecated Will be removed soon. + */ +export function reportInterpretersChanged(e: EnvironmentsChangedParams[]): void { + onDidInterpretersChangedEvent.fire(e); +} + +const onDidActiveInterpreterChangedEvent = new EventEmitter(); +/** + * @deprecated Will be removed soon. + */ +export function reportActiveInterpreterChangedDeprecated(e: ActiveEnvironmentChangedParams): void { + onDidActiveInterpreterChangedEvent.fire(e); +} + +function getVersionString(env: PythonEnvInfo): string[] { + const ver = [`${env.version.major}`, `${env.version.minor}`, `${env.version.micro}`]; + if (env.version.release) { + ver.push(`${env.version.release}`); + if (env.version.sysVersion) { + ver.push(`${env.version.release}`); + } + } + return ver; +} + +/** + * Returns whether the path provided matches the environment. + * @param path Path to environment folder or path to interpreter that uniquely identifies an environment. + * @param env Environment to match with. + */ +function isEnvSame(path: string, env: PythonEnvInfo) { + return arePathsSame(path, env.location) || arePathsSame(path, env.executable.filename); +} + +export function buildDeprecatedProposedApi( + discoveryApi: IDiscoveryAPI, + serviceContainer: IServiceContainer, +): DeprecatedProposedAPI { + const interpreterPathService = serviceContainer.get(IInterpreterPathService); + const interpreterService = serviceContainer.get(IInterpreterService); + const extensions = serviceContainer.get(IExtensions); + const warningLogged = new Set(); + function sendApiTelemetry(apiName: string, warnLog = true) { + extensions + .determineExtensionFromCallStack() + .then((info) => { + sendTelemetryEvent(EventName.PYTHON_ENVIRONMENTS_API, undefined, { + apiName, + extensionId: info.extensionId, + }); + traceVerbose(`Extension ${info.extensionId} accessed ${apiName}`); + if (warnLog && !warningLogged.has(info.extensionId)) { + traceWarn( + `${info.extensionId} extension is using deprecated python APIs which will be removed soon.`, + ); + warningLogged.add(info.extensionId); + } + }) + .ignoreErrors(); + } + + const proposed: DeprecatedProposedAPI = { + environment: { + async getExecutionDetails(resource?: Resource) { + sendApiTelemetry('deprecated.getExecutionDetails'); + const env = await interpreterService.getActiveInterpreter(resource); + return env ? { execCommand: [env.path] } : { execCommand: undefined }; + }, + async getActiveEnvironmentPath(resource?: Resource) { + sendApiTelemetry('deprecated.getActiveEnvironmentPath'); + const env = await interpreterService.getActiveInterpreter(resource); + if (!env) { + return undefined; + } + return getEnvPath(env.path, env.envPath); + }, + async getEnvironmentDetails( + path: string, + options?: EnvironmentDetailsOptions, + ): Promise { + sendApiTelemetry('deprecated.getEnvironmentDetails'); + let env: PythonEnvInfo | undefined; + if (options?.useCache) { + env = discoveryApi.getEnvs().find((v) => isEnvSame(path, v)); + } + if (!env) { + env = await discoveryApi.resolveEnv(path); + if (!env) { + return undefined; + } + } + return { + interpreterPath: env.executable.filename, + envFolderPath: env.location.length ? env.location : undefined, + version: getVersionString(env), + environmentType: [env.kind], + metadata: { + sysPrefix: env.executable.sysPrefix, + bitness: env.arch, + project: env.searchLocation, + }, + }; + }, + getEnvironmentPaths() { + sendApiTelemetry('deprecated.getEnvironmentPaths'); + const paths = discoveryApi.getEnvs().map((e) => getEnvPath(e.executable.filename, e.location)); + return Promise.resolve(paths); + }, + setActiveEnvironment(path: string, resource?: Resource): Promise { + sendApiTelemetry('deprecated.setActiveEnvironment'); + return interpreterPathService.update(resource, ConfigurationTarget.WorkspaceFolder, path); + }, + async refreshEnvironment() { + sendApiTelemetry('deprecated.refreshEnvironment'); + await discoveryApi.triggerRefresh(); + const paths = discoveryApi.getEnvs().map((e) => getEnvPath(e.executable.filename, e.location)); + return Promise.resolve(paths); + }, + getRefreshPromise(options?: GetRefreshEnvironmentsOptions): Promise | undefined { + sendApiTelemetry('deprecated.getRefreshPromise'); + return discoveryApi.getRefreshPromise(options); + }, + get onDidChangeExecutionDetails() { + sendApiTelemetry('deprecated.onDidChangeExecutionDetails', false); + return interpreterService.onDidChangeInterpreterConfiguration; + }, + get onDidEnvironmentsChanged() { + sendApiTelemetry('deprecated.onDidEnvironmentsChanged', false); + return onDidInterpretersChangedEvent.event; + }, + get onDidActiveEnvironmentChanged() { + sendApiTelemetry('deprecated.onDidActiveEnvironmentChanged', false); + return onDidActiveInterpreterChangedEvent.event; + }, + get onRefreshProgress() { + sendApiTelemetry('deprecated.onRefreshProgress', false); + return discoveryApi.onProgress; + }, + }, + }; + return proposed; +} diff --git a/src/client/deprecatedProposedApiTypes.ts b/src/client/deprecatedProposedApiTypes.ts new file mode 100644 index 000000000000..eb76d61dc907 --- /dev/null +++ b/src/client/deprecatedProposedApiTypes.ts @@ -0,0 +1,146 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { Uri, Event } from 'vscode'; +import { PythonEnvKind, EnvPathType } from './pythonEnvironments/base/info'; +import { ProgressNotificationEvent, GetRefreshEnvironmentsOptions } from './pythonEnvironments/base/locator'; +import { Resource } from './api/types'; + +export interface EnvironmentDetailsOptions { + useCache: boolean; +} + +export interface EnvironmentDetails { + interpreterPath: string; + envFolderPath?: string; + version: string[]; + environmentType: PythonEnvKind[]; + metadata: Record; +} + +export interface EnvironmentsChangedParams { + /** + * Path to environment folder or path to interpreter that uniquely identifies an environment. + * Virtual environments lacking an interpreter are identified by environment folder paths, + * whereas other envs can be identified using interpreter path. + */ + path?: string; + type: 'add' | 'remove' | 'update' | 'clear-all'; +} + +export interface ActiveEnvironmentChangedParams { + /** + * Path to environment folder or path to interpreter that uniquely identifies an environment. + * Virtual environments lacking an interpreter are identified by environment folder paths, + * whereas other envs can be identified using interpreter path. + */ + path: string; + resource?: Uri; +} + +/** + * @deprecated Use {@link ProposedExtensionAPI} instead. + */ +export interface DeprecatedProposedAPI { + /** + * @deprecated Use {@link ProposedExtensionAPI.environments} instead. This will soon be removed. + */ + environment: { + /** + * An event that is emitted when execution details (for a resource) change. For instance, when interpreter configuration changes. + */ + readonly onDidChangeExecutionDetails: Event; + /** + * Returns all the details the consumer needs to execute code within the selected environment, + * corresponding to the specified resource taking into account any workspace-specific settings + * for the workspace to which this resource belongs. + * @param {Resource} [resource] A resource for which the setting is asked for. + * * When no resource is provided, the setting scoped to the first workspace folder is returned. + * * If no folder is present, it returns the global setting. + */ + getExecutionDetails( + resource?: Resource, + ): Promise<{ + /** + * E.g of execution commands returned could be, + * * `['']` + * * `['']` + * * `['conda', 'run', 'python']` which is used to run from within Conda environments. + * or something similar for some other Python environments. + * + * @type {(string[] | undefined)} When return value is `undefined`, it means no interpreter is set. + * Otherwise, join the items returned using space to construct the full execution command. + */ + execCommand: string[] | undefined; + }>; + /** + * Returns the path to the python binary selected by the user or as in the settings. + * This is just the path to the python binary, this does not provide activation or any + * other activation command. The `resource` if provided will be used to determine the + * python binary in a multi-root scenario. If resource is `undefined` then the API + * returns what ever is set for the workspace. + * @param resource : Uri of a file or workspace + */ + getActiveEnvironmentPath(resource?: Resource): Promise; + /** + * Returns details for the given interpreter. Details such as absolute interpreter path, + * version, type (conda, pyenv, etc). Metadata such as `sysPrefix` can be found under + * metadata field. + * @param path : Full path to environment folder or interpreter whose details you need. + * @param options : [optional] + * * useCache : When true, cache is checked first for any data, returns even if there + * is partial data. + */ + getEnvironmentDetails( + path: string, + options?: EnvironmentDetailsOptions, + ): Promise; + /** + * Returns paths to environments that uniquely identifies an environment found by the extension + * at the time of calling. This API will *not* trigger a refresh. If a refresh is going on it + * will *not* wait for the refresh to finish. This will return what is known so far. To get + * complete list `await` on promise returned by `getRefreshPromise()`. + * + * Virtual environments lacking an interpreter are identified by environment folder paths, + * whereas other envs can be identified using interpreter path. + */ + getEnvironmentPaths(): Promise; + /** + * Sets the active environment path for the python extension for the resource. Configuration target + * will always be the workspace folder. + * @param path : Full path to environment folder or interpreter to set. + * @param resource : [optional] Uri of a file ro workspace to scope to a particular workspace + * folder. + */ + setActiveEnvironment(path: string, resource?: Resource): Promise; + /** + * This API will re-trigger environment discovery. Extensions can wait on the returned + * promise to get the updated environment list. If there is a refresh already going on + * then it returns the promise for that refresh. + * @param options : [optional] + * * clearCache : When true, this will clear the cache before environment refresh + * is triggered. + */ + refreshEnvironment(): Promise; + /** + * Tracks discovery progress for current list of known environments, i.e when it starts, finishes or any other relevant + * stage. Note the progress for a particular query is currently not tracked or reported, this only indicates progress of + * the entire collection. + */ + readonly onRefreshProgress: Event; + /** + * Returns a promise for the ongoing refresh. Returns `undefined` if there are no active + * refreshes going on. + */ + getRefreshPromise(options?: GetRefreshEnvironmentsOptions): Promise | undefined; + /** + * This event is triggered when the known environment list changes, like when a environment + * is found, existing environment is removed, or some details changed on an environment. + */ + onDidEnvironmentsChanged: Event; + /** + * @deprecated Use {@link ProposedExtensionAPI.environments} `onDidChangeActiveEnvironmentPath` instead. This will soon be removed. + */ + onDidActiveEnvironmentChanged: Event; + }; +} diff --git a/src/client/envExt/api.internal.ts b/src/client/envExt/api.internal.ts new file mode 100644 index 000000000000..5edfb712072e --- /dev/null +++ b/src/client/envExt/api.internal.ts @@ -0,0 +1,179 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { EventEmitter, Terminal, Uri, Disposable } from 'vscode'; +import { getExtension } from '../common/vscodeApis/extensionsApi'; +import { + GetEnvironmentScope, + PythonBackgroundRunOptions, + PythonEnvironment, + PythonEnvironmentApi, + PythonProcess, + RefreshEnvironmentsScope, + DidChangeEnvironmentEventArgs, +} from './types'; +import { executeCommand } from '../common/vscodeApis/commandApis'; +import { getConfiguration, getWorkspaceFolders } from '../common/vscodeApis/workspaceApis'; +import { traceError, traceLog } from '../logging'; +import { Interpreters } from '../common/utils/localize'; + +export const ENVS_EXTENSION_ID = 'ms-python.vscode-python-envs'; + +export function isEnvExtensionInstalled(): boolean { + return !!getExtension(ENVS_EXTENSION_ID); +} + +/** + * Returns true if the Python Environments extension is installed and not explicitly + * disabled by the user. Mirrors the envs extension's own activation logic: it + * deactivates only when `python.useEnvironmentsExtension` is explicitly set to false + * at the global, workspace, or workspace-folder level. + */ +export function shouldEnvExtHandleActivation(): boolean { + if (!isEnvExtensionInstalled()) { + return false; + } + const config = getConfiguration('python'); + const inspection = config.inspect('useEnvironmentsExtension'); + if (inspection?.globalValue === false || inspection?.workspaceValue === false) { + return false; + } + // The envs extension also checks folder-scoped settings in multi-root workspaces. + // Any single folder with the setting set to false causes the envs extension to + // deactivate entirely (window-wide), so we must mirror that here. + const workspaceFolders = getWorkspaceFolders(); + if (workspaceFolders) { + for (const folder of workspaceFolders) { + const folderConfig = getConfiguration('python', folder.uri); + const folderInspection = folderConfig.inspect('useEnvironmentsExtension'); + if (folderInspection?.workspaceFolderValue === false) { + return false; + } + } + } + return true; +} + +let _useExt: boolean | undefined; +export function useEnvExtension(): boolean { + if (_useExt !== undefined) { + return _useExt; + } + const config = getConfiguration('python'); + const inExpSetting = config?.get('useEnvironmentsExtension', false) ?? false; + // If extension is installed and in experiment, then use it. + _useExt = !!getExtension(ENVS_EXTENSION_ID) && inExpSetting; + return _useExt; +} + +const onDidChangeEnvironmentEnvExtEmitter: EventEmitter = new EventEmitter< + DidChangeEnvironmentEventArgs +>(); +export function onDidChangeEnvironmentEnvExt( + listener: (e: DidChangeEnvironmentEventArgs) => unknown, + thisArgs?: unknown, + disposables?: Disposable[], +): Disposable { + return onDidChangeEnvironmentEnvExtEmitter.event(listener, thisArgs, disposables); +} + +let _extApi: PythonEnvironmentApi | undefined; +export async function getEnvExtApi(): Promise { + if (_extApi) { + return _extApi; + } + const extension = getExtension(ENVS_EXTENSION_ID); + if (!extension) { + traceError(Interpreters.envExtActivationFailed); + throw new Error('Python Environments extension not found.'); + } + if (!extension?.isActive) { + try { + await extension.activate(); + } catch (ex) { + traceError(Interpreters.envExtActivationFailed, ex); + throw ex; + } + } + + traceLog(Interpreters.envExtDiscoveryAttribution); + + _extApi = extension.exports as PythonEnvironmentApi; + _extApi.onDidChangeEnvironment((e) => { + onDidChangeEnvironmentEnvExtEmitter.fire(e); + }); + + return _extApi; +} + +export async function runInBackground( + environment: PythonEnvironment, + options: PythonBackgroundRunOptions, +): Promise { + const envExtApi = await getEnvExtApi(); + return envExtApi.runInBackground(environment, options); +} + +export async function getEnvironment(scope: GetEnvironmentScope): Promise { + const envExtApi = await getEnvExtApi(); + const env = await envExtApi.getEnvironment(scope); + if (!env) { + traceLog(Interpreters.envExtNoActiveEnvironment); + } + return env; +} + +export async function resolveEnvironment(pythonPath: string): Promise { + const envExtApi = await getEnvExtApi(); + return envExtApi.resolveEnvironment(Uri.file(pythonPath)); +} + +export async function refreshEnvironments(scope: RefreshEnvironmentsScope): Promise { + const envExtApi = await getEnvExtApi(); + return envExtApi.refreshEnvironments(scope); +} + +export async function runInTerminal( + resource: Uri | undefined, + args?: string[], + cwd?: string | Uri, + show?: boolean, +): Promise { + const envExtApi = await getEnvExtApi(); + const env = await getEnvironment(resource); + const project = resource ? envExtApi.getPythonProject(resource) : undefined; + if (env && resource) { + return envExtApi.runInTerminal(env, { + cwd: cwd ?? project?.uri ?? process.cwd(), + args, + show, + }); + } + throw new Error('Invalid arguments to run in terminal'); +} + +export async function runInDedicatedTerminal( + resource: Uri | undefined, + args?: string[], + cwd?: string | Uri, + show?: boolean, +): Promise { + const envExtApi = await getEnvExtApi(); + const env = await getEnvironment(resource); + const project = resource ? envExtApi.getPythonProject(resource) : undefined; + if (env) { + return envExtApi.runInDedicatedTerminal(resource ?? 'global', env, { + cwd: cwd ?? project?.uri ?? process.cwd(), + args, + show, + }); + } + throw new Error('Invalid arguments to run in dedicated terminal'); +} + +export async function clearCache(): Promise { + const envExtApi = await getEnvExtApi(); + if (envExtApi) { + await executeCommand('python-envs.clearCache'); + } +} diff --git a/src/client/envExt/api.legacy.ts b/src/client/envExt/api.legacy.ts new file mode 100644 index 000000000000..6f2e60774033 --- /dev/null +++ b/src/client/envExt/api.legacy.ts @@ -0,0 +1,171 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { Terminal, Uri } from 'vscode'; +import { getEnvExtApi, getEnvironment } from './api.internal'; +import { EnvironmentType, PythonEnvironment as PythonEnvironmentLegacy } from '../pythonEnvironments/info'; +import { PythonEnvironment, PythonTerminalCreateOptions } from './types'; +import { Architecture } from '../common/utils/platform'; +import { parseVersion } from '../pythonEnvironments/base/info/pythonVersion'; +import { PythonEnvType } from '../pythonEnvironments/base/info'; +import { traceError } from '../logging'; +import { reportActiveInterpreterChanged } from '../environmentApi'; +import { getWorkspaceFolder, getWorkspaceFolders } from '../common/vscodeApis/workspaceApis'; + +function toEnvironmentType(pythonEnv: PythonEnvironment): EnvironmentType { + if (pythonEnv.envId.managerId.toLowerCase().endsWith('system')) { + return EnvironmentType.System; + } + if (pythonEnv.envId.managerId.toLowerCase().endsWith('venv')) { + return EnvironmentType.Venv; + } + if (pythonEnv.envId.managerId.toLowerCase().endsWith('virtualenv')) { + return EnvironmentType.VirtualEnv; + } + if (pythonEnv.envId.managerId.toLowerCase().endsWith('conda')) { + return EnvironmentType.Conda; + } + if (pythonEnv.envId.managerId.toLowerCase().endsWith('pipenv')) { + return EnvironmentType.Pipenv; + } + if (pythonEnv.envId.managerId.toLowerCase().endsWith('poetry')) { + return EnvironmentType.Poetry; + } + if (pythonEnv.envId.managerId.toLowerCase().endsWith('pyenv')) { + return EnvironmentType.Pyenv; + } + if (pythonEnv.envId.managerId.toLowerCase().endsWith('hatch')) { + return EnvironmentType.Hatch; + } + if (pythonEnv.envId.managerId.toLowerCase().endsWith('pixi')) { + return EnvironmentType.Pixi; + } + if (pythonEnv.envId.managerId.toLowerCase().endsWith('virtualenvwrapper')) { + return EnvironmentType.VirtualEnvWrapper; + } + if (pythonEnv.envId.managerId.toLowerCase().endsWith('activestate')) { + return EnvironmentType.ActiveState; + } + return EnvironmentType.Unknown; +} + +function getEnvType(kind: EnvironmentType): PythonEnvType | undefined { + switch (kind) { + case EnvironmentType.Pipenv: + case EnvironmentType.VirtualEnv: + case EnvironmentType.Pyenv: + case EnvironmentType.Venv: + case EnvironmentType.Poetry: + case EnvironmentType.Hatch: + case EnvironmentType.Pixi: + case EnvironmentType.VirtualEnvWrapper: + case EnvironmentType.ActiveState: + return PythonEnvType.Virtual; + + case EnvironmentType.Conda: + return PythonEnvType.Conda; + + case EnvironmentType.MicrosoftStore: + case EnvironmentType.Global: + case EnvironmentType.System: + default: + return undefined; + } +} + +function toLegacyType(env: PythonEnvironment): PythonEnvironmentLegacy { + const ver = parseVersion(env.version); + const envType = toEnvironmentType(env); + return { + id: env.execInfo.run.executable, + displayName: env.displayName, + detailedDisplayName: env.name, + envType, + envPath: env.sysPrefix, + type: getEnvType(envType), + path: env.execInfo.run.executable, + version: { + raw: env.version, + major: ver.major, + minor: ver.minor, + patch: ver.micro, + build: [], + prerelease: [], + }, + sysVersion: env.version, + architecture: Architecture.x64, + sysPrefix: env.sysPrefix, + }; +} + +const previousEnvMap = new Map(); +export async function getActiveInterpreterLegacy(resource?: Uri): Promise { + const api = await getEnvExtApi(); + const uri = resource ? api.getPythonProject(resource)?.uri : undefined; + + const pythonEnv = await getEnvironment(resource); + const oldEnv = previousEnvMap.get(uri?.fsPath || ''); + const newEnv = pythonEnv ? toLegacyType(pythonEnv) : undefined; + + const folders = getWorkspaceFolders() ?? []; + const shouldReport = + (folders.length === 0 && resource === undefined) || (folders.length > 0 && resource !== undefined); + if (shouldReport && newEnv && oldEnv?.envId.id !== pythonEnv?.envId.id) { + reportActiveInterpreterChanged({ + resource: getWorkspaceFolder(resource), + path: newEnv.path, + }); + previousEnvMap.set(uri?.fsPath || '', pythonEnv); + } + return pythonEnv ? toLegacyType(pythonEnv) : undefined; +} + +export async function setInterpreterLegacy(pythonPath: string, uri: Uri | undefined): Promise { + const api = await getEnvExtApi(); + const pythonEnv = await api.resolveEnvironment(Uri.file(pythonPath)); + if (!pythonEnv) { + traceError(`EnvExt: Failed to resolve environment for ${pythonPath}`); + return; + } + await api.setEnvironment(uri, pythonEnv); +} + +export async function resetInterpreterLegacy(uri: Uri | undefined): Promise { + const api = await getEnvExtApi(); + await api.setEnvironment(uri, undefined); +} + +export async function ensureTerminalLegacy( + resource: Uri | undefined, + options?: PythonTerminalCreateOptions, +): Promise { + const api = await getEnvExtApi(); + const pythonEnv = await api.getEnvironment(resource); + const project = resource ? api.getPythonProject(resource) : undefined; + + if (pythonEnv && project) { + const fixedOptions = options ? { ...options } : { cwd: project.uri }; + const terminal = await api.createTerminal(pythonEnv, fixedOptions); + return terminal; + } + traceError('ensureTerminalLegacy - Did not return terminal successfully.'); + traceError( + 'ensureTerminalLegacy - pythonEnv:', + pythonEnv + ? `id=${pythonEnv.envId.id}, managerId=${pythonEnv.envId.managerId}, name=${pythonEnv.name}, version=${pythonEnv.version}, executable=${pythonEnv.execInfo.run.executable}` + : 'undefined', + ); + traceError( + 'ensureTerminalLegacy - project:', + project ? `name=${project.name}, uri=${project.uri.toString()}` : 'undefined', + ); + traceError( + 'ensureTerminalLegacy - options:', + options + ? `name=${options.name}, cwd=${options.cwd?.toString()}, hideFromUser=${options.hideFromUser}` + : 'undefined', + ); + traceError('ensureTerminalLegacy - resource:', resource?.toString() || 'undefined'); + + throw new Error('Invalid arguments to create terminal'); +} diff --git a/src/client/envExt/envExtApi.ts b/src/client/envExt/envExtApi.ts new file mode 100644 index 000000000000..34f42f0d6954 --- /dev/null +++ b/src/client/envExt/envExtApi.ts @@ -0,0 +1,345 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +/* eslint-disable class-methods-use-this */ + +import * as path from 'path'; +import { Event, EventEmitter, Disposable, Uri } from 'vscode'; +import { PythonEnvInfo, PythonEnvKind, PythonEnvType, PythonVersion } from '../pythonEnvironments/base/info'; +import { + GetRefreshEnvironmentsOptions, + IDiscoveryAPI, + ProgressNotificationEvent, + ProgressReportStage, + PythonLocatorQuery, + TriggerRefreshOptions, +} from '../pythonEnvironments/base/locator'; +import { PythonEnvCollectionChangedEvent } from '../pythonEnvironments/base/watcher'; +import { getEnvExtApi } from './api.internal'; +import { createDeferred, Deferred } from '../common/utils/async'; +import { StopWatch } from '../common/utils/stopWatch'; +import { traceError, traceLog, traceWarn } from '../logging'; +import { + DidChangeEnvironmentsEventArgs, + EnvironmentChangeKind, + PythonEnvironment, + PythonEnvironmentApi, +} from './types'; +import { FileChangeType } from '../common/platform/fileSystemWatcher'; +import { Architecture, isWindows } from '../common/utils/platform'; +import { parseVersion } from '../pythonEnvironments/base/info/pythonVersion'; +import { Interpreters } from '../common/utils/localize'; + +function getKind(pythonEnv: PythonEnvironment): PythonEnvKind { + if (pythonEnv.envId.managerId.toLowerCase().endsWith('system')) { + return PythonEnvKind.System; + } + if (pythonEnv.envId.managerId.toLowerCase().endsWith('conda')) { + return PythonEnvKind.Conda; + } + if (pythonEnv.envId.managerId.toLowerCase().endsWith('venv')) { + return PythonEnvKind.Venv; + } + if (pythonEnv.envId.managerId.toLowerCase().endsWith('virtualenv')) { + return PythonEnvKind.VirtualEnv; + } + if (pythonEnv.envId.managerId.toLowerCase().endsWith('virtualenvwrapper')) { + return PythonEnvKind.VirtualEnvWrapper; + } + if (pythonEnv.envId.managerId.toLowerCase().endsWith('pyenv')) { + return PythonEnvKind.Pyenv; + } + if (pythonEnv.envId.managerId.toLowerCase().endsWith('pipenv')) { + return PythonEnvKind.Pipenv; + } + if (pythonEnv.envId.managerId.toLowerCase().endsWith('poetry')) { + return PythonEnvKind.Poetry; + } + if (pythonEnv.envId.managerId.toLowerCase().endsWith('pixi')) { + return PythonEnvKind.Pixi; + } + if (pythonEnv.envId.managerId.toLowerCase().endsWith('hatch')) { + return PythonEnvKind.Hatch; + } + if (pythonEnv.envId.managerId.toLowerCase().endsWith('activestate')) { + return PythonEnvKind.ActiveState; + } + + return PythonEnvKind.Unknown; +} + +function makeExecutablePath(prefix?: string): string { + if (!prefix) { + return process.platform === 'win32' ? 'python.exe' : 'python'; + } + return process.platform === 'win32' ? path.join(prefix, 'python.exe') : path.join(prefix, 'python'); +} + +function getExecutable(pythonEnv: PythonEnvironment): string { + if (pythonEnv.execInfo?.run?.executable) { + return pythonEnv.execInfo?.run?.executable; + } + + const basename = path.basename(pythonEnv.environmentPath.fsPath).toLowerCase(); + if (isWindows() && basename.startsWith('python') && basename.endsWith('.exe')) { + return pythonEnv.environmentPath.fsPath; + } + + if (!isWindows() && basename.startsWith('python')) { + return pythonEnv.environmentPath.fsPath; + } + + return makeExecutablePath(pythonEnv.sysPrefix); +} + +function getLocation(pythonEnv: PythonEnvironment): string { + if (pythonEnv.envId.managerId.toLowerCase().endsWith('conda')) { + return pythonEnv.sysPrefix; + } + + return pythonEnv.environmentPath.fsPath; +} + +function getEnvType(kind: PythonEnvKind): PythonEnvType | undefined { + switch (kind) { + case PythonEnvKind.Poetry: + case PythonEnvKind.Pyenv: + case PythonEnvKind.VirtualEnv: + case PythonEnvKind.Venv: + case PythonEnvKind.VirtualEnvWrapper: + case PythonEnvKind.OtherVirtual: + case PythonEnvKind.Pipenv: + case PythonEnvKind.ActiveState: + case PythonEnvKind.Hatch: + case PythonEnvKind.Pixi: + return PythonEnvType.Virtual; + + case PythonEnvKind.Conda: + return PythonEnvType.Conda; + + case PythonEnvKind.System: + case PythonEnvKind.Unknown: + case PythonEnvKind.OtherGlobal: + case PythonEnvKind.Custom: + case PythonEnvKind.MicrosoftStore: + default: + return undefined; + } +} + +function toPythonEnvInfo(pythonEnv: PythonEnvironment): PythonEnvInfo | undefined { + const kind = getKind(pythonEnv); + const arch = Architecture.x64; + const version: PythonVersion = parseVersion(pythonEnv.version); + const { name, displayName, sysPrefix } = pythonEnv; + const executable = getExecutable(pythonEnv); + const location = getLocation(pythonEnv); + + return { + name, + location, + kind, + id: executable, + executable: { + filename: executable, + sysPrefix, + ctime: -1, + mtime: -1, + }, + version: { + sysVersion: pythonEnv.version, + major: version.major, + minor: version.minor, + micro: version.micro, + }, + arch, + distro: { + org: '', + }, + source: [], + detailedDisplayName: displayName, + display: displayName, + type: getEnvType(kind), + }; +} + +function hasChanged(old: PythonEnvInfo, newEnv: PythonEnvInfo): boolean { + if (old.executable.filename !== newEnv.executable.filename) { + return true; + } + if (old.version.major !== newEnv.version.major) { + return true; + } + if (old.version.minor !== newEnv.version.minor) { + return true; + } + if (old.version.micro !== newEnv.version.micro) { + return true; + } + if (old.location !== newEnv.location) { + return true; + } + if (old.kind !== newEnv.kind) { + return true; + } + if (old.arch !== newEnv.arch) { + return true; + } + + return false; +} + +class EnvExtApis implements IDiscoveryAPI, Disposable { + private _onProgress: EventEmitter; + + private _onChanged: EventEmitter; + + private _refreshPromise?: Deferred; + + private _envs: PythonEnvInfo[] = []; + + refreshState: ProgressReportStage; + + private _disposables: Disposable[] = []; + + constructor(private envExtApi: PythonEnvironmentApi) { + this._onProgress = new EventEmitter(); + this._onChanged = new EventEmitter(); + + this.onProgress = this._onProgress.event; + this.onChanged = this._onChanged.event; + + this.refreshState = ProgressReportStage.idle; + this._disposables.push( + this._onProgress, + this._onChanged, + this.envExtApi.onDidChangeEnvironments((e) => this.onDidChangeEnvironments(e)), + this.envExtApi.onDidChangeEnvironment((e) => { + this._onChanged.fire({ + type: FileChangeType.Changed, + searchLocation: e.uri, + old: e.old ? toPythonEnvInfo(e.old) : undefined, + new: e.new ? toPythonEnvInfo(e.new) : undefined, + }); + }), + ); + } + + onProgress: Event; + + onChanged: Event; + + getRefreshPromise(_options?: GetRefreshEnvironmentsOptions): Promise | undefined { + return this._refreshPromise?.promise; + } + + triggerRefresh(_query?: PythonLocatorQuery, _options?: TriggerRefreshOptions): Promise { + const stopwatch = new StopWatch(); + traceLog('Native locator: Refresh started'); + if (this.refreshState === ProgressReportStage.discoveryStarted && this._refreshPromise?.promise) { + return this._refreshPromise?.promise; + } + + this.refreshState = ProgressReportStage.discoveryStarted; + this._onProgress.fire({ stage: this.refreshState }); + this._refreshPromise = createDeferred(); + + const SLOW_DISCOVERY_THRESHOLD_MS = 25_000; + const slowDiscoveryTimer = setTimeout(() => { + traceWarn(Interpreters.envExtDiscoverySlow); + }, SLOW_DISCOVERY_THRESHOLD_MS); + + setImmediate(async () => { + try { + await this.envExtApi.refreshEnvironments(undefined); + if (this._envs.length === 0) { + traceWarn(Interpreters.envExtDiscoveryNoEnvironments); + } + this._refreshPromise?.resolve(); + } catch (error) { + traceError(Interpreters.envExtDiscoveryFailed, error); + this._refreshPromise?.reject(error); + } finally { + clearTimeout(slowDiscoveryTimer); + traceLog(`Native locator: Refresh finished in ${stopwatch.elapsedTime} ms`); + this.refreshState = ProgressReportStage.discoveryFinished; + this._refreshPromise = undefined; + this._onProgress.fire({ stage: this.refreshState }); + } + }); + + return this._refreshPromise?.promise; + } + + getEnvs(_query?: PythonLocatorQuery): PythonEnvInfo[] { + return this._envs; + } + + private addEnv(pythonEnv: PythonEnvironment, searchLocation?: Uri): PythonEnvInfo | undefined { + const info = toPythonEnvInfo(pythonEnv); + if (info) { + const old = this._envs.find((item) => item.executable.filename === info.executable.filename); + if (old) { + this._envs = this._envs.filter((item) => item.executable.filename !== info.executable.filename); + this._envs.push(info); + if (hasChanged(old, info)) { + this._onChanged.fire({ type: FileChangeType.Changed, old, new: info, searchLocation }); + } + } else { + this._envs.push(info); + this._onChanged.fire({ type: FileChangeType.Created, new: info, searchLocation }); + } + } + + return info; + } + + private removeEnv(env: PythonEnvInfo | string): void { + if (typeof env === 'string') { + const old = this._envs.find((item) => item.executable.filename === env); + this._envs = this._envs.filter((item) => item.executable.filename !== env); + this._onChanged.fire({ type: FileChangeType.Deleted, old }); + return; + } + this._envs = this._envs.filter((item) => item.executable.filename !== env.executable.filename); + this._onChanged.fire({ type: FileChangeType.Deleted, old: env }); + } + + async resolveEnv(envPath?: string): Promise { + if (envPath === undefined) { + return undefined; + } + try { + const pythonEnv = await this.envExtApi.resolveEnvironment(Uri.file(envPath)); + if (pythonEnv) { + return this.addEnv(pythonEnv); + } + } catch (error) { + traceError( + `Failed to resolve environment "${envPath}" via the Python Environments extension (ms-python.vscode-python-envs). Check the "Python Environments" output channel for details.`, + error, + ); + } + return undefined; + } + + dispose(): void { + this._disposables.forEach((d) => d.dispose()); + } + + onDidChangeEnvironments(e: DidChangeEnvironmentsEventArgs): void { + e.forEach((item) => { + if (item.kind === EnvironmentChangeKind.remove) { + this.removeEnv(item.environment.environmentPath.fsPath); + } + if (item.kind === EnvironmentChangeKind.add) { + this.addEnv(item.environment); + } + }); + } +} + +export async function createEnvExtApi(disposables: Disposable[]): Promise { + const api = new EnvExtApis(await getEnvExtApi()); + disposables.push(api); + return api; +} diff --git a/src/client/envExt/types.ts b/src/client/envExt/types.ts new file mode 100644 index 000000000000..707d641bbfe8 --- /dev/null +++ b/src/client/envExt/types.ts @@ -0,0 +1,1274 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { + Disposable, + Event, + FileChangeType, + LogOutputChannel, + MarkdownString, + TaskExecution, + Terminal, + TerminalOptions, + ThemeIcon, + Uri, +} from 'vscode'; + +/** + * The path to an icon, or a theme-specific configuration of icons. + */ +export type IconPath = + | Uri + | { + /** + * The icon path for the light theme. + */ + light: Uri; + /** + * The icon path for the dark theme. + */ + dark: Uri; + } + | ThemeIcon; + +/** + * Options for executing a Python executable. + */ +export interface PythonCommandRunConfiguration { + /** + * Path to the binary like `python.exe` or `python3` to execute. This should be an absolute path + * to an executable that can be spawned. + */ + executable: string; + + /** + * Arguments to pass to the python executable. These arguments will be passed on all execute calls. + * This is intended for cases where you might want to do interpreter specific flags. + */ + args?: string[]; +} + +/** + * Contains details on how to use a particular python environment + * + * Running In Terminal: + * 1. If {@link PythonEnvironmentExecutionInfo.activatedRun} is provided, then that will be used. + * 2. If {@link PythonEnvironmentExecutionInfo.activatedRun} is not provided, then: + * - If {@link PythonEnvironmentExecutionInfo.shellActivation} is provided and shell type is known, then that will be used. + * - If {@link PythonEnvironmentExecutionInfo.shellActivation} is provided and shell type is not known, then: + * - 'unknown' will be used if provided. + * - {@link PythonEnvironmentExecutionInfo.activation} will be used otherwise. + * - If {@link PythonEnvironmentExecutionInfo.shellActivation} is not provided, then {@link PythonEnvironmentExecutionInfo.activation} will be used. + * - If {@link PythonEnvironmentExecutionInfo.activation} is not provided, then {@link PythonEnvironmentExecutionInfo.run} will be used. + * + * Creating a Terminal: + * 1. If {@link PythonEnvironmentExecutionInfo.shellActivation} is provided and shell type is known, then that will be used. + * 2. If {@link PythonEnvironmentExecutionInfo.shellActivation} is provided and shell type is not known, then {@link PythonEnvironmentExecutionInfo.activation} will be used. + * 3. If {@link PythonEnvironmentExecutionInfo.shellActivation} is not provided, then: + * - 'unknown' will be used if provided. + * - {@link PythonEnvironmentExecutionInfo.activation} will be used otherwise. + * 4. If {@link PythonEnvironmentExecutionInfo.activation} is not provided, then {@link PythonEnvironmentExecutionInfo.run} will be used. + * + */ +export interface PythonEnvironmentExecutionInfo { + /** + * Details on how to run the python executable. + */ + run: PythonCommandRunConfiguration; + + /** + * Details on how to run the python executable after activating the environment. + * If set this will overrides the {@link PythonEnvironmentExecutionInfo.run} command. + */ + activatedRun?: PythonCommandRunConfiguration; + + /** + * Details on how to activate an environment. + */ + activation?: PythonCommandRunConfiguration[]; + + /** + * Details on how to activate an environment using a shell specific command. + * If set this will override the {@link PythonEnvironmentExecutionInfo.activation}. + * 'unknown' is used if shell type is not known. + * If 'unknown' is not provided and shell type is not known then + * {@link PythonEnvironmentExecutionInfo.activation} if set. + */ + shellActivation?: Map; + + /** + * Details on how to deactivate an environment. + */ + deactivation?: PythonCommandRunConfiguration[]; + + /** + * Details on how to deactivate an environment using a shell specific command. + * If set this will override the {@link PythonEnvironmentExecutionInfo.deactivation} property. + * 'unknown' is used if shell type is not known. + * If 'unknown' is not provided and shell type is not known then + * {@link PythonEnvironmentExecutionInfo.deactivation} if set. + */ + shellDeactivation?: Map; +} + +/** + * Interface representing the ID of a Python environment. + */ +export interface PythonEnvironmentId { + /** + * The unique identifier of the Python environment. + */ + id: string; + + /** + * The ID of the manager responsible for the Python environment. + */ + managerId: string; +} + +/** + * Display information for an environment group. + */ +export interface EnvironmentGroupInfo { + /** + * The name of the environment group. This is used as an identifier for the group. + * + * Note: The first instance of the group with the given name will be used in the UI. + */ + readonly name: string; + + /** + * The description of the environment group. + */ + readonly description?: string; + + /** + * The tooltip for the environment group, which can be a string or a Markdown string. + */ + readonly tooltip?: string | MarkdownString; + + /** + * The icon path for the environment group, which can be a string, Uri, or an object with light and dark theme paths. + */ + readonly iconPath?: IconPath; +} + +/** + * Interface representing information about a Python environment. + */ +export interface PythonEnvironmentInfo { + /** + * The name of the Python environment. + */ + readonly name: string; + + /** + * The display name of the Python environment. + */ + readonly displayName: string; + + /** + * The short display name of the Python environment. + */ + readonly shortDisplayName?: string; + + /** + * The display path of the Python environment. + */ + readonly displayPath: string; + + /** + * The version of the Python environment. + */ + readonly version: string; + + /** + * Path to the python binary or environment folder. + */ + readonly environmentPath: Uri; + + /** + * The description of the Python environment. + */ + readonly description?: string; + + /** + * The tooltip for the Python environment, which can be a string or a Markdown string. + */ + readonly tooltip?: string | MarkdownString; + + /** + * The icon path for the Python environment, which can be a string, Uri, or an object with light and dark theme paths. + */ + readonly iconPath?: IconPath; + + /** + * Information on how to execute the Python environment. This is required for executing Python code in the environment. + */ + readonly execInfo: PythonEnvironmentExecutionInfo; + + /** + * `sys.prefix` is the path to the base directory of the Python installation. Typically obtained by executing `sys.prefix` in the Python interpreter. + * This is required by extension like Jupyter, Pylance, and other extensions to provide better experience with python. + */ + readonly sysPrefix: string; + + /** + * Optional `group` for this environment. This is used to group environments in the Environment Manager UI. + */ + readonly group?: string | EnvironmentGroupInfo; +} + +/** + * Interface representing a Python environment. + */ +export interface PythonEnvironment extends PythonEnvironmentInfo { + /** + * The ID of the Python environment. + */ + readonly envId: PythonEnvironmentId; +} + +/** + * Type representing the scope for setting a Python environment. + * Can be undefined or a URI. + */ +export type SetEnvironmentScope = undefined | Uri | Uri[]; + +/** + * Type representing the scope for getting a Python environment. + * Can be undefined or a URI. + */ +export type GetEnvironmentScope = undefined | Uri; + +/** + * Type representing the scope for creating a Python environment. + * Can be a Python project or 'global'. + */ +export type CreateEnvironmentScope = Uri | Uri[] | 'global'; +/** + * The scope for which environments are to be refreshed. + * - `undefined`: Search for environments globally and workspaces. + * - {@link Uri}: Environments in the workspace/folder or associated with the Uri. + */ +export type RefreshEnvironmentsScope = Uri | undefined; + +/** + * The scope for which environments are required. + * - `"all"`: All environments. + * - `"global"`: Python installations that are usually a base for creating virtual environments. + * - {@link Uri}: Environments for the workspace/folder/file pointed to by the Uri. + */ +export type GetEnvironmentsScope = Uri | 'all' | 'global'; + +/** + * Event arguments for when the current Python environment changes. + */ +export type DidChangeEnvironmentEventArgs = { + /** + * The URI of the environment that changed. + */ + readonly uri: Uri | undefined; + + /** + * The old Python environment before the change. + */ + readonly old: PythonEnvironment | undefined; + + /** + * The new Python environment after the change. + */ + readonly new: PythonEnvironment | undefined; +}; + +/** + * Enum representing the kinds of environment changes. + */ +export enum EnvironmentChangeKind { + /** + * Indicates that an environment was added. + */ + add = 'add', + + /** + * Indicates that an environment was removed. + */ + remove = 'remove', +} + +/** + * Event arguments for when the list of Python environments changes. + */ +export type DidChangeEnvironmentsEventArgs = { + /** + * The kind of change that occurred (add or remove). + */ + kind: EnvironmentChangeKind; + + /** + * The Python environment that was added or removed. + */ + environment: PythonEnvironment; +}[]; + +/** + * Type representing the context for resolving a Python environment. + */ +export type ResolveEnvironmentContext = Uri; + +export interface QuickCreateConfig { + /** + * The description of the quick create step. + */ + readonly description: string; + + /** + * The detail of the quick create step. + */ + readonly detail?: string; +} + +/** + * Interface representing an environment manager. + */ +export interface EnvironmentManager { + /** + * The name of the environment manager. Allowed characters (a-z, A-Z, 0-9, -, _). + */ + readonly name: string; + + /** + * The display name of the environment manager. + */ + readonly displayName?: string; + + /** + * The preferred package manager ID for the environment manager. This is a combination + * of publisher id, extension id, and {@link EnvironmentManager.name package manager name}. + * `.:` + * + * @example + * 'ms-python.python:pip' + */ + readonly preferredPackageManagerId: string; + + /** + * The description of the environment manager. + */ + readonly description?: string; + + /** + * The tooltip for the environment manager, which can be a string or a Markdown string. + */ + readonly tooltip?: string | MarkdownString | undefined; + + /** + * The icon path for the environment manager, which can be a string, Uri, or an object with light and dark theme paths. + */ + readonly iconPath?: IconPath; + + /** + * The log output channel for the environment manager. + */ + readonly log?: LogOutputChannel; + + /** + * The quick create details for the environment manager. Having this method also enables the quick create feature + * for the environment manager. Should Implement {@link EnvironmentManager.create} to support quick create. + */ + quickCreateConfig?(): QuickCreateConfig | undefined; + + /** + * Creates a new Python environment within the specified scope. + * @param scope - The scope within which to create the environment. + * @param options - Optional parameters for creating the Python environment. + * @returns A promise that resolves to the created Python environment, or undefined if creation failed. + */ + create?(scope: CreateEnvironmentScope, options?: CreateEnvironmentOptions): Promise; + + /** + * Removes the specified Python environment. + * @param environment - The Python environment to remove. + * @returns A promise that resolves when the environment is removed. + */ + remove?(environment: PythonEnvironment): Promise; + + /** + * Refreshes the list of Python environments within the specified scope. + * @param scope - The scope within which to refresh environments. + * @returns A promise that resolves when the refresh is complete. + */ + refresh(scope: RefreshEnvironmentsScope): Promise; + + /** + * Retrieves a list of Python environments within the specified scope. + * @param scope - The scope within which to retrieve environments. + * @returns A promise that resolves to an array of Python environments. + */ + getEnvironments(scope: GetEnvironmentsScope): Promise; + + /** + * Event that is fired when the list of Python environments changes. + */ + onDidChangeEnvironments?: Event; + + /** + * Sets the current Python environment within the specified scope. + * @param scope - The scope within which to set the environment. + * @param environment - The Python environment to set. If undefined, the environment is unset. + * @returns A promise that resolves when the environment is set. + */ + set(scope: SetEnvironmentScope, environment?: PythonEnvironment): Promise; + + /** + * Retrieves the current Python environment within the specified scope. + * @param scope - The scope within which to retrieve the environment. + * @returns A promise that resolves to the current Python environment, or undefined if none is set. + */ + get(scope: GetEnvironmentScope): Promise; + + /** + * Event that is fired when the current Python environment changes. + */ + onDidChangeEnvironment?: Event; + + /** + * Resolves the specified Python environment. The environment can be either a {@link PythonEnvironment} or a {@link Uri} context. + * + * This method is used to obtain a fully detailed {@link PythonEnvironment} object. The input can be: + * - A {@link PythonEnvironment} object, which might be missing key details such as {@link PythonEnvironment.execInfo}. + * - A {@link Uri} object, which typically represents either: + * - A folder that contains the Python environment. + * - The path to a Python executable. + * + * @param context - The context for resolving the environment, which can be a {@link PythonEnvironment} or a {@link Uri}. + * @returns A promise that resolves to the fully detailed {@link PythonEnvironment}, or `undefined` if the environment cannot be resolved. + */ + resolve(context: ResolveEnvironmentContext): Promise; + + /** + * Clears the environment manager's cache. + * + * @returns A promise that resolves when the cache is cleared. + */ + clearCache?(): Promise; +} + +/** + * Interface representing a package ID. + */ +export interface PackageId { + /** + * The ID of the package. + */ + id: string; + + /** + * The ID of the package manager. + */ + managerId: string; + + /** + * The ID of the environment in which the package is installed. + */ + environmentId: string; +} + +/** + * Interface representing package information. + */ +export interface PackageInfo { + /** + * The name of the package. + */ + readonly name: string; + + /** + * The display name of the package. + */ + readonly displayName: string; + + /** + * The version of the package. + */ + readonly version?: string; + + /** + * The description of the package. + */ + readonly description?: string; + + /** + * The tooltip for the package, which can be a string or a Markdown string. + */ + readonly tooltip?: string | MarkdownString | undefined; + + /** + * The icon path for the package, which can be a string, Uri, or an object with light and dark theme paths. + */ + readonly iconPath?: IconPath; + + /** + * The URIs associated with the package. + */ + readonly uris?: readonly Uri[]; +} + +/** + * Interface representing a package. + */ +export interface Package extends PackageInfo { + /** + * The ID of the package. + */ + readonly pkgId: PackageId; +} + +/** + * Enum representing the kinds of package changes. + */ +export enum PackageChangeKind { + /** + * Indicates that a package was added. + */ + add = 'add', + + /** + * Indicates that a package was removed. + */ + remove = 'remove', +} + +/** + * Event arguments for when packages change. + */ +export interface DidChangePackagesEventArgs { + /** + * The Python environment in which the packages changed. + */ + environment: PythonEnvironment; + + /** + * The package manager responsible for the changes. + */ + manager: PackageManager; + + /** + * The list of changes, each containing the kind of change and the package affected. + */ + changes: { kind: PackageChangeKind; pkg: Package }[]; +} + +/** + * Interface representing a package manager. + */ +export interface PackageManager { + /** + * The name of the package manager. Allowed characters (a-z, A-Z, 0-9, -, _). + */ + name: string; + + /** + * The display name of the package manager. + */ + displayName?: string; + + /** + * The description of the package manager. + */ + description?: string; + + /** + * The tooltip for the package manager, which can be a string or a Markdown string. + */ + tooltip?: string | MarkdownString | undefined; + + /** + * The icon path for the package manager, which can be a string, Uri, or an object with light and dark theme paths. + */ + iconPath?: IconPath; + + /** + * The log output channel for the package manager. + */ + log?: LogOutputChannel; + + /** + * Installs/Uninstall packages in the specified Python environment. + * @param environment - The Python environment in which to install packages. + * @param options - Options for managing packages. + * @returns A promise that resolves when the installation is complete. + */ + manage(environment: PythonEnvironment, options: PackageManagementOptions): Promise; + + /** + * Refreshes the package list for the specified Python environment. + * @param environment - The Python environment for which to refresh the package list. + * @returns A promise that resolves when the refresh is complete. + */ + refresh(environment: PythonEnvironment): Promise; + + /** + * Retrieves the list of packages for the specified Python environment. + * @param environment - The Python environment for which to retrieve packages. + * @returns An array of packages, or undefined if the packages could not be retrieved. + */ + getPackages(environment: PythonEnvironment): Promise; + + /** + * Event that is fired when packages change. + */ + onDidChangePackages?: Event; + + /** + * Clears the package manager's cache. + * @returns A promise that resolves when the cache is cleared. + */ + clearCache?(): Promise; +} + +/** + * Interface representing a Python project. + */ +export interface PythonProject { + /** + * The name of the Python project. + */ + readonly name: string; + + /** + * The URI of the Python project. + */ + readonly uri: Uri; + + /** + * The description of the Python project. + */ + readonly description?: string; + + /** + * The tooltip for the Python project, which can be a string or a Markdown string. + */ + readonly tooltip?: string | MarkdownString; + + /** + * The icon path for the Python project, which can be a string, Uri, or an object with light and dark theme paths. + */ + readonly iconPath?: IconPath; +} + +/** + * Options for creating a Python project. + */ +export interface PythonProjectCreatorOptions { + /** + * The name of the Python project. + */ + name: string; + + /** + * Path provided as the root for the project. + */ + rootUri: Uri; + + /** + * Boolean indicating whether the project should be created without any user input. + */ + quickCreate?: boolean; +} + +/** + * Interface representing a creator for Python projects. + */ +export interface PythonProjectCreator { + /** + * The name of the Python project creator. + */ + readonly name: string; + + /** + * The display name of the Python project creator. + */ + readonly displayName?: string; + + /** + * The description of the Python project creator. + */ + readonly description?: string; + + /** + * The tooltip for the Python project creator, which can be a string or a Markdown string. + */ + readonly tooltip?: string | MarkdownString; + + /** + * The icon path for the Python project creator, which can be a string, Uri, or an object with light and dark theme paths. + */ + readonly iconPath?: IconPath; + + /** + * Creates a new Python project(s) or, if files are not a project, returns Uri(s) to the created files. + * Anything that needs its own python environment constitutes a project. + * @param options Optional parameters for creating the Python project. + * @returns A promise that resolves to one of the following: + * - PythonProject or PythonProject[]: when a single or multiple projects are created. + * - Uri or Uri[]: when files are created that do not constitute a project. + * - undefined: if project creation fails. + */ + create(options?: PythonProjectCreatorOptions): Promise; + + /** + * A flag indicating whether the project creator supports quick create where no user input is required. + */ + readonly supportsQuickCreate?: boolean; +} + +/** + * Event arguments for when Python projects change. + */ +export interface DidChangePythonProjectsEventArgs { + /** + * The list of Python projects that were added. + */ + added: PythonProject[]; + + /** + * The list of Python projects that were removed. + */ + removed: PythonProject[]; +} + +export type PackageManagementOptions = + | { + /** + * Upgrade the packages if they are already installed. + */ + upgrade?: boolean; + + /** + * Show option to skip package installation or uninstallation. + */ + showSkipOption?: boolean; + /** + * The list of packages to install. + */ + install: string[]; + + /** + * The list of packages to uninstall. + */ + uninstall?: string[]; + } + | { + /** + * Upgrade the packages if they are already installed. + */ + upgrade?: boolean; + + /** + * Show option to skip package installation or uninstallation. + */ + showSkipOption?: boolean; + /** + * The list of packages to install. + */ + install?: string[]; + + /** + * The list of packages to uninstall. + */ + uninstall: string[]; + }; + +/** + * Options for creating a Python environment. + */ +export interface CreateEnvironmentOptions { + /** + * Provides some context about quick create based on user input. + * - if true, the environment should be created without any user input or prompts. + * - if false, the environment creation can show user input or prompts. + * This also means user explicitly skipped the quick create option. + * - if undefined, the environment creation can show user input or prompts. + * You can show quick create option to the user if you support it. + */ + quickCreate?: boolean; + /** + * Packages to install in addition to the automatically picked packages as a part of creating environment. + */ + additionalPackages?: string[]; +} + +/** + * Object representing the process started using run in background API. + */ +export interface PythonProcess { + /** + * The process ID of the Python process. + */ + readonly pid?: number; + + /** + * The standard input of the Python process. + */ + readonly stdin: NodeJS.WritableStream; + + /** + * The standard output of the Python process. + */ + readonly stdout: NodeJS.ReadableStream; + + /** + * The standard error of the Python process. + */ + readonly stderr: NodeJS.ReadableStream; + + /** + * Kills the Python process. + */ + kill(): void; + + /** + * Event that is fired when the Python process exits. + */ + onExit(listener: (code: number | null, signal: NodeJS.Signals | null) => void): void; +} + +export interface PythonEnvironmentManagerRegistrationApi { + /** + * Register an environment manager implementation. + * + * @param manager Environment Manager implementation to register. + * @returns A disposable that can be used to unregister the environment manager. + * @see {@link EnvironmentManager} + */ + registerEnvironmentManager(manager: EnvironmentManager): Disposable; +} + +export interface PythonEnvironmentItemApi { + /** + * Create a Python environment item from the provided environment info. This item is used to interact + * with the environment. + * + * @param info Some details about the environment like name, version, etc. needed to interact with the environment. + * @param manager The environment manager to associate with the environment. + * @returns The Python environment. + */ + createPythonEnvironmentItem(info: PythonEnvironmentInfo, manager: EnvironmentManager): PythonEnvironment; +} + +export interface PythonEnvironmentManagementApi { + /** + * Create a Python environment using environment manager associated with the scope. + * + * @param scope Where the environment is to be created. + * @param options Optional parameters for creating the Python environment. + * @returns The Python environment created. `undefined` if not created. + */ + createEnvironment( + scope: CreateEnvironmentScope, + options?: CreateEnvironmentOptions, + ): Promise; + + /** + * Remove a Python environment. + * + * @param environment The Python environment to remove. + * @returns A promise that resolves when the environment has been removed. + */ + removeEnvironment(environment: PythonEnvironment): Promise; +} + +export interface PythonEnvironmentsApi { + /** + * Initiates a refresh of Python environments within the specified scope. + * @param scope - The scope within which to search for environments. + * @returns A promise that resolves when the search is complete. + */ + refreshEnvironments(scope: RefreshEnvironmentsScope): Promise; + + /** + * Retrieves a list of Python environments within the specified scope. + * @param scope - The scope within which to retrieve environments. + * @returns A promise that resolves to an array of Python environments. + */ + getEnvironments(scope: GetEnvironmentsScope): Promise; + + /** + * Event that is fired when the list of Python environments changes. + * @see {@link DidChangeEnvironmentsEventArgs} + */ + onDidChangeEnvironments: Event; + + /** + * This method is used to get the details missing from a PythonEnvironment. Like + * {@link PythonEnvironment.execInfo} and other details. + * + * @param context : The PythonEnvironment or Uri for which details are required. + */ + resolveEnvironment(context: ResolveEnvironmentContext): Promise; +} + +export interface PythonProjectEnvironmentApi { + /** + * Sets the current Python environment within the specified scope. + * @param scope - The scope within which to set the environment. + * @param environment - The Python environment to set. If undefined, the environment is unset. + */ + setEnvironment(scope: SetEnvironmentScope, environment?: PythonEnvironment): Promise; + + /** + * Retrieves the current Python environment within the specified scope. + * @param scope - The scope within which to retrieve the environment. + * @returns A promise that resolves to the current Python environment, or undefined if none is set. + */ + getEnvironment(scope: GetEnvironmentScope): Promise; + + /** + * Event that is fired when the selected Python environment changes for Project, Folder or File. + * @see {@link DidChangeEnvironmentEventArgs} + */ + onDidChangeEnvironment: Event; +} + +export interface PythonEnvironmentManagerApi + extends PythonEnvironmentManagerRegistrationApi, + PythonEnvironmentItemApi, + PythonEnvironmentManagementApi, + PythonEnvironmentsApi, + PythonProjectEnvironmentApi {} + +export interface PythonPackageManagerRegistrationApi { + /** + * Register a package manager implementation. + * + * @param manager Package Manager implementation to register. + * @returns A disposable that can be used to unregister the package manager. + * @see {@link PackageManager} + */ + registerPackageManager(manager: PackageManager): Disposable; +} + +export interface PythonPackageGetterApi { + /** + * Refresh the list of packages in a Python Environment. + * + * @param environment The Python Environment for which the list of packages is to be refreshed. + * @returns A promise that resolves when the list of packages has been refreshed. + */ + refreshPackages(environment: PythonEnvironment): Promise; + + /** + * Get the list of packages in a Python Environment. + * + * @param environment The Python Environment for which the list of packages is required. + * @returns The list of packages in the Python Environment. + */ + getPackages(environment: PythonEnvironment): Promise; + + /** + * Event raised when the list of packages in a Python Environment changes. + * @see {@link DidChangePackagesEventArgs} + */ + onDidChangePackages: Event; +} + +export interface PythonPackageItemApi { + /** + * Create a package item from the provided package info. + * + * @param info The package info. + * @param environment The Python Environment in which the package is installed. + * @param manager The package manager that installed the package. + * @returns The package item. + */ + createPackageItem(info: PackageInfo, environment: PythonEnvironment, manager: PackageManager): Package; +} + +export interface PythonPackageManagementApi { + /** + * Install/Uninstall packages into a Python Environment. + * + * @param environment The Python Environment into which packages are to be installed. + * @param packages The packages to install. + * @param options Options for installing packages. + */ + managePackages(environment: PythonEnvironment, options: PackageManagementOptions): Promise; +} + +export interface PythonPackageManagerApi + extends PythonPackageManagerRegistrationApi, + PythonPackageGetterApi, + PythonPackageManagementApi, + PythonPackageItemApi {} + +export interface PythonProjectCreationApi { + /** + * Register a Python project creator. + * + * @param creator The project creator to register. + * @returns A disposable that can be used to unregister the project creator. + * @see {@link PythonProjectCreator} + */ + registerPythonProjectCreator(creator: PythonProjectCreator): Disposable; +} +export interface PythonProjectGetterApi { + /** + * Get all python projects. + */ + getPythonProjects(): readonly PythonProject[]; + + /** + * Get the python project for a given URI. + * + * @param uri The URI of the project + * @returns The project or `undefined` if not found. + */ + getPythonProject(uri: Uri): PythonProject | undefined; +} + +export interface PythonProjectModifyApi { + /** + * Add a python project or projects to the list of projects. + * + * @param projects The project or projects to add. + */ + addPythonProject(projects: PythonProject | PythonProject[]): void; + + /** + * Remove a python project from the list of projects. + * + * @param project The project to remove. + */ + removePythonProject(project: PythonProject): void; + + /** + * Event raised when python projects are added or removed. + * @see {@link DidChangePythonProjectsEventArgs} + */ + onDidChangePythonProjects: Event; +} + +/** + * The API for interacting with Python projects. A project in python is any folder or file that is a contained + * in some manner. For example, a PEP-723 compliant file can be treated as a project. A folder with a `pyproject.toml`, + * or just python files can be treated as a project. All this allows you to do is set a python environment for that project. + * + * By default all `vscode.workspace.workspaceFolders` are treated as projects. + */ +export interface PythonProjectApi extends PythonProjectCreationApi, PythonProjectGetterApi, PythonProjectModifyApi {} + +export interface PythonTerminalCreateOptions extends TerminalOptions { + /** + * Whether to disable activation on create. + */ + disableActivation?: boolean; +} + +export interface PythonTerminalCreateApi { + /** + * Creates a terminal and activates any (activatable) environment for the terminal. + * + * @param environment The Python environment to activate. + * @param options Options for creating the terminal. + * + * Note: Non-activatable environments have no effect on the terminal. + */ + createTerminal(environment: PythonEnvironment, options: PythonTerminalCreateOptions): Promise; +} + +/** + * Options for running a Python script or module in a terminal. + * + * Example: + * * Running Script: `python myscript.py --arg1` + * ```typescript + * { + * args: ["myscript.py", "--arg1"] + * } + * ``` + * * Running a module: `python -m my_module --arg1` + * ```typescript + * { + * args: ["-m", "my_module", "--arg1"] + * } + * ``` + */ +export interface PythonTerminalExecutionOptions { + /** + * Current working directory for the terminal. This in only used to create the terminal. + */ + cwd: string | Uri; + + /** + * Arguments to pass to the python executable. + */ + args?: string[]; + + /** + * Set `true` to show the terminal. + */ + show?: boolean; +} + +export interface PythonTerminalRunApi { + /** + * Runs a Python script or module in a terminal. This API will create a terminal if one is not available to use. + * If a terminal is available, it will be used to run the script or module. + * + * Note: + * - If you restart VS Code, this will create a new terminal, this is a limitation of VS Code. + * - If you close the terminal, this will create a new terminal. + * - In cases of multi-root/project scenario, it will create a separate terminal for each project. + */ + runInTerminal(environment: PythonEnvironment, options: PythonTerminalExecutionOptions): Promise; + + /** + * Runs a Python script or module in a dedicated terminal. This API will create a terminal if one is not available to use. + * If a terminal is available, it will be used to run the script or module. This terminal will be dedicated to the script, + * and selected based on the `terminalKey`. + * + * @param terminalKey A unique key to identify the terminal. For scripts you can use the Uri of the script file. + */ + runInDedicatedTerminal( + terminalKey: Uri | string, + environment: PythonEnvironment, + options: PythonTerminalExecutionOptions, + ): Promise; +} + +/** + * Options for running a Python task. + * + * Example: + * * Running Script: `python myscript.py --arg1` + * ```typescript + * { + * args: ["myscript.py", "--arg1"] + * } + * ``` + * * Running a module: `python -m my_module --arg1` + * ```typescript + * { + * args: ["-m", "my_module", "--arg1"] + * } + * ``` + */ +export interface PythonTaskExecutionOptions { + /** + * Name of the task to run. + */ + name: string; + + /** + * Arguments to pass to the python executable. + */ + args: string[]; + + /** + * The Python project to use for the task. + */ + project?: PythonProject; + + /** + * Current working directory for the task. Default is the project directory for the script being run. + */ + cwd?: string; + + /** + * Environment variables to set for the task. + */ + env?: { [key: string]: string }; +} + +export interface PythonTaskRunApi { + /** + * Run a Python script or module as a task. + * + */ + runAsTask(environment: PythonEnvironment, options: PythonTaskExecutionOptions): Promise; +} + +/** + * Options for running a Python script or module in the background. + */ +export interface PythonBackgroundRunOptions { + /** + * The Python environment to use for running the script or module. + */ + args: string[]; + + /** + * Current working directory for the script or module. Default is the project directory for the script being run. + */ + cwd?: string; + + /** + * Environment variables to set for the script or module. + */ + env?: { [key: string]: string | undefined }; +} +export interface PythonBackgroundRunApi { + /** + * Run a Python script or module in the background. This API will create a new process to run the script or module. + */ + runInBackground(environment: PythonEnvironment, options: PythonBackgroundRunOptions): Promise; +} + +export interface PythonExecutionApi + extends PythonTerminalCreateApi, + PythonTerminalRunApi, + PythonTaskRunApi, + PythonBackgroundRunApi {} + +/** + * Event arguments for when the monitored `.env` files or any other sources change. + */ +export interface DidChangeEnvironmentVariablesEventArgs { + /** + * The URI of the file that changed. No `Uri` means a non-file source of environment variables changed. + */ + uri?: Uri; + + /** + * The type of change that occurred. + */ + changeTye: FileChangeType; +} + +export interface PythonEnvironmentVariablesApi { + /** + * Get environment variables for a workspace. This picks up `.env` file from the root of the + * workspace. + * + * Order of overrides: + * 1. `baseEnvVar` if given or `process.env` + * 2. `.env` file from the "python.envFile" setting in the workspace. + * 3. `.env` file at the root of the python project. + * 4. `overrides` in the order provided. + * + * @param uri The URI of the project, workspace or a file in a for which environment variables are required. + * @param overrides Additional environment variables to override the defaults. + * @param baseEnvVar The base environment variables that should be used as a starting point. + */ + getEnvironmentVariables( + uri: Uri, + overrides?: ({ [key: string]: string | undefined } | Uri)[], + baseEnvVar?: { [key: string]: string | undefined }, + ): Promise<{ [key: string]: string | undefined }>; + + /** + * Event raised when `.env` file changes or any other monitored source of env variable changes. + */ + onDidChangeEnvironmentVariables: Event; +} + +/** + * The API for interacting with Python environments, package managers, and projects. + */ +export interface PythonEnvironmentApi + extends PythonEnvironmentManagerApi, + PythonPackageManagerApi, + PythonProjectApi, + PythonExecutionApi, + PythonEnvironmentVariablesApi {} diff --git a/src/client/environmentApi.ts b/src/client/environmentApi.ts new file mode 100644 index 000000000000..ecd8eef21845 --- /dev/null +++ b/src/client/environmentApi.ts @@ -0,0 +1,444 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { ConfigurationTarget, EventEmitter, Uri, workspace, WorkspaceFolder } from 'vscode'; +import * as pathUtils from 'path'; +import { IConfigurationService, IDisposableRegistry, IExtensions, IInterpreterPathService } from './common/types'; +import { Architecture } from './common/utils/platform'; +import { IServiceContainer } from './ioc/types'; +import { PythonEnvInfo, PythonEnvKind, PythonEnvType } from './pythonEnvironments/base/info'; +import { getEnvPath } from './pythonEnvironments/base/info/env'; +import { IDiscoveryAPI, ProgressReportStage } from './pythonEnvironments/base/locator'; +import { IPythonExecutionFactory } from './common/process/types'; +import { traceError, traceInfo, traceVerbose } from './logging'; +import { isParentPath, normCasePath } from './common/platform/fs-paths'; +import { sendTelemetryEvent } from './telemetry'; +import { EventName } from './telemetry/constants'; +import { reportActiveInterpreterChangedDeprecated, reportInterpretersChanged } from './deprecatedProposedApi'; +import { IEnvironmentVariablesProvider } from './common/variables/types'; +import { getWorkspaceFolder, getWorkspaceFolders } from './common/vscodeApis/workspaceApis'; +import { + ActiveEnvironmentPathChangeEvent, + Environment, + EnvironmentPath, + EnvironmentsChangeEvent, + EnvironmentTools, + EnvironmentType, + EnvironmentVariablesChangeEvent, + PythonExtension, + RefreshOptions, + ResolvedEnvironment, + Resource, +} from './api/types'; +import { buildEnvironmentCreationApi } from './pythonEnvironments/creation/createEnvApi'; +import { EnvironmentKnownCache } from './environmentKnownCache'; +import type { JupyterPythonEnvironmentApi } from './jupyter/jupyterIntegration'; +import { noop } from './common/utils/misc'; + +type ActiveEnvironmentChangeEvent = { + resource: WorkspaceFolder | undefined; + path: string; +}; + +const onDidActiveInterpreterChangedEvent = new EventEmitter(); +const previousEnvMap = new Map(); +export function reportActiveInterpreterChanged(e: ActiveEnvironmentChangeEvent): void { + const oldPath = previousEnvMap.get(e.resource?.uri.fsPath ?? ''); + if (oldPath === e.path) { + return; + } + previousEnvMap.set(e.resource?.uri.fsPath ?? '', e.path); + onDidActiveInterpreterChangedEvent.fire({ id: getEnvID(e.path), path: e.path, resource: e.resource }); + reportActiveInterpreterChangedDeprecated({ path: e.path, resource: e.resource?.uri }); +} + +const onEnvironmentsChanged = new EventEmitter(); +const onEnvironmentVariablesChanged = new EventEmitter(); +const environmentsReference = new Map(); + +/** + * Make all properties in T mutable. + */ +type Mutable = { + -readonly [P in keyof T]: Mutable; +}; + +export class EnvironmentReference implements Environment { + readonly id: string; + + constructor(public internal: Environment) { + this.id = internal.id; + } + + get executable() { + return Object.freeze(this.internal.executable); + } + + get environment() { + return Object.freeze(this.internal.environment); + } + + get version() { + return Object.freeze(this.internal.version); + } + + get tools() { + return Object.freeze(this.internal.tools); + } + + get path() { + return Object.freeze(this.internal.path); + } + + updateEnv(newInternal: Environment) { + this.internal = newInternal; + } +} + +function getEnvReference(e: Environment) { + let envClass = environmentsReference.get(e.id); + if (!envClass) { + envClass = new EnvironmentReference(e); + } else { + envClass.updateEnv(e); + } + environmentsReference.set(e.id, envClass); + return envClass; +} + +function filterUsingVSCodeContext(e: PythonEnvInfo) { + const folders = getWorkspaceFolders(); + if (e.searchLocation) { + // Only return local environments that are in the currently opened workspace folders. + const envFolderUri = e.searchLocation; + if (folders) { + return folders.some((folder) => isParentPath(envFolderUri.fsPath, folder.uri.fsPath)); + } + return false; + } + return true; +} + +export function buildEnvironmentApi( + discoveryApi: IDiscoveryAPI, + serviceContainer: IServiceContainer, + jupyterPythonEnvsApi: JupyterPythonEnvironmentApi, +): PythonExtension['environments'] { + const interpreterPathService = serviceContainer.get(IInterpreterPathService); + const configService = serviceContainer.get(IConfigurationService); + const disposables = serviceContainer.get(IDisposableRegistry); + const extensions = serviceContainer.get(IExtensions); + const envVarsProvider = serviceContainer.get(IEnvironmentVariablesProvider); + let knownCache: EnvironmentKnownCache; + + function initKnownCache() { + const knownEnvs = discoveryApi + .getEnvs() + .filter((e) => filterUsingVSCodeContext(e)) + .map((e) => updateReference(e)); + return new EnvironmentKnownCache(knownEnvs); + } + function sendApiTelemetry(apiName: string, args?: unknown) { + extensions + .determineExtensionFromCallStack() + .then((info) => { + const p = Math.random(); + if (p <= 0.001) { + // Only send API telemetry 1% of the time, as it can be chatty. + sendTelemetryEvent(EventName.PYTHON_ENVIRONMENTS_API, undefined, { + apiName, + extensionId: info.extensionId, + }); + } + traceVerbose(`Extension ${info.extensionId} accessed ${apiName} with args: ${JSON.stringify(args)}`); + }) + .ignoreErrors(); + } + + function getActiveEnvironmentPath(resource?: Resource) { + resource = resource && 'uri' in resource ? resource.uri : resource; + const jupyterEnv = + resource && jupyterPythonEnvsApi.getPythonEnvironment + ? jupyterPythonEnvsApi.getPythonEnvironment(resource) + : undefined; + if (jupyterEnv) { + traceVerbose('Python Environment returned from Jupyter', resource?.fsPath, jupyterEnv.id); + return { + id: jupyterEnv.id, + path: jupyterEnv.path, + }; + } + const path = configService.getSettings(resource).pythonPath; + const id = path === 'python' ? 'DEFAULT_PYTHON' : getEnvID(path); + return { + id, + path, + }; + } + + disposables.push( + onDidActiveInterpreterChangedEvent.event((e) => { + let scope = 'global'; + if (e.resource) { + scope = e.resource instanceof Uri ? e.resource.fsPath : e.resource.uri.fsPath; + } + traceInfo(`Active interpreter [${scope}]: `, e.path); + }), + discoveryApi.onProgress((e) => { + if (e.stage === ProgressReportStage.discoveryFinished) { + knownCache = initKnownCache(); + } + }), + discoveryApi.onChanged((e) => { + const env = e.new ?? e.old; + if (!env || !filterUsingVSCodeContext(env)) { + // Filter out environments that are not in the current workspace. + return; + } + if (!knownCache) { + knownCache = initKnownCache(); + } + if (e.old) { + if (e.new) { + const newEnv = updateReference(e.new); + knownCache.updateEnv(convertEnvInfo(e.old), newEnv); + traceVerbose('Python API env change detected', env.id, 'update'); + onEnvironmentsChanged.fire({ type: 'update', env: newEnv }); + reportInterpretersChanged([ + { + path: getEnvPath(e.new.executable.filename, e.new.location).path, + type: 'update', + }, + ]); + } else { + const oldEnv = updateReference(e.old); + knownCache.updateEnv(oldEnv, undefined); + traceVerbose('Python API env change detected', env.id, 'remove'); + onEnvironmentsChanged.fire({ type: 'remove', env: oldEnv }); + reportInterpretersChanged([ + { + path: getEnvPath(e.old.executable.filename, e.old.location).path, + type: 'remove', + }, + ]); + } + } else if (e.new) { + const newEnv = updateReference(e.new); + knownCache.addEnv(newEnv); + traceVerbose('Python API env change detected', env.id, 'add'); + onEnvironmentsChanged.fire({ type: 'add', env: newEnv }); + reportInterpretersChanged([ + { + path: getEnvPath(e.new.executable.filename, e.new.location).path, + type: 'add', + }, + ]); + } + }), + envVarsProvider.onDidEnvironmentVariablesChange((e) => { + onEnvironmentVariablesChanged.fire({ + resource: getWorkspaceFolder(e), + env: envVarsProvider.getEnvironmentVariablesSync(e), + }); + }), + onEnvironmentsChanged, + onEnvironmentVariablesChanged, + jupyterPythonEnvsApi.onDidChangePythonEnvironment + ? jupyterPythonEnvsApi.onDidChangePythonEnvironment((e) => { + const jupyterEnv = getActiveEnvironmentPath(e); + onDidActiveInterpreterChangedEvent.fire({ + id: jupyterEnv.id, + path: jupyterEnv.path, + resource: e, + }); + }, undefined) + : { dispose: noop }, + ); + if (!knownCache!) { + knownCache = initKnownCache(); + } + + const environmentApi: PythonExtension['environments'] = { + getEnvironmentVariables: (resource?: Resource) => { + sendApiTelemetry('getEnvironmentVariables'); + resource = resource && 'uri' in resource ? resource.uri : resource; + return envVarsProvider.getEnvironmentVariablesSync(resource); + }, + get onDidEnvironmentVariablesChange() { + sendApiTelemetry('onDidEnvironmentVariablesChange'); + return onEnvironmentVariablesChanged.event; + }, + getActiveEnvironmentPath(resource?: Resource) { + sendApiTelemetry('getActiveEnvironmentPath'); + return getActiveEnvironmentPath(resource); + }, + updateActiveEnvironmentPath(env: Environment | EnvironmentPath | string, resource?: Resource): Promise { + sendApiTelemetry('updateActiveEnvironmentPath'); + const path = typeof env !== 'string' ? env.path : env; + resource = resource && 'uri' in resource ? resource.uri : resource; + return interpreterPathService.update(resource, ConfigurationTarget.WorkspaceFolder, path); + }, + get onDidChangeActiveEnvironmentPath() { + sendApiTelemetry('onDidChangeActiveEnvironmentPath'); + return onDidActiveInterpreterChangedEvent.event; + }, + resolveEnvironment: async (env: Environment | EnvironmentPath | string) => { + if (!workspace.isTrusted) { + throw new Error('Not allowed to resolve environment in an untrusted workspace'); + } + let path = typeof env !== 'string' ? env.path : env; + if (pathUtils.basename(path) === path) { + // Value can be `python`, `python3`, `python3.9` etc. + // This case could eventually be handled by the internal discovery API itself. + const pythonExecutionFactory = serviceContainer.get(IPythonExecutionFactory); + const pythonExecutionService = await pythonExecutionFactory.create({ pythonPath: path }); + const fullyQualifiedPath = await pythonExecutionService.getExecutablePath().catch((ex) => { + traceError('Cannot resolve full path', ex); + return undefined; + }); + // Python path is invalid or python isn't installed. + if (!fullyQualifiedPath) { + return undefined; + } + path = fullyQualifiedPath; + } + sendApiTelemetry('resolveEnvironment', env); + return resolveEnvironment(path, discoveryApi); + }, + get known(): Environment[] { + // Do not send telemetry for "known", as this may be called 1000s of times so it can significant: + // sendApiTelemetry('known'); + return knownCache.envs; + }, + async refreshEnvironments(options?: RefreshOptions) { + if (!workspace.isTrusted) { + traceError('Not allowed to refresh environments in an untrusted workspace'); + return; + } + await discoveryApi.triggerRefresh(undefined, { + ifNotTriggerredAlready: !options?.forceRefresh, + }); + sendApiTelemetry('refreshEnvironments'); + }, + get onDidChangeEnvironments() { + sendApiTelemetry('onDidChangeEnvironments'); + return onEnvironmentsChanged.event; + }, + ...buildEnvironmentCreationApi(), + }; + return environmentApi; +} + +async function resolveEnvironment(path: string, discoveryApi: IDiscoveryAPI): Promise { + const env = await discoveryApi.resolveEnv(path); + if (!env) { + return undefined; + } + const resolvedEnv = getEnvReference(convertCompleteEnvInfo(env)) as ResolvedEnvironment; + if (resolvedEnv.version?.major === -1 || resolvedEnv.version?.minor === -1 || resolvedEnv.version?.micro === -1) { + traceError(`Invalid version for ${path}: ${JSON.stringify(env)}`); + } + return resolvedEnv; +} + +export function convertCompleteEnvInfo(env: PythonEnvInfo): ResolvedEnvironment { + const version = { ...env.version, sysVersion: env.version.sysVersion }; + let tool = convertKind(env.kind); + if (env.type && !tool) { + tool = 'Unknown'; + } + const { path } = getEnvPath(env.executable.filename, env.location); + const resolvedEnv: ResolvedEnvironment = { + path, + id: env.id!, + executable: { + uri: env.executable.filename === 'python' ? undefined : Uri.file(env.executable.filename), + bitness: convertBitness(env.arch), + sysPrefix: env.executable.sysPrefix, + }, + environment: env.type + ? { + type: convertEnvType(env.type), + name: env.name === '' ? undefined : env.name, + folderUri: Uri.file(env.location), + workspaceFolder: getWorkspaceFolder(env.searchLocation), + } + : undefined, + version: env.executable.filename === 'python' ? undefined : (version as ResolvedEnvironment['version']), + tools: tool ? [tool] : [], + }; + return resolvedEnv; +} + +function convertEnvType(envType: PythonEnvType): EnvironmentType { + if (envType === PythonEnvType.Conda) { + return 'Conda'; + } + if (envType === PythonEnvType.Virtual) { + return 'VirtualEnvironment'; + } + return 'Unknown'; +} + +function convertKind(kind: PythonEnvKind): EnvironmentTools | undefined { + switch (kind) { + case PythonEnvKind.Venv: + return 'Venv'; + case PythonEnvKind.Pipenv: + return 'Pipenv'; + case PythonEnvKind.Poetry: + return 'Poetry'; + case PythonEnvKind.Hatch: + return 'Hatch'; + case PythonEnvKind.VirtualEnvWrapper: + return 'VirtualEnvWrapper'; + case PythonEnvKind.VirtualEnv: + return 'VirtualEnv'; + case PythonEnvKind.Conda: + return 'Conda'; + case PythonEnvKind.Pyenv: + return 'Pyenv'; + default: + return undefined; + } +} + +export function convertEnvInfo(env: PythonEnvInfo): Environment { + const convertedEnv = convertCompleteEnvInfo(env) as Mutable; + if (convertedEnv.executable.sysPrefix === '') { + convertedEnv.executable.sysPrefix = undefined; + } + if (convertedEnv.version?.sysVersion === '') { + convertedEnv.version.sysVersion = undefined; + } + if (convertedEnv.version?.major === -1) { + convertedEnv.version.major = undefined; + } + if (convertedEnv.version?.micro === -1) { + convertedEnv.version.micro = undefined; + } + if (convertedEnv.version?.minor === -1) { + convertedEnv.version.minor = undefined; + } + return convertedEnv as Environment; +} + +function updateReference(env: PythonEnvInfo): Environment { + return getEnvReference(convertEnvInfo(env)); +} + +function convertBitness(arch: Architecture) { + switch (arch) { + case Architecture.x64: + return '64-bit'; + case Architecture.x86: + return '32-bit'; + default: + return 'Unknown'; + } +} + +function getEnvID(path: string) { + return normCasePath(path); +} diff --git a/src/client/environmentKnownCache.ts b/src/client/environmentKnownCache.ts new file mode 100644 index 000000000000..287f5bab343f --- /dev/null +++ b/src/client/environmentKnownCache.ts @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { Environment } from './api/types'; + +/** + * Workaround temp cache until types are consolidated. + */ +export class EnvironmentKnownCache { + private _envs: Environment[] = []; + + constructor(envs: Environment[]) { + this._envs = envs; + } + + public get envs(): Environment[] { + return this._envs; + } + + public addEnv(env: Environment): void { + const found = this._envs.find((e) => env.id === e.id); + if (!found) { + this._envs.push(env); + } + } + + public updateEnv(oldValue: Environment, newValue: Environment | undefined): void { + const index = this._envs.findIndex((e) => oldValue.id === e.id); + if (index !== -1) { + if (newValue === undefined) { + this._envs.splice(index, 1); + } else { + this._envs[index] = newValue; + } + } + } +} diff --git a/src/client/extension.ts b/src/client/extension.ts index a7407ac9d241..c3fb2a3ab3b0 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -1,44 +1,52 @@ 'use strict'; -// tslint:disable:no-var-requires no-require-imports - // This line should always be right on top. -// tslint:disable:no-any + if ((Reflect as any).metadata === undefined) { require('reflect-metadata'); } -// Initialize source maps (this must never be moved up nor further down). -import { initialize } from './sourceMapSupport'; -initialize(require('vscode')); -// Initialize the logger first. -require('./common/logger'); - //=============================================== // We start tracking the extension's startup time at this point. The // locations at which we record various Intervals are marked below in // the same way as this. -const durations: Record = {}; +const durations = {} as IStartupDurations; import { StopWatch } from './common/utils/stopWatch'; // Do not move this line of code (used to measure extension load times). const stopWatch = new StopWatch(); +// Initialize file logging here. This should not depend on too many things. +import { initializeFileLogging, traceError } from './logging'; +const logDispose: { dispose: () => void }[] = []; +initializeFileLogging(logDispose); + //=============================================== // loading starts here import { ProgressLocation, ProgressOptions, window } from 'vscode'; - -import { buildApi, IExtensionApi } from './api'; -import { IApplicationShell } from './common/application/types'; -import { traceError } from './common/logger'; -import { IAsyncDisposableRegistry, IExtensionContext } from './common/types'; +import { buildApi } from './api'; +import { IApplicationShell, IWorkspaceService } from './common/application/types'; +import { IDisposableRegistry, IExperimentService, IExtensionContext } from './common/types'; import { createDeferred } from './common/utils/async'; import { Common } from './common/utils/localize'; -import { activateComponents } from './extensionActivation'; -import { initializeCommon, initializeComponents, initializeGlobals } from './extensionInit'; +import { activateComponents, activateFeatures } from './extensionActivation'; +import { initializeStandard, initializeComponents, initializeGlobals } from './extensionInit'; import { IServiceContainer } from './ioc/types'; import { sendErrorTelemetry, sendStartupTelemetry } from './startupTelemetry'; +import { IStartupDurations } from './types'; +import { runAfterActivation } from './common/utils/runAfterActivation'; +import { IInterpreterService } from './interpreter/contracts'; +import { PythonExtension } from './api/types'; +import { WorkspaceService } from './common/application/workspace'; +import { disposeAll } from './common/utils/resourceLifecycle'; +import { ProposedExtensionAPI } from './proposedApiTypes'; +import { buildProposedApi } from './proposedApi'; +import { GLOBAL_PERSISTENT_KEYS } from './common/persistentState'; +import { registerTools } from './chat'; +import { IRecommendedEnvironmentService } from './interpreter/configuration/types'; +import { registerTypes as unitTestsRegisterTypes } from './testing/serviceRegistry'; +import { registerTestCommands } from './testing/main'; durations.codeLoadingTime = stopWatch.elapsedTime; @@ -51,84 +59,139 @@ let activatedServiceContainer: IServiceContainer | undefined; ///////////////////////////// // public functions -export async function activate(context: IExtensionContext): Promise { - let api: IExtensionApi; +export async function activate(context: IExtensionContext): Promise { + let api: PythonExtension; let ready: Promise; let serviceContainer: IServiceContainer; + let isFirstSession: boolean | undefined; try { + isFirstSession = context.globalState.get(GLOBAL_PERSISTENT_KEYS, []).length === 0; + const workspaceService = new WorkspaceService(); + context.subscriptions.push( + workspaceService.onDidGrantWorkspaceTrust(async () => { + await deactivate(); + await activate(context); + }), + ); [api, ready, serviceContainer] = await activateUnsafe(context, stopWatch, durations); } catch (ex) { // We want to completely handle the error // before notifying VS Code. - await handleError(ex, durations); + await handleError(ex as Error, durations); throw ex; // re-raise } // Send the "success" telemetry only if activation did not fail. // Otherwise Telemetry is send via the error handler. - sendStartupTelemetry(ready, durations, stopWatch, serviceContainer) + sendStartupTelemetry(ready, durations, stopWatch, serviceContainer, isFirstSession) // Run in the background. .ignoreErrors(); return api; } -export function deactivate(): Thenable { +export async function deactivate(): Promise { // Make sure to shutdown anybody who needs it. if (activatedServiceContainer) { - const registry = activatedServiceContainer.get(IAsyncDisposableRegistry); - if (registry) { - return registry.dispose(); - } + const disposables = activatedServiceContainer.get(IDisposableRegistry); + await disposeAll(disposables); + // Remove everything that is already disposed. + while (disposables.pop()); } - - return Promise.resolve(); } ///////////////////////////// // activation helpers -// tslint:disable-next-line:max-func-body-length async function activateUnsafe( context: IExtensionContext, startupStopWatch: StopWatch, - startupDurations: Record -): Promise<[IExtensionApi, Promise, IServiceContainer]> { + startupDurations: IStartupDurations, +): Promise<[PythonExtension & ProposedExtensionAPI, Promise, IServiceContainer]> { + // Add anything that we got from initializing logs to dispose. + context.subscriptions.push(...logDispose); + const activationDeferred = createDeferred(); displayProgress(activationDeferred.promise); startupDurations.startActivateTime = startupStopWatch.elapsedTime; + const activationStopWatch = new StopWatch(); //=============================================== // activation starts here - const [serviceManager, serviceContainer] = initializeGlobals(context); - activatedServiceContainer = serviceContainer; - initializeCommon(context, serviceManager, serviceContainer); - initializeComponents(context, serviceManager, serviceContainer); - const { activationPromise } = await activateComponents(context, serviceManager, serviceContainer); + // First we initialize. + const ext = initializeGlobals(context); + activatedServiceContainer = ext.legacyIOC.serviceContainer; + // Note standard utils especially experiment and platform code are fundamental to the extension + // and should be available before we activate anything else.Hence register them first. + initializeStandard(ext); + + // Register test services and commands early to prevent race conditions. + unitTestsRegisterTypes(ext.legacyIOC.serviceManager); + registerTestCommands(activatedServiceContainer); + + // We need to activate experiments before initializing components as objects are created or not created based on experiments. + const experimentService = activatedServiceContainer.get(IExperimentService); + // This guarantees that all experiment information has loaded & all telemetry will contain experiment info. + await experimentService.activate(); + const components = await initializeComponents(ext); + + // Then we finish activating. + const componentsActivated = await activateComponents(ext, components, activationStopWatch); + activateFeatures(ext, components); + + const nonBlocking = componentsActivated.map((r) => r.fullyReady); + const activationPromise = (async () => { + await Promise.all(nonBlocking); + })(); //=============================================== // activation ends here - startupDurations.endActivateTime = startupStopWatch.elapsedTime; + startupDurations.totalActivateTime = startupStopWatch.elapsedTime - startupDurations.startActivateTime; activationDeferred.resolve(); - const api = buildApi(activationPromise, serviceManager, serviceContainer); - return [api, activationPromise, serviceContainer]; + setTimeout(async () => { + if (activatedServiceContainer) { + const workspaceService = activatedServiceContainer.get(IWorkspaceService); + if (workspaceService.isTrusted) { + const interpreterManager = activatedServiceContainer.get(IInterpreterService); + const workspaces = workspaceService.workspaceFolders ?? []; + await interpreterManager + .refresh(workspaces.length > 0 ? workspaces[0].uri : undefined) + .catch((ex) => traceError('Python Extension: interpreterManager.refresh', ex)); + } + } + + runAfterActivation(); + }); + + const api = buildApi( + activationPromise, + ext.legacyIOC.serviceManager, + ext.legacyIOC.serviceContainer, + components.pythonEnvs, + ); + const proposedApi = buildProposedApi(components.pythonEnvs, ext.legacyIOC.serviceContainer); + registerTools(context, components.pythonEnvs, api.environments, ext.legacyIOC.serviceContainer); + ext.legacyIOC.serviceContainer + .get(IRecommendedEnvironmentService) + .registerEnvApi(api.environments); + return [{ ...api, ...proposedApi }, activationPromise, ext.legacyIOC.serviceContainer]; } -// tslint:disable-next-line:no-any function displayProgress(promise: Promise) { - const progressOptions: ProgressOptions = { location: ProgressLocation.Window, title: Common.loadingExtension() }; + const progressOptions: ProgressOptions = { location: ProgressLocation.Window, title: Common.loadingExtension }; window.withProgress(progressOptions, () => promise); } ///////////////////////////// // error handling -async function handleError(ex: Error, startupDurations: Record) { +async function handleError(ex: Error, startupDurations: IStartupDurations) { notifyUser( - "Extension activation failed, run the 'Developer: Toggle Developer Tools' command for more information." + "Extension activation failed, run the 'Developer: Toggle Developer Tools' command for more information.", ); traceError('extension activation failed', ex); + await sendErrorTelemetry(ex, startupDurations, activatedServiceContainer); } @@ -138,14 +201,12 @@ interface IAppShell { function notifyUser(msg: string) { try { - // tslint:disable-next-line:no-any let appShell: IAppShell = (window as any) as IAppShell; if (activatedServiceContainer) { - // tslint:disable-next-line:no-any appShell = (activatedServiceContainer.get(IApplicationShell) as any) as IAppShell; } appShell.showErrorMessage(msg).ignoreErrors(); } catch (ex) { - traceError('failed to notify user', ex); + traceError('Failed to Notify User', ex); } } diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index e0f61ae7bfca..57bcb8237eeb 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -3,224 +3,199 @@ 'use strict'; -// tslint:disable:max-func-body-length - -import { CodeActionKind, debug, DebugConfigurationProvider, languages, OutputChannel, window } from 'vscode'; +import { DebugConfigurationProvider, debug, languages, window } from 'vscode'; import { registerTypes as activationRegisterTypes } from './activation/serviceRegistry'; -import { IExtensionActivationManager, ILanguageServerExtension } from './activation/types'; +import { IExtensionActivationManager } from './activation/types'; import { registerTypes as appRegisterTypes } from './application/serviceRegistry'; import { IApplicationDiagnostics } from './application/types'; -import { DebugService } from './common/application/debugService'; import { IApplicationEnvironment, ICommandManager, IWorkspaceService } from './common/application/types'; -import { Commands, PYTHON, PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL, UseProposedApi } from './common/constants'; +import { Commands, PYTHON_LANGUAGE, UseProposedApi } from './common/constants'; import { registerTypes as installerRegisterTypes } from './common/installer/serviceRegistry'; -import { traceError } from './common/logger'; -import { registerTypes as platformRegisterTypes } from './common/platform/serviceRegistry'; import { IFileSystem } from './common/platform/types'; -import { registerTypes as processRegisterTypes } from './common/process/serviceRegistry'; -import { - IConfigurationService, - IDisposableRegistry, - IExperimentsManager, - IExtensionContext, - IFeatureDeprecationManager, - IOutputChannel -} from './common/types'; -import { OutputChannelNames } from './common/utils/localize'; +import { IConfigurationService, IDisposableRegistry, IExtensions, ILogOutputChannel, IPathUtils } from './common/types'; import { noop } from './common/utils/misc'; -import { registerTypes as variableRegisterTypes } from './common/variables/serviceRegistry'; -import { JUPYTER_OUTPUT_CHANNEL } from './datascience/constants'; -import { registerTypes as dataScienceRegisterTypes } from './datascience/serviceRegistry'; -import { IDataScience } from './datascience/types'; -import { DebuggerTypeName } from './debugger/constants'; -import { DebugSessionEventDispatcher } from './debugger/extension/hooks/eventHandlerDispatcher'; -import { IDebugSessionEventHandlers } from './debugger/extension/hooks/types'; import { registerTypes as debugConfigurationRegisterTypes } from './debugger/extension/serviceRegistry'; -import { IDebugConfigurationService, IDebuggerBanner } from './debugger/extension/types'; -import { registerTypes as formattersRegisterTypes } from './formatters/serviceRegistry'; -import { - IInterpreterLocatorProgressHandler, - IInterpreterLocatorProgressService, - IInterpreterService -} from './interpreter/contracts'; -import { registerTypes as interpretersRegisterTypes } from './interpreter/serviceRegistry'; -import { IServiceContainer, IServiceManager } from './ioc/types'; +import { IDebugConfigurationService } from './debugger/extension/types'; +import { IInterpreterService } from './interpreter/contracts'; import { getLanguageConfiguration } from './language/languageConfiguration'; -import { LinterCommands } from './linters/linterCommands'; -import { registerTypes as lintersRegisterTypes } from './linters/serviceRegistry'; -import { addOutputChannelLogging, setLoggingLevel } from './logging'; -import { PythonCodeActionProvider } from './providers/codeActionProvider/pythonCodeActionProvider'; -import { PythonFormattingEditProvider } from './providers/formatProvider'; import { ReplProvider } from './providers/replProvider'; import { registerTypes as providersRegisterTypes } from './providers/serviceRegistry'; -import { activateSimplePythonRefactorProvider } from './providers/simpleRefactorProvider'; import { TerminalProvider } from './providers/terminalProvider'; -import { ISortImportsEditingProvider } from './providers/types'; import { setExtensionInstallTelemetryProperties } from './telemetry/extensionInstallTelemetry'; +import { registerTypes as tensorBoardRegisterTypes } from './tensorBoard/serviceRegistry'; import { registerTypes as commonRegisterTerminalTypes } from './terminals/serviceRegistry'; -import { ICodeExecutionManager, ITerminalAutoActivation } from './terminals/types'; -import { TEST_OUTPUT_CHANNEL } from './testing/common/constants'; -import { ITestContextService } from './testing/common/types'; -import { ITestCodeNavigatorCommandHandler, ITestExplorerCommandHandler } from './testing/navigation/types'; -import { registerTypes as unitTestsRegisterTypes } from './testing/serviceRegistry'; +import { ICodeExecutionHelper, ICodeExecutionManager, ITerminalAutoActivation } from './terminals/types'; + +// components +import * as pythonEnvironments from './pythonEnvironments'; + +import { ActivationResult, ExtensionState } from './components'; +import { Components } from './extensionInit'; +import { setDefaultLanguageServer } from './activation/common/defaultlanguageServer'; +import { DebugService } from './common/application/debugService'; +import { DebugSessionEventDispatcher } from './debugger/extension/hooks/eventHandlerDispatcher'; +import { IDebugSessionEventHandlers } from './debugger/extension/hooks/types'; +import { WorkspaceService } from './common/application/workspace'; +import { IInterpreterQuickPick, IPythonPathUpdaterServiceManager } from './interpreter/configuration/types'; +import { registerAllCreateEnvironmentFeatures } from './pythonEnvironments/creation/registrations'; +import { registerCreateEnvironmentTriggers } from './pythonEnvironments/creation/createEnvironmentTrigger'; +import { initializePersistentStateForTriggers } from './common/persistentState'; +import { DebuggerTypeName } from './debugger/constants'; +import { StopWatch } from './common/utils/stopWatch'; +import { registerReplCommands, registerReplExecuteOnEnter, registerStartNativeReplCommand } from './repl/replCommands'; +import { registerTriggerForTerminalREPL } from './terminals/codeExecution/terminalReplWatcher'; +import { registerPythonStartup } from './terminals/pythonStartup'; +import { registerPixiFeatures } from './pythonEnvironments/common/environmentManagers/pixi'; +import { registerCustomTerminalLinkProvider } from './terminals/pythonStartupLinkProvider'; export async function activateComponents( - context: IExtensionContext, - serviceManager: IServiceManager, - serviceContainer: IServiceContainer -) { - // We will be pulling code over from activateLegacy(). + // `ext` is passed to any extra activation funcs. + ext: ExtensionState, + components: Components, + startupStopWatch: StopWatch, +): Promise { + // Note that each activation returns a promise that resolves + // when that activation completes. However, it might have started + // some non-critical background operations that do not block + // extension activation but do block use of the extension "API". + // Each component activation can't just resolve an "inner" promise + // for those non-critical operations because `await` (and + // `Promise.all()`, etc.) will flatten nested promises. Thus + // activation resolves `ActivationResult`, which can safely wrap + // the "inner" promise. + + // TODO: As of now activateLegacy() registers various classes which might + // be required while activating components. Once registration from + // activateLegacy() are moved before we activate other components, we can + // activate them in parallel with the other components. + // https://github.com/microsoft/vscode-python/issues/15380 + // These will go away eventually once everything is refactored into components. + const legacyActivationResult = await activateLegacy(ext, startupStopWatch); + const workspaceService = new WorkspaceService(); + if (!workspaceService.isTrusted) { + return [legacyActivationResult]; + } + const promises: Promise[] = [ + // More component activations will go here + pythonEnvironments.activate(components.pythonEnvs, ext), + ]; + return Promise.all([legacyActivationResult, ...promises]); +} - return activateLegacy(context, serviceManager, serviceContainer); +export function activateFeatures(ext: ExtensionState, _components: Components): void { + const interpreterQuickPick: IInterpreterQuickPick = ext.legacyIOC.serviceContainer.get( + IInterpreterQuickPick, + ); + const interpreterService: IInterpreterService = ext.legacyIOC.serviceContainer.get( + IInterpreterService, + ); + const pathUtils = ext.legacyIOC.serviceContainer.get(IPathUtils); + registerPixiFeatures(ext.disposables); + registerAllCreateEnvironmentFeatures( + ext.disposables, + interpreterQuickPick, + ext.legacyIOC.serviceContainer.get(IPythonPathUpdaterServiceManager), + interpreterService, + pathUtils, + ); + const executionHelper = ext.legacyIOC.serviceContainer.get(ICodeExecutionHelper); + const commandManager = ext.legacyIOC.serviceContainer.get(ICommandManager); + registerTriggerForTerminalREPL(ext.disposables); + registerStartNativeReplCommand(ext.disposables, interpreterService); + registerReplCommands(ext.disposables, interpreterService, executionHelper, commandManager); + registerReplExecuteOnEnter(ext.disposables, interpreterService, commandManager); + registerCustomTerminalLinkProvider(ext.disposables); } -///////////////////////////// +/// ////////////////////////// // old activation code -// tslint:disable-next-line:no-suspicious-comment // TODO: Gradually move simple initialization // and DI registration currently in this function over // to initializeComponents(). Likewise with complex // init and activation: move them to activateComponents(). // See https://github.com/microsoft/vscode-python/issues/10454. -async function activateLegacy( - context: IExtensionContext, - serviceManager: IServiceManager, - serviceContainer: IServiceContainer -) { - // register "services" - - const standardOutputChannel = window.createOutputChannel(OutputChannelNames.python()); - addOutputChannelLogging(standardOutputChannel); - const unitTestOutChannel = window.createOutputChannel(OutputChannelNames.pythonTest()); - const jupyterOutputChannel = window.createOutputChannel(OutputChannelNames.jupyter()); - serviceManager.addSingletonInstance(IOutputChannel, standardOutputChannel, STANDARD_OUTPUT_CHANNEL); - serviceManager.addSingletonInstance(IOutputChannel, unitTestOutChannel, TEST_OUTPUT_CHANNEL); - serviceManager.addSingletonInstance(IOutputChannel, jupyterOutputChannel, JUPYTER_OUTPUT_CHANNEL); +async function activateLegacy(ext: ExtensionState, startupStopWatch: StopWatch): Promise { + const { legacyIOC } = ext; + const { serviceManager, serviceContainer } = legacyIOC; - // Core registrations (non-feature specific). - platformRegisterTypes(serviceManager); - processRegisterTypes(serviceManager); + // register "services" // We need to setup this property before any telemetry is sent const fs = serviceManager.get(IFileSystem); await setExtensionInstallTelemetryProperties(fs); const applicationEnv = serviceManager.get(IApplicationEnvironment); - const enableProposedApi = applicationEnv.packageJson.enableProposedApi; + const { enableProposedApi } = applicationEnv.packageJson; serviceManager.addSingletonInstance(UseProposedApi, enableProposedApi); // Feature specific registrations. - variableRegisterTypes(serviceManager); - unitTestsRegisterTypes(serviceManager); - lintersRegisterTypes(serviceManager); - interpretersRegisterTypes(serviceManager); - formattersRegisterTypes(serviceManager); installerRegisterTypes(serviceManager); commonRegisterTerminalTypes(serviceManager); debugConfigurationRegisterTypes(serviceManager); + tensorBoardRegisterTypes(serviceManager); - const configuration = serviceManager.get(IConfigurationService); - // We should start logging using the log level as soon as possible, so set it as soon as we can access the level. - // `IConfigurationService` may depend any of the registered types, so doing it after all registrations are finished. - // XXX Move this *after* abExperiments is activated? - setLoggingLevel(configuration.getSettings().logging.level); + const extensions = serviceContainer.get(IExtensions); + await setDefaultLanguageServer(extensions, serviceManager); - const abExperiments = serviceContainer.get(IExperimentsManager); - await abExperiments.activate(); - - // Register datascience types after experiments have loaded. - // To ensure we can register types based on experiments. - dataScienceRegisterTypes(serviceManager); - - const languageServerType = configuration.getSettings().languageServer; + // Settings are dependent on Experiment service, so we need to initialize it after experiments are activated. + serviceContainer.get(IConfigurationService).getSettings().register(); // Language feature registrations. - appRegisterTypes(serviceManager, languageServerType); + appRegisterTypes(serviceManager); providersRegisterTypes(serviceManager); - activationRegisterTypes(serviceManager, languageServerType); + activationRegisterTypes(serviceManager); // "initialize" "services" - const interpreterManager = serviceContainer.get(IInterpreterService); - interpreterManager.initialize(); - - const handlers = serviceManager.getAll(IDebugSessionEventHandlers); const disposables = serviceManager.get(IDisposableRegistry); - const dispatcher = new DebugSessionEventDispatcher(handlers, DebugService.instance, disposables); - dispatcher.registerEventHandlers(); - + const workspaceService = serviceContainer.get(IWorkspaceService); const cmdManager = serviceContainer.get(ICommandManager); - const outputChannel = serviceManager.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); - disposables.push(cmdManager.registerCommand(Commands.ViewOutput, () => outputChannel.show())); - cmdManager.executeCommand('setContext', 'python.vscode.channel', applicationEnv.channel).then(noop, noop); - - // Display progress of interpreter refreshes only after extension has activated. - serviceContainer.get(IInterpreterLocatorProgressHandler).register(); - serviceContainer.get(IInterpreterLocatorProgressService).register(); - serviceContainer.get(IApplicationDiagnostics).register(); - serviceContainer.get(ITestCodeNavigatorCommandHandler).register(); - serviceContainer.get(ITestExplorerCommandHandler).register(); - serviceContainer.get(ILanguageServerExtension).register(); - serviceContainer.get(ITestContextService).register(); - - // "activate" everything else - - const manager = serviceContainer.get(IExtensionActivationManager); - context.subscriptions.push(manager); - const activationPromise = manager.activate(); - serviceManager.get(ITerminalAutoActivation).register(); - const pythonSettings = configuration.getSettings(); + languages.setLanguageConfiguration(PYTHON_LANGUAGE, getLanguageConfiguration()); + if (workspaceService.isTrusted) { + const interpreterManager = serviceContainer.get(IInterpreterService); + interpreterManager.initialize(); + if (!workspaceService.isVirtualWorkspace) { + const handlers = serviceManager.getAll(IDebugSessionEventHandlers); + const dispatcher = new DebugSessionEventDispatcher(handlers, DebugService.instance, disposables); + dispatcher.registerEventHandlers(); + const outputChannel = serviceManager.get(ILogOutputChannel); + disposables.push(cmdManager.registerCommand(Commands.ViewOutput, () => outputChannel.show())); + cmdManager.executeCommand('setContext', 'python.vscode.channel', applicationEnv.channel).then(noop, noop); - activateSimplePythonRefactorProvider(context, standardOutputChannel, serviceContainer); + serviceContainer.get(IApplicationDiagnostics).register(); - const sortImports = serviceContainer.get(ISortImportsEditingProvider); - sortImports.registerCommands(); + serviceManager.get(ITerminalAutoActivation).register(); - serviceManager.get(ICodeExecutionManager).registerCommands(); + await registerPythonStartup(ext.context); - const workspaceService = serviceContainer.get(IWorkspaceService); - interpreterManager - .refresh(workspaceService.hasWorkspaceFolders ? workspaceService.workspaceFolders![0].uri : undefined) - .catch((ex) => traceError('Python Extension: interpreterManager.refresh', ex)); + serviceManager.get(ICodeExecutionManager).registerCommands(); - // Activate data science features - const dataScience = serviceManager.get(IDataScience); - dataScience.activate().ignoreErrors(); + disposables.push(new ReplProvider(serviceContainer)); - context.subscriptions.push(new LinterCommands(serviceManager)); + const terminalProvider = new TerminalProvider(serviceContainer); + terminalProvider.initialize(window.activeTerminal).ignoreErrors(); - languages.setLanguageConfiguration(PYTHON_LANGUAGE, getLanguageConfiguration()); + serviceContainer + .getAll(IDebugConfigurationService) + .forEach((debugConfigProvider) => { + disposables.push(debug.registerDebugConfigurationProvider(DebuggerTypeName, debugConfigProvider)); + }); + disposables.push(terminalProvider); - if (pythonSettings && pythonSettings.formatting && pythonSettings.formatting.provider !== 'internalConsole') { - const formatProvider = new PythonFormattingEditProvider(context, serviceContainer); - context.subscriptions.push(languages.registerDocumentFormattingEditProvider(PYTHON, formatProvider)); - context.subscriptions.push(languages.registerDocumentRangeFormattingEditProvider(PYTHON, formatProvider)); + registerCreateEnvironmentTriggers(disposables); + initializePersistentStateForTriggers(ext.context); + } } - const deprecationMgr = serviceContainer.get(IFeatureDeprecationManager); - deprecationMgr.initialize(); - context.subscriptions.push(deprecationMgr); - - context.subscriptions.push(new ReplProvider(serviceContainer)); - - const terminalProvider = new TerminalProvider(serviceContainer); - terminalProvider.initialize(window.activeTerminal).ignoreErrors(); - context.subscriptions.push(terminalProvider); - - context.subscriptions.push( - languages.registerCodeActionsProvider(PYTHON, new PythonCodeActionProvider(), { - providedCodeActionKinds: [CodeActionKind.SourceOrganizeImports] - }) - ); + // "activate" everything else - serviceContainer.getAll(IDebugConfigurationService).forEach((debugConfigProvider) => { - context.subscriptions.push(debug.registerDebugConfigurationProvider(DebuggerTypeName, debugConfigProvider)); - }); + const manager = serviceContainer.get(IExtensionActivationManager); + disposables.push(manager); - serviceContainer.get(IDebuggerBanner).initialize(); + const activationPromise = manager.activate(startupStopWatch); - return { activationPromise }; + return { fullyReady: activationPromise }; } diff --git a/src/client/extensionInit.ts b/src/client/extensionInit.ts index 43336cef3229..b161643d2d97 100644 --- a/src/client/extensionInit.ts +++ b/src/client/extensionInit.ts @@ -3,54 +3,98 @@ 'use strict'; -// tslint:disable:max-func-body-length - import { Container } from 'inversify'; -import { Disposable, Memento } from 'vscode'; - +import { Disposable, Memento, window } from 'vscode'; +import { registerTypes as platformRegisterTypes } from './common/platform/serviceRegistry'; +import { registerTypes as processRegisterTypes } from './common/process/serviceRegistry'; import { registerTypes as commonRegisterTypes } from './common/serviceRegistry'; -import { GLOBAL_MEMENTO, IDisposableRegistry, IExtensionContext, IMemento, WORKSPACE_MEMENTO } from './common/types'; +import { registerTypes as interpretersRegisterTypes } from './interpreter/serviceRegistry'; +import { + GLOBAL_MEMENTO, + IDisposableRegistry, + IExtensionContext, + IMemento, + ILogOutputChannel, + WORKSPACE_MEMENTO, +} from './common/types'; +import { registerTypes as variableRegisterTypes } from './common/variables/serviceRegistry'; +import { OutputChannelNames } from './common/utils/localize'; +import { ExtensionState } from './components'; import { ServiceContainer } from './ioc/container'; import { ServiceManager } from './ioc/serviceManager'; import { IServiceContainer, IServiceManager } from './ioc/types'; -import { activate as activatePythonEnvironments } from './pythonEnvironments'; +import * as pythonEnvironments from './pythonEnvironments'; +import { IDiscoveryAPI } from './pythonEnvironments/base/locator'; +import { registerLogger } from './logging'; +import { OutputChannelLogger } from './logging/outputChannelLogger'; // The code in this module should do nothing more complex than register // objects to DI and simple init (e.g. no side effects). That implies // that constructors are likewise simple and do no work. It also means // that it is inherently synchronous. -export function initializeGlobals(context: IExtensionContext): [IServiceManager, IServiceContainer] { - const cont = new Container(); +export function initializeGlobals( + // This is stored in ExtensionState. + context: IExtensionContext, +): ExtensionState { + const disposables: IDisposableRegistry = context.subscriptions; + const cont = new Container({ skipBaseClassChecks: true }); const serviceManager = new ServiceManager(cont); const serviceContainer = new ServiceContainer(cont); serviceManager.addSingletonInstance(IServiceContainer, serviceContainer); serviceManager.addSingletonInstance(IServiceManager, serviceManager); - serviceManager.addSingletonInstance(IDisposableRegistry, context.subscriptions); + serviceManager.addSingletonInstance(IDisposableRegistry, disposables); serviceManager.addSingletonInstance(IMemento, context.globalState, GLOBAL_MEMENTO); serviceManager.addSingletonInstance(IMemento, context.workspaceState, WORKSPACE_MEMENTO); serviceManager.addSingletonInstance(IExtensionContext, context); - return [serviceManager, serviceContainer]; + const standardOutputChannel = window.createOutputChannel(OutputChannelNames.python, { log: true }); + disposables.push(standardOutputChannel); + disposables.push(registerLogger(new OutputChannelLogger(standardOutputChannel))); + + serviceManager.addSingletonInstance(ILogOutputChannel, standardOutputChannel); + + return { + context, + disposables, + legacyIOC: { serviceManager, serviceContainer }, + }; } -export function initializeCommon( - _context: IExtensionContext, - serviceManager: IServiceManager, - _serviceContainer: IServiceContainer -): void { +/** + * Registers standard utils like experiment and platform code which are fundamental to the extension. + */ +export function initializeStandard(ext: ExtensionState): void { + const { serviceManager } = ext.legacyIOC; // Core registrations (non-feature specific). commonRegisterTypes(serviceManager); + variableRegisterTypes(serviceManager); + platformRegisterTypes(serviceManager); + processRegisterTypes(serviceManager); + interpretersRegisterTypes(serviceManager); // We will be pulling other code over from activateLegacy(). } -export function initializeComponents( - _context: IExtensionContext, - serviceManager: IServiceManager, - serviceContainer: IServiceContainer -) { - activatePythonEnvironments(serviceManager, serviceContainer); +/** + * The set of public APIs from initialized components. + */ +export type Components = { + pythonEnvs: IDiscoveryAPI; +}; + +/** + * Initialize all components in the extension. + */ +export async function initializeComponents(ext: ExtensionState): Promise { + const pythonEnvs = await pythonEnvironments.initialize(ext); + + // Other component initializers go here. + // We will be factoring them out of activateLegacy(). + + return { + pythonEnvs, + }; } diff --git a/src/client/formatters/autoPep8Formatter.ts b/src/client/formatters/autoPep8Formatter.ts deleted file mode 100644 index e086e37f549b..000000000000 --- a/src/client/formatters/autoPep8Formatter.ts +++ /dev/null @@ -1,44 +0,0 @@ -import * as vscode from 'vscode'; -import { Product } from '../common/installer/productInstaller'; -import { IConfigurationService } from '../common/types'; -import { StopWatch } from '../common/utils/stopWatch'; -import { IServiceContainer } from '../ioc/types'; -import { sendTelemetryWhenDone } from '../telemetry'; -import { EventName } from '../telemetry/constants'; -import { BaseFormatter } from './baseFormatter'; - -export class AutoPep8Formatter extends BaseFormatter { - constructor(serviceContainer: IServiceContainer) { - super('autopep8', Product.autopep8, serviceContainer); - } - - public formatDocument( - document: vscode.TextDocument, - options: vscode.FormattingOptions, - token: vscode.CancellationToken, - range?: vscode.Range - ): Thenable { - const stopWatch = new StopWatch(); - const settings = this.serviceContainer - .get(IConfigurationService) - .getSettings(document.uri); - const hasCustomArgs = - Array.isArray(settings.formatting.autopep8Args) && settings.formatting.autopep8Args.length > 0; - const formatSelection = range ? !range.isEmpty : false; - - const autoPep8Args = ['--diff']; - if (formatSelection) { - // tslint:disable-next-line:no-non-null-assertion - autoPep8Args.push( - ...['--line-range', (range!.start.line + 1).toString(), (range!.end.line + 1).toString()] - ); - } - const promise = super.provideDocumentFormattingEdits(document, options, token, autoPep8Args); - sendTelemetryWhenDone(EventName.FORMAT, promise, stopWatch, { - tool: 'autopep8', - hasCustomArgs, - formatSelection - }); - return promise; - } -} diff --git a/src/client/formatters/baseFormatter.ts b/src/client/formatters/baseFormatter.ts deleted file mode 100644 index 435a5503337c..000000000000 --- a/src/client/formatters/baseFormatter.ts +++ /dev/null @@ -1,148 +0,0 @@ -import * as path from 'path'; -import * as vscode from 'vscode'; -import { IApplicationShell, IWorkspaceService } from '../common/application/types'; -import { STANDARD_OUTPUT_CHANNEL } from '../common/constants'; -import '../common/extensions'; -import { isNotInstalledError } from '../common/helpers'; -import { traceError } from '../common/logger'; -import { IFileSystem } from '../common/platform/types'; -import { IPythonToolExecutionService } from '../common/process/types'; -import { IDisposableRegistry, IInstaller, IOutputChannel, Product } from '../common/types'; -import { isNotebookCell } from '../common/utils/misc'; -import { IServiceContainer } from '../ioc/types'; -import { getTempFileWithDocumentContents, getTextEditsFromPatch } from './../common/editor'; -import { IFormatterHelper } from './types'; - -export abstract class BaseFormatter { - protected readonly outputChannel: vscode.OutputChannel; - protected readonly workspace: IWorkspaceService; - private readonly helper: IFormatterHelper; - - constructor(public Id: string, private product: Product, protected serviceContainer: IServiceContainer) { - this.outputChannel = serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); - this.helper = serviceContainer.get(IFormatterHelper); - this.workspace = serviceContainer.get(IWorkspaceService); - } - - public abstract formatDocument( - document: vscode.TextDocument, - options: vscode.FormattingOptions, - token: vscode.CancellationToken, - range?: vscode.Range - ): Thenable; - protected getDocumentPath(document: vscode.TextDocument, fallbackPath: string) { - if (path.basename(document.uri.fsPath) === document.uri.fsPath) { - return fallbackPath; - } - return path.dirname(document.fileName); - } - protected getWorkspaceUri(document: vscode.TextDocument) { - const workspaceFolder = this.workspace.getWorkspaceFolder(document.uri); - if (workspaceFolder) { - return workspaceFolder.uri; - } - const folders = this.workspace.workspaceFolders; - if (Array.isArray(folders) && folders.length > 0) { - return folders[0].uri; - } - return vscode.Uri.file(__dirname); - } - protected async provideDocumentFormattingEdits( - document: vscode.TextDocument, - _options: vscode.FormattingOptions, - token: vscode.CancellationToken, - args: string[], - cwd?: string - ): Promise { - if (typeof cwd !== 'string' || cwd.length === 0) { - cwd = this.getWorkspaceUri(document).fsPath; - } - - // autopep8 and yapf have the ability to read from the process input stream and return the formatted code out of the output stream. - // However they don't support returning the diff of the formatted text when reading data from the input stream. - // Yet getting text formatted that way avoids having to create a temporary file, however the diffing will have - // to be done here in node (extension), i.e. extension CPU, i.e. less responsive solution. - // Also, always create temp files for Notebook cells. - const tempFile = await this.createTempFile(document); - if (this.checkCancellation(document.fileName, tempFile, token)) { - return []; - } - - const executionInfo = this.helper.getExecutionInfo(this.product, args, document.uri); - executionInfo.args.push(tempFile); - const pythonToolsExecutionService = this.serviceContainer.get( - IPythonToolExecutionService - ); - const promise = pythonToolsExecutionService - .exec(executionInfo, { cwd, throwOnStdErr: false, token }, document.uri) - .then((output) => output.stdout) - .then((data) => { - if (this.checkCancellation(document.fileName, tempFile, token)) { - return [] as vscode.TextEdit[]; - } - return getTextEditsFromPatch(document.getText(), data); - }) - .catch((error) => { - if (this.checkCancellation(document.fileName, tempFile, token)) { - return [] as vscode.TextEdit[]; - } - // tslint:disable-next-line:no-empty - this.handleError(this.Id, error, document.uri).catch(() => {}); - return [] as vscode.TextEdit[]; - }) - .then((edits) => { - this.deleteTempFile(document.fileName, tempFile).ignoreErrors(); - return edits; - }); - - const appShell = this.serviceContainer.get(IApplicationShell); - const disposableRegistry = this.serviceContainer.get(IDisposableRegistry); - const disposable = appShell.setStatusBarMessage(`Formatting with ${this.Id}`, promise); - disposableRegistry.push(disposable); - return promise; - } - - protected async handleError(_expectedFileName: string, error: Error, resource?: vscode.Uri) { - let customError = `Formatting with ${this.Id} failed.`; - - if (isNotInstalledError(error)) { - const installer = this.serviceContainer.get(IInstaller); - const isInstalled = await installer.isInstalled(this.product, resource); - if (!isInstalled) { - customError += `\nYou could either install the '${this.Id}' formatter, turn it off or use another formatter.`; - installer - .promptToInstall(this.product, resource) - .catch((ex) => traceError('Python Extension: promptToInstall', ex)); - } - } - - this.outputChannel.appendLine(`\n${customError}\n${error}`); - } - - /** - * Always create a temporary file when formatting notebook cells. - * This is because there is no physical file associated with notebook cells (they are all virtual). - */ - private async createTempFile(document: vscode.TextDocument): Promise { - const fs = this.serviceContainer.get(IFileSystem); - return document.isDirty || isNotebookCell(document) - ? getTempFileWithDocumentContents(document, fs) - : document.fileName; - } - - private deleteTempFile(originalFile: string, tempFile: string): Promise { - if (originalFile !== tempFile) { - const fs = this.serviceContainer.get(IFileSystem); - return fs.deleteFile(tempFile); - } - return Promise.resolve(); - } - - private checkCancellation(originalFile: string, tempFile: string, token?: vscode.CancellationToken): boolean { - if (token && token.isCancellationRequested) { - this.deleteTempFile(originalFile, tempFile).ignoreErrors(); - return true; - } - return false; - } -} diff --git a/src/client/formatters/blackFormatter.ts b/src/client/formatters/blackFormatter.ts deleted file mode 100644 index 4c13ef1acbc3..000000000000 --- a/src/client/formatters/blackFormatter.ts +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as vscode from 'vscode'; -import { IApplicationShell } from '../common/application/types'; -import { Product } from '../common/installer/productInstaller'; -import { IConfigurationService } from '../common/types'; -import { noop } from '../common/utils/misc'; -import { StopWatch } from '../common/utils/stopWatch'; -import { IServiceContainer } from '../ioc/types'; -import { sendTelemetryWhenDone } from '../telemetry'; -import { EventName } from '../telemetry/constants'; -import { BaseFormatter } from './baseFormatter'; - -export class BlackFormatter extends BaseFormatter { - constructor(serviceContainer: IServiceContainer) { - super('black', Product.black, serviceContainer); - } - - public async formatDocument( - document: vscode.TextDocument, - options: vscode.FormattingOptions, - token: vscode.CancellationToken, - range?: vscode.Range - ): Promise { - const stopWatch = new StopWatch(); - const settings = this.serviceContainer - .get(IConfigurationService) - .getSettings(document.uri); - const hasCustomArgs = Array.isArray(settings.formatting.blackArgs) && settings.formatting.blackArgs.length > 0; - const formatSelection = range ? !range.isEmpty : false; - - if (formatSelection) { - const shell = this.serviceContainer.get(IApplicationShell); - // Black does not support partial formatting on purpose. - shell.showErrorMessage('Black does not support the "Format Selection" command').then(noop, noop); - return []; - } - - const blackArgs = ['--diff', '--quiet']; - const promise = super.provideDocumentFormattingEdits(document, options, token, blackArgs); - sendTelemetryWhenDone(EventName.FORMAT, promise, stopWatch, { tool: 'black', hasCustomArgs, formatSelection }); - return promise; - } -} diff --git a/src/client/formatters/dummyFormatter.ts b/src/client/formatters/dummyFormatter.ts deleted file mode 100644 index b82496a5f1b0..000000000000 --- a/src/client/formatters/dummyFormatter.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as vscode from 'vscode'; -import { Product } from '../common/types'; -import { IServiceContainer } from '../ioc/types'; -import { BaseFormatter } from './baseFormatter'; - -export class DummyFormatter extends BaseFormatter { - constructor(serviceContainer: IServiceContainer) { - super('none', Product.yapf, serviceContainer); - } - - public formatDocument( - _document: vscode.TextDocument, - _options: vscode.FormattingOptions, - _token: vscode.CancellationToken, - _range?: vscode.Range - ): Thenable { - return Promise.resolve([]); - } -} diff --git a/src/client/formatters/helper.ts b/src/client/formatters/helper.ts deleted file mode 100644 index 3c09be283815..000000000000 --- a/src/client/formatters/helper.ts +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { Uri } from 'vscode'; -import { ExecutionInfo, IConfigurationService, IFormattingSettings, Product } from '../common/types'; -import { IServiceContainer } from '../ioc/types'; -import { FormatterId, FormatterSettingsPropertyNames, IFormatterHelper } from './types'; - -@injectable() -export class FormatterHelper implements IFormatterHelper { - constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {} - public translateToId(formatter: Product): FormatterId { - switch (formatter) { - case Product.autopep8: - return 'autopep8'; - case Product.black: - return 'black'; - case Product.yapf: - return 'yapf'; - default: { - throw new Error(`Unrecognized Formatter '${formatter}'`); - } - } - } - public getSettingsPropertyNames(formatter: Product): FormatterSettingsPropertyNames { - const id = this.translateToId(formatter); - return { - argsName: `${id}Args` as keyof IFormattingSettings, - pathName: `${id}Path` as keyof IFormattingSettings - }; - } - public getExecutionInfo(formatter: Product, customArgs: string[], resource?: Uri): ExecutionInfo { - const settings = this.serviceContainer.get(IConfigurationService).getSettings(resource); - const names = this.getSettingsPropertyNames(formatter); - - const execPath = settings.formatting[names.pathName] as string; - let args: string[] = Array.isArray(settings.formatting[names.argsName]) - ? (settings.formatting[names.argsName] as string[]) - : []; - args = args.concat(customArgs); - - let moduleName: string | undefined; - - // If path information is not available, then treat it as a module, - if (path.basename(execPath) === execPath) { - moduleName = execPath; - } - - return { execPath, moduleName, args, product: formatter }; - } -} diff --git a/src/client/formatters/lineFormatter.ts b/src/client/formatters/lineFormatter.ts deleted file mode 100644 index e6a3bb6f1728..000000000000 --- a/src/client/formatters/lineFormatter.ts +++ /dev/null @@ -1,484 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// tslint:disable-next-line:import-name -import Char from 'typescript-char'; -import { Position, Range, TextDocument } from 'vscode'; -import { BraceCounter } from '../language/braceCounter'; -import { TextBuilder } from '../language/textBuilder'; -import { TextRangeCollection } from '../language/textRangeCollection'; -import { Tokenizer } from '../language/tokenizer'; -import { ITextRangeCollection, IToken, TokenType } from '../language/types'; - -const keywordsWithSpaceBeforeBrace = [ - 'and', - 'as', - 'assert', - 'await', - 'del', - 'except', - 'elif', - 'for', - 'from', - 'global', - 'if', - 'import', - 'in', - 'is', - 'lambda', - 'nonlocal', - 'not', - 'or', - 'raise', - 'return', - 'while', - 'with', - 'yield' -]; - -export class LineFormatter { - private builder = new TextBuilder(); - private tokens: ITextRangeCollection = new TextRangeCollection([]); - private braceCounter = new BraceCounter(); - private text = ''; - private document?: TextDocument; - private lineNumber = 0; - - // tslint:disable-next-line:cyclomatic-complexity - public formatLine(document: TextDocument, lineNumber: number): string { - this.document = document; - this.lineNumber = lineNumber; - this.text = document.lineAt(lineNumber).text; - this.tokens = new Tokenizer().tokenize(this.text); - this.builder = new TextBuilder(); - this.braceCounter = new BraceCounter(); - - if (this.tokens.count === 0) { - return this.text; - } - - const ws = this.text.substr(0, this.tokens.getItemAt(0).start); - if (ws.length > 0) { - this.builder.append(ws); // Preserve leading indentation. - } - - for (let i = 0; i < this.tokens.count; i += 1) { - const t = this.tokens.getItemAt(i); - const prev = i > 0 ? this.tokens.getItemAt(i - 1) : undefined; - const next = i < this.tokens.count - 1 ? this.tokens.getItemAt(i + 1) : undefined; - - switch (t.type) { - case TokenType.Operator: - this.handleOperator(i); - break; - - case TokenType.Comma: - this.builder.append(','); - if (next && !this.isCloseBraceType(next.type) && next.type !== TokenType.Colon) { - this.builder.softAppendSpace(); - } - break; - - case TokenType.Identifier: - if ( - prev && - !this.isOpenBraceType(prev.type) && - prev.type !== TokenType.Colon && - prev.type !== TokenType.Operator - ) { - this.builder.softAppendSpace(); - } - const id = this.text.substring(t.start, t.end); - this.builder.append(id); - if (this.isKeywordWithSpaceBeforeBrace(id) && next && this.isOpenBraceType(next.type)) { - // for x in () - this.builder.softAppendSpace(); - } - break; - - case TokenType.Colon: - // x: 1 if not in slice, x[1:y] if inside the slice. - this.builder.append(':'); - if (!this.braceCounter.isOpened(TokenType.OpenBracket) && next && next.type !== TokenType.Colon) { - // Not inside opened [[ ... ] sequence. - this.builder.softAppendSpace(); - } - break; - - case TokenType.Comment: - // Add 2 spaces before in-line comment per PEP guidelines. - if (prev) { - this.builder.softAppendSpace(2); - } - this.builder.append(this.text.substring(t.start, t.end)); - break; - - case TokenType.Semicolon: - this.builder.append(';'); - break; - - default: - this.handleOther(t, i); - break; - } - } - return this.builder.getText(); - } - - // tslint:disable-next-line:cyclomatic-complexity - private handleOperator(index: number): void { - const t = this.tokens.getItemAt(index); - const prev = index > 0 ? this.tokens.getItemAt(index - 1) : undefined; - const opCode = this.text.charCodeAt(t.start); - const next = index < this.tokens.count - 1 ? this.tokens.getItemAt(index + 1) : undefined; - - if (t.length === 1) { - switch (opCode) { - case Char.Equal: - this.handleEqual(t, index); - return; - case Char.Period: - if (prev && this.isKeyword(prev, 'from')) { - this.builder.softAppendSpace(); - } - this.builder.append('.'); - if (next && this.isKeyword(next, 'import')) { - this.builder.softAppendSpace(); - } - return; - case Char.At: - if (prev) { - // Binary case - this.builder.softAppendSpace(); - this.builder.append('@'); - this.builder.softAppendSpace(); - } else { - this.builder.append('@'); - } - return; - case Char.ExclamationMark: - this.builder.append('!'); - return; - case Char.Asterisk: - if (prev && this.isKeyword(prev, 'lambda')) { - this.builder.softAppendSpace(); - this.builder.append('*'); - return; - } - if (this.handleStarOperator(t, prev!)) { - return; - } - break; - default: - break; - } - } else if (t.length === 2) { - if ( - this.text.charCodeAt(t.start) === Char.Asterisk && - this.text.charCodeAt(t.start + 1) === Char.Asterisk - ) { - if (this.handleStarOperator(t, prev!)) { - return; - } - } - } - - // Do not append space if operator is preceded by '(' or ',' as in foo(**kwarg) - if (prev && (this.isOpenBraceType(prev.type) || prev.type === TokenType.Comma)) { - this.builder.append(this.text.substring(t.start, t.end)); - return; - } - - this.builder.softAppendSpace(); - this.builder.append(this.text.substring(t.start, t.end)); - - // Check unary case - if (prev && prev.type === TokenType.Operator) { - if (opCode === Char.Hyphen || opCode === Char.Plus || opCode === Char.Tilde) { - return; - } - } - this.builder.softAppendSpace(); - } - - private handleStarOperator(current: IToken, prev: IToken): boolean { - if ( - this.text.charCodeAt(current.start) === Char.Asterisk && - this.text.charCodeAt(current.start + 1) === Char.Asterisk - ) { - if (!prev || (prev.type !== TokenType.Identifier && prev.type !== TokenType.Number)) { - this.builder.append('**'); - return true; - } - if (prev && this.isKeyword(prev, 'lambda')) { - this.builder.softAppendSpace(); - this.builder.append('**'); - return true; - } - } - // Check previous line for the **/* condition - const lastLine = this.getPreviousLineTokens(); - const lastToken = lastLine && lastLine.count > 0 ? lastLine.getItemAt(lastLine.count - 1) : undefined; - if (lastToken && (this.isOpenBraceType(lastToken.type) || lastToken.type === TokenType.Comma)) { - this.builder.append(this.text.substring(current.start, current.end)); - return true; - } - return false; - } - - private handleEqual(_t: IToken, index: number): void { - if (this.isMultipleStatements(index) && !this.braceCounter.isOpened(TokenType.OpenBrace)) { - // x = 1; x, y = y, x - this.builder.softAppendSpace(); - this.builder.append('='); - this.builder.softAppendSpace(); - return; - } - - // Check if this is = in function arguments. If so, do not add spaces around it. - if (this.isEqualsInsideArguments(index)) { - this.builder.append('='); - return; - } - - this.builder.softAppendSpace(); - this.builder.append('='); - this.builder.softAppendSpace(); - } - - private handleOther(t: IToken, index: number): void { - if (this.isBraceType(t.type)) { - this.braceCounter.countBrace(t); - this.builder.append(this.text.substring(t.start, t.end)); - return; - } - - const prev = index > 0 ? this.tokens.getItemAt(index - 1) : undefined; - if ( - prev && - prev.length === 1 && - this.text.charCodeAt(prev.start) === Char.Equal && - this.isEqualsInsideArguments(index - 1) - ) { - // Don't add space around = inside function arguments. - this.builder.append(this.text.substring(t.start, t.end)); - return; - } - - if (prev && (this.isOpenBraceType(prev.type) || prev.type === TokenType.Colon)) { - // Don't insert space after (, [ or { . - this.builder.append(this.text.substring(t.start, t.end)); - return; - } - - if ( - t.type === TokenType.Number && - prev && - prev.type === TokenType.Operator && - prev.length === 1 && - this.text.charCodeAt(prev.start) === Char.Tilde - ) { - // Special case for ~ before numbers - this.builder.append(this.text.substring(t.start, t.end)); - return; - } - - if (t.type === TokenType.Unknown) { - this.handleUnknown(t); - } else { - // In general, keep tokens separated. - this.builder.softAppendSpace(); - this.builder.append(this.text.substring(t.start, t.end)); - } - } - - private handleUnknown(t: IToken): void { - const prevChar = t.start > 0 ? this.text.charCodeAt(t.start - 1) : 0; - if (prevChar === Char.Space || prevChar === Char.Tab) { - this.builder.softAppendSpace(); - } - this.builder.append(this.text.substring(t.start, t.end)); - - const nextChar = t.end < this.text.length - 1 ? this.text.charCodeAt(t.end) : 0; - if (nextChar === Char.Space || nextChar === Char.Tab) { - this.builder.softAppendSpace(); - } - } - - // tslint:disable-next-line:cyclomatic-complexity - private isEqualsInsideArguments(index: number): boolean { - if (index < 1) { - return false; - } - - // We are looking for IDENT = ? - const prev = this.tokens.getItemAt(index - 1); - if (prev.type !== TokenType.Identifier) { - return false; - } - - if (index > 1 && this.tokens.getItemAt(index - 2).type === TokenType.Colon) { - return false; // Type hint should have spaces around like foo(x: int = 1) per PEP 8 - } - - return this.isInsideFunctionArguments(this.tokens.getItemAt(index).start); - } - - private isOpenBraceType(type: TokenType): boolean { - return type === TokenType.OpenBrace || type === TokenType.OpenBracket || type === TokenType.OpenCurly; - } - private isCloseBraceType(type: TokenType): boolean { - return type === TokenType.CloseBrace || type === TokenType.CloseBracket || type === TokenType.CloseCurly; - } - private isBraceType(type: TokenType): boolean { - return this.isOpenBraceType(type) || this.isCloseBraceType(type); - } - - private isMultipleStatements(index: number): boolean { - for (let i = index; i >= 0; i -= 1) { - if (this.tokens.getItemAt(i).type === TokenType.Semicolon) { - return true; - } - } - return false; - } - - private isKeywordWithSpaceBeforeBrace(s: string): boolean { - return keywordsWithSpaceBeforeBrace.indexOf(s) >= 0; - } - private isKeyword(t: IToken, keyword: string): boolean { - return ( - t.type === TokenType.Identifier && - t.length === keyword.length && - this.text.substr(t.start, t.length) === keyword - ); - } - - // tslint:disable-next-line:cyclomatic-complexity - private isInsideFunctionArguments(position: number): boolean { - if (!this.document) { - return false; // unable to determine - } - - // Walk up until beginning of the document or line with 'def IDENT(' or line ending with : - // IDENT( by itself is not reliable since they can be nested in IDENT(IDENT(a), x=1) - let start = new Position(0, 0); - for (let i = this.lineNumber; i >= 0; i -= 1) { - const line = this.document.lineAt(i); - const lineTokens = new Tokenizer().tokenize(line.text); - if (lineTokens.count === 0) { - continue; - } - // 'def IDENT(' - const first = lineTokens.getItemAt(0); - if ( - lineTokens.count >= 3 && - first.length === 3 && - line.text.substr(first.start, first.length) === 'def' && - lineTokens.getItemAt(1).type === TokenType.Identifier && - lineTokens.getItemAt(2).type === TokenType.OpenBrace - ) { - start = line.range.start; - break; - } - - if (lineTokens.count > 0 && i < this.lineNumber) { - // One of previous lines ends with : - const last = lineTokens.getItemAt(lineTokens.count - 1); - if (last.type === TokenType.Colon) { - start = this.document.lineAt(i + 1).range.start; - break; - } else if (lineTokens.count > 1) { - const beforeLast = lineTokens.getItemAt(lineTokens.count - 2); - if (beforeLast.type === TokenType.Colon && last.type === TokenType.Comment) { - start = this.document.lineAt(i + 1).range.start; - break; - } - } - } - } - - // Now tokenize from the nearest reasonable point - const currentLine = this.document.lineAt(this.lineNumber); - const text = this.document.getText(new Range(start, currentLine.range.end)); - const tokens = new Tokenizer().tokenize(text); - - // Translate position in the line being formatted to the position in the tokenized block - position = this.document.offsetAt(currentLine.range.start) + position - this.document.offsetAt(start); - - // Walk tokens locating narrowest function signature as in IDENT( | ) - let funcCallStartIndex = -1; - let funcCallEndIndex = -1; - for (let i = 0; i < tokens.count - 1; i += 1) { - const t = tokens.getItemAt(i); - if (t.type === TokenType.Identifier) { - const next = tokens.getItemAt(i + 1); - if ( - next.type === TokenType.OpenBrace && - !this.isKeywordWithSpaceBeforeBrace(text.substr(t.start, t.length)) - ) { - // We are at IDENT(, try and locate the closing brace - let closeBraceIndex = this.findClosingBrace(tokens, i + 1); - // Closing brace is not required in case construct is not yet terminated - closeBraceIndex = closeBraceIndex > 0 ? closeBraceIndex : tokens.count - 1; - // Are we in range? - if (position > next.start && position < tokens.getItemAt(closeBraceIndex).start) { - funcCallStartIndex = i; - funcCallEndIndex = closeBraceIndex; - } - } - } - } - // Did we find anything? - if (funcCallStartIndex < 0) { - // No? See if we are between 'lambda' and ':' - for (let i = 0; i < tokens.count; i += 1) { - const t = tokens.getItemAt(i); - if (t.type === TokenType.Identifier && text.substr(t.start, t.length) === 'lambda') { - if (position < t.start) { - break; // Position is before the nearest 'lambda' - } - let colonIndex = this.findNearestColon(tokens, i + 1); - // Closing : is not required in case construct is not yet terminated - colonIndex = colonIndex > 0 ? colonIndex : tokens.count - 1; - if (position > t.start && position < tokens.getItemAt(colonIndex).start) { - funcCallStartIndex = i; - funcCallEndIndex = colonIndex; - } - } - } - } - return funcCallStartIndex >= 0 && funcCallEndIndex > 0; - } - - private findNearestColon(tokens: ITextRangeCollection, index: number): number { - for (let i = index; i < tokens.count; i += 1) { - if (tokens.getItemAt(i).type === TokenType.Colon) { - return i; - } - } - return -1; - } - - private findClosingBrace(tokens: ITextRangeCollection, index: number): number { - const braceCounter = new BraceCounter(); - for (let i = index; i < tokens.count; i += 1) { - const t = tokens.getItemAt(i); - if (t.type === TokenType.OpenBrace || t.type === TokenType.CloseBrace) { - braceCounter.countBrace(t); - } - if (braceCounter.count === 0) { - return i; - } - } - return -1; - } - - private getPreviousLineTokens(): ITextRangeCollection | undefined { - if (!this.document || this.lineNumber === 0) { - return undefined; // unable to determine - } - const line = this.document.lineAt(this.lineNumber - 1); - return new Tokenizer().tokenize(line.text); - } -} diff --git a/src/client/formatters/serviceRegistry.ts b/src/client/formatters/serviceRegistry.ts deleted file mode 100644 index 196e6c806b5f..000000000000 --- a/src/client/formatters/serviceRegistry.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { IServiceManager } from '../ioc/types'; -import { FormatterHelper } from './helper'; -import { IFormatterHelper } from './types'; - -export function registerTypes(serviceManager: IServiceManager) { - serviceManager.addSingleton(IFormatterHelper, FormatterHelper); -} diff --git a/src/client/formatters/types.ts b/src/client/formatters/types.ts deleted file mode 100644 index 7f4bcf5b7524..000000000000 --- a/src/client/formatters/types.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { Uri } from 'vscode'; -import { ExecutionInfo, IFormattingSettings, Product } from '../common/types'; - -export const IFormatterHelper = Symbol('IFormatterHelper'); - -export type FormatterId = 'autopep8' | 'black' | 'yapf'; - -export type FormatterSettingsPropertyNames = { - argsName: keyof IFormattingSettings; - pathName: keyof IFormattingSettings; -}; - -export interface IFormatterHelper { - translateToId(formatter: Product): FormatterId; - getSettingsPropertyNames(formatter: Product): FormatterSettingsPropertyNames; - getExecutionInfo(formatter: Product, customArgs: string[], resource?: Uri): ExecutionInfo; -} diff --git a/src/client/formatters/yapfFormatter.ts b/src/client/formatters/yapfFormatter.ts deleted file mode 100644 index 8bf0d3ad413b..000000000000 --- a/src/client/formatters/yapfFormatter.ts +++ /dev/null @@ -1,39 +0,0 @@ -import * as vscode from 'vscode'; -import { IConfigurationService, Product } from '../common/types'; -import { StopWatch } from '../common/utils/stopWatch'; -import { IServiceContainer } from '../ioc/types'; -import { sendTelemetryWhenDone } from '../telemetry'; -import { EventName } from '../telemetry/constants'; -import { BaseFormatter } from './baseFormatter'; - -export class YapfFormatter extends BaseFormatter { - constructor(serviceContainer: IServiceContainer) { - super('yapf', Product.yapf, serviceContainer); - } - - public formatDocument( - document: vscode.TextDocument, - options: vscode.FormattingOptions, - token: vscode.CancellationToken, - range?: vscode.Range - ): Thenable { - const stopWatch = new StopWatch(); - const settings = this.serviceContainer - .get(IConfigurationService) - .getSettings(document.uri); - const hasCustomArgs = Array.isArray(settings.formatting.yapfArgs) && settings.formatting.yapfArgs.length > 0; - const formatSelection = range ? !range.isEmpty : false; - - const yapfArgs = ['--diff']; - if (formatSelection) { - // tslint:disable-next-line:no-non-null-assertion - yapfArgs.push(...['--lines', `${range!.start.line + 1}-${range!.end.line + 1}`]); - } - // Yapf starts looking for config file starting from the file path. - const fallbarFolder = this.getWorkspaceUri(document).fsPath; - const cwd = this.getDocumentPath(document, fallbarFolder); - const promise = super.provideDocumentFormattingEdits(document, options, token, yapfArgs, cwd); - sendTelemetryWhenDone(EventName.FORMAT, promise, stopWatch, { tool: 'yapf', hasCustomArgs, formatSelection }); - return promise; - } -} diff --git a/src/client/interpreter/activation/preWarmVariables.ts b/src/client/interpreter/activation/preWarmVariables.ts deleted file mode 100644 index 1532b152eaab..000000000000 --- a/src/client/interpreter/activation/preWarmVariables.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IExtensionSingleActivationService } from '../../activation/types'; -import '../../common/extensions'; -import { IInterpreterService } from '../contracts'; -import { IEnvironmentActivationService } from './types'; - -@injectable() -export class PreWarmActivatedEnvironmentVariables implements IExtensionSingleActivationService { - constructor( - @inject(IEnvironmentActivationService) private readonly activationService: IEnvironmentActivationService, - @inject(IInterpreterService) private readonly interpreterService: IInterpreterService - ) {} - public async activate(): Promise { - this.interpreterService.onDidChangeInterpreter(() => - this.activationService.getActivatedEnvironmentVariables(undefined).ignoreErrors() - ); - this.activationService.getActivatedEnvironmentVariables(undefined).ignoreErrors(); - } -} diff --git a/src/client/interpreter/activation/service.ts b/src/client/interpreter/activation/service.ts index e8355ee14ee3..f47575cad60b 100644 --- a/src/client/interpreter/activation/service.ts +++ b/src/client/interpreter/activation/service.ts @@ -1,13 +1,16 @@ +/* eslint-disable max-classes-per-file */ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + 'use strict'; + import '../../common/extensions'; +import * as path from 'path'; import { inject, injectable } from 'inversify'; import { IWorkspaceService } from '../../common/application/types'; import { PYTHON_WARNINGS } from '../../common/constants'; -import { LogOptions, traceDecorators, traceError, traceInfo, traceVerbose } from '../../common/logger'; import { IPlatformService } from '../../common/platform/types'; import * as internalScripts from '../../common/process/internal/scripts'; import { ExecutionResult, IProcessServiceFactory } from '../../common/process/types'; @@ -16,28 +19,44 @@ import { ICurrentProcess, IDisposable, Resource } from '../../common/types'; import { sleep } from '../../common/utils/async'; import { InMemoryCache } from '../../common/utils/cacheUtils'; import { OSType } from '../../common/utils/platform'; -import { IEnvironmentVariablesProvider } from '../../common/variables/types'; -import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; +import { EnvironmentVariables, IEnvironmentVariablesProvider } from '../../common/variables/types'; +import { EnvironmentType, PythonEnvironment, virtualEnvTypes } from '../../pythonEnvironments/info'; +import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; import { IInterpreterService } from '../contracts'; import { IEnvironmentActivationService } from './types'; +import { TraceOptions } from '../../logging/types'; +import { + traceDecoratorError, + traceDecoratorVerbose, + traceError, + traceInfo, + traceVerbose, + traceWarn, +} from '../../logging'; +import { Conda } from '../../pythonEnvironments/common/environmentManagers/conda'; +import { StopWatch } from '../../common/utils/stopWatch'; +import { identifyShellFromShellPath } from '../../common/terminal/shellDetectors/baseShellDetector'; +import { getSearchPathEnvVarNames } from '../../common/utils/exec'; +import { cache } from '../../common/utils/decorators'; +import { getRunPixiPythonCommand } from '../../pythonEnvironments/common/environmentManagers/pixi'; -const getEnvironmentPrefix = 'e8b39361-0157-4923-80e1-22d70d46dee6'; -const cacheDuration = 10 * 60 * 1000; -export const getEnvironmentTimeout = 30000; +const ENVIRONMENT_PREFIX = 'e8b39361-0157-4923-80e1-22d70d46dee6'; +const CACHE_DURATION = 10 * 60 * 1000; +const ENVIRONMENT_TIMEOUT = 30000; +const CONDA_ENVIRONMENT_TIMEOUT = 60_000; // The shell under which we'll execute activation scripts. -const defaultShells = { +export const defaultShells = { [OSType.Windows]: { shell: 'cmd', shellType: TerminalShellType.commandPrompt }, [OSType.OSX]: { shell: 'bash', shellType: TerminalShellType.bash }, [OSType.Linux]: { shell: 'bash', shellType: TerminalShellType.bash }, - [OSType.Unknown]: undefined + [OSType.Unknown]: undefined, }; const condaRetryMessages = [ 'The process cannot access the file because it is being used by another process', - 'The directory is not empty' + 'The directory is not empty', ]; /** @@ -48,15 +67,19 @@ const condaRetryMessages = [ */ export class EnvironmentActivationServiceCache { private static useStatic = false; + private static staticMap = new Map>(); + private normalMap = new Map>(); - public static forceUseStatic() { + public static forceUseStatic(): void { EnvironmentActivationServiceCache.useStatic = true; } - public static forceUseNormal() { + + public static forceUseNormal(): void { EnvironmentActivationServiceCache.useStatic = false; } + public get(key: string): InMemoryCache | undefined { if (EnvironmentActivationServiceCache.useStatic) { return EnvironmentActivationServiceCache.staticMap.get(key); @@ -64,7 +87,7 @@ export class EnvironmentActivationServiceCache { return this.normalMap.get(key); } - public set(key: string, value: InMemoryCache) { + public set(key: string, value: InMemoryCache): void { if (EnvironmentActivationServiceCache.useStatic) { EnvironmentActivationServiceCache.staticMap.set(key, value); } else { @@ -72,7 +95,7 @@ export class EnvironmentActivationServiceCache { } } - public delete(key: string) { + public delete(key: string): void { if (EnvironmentActivationServiceCache.useStatic) { EnvironmentActivationServiceCache.staticMap.delete(key); } else { @@ -80,7 +103,7 @@ export class EnvironmentActivationServiceCache { } } - public clear() { + public clear(): void { // Don't clear during a test as the environment isn't going to change if (!EnvironmentActivationServiceCache.useStatic) { this.normalMap.clear(); @@ -91,7 +114,9 @@ export class EnvironmentActivationServiceCache { @injectable() export class EnvironmentActivationService implements IEnvironmentActivationService, IDisposable { private readonly disposables: IDisposable[] = []; + private readonly activatedEnvVariablesCache = new EnvironmentActivationServiceCache(); + constructor( @inject(ITerminalHelper) private readonly helper: ITerminalHelper, @inject(IPlatformService) private readonly platform: IPlatformService, @@ -99,91 +124,182 @@ export class EnvironmentActivationService implements IEnvironmentActivationServi @inject(ICurrentProcess) private currentProcess: ICurrentProcess, @inject(IWorkspaceService) private workspace: IWorkspaceService, @inject(IInterpreterService) private interpreterService: IInterpreterService, - @inject(IEnvironmentVariablesProvider) private readonly envVarsService: IEnvironmentVariablesProvider + @inject(IEnvironmentVariablesProvider) private readonly envVarsService: IEnvironmentVariablesProvider, ) { this.envVarsService.onDidEnvironmentVariablesChange( () => this.activatedEnvVariablesCache.clear(), this, - this.disposables - ); - - this.interpreterService.onDidChangeInterpreter( - () => this.activatedEnvVariablesCache.clear(), - this, - this.disposables + this.disposables, ); } public dispose(): void { this.disposables.forEach((d) => d.dispose()); } - @traceDecorators.verbose('getActivatedEnvironmentVariables', LogOptions.Arguments) - @captureTelemetry(EventName.PYTHON_INTERPRETER_ACTIVATION_ENVIRONMENT_VARIABLES, { failed: false }, true) + + @traceDecoratorVerbose('getActivatedEnvironmentVariables', TraceOptions.Arguments) public async getActivatedEnvironmentVariables( resource: Resource, interpreter?: PythonEnvironment, - allowExceptions?: boolean + allowExceptions?: boolean, + shell?: string, ): Promise { + const stopWatch = new StopWatch(); // Cache key = resource + interpreter. const workspaceKey = this.workspace.getWorkspaceFolderIdentifier(resource); + interpreter = interpreter ?? (await this.interpreterService.getActiveInterpreter(resource)); const interpreterPath = this.platform.isWindows ? interpreter?.path.toLowerCase() : interpreter?.path; - const cacheKey = `${workspaceKey}_${interpreterPath}`; + const cacheKey = `${workspaceKey}_${interpreterPath}_${shell}`; if (this.activatedEnvVariablesCache.get(cacheKey)?.hasData) { return this.activatedEnvVariablesCache.get(cacheKey)!.data; } // Cache only if successful, else keep trying & failing if necessary. - const cache = new InMemoryCache(cacheDuration, ''); - return this.getActivatedEnvironmentVariablesImpl(resource, interpreter, allowExceptions).then((vars) => { - cache.data = vars; - this.activatedEnvVariablesCache.set(cacheKey, cache); - return vars; - }); + const memCache = new InMemoryCache(CACHE_DURATION); + return this.getActivatedEnvironmentVariablesImpl(resource, interpreter, allowExceptions, shell) + .then((vars) => { + memCache.data = vars; + this.activatedEnvVariablesCache.set(cacheKey, memCache); + sendTelemetryEvent( + EventName.PYTHON_INTERPRETER_ACTIVATION_ENVIRONMENT_VARIABLES, + stopWatch.elapsedTime, + { failed: false }, + ); + return vars; + }) + .catch((ex) => { + sendTelemetryEvent( + EventName.PYTHON_INTERPRETER_ACTIVATION_ENVIRONMENT_VARIABLES, + stopWatch.elapsedTime, + { failed: true }, + ); + throw ex; + }); + } + + @cache(-1, true) + public async getProcessEnvironmentVariables(resource: Resource, shell?: string): Promise { + // Try to get the process environment variables using Python by printing variables, that can be little different + // from `process.env` and is preferred when calculating diff. + const globalInterpreters = this.interpreterService + .getInterpreters() + .filter((i) => !virtualEnvTypes.includes(i.envType)); + const interpreterPath = + globalInterpreters.length > 0 && globalInterpreters[0] ? globalInterpreters[0].path : 'python'; + try { + const [args, parse] = internalScripts.printEnvVariables(); + args.forEach((arg, i) => { + args[i] = arg.toCommandArgumentForPythonExt(); + }); + const command = `${interpreterPath} ${args.join(' ')}`; + const processService = await this.processServiceFactory.create(resource, { doNotUseCustomEnvs: true }); + const result = await processService.shellExec(command, { + shell, + timeout: ENVIRONMENT_TIMEOUT, + maxBuffer: 1000 * 1000, + throwOnStdErr: false, + }); + const returnedEnv = this.parseEnvironmentOutput(result.stdout, parse); + return returnedEnv ?? process.env; + } catch (ex) { + return process.env; + } + } + + public async getEnvironmentActivationShellCommands( + resource: Resource, + interpreter?: PythonEnvironment, + ): Promise { + const shellInfo = defaultShells[this.platform.osType]; + if (!shellInfo) { + return []; + } + return this.helper.getEnvironmentActivationShellCommands(resource, shellInfo.shellType, interpreter); } public async getActivatedEnvironmentVariablesImpl( resource: Resource, interpreter?: PythonEnvironment, - allowExceptions?: boolean + allowExceptions?: boolean, + shell?: string, ): Promise { - const shellInfo = defaultShells[this.platform.osType]; + let shellInfo = defaultShells[this.platform.osType]; if (!shellInfo) { - return; + return undefined; + } + if (shell) { + const customShellType = identifyShellFromShellPath(shell); + shellInfo = { shellType: customShellType, shell }; } - let isPossiblyCondaEnv = false; try { - const activationCommands = await this.helper.getEnvironmentActivationShellCommands( - resource, - shellInfo.shellType, - interpreter - ); - traceVerbose(`Activation Commands received ${activationCommands} for shell ${shellInfo.shell}`); - if (!activationCommands || !Array.isArray(activationCommands) || activationCommands.length === 0) { - return; - } - isPossiblyCondaEnv = activationCommands.join(' ').toLowerCase().includes('conda'); - // Run the activate command collect the environment from it. - const activationCommand = this.fixActivationCommands(activationCommands).join(' && '); const processService = await this.processServiceFactory.create(resource); - const customEnvVars = await this.envVarsService.getEnvironmentVariables(resource); + const customEnvVars = (await this.envVarsService.getEnvironmentVariables(resource)) ?? {}; const hasCustomEnvVars = Object.keys(customEnvVars).length; const env = hasCustomEnvVars ? customEnvVars : { ...this.currentProcess.env }; + let command: string | undefined; + const [args, parse] = internalScripts.printEnvVariables(); + args.forEach((arg, i) => { + args[i] = arg.toCommandArgumentForPythonExt(); + }); + if (interpreter?.envType === EnvironmentType.Conda) { + const conda = await Conda.getConda(shell); + const pythonArgv = await conda?.getRunPythonArgs({ + name: interpreter.envName, + prefix: interpreter.envPath ?? '', + }); + if (pythonArgv) { + // Using environment prefix isn't needed as the marker script already takes care of it. + command = [...pythonArgv, ...args].map((arg) => arg.toCommandArgumentForPythonExt()).join(' '); + } + } else if (interpreter?.envType === EnvironmentType.Pixi) { + const pythonArgv = await getRunPixiPythonCommand(interpreter.path); + if (pythonArgv) { + command = [...pythonArgv, ...args].map((arg) => arg.toCommandArgumentForPythonExt()).join(' '); + } + } + if (!command) { + const activationCommands = await this.helper.getEnvironmentActivationShellCommands( + resource, + shellInfo.shellType, + interpreter, + ); + traceVerbose( + `Activation Commands received ${activationCommands} for shell ${shellInfo.shell}, resource ${resource?.fsPath} and interpreter ${interpreter?.path}`, + ); + if (!activationCommands || !Array.isArray(activationCommands) || activationCommands.length === 0) { + if (interpreter && [EnvironmentType.Venv, EnvironmentType.Pyenv].includes(interpreter?.envType)) { + const key = getSearchPathEnvVarNames()[0]; + if (env[key]) { + env[key] = `${path.dirname(interpreter.path)}${path.delimiter}${env[key]}`; + } else { + env[key] = `${path.dirname(interpreter.path)}`; + } + + return env; + } + return undefined; + } + const commandSeparator = [TerminalShellType.powershell, TerminalShellType.powershellCore].includes( + shellInfo.shellType, + ) + ? ';' + : '&&'; + // Run the activate command collect the environment from it. + const activationCommand = fixActivationCommands(activationCommands).join(` ${commandSeparator} `); + // In order to make sure we know where the environment output is, + // put in a dummy echo we can look for + command = `${activationCommand} ${commandSeparator} echo '${ENVIRONMENT_PREFIX}' ${commandSeparator} python ${args.join( + ' ', + )}`; + } + // Make sure python warnings don't interfere with getting the environment. However // respect the warning in the returned values const oldWarnings = env[PYTHON_WARNINGS]; env[PYTHON_WARNINGS] = 'ignore'; - traceVerbose(`${hasCustomEnvVars ? 'Has' : 'No'} Custom Env Vars`); - - // In order to make sure we know where the environment output is, - // put in a dummy echo we can look for - const [args, parse] = internalScripts.printEnvVariables(); - args.forEach((arg, i) => { - args[i] = arg.toCommandArgument(); - }); - const command = `${activationCommand} && echo '${getEnvironmentPrefix}' && python ${args.join(' ')}`; traceVerbose(`Activating Environment to capture Environment variables, ${command}`); // Do some wrapping of the call. For two reasons: @@ -195,22 +311,48 @@ export class EnvironmentActivationService implements IEnvironmentActivationServi // This happens on AzDo machines a bunch when using Conda (and we can't dictate the conda version in order to get the fix) let result: ExecutionResult | undefined; let tryCount = 1; + let returnedEnv: NodeJS.ProcessEnv | undefined; while (!result) { try { result = await processService.shellExec(command, { env, shell: shellInfo.shell, - timeout: getEnvironmentTimeout, + timeout: + interpreter?.envType === EnvironmentType.Conda + ? CONDA_ENVIRONMENT_TIMEOUT + : ENVIRONMENT_TIMEOUT, maxBuffer: 1000 * 1000, - throwOnStdErr: false + throwOnStdErr: false, }); - if (result.stderr && result.stderr.length > 0) { - throw new Error(`StdErr from ShellExec, ${result.stderr} for ${command}`); + + try { + // Try to parse the output, even if we have errors in stderr, its possible they are false positives. + // If variables are available, then ignore errors (but log them). + returnedEnv = this.parseEnvironmentOutput(result.stdout, parse); + } catch (ex) { + if (!result.stderr) { + throw ex; + } + } + if (result.stderr) { + if (returnedEnv) { + traceWarn('Got env variables but with errors', result.stderr, returnedEnv); + if ( + result.stderr.includes('running scripts is disabled') || + result.stderr.includes('FullyQualifiedErrorId : UnauthorizedAccess') + ) { + throw new Error( + `Skipping returned result when powershell execution is disabled, stderr ${result.stderr} for ${command}`, + ); + } + } else { + throw new Error(`StdErr from ShellExec, ${result.stderr} for ${command}`); + } } } catch (exc) { // Special case. Conda for some versions will state a file is in use. If // that's the case, wait and try again. This happens especially on AzDo - const excString = exc.toString(); + const excString = (exc as Error).toString(); if (condaRetryMessages.find((m) => excString.includes(m)) && tryCount < 10) { traceInfo(`Conda is busy, attempting to retry ...`); result = undefined; @@ -221,7 +363,6 @@ export class EnvironmentActivationService implements IEnvironmentActivationServi } } } - const returnedEnv = this.parseEnvironmentOutput(result.stdout, parse); // Put back the PYTHONWARNINGS value if (oldWarnings && returnedEnv) { @@ -233,8 +374,8 @@ export class EnvironmentActivationService implements IEnvironmentActivationServi } catch (e) { traceError('getActivatedEnvironmentVariables', e); sendTelemetryEvent(EventName.ACTIVATE_ENV_TO_GET_ENV_VARS_FAILED, undefined, { - isPossiblyCondaEnv, - terminal: shellInfo.shellType + isPossiblyCondaEnv: interpreter?.envType === EnvironmentType.Conda, + terminal: shellInfo.shellType, }); // Some callers want this to bubble out, others don't @@ -242,17 +383,23 @@ export class EnvironmentActivationService implements IEnvironmentActivationServi throw e; } } + return undefined; } - protected fixActivationCommands(commands: string[]): string[] { - // Replace 'source ' with '. ' as that works in shell exec - return commands.map((cmd) => cmd.replace(/^source\s+/, '. ')); - } - @traceDecorators.error('Failed to parse Environment variables') - @traceDecorators.verbose('parseEnvironmentOutput', LogOptions.None) - protected parseEnvironmentOutput(output: string, parse: (out: string) => NodeJS.ProcessEnv | undefined) { - output = output.substring(output.indexOf(getEnvironmentPrefix) + getEnvironmentPrefix.length); + // eslint-disable-next-line class-methods-use-this + @traceDecoratorError('Failed to parse Environment variables') + @traceDecoratorVerbose('parseEnvironmentOutput', TraceOptions.None) + private parseEnvironmentOutput(output: string, parse: (out: string) => NodeJS.ProcessEnv | undefined) { + if (output.indexOf(ENVIRONMENT_PREFIX) === -1) { + return parse(output); + } + output = output.substring(output.indexOf(ENVIRONMENT_PREFIX) + ENVIRONMENT_PREFIX.length); const js = output.substring(output.indexOf('{')).trim(); return parse(js); } } + +function fixActivationCommands(commands: string[]): string[] { + // Replace 'source ' with '. ' as that works in shell exec + return commands.map((cmd) => cmd.replace(/^source\s+/, '. ')); +} diff --git a/src/client/interpreter/activation/terminalEnvironmentActivationService.ts b/src/client/interpreter/activation/terminalEnvironmentActivationService.ts deleted file mode 100644 index a29c005d6369..000000000000 --- a/src/client/interpreter/activation/terminalEnvironmentActivationService.ts +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { CancellationTokenSource } from 'vscode'; -import { LogOptions, traceDecorators } from '../../common/logger'; -import { IFileSystem } from '../../common/platform/types'; -import * as internalScripts from '../../common/process/internal/scripts'; -import { ITerminalServiceFactory } from '../../common/terminal/types'; -import { Resource } from '../../common/types'; -import { IEnvironmentVariablesProvider } from '../../common/variables/types'; -import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { captureTelemetry } from '../../telemetry'; -import { EventName } from '../../telemetry/constants'; -import { IEnvironmentActivationService } from './types'; - -/** - * This class will provide the environment variables of an interpreter by activating it in a terminal. - * This has the following benefit: - * - Using a shell that's configured by the user (using their default shell). - * - Environment variables are dumped into a file instead of reading from stdout. - * - * @export - * @class TerminalEnvironmentActivationService - * @implements {IEnvironmentActivationService} - * @implements {IDisposable} - */ -@injectable() -export class TerminalEnvironmentActivationService implements IEnvironmentActivationService { - constructor( - @inject(ITerminalServiceFactory) private readonly terminalFactory: ITerminalServiceFactory, - @inject(IFileSystem) private readonly fs: IFileSystem, - @inject(IEnvironmentVariablesProvider) private readonly envVarsProvider: IEnvironmentVariablesProvider - ) {} - @traceDecorators.verbose('getActivatedEnvironmentVariables', LogOptions.Arguments) - @captureTelemetry( - EventName.PYTHON_INTERPRETER_ACTIVATION_ENVIRONMENT_VARIABLES, - { failed: false, activatedInTerminal: true }, - true - ) - public async getActivatedEnvironmentVariables( - resource: Resource, - interpreter?: PythonEnvironment | undefined, - _allowExceptions?: boolean | undefined - ): Promise { - const env = (await this.envVarsProvider.getCustomEnvironmentVariables(resource)) as - | { [key: string]: string | null } - | undefined; - const terminal = this.terminalFactory.getTerminalService({ - env, - hideFromUser: true, - interpreter, - resource, - title: `${interpreter?.displayName}${new Date().getTime()}` - }); - - const command = interpreter?.path || 'python'; - const jsonFile = await this.fs.createTemporaryFile('.json'); - try { - const [args, parse] = internalScripts.printEnvVariablesToFile(jsonFile.filePath); - - // Pass a cancellation token to ensure we wait until command has completed. - // If there are any errors in executing in the terminal, throw them so they get logged and bubbled up. - await terminal.sendCommand(command, args, new CancellationTokenSource().token, false); - - const contents = await this.fs.readFile(jsonFile.filePath); - return parse(contents); - } finally { - // We created a hidden terminal for temp usage, hence dispose when done. - terminal.dispose(); - jsonFile.dispose(); - } - } -} diff --git a/src/client/interpreter/activation/types.ts b/src/client/interpreter/activation/types.ts index e8ae94e3f284..e00ef9b62b3f 100644 --- a/src/client/interpreter/activation/types.ts +++ b/src/client/interpreter/activation/types.ts @@ -4,13 +4,20 @@ 'use strict'; import { Resource } from '../../common/types'; +import { EnvironmentVariables } from '../../common/variables/types'; import { PythonEnvironment } from '../../pythonEnvironments/info'; export const IEnvironmentActivationService = Symbol('IEnvironmentActivationService'); export interface IEnvironmentActivationService { + getProcessEnvironmentVariables(resource: Resource, shell?: string): Promise; getActivatedEnvironmentVariables( resource: Resource, interpreter?: PythonEnvironment, - allowExceptions?: boolean + allowExceptions?: boolean, + shell?: string, ): Promise; + getEnvironmentActivationShellCommands( + resource: Resource, + interpreter?: PythonEnvironment, + ): Promise; } diff --git a/src/client/interpreter/activation/wrapperEnvironmentActivationService.ts b/src/client/interpreter/activation/wrapperEnvironmentActivationService.ts deleted file mode 100644 index 6bf4230ece33..000000000000 --- a/src/client/interpreter/activation/wrapperEnvironmentActivationService.ts +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { UseTerminalToGetActivatedEnvVars } from '../../common/experiments/groups'; -import '../../common/extensions'; -import { traceError } from '../../common/logger'; -import { IFileSystem } from '../../common/platform/types'; -import { - ICryptoUtils, - IDisposableRegistry, - IExperimentsManager, - IExtensionContext, - Resource -} from '../../common/types'; -import { createDeferredFromPromise } from '../../common/utils/async'; -import { IEnvironmentVariablesProvider } from '../../common/variables/types'; -import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { captureTelemetry } from '../../telemetry'; -import { EventName } from '../../telemetry/constants'; -import { IInterpreterService } from '../contracts'; -import { EnvironmentActivationService } from './service'; -import { TerminalEnvironmentActivationService } from './terminalEnvironmentActivationService'; -import { IEnvironmentActivationService } from './types'; - -// We have code in terminal activation that waits for a min of 500ms. -// Observed on a Mac that it can take up to 2.5s (the delay is caused by initialization scripts running in the shell). -// On some other machines (windows) with Conda, this could take up to 40s. -// To get around this we will: -// 1. Load the variables when extension loads -// 2. Cache variables in a file (so its available when VSC re-loads). - -type EnvVariablesInCachedFile = { env?: NodeJS.ProcessEnv }; -@injectable() -export class WrapperEnvironmentActivationService implements IEnvironmentActivationService { - private readonly cachePerResourceAndInterpreter = new Map>(); - constructor( - @inject(EnvironmentActivationService) private readonly procActivation: IEnvironmentActivationService, - @inject(TerminalEnvironmentActivationService) - private readonly terminalActivation: IEnvironmentActivationService, - @inject(IExperimentsManager) private readonly experiment: IExperimentsManager, - @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, - @inject(IEnvironmentVariablesProvider) private readonly envVarsProvider: IEnvironmentVariablesProvider, - @inject(IExtensionContext) private readonly context: IExtensionContext, - @inject(IFileSystem) private readonly fs: IFileSystem, - @inject(ICryptoUtils) private readonly crypto: ICryptoUtils, - - @inject(IDisposableRegistry) disposables: IDisposableRegistry - ) { - // Environment variables rely on custom variables defined by the user in `.env` files. - disposables.push( - envVarsProvider.onDidEnvironmentVariablesChange(() => this.cachePerResourceAndInterpreter.clear()) - ); - } - @captureTelemetry( - EventName.PYTHON_INTERPRETER_ACTIVATION_ENVIRONMENT_VARIABLES, - { failed: false, activatedByWrapper: true }, - true - ) - public async getActivatedEnvironmentVariables( - resource: Resource, - interpreter?: PythonEnvironment | undefined, - allowExceptions?: boolean | undefined - ): Promise { - let key: string; - [key, interpreter] = await Promise.all([ - this.getCacheKey(resource, interpreter), - interpreter || (await this.interpreterService.getActiveInterpreter(undefined)) - ]); - - const procCacheKey = `Process${key}`; - const terminalCacheKey = `Terminal${key}`; - const procEnvVarsPromise = this.cacheCallback(procCacheKey, () => - this.getActivatedEnvVarsFromProc(resource, interpreter, allowExceptions) - ); - const terminalEnvVarsPromise = this.cacheCallback(terminalCacheKey, () => - this.getActivatedEnvVarsFromTerminal(procEnvVarsPromise, resource, interpreter, allowExceptions) - ); - - const procEnvVars = createDeferredFromPromise(procEnvVarsPromise); - const terminalEnvVars = createDeferredFromPromise(terminalEnvVarsPromise); - - // Do not return this value, its possible both complete almost at the same time. - // Hence wait for another tick, then check and return. - await Promise.race([terminalEnvVars.promise, procEnvVars.promise]); - - // Give preference to the terminal environment variables promise. - return terminalEnvVars.completed ? terminalEnvVars.promise : procEnvVars.promise; - } - /** - * Cache the implementation so it can be used in the future. - * If a cached entry already exists, then ignore the implementation. - * - * @private - * @param {string} cacheKey - * @param {(() => Promise)} implementation - * @returns {(Promise)} - * @memberof WrapperEnvironmentActivationService - */ - private async cacheCallback( - cacheKey: string, - implementation: () => Promise - ): Promise { - const contents = await this.getDataCachedInFile(cacheKey); - if (contents) { - // If we have it in file cache, then blow away in memory cache, we don't need that anymore. - this.cachePerResourceAndInterpreter.delete(cacheKey); - return contents.env; - } - - // If we don't have this cached in file, we need to ensure the request is cached in memory. - // This way if two different parts of the extension request variables for the same resource + interpreter, they get the same result (promise). - if (!this.cachePerResourceAndInterpreter.get(cacheKey)) { - const promise = implementation(); - this.cachePerResourceAndInterpreter.set(cacheKey, promise); - - // What ever result we get back, store that in file (cache it for other VSC sessions). - promise - .then((env) => this.writeDataToCacheFile(cacheKey, { env })) - .catch((ex) => traceError('Failed to write Env Vars to disc', ex)); - } - - return this.cachePerResourceAndInterpreter.get(cacheKey)!; - } - private getCacheFile(cacheKey: string): string | undefined { - return this.context.storagePath - ? path.join(this.context.storagePath, `pvscEnvVariables${cacheKey}.json`) - : undefined; - } - private async getDataCachedInFile(cacheKey: string): Promise { - const cacheFile = this.getCacheFile(cacheKey); - if (!cacheFile) { - return; - } - return this.fs - .readFile(cacheFile) - .then((data) => JSON.parse(data) as EnvVariablesInCachedFile) - .catch(() => undefined); - } - /** - * Writes the environment variables to disc. - * This way it is available to other VSC Sessions (between VSC reloads). - */ - private async writeDataToCacheFile(cacheKey: string, data: EnvVariablesInCachedFile): Promise { - const cacheFile = this.getCacheFile(cacheKey); - if (!cacheFile || !this.context.storagePath) { - return; - } - if (!(await this.fs.directoryExists(this.context.storagePath))) { - await this.fs.createDirectory(this.context.storagePath); - } - await this.fs.writeFile(cacheFile, JSON.stringify(data)); - } - /** - * Get environment variables by spawning a process (old approach). - */ - private async getActivatedEnvVarsFromProc( - resource: Resource, - interpreter?: PythonEnvironment, - allowExceptions?: boolean - ): Promise { - return this.procActivation.getActivatedEnvironmentVariables(resource, interpreter, allowExceptions); - } - /** - * Get environment variables by activating a terminal. - * As a fallback use the `fallback` promise passed in (old approach). - */ - private async getActivatedEnvVarsFromTerminal( - fallback: Promise, - resource: Resource, - interpreter?: PythonEnvironment, - allowExceptions?: boolean - ): Promise { - if (!this.experiment.inExperiment(UseTerminalToGetActivatedEnvVars.experiment)) { - return fallback; - } - - return this.terminalActivation - .getActivatedEnvironmentVariables(resource, interpreter, allowExceptions) - .then((vars) => { - // If no variables in terminal, then revert to old approach. - return vars || fallback; - }) - .catch((ex) => { - // Swallow exceptions when using terminal env and revert to using old approach. - traceError('Failed to get variables using Terminal Service', ex); - return fallback; - }); - } - /** - * Computes a key used to cache environment variables. - * 1. If resource changes, then environment variables could be different, as user could have `.env` files or similar. - * (this might not be necessary, if there are no custom variables, but paths such as PYTHONPATH, PATH might be different too when computed). - * 2. If interpreter changes, then environment variables could be different (conda has its own env variables). - * 3. Similarly, each workspace could have its own env variables defined in `.env` files, and these could change as well. - * Hence the key is computed based off of these three. - */ - private async getCacheKey(resource: Resource, interpreter?: PythonEnvironment | undefined): Promise { - // Get the custom environment variables as a string (if any errors, ignore and use empty string). - const customEnvVariables = await this.envVarsProvider - .getCustomEnvironmentVariables(resource) - .then((item) => (item ? JSON.stringify(item) : '')) - .catch(() => ''); - - return this.crypto.createHash( - `${customEnvVariables}${interpreter?.path}${interpreter?.version?.raw}`, - 'string', - 'SHA256' - ); - } -} diff --git a/src/client/interpreter/autoSelection/constants.ts b/src/client/interpreter/autoSelection/constants.ts deleted file mode 100644 index ca3b4d131e97..000000000000 --- a/src/client/interpreter/autoSelection/constants.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -export const unsafeInterpreterPromptKey = 'unsafeInterpreterPromptKey'; -export const unsafeInterpretersKey = 'unsafeInterpretersKey'; -export const safeInterpretersKey = 'safeInterpretersKey'; -export const flaggedWorkspacesKeysStorageKey = 'flaggedWorkspacesKeysInterpreterSecurityStorageKey'; -export const learnMoreOnInterpreterSecurityURI = 'https://aka.ms/AA7jfor'; diff --git a/src/client/interpreter/autoSelection/index.ts b/src/client/interpreter/autoSelection/index.ts index a9ca7e1b4b25..5ad5362e8210 100644 --- a/src/client/interpreter/autoSelection/index.ts +++ b/src/client/interpreter/autoSelection/index.ts @@ -3,25 +3,22 @@ 'use strict'; -import { inject, injectable, named } from 'inversify'; -import { compare } from 'semver'; +import { inject, injectable } from 'inversify'; import { Event, EventEmitter, Uri } from 'vscode'; import { IWorkspaceService } from '../../common/application/types'; +import { DiscoveryUsingWorkers } from '../../common/experiments/groups'; import '../../common/extensions'; import { IFileSystem } from '../../common/platform/types'; -import { IPersistentState, IPersistentStateFactory, Resource } from '../../common/types'; +import { IExperimentService, IPersistentState, IPersistentStateFactory, Resource } from '../../common/types'; import { createDeferred, Deferred } from '../../common/utils/async'; +import { compareSemVerLikeVersions } from '../../pythonEnvironments/base/info/pythonVersion'; +import { ProgressReportStage } from '../../pythonEnvironments/base/locator'; import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; +import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; -import { IInterpreterHelper } from '../contracts'; -import { - AutoSelectionRule, - IInterpreterAutoSelectionRule, - IInterpreterAutoSelectionService, - IInterpreterAutoSeletionProxyService, - IInterpreterSecurityService -} from './types'; +import { IInterpreterComparer } from '../configuration/types'; +import { IInterpreterHelper, IInterpreterService } from '../contracts'; +import { IInterpreterAutoSelectionService, IInterpreterAutoSelectionProxyService } from './types'; const preferredGlobalInterpreter = 'preferredGlobalPyInterpreter'; const workspacePathNameForGlobalWorkspaces = ''; @@ -29,100 +26,65 @@ const workspacePathNameForGlobalWorkspaces = ''; @injectable() export class InterpreterAutoSelectionService implements IInterpreterAutoSelectionService { protected readonly autoSelectedWorkspacePromises = new Map>(); + private readonly didAutoSelectedInterpreterEmitter = new EventEmitter(); + private readonly autoSelectedInterpreterByWorkspace = new Map(); - private globallyPreferredInterpreter!: IPersistentState; - private readonly rules: IInterpreterAutoSelectionRule[] = []; + + private globallyPreferredInterpreter: IPersistentState< + PythonEnvironment | undefined + > = this.stateFactory.createGlobalPersistentState( + preferredGlobalInterpreter, + undefined, + ); + constructor( @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, @inject(IPersistentStateFactory) private readonly stateFactory: IPersistentStateFactory, @inject(IFileSystem) private readonly fs: IFileSystem, - @inject(IInterpreterAutoSelectionRule) - @named(AutoSelectionRule.systemWide) - systemInterpreter: IInterpreterAutoSelectionRule, - @inject(IInterpreterAutoSelectionRule) - @named(AutoSelectionRule.currentPath) - currentPathInterpreter: IInterpreterAutoSelectionRule, - @inject(IInterpreterAutoSelectionRule) - @named(AutoSelectionRule.windowsRegistry) - winRegInterpreter: IInterpreterAutoSelectionRule, - @inject(IInterpreterAutoSelectionRule) - @named(AutoSelectionRule.cachedInterpreters) - cachedPaths: IInterpreterAutoSelectionRule, - @inject(IInterpreterAutoSelectionRule) - @named(AutoSelectionRule.settings) - private readonly userDefinedInterpreter: IInterpreterAutoSelectionRule, - @inject(IInterpreterAutoSelectionRule) - @named(AutoSelectionRule.workspaceVirtualEnvs) - workspaceInterpreter: IInterpreterAutoSelectionRule, - @inject(IInterpreterAutoSeletionProxyService) proxy: IInterpreterAutoSeletionProxyService, + @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, + @inject(IInterpreterComparer) private readonly envTypeComparer: IInterpreterComparer, + @inject(IInterpreterAutoSelectionProxyService) proxy: IInterpreterAutoSelectionProxyService, @inject(IInterpreterHelper) private readonly interpreterHelper: IInterpreterHelper, - @inject(IInterpreterSecurityService) private readonly interpreterSecurityService: IInterpreterSecurityService + @inject(IExperimentService) private readonly experimentService: IExperimentService, ) { - // It is possible we area always opening the same workspace folder, but we still need to determine and cache - // the best available interpreters based on other rules (cache for furture use). - this.rules.push( - ...[ - winRegInterpreter, - currentPathInterpreter, - systemInterpreter, - cachedPaths, - userDefinedInterpreter, - workspaceInterpreter - ] - ); proxy.registerInstance!(this); - // Rules are as follows in order - // 1. First check user settings.json - // If we have user settings, then always use that, do not proceed. - // 2. Check workspace virtual environments (pipenv, etc). - // If we have some, then use those as preferred workspace environments. - // 3. Check list of cached interpreters (previously cachced from all the rules). - // If we find a good one, use that as preferred global env. - // Provided its better than what we have already cached as globally preffered interpreter (globallyPreferredInterpreter). - // 4. Check current path. - // If we find a good one, use that as preferred global env. - // Provided its better than what we have already cached as globally preffered interpreter (globallyPreferredInterpreter). - // 5. Check windows registry. - // If we find a good one, use that as preferred global env. - // Provided its better than what we have already cached as globally preffered interpreter (globallyPreferredInterpreter). - // 6. Check the entire system. - // If we find a good one, use that as preferred global env. - // Provided its better than what we have already cached as globally preffered interpreter (globallyPreferredInterpreter). - userDefinedInterpreter.setNextRule(workspaceInterpreter); - workspaceInterpreter.setNextRule(cachedPaths); - cachedPaths.setNextRule(currentPathInterpreter); - currentPathInterpreter.setNextRule(winRegInterpreter); - winRegInterpreter.setNextRule(systemInterpreter); } - @captureTelemetry(EventName.PYTHON_INTERPRETER_AUTO_SELECTION, { rule: AutoSelectionRule.all }, true) + + /** + * Auto-select a Python environment from the list returned by environment discovery. + * If there's a cached auto-selected environment -> return it. + */ public async autoSelectInterpreter(resource: Resource): Promise { const key = this.getWorkspacePathKey(resource); - if (!this.autoSelectedWorkspacePromises.has(key)) { + const useCachedInterpreter = this.autoSelectedWorkspacePromises.has(key); + + if (!useCachedInterpreter) { const deferred = createDeferred(); this.autoSelectedWorkspacePromises.set(key, deferred); + await this.initializeStore(resource); await this.clearWorkspaceStoreIfInvalid(resource); - await this.userDefinedInterpreter.autoSelectInterpreter(resource, this); - this.didAutoSelectedInterpreterEmitter.fire(); - Promise.all(this.rules.map((item) => item.autoSelectInterpreter(resource))).ignoreErrors(); + await this.autoselectInterpreterWithLocators(resource); + deferred.resolve(); } + + sendTelemetryEvent(EventName.PYTHON_INTERPRETER_AUTO_SELECTION, undefined, { + useCachedInterpreter, + }); + return this.autoSelectedWorkspacePromises.get(key)!.promise; } + public get onDidChangeAutoSelectedInterpreter(): Event { return this.didAutoSelectedInterpreterEmitter.event; } + public getAutoSelectedInterpreter(resource: Resource): PythonEnvironment | undefined { // Do not execute anycode other than fetching fromm a property. // This method gets invoked from settings class, and this class in turn uses classes that relies on settings. // I.e. we can end up in a recursive loop. - const interpreter = this._getAutoSelectedInterpreter(resource); - // Unless the interpreter is marked as unsafe, return interpreter. - return interpreter && this.interpreterSecurityService.isSafe(interpreter) === false ? undefined : interpreter; - } - - public _getAutoSelectedInterpreter(resource: Resource): PythonEnvironment | undefined { const workspaceState = this.getWorkspaceState(resource); if (workspaceState && workspaceState.value) { return workspaceState.value; @@ -135,20 +97,26 @@ export class InterpreterAutoSelectionService implements IInterpreterAutoSelectio return this.globallyPreferredInterpreter.value; } - public async setWorkspaceInterpreter(resource: Uri, interpreter: PythonEnvironment | undefined) { + + public async setWorkspaceInterpreter(resource: Uri, interpreter: PythonEnvironment | undefined): Promise { await this.storeAutoSelectedInterpreter(resource, interpreter); } - public async setGlobalInterpreter(interpreter: PythonEnvironment) { + + public async setGlobalInterpreter(interpreter: PythonEnvironment): Promise { await this.storeAutoSelectedInterpreter(undefined, interpreter); } - protected async clearWorkspaceStoreIfInvalid(resource: Resource) { + + protected async clearWorkspaceStoreIfInvalid(resource: Resource): Promise { const stateStore = this.getWorkspaceState(resource); if (stateStore && stateStore.value && !(await this.fs.fileExists(stateStore.value.path))) { - sendTelemetryEvent(EventName.PYTHON_INTERPRETER_AUTO_SELECTION, {}, { interpreterMissing: true }); await stateStore.updateValue(undefined); } } - protected async storeAutoSelectedInterpreter(resource: Resource, interpreter: PythonEnvironment | undefined) { + + protected async storeAutoSelectedInterpreter( + resource: Resource, + interpreter: PythonEnvironment | undefined, + ): Promise { const workspaceFolderPath = this.getWorkspacePathKey(resource); if (workspaceFolderPath === workspacePathNameForGlobalWorkspaces) { // Update store only if this version is better. @@ -157,7 +125,7 @@ export class InterpreterAutoSelectionService implements IInterpreterAutoSelectio this.globallyPreferredInterpreter.value.version && interpreter && interpreter.version && - compare(this.globallyPreferredInterpreter.value.version.raw, interpreter.version.raw) > 0 + compareSemVerLikeVersions(this.globallyPreferredInterpreter.value.version, interpreter.version) > 0 ) { return; } @@ -173,7 +141,8 @@ export class InterpreterAutoSelectionService implements IInterpreterAutoSelectio this.autoSelectedInterpreterByWorkspace.set(workspaceFolderPath, interpreter); } } - protected async initializeStore(resource: Resource) { + + protected async initializeStore(resource: Resource): Promise { const workspaceFolderPath = this.getWorkspacePathKey(resource); // Since we're initializing for this resource, // Ensure any cached information for this workspace have been removed. @@ -183,6 +152,7 @@ export class InterpreterAutoSelectionService implements IInterpreterAutoSelectio } await this.clearStoreIfFileIsInvalid(); } + private async clearStoreIfFileIsInvalid() { this.globallyPreferredInterpreter = this.stateFactory.createGlobalPersistentState< PythonEnvironment | undefined @@ -194,15 +164,95 @@ export class InterpreterAutoSelectionService implements IInterpreterAutoSelectio await this.globallyPreferredInterpreter.updateValue(undefined); } } + private getWorkspacePathKey(resource: Resource): string { return this.workspaceService.getWorkspaceFolderIdentifier(resource, workspacePathNameForGlobalWorkspaces); } + private getWorkspaceState(resource: Resource): undefined | IPersistentState { const workspaceUri = this.interpreterHelper.getActiveWorkspaceUri(resource); - if (!workspaceUri) { - return; + if (workspaceUri) { + const key = `autoSelectedWorkspacePythonInterpreter-${workspaceUri.folderUri.fsPath}`; + return this.stateFactory.createWorkspacePersistentState(key, undefined); } - const key = `autoSelectedWorkspacePythonInterpreter-${workspaceUri.folderUri.fsPath}`; + return undefined; + } + + private getAutoSelectionInterpretersQueryState(resource: Resource): IPersistentState { + const workspaceUri = this.interpreterHelper.getActiveWorkspaceUri(resource); + const key = `autoSelectionInterpretersQueried-${workspaceUri?.folderUri.fsPath || 'global'}`; return this.stateFactory.createWorkspacePersistentState(key, undefined); } + + private getAutoSelectionQueriedOnceState(): IPersistentState { + const key = `autoSelectionInterpretersQueriedOnce`; + return this.stateFactory.createGlobalPersistentState(key, undefined); + } + + /** + * Auto-selection logic: + * 1. If there are cached interpreters (not the first session in this workspace) + * -> sort using the same logic as in the interpreter quickpick and return the first one; + * 2. If not, we already fire all the locators, so wait for their response, sort the interpreters and return the first one. + * + * `getInterpreters` will check the cache first and return early if there are any cached interpreters, + * and if not it will wait for locators to return. + * As such, we can sort interpreters based on what it returns. + */ + private async autoselectInterpreterWithLocators(resource: Resource): Promise { + // Do not perform a full interpreter search if we already have cached interpreters for this workspace. + const queriedState = this.getAutoSelectionInterpretersQueryState(resource); + const globalQueriedState = this.getAutoSelectionQueriedOnceState(); + if (globalQueriedState.value && queriedState.value !== true && resource) { + await this.interpreterService.triggerRefresh({ + searchLocations: { roots: [resource], doNotIncludeNonRooted: true }, + }); + } + + await this.envTypeComparer.initialize(resource); + const inExperiment = this.experimentService.inExperimentSync(DiscoveryUsingWorkers.experiment); + const workspaceUri = this.interpreterHelper.getActiveWorkspaceUri(resource); + let recommendedInterpreter: PythonEnvironment | undefined; + if (inExperiment) { + if (!globalQueriedState.value) { + // Global interpreters are loaded the first time an extension loads, after which we don't need to + // wait on global interpreter promise refresh. + // Do not wait for validation of all interpreters to finish, we only need to validate the recommended interpreter. + await this.interpreterService.getRefreshPromise({ stage: ProgressReportStage.allPathsDiscovered }); + } + let interpreters = this.interpreterService.getInterpreters(resource); + + recommendedInterpreter = this.envTypeComparer.getRecommended(interpreters, workspaceUri?.folderUri); + const details = recommendedInterpreter + ? await this.interpreterService.getInterpreterDetails(recommendedInterpreter.path) + : undefined; + if (!details || !recommendedInterpreter) { + await this.interpreterService.refreshPromise; // Interpreter is invalid, wait for all of validation to finish. + interpreters = this.interpreterService.getInterpreters(resource); + recommendedInterpreter = this.envTypeComparer.getRecommended(interpreters, workspaceUri?.folderUri); + } + } else { + if (!globalQueriedState.value) { + // Global interpreters are loaded the first time an extension loads, after which we don't need to + // wait on global interpreter promise refresh. + await this.interpreterService.refreshPromise; + } + const interpreters = this.interpreterService.getInterpreters(resource); + + recommendedInterpreter = this.envTypeComparer.getRecommended(interpreters, workspaceUri?.folderUri); + } + if (!recommendedInterpreter) { + return; + } + if (workspaceUri) { + this.setWorkspaceInterpreter(workspaceUri.folderUri, recommendedInterpreter); + } else { + this.setGlobalInterpreter(recommendedInterpreter); + } + + queriedState.updateValue(true); + globalQueriedState.updateValue(true); + + this.didAutoSelectedInterpreterEmitter.fire(); + } } diff --git a/src/client/interpreter/autoSelection/interpreterSecurity/interpreterEvaluation.ts b/src/client/interpreter/autoSelection/interpreterSecurity/interpreterEvaluation.ts deleted file mode 100644 index 0a0ba074e3d1..000000000000 --- a/src/client/interpreter/autoSelection/interpreterSecurity/interpreterEvaluation.ts +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { Uri } from 'vscode'; -import { IApplicationShell } from '../../../common/application/types'; -import { IBrowserService, Resource } from '../../../common/types'; -import { Common, Interpreters } from '../../../common/utils/localize'; -import { PythonEnvironment } from '../../../pythonEnvironments/info'; -import { sendTelemetryEvent } from '../../../telemetry'; -import { EventName } from '../../../telemetry/constants'; -import { IInterpreterHelper } from '../../contracts'; -import { isInterpreterLocatedInWorkspace } from '../../helpers'; -import { learnMoreOnInterpreterSecurityURI } from '../constants'; -import { IInterpreterEvaluation, IInterpreterSecurityStorage } from '../types'; - -const prompts = [Common.bannerLabelYes(), Common.bannerLabelNo(), Common.learnMore(), Common.doNotShowAgain()]; -const telemetrySelections: ['Yes', 'No', 'Learn more', 'Do not show again'] = [ - 'Yes', - 'No', - 'Learn more', - 'Do not show again' -]; - -@injectable() -export class InterpreterEvaluation implements IInterpreterEvaluation { - constructor( - @inject(IApplicationShell) private readonly appShell: IApplicationShell, - @inject(IBrowserService) private browserService: IBrowserService, - @inject(IInterpreterHelper) private readonly interpreterHelper: IInterpreterHelper, - @inject(IInterpreterSecurityStorage) private readonly interpreterSecurityStorage: IInterpreterSecurityStorage - ) {} - - public async evaluateIfInterpreterIsSafe(interpreter: PythonEnvironment, resource: Resource): Promise { - const activeWorkspaceUri = this.interpreterHelper.getActiveWorkspaceUri(resource)?.folderUri; - if (!activeWorkspaceUri) { - return true; - } - const isSafe = this.inferValueUsingCurrentState(interpreter, resource); - return isSafe !== undefined ? isSafe : this._inferValueUsingPrompt(activeWorkspaceUri); - } - - public inferValueUsingCurrentState(interpreter: PythonEnvironment, resource: Resource) { - const activeWorkspaceUri = this.interpreterHelper.getActiveWorkspaceUri(resource)?.folderUri; - if (!activeWorkspaceUri) { - return true; - } - if (!isInterpreterLocatedInWorkspace(interpreter, activeWorkspaceUri)) { - return true; - } - const isSafe = this.interpreterSecurityStorage.hasUserApprovedWorkspaceInterpreters(activeWorkspaceUri).value; - if (isSafe !== undefined) { - return isSafe; - } - if (!this.interpreterSecurityStorage.unsafeInterpreterPromptEnabled.value) { - // If the prompt is disabled, assume all environments are safe from now on. - return true; - } - } - - public async _inferValueUsingPrompt(activeWorkspaceUri: Uri): Promise { - const areInterpretersInWorkspaceSafe = this.interpreterSecurityStorage.hasUserApprovedWorkspaceInterpreters( - activeWorkspaceUri - ); - await this.interpreterSecurityStorage.storeKeyForWorkspace(activeWorkspaceUri); - let selection = await this.showPromptAndGetSelection(); - while (selection === Common.learnMore()) { - this.browserService.launch(learnMoreOnInterpreterSecurityURI); - selection = await this.showPromptAndGetSelection(); - } - if (!selection || selection === Common.bannerLabelNo()) { - await areInterpretersInWorkspaceSafe.updateValue(false); - return false; - } else if (selection === Common.doNotShowAgain()) { - await this.interpreterSecurityStorage.unsafeInterpreterPromptEnabled.updateValue(false); - } - await areInterpretersInWorkspaceSafe.updateValue(true); - return true; - } - - private async showPromptAndGetSelection(): Promise { - const selection = await this.appShell.showInformationMessage( - Interpreters.unsafeInterpreterMessage(), - ...prompts - ); - sendTelemetryEvent(EventName.UNSAFE_INTERPRETER_PROMPT, undefined, { - selection: selection ? telemetrySelections[prompts.indexOf(selection)] : undefined - }); - return selection; - } -} diff --git a/src/client/interpreter/autoSelection/interpreterSecurity/interpreterSecurityService.ts b/src/client/interpreter/autoSelection/interpreterSecurity/interpreterSecurityService.ts deleted file mode 100644 index 72f09e347f7b..000000000000 --- a/src/client/interpreter/autoSelection/interpreterSecurity/interpreterSecurityService.ts +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { Event, EventEmitter } from 'vscode'; -import { Resource } from '../../../common/types'; -import { PythonEnvironment } from '../../../pythonEnvironments/info'; -import { IInterpreterEvaluation, IInterpreterSecurityService, IInterpreterSecurityStorage } from '../types'; - -@injectable() -export class InterpreterSecurityService implements IInterpreterSecurityService { - public _didSafeInterpretersChange = new EventEmitter(); - constructor( - @inject(IInterpreterSecurityStorage) private readonly interpreterSecurityStorage: IInterpreterSecurityStorage, - @inject(IInterpreterEvaluation) private readonly interpreterEvaluation: IInterpreterEvaluation - ) {} - - public isSafe(interpreter: PythonEnvironment, resource?: Resource): boolean | undefined { - const unsafeInterpreters = this.interpreterSecurityStorage.unsafeInterpreters.value; - if (unsafeInterpreters.includes(interpreter.path)) { - return false; - } - const safeInterpreters = this.interpreterSecurityStorage.safeInterpreters.value; - if (safeInterpreters.includes(interpreter.path)) { - return true; - } - return this.interpreterEvaluation.inferValueUsingCurrentState(interpreter, resource); - } - - public async evaluateAndRecordInterpreterSafety(interpreter: PythonEnvironment, resource: Resource): Promise { - const unsafeInterpreters = this.interpreterSecurityStorage.unsafeInterpreters.value; - const safeInterpreters = this.interpreterSecurityStorage.safeInterpreters.value; - if (unsafeInterpreters.includes(interpreter.path) || safeInterpreters.includes(interpreter.path)) { - return; - } - const isSafe = await this.interpreterEvaluation.evaluateIfInterpreterIsSafe(interpreter, resource); - if (isSafe) { - await this.interpreterSecurityStorage.safeInterpreters.updateValue([interpreter.path, ...safeInterpreters]); - } else { - await this.interpreterSecurityStorage.unsafeInterpreters.updateValue([ - interpreter.path, - ...unsafeInterpreters - ]); - } - this._didSafeInterpretersChange.fire(); - } - - public get onDidChangeSafeInterpreters(): Event { - return this._didSafeInterpretersChange.event; - } -} diff --git a/src/client/interpreter/autoSelection/interpreterSecurity/interpreterSecurityStorage.ts b/src/client/interpreter/autoSelection/interpreterSecurity/interpreterSecurityStorage.ts deleted file mode 100644 index f75317a859cb..000000000000 --- a/src/client/interpreter/autoSelection/interpreterSecurity/interpreterSecurityStorage.ts +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { Uri } from 'vscode'; -import { ICommandManager, IWorkspaceService } from '../../../common/application/types'; -import { Commands } from '../../../common/constants'; -import { IDisposable, IDisposableRegistry, IPersistentState, IPersistentStateFactory } from '../../../common/types'; -import { - flaggedWorkspacesKeysStorageKey, - safeInterpretersKey, - unsafeInterpreterPromptKey, - unsafeInterpretersKey -} from '../constants'; -import { IInterpreterSecurityStorage } from '../types'; - -@injectable() -export class InterpreterSecurityStorage implements IInterpreterSecurityStorage { - public get unsafeInterpreterPromptEnabled(): IPersistentState { - return this._unsafeInterpreterPromptEnabled; - } - public get unsafeInterpreters(): IPersistentState { - return this._unsafeInterpreters; - } - public get safeInterpreters(): IPersistentState { - return this._safeInterpreters; - } - private _unsafeInterpreterPromptEnabled: IPersistentState; - private _unsafeInterpreters: IPersistentState; - private _safeInterpreters: IPersistentState; - private flaggedWorkspacesKeysStorage: IPersistentState; - - constructor( - @inject(IPersistentStateFactory) private readonly persistentStateFactory: IPersistentStateFactory, - @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, - @inject(ICommandManager) private readonly commandManager: ICommandManager, - @inject(IDisposableRegistry) private readonly disposables: IDisposable[] - ) { - this._unsafeInterpreterPromptEnabled = this.persistentStateFactory.createGlobalPersistentState( - unsafeInterpreterPromptKey, - true - ); - this._unsafeInterpreters = this.persistentStateFactory.createGlobalPersistentState( - unsafeInterpretersKey, - [] - ); - this._safeInterpreters = this.persistentStateFactory.createGlobalPersistentState( - safeInterpretersKey, - [] - ); - this.flaggedWorkspacesKeysStorage = this.persistentStateFactory.createGlobalPersistentState( - flaggedWorkspacesKeysStorageKey, - [] - ); - } - - public hasUserApprovedWorkspaceInterpreters(resource: Uri): IPersistentState { - return this.persistentStateFactory.createGlobalPersistentState( - this._getKeyForWorkspace(resource), - undefined - ); - } - - public async activate(): Promise { - this.disposables.push( - this.commandManager.registerCommand( - Commands.ResetInterpreterSecurityStorage, - this.resetInterpreterSecurityStorage.bind(this) - ) - ); - } - - public async resetInterpreterSecurityStorage(): Promise { - this.flaggedWorkspacesKeysStorage.value.forEach(async (key) => { - const areInterpretersInWorkspaceSafe = this.persistentStateFactory.createGlobalPersistentState< - boolean | undefined - >(key, undefined); - await areInterpretersInWorkspaceSafe.updateValue(undefined); - }); - await this.flaggedWorkspacesKeysStorage.updateValue([]); - await this._safeInterpreters.updateValue([]); - await this._unsafeInterpreters.updateValue([]); - await this._unsafeInterpreterPromptEnabled.updateValue(true); - } - - public _getKeyForWorkspace(resource: Uri): string { - return `ARE_INTERPRETERS_SAFE_FOR_WS_${this.workspaceService.getWorkspaceFolderIdentifier(resource)}`; - } - - public async storeKeyForWorkspace(resource: Uri): Promise { - const key = this._getKeyForWorkspace(resource); - const flaggedWorkspacesKeys = this.flaggedWorkspacesKeysStorage.value; - if (!flaggedWorkspacesKeys.includes(key)) { - await this.flaggedWorkspacesKeysStorage.updateValue([key, ...flaggedWorkspacesKeys]); - } - } -} diff --git a/src/client/interpreter/autoSelection/proxy.ts b/src/client/interpreter/autoSelection/proxy.ts index 7fef629f1463..ea9be593d386 100644 --- a/src/client/interpreter/autoSelection/proxy.ts +++ b/src/client/interpreter/autoSelection/proxy.ts @@ -5,27 +5,33 @@ import { inject, injectable } from 'inversify'; import { Event, EventEmitter, Uri } from 'vscode'; -import { IAsyncDisposableRegistry, IDisposableRegistry, Resource } from '../../common/types'; +import { IDisposableRegistry, Resource } from '../../common/types'; import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { IInterpreterAutoSeletionProxyService } from './types'; +import { IInterpreterAutoSelectionProxyService } from './types'; @injectable() -export class InterpreterAutoSeletionProxyService implements IInterpreterAutoSeletionProxyService { +export class InterpreterAutoSelectionProxyService implements IInterpreterAutoSelectionProxyService { private readonly didAutoSelectedInterpreterEmitter = new EventEmitter(); - private instance?: IInterpreterAutoSeletionProxyService; - constructor(@inject(IDisposableRegistry) private readonly disposables: IAsyncDisposableRegistry) {} - public registerInstance(instance: IInterpreterAutoSeletionProxyService): void { + + private instance?: IInterpreterAutoSelectionProxyService; + + constructor(@inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry) {} + + public registerInstance(instance: IInterpreterAutoSelectionProxyService): void { this.instance = instance; this.disposables.push( - this.instance.onDidChangeAutoSelectedInterpreter(() => this.didAutoSelectedInterpreterEmitter.fire()) + this.instance.onDidChangeAutoSelectedInterpreter(() => this.didAutoSelectedInterpreterEmitter.fire()), ); } + public get onDidChangeAutoSelectedInterpreter(): Event { return this.didAutoSelectedInterpreterEmitter.event; } + public getAutoSelectedInterpreter(resource: Resource): PythonEnvironment | undefined { return this.instance ? this.instance.getAutoSelectedInterpreter(resource) : undefined; } + public async setWorkspaceInterpreter(resource: Uri, interpreter: PythonEnvironment | undefined): Promise { return this.instance ? this.instance.setWorkspaceInterpreter(resource, interpreter) : undefined; } diff --git a/src/client/interpreter/autoSelection/rules/baseRule.ts b/src/client/interpreter/autoSelection/rules/baseRule.ts deleted file mode 100644 index 5edae0a6c3e4..000000000000 --- a/src/client/interpreter/autoSelection/rules/baseRule.ts +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable, unmanaged } from 'inversify'; -import { compare } from 'semver'; -import '../../../common/extensions'; -import { traceDecorators, traceVerbose } from '../../../common/logger'; -import { IFileSystem } from '../../../common/platform/types'; -import { IPersistentState, IPersistentStateFactory, Resource } from '../../../common/types'; -import { StopWatch } from '../../../common/utils/stopWatch'; -import { PythonEnvironment } from '../../../pythonEnvironments/info'; -import { sendTelemetryEvent } from '../../../telemetry'; -import { EventName } from '../../../telemetry/constants'; -import { AutoSelectionRule, IInterpreterAutoSelectionRule, IInterpreterAutoSelectionService } from '../types'; - -export enum NextAction { - runNextRule = 'runNextRule', - exit = 'exit' -} - -@injectable() -export abstract class BaseRuleService implements IInterpreterAutoSelectionRule { - protected nextRule?: IInterpreterAutoSelectionRule; - private readonly stateStore: IPersistentState; - constructor( - @unmanaged() protected readonly ruleName: AutoSelectionRule, - @inject(IFileSystem) private readonly fs: IFileSystem, - @inject(IPersistentStateFactory) stateFactory: IPersistentStateFactory - ) { - this.stateStore = stateFactory.createGlobalPersistentState( - `InterpreterAutoSeletionRule-${this.ruleName}`, - undefined - ); - } - public setNextRule(rule: IInterpreterAutoSelectionRule): void { - this.nextRule = rule; - } - @traceDecorators.verbose('autoSelectInterpreter') - public async autoSelectInterpreter(resource: Resource, manager?: IInterpreterAutoSelectionService): Promise { - await this.clearCachedInterpreterIfInvalid(resource); - const stopWatch = new StopWatch(); - const action = await this.onAutoSelectInterpreter(resource, manager); - traceVerbose(`Rule = ${this.ruleName}, result = ${action}`); - const identified = action === NextAction.runNextRule; - sendTelemetryEvent( - EventName.PYTHON_INTERPRETER_AUTO_SELECTION, - { elapsedTime: stopWatch.elapsedTime }, - { rule: this.ruleName, identified } - ); - if (action === NextAction.runNextRule) { - await this.next(resource, manager); - } - } - public getPreviouslyAutoSelectedInterpreter(_resource: Resource): PythonEnvironment | undefined { - const value = this.stateStore.value; - traceVerbose(`Current value for rule ${this.ruleName} is ${value ? JSON.stringify(value) : 'nothing'}`); - return value; - } - protected abstract onAutoSelectInterpreter( - resource: Resource, - manager?: IInterpreterAutoSelectionService - ): Promise; - @traceDecorators.verbose('setGlobalInterpreter') - protected async setGlobalInterpreter( - interpreter?: PythonEnvironment, - manager?: IInterpreterAutoSelectionService - ): Promise { - await this.cacheSelectedInterpreter(undefined, interpreter); - if (!interpreter || !manager || !interpreter.version) { - return false; - } - const preferredInterpreter = manager.getAutoSelectedInterpreter(undefined); - const comparison = - preferredInterpreter && preferredInterpreter.version - ? compare(interpreter.version.raw, preferredInterpreter.version.raw) - : 1; - if (comparison > 0) { - await manager.setGlobalInterpreter(interpreter); - return true; - } - if (comparison === 0) { - return true; - } - - return false; - } - protected async clearCachedInterpreterIfInvalid(resource: Resource) { - if (!this.stateStore.value || (await this.fs.fileExists(this.stateStore.value.path))) { - return; - } - sendTelemetryEvent( - EventName.PYTHON_INTERPRETER_AUTO_SELECTION, - {}, - { rule: this.ruleName, interpreterMissing: true } - ); - await this.cacheSelectedInterpreter(resource, undefined); - } - protected async cacheSelectedInterpreter(_resource: Resource, interpreter: PythonEnvironment | undefined) { - const interpreterPath = interpreter ? interpreter.path : ''; - const interpreterPathInCache = this.stateStore.value ? this.stateStore.value.path : ''; - const updated = interpreterPath === interpreterPathInCache; - sendTelemetryEvent(EventName.PYTHON_INTERPRETER_AUTO_SELECTION, {}, { rule: this.ruleName, updated }); - await this.stateStore.updateValue(interpreter); - } - protected async next(resource: Resource, manager?: IInterpreterAutoSelectionService): Promise { - traceVerbose(`Executing next rule from ${this.ruleName}`); - return this.nextRule && manager ? this.nextRule.autoSelectInterpreter(resource, manager) : undefined; - } -} diff --git a/src/client/interpreter/autoSelection/rules/cached.ts b/src/client/interpreter/autoSelection/rules/cached.ts deleted file mode 100644 index 134a53203051..000000000000 --- a/src/client/interpreter/autoSelection/rules/cached.ts +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable, named } from 'inversify'; -import { traceVerbose } from '../../../common/logger'; -import { IFileSystem } from '../../../common/platform/types'; -import { IPersistentStateFactory, Resource } from '../../../common/types'; -import { IInterpreterHelper } from '../../contracts'; -import { AutoSelectionRule, IInterpreterAutoSelectionRule, IInterpreterAutoSelectionService } from '../types'; -import { BaseRuleService, NextAction } from './baseRule'; - -@injectable() -export class CachedInterpretersAutoSelectionRule extends BaseRuleService { - protected readonly rules: IInterpreterAutoSelectionRule[]; - constructor( - @inject(IFileSystem) fs: IFileSystem, - @inject(IInterpreterHelper) private readonly helper: IInterpreterHelper, - @inject(IPersistentStateFactory) stateFactory: IPersistentStateFactory, - @inject(IInterpreterAutoSelectionRule) - @named(AutoSelectionRule.systemWide) - systemInterpreter: IInterpreterAutoSelectionRule, - @inject(IInterpreterAutoSelectionRule) - @named(AutoSelectionRule.currentPath) - currentPathInterpreter: IInterpreterAutoSelectionRule, - @inject(IInterpreterAutoSelectionRule) - @named(AutoSelectionRule.windowsRegistry) - winRegInterpreter: IInterpreterAutoSelectionRule - ) { - super(AutoSelectionRule.cachedInterpreters, fs, stateFactory); - this.rules = [systemInterpreter, currentPathInterpreter, winRegInterpreter]; - } - protected async onAutoSelectInterpreter( - resource: Resource, - manager?: IInterpreterAutoSelectionService - ): Promise { - const cachedInterpreters = this.rules - .map((item) => item.getPreviouslyAutoSelectedInterpreter(resource)) - .filter((item) => !!item) - .map((item) => item!); - const bestInterpreter = this.helper.getBestInterpreter(cachedInterpreters); - traceVerbose( - `Selected Interpreter from ${this.ruleName}, ${ - bestInterpreter ? JSON.stringify(bestInterpreter) : 'Nothing Selected' - }` - ); - return (await this.setGlobalInterpreter(bestInterpreter, manager)) ? NextAction.exit : NextAction.runNextRule; - } -} diff --git a/src/client/interpreter/autoSelection/rules/currentPath.ts b/src/client/interpreter/autoSelection/rules/currentPath.ts deleted file mode 100644 index a45f9cbbc98b..000000000000 --- a/src/client/interpreter/autoSelection/rules/currentPath.ts +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable, named } from 'inversify'; -import { traceVerbose } from '../../../common/logger'; -import { IFileSystem } from '../../../common/platform/types'; -import { IPersistentStateFactory, Resource } from '../../../common/types'; -import { CURRENT_PATH_SERVICE, IInterpreterHelper, IInterpreterLocatorService } from '../../contracts'; -import { AutoSelectionRule, IInterpreterAutoSelectionService } from '../types'; -import { BaseRuleService, NextAction } from './baseRule'; - -@injectable() -export class CurrentPathInterpretersAutoSelectionRule extends BaseRuleService { - constructor( - @inject(IFileSystem) fs: IFileSystem, - @inject(IInterpreterHelper) private readonly helper: IInterpreterHelper, - @inject(IPersistentStateFactory) stateFactory: IPersistentStateFactory, - @inject(IInterpreterLocatorService) - @named(CURRENT_PATH_SERVICE) - private readonly currentPathInterpreterLocator: IInterpreterLocatorService - ) { - super(AutoSelectionRule.currentPath, fs, stateFactory); - } - protected async onAutoSelectInterpreter( - resource: Resource, - manager?: IInterpreterAutoSelectionService - ): Promise { - const interpreters = await this.currentPathInterpreterLocator.getInterpreters(resource); - const bestInterpreter = this.helper.getBestInterpreter(interpreters); - traceVerbose( - `Selected Interpreter from ${this.ruleName}, ${ - bestInterpreter ? JSON.stringify(bestInterpreter) : 'Nothing Selected' - }` - ); - return (await this.setGlobalInterpreter(bestInterpreter, manager)) ? NextAction.exit : NextAction.runNextRule; - } -} diff --git a/src/client/interpreter/autoSelection/rules/settings.ts b/src/client/interpreter/autoSelection/rules/settings.ts deleted file mode 100644 index eae85e915ca2..000000000000 --- a/src/client/interpreter/autoSelection/rules/settings.ts +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IWorkspaceService } from '../../../common/application/types'; -import { DeprecatePythonPath } from '../../../common/experiments/groups'; -import { IFileSystem } from '../../../common/platform/types'; -import { IExperimentsManager, IInterpreterPathService, IPersistentStateFactory, Resource } from '../../../common/types'; -import { AutoSelectionRule, IInterpreterAutoSelectionService } from '../types'; -import { BaseRuleService, NextAction } from './baseRule'; - -@injectable() -export class SettingsInterpretersAutoSelectionRule extends BaseRuleService { - constructor( - @inject(IFileSystem) fs: IFileSystem, - @inject(IPersistentStateFactory) stateFactory: IPersistentStateFactory, - @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, - @inject(IExperimentsManager) private readonly experiments: IExperimentsManager, - @inject(IInterpreterPathService) private readonly interpreterPathService: IInterpreterPathService - ) { - super(AutoSelectionRule.settings, fs, stateFactory); - } - protected async onAutoSelectInterpreter( - _resource: Resource, - _manager?: IInterpreterAutoSelectionService - ): Promise { - // tslint:disable-next-line:no-any - const pythonConfig = this.workspaceService.getConfiguration('python', null as any)!; - const pythonPathInConfig = this.experiments.inExperiment(DeprecatePythonPath.experiment) - ? this.interpreterPathService.inspect(undefined) - : pythonConfig.inspect('pythonPath')!; - this.experiments.sendTelemetryIfInExperiment(DeprecatePythonPath.control); - // No need to store python paths defined in settings in our caches, they can be retrieved from the settings directly. - return pythonPathInConfig.globalValue && pythonPathInConfig.globalValue !== 'python' - ? NextAction.exit - : NextAction.runNextRule; - } -} diff --git a/src/client/interpreter/autoSelection/rules/system.ts b/src/client/interpreter/autoSelection/rules/system.ts deleted file mode 100644 index 00be6177cdff..000000000000 --- a/src/client/interpreter/autoSelection/rules/system.ts +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { traceVerbose } from '../../../common/logger'; -import { IFileSystem } from '../../../common/platform/types'; -import { IPersistentStateFactory, Resource } from '../../../common/types'; -import { EnvironmentType } from '../../../pythonEnvironments/info'; -import { IInterpreterHelper, IInterpreterService } from '../../contracts'; -import { AutoSelectionRule, IInterpreterAutoSelectionService } from '../types'; -import { BaseRuleService, NextAction } from './baseRule'; - -@injectable() -export class SystemWideInterpretersAutoSelectionRule extends BaseRuleService { - constructor( - @inject(IFileSystem) fs: IFileSystem, - @inject(IInterpreterHelper) private readonly helper: IInterpreterHelper, - @inject(IPersistentStateFactory) stateFactory: IPersistentStateFactory, - @inject(IInterpreterService) private readonly interpreterService: IInterpreterService - ) { - super(AutoSelectionRule.systemWide, fs, stateFactory); - } - protected async onAutoSelectInterpreter( - resource: Resource, - manager?: IInterpreterAutoSelectionService - ): Promise { - const interpreters = await this.interpreterService.getInterpreters(resource); - // Exclude non-local interpreters. - const filteredInterpreters = interpreters.filter( - (int) => - int.envType !== EnvironmentType.VirtualEnv && - int.envType !== EnvironmentType.Venv && - int.envType !== EnvironmentType.Pipenv - ); - const bestInterpreter = this.helper.getBestInterpreter(filteredInterpreters); - traceVerbose( - `Selected Interpreter from ${this.ruleName}, ${ - bestInterpreter ? JSON.stringify(bestInterpreter) : 'Nothing Selected' - }` - ); - return (await this.setGlobalInterpreter(bestInterpreter, manager)) ? NextAction.exit : NextAction.runNextRule; - } -} diff --git a/src/client/interpreter/autoSelection/rules/winRegistry.ts b/src/client/interpreter/autoSelection/rules/winRegistry.ts deleted file mode 100644 index 2442e52eaf24..000000000000 --- a/src/client/interpreter/autoSelection/rules/winRegistry.ts +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable, named } from 'inversify'; -import { traceVerbose } from '../../../common/logger'; -import { IFileSystem, IPlatformService } from '../../../common/platform/types'; -import { IPersistentStateFactory, Resource } from '../../../common/types'; -import { OSType } from '../../../common/utils/platform'; -import { IInterpreterHelper, IInterpreterLocatorService, WINDOWS_REGISTRY_SERVICE } from '../../contracts'; -import { AutoSelectionRule, IInterpreterAutoSelectionService } from '../types'; -import { BaseRuleService, NextAction } from './baseRule'; - -@injectable() -export class WindowsRegistryInterpretersAutoSelectionRule extends BaseRuleService { - constructor( - @inject(IFileSystem) fs: IFileSystem, - @inject(IInterpreterHelper) private readonly helper: IInterpreterHelper, - @inject(IPersistentStateFactory) stateFactory: IPersistentStateFactory, - @inject(IPlatformService) private readonly platform: IPlatformService, - @inject(IInterpreterLocatorService) - @named(WINDOWS_REGISTRY_SERVICE) - private winRegInterpreterLocator: IInterpreterLocatorService - ) { - super(AutoSelectionRule.windowsRegistry, fs, stateFactory); - } - protected async onAutoSelectInterpreter( - resource: Resource, - manager?: IInterpreterAutoSelectionService - ): Promise { - if (this.platform.osType !== OSType.Windows) { - return NextAction.runNextRule; - } - const interpreters = await this.winRegInterpreterLocator.getInterpreters(resource); - const bestInterpreter = this.helper.getBestInterpreter(interpreters); - traceVerbose( - `Selected Interpreter from ${this.ruleName}, ${ - bestInterpreter ? JSON.stringify(bestInterpreter) : 'Nothing Selected' - }` - ); - return (await this.setGlobalInterpreter(bestInterpreter, manager)) ? NextAction.exit : NextAction.runNextRule; - } -} diff --git a/src/client/interpreter/autoSelection/rules/workspaceEnv.ts b/src/client/interpreter/autoSelection/rules/workspaceEnv.ts deleted file mode 100644 index 735965dc2344..000000000000 --- a/src/client/interpreter/autoSelection/rules/workspaceEnv.ts +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable, named } from 'inversify'; -import { Uri } from 'vscode'; -import { IWorkspaceService } from '../../../common/application/types'; -import { DeprecatePythonPath } from '../../../common/experiments/groups'; -import { traceVerbose } from '../../../common/logger'; -import { IFileSystem, IPlatformService } from '../../../common/platform/types'; -import { IExperimentsManager, IInterpreterPathService, IPersistentStateFactory, Resource } from '../../../common/types'; -import { createDeferredFromPromise } from '../../../common/utils/async'; -import { OSType } from '../../../common/utils/platform'; -import { PythonEnvironment } from '../../../pythonEnvironments/info'; -import { IInterpreterHelper, IInterpreterLocatorService, WORKSPACE_VIRTUAL_ENV_SERVICE } from '../../contracts'; -import { AutoSelectionRule, IInterpreterAutoSelectionService } from '../types'; -import { BaseRuleService, NextAction } from './baseRule'; - -@injectable() -export class WorkspaceVirtualEnvInterpretersAutoSelectionRule extends BaseRuleService { - constructor( - @inject(IFileSystem) fs: IFileSystem, - @inject(IInterpreterHelper) private readonly helper: IInterpreterHelper, - @inject(IPersistentStateFactory) stateFactory: IPersistentStateFactory, - @inject(IPlatformService) private readonly platform: IPlatformService, - @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, - @inject(IInterpreterLocatorService) - @named(WORKSPACE_VIRTUAL_ENV_SERVICE) - private readonly workspaceVirtualEnvInterpreterLocator: IInterpreterLocatorService, - @inject(IExperimentsManager) private readonly experiments: IExperimentsManager, - @inject(IInterpreterPathService) private readonly interpreterPathService: IInterpreterPathService - ) { - super(AutoSelectionRule.workspaceVirtualEnvs, fs, stateFactory); - } - protected async onAutoSelectInterpreter( - resource: Resource, - manager?: IInterpreterAutoSelectionService - ): Promise { - const workspacePath = this.helper.getActiveWorkspaceUri(resource); - if (!workspacePath) { - return NextAction.runNextRule; - } - - const pythonConfig = this.workspaceService.getConfiguration('python', workspacePath.folderUri)!; - const pythonPathInConfig = this.experiments.inExperiment(DeprecatePythonPath.experiment) - ? this.interpreterPathService.inspect(workspacePath.folderUri) - : pythonConfig.inspect('pythonPath')!; - this.experiments.sendTelemetryIfInExperiment(DeprecatePythonPath.control); - // If user has defined custom values in settings for this workspace folder, then use that. - if (pythonPathInConfig.workspaceFolderValue || pythonPathInConfig.workspaceValue) { - return NextAction.runNextRule; - } - const virtualEnvPromise = createDeferredFromPromise( - this.getWorkspaceVirtualEnvInterpreters(workspacePath.folderUri) - ); - - const interpreters = await virtualEnvPromise.promise; - const bestInterpreter = - Array.isArray(interpreters) && interpreters.length > 0 - ? this.helper.getBestInterpreter(interpreters) - : undefined; - - if (bestInterpreter && manager) { - await super.cacheSelectedInterpreter(workspacePath.folderUri, bestInterpreter); - await manager.setWorkspaceInterpreter(workspacePath.folderUri!, bestInterpreter); - } - - traceVerbose( - `Selected Interpreter from ${this.ruleName}, ${ - bestInterpreter ? JSON.stringify(bestInterpreter) : 'Nothing Selected' - }` - ); - return NextAction.runNextRule; - } - protected async getWorkspaceVirtualEnvInterpreters(resource: Resource): Promise { - if (!resource) { - return; - } - const workspaceFolder = this.workspaceService.getWorkspaceFolder(resource); - if (!workspaceFolder) { - return; - } - // Now check virtual environments under the workspace root - const interpreters = await this.workspaceVirtualEnvInterpreterLocator.getInterpreters(resource, { - ignoreCache: true - }); - const workspacePath = - this.platform.osType === OSType.Windows - ? workspaceFolder.uri.fsPath.toUpperCase() - : workspaceFolder.uri.fsPath; - - return interpreters.filter((interpreter) => { - const fsPath = Uri.file(interpreter.path).fsPath; - const fsPathToCompare = this.platform.osType === OSType.Windows ? fsPath.toUpperCase() : fsPath; - return fsPathToCompare.startsWith(workspacePath); - }); - } -} diff --git a/src/client/interpreter/autoSelection/types.ts b/src/client/interpreter/autoSelection/types.ts index 03b952ece6f0..91d0224717d4 100644 --- a/src/client/interpreter/autoSelection/types.ts +++ b/src/client/interpreter/autoSelection/types.ts @@ -4,30 +4,26 @@ 'use strict'; import { Event, Uri } from 'vscode'; -import { IExtensionSingleActivationService } from '../../activation/types'; -import { IPersistentState, Resource } from '../../common/types'; +import { Resource } from '../../common/types'; import { PythonEnvironment } from '../../pythonEnvironments/info'; -export const IInterpreterAutoSeletionProxyService = Symbol('IInterpreterAutoSeletionProxyService'); +export const IInterpreterAutoSelectionProxyService = Symbol('IInterpreterAutoSelectionProxyService'); /** * Interface similar to IInterpreterAutoSelectionService, to avoid chickn n egg situation. * Do we get python path from config first or get auto selected interpreter first!? * However, the class that reads python Path, must first give preference to selected interpreter. * But all classes everywhere make use of python settings! * Solution - Use a proxy that does nothing first, but later the real instance is injected. - * - * @export - * @interface IInterpreterAutoSeletionProxyService */ -export interface IInterpreterAutoSeletionProxyService { +export interface IInterpreterAutoSelectionProxyService { readonly onDidChangeAutoSelectedInterpreter: Event; getAutoSelectedInterpreter(resource: Resource): PythonEnvironment | undefined; - registerInstance?(instance: IInterpreterAutoSeletionProxyService): void; + registerInstance?(instance: IInterpreterAutoSelectionProxyService): void; setWorkspaceInterpreter(resource: Uri, interpreter: PythonEnvironment | undefined): Promise; } export const IInterpreterAutoSelectionService = Symbol('IInterpreterAutoSelectionService'); -export interface IInterpreterAutoSelectionService extends IInterpreterAutoSeletionProxyService { +export interface IInterpreterAutoSelectionService extends IInterpreterAutoSelectionProxyService { readonly onDidChangeAutoSelectedInterpreter: Event; autoSelectInterpreter(resource: Resource): Promise; getAutoSelectedInterpreter(resource: Resource): PythonEnvironment | undefined; @@ -41,34 +37,5 @@ export enum AutoSelectionRule { settings = 'settings', cachedInterpreters = 'cachedInterpreters', systemWide = 'system', - windowsRegistry = 'windowsRegistry' -} - -export const IInterpreterAutoSelectionRule = Symbol('IInterpreterAutoSelectionRule'); -export interface IInterpreterAutoSelectionRule { - setNextRule(rule: IInterpreterAutoSelectionRule): void; - autoSelectInterpreter(resource: Resource, manager?: IInterpreterAutoSelectionService): Promise; - getPreviouslyAutoSelectedInterpreter(resource: Resource): PythonEnvironment | undefined; -} - -export const IInterpreterSecurityService = Symbol('IInterpreterSecurityService'); -export interface IInterpreterSecurityService { - readonly onDidChangeSafeInterpreters: Event; - evaluateAndRecordInterpreterSafety(interpreter: PythonEnvironment, resource: Resource): Promise; - isSafe(interpreter: PythonEnvironment, resource?: Resource): boolean | undefined; -} - -export const IInterpreterSecurityStorage = Symbol('IInterpreterSecurityStorage'); -export interface IInterpreterSecurityStorage extends IExtensionSingleActivationService { - readonly unsafeInterpreterPromptEnabled: IPersistentState; - readonly unsafeInterpreters: IPersistentState; - readonly safeInterpreters: IPersistentState; - hasUserApprovedWorkspaceInterpreters(resource: Uri): IPersistentState; - storeKeyForWorkspace(resource: Uri): Promise; -} - -export const IInterpreterEvaluation = Symbol('IInterpreterEvaluation'); -export interface IInterpreterEvaluation { - evaluateIfInterpreterIsSafe(interpreter: PythonEnvironment, resource: Resource): Promise; - inferValueUsingCurrentState(interpreter: PythonEnvironment, resource: Resource): boolean | undefined; + windowsRegistry = 'windowsRegistry', } diff --git a/src/client/interpreter/configuration/environmentTypeComparer.ts b/src/client/interpreter/configuration/environmentTypeComparer.ts new file mode 100644 index 000000000000..2e1013b7b5a8 --- /dev/null +++ b/src/client/interpreter/configuration/environmentTypeComparer.ts @@ -0,0 +1,299 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { injectable, inject } from 'inversify'; +import { Resource } from '../../common/types'; +import { Architecture } from '../../common/utils/platform'; +import { isActiveStateEnvironmentForWorkspace } from '../../pythonEnvironments/common/environmentManagers/activestate'; +import { isParentPath } from '../../pythonEnvironments/common/externalDependencies'; +import { + EnvironmentType, + PythonEnvironment, + virtualEnvTypes, + workspaceVirtualEnvTypes, +} from '../../pythonEnvironments/info'; +import { PythonVersion } from '../../pythonEnvironments/info/pythonVersion'; +import { IInterpreterHelper } from '../contracts'; +import { IInterpreterComparer } from './types'; +import { getActivePyenvForDirectory } from '../../pythonEnvironments/common/environmentManagers/pyenv'; +import { arePathsSame } from '../../common/platform/fs-paths'; + +export enum EnvLocationHeuristic { + /** + * Environments inside the workspace. + */ + Local = 1, + /** + * Environments outside the workspace. + */ + Global = 2, +} + +@injectable() +export class EnvironmentTypeComparer implements IInterpreterComparer { + private workspaceFolderPath: string; + + private preferredPyenvInterpreterPath = new Map(); + + constructor(@inject(IInterpreterHelper) private readonly interpreterHelper: IInterpreterHelper) { + this.workspaceFolderPath = this.interpreterHelper.getActiveWorkspaceUri(undefined)?.folderUri.fsPath ?? ''; + } + + /** + * Compare 2 Python environments, sorting them by assumed usefulness. + * Return 0 if both environments are equal, -1 if a should be closer to the beginning of the list, or 1 if a comes after b. + * + * The comparison guidelines are: + * 1. Local environments first (same path as the workspace root); + * 2. Global environments next (anything not local), with conda environments at a lower priority, and "base" being last; + * 3. Globally-installed interpreters (/usr/bin/python3, Microsoft Store). + * + * Always sort with newest version of Python first within each subgroup. + */ + public compare(a: PythonEnvironment, b: PythonEnvironment): number { + if (isProblematicCondaEnvironment(a)) { + return 1; + } + if (isProblematicCondaEnvironment(b)) { + return -1; + } + // Check environment location. + const envLocationComparison = compareEnvironmentLocation(a, b, this.workspaceFolderPath); + if (envLocationComparison !== 0) { + return envLocationComparison; + } + + if (a.envType === EnvironmentType.Pyenv && b.envType === EnvironmentType.Pyenv) { + const preferredPyenv = this.preferredPyenvInterpreterPath.get(this.workspaceFolderPath); + if (preferredPyenv) { + if (arePathsSame(preferredPyenv, b.path)) { + return 1; + } + if (arePathsSame(preferredPyenv, a.path)) { + return -1; + } + } + } + + // Check environment type. + const envTypeComparison = compareEnvironmentType(a, b); + if (envTypeComparison !== 0) { + return envTypeComparison; + } + + // Check Python version. + const versionComparison = comparePythonVersionDescending(a.version, b.version); + if (versionComparison !== 0) { + return versionComparison; + } + + // If we have the "base" Conda env, put it last in its Python version subgroup. + if (isBaseCondaEnvironment(a)) { + return 1; + } + + if (isBaseCondaEnvironment(b)) { + return -1; + } + + // Check alphabetical order. + const nameA = getSortName(a, this.interpreterHelper); + const nameB = getSortName(b, this.interpreterHelper); + if (nameA === nameB) { + return 0; + } + + return nameA > nameB ? 1 : -1; + } + + public async initialize(resource: Resource): Promise { + const workspaceUri = this.interpreterHelper.getActiveWorkspaceUri(resource); + const cwd = workspaceUri?.folderUri.fsPath; + if (!cwd) { + return; + } + const preferredPyenvInterpreter = await getActivePyenvForDirectory(cwd); + this.preferredPyenvInterpreterPath.set(cwd, preferredPyenvInterpreter); + } + + public getRecommended(interpreters: PythonEnvironment[], resource: Resource): PythonEnvironment | undefined { + // When recommending an intepreter for a workspace, we either want to return a local one + // or fallback on a globally-installed interpreter, and we don't want want to suggest a global environment + // because we would have to add a way to match environments to a workspace. + const workspaceUri = this.interpreterHelper.getActiveWorkspaceUri(resource); + const filteredInterpreters = interpreters.filter((i) => { + if (isProblematicCondaEnvironment(i)) { + return false; + } + if ( + i.envType === EnvironmentType.ActiveState && + (!i.path || + !workspaceUri || + !isActiveStateEnvironmentForWorkspace(i.path, workspaceUri.folderUri.fsPath)) + ) { + return false; + } + if (getEnvLocationHeuristic(i, workspaceUri?.folderUri.fsPath || '') === EnvLocationHeuristic.Local) { + return true; + } + if (!workspaceVirtualEnvTypes.includes(i.envType) && virtualEnvTypes.includes(i.envType)) { + // These are global virtual envs so we're not sure if these envs were created for the workspace, skip them. + return false; + } + if (i.version?.major === 2) { + return false; + } + return true; + }); + filteredInterpreters.sort(this.compare.bind(this)); + return filteredInterpreters.length ? filteredInterpreters[0] : undefined; + } +} + +function getSortName(info: PythonEnvironment, interpreterHelper: IInterpreterHelper): string { + const sortNameParts: string[] = []; + const envSuffixParts: string[] = []; + + // Sort order for interpreters is: + // * Version + // * Architecture + // * Interpreter Type + // * Environment name + if (info.version) { + sortNameParts.push(info.version.raw); + } + if (info.architecture) { + sortNameParts.push(getArchitectureSortName(info.architecture)); + } + if (info.companyDisplayName && info.companyDisplayName.length > 0) { + sortNameParts.push(info.companyDisplayName.trim()); + } else { + sortNameParts.push('Python'); + } + + if (info.envType) { + const name = interpreterHelper.getInterpreterTypeDisplayName(info.envType); + if (name) { + envSuffixParts.push(name); + } + } + if (info.envName && info.envName.length > 0) { + envSuffixParts.push(info.envName); + } + + const envSuffix = envSuffixParts.length === 0 ? '' : `(${envSuffixParts.join(': ')})`; + return `${sortNameParts.join(' ')} ${envSuffix}`.trim(); +} + +function getArchitectureSortName(arch?: Architecture) { + // Strings are choosen keeping in mind that 64-bit gets preferred over 32-bit. + switch (arch) { + case Architecture.x64: + return 'x64'; + case Architecture.x86: + return 'x86'; + default: + return ''; + } +} + +function isBaseCondaEnvironment(environment: PythonEnvironment): boolean { + return ( + environment.envType === EnvironmentType.Conda && + (environment.envName === 'base' || environment.envName === 'miniconda') + ); +} + +export function isProblematicCondaEnvironment(environment: PythonEnvironment): boolean { + return environment.envType === EnvironmentType.Conda && environment.path === 'python'; +} + +/** + * Compare 2 Python versions in decending order, most recent one comes first. + */ +function comparePythonVersionDescending(a: PythonVersion | undefined, b: PythonVersion | undefined): number { + if (!a) { + return 1; + } + + if (!b) { + return -1; + } + + if (a.raw === b.raw) { + return 0; + } + + if (a.major === b.major) { + if (a.minor === b.minor) { + if (a.patch === b.patch) { + return a.build.join(' ') > b.build.join(' ') ? -1 : 1; + } + return a.patch > b.patch ? -1 : 1; + } + return a.minor > b.minor ? -1 : 1; + } + + return a.major > b.major ? -1 : 1; +} + +/** + * Compare 2 environment locations: return 0 if they are the same, -1 if a comes before b, 1 otherwise. + */ +function compareEnvironmentLocation(a: PythonEnvironment, b: PythonEnvironment, workspacePath: string): number { + const aHeuristic = getEnvLocationHeuristic(a, workspacePath); + const bHeuristic = getEnvLocationHeuristic(b, workspacePath); + + return Math.sign(aHeuristic - bHeuristic); +} + +/** + * Return a heuristic value depending on the environment type. + */ +export function getEnvLocationHeuristic(environment: PythonEnvironment, workspacePath: string): EnvLocationHeuristic { + if ( + workspacePath.length > 0 && + ((environment.envPath && isParentPath(environment.envPath, workspacePath)) || + (environment.path && isParentPath(environment.path, workspacePath))) + ) { + return EnvLocationHeuristic.Local; + } + return EnvLocationHeuristic.Global; +} + +/** + * Compare 2 environment types: return 0 if they are the same, -1 if a comes before b, 1 otherwise. + */ +function compareEnvironmentType(a: PythonEnvironment, b: PythonEnvironment): number { + if (!a.type && !b.type) { + // Unless one of them is pyenv interpreter, return 0 if two global interpreters are being compared. + if (a.envType === EnvironmentType.Pyenv && b.envType !== EnvironmentType.Pyenv) { + return -1; + } + if (a.envType !== EnvironmentType.Pyenv && b.envType === EnvironmentType.Pyenv) { + return 1; + } + return 0; + } + const envTypeByPriority = getPrioritizedEnvironmentType(); + return Math.sign(envTypeByPriority.indexOf(a.envType) - envTypeByPriority.indexOf(b.envType)); +} + +function getPrioritizedEnvironmentType(): EnvironmentType[] { + return [ + // Prioritize non-Conda environments. + EnvironmentType.Poetry, + EnvironmentType.Pipenv, + EnvironmentType.VirtualEnvWrapper, + EnvironmentType.Hatch, + EnvironmentType.Venv, + EnvironmentType.VirtualEnv, + EnvironmentType.ActiveState, + EnvironmentType.Conda, + EnvironmentType.Pyenv, + EnvironmentType.MicrosoftStore, + EnvironmentType.Global, + EnvironmentType.System, + EnvironmentType.Unknown, + ]; +} diff --git a/src/client/interpreter/configuration/interpreterComparer.ts b/src/client/interpreter/configuration/interpreterComparer.ts deleted file mode 100644 index b309050590a3..000000000000 --- a/src/client/interpreter/configuration/interpreterComparer.ts +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { getArchitectureDisplayName } from '../../common/platform/registry'; -import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { IInterpreterHelper } from '../contracts'; -import { IInterpreterComparer } from './types'; - -@injectable() -export class InterpreterComparer implements IInterpreterComparer { - constructor(@inject(IInterpreterHelper) private readonly interpreterHelper: IInterpreterHelper) {} - public compare(a: PythonEnvironment, b: PythonEnvironment): number { - const nameA = this.getSortName(a); - const nameB = this.getSortName(b); - if (nameA === nameB) { - return 0; - } - return nameA > nameB ? 1 : -1; - } - private getSortName(info: PythonEnvironment): string { - const sortNameParts: string[] = []; - const envSuffixParts: string[] = []; - - // Sort order for interpreters is: - // * Version - // * Architecture - // * Interpreter Type - // * Environment name - if (info.version) { - sortNameParts.push(info.version.raw); - } - if (info.architecture) { - sortNameParts.push(getArchitectureDisplayName(info.architecture)); - } - if (info.companyDisplayName && info.companyDisplayName.length > 0) { - sortNameParts.push(info.companyDisplayName.trim()); - } else { - sortNameParts.push('Python'); - } - - if (info.envType) { - const name = this.interpreterHelper.getInterpreterTypeDisplayName(info.envType); - if (name) { - envSuffixParts.push(name); - } - } - if (info.envName && info.envName.length > 0) { - envSuffixParts.push(info.envName); - } - - const envSuffix = envSuffixParts.length === 0 ? '' : `(${envSuffixParts.join(': ')})`; - return `${sortNameParts.join(' ')} ${envSuffix}`.trim(); - } -} diff --git a/src/client/interpreter/configuration/interpreterSelector/commands/base.ts b/src/client/interpreter/configuration/interpreterSelector/commands/base.ts index d47bcab9aff5..6307e286dbfe 100644 --- a/src/client/interpreter/configuration/interpreterSelector/commands/base.ts +++ b/src/client/interpreter/configuration/interpreterSelector/commands/base.ts @@ -8,18 +8,23 @@ import * as path from 'path'; import { ConfigurationTarget, Disposable, QuickPickItem, Uri } from 'vscode'; import { IExtensionSingleActivationService } from '../../../../activation/types'; import { IApplicationShell, ICommandManager, IWorkspaceService } from '../../../../common/application/types'; -import { IDisposable, Resource } from '../../../../common/types'; -import { Interpreters } from '../../../../common/utils/localize'; +import { IConfigurationService, IDisposable, IPathUtils, Resource } from '../../../../common/types'; +import { Common, Interpreters } from '../../../../common/utils/localize'; import { IPythonPathUpdaterServiceManager } from '../../types'; - +export interface WorkspaceSelectionQuickPickItem extends QuickPickItem { + uri?: Uri; +} @injectable() export abstract class BaseInterpreterSelectorCommand implements IExtensionSingleActivationService, IDisposable { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; protected disposables: Disposable[] = []; constructor( @unmanaged() protected readonly pythonPathUpdaterService: IPythonPathUpdaterServiceManager, @unmanaged() protected readonly commandManager: ICommandManager, @unmanaged() protected readonly applicationShell: IApplicationShell, - @unmanaged() protected readonly workspaceService: IWorkspaceService + @unmanaged() protected readonly workspaceService: IWorkspaceService, + @unmanaged() protected readonly pathUtils: IPathUtils, + @unmanaged() protected readonly configurationService: IConfigurationService, ) { this.disposables.push(this); } @@ -28,54 +33,87 @@ export abstract class BaseInterpreterSelectorCommand implements IExtensionSingle this.disposables.forEach((disposable) => disposable.dispose()); } - public abstract async activate(): Promise; + public abstract activate(): Promise; - protected async getConfigTarget(): Promise< + protected async getConfigTargets(options?: { + resetTarget?: boolean; + }): Promise< | { folderUri: Resource; configTarget: ConfigurationTarget; - } + }[] | undefined > { - if ( - !Array.isArray(this.workspaceService.workspaceFolders) || - this.workspaceService.workspaceFolders.length === 0 - ) { - return { - folderUri: undefined, - configTarget: ConfigurationTarget.Global - }; + const workspaceFolders = this.workspaceService.workspaceFolders; + if (workspaceFolders === undefined || workspaceFolders.length === 0) { + return [ + { + folderUri: undefined, + configTarget: ConfigurationTarget.Global, + }, + ]; } - if (!this.workspaceService.workspaceFile && this.workspaceService.workspaceFolders.length === 1) { - return { - folderUri: this.workspaceService.workspaceFolders[0].uri, - configTarget: ConfigurationTarget.WorkspaceFolder - }; + if (workspaceFolders.length === 1) { + return [ + { + folderUri: workspaceFolders[0].uri, + configTarget: ConfigurationTarget.WorkspaceFolder, + }, + ]; } // Ok we have multiple workspaces, get the user to pick a folder. - type WorkspaceSelectionQuickPickItem = QuickPickItem & { uri: Uri }; - const quickPickItems: WorkspaceSelectionQuickPickItem[] = [ - ...this.workspaceService.workspaceFolders.map((w) => ({ - label: w.name, - description: path.dirname(w.uri.fsPath), - uri: w.uri - })), + let quickPickItems: WorkspaceSelectionQuickPickItem[] = options?.resetTarget + ? [ + { + label: Common.clearAll, + }, + ] + : []; + quickPickItems.push( + ...workspaceFolders.map((w) => { + const selectedInterpreter = this.pathUtils.getDisplayName( + this.configurationService.getSettings(w.uri).pythonPath, + w.uri.fsPath, + ); + return { + label: w.name, + description: this.pathUtils.getDisplayName(path.dirname(w.uri.fsPath)), + uri: w.uri, + detail: selectedInterpreter, + }; + }), { - label: Interpreters.entireWorkspace(), - uri: this.workspaceService.workspaceFolders[0].uri - } - ]; + label: options?.resetTarget ? Interpreters.clearAtWorkspace : Interpreters.entireWorkspace, + uri: workspaceFolders[0].uri, + }, + ); const selection = await this.applicationShell.showQuickPick(quickPickItems, { - placeHolder: 'Select the workspace to set the interpreter' + placeHolder: options?.resetTarget + ? 'Select the workspace folder to clear the interpreter for' + : 'Select the workspace folder to set the interpreter', }); + if (selection?.label === Common.clearAll) { + const folderTargets: { + folderUri: Resource; + configTarget: ConfigurationTarget; + }[] = workspaceFolders.map((w) => ({ + folderUri: w.uri, + configTarget: ConfigurationTarget.WorkspaceFolder, + })); + return [ + ...folderTargets, + { folderUri: workspaceFolders[0].uri, configTarget: ConfigurationTarget.Workspace }, + ]; + } + return selection - ? selection.label === Interpreters.entireWorkspace() - ? { folderUri: selection.uri, configTarget: ConfigurationTarget.Workspace } - : { folderUri: selection.uri, configTarget: ConfigurationTarget.WorkspaceFolder } + ? selection.label === Interpreters.entireWorkspace || selection.label === Interpreters.clearAtWorkspace + ? [{ folderUri: selection.uri, configTarget: ConfigurationTarget.Workspace }] + : [{ folderUri: selection.uri, configTarget: ConfigurationTarget.WorkspaceFolder }] : undefined; } } diff --git a/src/client/interpreter/configuration/interpreterSelector/commands/installPython/index.ts b/src/client/interpreter/configuration/interpreterSelector/commands/installPython/index.ts new file mode 100644 index 000000000000..d6d423c1eab8 --- /dev/null +++ b/src/client/interpreter/configuration/interpreterSelector/commands/installPython/index.ts @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { IExtensionSingleActivationService } from '../../../../../activation/types'; +import { ExtensionContextKey } from '../../../../../common/application/contextKeys'; +import { ICommandManager, IContextKeyManager } from '../../../../../common/application/types'; +import { PythonWelcome } from '../../../../../common/application/walkThroughs'; +import { Commands, PVSC_EXTENSION_ID } from '../../../../../common/constants'; +import { IBrowserService, IDisposableRegistry } from '../../../../../common/types'; +import { IPlatformService } from '../../../../../common/platform/types'; + +@injectable() +export class InstallPythonCommand implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: true, virtualWorkspace: false }; + + constructor( + @inject(ICommandManager) private readonly commandManager: ICommandManager, + @inject(IContextKeyManager) private readonly contextManager: IContextKeyManager, + @inject(IBrowserService) private readonly browserService: IBrowserService, + @inject(IPlatformService) private readonly platformService: IPlatformService, + @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry, + ) {} + + public async activate(): Promise { + this.disposables.push(this.commandManager.registerCommand(Commands.InstallPython, () => this._installPython())); + } + + public async _installPython(): Promise { + if (this.platformService.isWindows) { + const version = await this.platformService.getVersion(); + if (version.major > 8) { + // OS is not Windows 8, ms-windows-store URIs are available: + // https://docs.microsoft.com/en-us/windows/uwp/launch-resume/launch-store-app + this.browserService.launch('ms-windows-store://pdp/?ProductId=9NRWMJP3717K'); + return; + } + } + this.showInstallPythonTile(); + } + + private showInstallPythonTile() { + this.contextManager.setContext(ExtensionContextKey.showInstallPythonTile, true); + let step: string; + if (this.platformService.isWindows) { + step = PythonWelcome.windowsInstallId; + } else if (this.platformService.isLinux) { + step = PythonWelcome.linuxInstallId; + } else { + step = PythonWelcome.macOSInstallId; + } + this.commandManager.executeCommand( + 'workbench.action.openWalkthrough', + { + category: `${PVSC_EXTENSION_ID}#${PythonWelcome.name}`, + step: `${PVSC_EXTENSION_ID}#${PythonWelcome.name}#${step}`, + }, + false, + ); + } +} diff --git a/src/client/interpreter/configuration/interpreterSelector/commands/installPython/installPythonViaTerminal.ts b/src/client/interpreter/configuration/interpreterSelector/commands/installPython/installPythonViaTerminal.ts new file mode 100644 index 000000000000..3b4a6d428baa --- /dev/null +++ b/src/client/interpreter/configuration/interpreterSelector/commands/installPython/installPythonViaTerminal.ts @@ -0,0 +1,115 @@ +/* eslint-disable global-require */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import type * as whichTypes from 'which'; +import { inject, injectable } from 'inversify'; +import { IExtensionSingleActivationService } from '../../../../../activation/types'; +import { Commands } from '../../../../../common/constants'; +import { IDisposableRegistry } from '../../../../../common/types'; +import { ICommandManager, ITerminalManager } from '../../../../../common/application/types'; +import { sleep } from '../../../../../common/utils/async'; +import { OSType } from '../../../../../common/utils/platform'; +import { traceVerbose } from '../../../../../logging'; +import { Interpreters } from '../../../../../common/utils/localize'; + +enum PackageManagers { + brew = 'brew', + apt = 'apt', + dnf = 'dnf', +} + +/** + * Runs commands listed in walkthrough to install Python. + */ +@injectable() +export class InstallPythonViaTerminal implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: true, virtualWorkspace: false }; + + private readonly packageManagerCommands: Record = { + brew: ['brew install python3'], + dnf: ['sudo dnf install python3'], + apt: ['sudo apt-get update', 'sudo apt-get install python3 python3-venv python3-pip'], + }; + + constructor( + @inject(ICommandManager) private readonly commandManager: ICommandManager, + @inject(ITerminalManager) private readonly terminalManager: ITerminalManager, + @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry, + ) {} + + public async activate(): Promise { + this.disposables.push( + this.commandManager.registerCommand(Commands.InstallPythonOnMac, () => + this._installPythonOnUnix(OSType.OSX), + ), + ); + this.disposables.push( + this.commandManager.registerCommand(Commands.InstallPythonOnLinux, () => + this._installPythonOnUnix(OSType.Linux), + ), + ); + } + + public async _installPythonOnUnix(os: OSType.Linux | OSType.OSX): Promise { + const commands = await this.getCommands(os); + const installMessage = + os === OSType.OSX + ? Interpreters.installPythonTerminalMacMessage + : Interpreters.installPythonTerminalMessageLinux; + const terminal = this.terminalManager.createTerminal({ + name: 'Python', + message: commands.length ? undefined : installMessage, + }); + terminal.show(true); + await waitForTerminalToStartup(); + for (const command of commands) { + terminal.sendText(command); + await waitForCommandToProcess(); + } + } + + private async getCommands(os: OSType.Linux | OSType.OSX) { + if (os === OSType.OSX) { + return this.getCommandsForPackageManagers([PackageManagers.brew]); + } + if (os === OSType.Linux) { + return this.getCommandsForPackageManagers([PackageManagers.apt, PackageManagers.dnf]); + } + throw new Error('OS not supported'); + } + + private async getCommandsForPackageManagers(packageManagers: PackageManagers[]) { + for (const packageManager of packageManagers) { + if (await isPackageAvailable(packageManager)) { + return this.packageManagerCommands[packageManager]; + } + } + return []; + } +} + +async function isPackageAvailable(packageManager: PackageManagers) { + try { + const which = require('which') as typeof whichTypes; + const resolvedPath = await which.default(packageManager); + traceVerbose(`Resolved path to ${packageManager} module:`, resolvedPath); + return resolvedPath.trim().length > 0; + } catch (ex) { + traceVerbose(`${packageManager} not found`, ex); + return false; + } +} + +async function waitForTerminalToStartup() { + // Sometimes the terminal takes some time to start up before it can start accepting input. + await sleep(100); +} + +async function waitForCommandToProcess() { + // Give the command some time to complete. + // Its been observed that sending commands too early will strip some text off in VS Code Terminal. + await sleep(500); +} diff --git a/src/client/interpreter/configuration/interpreterSelector/commands/resetInterpreter.ts b/src/client/interpreter/configuration/interpreterSelector/commands/resetInterpreter.ts index b605b15389be..c10f90781adb 100644 --- a/src/client/interpreter/configuration/interpreterSelector/commands/resetInterpreter.ts +++ b/src/client/interpreter/configuration/interpreterSelector/commands/resetInterpreter.ts @@ -6,8 +6,11 @@ import { inject, injectable } from 'inversify'; import { IApplicationShell, ICommandManager, IWorkspaceService } from '../../../../common/application/types'; import { Commands } from '../../../../common/constants'; +import { IConfigurationService, IPathUtils } from '../../../../common/types'; import { IPythonPathUpdaterServiceManager } from '../../types'; import { BaseInterpreterSelectorCommand } from './base'; +import { useEnvExtension } from '../../../../envExt/api.internal'; +import { resetInterpreterLegacy } from '../../../../envExt/api.legacy'; @injectable() export class ResetInterpreterCommand extends BaseInterpreterSelectorCommand { @@ -15,25 +18,40 @@ export class ResetInterpreterCommand extends BaseInterpreterSelectorCommand { @inject(IPythonPathUpdaterServiceManager) pythonPathUpdaterService: IPythonPathUpdaterServiceManager, @inject(ICommandManager) commandManager: ICommandManager, @inject(IApplicationShell) applicationShell: IApplicationShell, - @inject(IWorkspaceService) workspaceService: IWorkspaceService + @inject(IWorkspaceService) workspaceService: IWorkspaceService, + @inject(IPathUtils) pathUtils: IPathUtils, + @inject(IConfigurationService) configurationService: IConfigurationService, ) { - super(pythonPathUpdaterService, commandManager, applicationShell, workspaceService); + super( + pythonPathUpdaterService, + commandManager, + applicationShell, + workspaceService, + pathUtils, + configurationService, + ); } public async activate() { this.disposables.push( - this.commandManager.registerCommand(Commands.ClearWorkspaceInterpreter, this.resetInterpreter.bind(this)) + this.commandManager.registerCommand(Commands.ClearWorkspaceInterpreter, this.resetInterpreter.bind(this)), ); } public async resetInterpreter() { - const targetConfig = await this.getConfigTarget(); - if (!targetConfig) { + const targetConfigs = await this.getConfigTargets({ resetTarget: true }); + if (!targetConfigs) { return; } - const configTarget = targetConfig.configTarget; - const wkspace = targetConfig.folderUri; - - await this.pythonPathUpdaterService.updatePythonPath(undefined, configTarget, 'ui', wkspace); + await Promise.all( + targetConfigs.map(async (targetConfig) => { + const configTarget = targetConfig.configTarget; + const wkspace = targetConfig.folderUri; + await this.pythonPathUpdaterService.updatePythonPath(undefined, configTarget, 'ui', wkspace); + if (useEnvExtension()) { + await resetInterpreterLegacy(wkspace); + } + }), + ); } } diff --git a/src/client/interpreter/configuration/interpreterSelector/commands/setInterpreter.ts b/src/client/interpreter/configuration/interpreterSelector/commands/setInterpreter.ts index 3c021195ef31..a629d1bc793c 100644 --- a/src/client/interpreter/configuration/interpreterSelector/commands/setInterpreter.ts +++ b/src/client/interpreter/configuration/interpreterSelector/commands/setInterpreter.ts @@ -4,139 +4,720 @@ 'use strict'; import { inject, injectable } from 'inversify'; -import { QuickPickItem } from 'vscode'; +import { cloneDeep } from 'lodash'; +import * as path from 'path'; +import { + l10n, + QuickInputButton, + QuickInputButtons, + QuickPick, + QuickPickItem, + QuickPickItemKind, + ThemeIcon, +} from 'vscode'; import { IApplicationShell, ICommandManager, IWorkspaceService } from '../../../../common/application/types'; -import { Commands } from '../../../../common/constants'; +import { Commands, Octicons, ThemeIcons } from '../../../../common/constants'; +import { isParentPath } from '../../../../common/platform/fs-paths'; import { IPlatformService } from '../../../../common/platform/types'; import { IConfigurationService, IPathUtils, Resource } from '../../../../common/types'; -import { InterpreterQuickPickList } from '../../../../common/utils/localize'; +import { Common, InterpreterQuickPickList } from '../../../../common/utils/localize'; +import { noop } from '../../../../common/utils/misc'; import { IMultiStepInput, IMultiStepInputFactory, + InputFlowAction, InputStep, - IQuickPickParameters + IQuickPickParameters, + QuickInputButtonSetup, } from '../../../../common/utils/multiStepInput'; +import { SystemVariables } from '../../../../common/variables/systemVariables'; +import { TriggerRefreshOptions } from '../../../../pythonEnvironments/base/locator'; +import { EnvironmentType, PythonEnvironment } from '../../../../pythonEnvironments/info'; import { captureTelemetry, sendTelemetryEvent } from '../../../../telemetry'; import { EventName } from '../../../../telemetry/constants'; -import { IInterpreterQuickPickItem, IInterpreterSelector, IPythonPathUpdaterServiceManager } from '../../types'; +import { IInterpreterService, PythonEnvironmentsChangedEvent } from '../../../contracts'; +import { isProblematicCondaEnvironment } from '../../environmentTypeComparer'; +import { + IInterpreterQuickPick, + IInterpreterQuickPickItem, + IInterpreterSelector, + InterpreterQuickPickParams, + IPythonPathUpdaterServiceManager, + ISpecialQuickPickItem, +} from '../../types'; import { BaseInterpreterSelectorCommand } from './base'; +import { untildify } from '../../../../common/helpers'; +import { useEnvExtension } from '../../../../envExt/api.internal'; +import { setInterpreterLegacy } from '../../../../envExt/api.legacy'; +import { CreateEnvironmentResult } from '../../../../pythonEnvironments/creation/proposed.createEnvApis'; export type InterpreterStateArgs = { path?: string; workspace: Resource }; +export type QuickPickType = IInterpreterQuickPickItem | ISpecialQuickPickItem | QuickPickItem; + +function isInterpreterQuickPickItem(item: QuickPickType): item is IInterpreterQuickPickItem { + return 'interpreter' in item; +} + +function isSpecialQuickPickItem(item: QuickPickType): item is ISpecialQuickPickItem { + return 'alwaysShow' in item; +} + +function isSeparatorItem(item: QuickPickType): item is QuickPickItem { + return 'kind' in item && item.kind === QuickPickItemKind.Separator; +} + +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace EnvGroups { + export const Workspace = InterpreterQuickPickList.workspaceGroupName; + export const Conda = 'Conda'; + export const Global = InterpreterQuickPickList.globalGroupName; + export const VirtualEnv = 'VirtualEnv'; + export const PipEnv = 'PipEnv'; + export const Pyenv = 'Pyenv'; + export const Venv = 'Venv'; + export const Poetry = 'Poetry'; + export const Hatch = 'Hatch'; + export const Pixi = 'Pixi'; + export const VirtualEnvWrapper = 'VirtualEnvWrapper'; + export const ActiveState = 'ActiveState'; + export const Recommended = Common.recommended; +} + @injectable() -export class SetInterpreterCommand extends BaseInterpreterSelectorCommand { +export class SetInterpreterCommand extends BaseInterpreterSelectorCommand implements IInterpreterQuickPick { + private readonly createEnvironmentSuggestion: QuickPickItem = { + label: `${Octicons.Add} ${InterpreterQuickPickList.create.label}`, + alwaysShow: true, + }; + + private readonly manualEntrySuggestion: ISpecialQuickPickItem = { + label: `${Octicons.Folder} ${InterpreterQuickPickList.enterPath.label}`, + alwaysShow: true, + }; + + private readonly refreshButton = { + iconPath: new ThemeIcon(ThemeIcons.Refresh), + tooltip: InterpreterQuickPickList.refreshInterpreterList, + }; + + private readonly noPythonInstalled: ISpecialQuickPickItem = { + label: `${Octicons.Error} ${InterpreterQuickPickList.noPythonInstalled}`, + detail: InterpreterQuickPickList.clickForInstructions, + alwaysShow: true, + }; + + private wasNoPythonInstalledItemClicked = false; + + private readonly tipToReloadWindow: ISpecialQuickPickItem = { + label: `${Octicons.Lightbulb} Reload the window if you installed Python but don't see it`, + detail: `Click to run \`Developer: Reload Window\` command`, + alwaysShow: true, + }; + constructor( @inject(IApplicationShell) applicationShell: IApplicationShell, - @inject(IPathUtils) private readonly pathUtils: IPathUtils, + @inject(IPathUtils) pathUtils: IPathUtils, @inject(IPythonPathUpdaterServiceManager) pythonPathUpdaterService: IPythonPathUpdaterServiceManager, - @inject(IConfigurationService) private readonly configurationService: IConfigurationService, + @inject(IConfigurationService) configurationService: IConfigurationService, @inject(ICommandManager) commandManager: ICommandManager, @inject(IMultiStepInputFactory) private readonly multiStepFactory: IMultiStepInputFactory, @inject(IPlatformService) private readonly platformService: IPlatformService, @inject(IInterpreterSelector) private readonly interpreterSelector: IInterpreterSelector, - @inject(IWorkspaceService) workspaceService: IWorkspaceService + @inject(IWorkspaceService) workspaceService: IWorkspaceService, + @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, ) { - super(pythonPathUpdaterService, commandManager, applicationShell, workspaceService); + super( + pythonPathUpdaterService, + commandManager, + applicationShell, + workspaceService, + pathUtils, + configurationService, + ); } - public async activate() { + public async activate(): Promise { this.disposables.push( - this.commandManager.registerCommand(Commands.Set_Interpreter, this.setInterpreter.bind(this)) + this.commandManager.registerCommand(Commands.Set_Interpreter, this.setInterpreter.bind(this)), ); } public async _pickInterpreter( input: IMultiStepInput, - state: InterpreterStateArgs + state: InterpreterStateArgs, + filter?: (i: PythonEnvironment) => boolean, + params?: InterpreterQuickPickParams, ): Promise> { - const interpreterSuggestions = await this.interpreterSelector.getSuggestions(state.workspace); - const enterInterpreterPathSuggestion = { - label: InterpreterQuickPickList.enterPath.label(), - detail: InterpreterQuickPickList.enterPath.detail(), - alwaysShow: true - }; - const suggestions = [enterInterpreterPathSuggestion, ...interpreterSuggestions]; - const currentPythonPath = this.pathUtils.getDisplayName( + // If the list is refreshing, it's crucial to maintain sorting order at all + // times so that the visible items do not change. + const preserveOrderWhenFiltering = !!this.interpreterService.refreshPromise; + const suggestions = this._getItems(state.workspace, filter, params); + state.path = undefined; + const currentInterpreterPathDisplay = this.pathUtils.getDisplayName( this.configurationService.getSettings(state.workspace).pythonPath, - state.workspace ? state.workspace.fsPath : undefined + state.workspace ? state.workspace.fsPath : undefined, ); + const placeholder = + params?.placeholder === null + ? undefined + : params?.placeholder ?? l10n.t('Selected Interpreter: {0}', currentInterpreterPathDisplay); + const title = + params?.title === null ? undefined : params?.title ?? InterpreterQuickPickList.browsePath.openButtonLabel; + const buttons: QuickInputButtonSetup[] = [ + { + button: this.refreshButton, + callback: (quickpickInput) => { + this.refreshCallback(quickpickInput, { isButton: true, showBackButton: params?.showBackButton }); + }, + }, + ]; + if (params?.showBackButton) { + buttons.push({ + button: QuickInputButtons.Back, + callback: () => { + // Do nothing. This is handled as a promise rejection in the quickpick. + }, + }); + } - state.path = undefined; - const selection = await input.showQuickPick< - IInterpreterQuickPickItem | typeof enterInterpreterPathSuggestion, - IQuickPickParameters - >({ - placeholder: InterpreterQuickPickList.quickPickListPlaceholder().format(currentPythonPath), + const selection = await input.showQuickPick>({ + placeholder, items: suggestions, - activeItem: suggestions[1], + sortByLabel: !preserveOrderWhenFiltering, + keepScrollPosition: true, + activeItem: (quickPick) => this.getActiveItem(state.workspace, quickPick), // Use a promise here to ensure quickpick is initialized synchronously. matchOnDetail: true, - matchOnDescription: true + matchOnDescription: true, + title, + customButtonSetups: buttons, + initialize: (quickPick) => { + // Note discovery is no longer guranteed to be auto-triggered on extension load, so trigger it when + // user interacts with the interpreter picker but only once per session. Users can rely on the + // refresh button if they want to trigger it more than once. However if no envs were found previously, + // always trigger a refresh. + if (this.interpreterService.getInterpreters().length === 0) { + this.refreshCallback(quickPick, { showBackButton: params?.showBackButton }); + } else { + this.refreshCallback(quickPick, { + ifNotTriggerredAlready: true, + showBackButton: params?.showBackButton, + }); + } + }, + onChangeItem: { + event: this.interpreterService.onDidChangeInterpreters, + // It's essential that each callback is handled synchronously, as result of the previous + // callback influences the input for the next one. Input here is the quickpick itself. + callback: (event: PythonEnvironmentsChangedEvent, quickPick) => { + if (this.interpreterService.refreshPromise) { + quickPick.busy = true; + this.interpreterService.refreshPromise.then(() => { + // Items are in the final state as all previous callbacks have finished executing. + quickPick.busy = false; + // Ensure we set a recommended item after refresh has finished. + this.updateQuickPickItems(quickPick, {}, state.workspace, filter, params); + }); + } + this.updateQuickPickItems(quickPick, event, state.workspace, filter, params); + }, + }, }); if (selection === undefined) { - return; - } else if (selection.label === enterInterpreterPathSuggestion.label) { - return this._enterOrBrowseInterpreterPath(input, state); + sendTelemetryEvent(EventName.SELECT_INTERPRETER_SELECTED, undefined, { action: 'escape' }); + } else if (selection.label === this.manualEntrySuggestion.label) { + sendTelemetryEvent(EventName.SELECT_INTERPRETER_ENTER_OR_FIND); + return this._enterOrBrowseInterpreterPath.bind(this); + } else if (selection.label === this.createEnvironmentSuggestion.label) { + const createdEnv = (await Promise.resolve( + this.commandManager.executeCommand(Commands.Create_Environment, { + showBackButton: false, + selectEnvironment: true, + }), + ).catch(noop)) as CreateEnvironmentResult | undefined; + state.path = createdEnv?.path; + } else if (selection.label === this.noPythonInstalled.label) { + this.commandManager.executeCommand(Commands.InstallPython).then(noop, noop); + this.wasNoPythonInstalledItemClicked = true; + } else if (selection.label === this.tipToReloadWindow.label) { + this.commandManager.executeCommand('workbench.action.reloadWindow').then(noop, noop); } else { + sendTelemetryEvent(EventName.SELECT_INTERPRETER_SELECTED, undefined, { action: 'selected' }); state.path = (selection as IInterpreterQuickPickItem).path; } + return undefined; + } + + public _getItems( + resource: Resource, + filter: ((i: PythonEnvironment) => boolean) | undefined, + params?: InterpreterQuickPickParams, + ): QuickPickType[] { + const suggestions: QuickPickType[] = []; + if (params?.showCreateEnvironment) { + suggestions.push(this.createEnvironmentSuggestion, { label: '', kind: QuickPickItemKind.Separator }); + } + + suggestions.push(this.manualEntrySuggestion, { label: '', kind: QuickPickItemKind.Separator }); + + const defaultInterpreterPathSuggestion = this.getDefaultInterpreterPathSuggestion(resource); + if (defaultInterpreterPathSuggestion) { + suggestions.push(defaultInterpreterPathSuggestion); + } + const interpreterSuggestions = this.getSuggestions(resource, filter, params); + this.finalizeItems(interpreterSuggestions, resource, params); + suggestions.push(...interpreterSuggestions); + return suggestions; + } + + private getSuggestions( + resource: Resource, + filter: ((i: PythonEnvironment) => boolean) | undefined, + params?: InterpreterQuickPickParams, + ): QuickPickType[] { + const workspaceFolder = this.workspaceService.getWorkspaceFolder(resource); + const items = this.interpreterSelector + .getSuggestions(resource, !!this.interpreterService.refreshPromise) + .filter((i) => !filter || filter(i.interpreter)); + if (this.interpreterService.refreshPromise) { + // We cannot put items in groups while the list is loading as group of an item can change. + return items; + } + const itemsWithFullName = this.interpreterSelector + .getSuggestions(resource, true) + .filter((i) => !filter || filter(i.interpreter)); + let recommended: IInterpreterQuickPickItem | undefined; + if (!params?.skipRecommended) { + recommended = this.interpreterSelector.getRecommendedSuggestion( + itemsWithFullName, + this.workspaceService.getWorkspaceFolder(resource)?.uri, + ); + } + if (recommended && items[0].interpreter.id === recommended.interpreter.id) { + items.shift(); + } + return getGroupedQuickPickItems(items, recommended, workspaceFolder?.uri.fsPath); + } + + private async getActiveItem(resource: Resource, quickPick: QuickPick) { + const interpreter = await this.interpreterService.getActiveInterpreter(resource); + const suggestions = quickPick.items; + const activeInterpreterItem = suggestions.find( + (i) => isInterpreterQuickPickItem(i) && i.interpreter.id === interpreter?.id, + ); + if (activeInterpreterItem) { + return activeInterpreterItem; + } + const firstInterpreterSuggestion = suggestions.find((s) => isInterpreterQuickPickItem(s)); + if (firstInterpreterSuggestion) { + return firstInterpreterSuggestion; + } + const noPythonInstalledItem = suggestions.find( + (i) => isSpecialQuickPickItem(i) && i.label === this.noPythonInstalled.label, + ); + return noPythonInstalledItem ?? suggestions[0]; + } + + private getDefaultInterpreterPathSuggestion(resource: Resource): ISpecialQuickPickItem | undefined { + const config = this.workspaceService.getConfiguration('python', resource); + const systemVariables = new SystemVariables(resource, undefined, this.workspaceService); + const defaultInterpreterPathValue = systemVariables.resolveAny(config.get('defaultInterpreterPath')); + if (defaultInterpreterPathValue && defaultInterpreterPathValue !== 'python') { + return { + label: `${Octicons.Gear} ${InterpreterQuickPickList.defaultInterpreterPath.label}`, + description: this.pathUtils.getDisplayName( + defaultInterpreterPathValue, + resource ? resource.fsPath : undefined, + ), + path: defaultInterpreterPathValue, + alwaysShow: true, + }; + } + return undefined; + } + + /** + * Updates quickpick using the change event received. + */ + private updateQuickPickItems( + quickPick: QuickPick, + event: PythonEnvironmentsChangedEvent, + resource: Resource, + filter: ((i: PythonEnvironment) => boolean) | undefined, + params?: InterpreterQuickPickParams, + ) { + // Active items are reset once we replace the current list with updated items, so save it. + const activeItemBeforeUpdate = quickPick.activeItems.length > 0 ? quickPick.activeItems[0] : undefined; + quickPick.items = this.getUpdatedItems(quickPick.items, event, resource, filter, params); + // Ensure we maintain the same active item as before. + const activeItem = activeItemBeforeUpdate + ? quickPick.items.find((item) => { + if (isInterpreterQuickPickItem(item) && isInterpreterQuickPickItem(activeItemBeforeUpdate)) { + return item.interpreter.id === activeItemBeforeUpdate.interpreter.id; + } + if (isSpecialQuickPickItem(item) && isSpecialQuickPickItem(activeItemBeforeUpdate)) { + // 'label' is a constant here instead of 'path'. + return item.label === activeItemBeforeUpdate.label; + } + return false; + }) + : undefined; + if (activeItem) { + quickPick.activeItems = [activeItem]; + } + } + + /** + * Prepare updated items to replace the quickpick list with. + */ + private getUpdatedItems( + items: readonly QuickPickType[], + event: PythonEnvironmentsChangedEvent, + resource: Resource, + filter: ((i: PythonEnvironment) => boolean) | undefined, + params?: InterpreterQuickPickParams, + ): QuickPickType[] { + const updatedItems = [...items.values()]; + const areItemsGrouped = items.find((item) => isSeparatorItem(item)); + const env = event.old ?? event.new; + if (filter && event.new && !filter(event.new)) { + event.new = undefined; // Remove envs we're not looking for from the list. + } + let envIndex = -1; + if (env) { + envIndex = updatedItems.findIndex( + (item) => isInterpreterQuickPickItem(item) && item.interpreter.id === env.id, + ); + } + if (event.new) { + const newSuggestion = this.interpreterSelector.suggestionToQuickPickItem( + event.new, + resource, + !areItemsGrouped, + ); + if (envIndex === -1) { + const noPyIndex = updatedItems.findIndex( + (item) => isSpecialQuickPickItem(item) && item.label === this.noPythonInstalled.label, + ); + if (noPyIndex !== -1) { + updatedItems.splice(noPyIndex, 1); + } + const tryReloadIndex = updatedItems.findIndex( + (item) => isSpecialQuickPickItem(item) && item.label === this.tipToReloadWindow.label, + ); + if (tryReloadIndex !== -1) { + updatedItems.splice(tryReloadIndex, 1); + } + if (areItemsGrouped) { + addSeparatorIfApplicable( + updatedItems, + newSuggestion, + this.workspaceService.getWorkspaceFolder(resource)?.uri.fsPath, + ); + } + updatedItems.push(newSuggestion); + } else { + updatedItems[envIndex] = newSuggestion; + } + } + if (envIndex !== -1 && event.new === undefined) { + updatedItems.splice(envIndex, 1); + } + this.finalizeItems(updatedItems, resource, params); + return updatedItems; + } + + private finalizeItems(items: QuickPickType[], resource: Resource, params?: InterpreterQuickPickParams) { + const interpreterSuggestions = this.interpreterSelector.getSuggestions(resource, true); + const r = this.interpreterService.refreshPromise; + if (!r) { + if (interpreterSuggestions.length) { + if (!params?.skipRecommended) { + this.setRecommendedItem(interpreterSuggestions, items, resource); + } + // Add warning label to certain environments + items.forEach((item, i) => { + if (isInterpreterQuickPickItem(item) && isProblematicCondaEnvironment(item.interpreter)) { + if (!items[i].label.includes(Octicons.Warning)) { + items[i].label = `${Octicons.Warning} ${items[i].label}`; + items[i].tooltip = InterpreterQuickPickList.condaEnvWithoutPythonTooltip; + } + } + }); + } else { + if (!items.some((i) => isSpecialQuickPickItem(i) && i.label === this.noPythonInstalled.label)) { + items.push(this.noPythonInstalled); + } + if ( + this.wasNoPythonInstalledItemClicked && + !items.some((i) => isSpecialQuickPickItem(i) && i.label === this.tipToReloadWindow.label) + ) { + items.push(this.tipToReloadWindow); + } + } + } + } + + private setRecommendedItem( + interpreterSuggestions: IInterpreterQuickPickItem[], + items: QuickPickType[], + resource: Resource, + ) { + const suggestion = this.interpreterSelector.getRecommendedSuggestion( + interpreterSuggestions, + this.workspaceService.getWorkspaceFolder(resource)?.uri, + ); + if (!suggestion) { + return; + } + const areItemsGrouped = items.find((item) => isSeparatorItem(item) && item.label === EnvGroups.Recommended); + const recommended = cloneDeep(suggestion); + recommended.description = areItemsGrouped + ? // No need to add a tag as "Recommended" group already exists. + recommended.description + : `${recommended.description ?? ''} - ${Common.recommended}`; + const index = items.findIndex( + (item) => isInterpreterQuickPickItem(item) && item.interpreter.id === recommended.interpreter.id, + ); + if (index !== -1) { + items[index] = recommended; + } + } + + private refreshCallback( + input: QuickPick, + options?: TriggerRefreshOptions & { isButton?: boolean; showBackButton?: boolean }, + ) { + input.buttons = this.getButtons(options); + + this.interpreterService + .triggerRefresh(undefined, options) + .finally(() => { + input.buttons = this.getButtons({ isButton: false, showBackButton: options?.showBackButton }); + }) + .ignoreErrors(); + if (this.interpreterService.refreshPromise) { + input.busy = true; + this.interpreterService.refreshPromise.then(() => { + input.busy = false; + }); + } + } + + private getButtons(options?: { isButton?: boolean; showBackButton?: boolean }): QuickInputButton[] { + const buttons: QuickInputButton[] = []; + if (options?.showBackButton) { + buttons.push(QuickInputButtons.Back); + } + if (options?.isButton) { + buttons.push({ + iconPath: new ThemeIcon(ThemeIcons.SpinningLoader), + tooltip: InterpreterQuickPickList.refreshingInterpreterList, + }); + } else { + buttons.push(this.refreshButton); + } + return buttons; } @captureTelemetry(EventName.SELECT_INTERPRETER_ENTER_BUTTON) public async _enterOrBrowseInterpreterPath( input: IMultiStepInput, - state: InterpreterStateArgs + state: InterpreterStateArgs, ): Promise> { const items: QuickPickItem[] = [ { - label: InterpreterQuickPickList.browsePath.label(), - detail: InterpreterQuickPickList.browsePath.detail() - } + label: InterpreterQuickPickList.browsePath.label, + detail: InterpreterQuickPickList.browsePath.detail, + }, ]; - const selection = await input.showQuickPick({ - placeholder: InterpreterQuickPickList.enterPath.placeholder(), + const selection = await input.showQuickPick({ + placeholder: InterpreterQuickPickList.enterPath.placeholder, items, - acceptFilterBoxTextAsSelection: true + acceptFilterBoxTextAsSelection: true, }); if (typeof selection === 'string') { // User entered text in the filter box to enter path to python, store it sendTelemetryEvent(EventName.SELECT_INTERPRETER_ENTER_CHOICE, undefined, { choice: 'enter' }); state.path = selection; - } else if (selection && selection.label === InterpreterQuickPickList.browsePath.label()) { + this.sendInterpreterEntryTelemetry(selection, state.workspace); + } else if (selection && selection.label === InterpreterQuickPickList.browsePath.label) { sendTelemetryEvent(EventName.SELECT_INTERPRETER_ENTER_CHOICE, undefined, { choice: 'browse' }); const filtersKey = 'Executables'; const filtersObject: { [name: string]: string[] } = {}; filtersObject[filtersKey] = ['exe']; const uris = await this.applicationShell.showOpenDialog({ filters: this.platformService.isWindows ? filtersObject : undefined, - openLabel: InterpreterQuickPickList.browsePath.openButtonLabel(), + openLabel: InterpreterQuickPickList.browsePath.openButtonLabel, canSelectMany: false, - title: InterpreterQuickPickList.browsePath.title() + title: InterpreterQuickPickList.browsePath.title, + defaultUri: state.workspace, }); if (uris && uris.length > 0) { state.path = uris[0].fsPath; + this.sendInterpreterEntryTelemetry(state.path!, state.workspace); + } else { + return Promise.reject(InputFlowAction.resume); } } + return Promise.resolve(); } + /** + * @returns true when an interpreter was set, undefined if the user cancelled the quickpick. + */ @captureTelemetry(EventName.SELECT_INTERPRETER) - public async setInterpreter() { - const targetConfig = await this.getConfigTarget(); + public async setInterpreter(options?: { + hideCreateVenv?: boolean; + showBackButton?: boolean; + }): Promise { + const targetConfig = await this.getConfigTargets(); if (!targetConfig) { return; } - const configTarget = targetConfig.configTarget; - const wkspace = targetConfig.folderUri; + const { configTarget } = targetConfig[0]; + const wkspace = targetConfig[0].folderUri; const interpreterState: InterpreterStateArgs = { path: undefined, workspace: wkspace }; const multiStep = this.multiStepFactory.create(); - await multiStep.run((input, s) => this._pickInterpreter(input, s), interpreterState); + try { + await multiStep.run( + (input, s) => + this._pickInterpreter(input, s, undefined, { + showCreateEnvironment: !options?.hideCreateVenv, + showBackButton: options?.showBackButton, + }), + interpreterState, + ); + } catch (ex) { + if (ex === InputFlowAction.back) { + // User clicked back button, so we need to return this action. + return { action: 'Back' }; + } + if (ex === InputFlowAction.cancel) { + // User clicked cancel button, so we need to return this action. + return { action: 'Cancel' }; + } + } if (interpreterState.path !== undefined) { // User may choose to have an empty string stored, so variable `interpreterState.path` may be // an empty string, in which case we should update. // Having the value `undefined` means user cancelled the quickpick, so we update nothing in that case. await this.pythonPathUpdaterService.updatePythonPath(interpreterState.path, configTarget, 'ui', wkspace); + if (useEnvExtension()) { + await setInterpreterLegacy(interpreterState.path, wkspace); + } + return { path: interpreterState.path }; } } + + public async getInterpreterViaQuickPick( + workspace: Resource, + filter: ((i: PythonEnvironment) => boolean) | undefined, + params?: InterpreterQuickPickParams, + ): Promise { + const interpreterState: InterpreterStateArgs = { path: undefined, workspace }; + const multiStep = this.multiStepFactory.create(); + await multiStep.run((input, s) => this._pickInterpreter(input, s, filter, params), interpreterState); + return interpreterState.path; + } + + /** + * Check if the interpreter that was entered exists in the list of suggestions. + * If it does, it means that it had already been discovered, + * and we didn't do a good job of surfacing it. + * + * @param selection Intepreter path that was either entered manually or picked by browsing through the filesystem. + */ + // eslint-disable-next-line class-methods-use-this + private sendInterpreterEntryTelemetry(selection: string, workspace: Resource): void { + const suggestions = this._getItems(workspace, undefined); + let interpreterPath = path.normalize(untildify(selection)); + + if (!path.isAbsolute(interpreterPath)) { + interpreterPath = path.resolve(workspace?.fsPath || '', selection); + } + + const expandedPaths = suggestions.map((s) => { + const suggestionPath = isInterpreterQuickPickItem(s) ? s.interpreter.path : ''; + let expandedPath = path.normalize(untildify(suggestionPath)); + + if (!path.isAbsolute(suggestionPath)) { + expandedPath = path.resolve(workspace?.fsPath || '', suggestionPath); + } + + return expandedPath; + }); + + const discovered = expandedPaths.includes(interpreterPath); + + sendTelemetryEvent(EventName.SELECT_INTERPRETER_ENTERED_EXISTS, undefined, { discovered }); + + return undefined; + } +} + +function getGroupedQuickPickItems( + items: IInterpreterQuickPickItem[], + recommended: IInterpreterQuickPickItem | undefined, + workspacePath?: string, +): QuickPickType[] { + const updatedItems: QuickPickType[] = []; + if (recommended) { + updatedItems.push({ label: EnvGroups.Recommended, kind: QuickPickItemKind.Separator }, recommended); + } + let previousGroup = EnvGroups.Recommended; + for (const item of items) { + previousGroup = addSeparatorIfApplicable(updatedItems, item, workspacePath, previousGroup); + updatedItems.push(item); + } + return updatedItems; +} + +function addSeparatorIfApplicable( + items: QuickPickType[], + newItem: IInterpreterQuickPickItem, + workspacePath?: string, + previousGroup?: string | undefined, +) { + if (!previousGroup) { + const lastItem = items.length ? items[items.length - 1] : undefined; + previousGroup = + lastItem && isInterpreterQuickPickItem(lastItem) ? getGroup(lastItem, workspacePath) : undefined; + } + const currentGroup = getGroup(newItem, workspacePath); + if (!previousGroup || currentGroup !== previousGroup) { + const separatorItem: QuickPickItem = { label: currentGroup, kind: QuickPickItemKind.Separator }; + items.push(separatorItem); + previousGroup = currentGroup; + } + return previousGroup; } + +function getGroup(item: IInterpreterQuickPickItem, workspacePath?: string) { + if (workspacePath && isParentPath(item.path, workspacePath)) { + return EnvGroups.Workspace; + } + switch (item.interpreter.envType) { + case EnvironmentType.Global: + case EnvironmentType.System: + case EnvironmentType.Unknown: + case EnvironmentType.MicrosoftStore: + return EnvGroups.Global; + default: + return EnvGroups[item.interpreter.envType]; + } +} + +export type SelectEnvironmentResult = { + /** + * Path to the executable python in the environment + */ + readonly path?: string; + /* + * User action that resulted in exit from the create environment flow. + */ + readonly action?: 'Back' | 'Cancel'; +}; diff --git a/src/client/interpreter/configuration/interpreterSelector/commands/setShebangInterpreter.ts b/src/client/interpreter/configuration/interpreterSelector/commands/setShebangInterpreter.ts deleted file mode 100644 index e3e63b299f83..000000000000 --- a/src/client/interpreter/configuration/interpreterSelector/commands/setShebangInterpreter.ts +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { ConfigurationTarget } from 'vscode'; -import { - IApplicationShell, - ICommandManager, - IDocumentManager, - IWorkspaceService -} from '../../../../common/application/types'; -import { Commands } from '../../../../common/constants'; -import { IShebangCodeLensProvider } from '../../../contracts'; -import { IPythonPathUpdaterServiceManager } from '../../types'; -import { BaseInterpreterSelectorCommand } from './base'; - -@injectable() -export class SetShebangInterpreterCommand extends BaseInterpreterSelectorCommand { - constructor( - @inject(IWorkspaceService) workspaceService: IWorkspaceService, - @inject(IDocumentManager) private readonly documentManager: IDocumentManager, - @inject(IPythonPathUpdaterServiceManager) pythonPathUpdaterService: IPythonPathUpdaterServiceManager, - @inject(IShebangCodeLensProvider) private readonly shebangCodeLensProvider: IShebangCodeLensProvider, - @inject(ICommandManager) commandManager: ICommandManager, - @inject(ICommandManager) applicationShell: IApplicationShell - ) { - super(pythonPathUpdaterService, commandManager, applicationShell, workspaceService); - } - - public async activate() { - this.disposables.push( - this.commandManager.registerCommand(Commands.Set_ShebangInterpreter, this.setShebangInterpreter.bind(this)) - ); - } - - protected async setShebangInterpreter(): Promise { - const shebang = await this.shebangCodeLensProvider.detectShebang( - this.documentManager.activeTextEditor!.document, - true - ); - if (!shebang) { - return; - } - - const isGlobalChange = - !Array.isArray(this.workspaceService.workspaceFolders) || - this.workspaceService.workspaceFolders.length === 0; - const workspaceFolder = this.workspaceService.getWorkspaceFolder( - this.documentManager.activeTextEditor!.document.uri - ); - const isWorkspaceChange = - Array.isArray(this.workspaceService.workspaceFolders) && - this.workspaceService.workspaceFolders.length === 1; - - if (isGlobalChange) { - await this.pythonPathUpdaterService.updatePythonPath(shebang, ConfigurationTarget.Global, 'shebang'); - return; - } - - if (isWorkspaceChange || !workspaceFolder) { - await this.pythonPathUpdaterService.updatePythonPath( - shebang, - ConfigurationTarget.Workspace, - 'shebang', - this.workspaceService.workspaceFolders![0].uri - ); - return; - } - - await this.pythonPathUpdaterService.updatePythonPath( - shebang, - ConfigurationTarget.WorkspaceFolder, - 'shebang', - workspaceFolder.uri - ); - } -} diff --git a/src/client/interpreter/configuration/interpreterSelector/interpreterSelector.ts b/src/client/interpreter/configuration/interpreterSelector/interpreterSelector.ts index e8f9a7356e31..6b33245bb907 100644 --- a/src/client/interpreter/configuration/interpreterSelector/interpreterSelector.ts +++ b/src/client/interpreter/configuration/interpreterSelector/interpreterSelector.ts @@ -5,10 +5,10 @@ import { inject, injectable } from 'inversify'; import { Disposable, Uri } from 'vscode'; -import { DeprecatePythonPath } from '../../../common/experiments/groups'; -import { IExperimentsManager, IPathUtils, Resource } from '../../../common/types'; +import { arePathsSame, isParentPath } from '../../../common/platform/fs-paths'; +import { IPathUtils, Resource } from '../../../common/types'; +import { getEnvPath } from '../../../pythonEnvironments/base/info/env'; import { PythonEnvironment } from '../../../pythonEnvironments/info'; -import { IInterpreterSecurityService } from '../../autoSelection/types'; import { IInterpreterService } from '../../contracts'; import { IInterpreterComparer, IInterpreterQuickPickItem, IInterpreterSelector } from '../types'; @@ -18,39 +18,63 @@ export class InterpreterSelector implements IInterpreterSelector { constructor( @inject(IInterpreterService) private readonly interpreterManager: IInterpreterService, - @inject(IInterpreterComparer) private readonly interpreterComparer: IInterpreterComparer, - @inject(IExperimentsManager) private readonly experimentsManager: IExperimentsManager, - @inject(IInterpreterSecurityService) private readonly interpreterSecurityService: IInterpreterSecurityService, - @inject(IPathUtils) private readonly pathUtils: IPathUtils + @inject(IInterpreterComparer) private readonly envTypeComparer: IInterpreterComparer, + @inject(IPathUtils) private readonly pathUtils: IPathUtils, ) {} - public dispose() { + + public dispose(): void { this.disposables.forEach((disposable) => disposable.dispose()); } - public async getSuggestions(resource: Resource) { - let interpreters = await this.interpreterManager.getInterpreters(resource, { onSuggestion: true }); - if (this.experimentsManager.inExperiment(DeprecatePythonPath.experiment)) { - interpreters = interpreters - ? interpreters.filter((item) => this.interpreterSecurityService.isSafe(item) !== false) - : []; - } - this.experimentsManager.sendTelemetryIfInExperiment(DeprecatePythonPath.control); - interpreters.sort(this.interpreterComparer.compare.bind(this.interpreterComparer)); + public getSuggestions(resource: Resource, useFullDisplayName = false): IInterpreterQuickPickItem[] { + const interpreters = this.interpreterManager.getInterpreters(resource); + interpreters.sort(this.envTypeComparer.compare.bind(this.envTypeComparer)); + + return interpreters.map((item) => this.suggestionToQuickPickItem(item, resource, useFullDisplayName)); + } + + public async getAllSuggestions(resource: Resource): Promise { + const interpreters = await this.interpreterManager.getAllInterpreters(resource); + interpreters.sort(this.envTypeComparer.compare.bind(this.envTypeComparer)); + return Promise.all(interpreters.map((item) => this.suggestionToQuickPickItem(item, resource))); } - protected async suggestionToQuickPickItem( - suggestion: PythonEnvironment, - workspaceUri?: Uri - ): Promise { - const detail = this.pathUtils.getDisplayName(suggestion.path, workspaceUri ? workspaceUri.fsPath : undefined); - const cachedPrefix = suggestion.cachedEntry ? '(cached) ' : ''; + public suggestionToQuickPickItem( + interpreter: PythonEnvironment, + workspaceUri?: Uri, + useDetailedName = false, + ): IInterpreterQuickPickItem { + if (!useDetailedName) { + const workspacePath = workspaceUri?.fsPath; + if (workspacePath && isParentPath(interpreter.path, workspacePath)) { + // If interpreter is in the workspace, then display the full path. + useDetailedName = true; + } + } + const path = + interpreter.envPath && getEnvPath(interpreter.path, interpreter.envPath).pathType === 'envFolderPath' + ? interpreter.envPath + : interpreter.path; + const detail = this.pathUtils.getDisplayName(path, workspaceUri ? workspaceUri.fsPath : undefined); + const cachedPrefix = interpreter.cachedEntry ? '(cached) ' : ''; return { - // tslint:disable-next-line:no-non-null-assertion - label: suggestion.displayName!, - detail: `${cachedPrefix}${detail}`, - path: suggestion.path, - interpreter: suggestion + label: (useDetailedName ? interpreter.detailedDisplayName : interpreter.displayName) || 'Python', + description: `${cachedPrefix}${detail}`, + path, + interpreter, }; } + + public getRecommendedSuggestion( + suggestions: IInterpreterQuickPickItem[], + resource: Resource, + ): IInterpreterQuickPickItem | undefined { + const envs = this.interpreterManager.getInterpreters(resource); + const recommendedEnv = this.envTypeComparer.getRecommended(envs, resource); + if (!recommendedEnv) { + return undefined; + } + return suggestions.find((item) => arePathsSame(item.interpreter.path, recommendedEnv.path)); + } } diff --git a/src/client/interpreter/configuration/pythonPathUpdaterService.ts b/src/client/interpreter/configuration/pythonPathUpdaterService.ts index 8ad4a38f3155..9814ff6ee4cb 100644 --- a/src/client/interpreter/configuration/pythonPathUpdaterService.ts +++ b/src/client/interpreter/configuration/pythonPathUpdaterService.ts @@ -1,84 +1,76 @@ import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { ConfigurationTarget, Uri, window } from 'vscode'; -import { traceError } from '../../common/logger'; -import { IPythonExecutionFactory } from '../../common/process/types'; +import { ConfigurationTarget, l10n, Uri, window } from 'vscode'; import { StopWatch } from '../../common/utils/stopWatch'; -import { IServiceContainer } from '../../ioc/types'; -import { InterpreterInformation } from '../../pythonEnvironments/info'; +import { SystemVariables } from '../../common/variables/systemVariables'; +import { traceError } from '../../logging'; import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; import { PythonInterpreterTelemetry } from '../../telemetry/types'; -import { IInterpreterVersionService } from '../contracts'; -import { IPythonPathUpdaterServiceFactory, IPythonPathUpdaterServiceManager } from './types'; +import { IComponentAdapter } from '../contracts'; +import { + IRecommendedEnvironmentService, + IPythonPathUpdaterServiceFactory, + IPythonPathUpdaterServiceManager, +} from './types'; @injectable() export class PythonPathUpdaterService implements IPythonPathUpdaterServiceManager { - private readonly pythonPathSettingsUpdaterFactory: IPythonPathUpdaterServiceFactory; - private readonly interpreterVersionService: IInterpreterVersionService; - private readonly executionFactory: IPythonExecutionFactory; - constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - this.pythonPathSettingsUpdaterFactory = serviceContainer.get( - IPythonPathUpdaterServiceFactory - ); - this.interpreterVersionService = serviceContainer.get(IInterpreterVersionService); - this.executionFactory = serviceContainer.get(IPythonExecutionFactory); - } + constructor( + @inject(IPythonPathUpdaterServiceFactory) + private readonly pythonPathSettingsUpdaterFactory: IPythonPathUpdaterServiceFactory, + @inject(IComponentAdapter) private readonly pyenvs: IComponentAdapter, + @inject(IRecommendedEnvironmentService) private readonly preferredEnvService: IRecommendedEnvironmentService, + ) {} + public async updatePythonPath( pythonPath: string | undefined, configTarget: ConfigurationTarget, trigger: 'ui' | 'shebang' | 'load', - wkspace?: Uri + wkspace?: Uri, ): Promise { const stopWatch = new StopWatch(); const pythonPathUpdater = this.getPythonUpdaterService(configTarget, wkspace); let failed = false; try { - await pythonPathUpdater.updatePythonPath(pythonPath ? path.normalize(pythonPath) : undefined); - } catch (reason) { + await pythonPathUpdater.updatePythonPath(pythonPath); + if (trigger === 'ui') { + this.preferredEnvService.trackUserSelectedEnvironment(pythonPath, wkspace); + } + } catch (err) { failed = true; - // tslint:disable-next-line:no-unsafe-any prefer-type-cast + const reason = err as Error; const message = reason && typeof reason.message === 'string' ? (reason.message as string) : ''; - window.showErrorMessage(`Failed to set 'pythonPath'. Error: ${message}`); + window.showErrorMessage(l10n.t('Failed to set interpreter path. Error: {0}', message)); traceError(reason); } // do not wait for this to complete - this.sendTelemetry(stopWatch.elapsedTime, failed, trigger, pythonPath).catch((ex) => - traceError('Python Extension: sendTelemetry', ex) + this.sendTelemetry(stopWatch.elapsedTime, failed, trigger, pythonPath, wkspace).catch((ex) => + traceError('Python Extension: sendTelemetry', ex), ); } + private async sendTelemetry( duration: number, failed: boolean, trigger: 'ui' | 'shebang' | 'load', - pythonPath: string | undefined + pythonPath: string | undefined, + wkspace?: Uri, ) { const telemetryProperties: PythonInterpreterTelemetry = { failed, - trigger + trigger, }; if (!failed && pythonPath) { - const processService = await this.executionFactory.create({ pythonPath }); - const infoPromise = processService - .getInterpreterInformation() - .catch(() => undefined); - const pipVersionPromise = this.interpreterVersionService - .getPipVersion(pythonPath) - .then((value) => (value.length === 0 ? undefined : value)) - .catch(() => ''); - const [info, pipVersion] = await Promise.all([infoPromise, pipVersionPromise]); - if (info) { - telemetryProperties.architecture = info.architecture; - if (info.version) { - telemetryProperties.pythonVersion = info.version.raw; - } - } - if (pipVersion) { - telemetryProperties.pipVersion = pipVersion; + const systemVariables = new SystemVariables(undefined, wkspace?.fsPath); + const interpreterInfo = await this.pyenvs.getInterpreterInformation(systemVariables.resolveAny(pythonPath)); + if (interpreterInfo) { + telemetryProperties.pythonVersion = interpreterInfo.version?.raw; } } + sendTelemetryEvent(EventName.PYTHON_INTERPRETER, duration, telemetryProperties); } + private getPythonUpdaterService(configTarget: ConfigurationTarget, wkspace?: Uri) { switch (configTarget) { case ConfigurationTarget.Global: { @@ -88,14 +80,14 @@ export class PythonPathUpdaterService implements IPythonPathUpdaterServiceManage if (!wkspace) { throw new Error('Workspace Uri not defined'); } - // tslint:disable-next-line:no-non-null-assertion + return this.pythonPathSettingsUpdaterFactory.getWorkspacePythonPathConfigurationService(wkspace!); } default: { if (!wkspace) { throw new Error('Workspace Uri not defined'); } - // tslint:disable-next-line:no-non-null-assertion + return this.pythonPathSettingsUpdaterFactory.getWorkspaceFolderPythonPathConfigurationService(wkspace!); } } diff --git a/src/client/interpreter/configuration/pythonPathUpdaterServiceFactory.ts b/src/client/interpreter/configuration/pythonPathUpdaterServiceFactory.ts index aecf7beeb5b6..ff42f53bcb5b 100644 --- a/src/client/interpreter/configuration/pythonPathUpdaterServiceFactory.ts +++ b/src/client/interpreter/configuration/pythonPathUpdaterServiceFactory.ts @@ -1,8 +1,6 @@ import { inject, injectable } from 'inversify'; import { Uri } from 'vscode'; -import { IWorkspaceService } from '../../common/application/types'; -import { DeprecatePythonPath } from '../../common/experiments/groups'; -import { IExperimentsManager, IInterpreterPathService } from '../../common/types'; +import { IInterpreterPathService } from '../../common/types'; import { IServiceContainer } from '../../ioc/types'; import { GlobalPythonPathUpdaterService } from './services/globalUpdaterService'; import { WorkspaceFolderPythonPathUpdaterService } from './services/workspaceFolderUpdaterService'; @@ -11,37 +9,17 @@ import { IPythonPathUpdaterService, IPythonPathUpdaterServiceFactory } from './t @injectable() export class PythonPathUpdaterServiceFactory implements IPythonPathUpdaterServiceFactory { - private readonly inDeprecatePythonPathExperiment: boolean; - private readonly workspaceService: IWorkspaceService; private readonly interpreterPathService: IInterpreterPathService; constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - const experiments = serviceContainer.get(IExperimentsManager); - this.workspaceService = serviceContainer.get(IWorkspaceService); this.interpreterPathService = serviceContainer.get(IInterpreterPathService); - this.inDeprecatePythonPathExperiment = experiments.inExperiment(DeprecatePythonPath.experiment); - experiments.sendTelemetryIfInExperiment(DeprecatePythonPath.control); } public getGlobalPythonPathConfigurationService(): IPythonPathUpdaterService { - return new GlobalPythonPathUpdaterService( - this.inDeprecatePythonPathExperiment, - this.workspaceService, - this.interpreterPathService - ); + return new GlobalPythonPathUpdaterService(this.interpreterPathService); } public getWorkspacePythonPathConfigurationService(wkspace: Uri): IPythonPathUpdaterService { - return new WorkspacePythonPathUpdaterService( - wkspace, - this.inDeprecatePythonPathExperiment, - this.workspaceService, - this.interpreterPathService - ); + return new WorkspacePythonPathUpdaterService(wkspace, this.interpreterPathService); } public getWorkspaceFolderPythonPathConfigurationService(workspaceFolder: Uri): IPythonPathUpdaterService { - return new WorkspaceFolderPythonPathUpdaterService( - workspaceFolder, - this.inDeprecatePythonPathExperiment, - this.workspaceService, - this.interpreterPathService - ); + return new WorkspaceFolderPythonPathUpdaterService(workspaceFolder, this.interpreterPathService); } } diff --git a/src/client/interpreter/configuration/recommededEnvironmentService.ts b/src/client/interpreter/configuration/recommededEnvironmentService.ts new file mode 100644 index 000000000000..c5356409fcee --- /dev/null +++ b/src/client/interpreter/configuration/recommededEnvironmentService.ts @@ -0,0 +1,200 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable } from 'inversify'; +import { IRecommendedEnvironmentService } from './types'; +import { PythonExtension, ResolvedEnvironment } from '../../api/types'; +import { IExtensionContext, Resource } from '../../common/types'; +import { commands, Uri, workspace } from 'vscode'; +import { getWorkspaceStateValue, updateWorkspaceStateValue } from '../../common/persistentState'; +import { traceError } from '../../logging'; +import { IExtensionActivationService } from '../../activation/types'; +import { StopWatch } from '../../common/utils/stopWatch'; +import { isParentPath } from '../../common/platform/fs-paths'; + +const MEMENTO_KEY = 'userSelectedEnvPath'; + +@injectable() +export class RecommendedEnvironmentService implements IRecommendedEnvironmentService, IExtensionActivationService { + private api?: PythonExtension['environments']; + constructor(@inject(IExtensionContext) private readonly extensionContext: IExtensionContext) {} + supportedWorkspaceTypes: { untrustedWorkspace: boolean; virtualWorkspace: boolean } = { + untrustedWorkspace: true, + virtualWorkspace: false, + }; + + async activate(_resource: Resource, _startupStopWatch?: StopWatch): Promise { + this.extensionContext.subscriptions.push( + commands.registerCommand('python.getRecommendedEnvironment', async (resource: Resource) => { + return this.getRecommededEnvironment(resource); + }), + ); + } + + registerEnvApi(api: PythonExtension['environments']) { + this.api = api; + } + + trackUserSelectedEnvironment(environmentPath: string | undefined, uri: Uri | undefined) { + if (workspace.workspaceFolders?.length) { + try { + void updateWorkspaceStateValue(MEMENTO_KEY, getDataToStore(environmentPath, uri)); + } catch (ex) { + traceError('Failed to update workspace state for preferred environment', ex); + } + } else { + void this.extensionContext.globalState.update(MEMENTO_KEY, environmentPath); + } + } + + async getRecommededEnvironment( + resource: Resource, + ): Promise< + | { + environment: ResolvedEnvironment; + reason: 'globalUserSelected' | 'workspaceUserSelected' | 'defaultRecommended'; + } + | undefined + > { + if (!workspace.isTrusted || !this.api) { + return undefined; + } + const preferred = await this.getRecommededInternal(resource); + if (!preferred) { + return undefined; + } + const activeEnv = await this.api.resolveEnvironment(this.api.getActiveEnvironmentPath(resource)); + const recommendedEnv = await this.api.resolveEnvironment(preferred.environmentPath); + if (activeEnv && recommendedEnv && activeEnv.id !== recommendedEnv.id) { + traceError( + `Active environment ${activeEnv.id} is different from recommended environment ${ + recommendedEnv.id + } for resource ${resource?.toString()}`, + ); + return undefined; + } + if (recommendedEnv) { + return { environment: recommendedEnv, reason: preferred.reason }; + } + const globalEnv = await this.api.resolveEnvironment(this.api.getActiveEnvironmentPath()); + if (activeEnv && globalEnv?.path !== activeEnv?.path) { + // User has definitely got a workspace specific environment selected. + // Given the fact that global !== workspace env, we can safely assume that + // at some time, the user has selected a workspace specific environment. + // This applies to cases where the user has selected a workspace specific environment before this version of the extension + // and we did not store it in the workspace state. + // So we can safely return the global environment as the recommended environment. + return { environment: activeEnv, reason: 'workspaceUserSelected' }; + } + return undefined; + } + async getRecommededInternal( + resource: Resource, + ): Promise< + | { environmentPath: string; reason: 'globalUserSelected' | 'workspaceUserSelected' | 'defaultRecommended' } + | undefined + > { + let workspaceState: string | undefined = undefined; + try { + workspaceState = getWorkspaceStateValue(MEMENTO_KEY); + } catch (ex) { + traceError('Failed to get workspace state for preferred environment', ex); + } + + if (workspace.workspaceFolders?.length && workspaceState) { + const workspaceUri = ( + (resource ? workspace.getWorkspaceFolder(resource)?.uri : undefined) || + workspace.workspaceFolders[0].uri + ).toString(); + + try { + const existingJson: Record = JSON.parse(workspaceState); + const selectedEnvPath = existingJson[workspaceUri]; + if (selectedEnvPath) { + return { environmentPath: selectedEnvPath, reason: 'workspaceUserSelected' }; + } + } catch (ex) { + traceError('Failed to parse existing workspace state value for preferred environment', ex); + } + } + + if (workspace.workspaceFolders?.length && this.api) { + // Check if we have a .venv or .conda environment in the workspace + // This is required for cases where user has selected a workspace specific environment + // but before this version of the extension, we did not store it in the workspace state. + const workspaceEnv = await getWorkspaceSpecificVirtualEnvironment(this.api, resource); + if (workspaceEnv) { + return { environmentPath: workspaceEnv.path, reason: 'workspaceUserSelected' }; + } + } + + const globalSelectedEnvPath = this.extensionContext.globalState.get(MEMENTO_KEY); + if (globalSelectedEnvPath) { + return { environmentPath: globalSelectedEnvPath, reason: 'globalUserSelected' }; + } + return this.api && workspace.isTrusted + ? { + environmentPath: this.api.getActiveEnvironmentPath(resource).path, + reason: 'defaultRecommended', + } + : undefined; + } +} + +async function getWorkspaceSpecificVirtualEnvironment(api: PythonExtension['environments'], resource: Resource) { + const workspaceUri = + (resource ? workspace.getWorkspaceFolder(resource)?.uri : undefined) || + (workspace.workspaceFolders?.length ? workspace.workspaceFolders[0].uri : undefined); + if (!workspaceUri) { + return undefined; + } + let workspaceEnv = api.known.find((env) => { + if (!env.environment?.folderUri) { + return false; + } + if (env.environment.type !== 'VirtualEnvironment' && env.environment.type !== 'Conda') { + return false; + } + return isParentPath(env.environment.folderUri.fsPath, workspaceUri.fsPath); + }); + let resolvedEnv = workspaceEnv ? api.resolveEnvironment(workspaceEnv) : undefined; + if (resolvedEnv) { + return resolvedEnv; + } + workspaceEnv = api.known.find((env) => { + // Look for any other type of env thats inside this workspace + // Or look for an env thats associated with this workspace (pipenv or the like). + return ( + (env.environment?.folderUri && isParentPath(env.environment.folderUri.fsPath, workspaceUri.fsPath)) || + (env.environment?.workspaceFolder && env.environment.workspaceFolder.uri.fsPath === workspaceUri.fsPath) + ); + }); + return workspaceEnv ? api.resolveEnvironment(workspaceEnv) : undefined; +} + +function getDataToStore(environmentPath: string | undefined, uri: Uri | undefined): string | undefined { + if (!workspace.workspaceFolders?.length) { + return environmentPath; + } + const workspaceUri = ( + (uri ? workspace.getWorkspaceFolder(uri)?.uri : undefined) || workspace.workspaceFolders[0].uri + ).toString(); + const existingData = getWorkspaceStateValue(MEMENTO_KEY); + if (!existingData) { + return JSON.stringify(environmentPath ? { [workspaceUri]: environmentPath } : {}); + } + try { + const existingJson: Record = JSON.parse(existingData); + if (environmentPath) { + existingJson[workspaceUri] = environmentPath; + } else { + delete existingJson[workspaceUri]; + } + return JSON.stringify(existingJson); + } catch (ex) { + traceError('Failed to parse existing workspace state value for preferred environment', ex); + return JSON.stringify({ + [workspaceUri]: environmentPath, + }); + } +} diff --git a/src/client/interpreter/configuration/services/globalUpdaterService.ts b/src/client/interpreter/configuration/services/globalUpdaterService.ts index 39a1c1731019..1cf2a7cc478f 100644 --- a/src/client/interpreter/configuration/services/globalUpdaterService.ts +++ b/src/client/interpreter/configuration/services/globalUpdaterService.ts @@ -1,27 +1,15 @@ import { ConfigurationTarget } from 'vscode'; -import { IWorkspaceService } from '../../../common/application/types'; import { IInterpreterPathService } from '../../../common/types'; import { IPythonPathUpdaterService } from '../types'; export class GlobalPythonPathUpdaterService implements IPythonPathUpdaterService { - constructor( - private readonly inDeprecatePythonPathExperiment: boolean, - private readonly workspaceService: IWorkspaceService, - private readonly interpreterPathService: IInterpreterPathService - ) {} + constructor(private readonly interpreterPathService: IInterpreterPathService) {} public async updatePythonPath(pythonPath: string | undefined): Promise { - const pythonConfig = this.workspaceService.getConfiguration('python'); - const pythonPathValue = this.inDeprecatePythonPathExperiment - ? this.interpreterPathService.inspect(undefined) - : pythonConfig.inspect('pythonPath')!; + const pythonPathValue = this.interpreterPathService.inspect(undefined); if (pythonPathValue && pythonPathValue.globalValue === pythonPath) { return; } - if (this.inDeprecatePythonPathExperiment) { - await this.interpreterPathService.update(undefined, ConfigurationTarget.Global, pythonPath); - } else { - await pythonConfig.update('pythonPath', pythonPath, true); - } + await this.interpreterPathService.update(undefined, ConfigurationTarget.Global, pythonPath); } } diff --git a/src/client/interpreter/configuration/services/workspaceFolderUpdaterService.ts b/src/client/interpreter/configuration/services/workspaceFolderUpdaterService.ts index 3e7888bab2b3..8c9656b3febf 100644 --- a/src/client/interpreter/configuration/services/workspaceFolderUpdaterService.ts +++ b/src/client/interpreter/configuration/services/workspaceFolderUpdaterService.ts @@ -1,36 +1,15 @@ -import * as path from 'path'; import { ConfigurationTarget, Uri } from 'vscode'; -import { IWorkspaceService } from '../../../common/application/types'; import { IInterpreterPathService } from '../../../common/types'; import { IPythonPathUpdaterService } from '../types'; export class WorkspaceFolderPythonPathUpdaterService implements IPythonPathUpdaterService { - constructor( - private workspaceFolder: Uri, - private readonly inDeprecatePythonPathExperiment: boolean, - private readonly workspaceService: IWorkspaceService, - private readonly interpreterPathService: IInterpreterPathService - ) {} + constructor(private workspaceFolder: Uri, private readonly interpreterPathService: IInterpreterPathService) {} public async updatePythonPath(pythonPath: string | undefined): Promise { - const pythonConfig = this.workspaceService.getConfiguration('python', this.workspaceFolder); - const pythonPathValue = this.inDeprecatePythonPathExperiment - ? this.interpreterPathService.inspect(this.workspaceFolder) - : pythonConfig.inspect('pythonPath')!; + const pythonPathValue = this.interpreterPathService.inspect(this.workspaceFolder); if (pythonPathValue && pythonPathValue.workspaceFolderValue === pythonPath) { return; } - if (pythonPath && pythonPath.startsWith(this.workspaceFolder.fsPath)) { - pythonPath = path.relative(this.workspaceFolder.fsPath, pythonPath); - } - if (this.inDeprecatePythonPathExperiment) { - await this.interpreterPathService.update( - this.workspaceFolder, - ConfigurationTarget.WorkspaceFolder, - pythonPath - ); - } else { - await pythonConfig.update('pythonPath', pythonPath, ConfigurationTarget.WorkspaceFolder); - } + await this.interpreterPathService.update(this.workspaceFolder, ConfigurationTarget.WorkspaceFolder, pythonPath); } } diff --git a/src/client/interpreter/configuration/services/workspaceUpdaterService.ts b/src/client/interpreter/configuration/services/workspaceUpdaterService.ts index 198eaf5f59cb..65bcd0b30e39 100644 --- a/src/client/interpreter/configuration/services/workspaceUpdaterService.ts +++ b/src/client/interpreter/configuration/services/workspaceUpdaterService.ts @@ -1,32 +1,15 @@ -import * as path from 'path'; import { ConfigurationTarget, Uri } from 'vscode'; -import { IWorkspaceService } from '../../../common/application/types'; import { IInterpreterPathService } from '../../../common/types'; import { IPythonPathUpdaterService } from '../types'; export class WorkspacePythonPathUpdaterService implements IPythonPathUpdaterService { - constructor( - private workspace: Uri, - private readonly inDeprecatePythonPathExperiment: boolean, - private readonly workspaceService: IWorkspaceService, - private readonly interpreterPathService: IInterpreterPathService - ) {} + constructor(private workspace: Uri, private readonly interpreterPathService: IInterpreterPathService) {} public async updatePythonPath(pythonPath: string | undefined): Promise { - const pythonConfig = this.workspaceService.getConfiguration('python', this.workspace); - const pythonPathValue = this.inDeprecatePythonPathExperiment - ? this.interpreterPathService.inspect(this.workspace) - : pythonConfig.inspect('pythonPath')!; + const pythonPathValue = this.interpreterPathService.inspect(this.workspace); if (pythonPathValue && pythonPathValue.workspaceValue === pythonPath) { return; } - if (pythonPath && pythonPath.startsWith(this.workspace.fsPath)) { - pythonPath = path.relative(this.workspace.fsPath, pythonPath); - } - if (this.inDeprecatePythonPathExperiment) { - await this.interpreterPathService.update(this.workspace, ConfigurationTarget.Workspace, pythonPath); - } else { - await pythonConfig.update('pythonPath', pythonPath, false); - } + await this.interpreterPathService.update(this.workspace, ConfigurationTarget.Workspace, pythonPath); } } diff --git a/src/client/interpreter/configuration/types.ts b/src/client/interpreter/configuration/types.ts index 4d5c7ad36a51..05ff8e32c18e 100644 --- a/src/client/interpreter/configuration/types.ts +++ b/src/client/interpreter/configuration/types.ts @@ -1,6 +1,7 @@ import { ConfigurationTarget, Disposable, QuickPickItem, Uri } from 'vscode'; import { Resource } from '../../common/types'; import { PythonEnvironment } from '../../pythonEnvironments/info'; +import { PythonExtension, ResolvedEnvironment } from '../../api/types'; export interface IPythonPathUpdaterService { updatePythonPath(pythonPath: string | undefined): Promise; @@ -19,13 +20,26 @@ export interface IPythonPathUpdaterServiceManager { pythonPath: string | undefined, configTarget: ConfigurationTarget, trigger: 'ui' | 'shebang' | 'load', - wkspace?: Uri + wkspace?: Uri, ): Promise; } export const IInterpreterSelector = Symbol('IInterpreterSelector'); export interface IInterpreterSelector extends Disposable { - getSuggestions(resource: Resource): Promise; + getRecommendedSuggestion( + suggestions: IInterpreterQuickPickItem[], + resource: Resource, + ): IInterpreterQuickPickItem | undefined; + /** + * @deprecated Only exists for old Jupyter integration. + */ + getAllSuggestions(resource: Resource): Promise; + getSuggestions(resource: Resource, useFullDisplayName?: boolean): IInterpreterQuickPickItem[]; + suggestionToQuickPickItem( + suggestion: PythonEnvironment, + workspaceUri?: Uri | undefined, + useDetailedName?: boolean, + ): IInterpreterQuickPickItem; } export interface IInterpreterQuickPickItem extends QuickPickItem { @@ -39,7 +53,62 @@ export interface IInterpreterQuickPickItem extends QuickPickItem { interpreter: PythonEnvironment; } +export interface ISpecialQuickPickItem extends QuickPickItem { + path?: string; +} + export const IInterpreterComparer = Symbol('IInterpreterComparer'); export interface IInterpreterComparer { + initialize(resource: Resource): Promise; compare(a: PythonEnvironment, b: PythonEnvironment): number; + getRecommended(interpreters: PythonEnvironment[], resource: Resource): PythonEnvironment | undefined; +} + +export interface InterpreterQuickPickParams { + /** + * Specify `null` if a placeholder is not required. + */ + placeholder?: string | null; + /** + * Specify `null` if a title is not required. + */ + title?: string | null; + /** + * Specify `true` to skip showing recommended python interpreter. + */ + skipRecommended?: boolean; + + /** + * Specify `true` to show back button. + */ + showBackButton?: boolean; + + /** + * Show button to create a new environment. + */ + showCreateEnvironment?: boolean; +} + +export const IInterpreterQuickPick = Symbol('IInterpreterQuickPick'); +export interface IInterpreterQuickPick { + getInterpreterViaQuickPick( + workspace: Resource, + filter?: (i: PythonEnvironment) => boolean, + params?: InterpreterQuickPickParams, + ): Promise; +} + +export const IRecommendedEnvironmentService = Symbol('IRecommendedEnvironmentService'); +export interface IRecommendedEnvironmentService { + registerEnvApi(api: PythonExtension['environments']): void; + trackUserSelectedEnvironment(environmentPath: string | undefined, uri: Uri | undefined): void; + getRecommededEnvironment( + resource: Resource, + ): Promise< + | { + environment: ResolvedEnvironment; + reason: 'globalUserSelected' | 'workspaceUserSelected' | 'defaultRecommended'; + } + | undefined + >; } diff --git a/src/client/interpreter/contracts.ts b/src/client/interpreter/contracts.ts index 179415189f64..30a05c140249 100644 --- a/src/client/interpreter/contracts.ts +++ b/src/client/interpreter/contracts.ts @@ -1,99 +1,105 @@ import { SemVer } from 'semver'; -import { CodeLensProvider, Disposable, Event, TextDocument, Uri } from 'vscode'; +import { ConfigurationTarget, Disposable, Event, Uri } from 'vscode'; +import { FileChangeType } from '../common/platform/fileSystemWatcher'; import { Resource } from '../common/types'; -import { CondaEnvironmentInfo, CondaInfo } from '../pythonEnvironments/discovery/locators/services/conda'; -import { GetInterpreterLocatorOptions } from '../pythonEnvironments/discovery/locators/types'; +import { PythonEnvSource } from '../pythonEnvironments/base/info'; +import { + GetRefreshEnvironmentsOptions, + ProgressNotificationEvent, + PythonLocatorQuery, + TriggerRefreshOptions, +} from '../pythonEnvironments/base/locator'; +import { CondaEnvironmentInfo, CondaInfo } from '../pythonEnvironments/common/environmentManagers/conda'; import { EnvironmentType, PythonEnvironment } from '../pythonEnvironments/info'; -import { WorkspacePythonPath } from './helpers'; -import { GetInterpreterOptions } from './interpreterService'; -export const INTERPRETER_LOCATOR_SERVICE = 'IInterpreterLocatorService'; -export const WINDOWS_REGISTRY_SERVICE = 'WindowsRegistryService'; -export const CONDA_ENV_FILE_SERVICE = 'CondaEnvFileService'; -export const CONDA_ENV_SERVICE = 'CondaEnvService'; -export const CURRENT_PATH_SERVICE = 'CurrentPathService'; -export const KNOWN_PATH_SERVICE = 'KnownPathsService'; -export const GLOBAL_VIRTUAL_ENV_SERVICE = 'VirtualEnvService'; -export const WORKSPACE_VIRTUAL_ENV_SERVICE = 'WorkspaceVirtualEnvService'; -export const PIPENV_SERVICE = 'PipEnvService'; -export const IInterpreterVersionService = Symbol('IInterpreterVersionService'); -export interface IInterpreterVersionService { - getVersion(pythonPath: string, defaultValue: string): Promise; - getPipVersion(pythonPath: string): Promise; -} - -export const IKnownSearchPathsForInterpreters = Symbol('IKnownSearchPathsForInterpreters'); -export interface IKnownSearchPathsForInterpreters { - getSearchPaths(): string[]; -} -export const IVirtualEnvironmentsSearchPathProvider = Symbol('IVirtualEnvironmentsSearchPathProvider'); -export interface IVirtualEnvironmentsSearchPathProvider { - getSearchPaths(resource?: Uri): Promise; -} +export type PythonEnvironmentsChangedEvent = { + type?: FileChangeType; + resource?: Uri; + old?: PythonEnvironment; + new?: PythonEnvironment | undefined; +}; export const IComponentAdapter = Symbol('IComponentAdapter'); export interface IComponentAdapter { + readonly onProgress: Event; + triggerRefresh(query?: PythonLocatorQuery, options?: TriggerRefreshOptions): Promise; + getRefreshPromise(options?: GetRefreshEnvironmentsOptions): Promise | undefined; + readonly onChanged: Event; + // VirtualEnvPrompt + onDidCreate(resource: Resource, callback: () => void): Disposable; // IInterpreterLocatorService - hasInterpreters: Promise; - getInterpreters(resource?: Uri): Promise; + hasInterpreters(filter?: (e: PythonEnvironment) => Promise): Promise; + getInterpreters(resource?: Uri, source?: PythonEnvSource[]): PythonEnvironment[]; + + // WorkspaceVirtualEnvInterpretersAutoSelectionRule + getWorkspaceVirtualEnvInterpreters( + resource: Uri, + options?: { ignoreCache?: boolean }, + ): Promise; + // IInterpreterService - getInterpreterDetails(pythonPath: string, _resource?: Uri): Promise; + getInterpreterDetails(pythonPath: string): Promise; + // IInterpreterHelper - getInterpreterInformation(pythonPath: string): Promise>; - isMacDefaultPythonPath(pythonPath: string): Promise; + // Undefined is expected on this API, if the environment info retrieval fails. + getInterpreterInformation(pythonPath: string): Promise | undefined>; + + isMacDefaultPythonPath(pythonPath: string): Promise; + // ICondaService - isCondaEnvironment(interpreterPath: string): Promise; + isCondaEnvironment(interpreterPath: string): Promise; + // Undefined is expected on this API, if the environment is not conda env. getCondaEnvironment(interpreterPath: string): Promise; - // IWindowsStoreInterpreter - isWindowsStoreInterpreter(pythonPath: string): Promise; -} -export const IInterpreterLocatorService = Symbol('IInterpreterLocatorService'); - -export interface IInterpreterLocatorService extends Disposable { - readonly onLocating: Event>; - readonly hasInterpreters: Promise; - didTriggerInterpreterSuggestions?: boolean; - getInterpreters(resource?: Uri, options?: GetInterpreterLocatorOptions): Promise; + isMicrosoftStoreInterpreter(pythonPath: string): Promise; } export const ICondaService = Symbol('ICondaService'); - +/** + * Interface carries the properties which are not available via the discovery component interface. + */ export interface ICondaService { - readonly condaEnvironmentsFile: string | undefined; - getCondaFile(): Promise; + getCondaFile(forShellExecution?: boolean): Promise; + getCondaInfo(): Promise; isCondaAvailable(): Promise; getCondaVersion(): Promise; - getCondaInfo(): Promise; - getCondaEnvironments(ignoreCache: boolean): Promise; - getInterpreterPath(condaEnvironmentPath: string): string; + getInterpreterPathForEnvironment(condaEnv: CondaEnvironmentInfo): Promise; getCondaFileFromInterpreter(interpreterPath?: string, envName?: string): Promise; - isCondaEnvironment(interpreterPath: string): Promise; - getCondaEnvironment(interpreterPath: string): Promise; + getActivationScriptFromInterpreter( + interpreterPath?: string, + envName?: string, + ): Promise<{ path: string | undefined; type: 'local' | 'global' } | undefined>; } export const IInterpreterService = Symbol('IInterpreterService'); export interface IInterpreterService { + triggerRefresh(query?: PythonLocatorQuery, options?: TriggerRefreshOptions): Promise; + readonly refreshPromise: Promise | undefined; + getRefreshPromise(options?: GetRefreshEnvironmentsOptions): Promise | undefined; + readonly onDidChangeInterpreters: Event; onDidChangeInterpreterConfiguration: Event; - onDidChangeInterpreter: Event; + onDidChangeInterpreter: Event; onDidChangeInterpreterInformation: Event; - hasInterpreters: Promise; - getInterpreters(resource?: Uri, options?: GetInterpreterOptions): Promise; + /** + * Note this API does not trigger the refresh but only works with the current refresh if any. Information + * returned by this is more or less upto date but is not guaranteed to be. + */ + hasInterpreters(filter?: (e: PythonEnvironment) => Promise): Promise; + getInterpreters(resource?: Uri): PythonEnvironment[]; + /** + * @deprecated Only exists for old Jupyter integration. + */ + getAllInterpreters(resource?: Uri): Promise; getActiveInterpreter(resource?: Uri): Promise; getInterpreterDetails(pythonPath: string, resoure?: Uri): Promise; refresh(resource: Resource): Promise; initialize(): void; - getDisplayName(interpreter: Partial): Promise; } export const IInterpreterDisplay = Symbol('IInterpreterDisplay'); export interface IInterpreterDisplay { refresh(resource?: Uri): Promise; -} - -export const IShebangCodeLensProvider = Symbol('IShebangCodeLensProvider'); -export interface IShebangCodeLensProvider extends CodeLensProvider { - detectShebang(document: TextDocument, resolveShebangAsInterpreter?: boolean): Promise; + registerVisibilityFilter(filter: IInterpreterStatusbarVisibilityFilter): void; } export const IInterpreterHelper = Symbol('IInterpreterHelper'); @@ -105,39 +111,6 @@ export interface IInterpreterHelper { getBestInterpreter(interpreters?: PythonEnvironment[]): PythonEnvironment | undefined; } -export const IPipEnvService = Symbol('IPipEnvService'); -export interface IPipEnvService extends IInterpreterLocatorService { - executable: string; - isRelatedPipEnvironment(dir: string, pythonPath: string): Promise; -} - -export const IInterpreterLocatorHelper = Symbol('IInterpreterLocatorHelper'); -export interface IInterpreterLocatorHelper { - mergeInterpreters(interpreters: PythonEnvironment[]): Promise; -} - -export const IInterpreterWatcher = Symbol('IInterpreterWatcher'); -export interface IInterpreterWatcher { - onDidCreate: Event; -} - -export const IInterpreterWatcherBuilder = Symbol('IInterpreterWatcherBuilder'); -export interface IInterpreterWatcherBuilder { - getWorkspaceVirtualEnvInterpreterWatcher(resource: Resource): Promise; -} - -export const IInterpreterLocatorProgressHandler = Symbol('IInterpreterLocatorProgressHandler'); -export interface IInterpreterLocatorProgressHandler { - register(): void; -} - -export const IInterpreterLocatorProgressService = Symbol('IInterpreterLocatorProgressService'); -export interface IInterpreterLocatorProgressService { - readonly onRefreshing: Event; - readonly onRefreshed: Event; - register(): void; -} - export const IInterpreterStatusbarVisibilityFilter = Symbol('IInterpreterStatusbarVisibilityFilter'); /** * Implement this interface to control the visibility of the interpreter statusbar. @@ -146,3 +119,13 @@ export interface IInterpreterStatusbarVisibilityFilter { readonly changed?: Event; readonly hidden: boolean; } + +export type WorkspacePythonPath = { + folderUri: Uri; + configTarget: ConfigurationTarget.Workspace | ConfigurationTarget.WorkspaceFolder; +}; + +export const IActivatedEnvironmentLaunch = Symbol('IActivatedEnvironmentLaunch'); +export interface IActivatedEnvironmentLaunch { + selectIfLaunchedViaActivatedEnv(doNotBlockOnSelection?: boolean): Promise; +} diff --git a/src/client/interpreter/display/index.ts b/src/client/interpreter/display/index.ts index b521dcee9b81..3a602093d4f9 100644 --- a/src/client/interpreter/display/index.ts +++ b/src/client/interpreter/display/index.ts @@ -1,71 +1,96 @@ -import { inject, injectable, multiInject } from 'inversify'; -import { Disposable, OutputChannel, StatusBarAlignment, StatusBarItem, Uri } from 'vscode'; +import { inject, injectable } from 'inversify'; +import { + Disposable, + l10n, + LanguageStatusItem, + LanguageStatusSeverity, + StatusBarAlignment, + StatusBarItem, + ThemeColor, + Uri, +} from 'vscode'; +import { IExtensionSingleActivationService } from '../../activation/types'; import { IApplicationShell, IWorkspaceService } from '../../common/application/types'; -import { STANDARD_OUTPUT_CHANNEL } from '../../common/constants'; +import { Commands, PYTHON_LANGUAGE } from '../../common/constants'; import '../../common/extensions'; -import { IDisposableRegistry, IOutputChannel, IPathUtils, Resource } from '../../common/types'; -import { Interpreters } from '../../common/utils/localize'; +import { IDisposableRegistry, IPathUtils, Resource } from '../../common/types'; +import { InterpreterQuickPickList, Interpreters } from '../../common/utils/localize'; import { IServiceContainer } from '../../ioc/types'; +import { traceLog } from '../../logging'; import { PythonEnvironment } from '../../pythonEnvironments/info'; -import { IInterpreterAutoSelectionService } from '../autoSelection/types'; import { IInterpreterDisplay, IInterpreterHelper, IInterpreterService, - IInterpreterStatusbarVisibilityFilter + IInterpreterStatusbarVisibilityFilter, } from '../contracts'; +import { shouldEnvExtHandleActivation } from '../../envExt/api.internal'; /** - * Create this class as Inversify doesn't allow @multiinject if there are no registered items. - * i.e. we must always have one for @multiinject to work. + * Based on https://github.com/microsoft/vscode-python/issues/18040#issuecomment-992567670. + * This is to ensure the item appears right after the Python language status item. */ +const STATUS_BAR_ITEM_PRIORITY = 100.09999; + @injectable() -export class AlwaysDisplayStatusBar implements IInterpreterStatusbarVisibilityFilter { - public get hidden(): boolean { - return false; - } -} -// tslint:disable-next-line:completed-docs -@injectable() -export class InterpreterDisplay implements IInterpreterDisplay { - private readonly statusBar: StatusBarItem; +export class InterpreterDisplay implements IInterpreterDisplay, IExtensionSingleActivationService { + public supportedWorkspaceTypes: { untrustedWorkspace: boolean; virtualWorkspace: boolean } = { + untrustedWorkspace: false, + virtualWorkspace: true, + }; + private statusBar: StatusBarItem | undefined; + private useLanguageStatus = false; + private languageStatus: LanguageStatusItem | undefined; private readonly helper: IInterpreterHelper; private readonly workspaceService: IWorkspaceService; private readonly pathUtils: IPathUtils; private readonly interpreterService: IInterpreterService; + private currentlySelectedInterpreterDisplay?: string; private currentlySelectedInterpreterPath?: string; private currentlySelectedWorkspaceFolder: Resource; - private readonly autoSelection: IInterpreterAutoSelectionService; - private interpreterPath: string | undefined; private statusBarCanBeDisplayed?: boolean; + private visibilityFilters: IInterpreterStatusbarVisibilityFilter[] = []; + private disposableRegistry: Disposable[]; - constructor( - @inject(IServiceContainer) private readonly serviceContainer: IServiceContainer, - @multiInject(IInterpreterStatusbarVisibilityFilter) - private readonly visibilityFilters: IInterpreterStatusbarVisibilityFilter[] - ) { + constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) { this.helper = serviceContainer.get(IInterpreterHelper); this.workspaceService = serviceContainer.get(IWorkspaceService); this.pathUtils = serviceContainer.get(IPathUtils); this.interpreterService = serviceContainer.get(IInterpreterService); - this.autoSelection = serviceContainer.get(IInterpreterAutoSelectionService); - - const application = serviceContainer.get(IApplicationShell); - const disposableRegistry = serviceContainer.get(IDisposableRegistry); - this.statusBar = application.createStatusBarItem(StatusBarAlignment.Left, 100); - this.statusBar.command = 'python.setInterpreter'; - disposableRegistry.push(this.statusBar); + this.disposableRegistry = serviceContainer.get(IDisposableRegistry); this.interpreterService.onDidChangeInterpreterInformation( this.onDidChangeInterpreterInformation, this, - disposableRegistry + this.disposableRegistry, ); - this.visibilityFilters - .filter((item) => item.changed) - .forEach((item) => item.changed!(this.updateVisibility, this, disposableRegistry)); } + + public async activate(): Promise { + if (shouldEnvExtHandleActivation()) { + return; + } + const application = this.serviceContainer.get(IApplicationShell); + if (this.useLanguageStatus) { + this.languageStatus = application.createLanguageStatusItem('python.selectedInterpreter', { + language: PYTHON_LANGUAGE, + }); + this.languageStatus.severity = LanguageStatusSeverity.Information; + this.languageStatus.command = { + title: InterpreterQuickPickList.browsePath.openButtonLabel, + command: Commands.Set_Interpreter, + }; + this.disposableRegistry.push(this.languageStatus); + } else { + const [alignment, priority] = [StatusBarAlignment.Right, STATUS_BAR_ITEM_PRIORITY]; + this.statusBar = application.createStatusBarItem(alignment, priority, 'python.selectedInterpreterDisplay'); + this.statusBar.command = Commands.Set_Interpreter; + this.disposableRegistry.push(this.statusBar); + this.statusBar.name = Interpreters.selectedPythonInterpreter; + } + } + public async refresh(resource?: Uri) { // Use the workspace Uri if available if (resource && this.workspaceService.getWorkspaceFolder(resource)) { @@ -77,40 +102,88 @@ export class InterpreterDisplay implements IInterpreterDisplay { } await this.updateDisplay(resource); } + public registerVisibilityFilter(filter: IInterpreterStatusbarVisibilityFilter) { + const disposableRegistry = this.serviceContainer.get(IDisposableRegistry); + this.visibilityFilters.push(filter); + if (filter.changed) { + filter.changed(this.updateVisibility, this, disposableRegistry); + } + } private onDidChangeInterpreterInformation(info: PythonEnvironment) { - if (!this.currentlySelectedInterpreterPath || this.currentlySelectedInterpreterPath === info.path) { + if (this.currentlySelectedInterpreterPath === info.path) { this.updateDisplay(this.currentlySelectedWorkspaceFolder).ignoreErrors(); } } private async updateDisplay(workspaceFolder?: Uri) { - await this.autoSelection.autoSelectInterpreter(workspaceFolder); + if (shouldEnvExtHandleActivation()) { + this.statusBar?.hide(); + this.languageStatus?.dispose(); + this.languageStatus = undefined; + return; + } const interpreter = await this.interpreterService.getActiveInterpreter(workspaceFolder); + if ( + this.currentlySelectedInterpreterDisplay && + this.currentlySelectedInterpreterDisplay === interpreter?.detailedDisplayName && + this.currentlySelectedInterpreterPath === interpreter.path + ) { + return; + } this.currentlySelectedWorkspaceFolder = workspaceFolder; - if (interpreter) { - this.statusBar.color = ''; - this.statusBar.tooltip = this.pathUtils.getDisplayName(interpreter.path, workspaceFolder?.fsPath); - if (this.interpreterPath !== interpreter.path) { - const output = this.serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); - output.appendLine( - Interpreters.pythonInterpreterPath().format( - this.pathUtils.getDisplayName(interpreter.path, workspaceFolder?.fsPath) - ) - ); - this.interpreterPath = interpreter.path; + if (this.statusBar) { + if (interpreter) { + this.statusBar.color = ''; + this.statusBar.tooltip = this.pathUtils.getDisplayName(interpreter.path, workspaceFolder?.fsPath); + if (this.currentlySelectedInterpreterPath !== interpreter.path) { + traceLog( + l10n.t( + 'Python interpreter path: {0}', + this.pathUtils.getDisplayName(interpreter.path, workspaceFolder?.fsPath), + ), + ); + this.currentlySelectedInterpreterPath = interpreter.path; + } + let text = interpreter.detailedDisplayName; + text = text?.startsWith('Python') ? text?.substring('Python'.length)?.trim() : text; + this.statusBar.text = text ?? ''; + this.statusBar.backgroundColor = undefined; + this.currentlySelectedInterpreterDisplay = interpreter.detailedDisplayName; + } else { + this.statusBar.tooltip = ''; + this.statusBar.color = ''; + this.statusBar.backgroundColor = new ThemeColor('statusBarItem.warningBackground'); + this.statusBar.text = `$(alert) ${InterpreterQuickPickList.browsePath.openButtonLabel}`; + this.currentlySelectedInterpreterDisplay = undefined; + } + } else if (this.languageStatus) { + if (interpreter) { + this.languageStatus.detail = this.pathUtils.getDisplayName(interpreter.path, workspaceFolder?.fsPath); + if (this.currentlySelectedInterpreterPath !== interpreter.path) { + traceLog( + l10n.t( + 'Python interpreter path: {0}', + this.pathUtils.getDisplayName(interpreter.path, workspaceFolder?.fsPath), + ), + ); + this.currentlySelectedInterpreterPath = interpreter.path; + } + let text = interpreter.detailedDisplayName!; + text = text.startsWith('Python') ? text.substring('Python'.length).trim() : text; + this.languageStatus.text = text; + this.currentlySelectedInterpreterDisplay = interpreter.detailedDisplayName; + this.languageStatus.severity = LanguageStatusSeverity.Information; + } else { + this.languageStatus.severity = LanguageStatusSeverity.Warning; + this.languageStatus.text = `$(alert) ${InterpreterQuickPickList.browsePath.openButtonLabel}`; + this.languageStatus.detail = undefined; + this.currentlySelectedInterpreterDisplay = undefined; } - this.statusBar.text = interpreter.displayName!; - this.currentlySelectedInterpreterPath = interpreter.path; - } else { - this.statusBar.tooltip = ''; - this.statusBar.color = 'yellow'; - this.statusBar.text = '$(alert) Select Python Interpreter'; - this.currentlySelectedInterpreterPath = undefined; } this.statusBarCanBeDisplayed = true; this.updateVisibility(); } private updateVisibility() { - if (!this.statusBarCanBeDisplayed) { + if (!this.statusBar || !this.statusBarCanBeDisplayed) { return; } if (this.visibilityFilters.length === 0 || this.visibilityFilters.every((filter) => !filter.hidden)) { diff --git a/src/client/interpreter/display/interpreterSelectionTip.ts b/src/client/interpreter/display/interpreterSelectionTip.ts deleted file mode 100644 index 2d0eb0747b3a..000000000000 --- a/src/client/interpreter/display/interpreterSelectionTip.ts +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IExtensionSingleActivationService } from '../../activation/types'; -import { IApplicationShell } from '../../common/application/types'; -import { SurveyAndInterpreterTipNotification } from '../../common/experiments/groups'; -import { IBrowserService, IExperimentService, IPersistentState, IPersistentStateFactory } from '../../common/types'; -import { swallowExceptions } from '../../common/utils/decorators'; -import { Common } from '../../common/utils/localize'; -import { sendTelemetryEvent } from '../../telemetry'; -import { EventName } from '../../telemetry/constants'; - -enum NotificationType { - Tip, - Survey, - NoPrompt -} - -@injectable() -export class InterpreterSelectionTip implements IExtensionSingleActivationService { - private readonly storage: IPersistentState; - private notificationType: NotificationType; - private notificationContent: string | undefined; - - constructor( - @inject(IApplicationShell) private readonly shell: IApplicationShell, - @inject(IPersistentStateFactory) factory: IPersistentStateFactory, - @inject(IExperimentService) private readonly experiments: IExperimentService, - @inject(IBrowserService) private browserService: IBrowserService - ) { - this.storage = factory.createGlobalPersistentState('InterpreterSelectionTip', false); - this.notificationType = NotificationType.NoPrompt; - } - - public async activate(): Promise { - // Only show the prompt if we have never shown it before. True here, means we have - // shown the prompt before. - if (this.storage.value) { - return; - } - - if (await this.experiments.inExperiment(SurveyAndInterpreterTipNotification.surveyExperiment)) { - this.notificationType = NotificationType.Survey; - this.notificationContent = await this.experiments.getExperimentValue( - SurveyAndInterpreterTipNotification.surveyExperiment - ); - } else if (await this.experiments.inExperiment(SurveyAndInterpreterTipNotification.tipExperiment)) { - this.notificationType = NotificationType.Tip; - this.notificationContent = await this.experiments.getExperimentValue( - SurveyAndInterpreterTipNotification.tipExperiment - ); - } - - this.showTip().ignoreErrors(); - - // We will disable this prompt for all users even if they are not - // in any experiment. The idea is that people should get either the - // tip or survey or nothing, on first load. If we are here then that - // means we are done with this prompt (even if it was not shown). - await this.storage.updateValue(true); - } - @swallowExceptions('Failed to display tip') - private async showTip() { - if (this.notificationType === NotificationType.Tip) { - await this.shell.showInformationMessage(this.notificationContent!, Common.gotIt()); - sendTelemetryEvent(EventName.ACTIVATION_TIP_PROMPT, undefined); - } else if (this.notificationType === NotificationType.Survey) { - const selection = await this.shell.showInformationMessage( - this.notificationContent!, - Common.bannerLabelYes(), - Common.bannerLabelNo() - ); - - if (selection === Common.bannerLabelYes()) { - sendTelemetryEvent(EventName.ACTIVATION_SURVEY_PROMPT, undefined); - this.browserService.launch('https://aka.ms/mailingListSurvey'); - } - } - } -} diff --git a/src/client/interpreter/display/progressDisplay.ts b/src/client/interpreter/display/progressDisplay.ts index 83b5b9dfbe30..4b2811043d2f 100644 --- a/src/client/interpreter/display/progressDisplay.ts +++ b/src/client/interpreter/display/progressDisplay.ts @@ -5,44 +5,70 @@ import { inject, injectable } from 'inversify'; import { Disposable, ProgressLocation, ProgressOptions } from 'vscode'; +import { IExtensionSingleActivationService } from '../../activation/types'; import { IApplicationShell } from '../../common/application/types'; -import { traceDecorators } from '../../common/logger'; +import { Commands } from '../../common/constants'; import { IDisposableRegistry } from '../../common/types'; import { createDeferred, Deferred } from '../../common/utils/async'; -import { Common, Interpreters } from '../../common/utils/localize'; -import { IInterpreterLocatorProgressHandler, IInterpreterLocatorProgressService } from '../contracts'; +import { Interpreters } from '../../common/utils/localize'; +import { traceDecoratorVerbose } from '../../logging'; +import { ProgressReportStage } from '../../pythonEnvironments/base/locator'; +import { IComponentAdapter } from '../contracts'; +// The parts of IComponentAdapter used here. @injectable() -export class InterpreterLocatorProgressStatubarHandler implements IInterpreterLocatorProgressHandler { +export class InterpreterLocatorProgressStatusBarHandler implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; + private deferred: Deferred | undefined; + private isFirstTimeLoadingInterpreters = true; + constructor( @inject(IApplicationShell) private readonly shell: IApplicationShell, - @inject(IInterpreterLocatorProgressService) - private readonly progressService: IInterpreterLocatorProgressService, - @inject(IDisposableRegistry) private readonly disposables: Disposable[] + @inject(IDisposableRegistry) private readonly disposables: Disposable[], + @inject(IComponentAdapter) private readonly pyenvs: IComponentAdapter, ) {} - public register() { - this.progressService.onRefreshing(() => this.showProgress(), this, this.disposables); - this.progressService.onRefreshed(() => this.hideProgress(), this, this.disposables); + + public async activate(): Promise { + this.pyenvs.onProgress( + (event) => { + if (event.stage === ProgressReportStage.discoveryStarted) { + this.showProgress(); + const refreshPromise = this.pyenvs.getRefreshPromise(); + if (refreshPromise) { + refreshPromise.then(() => this.hideProgress()); + } + } else if (event.stage === ProgressReportStage.discoveryFinished) { + this.hideProgress(); + } + }, + this, + this.disposables, + ); } - @traceDecorators.verbose('Display locator refreshing progress') + + @traceDecoratorVerbose('Display locator refreshing progress') private showProgress(): void { if (!this.deferred) { this.createProgress(); } } - @traceDecorators.verbose('Hide locator refreshing progress') + + @traceDecoratorVerbose('Hide locator refreshing progress') private hideProgress(): void { if (this.deferred) { this.deferred.resolve(); this.deferred = undefined; } } + private createProgress() { const progressOptions: ProgressOptions = { location: ProgressLocation.Window, - title: this.isFirstTimeLoadingInterpreters ? Common.loadingExtension() : Interpreters.refreshing() + title: `[${ + this.isFirstTimeLoadingInterpreters ? Interpreters.discovering : Interpreters.refreshing + }](command:${Commands.Set_Interpreter})`, }; this.isFirstTimeLoadingInterpreters = false; this.shell.withProgress(progressOptions, () => { diff --git a/src/client/interpreter/display/shebangCodeLensProvider.ts b/src/client/interpreter/display/shebangCodeLensProvider.ts deleted file mode 100644 index 8737981c8369..000000000000 --- a/src/client/interpreter/display/shebangCodeLensProvider.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { inject, injectable } from 'inversify'; -import { CancellationToken, CodeLens, Command, Event, Position, Range, TextDocument, Uri } from 'vscode'; -import { IWorkspaceService } from '../../common/application/types'; -import { IPlatformService } from '../../common/platform/types'; -import * as internalPython from '../../common/process/internal/python'; -import { IProcessServiceFactory } from '../../common/process/types'; -import { IConfigurationService } from '../../common/types'; -import { IShebangCodeLensProvider } from '../contracts'; - -@injectable() -export class ShebangCodeLensProvider implements IShebangCodeLensProvider { - public readonly onDidChangeCodeLenses: Event; - constructor( - @inject(IProcessServiceFactory) private readonly processServiceFactory: IProcessServiceFactory, - @inject(IConfigurationService) private readonly configurationService: IConfigurationService, - @inject(IPlatformService) private readonly platformService: IPlatformService, - @inject(IWorkspaceService) workspaceService: IWorkspaceService - ) { - // tslint:disable-next-line:no-any - this.onDidChangeCodeLenses = (workspaceService.onDidChangeConfiguration as any) as Event; - } - public async detectShebang( - document: TextDocument, - resolveShebangAsInterpreter: boolean = false - ): Promise { - const firstLine = document.lineAt(0); - if (firstLine.isEmptyOrWhitespace) { - return; - } - - if (!firstLine.text.startsWith('#!')) { - return; - } - - const shebang = firstLine.text.substr(2).trim(); - if (resolveShebangAsInterpreter) { - const pythonPath = await this.getFullyQualifiedPathToInterpreter(shebang, document.uri); - return typeof pythonPath === 'string' && pythonPath.length > 0 ? pythonPath : undefined; - } else { - return typeof shebang === 'string' && shebang.length > 0 ? shebang : undefined; - } - } - public async provideCodeLenses(document: TextDocument, _token?: CancellationToken): Promise { - return this.createShebangCodeLens(document); - } - private async getFullyQualifiedPathToInterpreter(pythonPath: string, resource: Uri) { - let cmdFile = pythonPath; - const [args, parse] = internalPython.getExecutable(); - if (pythonPath.indexOf('bin/env ') >= 0 && !this.platformService.isWindows) { - // In case we have pythonPath as '/usr/bin/env python'. - const parts = pythonPath - .split(' ') - .map((part) => part.trim()) - .filter((part) => part.length > 0); - cmdFile = parts.shift()!; - args.splice(0, 0, ...parts); - } - const processService = await this.processServiceFactory.create(resource); - return processService - .exec(cmdFile, args) - .then((output) => parse(output.stdout)) - .catch(() => ''); - } - private async createShebangCodeLens(document: TextDocument) { - const shebang = await this.detectShebang(document); - if (!shebang) { - return []; - } - const pythonPath = this.configurationService.getSettings(document.uri).pythonPath; - const resolvedPythonPath = await this.getFullyQualifiedPathToInterpreter(pythonPath, document.uri); - if (shebang === resolvedPythonPath) { - return []; - } - const firstLine = document.lineAt(0); - const startOfShebang = new Position(0, 0); - const endOfShebang = new Position(0, firstLine.text.length - 1); - const shebangRange = new Range(startOfShebang, endOfShebang); - - const cmd: Command = { - command: 'python.setShebangInterpreter', - title: 'Set as interpreter' - }; - - return [new CodeLens(shebangRange, cmd)]; - } -} diff --git a/src/client/interpreter/helpers.ts b/src/client/interpreter/helpers.ts index daf7f6af2a37..413fa225f3ef 100644 --- a/src/client/interpreter/helpers.ts +++ b/src/client/interpreter/helpers.ts @@ -1,68 +1,47 @@ import { inject, injectable } from 'inversify'; import { ConfigurationTarget, Uri } from 'vscode'; import { IDocumentManager, IWorkspaceService } from '../common/application/types'; -import { traceError } from '../common/logger'; import { FileSystemPaths } from '../common/platform/fs-paths'; -import { IPythonExecutionFactory } from '../common/process/types'; -import { IPersistentStateFactory, Resource } from '../common/types'; +import { Resource } from '../common/types'; import { IServiceContainer } from '../ioc/types'; -import { isMacDefaultPythonPath } from '../pythonEnvironments/discovery'; -import { InterpeterHashProviderFactory } from '../pythonEnvironments/discovery/locators/services/hashProviderFactory'; -import { - EnvironmentType, - getEnvironmentTypeName, - InterpreterInformation, - PythonEnvironment, - sortInterpreters -} from '../pythonEnvironments/info'; -import { IComponentAdapter, IInterpreterHelper } from './contracts'; -import { IInterpreterHashProviderFactory } from './locators/types'; +import { PythonEnvSource } from '../pythonEnvironments/base/info'; +import { compareSemVerLikeVersions } from '../pythonEnvironments/base/info/pythonVersion'; +import { EnvironmentType, getEnvironmentTypeName, PythonEnvironment } from '../pythonEnvironments/info'; +import { IComponentAdapter, IInterpreterHelper, WorkspacePythonPath } from './contracts'; -const EXPITY_DURATION = 24 * 60 * 60 * 1000; -type CachedPythonInterpreter = Partial & { fileHash: string }; - -export type WorkspacePythonPath = { - folderUri: Uri; - configTarget: ConfigurationTarget.Workspace | ConfigurationTarget.WorkspaceFolder; -}; - -export function getFirstNonEmptyLineFromMultilineString(stdout: string) { - if (!stdout) { - return ''; - } - const lines = stdout - .split(/\r?\n/g) - .map((line) => line.trim()) - .filter((line) => line.length > 0); - return lines.length > 0 ? lines[0] : ''; -} - -export function isInterpreterLocatedInWorkspace(interpreter: PythonEnvironment, activeWorkspaceUri: Uri) { +export function isInterpreterLocatedInWorkspace(interpreter: PythonEnvironment, activeWorkspaceUri: Uri): boolean { const fileSystemPaths = FileSystemPaths.withDefaults(); const interpreterPath = fileSystemPaths.normCase(interpreter.path); const resourcePath = fileSystemPaths.normCase(activeWorkspaceUri.fsPath); return interpreterPath.startsWith(resourcePath); } -// The parts of IComponentAdapter used here. -interface IComponent { - getInterpreterInformation(pythonPath: string): Promise>; - isMacDefaultPythonPath(pythonPath: string): Promise; +/** + * Build a version-sorted list from the given one, with lowest first. + */ +export function sortInterpreters(interpreters: PythonEnvironment[]): PythonEnvironment[] { + if (interpreters.length === 0) { + return []; + } + if (interpreters.length === 1) { + return [interpreters[0]]; + } + const sorted = interpreters.slice(); + sorted.sort((a, b) => (a.version && b.version ? compareSemVerLikeVersions(a.version, b.version) : 0)); + return sorted; } @injectable() export class InterpreterHelper implements IInterpreterHelper { - private readonly persistentFactory: IPersistentStateFactory; constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(InterpeterHashProviderFactory) private readonly hashProviderFactory: IInterpreterHashProviderFactory, - @inject(IComponentAdapter) private readonly pyenvs: IComponent - ) { - this.persistentFactory = this.serviceContainer.get(IPersistentStateFactory); - } + @inject(IComponentAdapter) private readonly pyenvs: IComponentAdapter, + ) {} + public getActiveWorkspaceUri(resource: Resource): WorkspacePythonPath | undefined { const workspaceService = this.serviceContainer.get(IWorkspaceService); - if (!workspaceService.hasWorkspaceFolders) { + const hasWorkspaceFolders = (workspaceService.workspaceFolders?.length || 0) > 0; + if (!hasWorkspaceFolders) { return; } if (Array.isArray(workspaceService.workspaceFolders) && workspaceService.workspaceFolders.length === 1) { @@ -84,59 +63,35 @@ export class InterpreterHelper implements IInterpreterHelper { } } } + public async getInterpreterInformation(pythonPath: string): Promise> { - const found = await this.pyenvs.getInterpreterInformation(pythonPath); - if (found !== undefined) { - return found; - } + return this.pyenvs.getInterpreterInformation(pythonPath); + } - const fileHash = await this.hashProviderFactory - .create({ pythonPath }) - .then((provider) => provider.getInterpreterHash(pythonPath)) - .catch((ex) => { - traceError(`Failed to create File hash for interpreter ${pythonPath}`, ex); - return ''; - }); - const store = this.persistentFactory.createGlobalPersistentState( - `${pythonPath}.v3`, - undefined, - EXPITY_DURATION - ); - if (store.value && fileHash && store.value.fileHash === fileHash) { - return store.value; - } - const processService = await this.serviceContainer - .get(IPythonExecutionFactory) - .create({ pythonPath }); + public async getInterpreters({ resource, source }: { resource?: Uri; source?: PythonEnvSource[] } = {}): Promise< + PythonEnvironment[] + > { + const interpreters = await this.pyenvs.getInterpreters(resource, source); + return sortInterpreters(interpreters); + } - try { - const info = await processService - .getInterpreterInformation() - .catch(() => undefined); - if (!info) { - return; - } - const details = { - ...info, - fileHash - }; - await store.updateValue(details); - return details; - } catch (ex) { - traceError(`Failed to get interpreter information for '${pythonPath}'`, ex); - return; + public async getInterpreterPath(pythonPath: string): Promise { + const interpreterInfo: any = await this.getInterpreterInformation(pythonPath); + if (interpreterInfo) { + return interpreterInfo.path; + } else { + return pythonPath; } } + public async isMacDefaultPythonPath(pythonPath: string): Promise { - const result = await this.pyenvs.isMacDefaultPythonPath(pythonPath); - if (result !== undefined) { - return result; - } - return isMacDefaultPythonPath(pythonPath); + return this.pyenvs.isMacDefaultPythonPath(pythonPath); } - public getInterpreterTypeDisplayName(interpreterType: EnvironmentType) { + + public getInterpreterTypeDisplayName(interpreterType: EnvironmentType): string { return getEnvironmentTypeName(interpreterType); } + public getBestInterpreter(interpreters?: PythonEnvironment[]): PythonEnvironment | undefined { if (!Array.isArray(interpreters) || interpreters.length === 0) { return; diff --git a/src/client/interpreter/interpreterPathCommand.ts b/src/client/interpreter/interpreterPathCommand.ts new file mode 100644 index 000000000000..12f6756dafeb --- /dev/null +++ b/src/client/interpreter/interpreterPathCommand.ts @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { Uri, workspace } from 'vscode'; +import { IExtensionSingleActivationService } from '../activation/types'; +import { Commands } from '../common/constants'; +import { IDisposable, IDisposableRegistry } from '../common/types'; +import { registerCommand } from '../common/vscodeApis/commandApis'; +import { IInterpreterService } from './contracts'; +import { useEnvExtension } from '../envExt/api.internal'; + +@injectable() +export class InterpreterPathCommand implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; + + constructor( + @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, + @inject(IDisposableRegistry) private readonly disposables: IDisposable[], + ) {} + + public async activate(): Promise { + this.disposables.push( + registerCommand(Commands.GetSelectedInterpreterPath, (args) => this._getSelectedInterpreterPath(args)), + ); + } + + public async _getSelectedInterpreterPath( + args: { workspaceFolder: string; type: string } | string[], + ): Promise { + // If `launch.json` is launching this command, `args.workspaceFolder` carries the workspaceFolder + // If `tasks.json` is launching this command, `args[1]` carries the workspaceFolder + let workspaceFolder; + if ('workspaceFolder' in args) { + workspaceFolder = args.workspaceFolder; + } else if (args[1]) { + const [, second] = args; + workspaceFolder = second; + } else if (useEnvExtension() && 'type' in args && args.type === 'debugpy') { + // If using the envsExt and the type is debugpy, we need to add the workspace folder to get the interpreter path. + if (Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length > 0) { + workspaceFolder = workspace.workspaceFolders[0].uri.fsPath; + } + } else { + workspaceFolder = undefined; + } + + let workspaceFolderUri; + try { + workspaceFolderUri = workspaceFolder ? Uri.file(workspaceFolder) : undefined; + } catch (ex) { + workspaceFolderUri = undefined; + } + + const interpreterPath = + (await this.interpreterService.getActiveInterpreter(workspaceFolderUri))?.path ?? 'python'; + return interpreterPath.toCommandArgumentForPythonExt(); + } +} diff --git a/src/client/interpreter/interpreterService.ts b/src/client/interpreter/interpreterService.ts index 6d75fa78d65d..ad06fd7d051d 100644 --- a/src/client/interpreter/interpreterService.ts +++ b/src/client/interpreter/interpreterService.ts @@ -1,370 +1,322 @@ +// eslint-disable-next-line max-classes-per-file import { inject, injectable } from 'inversify'; -import * as md5 from 'md5'; -import * as path from 'path'; -import { Disposable, Event, EventEmitter, Uri } from 'vscode'; -import '../../client/common/extensions'; -import { IDocumentManager, IWorkspaceService } from '../common/application/types'; -import { DeprecatePythonPath } from '../common/experiments/groups'; -import { traceError } from '../common/logger'; -import { getArchitectureDisplayName } from '../common/platform/registry'; -import { IFileSystem } from '../common/platform/types'; -import { IPythonExecutionFactory } from '../common/process/types'; +import * as pathUtils from 'path'; +import { + ConfigurationChangeEvent, + Disposable, + Event, + EventEmitter, + ProgressLocation, + ProgressOptions, + Uri, + WorkspaceFolder, +} from 'vscode'; +import '../common/extensions'; +import { IApplicationShell, IDocumentManager, IWorkspaceService } from '../common/application/types'; import { IConfigurationService, IDisposableRegistry, - IExperimentsManager, + IInstaller, IInterpreterPathService, - IPersistentState, - IPersistentStateFactory, - Resource + Product, } from '../common/types'; -import { sleep } from '../common/utils/async'; import { IServiceContainer } from '../ioc/types'; -import { InterpeterHashProviderFactory } from '../pythonEnvironments/discovery/locators/services/hashProviderFactory'; -import { EnvironmentType, PythonEnvironment } from '../pythonEnvironments/info'; -import { captureTelemetry } from '../telemetry'; -import { EventName } from '../telemetry/constants'; +import { PythonEnvironment } from '../pythonEnvironments/info'; import { + IActivatedEnvironmentLaunch, IComponentAdapter, IInterpreterDisplay, - IInterpreterHelper, - IInterpreterLocatorService, IInterpreterService, - INTERPRETER_LOCATOR_SERVICE + IInterpreterStatusbarVisibilityFilter, + PythonEnvironmentsChangedEvent, } from './contracts'; -import { IInterpreterHashProviderFactory } from './locators/types'; -import { IVirtualEnvironmentManager } from './virtualEnvs/types'; - -const EXPITY_DURATION = 24 * 60 * 60 * 1000; - -export type GetInterpreterOptions = { - onSuggestion?: boolean; -}; +import { traceError, traceLog } from '../logging'; +import { Commands, PVSC_EXTENSION_ID, PYTHON_LANGUAGE } from '../common/constants'; +import { reportActiveInterpreterChanged } from '../environmentApi'; +import { IPythonExecutionFactory } from '../common/process/types'; +import { Interpreters } from '../common/utils/localize'; +import { sendTelemetryEvent } from '../telemetry'; +import { EventName } from '../telemetry/constants'; +import { cache } from '../common/utils/decorators'; +import { + GetRefreshEnvironmentsOptions, + PythonLocatorQuery, + TriggerRefreshOptions, +} from '../pythonEnvironments/base/locator'; +import { sleep } from '../common/utils/async'; +import { useEnvExtension } from '../envExt/api.internal'; +import { getActiveInterpreterLegacy } from '../envExt/api.legacy'; -// The parts of IComponentAdapter used here. -interface IComponent { - getInterpreterDetails(pythonPath: string): Promise; -} +type StoredPythonEnvironment = PythonEnvironment & { store?: boolean }; @injectable() export class InterpreterService implements Disposable, IInterpreterService { - public get hasInterpreters(): Promise { - return this.locator.hasInterpreters; + public async hasInterpreters( + filter: (e: PythonEnvironment) => Promise = async () => true, + ): Promise { + return this.pyenvs.hasInterpreters(filter); } - public get onDidChangeInterpreter(): Event { + public triggerRefresh(query?: PythonLocatorQuery, options?: TriggerRefreshOptions): Promise { + return this.pyenvs.triggerRefresh(query, options); + } + + public get refreshPromise(): Promise | undefined { + return this.pyenvs.getRefreshPromise(); + } + + public getRefreshPromise(options?: GetRefreshEnvironmentsOptions): Promise | undefined { + return this.pyenvs.getRefreshPromise(options); + } + + public get onDidChangeInterpreter(): Event { return this.didChangeInterpreterEmitter.event; } + public onDidChangeInterpreters: Event; + public get onDidChangeInterpreterInformation(): Event { return this.didChangeInterpreterInformation.event; } + public get onDidChangeInterpreterConfiguration(): Event { return this.didChangeInterpreterConfigurationEmitter.event; } - public _pythonPathSetting: string = ''; + + public _pythonPathSetting: string | undefined = ''; + private readonly didChangeInterpreterConfigurationEmitter = new EventEmitter(); - private readonly locator: IInterpreterLocatorService; - private readonly persistentStateFactory: IPersistentStateFactory; + private readonly configService: IConfigurationService; + private readonly interpreterPathService: IInterpreterPathService; - private readonly experiments: IExperimentsManager; - private readonly didChangeInterpreterEmitter = new EventEmitter(); + + private readonly didChangeInterpreterEmitter = new EventEmitter(); + private readonly didChangeInterpreterInformation = new EventEmitter(); - private readonly inMemoryCacheOfDisplayNames = new Map(); - private readonly updatedInterpreters = new Set(); + + private readonly activeInterpreterPaths = new Map< + string, + { path: string; workspaceFolder: WorkspaceFolder | undefined } + >(); constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(InterpeterHashProviderFactory) private readonly hashProviderFactory: IInterpreterHashProviderFactory, - @inject(IComponentAdapter) private readonly pyenvs: IComponent + @inject(IComponentAdapter) private readonly pyenvs: IComponentAdapter, ) { - this.locator = serviceContainer.get( - IInterpreterLocatorService, - INTERPRETER_LOCATOR_SERVICE - ); - this.persistentStateFactory = this.serviceContainer.get(IPersistentStateFactory); this.configService = this.serviceContainer.get(IConfigurationService); this.interpreterPathService = this.serviceContainer.get(IInterpreterPathService); - this.experiments = this.serviceContainer.get(IExperimentsManager); + this.onDidChangeInterpreters = pyenvs.onChanged; } - public async refresh(resource?: Uri) { + public async refresh(resource?: Uri): Promise { const interpreterDisplay = this.serviceContainer.get(IInterpreterDisplay); - return interpreterDisplay.refresh(resource); + await interpreterDisplay.refresh(resource); + const workspaceFolder = this.serviceContainer + .get(IWorkspaceService) + .getWorkspaceFolder(resource); + const path = this.configService.getSettings(resource).pythonPath; + const workspaceKey = this.serviceContainer + .get(IWorkspaceService) + .getWorkspaceFolderIdentifier(resource); + this.activeInterpreterPaths.set(workspaceKey, { path, workspaceFolder }); + this.ensureEnvironmentContainsPython(path, workspaceFolder).ignoreErrors(); } - public initialize() { + public initialize(): void { const disposables = this.serviceContainer.get(IDisposableRegistry); const documentManager = this.serviceContainer.get(IDocumentManager); - disposables.push( - documentManager.onDidChangeActiveTextEditor((e) => - e && e.document ? this.refresh(e.document.uri) : undefined - ) - ); - const workspaceService = this.serviceContainer.get(IWorkspaceService); - const pySettings = this.configService.getSettings(); - this._pythonPathSetting = pySettings.pythonPath; - if (this.experiments.inExperiment(DeprecatePythonPath.experiment)) { - disposables.push( - this.interpreterPathService.onDidChange((i) => { - this._onConfigChanged(i.uri); - }) - ); - } else { - const workspacesUris: (Uri | undefined)[] = workspaceService.hasWorkspaceFolders - ? workspaceService.workspaceFolders!.map((workspace) => workspace.uri) - : [undefined]; - const disposable = workspaceService.onDidChangeConfiguration((e) => { - const workspaceUriIndex = workspacesUris.findIndex((uri) => - e.affectsConfiguration('python.pythonPath', uri) + const interpreterDisplay = this.serviceContainer.get(IInterpreterDisplay); + const filter = new (class implements IInterpreterStatusbarVisibilityFilter { + constructor( + private readonly docManager: IDocumentManager, + private readonly configService: IConfigurationService, + private readonly disposablesReg: IDisposableRegistry, + ) { + this.disposablesReg.push( + this.configService.onDidChange(async (event: ConfigurationChangeEvent | undefined) => { + if (event?.affectsConfiguration('python.interpreter.infoVisibility')) { + this.interpreterVisibilityEmitter.fire(); + } + }), ); - const workspaceUri = workspaceUriIndex === -1 ? undefined : workspacesUris[workspaceUriIndex]; - this._onConfigChanged(workspaceUri); - }); - disposables.push(disposable); - } - this.experiments.sendTelemetryIfInExperiment(DeprecatePythonPath.control); - } + } + + public readonly interpreterVisibilityEmitter = new EventEmitter(); - @captureTelemetry(EventName.PYTHON_INTERPRETER_DISCOVERY, { locator: 'all' }, true) - public async getInterpreters(resource?: Uri, options?: GetInterpreterOptions): Promise { - const interpreters = await this.locator.getInterpreters(resource, options); - await Promise.all( - interpreters - .filter((item) => !item.displayName) - .map(async (item) => { - item.displayName = await this.getDisplayName(item, resource); - // Keep information up to date with latest details. - if (!item.cachedEntry) { - this.updateCachedInterpreterInformation(item, resource).ignoreErrors(); + public readonly changed = this.interpreterVisibilityEmitter.event; + + get hidden() { + const visibility = this.configService.getSettings().interpreter.infoVisibility; + if (visibility === 'never') { + return true; + } + if (visibility === 'always') { + return false; + } + const document = this.docManager.activeTextEditor?.document; + // Output channel for MS Python related extensions. These contain "ms-python" in their ID. + const pythonOutputChannelPattern = PVSC_EXTENSION_ID.split('.')[0]; + if ( + document?.fileName.endsWith('settings.json') || + document?.fileName.includes(pythonOutputChannelPattern) + ) { + return false; + } + return document?.languageId !== PYTHON_LANGUAGE; + } + })(documentManager, this.configService, disposables); + interpreterDisplay.registerVisibilityFilter(filter); + disposables.push( + this.onDidChangeInterpreters((e): void => { + const interpreter = e.old ?? e.new; + if (interpreter) { + this.didChangeInterpreterInformation.fire(interpreter); + for (const { path, workspaceFolder } of this.activeInterpreterPaths.values()) { + if (path === interpreter.path && !e.new) { + // If the active environment got deleted, notify it. + this.didChangeInterpreterEmitter.fire(workspaceFolder?.uri); + reportActiveInterpreterChanged({ + path, + resource: workspaceFolder, + }); + } } - }) + } + }), + ); + disposables.push( + documentManager.onDidOpenTextDocument(() => { + // To handle scenario when language mode is set to "python" + filter.interpreterVisibilityEmitter.fire(); + }), + documentManager.onDidChangeActiveTextEditor((e): void => { + filter.interpreterVisibilityEmitter.fire(); + if (e && e.document) { + this.refresh(e.document.uri); + } + }), ); - return interpreters; + disposables.push(this.interpreterPathService.onDidChange((i) => this._onConfigChanged(i.uri))); + } + + public getInterpreters(resource?: Uri): PythonEnvironment[] { + return this.pyenvs.getInterpreters(resource); + } + + public async getAllInterpreters(resource?: Uri): Promise { + // For backwards compatibility with old Jupyter APIs, ensure a + // fresh refresh is always triggered when using the API. As it is + // no longer auto-triggered by the extension. + this.triggerRefresh(undefined, { ifNotTriggerredAlready: true }).ignoreErrors(); + await this.refreshPromise; + return this.getInterpreters(resource); } public dispose(): void { - this.locator.dispose(); this.didChangeInterpreterEmitter.dispose(); this.didChangeInterpreterInformation.dispose(); } public async getActiveInterpreter(resource?: Uri): Promise { - // During shutdown we might not be able to get items out of the service container. - const pythonExecutionFactory = this.serviceContainer.tryGet(IPythonExecutionFactory); - const pythonExecutionService = pythonExecutionFactory - ? await pythonExecutionFactory.create({ resource }) - : undefined; - const fullyQualifiedPath = pythonExecutionService - ? await pythonExecutionService.getExecutablePath().catch(() => undefined) - : undefined; - // Python path is invalid or python isn't installed. - if (!fullyQualifiedPath) { - return; - } - - return this.getInterpreterDetails(fullyQualifiedPath, resource); - } - public async getInterpreterDetails(pythonPath: string, resource?: Uri): Promise { - const info = await this.pyenvs.getInterpreterDetails(pythonPath); - if (info !== undefined) { - return info; - } - - // If we don't have the fully qualified path, then get it. - if (path.basename(pythonPath) === pythonPath) { - const pythonExecutionFactory = this.serviceContainer.get(IPythonExecutionFactory); - const pythonExecutionService = await pythonExecutionFactory.create({ resource }); - pythonPath = await pythonExecutionService.getExecutablePath().catch(() => ''); - // Python path is invalid or python isn't installed. - if (!pythonPath) { - return; - } - } - - const store = await this.getInterpreterCache(pythonPath); - if (store.value && store.value.info) { - return store.value.info; + if (useEnvExtension()) { + return getActiveInterpreterLegacy(resource); } - const fs = this.serviceContainer.get(IFileSystem); - - // Don't want for all interpreters are collected. - // Try to collect the infromation manually, that's faster. - // Get from which ever comes first. - const option1 = (async () => { - const result = this.collectInterpreterDetails(pythonPath, resource); - await sleep(1000); // let the other option complete within 1s if possible. - return result; - })(); - - // This is the preferred approach, hence the delay in option 1. - const option2 = (async () => { - const interpreters = await this.getInterpreters(resource); - const found = interpreters.find((i) => fs.arePathsSame(i.path, pythonPath)); - if (found) { - // Cache the interpreter info, only if we get the data from interpretr list. - // tslint:disable-next-line:no-any - (found as any).__store = true; - return found; + const activatedEnvLaunch = this.serviceContainer.get(IActivatedEnvironmentLaunch); + let path = await activatedEnvLaunch.selectIfLaunchedViaActivatedEnv(true); + // This is being set as interpreter in background, after which it'll show up in `.pythonPath` config. + // However we need not wait on the update to take place, as we can use the value directly. + if (!path) { + path = this.configService.getSettings(resource).pythonPath; + if (pathUtils.basename(path) === path) { + // Value can be `python`, `python3`, `python3.9` etc. + // Note the following triggers autoselection if no interpreter is explictly + // selected, i.e the value is `python`. + // During shutdown we might not be able to get items out of the service container. + const pythonExecutionFactory = this.serviceContainer.tryGet( + IPythonExecutionFactory, + ); + const pythonExecutionService = pythonExecutionFactory + ? await pythonExecutionFactory.create({ resource }) + : undefined; + const fullyQualifiedPath = pythonExecutionService + ? await pythonExecutionService.getExecutablePath().catch((ex) => { + traceError(ex); + }) + : undefined; + // Python path is invalid or python isn't installed. + if (!fullyQualifiedPath) { + return undefined; + } + path = fullyQualifiedPath; } - // Use option1 as a fallback. - return option1; - })(); - - // Get the first one that doesn't return undefined - let interpreterInfo = await Promise.race([option2, option1]); - if (!interpreterInfo) { - // If undefined, wait for both - const both = await Promise.all([option1, option2]); - interpreterInfo = both[0] ? both[0] : both[1]; - } - - // tslint:disable-next-line:no-any - if (interpreterInfo && (interpreterInfo as any).__store) { - await this.updateCachedInterpreterInformation(interpreterInfo, resource); } - return interpreterInfo; + return this.getInterpreterDetails(path); } - /** - * Gets the display name of an interpreter. - * The format is `Python (: )` - * E.g. `Python 3.5.1 32-bit (myenv2: virtualenv)` - * @param {Partial} info - * @param {Uri} [resource] - * @returns {string} - * @memberof InterpreterService - */ - public async getDisplayName(info: Partial, resource?: Uri): Promise { - // faster than calculating file has agian and again, only when deailing with cached items. - if (!info.cachedEntry && info.path && this.inMemoryCacheOfDisplayNames.has(info.path)) { - return this.inMemoryCacheOfDisplayNames.get(info.path)!; - } - const fileHash = (info.path ? await this.getInterepreterFileHash(info.path).catch(() => '') : '') || ''; - // Do not include dipslay name into hash as that changes. - const interpreterHash = `${fileHash}-${md5(JSON.stringify({ ...info, displayName: '' }))}`; - const store = this.persistentStateFactory.createGlobalPersistentState<{ hash: string; displayName: string }>( - `${info.path}.interpreter.displayName.v7`, - undefined, - EXPITY_DURATION - ); - if (store.value && store.value.hash === interpreterHash && store.value.displayName) { - this.inMemoryCacheOfDisplayNames.set(info.path!, store.value.displayName); - return store.value.displayName; - } - - const displayName = await this.buildInterpreterDisplayName(info, resource); - - // If dealing with cached entry, then do not store the display name in cache. - if (!info.cachedEntry) { - await store.updateValue({ displayName, hash: interpreterHash }); - this.inMemoryCacheOfDisplayNames.set(info.path!, displayName); - } - return displayName; - } - public async getInterpreterCache( - pythonPath: string - ): Promise> { - const fileHash = (pythonPath ? await this.getInterepreterFileHash(pythonPath).catch(() => '') : '') || ''; - const store = this.persistentStateFactory.createGlobalPersistentState<{ - fileHash: string; - info?: PythonEnvironment; - }>(`${pythonPath}.interpreter.Details.v7`, undefined, EXPITY_DURATION); - if (!store.value || store.value.fileHash !== fileHash) { - await store.updateValue({ fileHash }); - } - return store; + public async getInterpreterDetails(pythonPath: string): Promise { + return this.pyenvs.getInterpreterDetails(pythonPath); } - public _onConfigChanged = (resource?: Uri) => { - this.didChangeInterpreterConfigurationEmitter.fire(resource); - // Check if we actually changed our python path + + public async _onConfigChanged(resource?: Uri): Promise { + // Check if we actually changed our python path. + // Config service also updates itself on interpreter config change, + // so yielding control here to make sure it goes first and updates + // itself before we can query it. + await sleep(1); const pySettings = this.configService.getSettings(resource); + this.didChangeInterpreterConfigurationEmitter.fire(resource); if (this._pythonPathSetting === '' || this._pythonPathSetting !== pySettings.pythonPath) { this._pythonPathSetting = pySettings.pythonPath; - this.didChangeInterpreterEmitter.fire(); + this.didChangeInterpreterEmitter.fire(resource); + const workspaceFolder = this.serviceContainer + .get(IWorkspaceService) + .getWorkspaceFolder(resource); + reportActiveInterpreterChanged({ + path: pySettings.pythonPath, + resource: workspaceFolder, + }); + const workspaceKey = this.serviceContainer + .get(IWorkspaceService) + .getWorkspaceFolderIdentifier(resource); + this.activeInterpreterPaths.set(workspaceKey, { path: pySettings.pythonPath, workspaceFolder }); const interpreterDisplay = this.serviceContainer.get(IInterpreterDisplay); interpreterDisplay.refresh().catch((ex) => traceError('Python Extension: display.refresh', ex)); + await this.ensureEnvironmentContainsPython(this._pythonPathSetting, workspaceFolder); } - }; - protected async getInterepreterFileHash(pythonPath: string): Promise { - return this.hashProviderFactory - .create({ pythonPath }) - .then((provider) => provider.getInterpreterHash(pythonPath)); - } - protected async updateCachedInterpreterInformation(info: PythonEnvironment, resource: Resource): Promise { - const key = JSON.stringify(info); - if (this.updatedInterpreters.has(key)) { - return; - } - this.updatedInterpreters.add(key); - const state = await this.getInterpreterCache(info.path); - info.displayName = await this.getDisplayName(info, resource); - // Check if info has indeed changed. - if (state.value && state.value.info && JSON.stringify(info) === JSON.stringify(state.value.info)) { - return; - } - this.inMemoryCacheOfDisplayNames.delete(info.path); - await state.updateValue({ fileHash: state.value.fileHash, info }); - this.didChangeInterpreterInformation.fire(info); } - protected async buildInterpreterDisplayName(info: Partial, resource?: Uri): Promise { - const displayNameParts: string[] = ['Python']; - const envSuffixParts: string[] = []; - if (info.version) { - displayNameParts.push(`${info.version.major}.${info.version.minor}.${info.version.patch}`); - } - if (info.architecture) { - displayNameParts.push(getArchitectureDisplayName(info.architecture)); - } - if (!info.envName && info.path && info.envType && info.envType === EnvironmentType.Pipenv) { - // If we do not have the name of the environment, then try to get it again. - // This can happen based on the context (i.e. resource). - // I.e. we can determine if an environment is PipEnv only when giving it the right workspacec path (i.e. resource). - const virtualEnvMgr = this.serviceContainer.get(IVirtualEnvironmentManager); - info.envName = await virtualEnvMgr.getEnvironmentName(info.path, resource); - } - if (info.envName && info.envName.length > 0) { - envSuffixParts.push(`'${info.envName}'`); - } - if (info.envType) { - const interpreterHelper = this.serviceContainer.get(IInterpreterHelper); - const name = interpreterHelper.getInterpreterTypeDisplayName(info.envType); - if (name) { - envSuffixParts.push(name); - } - } - - const envSuffix = envSuffixParts.length === 0 ? '' : `(${envSuffixParts.join(': ')})`; - return `${displayNameParts.join(' ')} ${envSuffix}`.trim(); - } - private async collectInterpreterDetails(pythonPath: string, resource: Uri | undefined) { - const interpreterHelper = this.serviceContainer.get(IInterpreterHelper); - const virtualEnvManager = this.serviceContainer.get(IVirtualEnvironmentManager); - const [info, type] = await Promise.all([ - interpreterHelper.getInterpreterInformation(pythonPath), - virtualEnvManager.getEnvironmentType(pythonPath) - ]); - if (!info) { + @cache(-1, true) + private async ensureEnvironmentContainsPython(pythonPath: string, workspaceFolder: WorkspaceFolder | undefined) { + if (useEnvExtension()) { return; } - const details: Partial = { - ...(info as PythonEnvironment), - path: pythonPath, - envType: type - }; - const envName = - type === EnvironmentType.Unknown - ? undefined - : await virtualEnvManager.getEnvironmentName(pythonPath, resource); - const pthonInfo = { - ...(details as PythonEnvironment), - envName - }; - pthonInfo.displayName = await this.getDisplayName(pthonInfo, resource); - return pthonInfo; + const installer = this.serviceContainer.get(IInstaller); + if (!(await installer.isInstalled(Product.python))) { + // If Python is not installed into the environment, install it. + sendTelemetryEvent(EventName.ENVIRONMENT_WITHOUT_PYTHON_SELECTED); + const shell = this.serviceContainer.get(IApplicationShell); + const progressOptions: ProgressOptions = { + location: ProgressLocation.Window, + title: `[${Interpreters.installingPython}](command:${Commands.ViewOutput})`, + }; + traceLog('Conda envs without Python are known to not work well; fixing conda environment...'); + const promise = installer.install(Product.python, await this.getInterpreterDetails(pythonPath)); + shell.withProgress(progressOptions, () => promise); + promise + .then(async () => { + // Fetch interpreter details so the cache is updated to include the newly installed Python. + await this.getInterpreterDetails(pythonPath); + // Fire an event as the executable for the environment has changed. + this.didChangeInterpreterEmitter.fire(workspaceFolder?.uri); + reportActiveInterpreterChanged({ + path: pythonPath, + resource: workspaceFolder, + }); + }) + .ignoreErrors(); + } } } diff --git a/src/client/interpreter/interpreterVersion.ts b/src/client/interpreter/interpreterVersion.ts deleted file mode 100644 index 47d551a94122..000000000000 --- a/src/client/interpreter/interpreterVersion.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { inject, injectable } from 'inversify'; -import '../common/extensions'; -import * as internalPython from '../common/process/internal/python'; -import { IProcessServiceFactory } from '../common/process/types'; -import { getPythonVersion } from '../pythonEnvironments/info/pythonVersion'; -import { IInterpreterVersionService } from './contracts'; - -export const PIP_VERSION_REGEX = '\\d+\\.\\d+(\\.\\d+)?'; - -@injectable() -export class InterpreterVersionService implements IInterpreterVersionService { - constructor(@inject(IProcessServiceFactory) private readonly processServiceFactory: IProcessServiceFactory) {} - - public async getVersion(pythonPath: string, defaultValue: string): Promise { - const processService = await this.processServiceFactory.create(); - return getPythonVersion(pythonPath, defaultValue, (cmd, args) => - processService.exec(cmd, args, { mergeStdOutErr: true }) - ); - } - - public async getPipVersion(pythonPath: string): Promise { - const [args, parse] = internalPython.getModuleVersion('pip'); - const processService = await this.processServiceFactory.create(); - const output = await processService.exec(pythonPath, args, { mergeStdOutErr: true }); - const version = parse(output.stdout); - if (version.length > 0) { - // Here's a sample output: - // pip 9.0.1 from /Users/donjayamanne/anaconda3/lib/python3.6/site-packages (python 3.6). - const re = new RegExp(PIP_VERSION_REGEX, 'g'); - const matches = re.exec(version); - if (matches && matches.length > 0) { - return matches[0].trim(); - } - } - throw new Error(`Unable to determine pip version from output '${output.stdout}'`); - } -} diff --git a/src/client/interpreter/locators/types.ts b/src/client/interpreter/locators/types.ts index 0b4ec5afab4b..d67d8c1d7da0 100644 --- a/src/client/interpreter/locators/types.ts +++ b/src/client/interpreter/locators/types.ts @@ -14,52 +14,3 @@ export interface IPipEnvServiceHelper { getPipEnvInfo(pythonPath: string): Promise<{ workspaceFolder: Uri; envName: string } | undefined>; trackWorkspaceFolder(pythonPath: string, workspaceFolder: Uri): Promise; } - -/** - * Factory to create a hash provider. - * Getting the hash of an interpreter can vary based on the type of the interpreter. - * - * @export - * @interface IInterpreterHashProviderFactory - */ -export const IInterpreterHashProviderFactory = Symbol('IInterpreterHashProviderFactory'); -export interface IInterpreterHashProviderFactory { - create(options: { pythonPath: string } | { resource: Uri }): Promise; -} - -/** - * Provides the ability to get the has of a given interpreter. - * - * @export - * @interface IInterpreterHashProvider - */ -export interface IInterpreterHashProvider { - /** - * Gets the hash of a given Python Interpreter. - * (hash is calculated based on last modified timestamp of executable) - * - * @param {string} pythonPath - * @returns {Promise} - * @memberof IInterpreterHashProvider - */ - getInterpreterHash(pythonPath: string): Promise; -} - -export interface IWindowsStoreInterpreter { - /** - * Whether this is a Windows Store/App Interpreter. - * - * @param {string} pythonPath - * @returns {boolean} - * @memberof WindowsStoreInterpreter - */ - isWindowsStoreInterpreter(pythonPath: string): Promise; - /** - * Whether this is a python executable in a windows app store folder that is internal and can be hidden from users. - * - * @param {string} pythonPath - * @returns {boolean} - * @memberof IInterpreterHelper - */ - isHiddenInterpreter(pythonPath: string): boolean; -} diff --git a/src/client/interpreter/serviceRegistry.ts b/src/client/interpreter/serviceRegistry.ts index 3ba92d220e1a..f54f8e5368fe 100644 --- a/src/client/interpreter/serviceRegistry.ts +++ b/src/client/interpreter/serviceRegistry.ts @@ -5,187 +5,119 @@ import { IExtensionActivationService, IExtensionSingleActivationService } from '../activation/types'; import { IServiceManager } from '../ioc/types'; -import { PreWarmActivatedEnvironmentVariables } from './activation/preWarmVariables'; import { EnvironmentActivationService } from './activation/service'; -import { TerminalEnvironmentActivationService } from './activation/terminalEnvironmentActivationService'; import { IEnvironmentActivationService } from './activation/types'; import { InterpreterAutoSelectionService } from './autoSelection/index'; -import { InterpreterEvaluation } from './autoSelection/interpreterSecurity/interpreterEvaluation'; -import { InterpreterSecurityService } from './autoSelection/interpreterSecurity/interpreterSecurityService'; -import { InterpreterSecurityStorage } from './autoSelection/interpreterSecurity/interpreterSecurityStorage'; -import { InterpreterAutoSeletionProxyService } from './autoSelection/proxy'; -import { CachedInterpretersAutoSelectionRule } from './autoSelection/rules/cached'; -import { CurrentPathInterpretersAutoSelectionRule } from './autoSelection/rules/currentPath'; -import { SettingsInterpretersAutoSelectionRule } from './autoSelection/rules/settings'; -import { SystemWideInterpretersAutoSelectionRule } from './autoSelection/rules/system'; -import { WindowsRegistryInterpretersAutoSelectionRule } from './autoSelection/rules/winRegistry'; -import { WorkspaceVirtualEnvInterpretersAutoSelectionRule } from './autoSelection/rules/workspaceEnv'; -import { - AutoSelectionRule, - IInterpreterAutoSelectionRule, - IInterpreterAutoSelectionService, - IInterpreterAutoSeletionProxyService, - IInterpreterEvaluation, - IInterpreterSecurityService, - IInterpreterSecurityStorage -} from './autoSelection/types'; -import { InterpreterComparer } from './configuration/interpreterComparer'; +import { InterpreterAutoSelectionProxyService } from './autoSelection/proxy'; +import { IInterpreterAutoSelectionService, IInterpreterAutoSelectionProxyService } from './autoSelection/types'; +import { EnvironmentTypeComparer } from './configuration/environmentTypeComparer'; +import { InstallPythonCommand } from './configuration/interpreterSelector/commands/installPython'; +import { InstallPythonViaTerminal } from './configuration/interpreterSelector/commands/installPython/installPythonViaTerminal'; import { ResetInterpreterCommand } from './configuration/interpreterSelector/commands/resetInterpreter'; import { SetInterpreterCommand } from './configuration/interpreterSelector/commands/setInterpreter'; -import { SetShebangInterpreterCommand } from './configuration/interpreterSelector/commands/setShebangInterpreter'; import { InterpreterSelector } from './configuration/interpreterSelector/interpreterSelector'; +import { RecommendedEnvironmentService } from './configuration/recommededEnvironmentService'; import { PythonPathUpdaterService } from './configuration/pythonPathUpdaterService'; import { PythonPathUpdaterServiceFactory } from './configuration/pythonPathUpdaterServiceFactory'; import { IInterpreterComparer, + IInterpreterQuickPick, IInterpreterSelector, + IRecommendedEnvironmentService, IPythonPathUpdaterServiceFactory, - IPythonPathUpdaterServiceManager + IPythonPathUpdaterServiceManager, } from './configuration/types'; -import { - IInterpreterDisplay, - IInterpreterHelper, - IInterpreterLocatorProgressHandler, - IInterpreterService, - IInterpreterStatusbarVisibilityFilter, - IInterpreterVersionService, - IShebangCodeLensProvider -} from './contracts'; -import { AlwaysDisplayStatusBar, InterpreterDisplay } from './display'; -import { InterpreterSelectionTip } from './display/interpreterSelectionTip'; -import { InterpreterLocatorProgressStatubarHandler } from './display/progressDisplay'; -import { ShebangCodeLensProvider } from './display/shebangCodeLensProvider'; +import { IActivatedEnvironmentLaunch, IInterpreterDisplay, IInterpreterHelper, IInterpreterService } from './contracts'; +import { InterpreterDisplay } from './display'; +import { InterpreterLocatorProgressStatusBarHandler } from './display/progressDisplay'; import { InterpreterHelper } from './helpers'; +import { InterpreterPathCommand } from './interpreterPathCommand'; import { InterpreterService } from './interpreterService'; -import { InterpreterVersionService } from './interpreterVersion'; +import { ActivatedEnvironmentLaunch } from './virtualEnvs/activatedEnvLaunch'; import { CondaInheritEnvPrompt } from './virtualEnvs/condaInheritEnvPrompt'; -import { VirtualEnvironmentManager } from './virtualEnvs/index'; -import { IVirtualEnvironmentManager } from './virtualEnvs/types'; import { VirtualEnvironmentPrompt } from './virtualEnvs/virtualEnvPrompt'; /** * Register all the new types inside this method. - * This method is created for testing purposes. Registers all interpreter types except `IInterpreterAutoSeletionProxyService`, `IEnvironmentActivationService`. + * This method is created for testing purposes. Registers all interpreter types except `IInterpreterAutoSelectionProxyService`, `IEnvironmentActivationService`. * See use case in `src\test\serviceRegistry.ts` for details * @param serviceManager */ -// tslint:disable-next-line: max-func-body-length -export function registerInterpreterTypes(serviceManager: IServiceManager) { + +export function registerInterpreterTypes(serviceManager: IServiceManager): void { serviceManager.addSingleton( IExtensionSingleActivationService, - InterpreterSecurityStorage + InstallPythonCommand, ); serviceManager.addSingleton( IExtensionSingleActivationService, - SetInterpreterCommand + InstallPythonViaTerminal, ); serviceManager.addSingleton( IExtensionSingleActivationService, - ResetInterpreterCommand + SetInterpreterCommand, ); serviceManager.addSingleton( IExtensionSingleActivationService, - SetShebangInterpreterCommand + ResetInterpreterCommand, ); - serviceManager.addSingleton(IInterpreterEvaluation, InterpreterEvaluation); - serviceManager.addSingleton(IInterpreterSecurityStorage, InterpreterSecurityStorage); - serviceManager.addSingleton(IInterpreterSecurityService, InterpreterSecurityService); - - serviceManager.addSingleton(IVirtualEnvironmentManager, VirtualEnvironmentManager); - serviceManager.addSingleton(IExtensionActivationService, VirtualEnvironmentPrompt); - serviceManager.addSingleton( - IExtensionSingleActivationService, - InterpreterSelectionTip + serviceManager.addSingleton( + IRecommendedEnvironmentService, + RecommendedEnvironmentService, ); + serviceManager.addBinding(IRecommendedEnvironmentService, IExtensionActivationService); + serviceManager.addSingleton(IInterpreterQuickPick, SetInterpreterCommand); - serviceManager.addSingleton(IInterpreterVersionService, InterpreterVersionService); + serviceManager.addSingleton(IExtensionActivationService, VirtualEnvironmentPrompt); serviceManager.addSingleton(IInterpreterService, InterpreterService); serviceManager.addSingleton(IInterpreterDisplay, InterpreterDisplay); + serviceManager.addBinding(IInterpreterDisplay, IExtensionSingleActivationService); serviceManager.addSingleton( IPythonPathUpdaterServiceFactory, - PythonPathUpdaterServiceFactory + PythonPathUpdaterServiceFactory, ); serviceManager.addSingleton( IPythonPathUpdaterServiceManager, - PythonPathUpdaterService + PythonPathUpdaterService, ); serviceManager.addSingleton(IInterpreterSelector, InterpreterSelector); - serviceManager.addSingleton(IShebangCodeLensProvider, ShebangCodeLensProvider); serviceManager.addSingleton(IInterpreterHelper, InterpreterHelper); - serviceManager.addSingleton(IInterpreterComparer, InterpreterComparer); + serviceManager.addSingleton(IInterpreterComparer, EnvironmentTypeComparer); - serviceManager.addSingleton( - IInterpreterLocatorProgressHandler, - InterpreterLocatorProgressStatubarHandler + serviceManager.addSingleton( + IExtensionSingleActivationService, + InterpreterLocatorProgressStatusBarHandler, ); - serviceManager.addSingleton( - IInterpreterAutoSelectionRule, - CurrentPathInterpretersAutoSelectionRule, - AutoSelectionRule.currentPath - ); - serviceManager.addSingleton( - IInterpreterAutoSelectionRule, - SystemWideInterpretersAutoSelectionRule, - AutoSelectionRule.systemWide - ); - serviceManager.addSingleton( - IInterpreterAutoSelectionRule, - WindowsRegistryInterpretersAutoSelectionRule, - AutoSelectionRule.windowsRegistry - ); - serviceManager.addSingleton( - IInterpreterAutoSelectionRule, - WorkspaceVirtualEnvInterpretersAutoSelectionRule, - AutoSelectionRule.workspaceVirtualEnvs - ); - serviceManager.addSingleton( - IInterpreterAutoSelectionRule, - CachedInterpretersAutoSelectionRule, - AutoSelectionRule.cachedInterpreters - ); - serviceManager.addSingleton( - IInterpreterAutoSelectionRule, - SettingsInterpretersAutoSelectionRule, - AutoSelectionRule.settings - ); serviceManager.addSingleton( IInterpreterAutoSelectionService, - InterpreterAutoSelectionService + InterpreterAutoSelectionService, ); serviceManager.addSingleton(IExtensionActivationService, CondaInheritEnvPrompt); - - serviceManager.addSingleton( - IExtensionSingleActivationService, - PreWarmActivatedEnvironmentVariables - ); - serviceManager.addSingleton( - IInterpreterStatusbarVisibilityFilter, - AlwaysDisplayStatusBar - ); + serviceManager.addSingleton(IActivatedEnvironmentLaunch, ActivatedEnvironmentLaunch); } -export function registerTypes(serviceManager: IServiceManager) { +export function registerTypes(serviceManager: IServiceManager): void { registerInterpreterTypes(serviceManager); - serviceManager.addSingleton( - IInterpreterAutoSeletionProxyService, - InterpreterAutoSeletionProxyService + serviceManager.addSingleton( + IInterpreterAutoSelectionProxyService, + InterpreterAutoSelectionProxyService, ); serviceManager.addSingleton( EnvironmentActivationService, - EnvironmentActivationService - ); - serviceManager.addSingleton( - TerminalEnvironmentActivationService, - TerminalEnvironmentActivationService + EnvironmentActivationService, ); serviceManager.addSingleton( IEnvironmentActivationService, - EnvironmentActivationService + EnvironmentActivationService, + ); + serviceManager.addSingleton( + IExtensionSingleActivationService, + InterpreterPathCommand, ); } diff --git a/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts b/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts new file mode 100644 index 000000000000..6b4334e13100 --- /dev/null +++ b/src/client/interpreter/virtualEnvs/activatedEnvLaunch.ts @@ -0,0 +1,162 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable } from 'inversify'; +import { ConfigurationTarget } from 'vscode'; +import * as path from 'path'; +import { IApplicationShell, IWorkspaceService } from '../../common/application/types'; +import { IProcessServiceFactory } from '../../common/process/types'; +import { sleep } from '../../common/utils/async'; +import { cache } from '../../common/utils/decorators'; +import { Common, Interpreters } from '../../common/utils/localize'; +import { traceError, traceLog, traceVerbose, traceWarn } from '../../logging'; +import { Conda } from '../../pythonEnvironments/common/environmentManagers/conda'; +import { sendTelemetryEvent } from '../../telemetry'; +import { EventName } from '../../telemetry/constants'; +import { IPythonPathUpdaterServiceManager } from '../configuration/types'; +import { IActivatedEnvironmentLaunch, IInterpreterService } from '../contracts'; + +@injectable() +export class ActivatedEnvironmentLaunch implements IActivatedEnvironmentLaunch { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; + + private inMemorySelection: string | undefined; + + constructor( + @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, + @inject(IApplicationShell) private readonly appShell: IApplicationShell, + @inject(IPythonPathUpdaterServiceManager) + private readonly pythonPathUpdaterService: IPythonPathUpdaterServiceManager, + @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, + @inject(IProcessServiceFactory) private readonly processServiceFactory: IProcessServiceFactory, + public wasSelected: boolean = false, + ) {} + + @cache(-1, true) + public async _promptIfApplicable(): Promise { + const baseCondaPrefix = getPrefixOfActivatedCondaEnv(); + if (!baseCondaPrefix) { + return; + } + const info = await this.interpreterService.getInterpreterDetails(baseCondaPrefix); + if (info?.envName !== 'base') { + // Only show prompt for base conda environments, as we need to check config for such envs which can be slow. + return; + } + const conda = await Conda.getConda(); + if (!conda) { + traceWarn('Conda not found even though activated environment vars are set'); + return; + } + const service = await this.processServiceFactory.create(); + const autoActivateBaseConfig = await service + .shellExec(`${conda.shellCommand} config --get auto_activate_base`) + .catch((ex) => { + traceError(ex); + return { stdout: '' }; + }); + if (autoActivateBaseConfig.stdout.trim().toLowerCase().endsWith('false')) { + await this.promptAndUpdate(baseCondaPrefix); + } + } + + private async promptAndUpdate(prefix: string) { + this.wasSelected = true; + const prompts = [Common.bannerLabelYes, Common.bannerLabelNo]; + const telemetrySelections: ['Yes', 'No'] = ['Yes', 'No']; + const selection = await this.appShell.showInformationMessage(Interpreters.activatedCondaEnvLaunch, ...prompts); + sendTelemetryEvent(EventName.ACTIVATED_CONDA_ENV_LAUNCH, undefined, { + selection: selection ? telemetrySelections[prompts.indexOf(selection)] : undefined, + }); + if (!selection) { + return; + } + if (selection === prompts[0]) { + await this.setInterpeterInStorage(prefix); + } + } + + public async selectIfLaunchedViaActivatedEnv(doNotBlockOnSelection = false): Promise { + if (this.wasSelected) { + return this.inMemorySelection; + } + return this._selectIfLaunchedViaActivatedEnv(doNotBlockOnSelection); + } + + @cache(-1, true) + private async _selectIfLaunchedViaActivatedEnv(doNotBlockOnSelection = false): Promise { + if (process.env.VSCODE_CLI !== '1') { + // We only want to select the interpreter if VS Code was launched from the command line. + traceLog("Skipping ActivatedEnv Detection: process.env.VSCODE_CLI !== '1'"); + return undefined; + } + traceVerbose('VS Code was not launched from the command line'); + const prefix = await this.getPrefixOfSelectedActivatedEnv(); + if (!prefix) { + this._promptIfApplicable().ignoreErrors(); + return undefined; + } + this.wasSelected = true; + this.inMemorySelection = prefix; + traceLog( + `VS Code was launched from an activated environment: '${path.basename( + prefix, + )}', selecting it as the interpreter for workspace.`, + ); + if (doNotBlockOnSelection) { + this.setInterpeterInStorage(prefix).ignoreErrors(); + } else { + await this.setInterpeterInStorage(prefix); + await sleep(1); // Yield control so config service can update itself. + } + this.inMemorySelection = undefined; // Once we have set the prefix in storage, clear the in memory selection. + return prefix; + } + + private async setInterpeterInStorage(prefix: string) { + const { workspaceFolders } = this.workspaceService; + if (!workspaceFolders || workspaceFolders.length === 0) { + await this.pythonPathUpdaterService.updatePythonPath(prefix, ConfigurationTarget.Global, 'load'); + } else { + await this.pythonPathUpdaterService.updatePythonPath( + prefix, + ConfigurationTarget.WorkspaceFolder, + 'load', + workspaceFolders[0].uri, + ); + } + } + + private async getPrefixOfSelectedActivatedEnv(): Promise { + const virtualEnvVar = process.env.VIRTUAL_ENV; + if (virtualEnvVar !== undefined && virtualEnvVar.length > 0) { + return virtualEnvVar; + } + const condaPrefixVar = getPrefixOfActivatedCondaEnv(); + if (!condaPrefixVar) { + return undefined; + } + const info = await this.interpreterService.getInterpreterDetails(condaPrefixVar); + if (info?.envName !== 'base') { + return condaPrefixVar; + } + // Ignoring base conda environments, as they could be automatically set by conda. + if (process.env.CONDA_AUTO_ACTIVATE_BASE !== undefined) { + if (process.env.CONDA_AUTO_ACTIVATE_BASE.toLowerCase() === 'false') { + return condaPrefixVar; + } + } + return undefined; + } +} + +function getPrefixOfActivatedCondaEnv() { + const condaPrefixVar = process.env.CONDA_PREFIX; + if (condaPrefixVar && condaPrefixVar.length > 0) { + const condaShlvl = process.env.CONDA_SHLVL; + if (condaShlvl !== undefined && condaShlvl.length > 0 && condaShlvl > '0') { + return condaPrefixVar; + } + } + return undefined; +} diff --git a/src/client/interpreter/virtualEnvs/condaInheritEnvPrompt.ts b/src/client/interpreter/virtualEnvs/condaInheritEnvPrompt.ts index a3fdac906e39..6b5295724449 100644 --- a/src/client/interpreter/virtualEnvs/condaInheritEnvPrompt.ts +++ b/src/client/interpreter/virtualEnvs/condaInheritEnvPrompt.ts @@ -1,14 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { inject, injectable, optional } from 'inversify'; +import { inject, injectable } from 'inversify'; import { ConfigurationTarget, Uri } from 'vscode'; import { IExtensionActivationService } from '../../activation/types'; -import { IApplicationShell, IWorkspaceService } from '../../common/application/types'; -import { traceDecorators, traceError } from '../../common/logger'; +import { IApplicationEnvironment, IApplicationShell, IWorkspaceService } from '../../common/application/types'; import { IPlatformService } from '../../common/platform/types'; -import { IBrowserService, IPersistentStateFactory } from '../../common/types'; +import { IPersistentStateFactory } from '../../common/types'; import { Common, Interpreters } from '../../common/utils/localize'; +import { traceDecoratorError, traceError } from '../../logging'; import { EnvironmentType } from '../../pythonEnvironments/info'; import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; @@ -18,21 +18,22 @@ export const condaInheritEnvPromptKey = 'CONDA_INHERIT_ENV_PROMPT_KEY'; @injectable() export class CondaInheritEnvPrompt implements IExtensionActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; constructor( @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, - @inject(IBrowserService) private browserService: IBrowserService, @inject(IApplicationShell) private readonly appShell: IApplicationShell, @inject(IPersistentStateFactory) private readonly persistentStateFactory: IPersistentStateFactory, @inject(IPlatformService) private readonly platformService: IPlatformService, - @optional() public hasPromptBeenShownInCurrentSession: boolean = false + @inject(IApplicationEnvironment) private readonly appEnvironment: IApplicationEnvironment, + public hasPromptBeenShownInCurrentSession: boolean = false, ) {} public async activate(resource: Uri): Promise { this.initializeInBackground(resource).ignoreErrors(); } - @traceDecorators.error('Failed to intialize conda inherit env prompt') + @traceDecoratorError('Failed to intialize conda inherit env prompt') public async initializeInBackground(resource: Uri): Promise { const show = await this.shouldShowPrompt(resource); if (!show) { @@ -41,20 +42,20 @@ export class CondaInheritEnvPrompt implements IExtensionActivationService { await this.promptAndUpdate(); } - @traceDecorators.error('Failed to display conda inherit env prompt') + @traceDecoratorError('Failed to display conda inherit env prompt') public async promptAndUpdate() { const notificationPromptEnabled = this.persistentStateFactory.createGlobalPersistentState( condaInheritEnvPromptKey, - true + true, ); if (!notificationPromptEnabled.value) { return; } - const prompts = [Common.bannerLabelYes(), Common.bannerLabelNo(), Common.moreInfo()]; - const telemetrySelections: ['Yes', 'No', 'More Info'] = ['Yes', 'No', 'More Info']; - const selection = await this.appShell.showInformationMessage(Interpreters.condaInheritEnvMessage(), ...prompts); + const prompts = [Common.allow, Common.close]; + const telemetrySelections: ['Allow', 'Close'] = ['Allow', 'Close']; + const selection = await this.appShell.showInformationMessage(Interpreters.condaInheritEnvMessage, ...prompts); sendTelemetryEvent(EventName.CONDA_INHERIT_ENV_PROMPT, undefined, { - selection: selection ? telemetrySelections[prompts.indexOf(selection)] : undefined + selection: selection ? telemetrySelections[prompts.indexOf(selection)] : undefined, }); if (!selection) { return; @@ -65,16 +66,19 @@ export class CondaInheritEnvPrompt implements IExtensionActivationService { .update('integrated.inheritEnv', false, ConfigurationTarget.Global); } else if (selection === prompts[1]) { await notificationPromptEnabled.updateValue(false); - } else if (selection === prompts[2]) { - this.browserService.launch('https://aka.ms/AA66i8f'); } } - @traceDecorators.error('Failed to check whether to display prompt for conda inherit env setting') + @traceDecoratorError('Failed to check whether to display prompt for conda inherit env setting') public async shouldShowPrompt(resource: Uri): Promise { if (this.hasPromptBeenShownInCurrentSession) { return false; } + if (this.appEnvironment.remoteName) { + // `terminal.integrated.inheritEnv` is only applicable user scope, so won't apply + // in remote scenarios: https://github.com/microsoft/vscode/issues/147421 + return false; + } if (this.platformService.isWindows) { return false; } @@ -87,7 +91,7 @@ export class CondaInheritEnvPrompt implements IExtensionActivationService { .inspect('integrated.inheritEnv'); if (!setting) { traceError( - 'WorkspaceConfiguration.inspect returns `undefined` for setting `terminal.integrated.inheritEnv`' + 'WorkspaceConfiguration.inspect returns `undefined` for setting `terminal.integrated.inheritEnv`', ); return false; } diff --git a/src/client/interpreter/virtualEnvs/index.ts b/src/client/interpreter/virtualEnvs/index.ts deleted file mode 100644 index 0886b1df99a4..000000000000 --- a/src/client/interpreter/virtualEnvs/index.ts +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { Uri } from 'vscode'; -import { IWorkspaceService } from '../../common/application/types'; -import { IFileSystem, IPlatformService } from '../../common/platform/types'; -import { IProcessServiceFactory } from '../../common/process/types'; -import { getAllScripts as getNonWindowsScripts } from '../../common/terminal/environmentActivationProviders/bash'; -import { getAllScripts as getWindowsScripts } from '../../common/terminal/environmentActivationProviders/commandPrompt'; -import { ICurrentProcess, IPathUtils } from '../../common/types'; -import { IServiceContainer } from '../../ioc/types'; -import * as globalenvs from '../../pythonEnvironments/discovery/globalenv'; -import * as subenvs from '../../pythonEnvironments/discovery/subenv'; -import { EnvironmentType } from '../../pythonEnvironments/info'; -import { IInterpreterLocatorService, IPipEnvService, PIPENV_SERVICE } from '../contracts'; -import { IVirtualEnvironmentManager } from './types'; - -@injectable() -export class VirtualEnvironmentManager implements IVirtualEnvironmentManager { - private processServiceFactory: IProcessServiceFactory; - private pipEnvService: IPipEnvService; - private fs: IFileSystem; - private workspaceService: IWorkspaceService; - constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) { - this.processServiceFactory = serviceContainer.get(IProcessServiceFactory); - this.fs = serviceContainer.get(IFileSystem); - this.pipEnvService = serviceContainer.get( - IInterpreterLocatorService, - PIPENV_SERVICE - ) as IPipEnvService; - this.workspaceService = serviceContainer.get(IWorkspaceService); - } - - public async getEnvironmentName(pythonPath: string, resource?: Uri): Promise { - const finders = subenvs.getNameFinders( - await this.getWorkspaceRoot(resource), - path.dirname, - path.basename, - // We use a closure on "this". - (d: string, p: string) => this.pipEnvService.isRelatedPipEnvironment(d, p) - ); - return (await subenvs.getName(pythonPath, finders)) || ''; - } - - public async getEnvironmentType(pythonPath: string, resource?: Uri): Promise { - const pathUtils = this.serviceContainer.get(IPathUtils); - const plat = this.serviceContainer.get(IPlatformService); - const candidates = plat.isWindows ? getWindowsScripts(path.join) : getNonWindowsScripts(); - const finders = subenvs.getTypeFinders( - pathUtils.home, - candidates, - path.sep, - path.join, - path.dirname, - () => this.getWorkspaceRoot(resource), - (d: string, p: string) => this.pipEnvService.isRelatedPipEnvironment(d, p), - (n: string) => { - const curProc = this.serviceContainer.get(ICurrentProcess); - return curProc.env[n]; - }, - (n: string) => this.fs.fileExists(n), - async (c: string, a: string[]) => { - const processService = await this.processServiceFactory.create(resource); - return processService.exec(c, a); - } - ); - return (await subenvs.getType(pythonPath, finders)) || EnvironmentType.Unknown; - } - - public async isVenvEnvironment(pythonPath: string) { - const find = subenvs.getVenvTypeFinder( - path.dirname, - path.join, - // We use a closure on "this". - (n: string) => this.fs.fileExists(n) - ); - return (await find(pythonPath)) === EnvironmentType.Venv; - } - - public async isPyEnvEnvironment(pythonPath: string, resource?: Uri) { - const pathUtils = this.serviceContainer.get(IPathUtils); - const find = globalenvs.getPyenvTypeFinder( - pathUtils.home, - path.sep, - path.join, - (n: string) => { - const curProc = this.serviceContainer.get(ICurrentProcess); - return curProc.env[n]; - }, - async (c: string, a: string[]) => { - const processService = await this.processServiceFactory.create(resource); - return processService.exec(c, a); - } - ); - return (await find(pythonPath)) === EnvironmentType.Pyenv; - } - - public async isPipEnvironment(pythonPath: string, resource?: Uri) { - const find = subenvs.getPipenvTypeFinder( - () => this.getWorkspaceRoot(resource), - // We use a closure on "this". - (d: string, p: string) => this.pipEnvService.isRelatedPipEnvironment(d, p) - ); - return (await find(pythonPath)) === EnvironmentType.Pipenv; - } - - public async getPyEnvRoot(resource?: Uri): Promise { - const pathUtils = this.serviceContainer.get(IPathUtils); - const find = globalenvs.getPyenvRootFinder( - pathUtils.home, - path.join, - (n: string) => { - const curProc = this.serviceContainer.get(ICurrentProcess); - return curProc.env[n]; - }, - async (c: string, a: string[]) => { - const processService = await this.processServiceFactory.create(resource); - return processService.exec(c, a); - } - ); - return find(); - } - - public async isVirtualEnvironment(pythonPath: string) { - const plat = this.serviceContainer.get(IPlatformService); - const candidates = plat.isWindows ? getWindowsScripts(path.join) : getNonWindowsScripts(); - const find = subenvs.getVirtualenvTypeFinder( - candidates, - path.dirname, - path.join, - // We use a closure on "this". - (n: string) => this.fs.fileExists(n) - ); - return (await find(pythonPath)) === EnvironmentType.VirtualEnv; - } - - private async getWorkspaceRoot(resource?: Uri): Promise { - const defaultWorkspaceUri = this.workspaceService.hasWorkspaceFolders - ? this.workspaceService.workspaceFolders![0].uri - : undefined; - const workspaceFolder = resource ? this.workspaceService.getWorkspaceFolder(resource) : undefined; - const uri = workspaceFolder ? workspaceFolder.uri : defaultWorkspaceUri; - return uri ? uri.fsPath : undefined; - } -} diff --git a/src/client/interpreter/virtualEnvs/types.ts b/src/client/interpreter/virtualEnvs/types.ts deleted file mode 100644 index 3781e4926d89..000000000000 --- a/src/client/interpreter/virtualEnvs/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { Uri } from 'vscode'; -import { EnvironmentType } from '../../pythonEnvironments/info'; -export const IVirtualEnvironmentManager = Symbol('VirtualEnvironmentManager'); -export interface IVirtualEnvironmentManager { - getEnvironmentName(pythonPath: string, resource?: Uri): Promise; - getEnvironmentType(pythonPath: string, resource?: Uri): Promise; - getPyEnvRoot(resource?: Uri): Promise; -} diff --git a/src/client/interpreter/virtualEnvs/virtualEnvPrompt.ts b/src/client/interpreter/virtualEnvs/virtualEnvPrompt.ts index c1056cadab6d..7ed18c0e8b2a 100644 --- a/src/client/interpreter/virtualEnvs/virtualEnvPrompt.ts +++ b/src/client/interpreter/virtualEnvs/virtualEnvPrompt.ts @@ -1,79 +1,75 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { inject, injectable, named } from 'inversify'; +import { inject, injectable } from 'inversify'; import { ConfigurationTarget, Disposable, Uri } from 'vscode'; import { IExtensionActivationService } from '../../activation/types'; import { IApplicationShell } from '../../common/application/types'; -import { traceDecorators } from '../../common/logger'; import { IDisposableRegistry, IPersistentStateFactory } from '../../common/types'; -import { sleep } from '../../common/utils/async'; import { Common, Interpreters } from '../../common/utils/localize'; +import { traceDecoratorError, traceVerbose } from '../../logging'; +import { isCreatingEnvironment } from '../../pythonEnvironments/creation/createEnvApi'; import { PythonEnvironment } from '../../pythonEnvironments/info'; import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; import { IPythonPathUpdaterServiceManager } from '../configuration/types'; -import { - IInterpreterHelper, - IInterpreterLocatorService, - IInterpreterWatcherBuilder, - WORKSPACE_VIRTUAL_ENV_SERVICE -} from '../contracts'; +import { IComponentAdapter, IInterpreterHelper, IInterpreterService } from '../contracts'; const doNotDisplayPromptStateKey = 'MESSAGE_KEY_FOR_VIRTUAL_ENV'; @injectable() export class VirtualEnvironmentPrompt implements IExtensionActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; + constructor( - @inject(IInterpreterWatcherBuilder) private readonly builder: IInterpreterWatcherBuilder, @inject(IPersistentStateFactory) private readonly persistentStateFactory: IPersistentStateFactory, @inject(IInterpreterHelper) private readonly helper: IInterpreterHelper, @inject(IPythonPathUpdaterServiceManager) private readonly pythonPathUpdaterService: IPythonPathUpdaterServiceManager, - @inject(IInterpreterLocatorService) - @named(WORKSPACE_VIRTUAL_ENV_SERVICE) - private readonly locator: IInterpreterLocatorService, @inject(IDisposableRegistry) private readonly disposableRegistry: Disposable[], - @inject(IApplicationShell) private readonly appShell: IApplicationShell + @inject(IApplicationShell) private readonly appShell: IApplicationShell, + @inject(IComponentAdapter) private readonly pyenvs: IComponentAdapter, + @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, ) {} public async activate(resource: Uri): Promise { - const watcher = await this.builder.getWorkspaceVirtualEnvInterpreterWatcher(resource); - watcher.onDidCreate( - () => { - this.handleNewEnvironment(resource).ignoreErrors(); - }, - this, - this.disposableRegistry - ); + const disposable = this.pyenvs.onDidCreate(resource, () => this.handleNewEnvironment(resource)); + this.disposableRegistry.push(disposable); } - @traceDecorators.error('Error in event handler for detection of new environment') + @traceDecoratorError('Error in event handler for detection of new environment') protected async handleNewEnvironment(resource: Uri): Promise { - // Wait for a while, to ensure environment gets created and is accessible (as this is slow on Windows) - await sleep(1000); - const interpreters = await this.locator.getInterpreters(resource); - const interpreter = this.helper.getBestInterpreter(interpreters); + if (isCreatingEnvironment()) { + return; + } + const interpreters = await this.pyenvs.getWorkspaceVirtualEnvInterpreters(resource); + const interpreter = + Array.isArray(interpreters) && interpreters.length > 0 + ? this.helper.getBestInterpreter(interpreters) + : undefined; if (!interpreter) { return; } + const currentInterpreter = await this.interpreterService.getActiveInterpreter(resource); + if (currentInterpreter?.id === interpreter.id) { + traceVerbose('New environment has already been selected'); + return; + } await this.notifyUser(interpreter, resource); } + protected async notifyUser(interpreter: PythonEnvironment, resource: Uri): Promise { const notificationPromptEnabled = this.persistentStateFactory.createWorkspacePersistentState( doNotDisplayPromptStateKey, - true + true, ); if (!notificationPromptEnabled.value) { return; } - const prompts = [Common.bannerLabelYes(), Common.bannerLabelNo(), Common.doNotShowAgain()]; + const prompts = [Common.bannerLabelYes, Common.bannerLabelNo, Common.doNotShowAgain]; const telemetrySelections: ['Yes', 'No', 'Ignore'] = ['Yes', 'No', 'Ignore']; - const selection = await this.appShell.showInformationMessage( - Interpreters.environmentPromptMessage(), - ...prompts - ); + const selection = await this.appShell.showInformationMessage(Interpreters.environmentPromptMessage, ...prompts); sendTelemetryEvent(EventName.PYTHON_INTERPRETER_ACTIVATE_ENVIRONMENT_PROMPT, undefined, { - selection: selection ? telemetrySelections[prompts.indexOf(selection)] : undefined + selection: selection ? telemetrySelections[prompts.indexOf(selection)] : undefined, }); if (!selection) { return; @@ -83,7 +79,7 @@ export class VirtualEnvironmentPrompt implements IExtensionActivationService { interpreter.path, ConfigurationTarget.WorkspaceFolder, 'ui', - resource + resource, ); } else if (selection === prompts[2]) { await notificationPromptEnabled.updateValue(false); diff --git a/src/client/ioc/container.ts b/src/client/ioc/container.ts index 07db6f95bca2..0f1302061a67 100644 --- a/src/client/ioc/container.ts +++ b/src/client/ioc/container.ts @@ -3,7 +3,7 @@ import { EventEmitter } from 'events'; import { Container, decorate, injectable, interfaces } from 'inversify'; -import { traceWarning } from '../common/logger'; +import { traceWarn } from '../logging'; import { Abstract, IServiceContainer, Newable } from './types'; // This needs to be done once, hence placed in a common location. @@ -13,26 +13,29 @@ import { Abstract, IServiceContainer, Newable } from './types'; try { decorate(injectable(), EventEmitter); } catch (ex) { - traceWarning('Failed to decorate EventEmitter for DI (possibly already decorated by another Extension)', ex); + traceWarn('Failed to decorate EventEmitter for DI (possibly already decorated by another Extension)', ex); } @injectable() export class ServiceContainer implements IServiceContainer { constructor(private container: Container) {} + public get(serviceIdentifier: interfaces.ServiceIdentifier, name?: string | number | symbol): T { return name ? this.container.getNamed(serviceIdentifier, name) : this.container.get(serviceIdentifier); } + public getAll( serviceIdentifier: string | symbol | Newable | Abstract, - name?: string | number | symbol | undefined + name?: string | number | symbol | undefined, ): T[] { return name ? this.container.getAllNamed(serviceIdentifier, name) : this.container.getAll(serviceIdentifier); } + public tryGet( serviceIdentifier: interfaces.ServiceIdentifier, - name?: string | number | symbol | undefined + name?: string | number | symbol | undefined, ): T | undefined { try { return name @@ -41,5 +44,7 @@ export class ServiceContainer implements IServiceContainer { } catch { // This might happen after the container has been destroyed } + + return undefined; } } diff --git a/src/client/ioc/index.ts b/src/client/ioc/index.ts deleted file mode 100644 index 0ee4070d5ded..000000000000 --- a/src/client/ioc/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { IServiceContainer } from './types'; - -let container: IServiceContainer; -export function getServiceContainer() { - return container; -} -export function setServiceContainer(serviceContainer: IServiceContainer) { - container = serviceContainer; -} diff --git a/src/client/ioc/serviceManager.ts b/src/client/ioc/serviceManager.ts index 837308dff514..a575b25e8c3f 100644 --- a/src/client/ioc/serviceManager.ts +++ b/src/client/ioc/serviceManager.ts @@ -9,12 +9,14 @@ type identifier = string | symbol | Newable | Abstract; @injectable() export class ServiceManager implements IServiceManager { constructor(private container: Container) {} + public add( serviceIdentifier: identifier, - // tslint:disable-next-line:no-any + + // eslint-disable-next-line @typescript-eslint/no-explicit-any constructor: new (...args: any[]) => T, name?: string | number | symbol | undefined, - bindings?: symbol[] + bindings?: symbol[], ): void { if (name) { this.container.bind(serviceIdentifier).to(constructor).whenTargetNamed(name); @@ -28,9 +30,10 @@ export class ServiceManager implements IServiceManager { }); } } + public addFactory( factoryIdentifier: interfaces.ServiceIdentifier>, - factoryMethod: interfaces.FactoryCreator + factoryMethod: interfaces.FactoryCreator, ): void { this.container.bind>(factoryIdentifier).toFactory(factoryMethod); } @@ -41,10 +44,11 @@ export class ServiceManager implements IServiceManager { public addSingleton( serviceIdentifier: identifier, - // tslint:disable-next-line:no-any + + // eslint-disable-next-line @typescript-eslint/no-explicit-any constructor: new (...args: any[]) => T, name?: string | number | symbol | undefined, - bindings?: symbol[] + bindings?: symbol[], ): void { if (name) { this.container.bind(serviceIdentifier).to(constructor).inSingletonScope().whenTargetNamed(name); @@ -62,7 +66,7 @@ export class ServiceManager implements IServiceManager { public addSingletonInstance( serviceIdentifier: identifier, instance: T, - name?: string | number | symbol | undefined + name?: string | number | symbol | undefined, ): void { if (name) { this.container.bind(serviceIdentifier).toConstantValue(instance).whenTargetNamed(name); @@ -70,9 +74,11 @@ export class ServiceManager implements IServiceManager { this.container.bind(serviceIdentifier).toConstantValue(instance); } } + public get(serviceIdentifier: identifier, name?: string | number | symbol | undefined): T { return name ? this.container.getNamed(serviceIdentifier, name) : this.container.get(serviceIdentifier); } + public tryGet(serviceIdentifier: identifier, name?: string | number | symbol | undefined): T | undefined { try { return name @@ -81,7 +87,10 @@ export class ServiceManager implements IServiceManager { } catch { // This might happen after the container has been destroyed } + + return undefined; } + public getAll(serviceIdentifier: identifier, name?: string | number | symbol | undefined): T[] { return name ? this.container.getAllNamed(serviceIdentifier, name) @@ -91,7 +100,7 @@ export class ServiceManager implements IServiceManager { public rebind( serviceIdentifier: interfaces.ServiceIdentifier, constructor: ClassType, - name?: string | number | symbol + name?: string | number | symbol, ): void { if (name) { this.container.rebind(serviceIdentifier).to(constructor).whenTargetNamed(name); @@ -103,7 +112,7 @@ export class ServiceManager implements IServiceManager { public rebindSingleton( serviceIdentifier: interfaces.ServiceIdentifier, constructor: ClassType, - name?: string | number | symbol + name?: string | number | symbol, ): void { if (name) { this.container.rebind(serviceIdentifier).to(constructor).inSingletonScope().whenTargetNamed(name); @@ -115,7 +124,7 @@ export class ServiceManager implements IServiceManager { public rebindInstance( serviceIdentifier: interfaces.ServiceIdentifier, instance: T, - name?: string | number | symbol + name?: string | number | symbol, ): void { if (name) { this.container.rebind(serviceIdentifier).toConstantValue(instance).whenTargetNamed(name); @@ -124,7 +133,7 @@ export class ServiceManager implements IServiceManager { } } - public dispose() { + public dispose(): void { this.container.unbindAll(); this.container.unload(); } diff --git a/src/client/ioc/types.ts b/src/client/ioc/types.ts index 079fca02e5bd..0a18e44824ed 100644 --- a/src/client/ioc/types.ts +++ b/src/client/ioc/types.ts @@ -4,25 +4,19 @@ import { interfaces } from 'inversify'; import { IDisposable } from '../common/types'; -//tslint:disable:callable-types -// tslint:disable-next-line:interface-name export interface Newable { - // tslint:disable-next-line:no-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any new (...args: any[]): T; } -//tslint:enable:callable-types -// tslint:disable-next-line:interface-name export interface Abstract { prototype: T; } -//tslint:disable:callable-types export type ClassType = { - // tslint:disable-next-line:no-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any new (...args: any[]): T; }; -//tslint:enable:callable-types export const IServiceManager = Symbol('IServiceManager'); @@ -31,22 +25,22 @@ export interface IServiceManager extends IDisposable { serviceIdentifier: interfaces.ServiceIdentifier, constructor: ClassType, name?: string | number | symbol | undefined, - bindings?: symbol[] + bindings?: symbol[], ): void; addSingleton( serviceIdentifier: interfaces.ServiceIdentifier, constructor: ClassType, name?: string | number | symbol, - bindings?: symbol[] + bindings?: symbol[], ): void; addSingletonInstance( serviceIdentifier: interfaces.ServiceIdentifier, instance: T, - name?: string | number | symbol + name?: string | number | symbol, ): void; addFactory( factoryIdentifier: interfaces.ServiceIdentifier>, - factoryMethod: interfaces.FactoryCreator + factoryMethod: interfaces.FactoryCreator, ): void; addBinding(from: interfaces.ServiceIdentifier, to: interfaces.ServiceIdentifier): void; get(serviceIdentifier: interfaces.ServiceIdentifier, name?: string | number | symbol): T; @@ -55,17 +49,17 @@ export interface IServiceManager extends IDisposable { rebind( serviceIdentifier: interfaces.ServiceIdentifier, constructor: ClassType, - name?: string | number | symbol + name?: string | number | symbol, ): void; rebindSingleton( serviceIdentifier: interfaces.ServiceIdentifier, constructor: ClassType, - name?: string | number | symbol + name?: string | number | symbol, ): void; rebindInstance( serviceIdentifier: interfaces.ServiceIdentifier, instance: T, - name?: string | number | symbol + name?: string | number | symbol, ): void; } diff --git a/src/client/jupyter/jupyterExtensionDependencyManager.ts b/src/client/jupyter/jupyterExtensionDependencyManager.ts new file mode 100644 index 000000000000..defd5ea38241 --- /dev/null +++ b/src/client/jupyter/jupyterExtensionDependencyManager.ts @@ -0,0 +1,13 @@ +import { inject, injectable } from 'inversify'; +import { IJupyterExtensionDependencyManager } from '../common/application/types'; +import { JUPYTER_EXTENSION_ID } from '../common/constants'; +import { IExtensions } from '../common/types'; + +@injectable() +export class JupyterExtensionDependencyManager implements IJupyterExtensionDependencyManager { + constructor(@inject(IExtensions) private extensions: IExtensions) {} + + public get isJupyterExtensionInstalled(): boolean { + return this.extensions.getExtension(JUPYTER_EXTENSION_ID) !== undefined; + } +} diff --git a/src/client/jupyter/jupyterIntegration.ts b/src/client/jupyter/jupyterIntegration.ts new file mode 100644 index 000000000000..5584682f3b86 --- /dev/null +++ b/src/client/jupyter/jupyterIntegration.ts @@ -0,0 +1,306 @@ +/* eslint-disable comma-dangle */ + +/* eslint-disable implicit-arrow-linebreak, max-classes-per-file */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable, named } from 'inversify'; +import { dirname } from 'path'; +import { EventEmitter, Extension, Memento, Uri, workspace, Event } from 'vscode'; +import type { SemVer } from 'semver'; +import { IContextKeyManager, IWorkspaceService } from '../common/application/types'; +import { JUPYTER_EXTENSION_ID, PYLANCE_EXTENSION_ID } from '../common/constants'; +import { GLOBAL_MEMENTO, IExtensions, IMemento, Resource } from '../common/types'; +import { IEnvironmentActivationService } from '../interpreter/activation/types'; +import { + IInterpreterQuickPickItem, + IInterpreterSelector, + IRecommendedEnvironmentService, +} from '../interpreter/configuration/types'; +import { + ICondaService, + IInterpreterDisplay, + IInterpreterService, + IInterpreterStatusbarVisibilityFilter, +} from '../interpreter/contracts'; +import { PylanceApi } from '../activation/node/pylanceApi'; +import { ExtensionContextKey } from '../common/application/contextKeys'; +import { getDebugpyPath } from '../debugger/pythonDebugger'; +import type { Environment, EnvironmentPath, PythonExtension } from '../api/types'; +import { DisposableBase } from '../common/utils/resourceLifecycle'; + +type PythonApiForJupyterExtension = { + /** + * IEnvironmentActivationService + */ + getActivatedEnvironmentVariables( + resource: Resource, + interpreter: Environment, + allowExceptions?: boolean, + ): Promise; + getKnownSuggestions(resource: Resource): IInterpreterQuickPickItem[]; + /** + * @deprecated Use `getKnownSuggestions` and `suggestionToQuickPickItem` instead. + */ + getSuggestions(resource: Resource): Promise; + /** + * Returns path to where `debugpy` is. In python extension this is `/python_files/lib/python`. + */ + getDebuggerPath(): Promise; + /** + * Retrieve interpreter path selected for Jupyter server from Python memento storage + */ + getInterpreterPathSelectedForJupyterServer(): string | undefined; + /** + * Registers a visibility filter for the interpreter status bar. + */ + registerInterpreterStatusFilter(filter: IInterpreterStatusbarVisibilityFilter): void; + getCondaVersion(): Promise; + /** + * Returns the conda executable. + */ + getCondaFile(): Promise; + + /** + * Call to provide a function that the Python extension can call to request the Python + * path to use for a particular notebook. + * @param func : The function that Python should call when requesting the Python path. + */ + registerJupyterPythonPathFunction(func: (uri: Uri) => Promise): void; + + /** + * Returns the preferred environment for the given URI. + */ + getRecommededEnvironment( + uri: Uri | undefined, + ): Promise< + | { + environment: EnvironmentPath; + reason: 'globalUserSelected' | 'workspaceUserSelected' | 'defaultRecommended'; + } + | undefined + >; +}; + +type JupyterExtensionApi = { + /** + * Registers python extension specific parts with the jupyter extension + * @param interpreterService + */ + registerPythonApi(interpreterService: PythonApiForJupyterExtension): void; +}; + +@injectable() +export class JupyterExtensionIntegration { + private jupyterExtension: Extension | undefined; + + private pylanceExtension: Extension | undefined; + private environmentApi: PythonExtension['environments'] | undefined; + + constructor( + @inject(IExtensions) private readonly extensions: IExtensions, + @inject(IInterpreterSelector) private readonly interpreterSelector: IInterpreterSelector, + @inject(IEnvironmentActivationService) private readonly envActivation: IEnvironmentActivationService, + @inject(IMemento) @named(GLOBAL_MEMENTO) private globalState: Memento, + @inject(IInterpreterDisplay) private interpreterDisplay: IInterpreterDisplay, + @inject(IWorkspaceService) private workspaceService: IWorkspaceService, + @inject(ICondaService) private readonly condaService: ICondaService, + @inject(IContextKeyManager) private readonly contextManager: IContextKeyManager, + @inject(IInterpreterService) private interpreterService: IInterpreterService, + @inject(IRecommendedEnvironmentService) private preferredEnvironmentService: IRecommendedEnvironmentService, + ) {} + public registerEnvApi(api: PythonExtension['environments']) { + this.environmentApi = api; + } + + public registerApi(jupyterExtensionApi: JupyterExtensionApi): JupyterExtensionApi | undefined { + this.contextManager.setContext(ExtensionContextKey.IsJupyterInstalled, true); + if (!this.workspaceService.isTrusted) { + this.workspaceService.onDidGrantWorkspaceTrust(() => this.registerApi(jupyterExtensionApi)); + return undefined; + } + // Forward python parts + jupyterExtensionApi.registerPythonApi({ + getActivatedEnvironmentVariables: async ( + resource: Resource, + env: Environment, + allowExceptions?: boolean, + ) => { + const interpreter = await this.interpreterService.getInterpreterDetails(env.path); + return this.envActivation.getActivatedEnvironmentVariables(resource, interpreter, allowExceptions); + }, + getSuggestions: async (resource: Resource): Promise => + this.interpreterSelector.getAllSuggestions(resource), + getKnownSuggestions: (resource: Resource): IInterpreterQuickPickItem[] => + this.interpreterSelector.getSuggestions(resource), + getDebuggerPath: async () => dirname(await getDebugpyPath()), + getInterpreterPathSelectedForJupyterServer: () => + this.globalState.get('INTERPRETER_PATH_SELECTED_FOR_JUPYTER_SERVER'), + registerInterpreterStatusFilter: this.interpreterDisplay.registerVisibilityFilter.bind( + this.interpreterDisplay, + ), + getCondaFile: () => this.condaService.getCondaFile(), + getCondaVersion: () => this.condaService.getCondaVersion(), + registerJupyterPythonPathFunction: (func: (uri: Uri) => Promise) => + this.registerJupyterPythonPathFunction(func), + getRecommededEnvironment: async (uri) => { + if (!this.environmentApi) { + return undefined; + } + return this.preferredEnvironmentService.getRecommededEnvironment(uri); + }, + }); + return undefined; + } + + public async integrateWithJupyterExtension(): Promise { + const api = await this.getExtensionApi(); + if (api) { + this.registerApi(api); + } + } + + private async getExtensionApi(): Promise { + if (!this.pylanceExtension) { + const pylanceExtension = this.extensions.getExtension(PYLANCE_EXTENSION_ID); + + if (pylanceExtension && !pylanceExtension.isActive) { + await pylanceExtension.activate(); + } + + this.pylanceExtension = pylanceExtension; + } + + if (!this.jupyterExtension) { + const jupyterExtension = this.extensions.getExtension(JUPYTER_EXTENSION_ID); + if (!jupyterExtension) { + return undefined; + } + await jupyterExtension.activate(); + if (jupyterExtension.isActive) { + this.jupyterExtension = jupyterExtension; + return this.jupyterExtension.exports; + } + } else { + return this.jupyterExtension.exports; + } + return undefined; + } + + private getPylanceApi(): PylanceApi | undefined { + const api = this.pylanceExtension?.exports; + return api && api.notebook && api.client && api.client.isEnabled() ? api : undefined; + } + + private registerJupyterPythonPathFunction(func: (uri: Uri) => Promise) { + const api = this.getPylanceApi(); + if (api) { + api.notebook!.registerJupyterPythonPathFunction(func); + } + } +} + +export interface JupyterPythonEnvironmentApi { + /** + * This event is triggered when the environment associated with a Jupyter Notebook or Interactive Window changes. + * The Uri in the event is the Uri of the Notebook/IW. + */ + onDidChangePythonEnvironment?: Event; + /** + * Returns the EnvironmentPath to the Python environment associated with a Jupyter Notebook or Interactive Window. + * If the Uri is not associated with a Jupyter Notebook or Interactive Window, then this method returns undefined. + * @param uri + */ + getPythonEnvironment?( + uri: Uri, + ): + | undefined + | { + /** + * The ID of the environment. + */ + readonly id: string; + /** + * Path to environment folder or path to python executable that uniquely identifies an environment. Environments + * lacking a python executable are identified by environment folder paths, whereas other envs can be identified + * using python executable path. + */ + readonly path: string; + }; +} + +@injectable() +export class JupyterExtensionPythonEnvironments extends DisposableBase implements JupyterPythonEnvironmentApi { + private jupyterExtension?: JupyterPythonEnvironmentApi; + + private readonly _onDidChangePythonEnvironment = this._register(new EventEmitter()); + + public readonly onDidChangePythonEnvironment = this._onDidChangePythonEnvironment.event; + + constructor(@inject(IExtensions) private readonly extensions: IExtensions) { + super(); + } + + public getPythonEnvironment( + uri: Uri, + ): + | undefined + | { + /** + * The ID of the environment. + */ + readonly id: string; + /** + * Path to environment folder or path to python executable that uniquely identifies an environment. Environments + * lacking a python executable are identified by environment folder paths, whereas other envs can be identified + * using python executable path. + */ + readonly path: string; + } { + if (!isJupyterResource(uri)) { + return undefined; + } + const api = this.getJupyterApi(); + if (api?.getPythonEnvironment) { + return api.getPythonEnvironment(uri); + } + return undefined; + } + + private getJupyterApi() { + if (!this.jupyterExtension) { + const ext = this.extensions.getExtension(JUPYTER_EXTENSION_ID); + if (!ext) { + return undefined; + } + if (!ext.isActive) { + ext.activate().then(() => { + this.hookupOnDidChangePythonEnvironment(ext.exports); + }); + return undefined; + } + this.hookupOnDidChangePythonEnvironment(ext.exports); + } + return this.jupyterExtension; + } + + private hookupOnDidChangePythonEnvironment(api: JupyterPythonEnvironmentApi) { + this.jupyterExtension = api; + if (api.onDidChangePythonEnvironment) { + this._register( + api.onDidChangePythonEnvironment( + this._onDidChangePythonEnvironment.fire, + this._onDidChangePythonEnvironment, + ), + ); + } + } +} + +function isJupyterResource(resource: Uri): boolean { + // Jupyter extension only deals with Notebooks and Interactive Windows. + return ( + resource.fsPath.endsWith('.ipynb') || + workspace.notebookDocuments.some((item) => item.uri.toString() === resource.toString()) + ); +} diff --git a/src/client/jupyter/requireJupyterPrompt.ts b/src/client/jupyter/requireJupyterPrompt.ts new file mode 100644 index 000000000000..3e6878ba4269 --- /dev/null +++ b/src/client/jupyter/requireJupyterPrompt.ts @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable } from 'inversify'; +import { IExtensionSingleActivationService } from '../activation/types'; +import { IApplicationShell, ICommandManager } from '../common/application/types'; +import { Common, Interpreters } from '../common/utils/localize'; +import { Commands, JUPYTER_EXTENSION_ID } from '../common/constants'; +import { IDisposable, IDisposableRegistry } from '../common/types'; +import { sendTelemetryEvent } from '../telemetry'; +import { EventName } from '../telemetry/constants'; + +@injectable() +export class RequireJupyterPrompt implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; + + constructor( + @inject(IApplicationShell) private readonly appShell: IApplicationShell, + @inject(ICommandManager) private readonly commandManager: ICommandManager, + @inject(IDisposableRegistry) private readonly disposables: IDisposable[], + ) {} + + public async activate(): Promise { + this.disposables.push(this.commandManager.registerCommand(Commands.InstallJupyter, () => this._showPrompt())); + } + + public async _showPrompt(): Promise { + const prompts = [Common.bannerLabelYes, Common.bannerLabelNo]; + const telemetrySelections: ['Yes', 'No'] = ['Yes', 'No']; + const selection = await this.appShell.showInformationMessage(Interpreters.requireJupyter, ...prompts); + sendTelemetryEvent(EventName.REQUIRE_JUPYTER_PROMPT, undefined, { + selection: selection ? telemetrySelections[prompts.indexOf(selection)] : undefined, + }); + if (!selection) { + return; + } + if (selection === prompts[0]) { + await this.commandManager.executeCommand( + 'workbench.extensions.installExtension', + JUPYTER_EXTENSION_ID, + undefined, + ); + } + } +} diff --git a/src/client/jupyter/types.ts b/src/client/jupyter/types.ts new file mode 100644 index 000000000000..5eb58c7cf2b2 --- /dev/null +++ b/src/client/jupyter/types.ts @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { QuickPickItem } from 'vscode'; + +interface IJupyterServerUri { + baseUrl: string; + token: string; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + authorizationHeader: any; // JSON object for authorization header. + expiration?: Date; // Date/time when header expires and should be refreshed. + displayName: string; +} + +type JupyterServerUriHandle = string; + +export interface IJupyterUriProvider { + readonly id: string; // Should be a unique string (like a guid) + getQuickPickEntryItems(): QuickPickItem[]; + handleQuickPick(item: QuickPickItem, backEnabled: boolean): Promise; + getServerUri(handle: JupyterServerUriHandle): Promise; +} + +interface IDataFrameInfo { + columns?: { key: string; type: ColumnType }[]; + indexColumn?: string; + rowCount?: number; +} + +export interface IDataViewerDataProvider { + dispose(): void; + getDataFrameInfo(): Promise; + getAllRows(): Promise; + getRows(start: number, end: number): Promise; +} + +enum ColumnType { + String = 'string', + Number = 'number', + Bool = 'bool', +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type IRowsResponse = any[]; diff --git a/src/client/language/braceCounter.ts b/src/client/language/braceCounter.ts deleted file mode 100644 index 30d91b537544..000000000000 --- a/src/client/language/braceCounter.ts +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { IToken, TokenType } from './types'; - -class BracePair { - public readonly openBrace: TokenType; - public readonly closeBrace: TokenType; - - constructor(openBrace: TokenType, closeBrace: TokenType) { - this.openBrace = openBrace; - this.closeBrace = closeBrace; - } -} - -class Stack { - private store: IToken[] = []; - public push(val: IToken) { - this.store.push(val); - } - public pop(): IToken | undefined { - return this.store.pop(); - } - public get length(): number { - return this.store.length; - } -} - -export class BraceCounter { - private readonly bracePairs: BracePair[] = [ - new BracePair(TokenType.OpenBrace, TokenType.CloseBrace), - new BracePair(TokenType.OpenBracket, TokenType.CloseBracket), - new BracePair(TokenType.OpenCurly, TokenType.CloseCurly) - ]; - private braceStacks: Stack[] = [new Stack(), new Stack(), new Stack()]; - - public get count(): number { - let c = 0; - for (const s of this.braceStacks) { - c += s.length; - } - return c; - } - - public isOpened(type: TokenType): boolean { - for (let i = 0; i < this.bracePairs.length; i += 1) { - const pair = this.bracePairs[i]; - if (pair.openBrace === type || pair.closeBrace === type) { - return this.braceStacks[i].length > 0; - } - } - return false; - } - - public countBrace(brace: IToken): boolean { - for (let i = 0; i < this.bracePairs.length; i += 1) { - const pair = this.bracePairs[i]; - if (pair.openBrace === brace.type) { - this.braceStacks[i].push(brace); - return true; - } - if (pair.closeBrace === brace.type) { - if (this.braceStacks[i].length > 0) { - this.braceStacks[i].pop(); - } - return true; - } - } - return false; - } -} diff --git a/src/client/language/characterStream.ts b/src/client/language/characterStream.ts deleted file mode 100644 index 09f3bed33f9d..000000000000 --- a/src/client/language/characterStream.ts +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -// tslint:disable-next-line:import-name -import Char from 'typescript-char'; -import { isLineBreak, isWhiteSpace } from './characters'; -import { TextIterator } from './textIterator'; -import { ICharacterStream, ITextIterator } from './types'; - -export class CharacterStream implements ICharacterStream { - private text: ITextIterator; - private _position: number; - private _currentChar: number; - private _isEndOfStream: boolean; - - constructor(text: string | ITextIterator) { - this.text = typeof text === 'string' ? new TextIterator(text) : text; - this._position = 0; - this._currentChar = text.length > 0 ? text.charCodeAt(0) : 0; - this._isEndOfStream = text.length === 0; - } - - public getText(): string { - return this.text.getText(); - } - - public get position(): number { - return this._position; - } - - public set position(value: number) { - this._position = value; - this.checkBounds(); - } - - public get currentChar(): number { - return this._currentChar; - } - - public get nextChar(): number { - return this.position + 1 < this.text.length ? this.text.charCodeAt(this.position + 1) : 0; - } - - public get prevChar(): number { - return this.position - 1 >= 0 ? this.text.charCodeAt(this.position - 1) : 0; - } - - public isEndOfStream(): boolean { - return this._isEndOfStream; - } - - public lookAhead(offset: number): number { - const pos = this._position + offset; - return pos < 0 || pos >= this.text.length ? 0 : this.text.charCodeAt(pos); - } - - public advance(offset: number) { - this.position += offset; - } - - public moveNext(): boolean { - if (this._position < this.text.length - 1) { - // Most common case, no need to check bounds extensively - this._position += 1; - this._currentChar = this.text.charCodeAt(this._position); - return true; - } - this.advance(1); - return !this.isEndOfStream(); - } - - public isAtWhiteSpace(): boolean { - return isWhiteSpace(this.currentChar); - } - - public isAtLineBreak(): boolean { - return isLineBreak(this.currentChar); - } - - public skipLineBreak(): void { - if (this._currentChar === Char.CarriageReturn) { - this.moveNext(); - if (this.currentChar === Char.LineFeed) { - this.moveNext(); - } - } else if (this._currentChar === Char.LineFeed) { - this.moveNext(); - } - } - - public skipWhitespace(): void { - while (!this.isEndOfStream() && this.isAtWhiteSpace()) { - this.moveNext(); - } - } - - public skipToEol(): void { - while (!this.isEndOfStream() && !this.isAtLineBreak()) { - this.moveNext(); - } - } - - public skipToWhitespace(): void { - while (!this.isEndOfStream() && !this.isAtWhiteSpace()) { - this.moveNext(); - } - } - - public isAtString(): boolean { - return this.currentChar === Char.SingleQuote || this.currentChar === Char.DoubleQuote; - } - - public charCodeAt(index: number): number { - return this.text.charCodeAt(index); - } - - public get length(): number { - return this.text.length; - } - - private checkBounds(): void { - if (this._position < 0) { - this._position = 0; - } - - this._isEndOfStream = this._position >= this.text.length; - if (this._isEndOfStream) { - this._position = this.text.length; - } - - this._currentChar = this._isEndOfStream ? 0 : this.text.charCodeAt(this._position); - } -} diff --git a/src/client/language/characters.ts b/src/client/language/characters.ts deleted file mode 100644 index 50d5f716a039..000000000000 --- a/src/client/language/characters.ts +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// tslint:disable-next-line:import-name -import Char from 'typescript-char'; -import { getUnicodeCategory, UnicodeCategory } from './unicode'; - -export function isIdentifierStartChar(ch: number) { - switch (ch) { - // Underscore is explicitly allowed to start an identifier - case Char.Underscore: - return true; - // Characters with the Other_ID_Start property - case 0x1885: - case 0x1886: - case 0x2118: - case 0x212e: - case 0x309b: - case 0x309c: - return true; - default: - break; - } - - const cat = getUnicodeCategory(ch); - switch (cat) { - // Supported categories for starting an identifier - case UnicodeCategory.UppercaseLetter: - case UnicodeCategory.LowercaseLetter: - case UnicodeCategory.TitlecaseLetter: - case UnicodeCategory.ModifierLetter: - case UnicodeCategory.OtherLetter: - case UnicodeCategory.LetterNumber: - return true; - default: - break; - } - return false; -} - -export function isIdentifierChar(ch: number) { - if (isIdentifierStartChar(ch)) { - return true; - } - - switch (ch) { - // Characters with the Other_ID_Continue property - case 0x00b7: - case 0x0387: - case 0x1369: - case 0x136a: - case 0x136b: - case 0x136c: - case 0x136d: - case 0x136e: - case 0x136f: - case 0x1370: - case 0x1371: - case 0x19da: - return true; - default: - break; - } - - switch (getUnicodeCategory(ch)) { - // Supported categories for continuing an identifier - case UnicodeCategory.NonSpacingMark: - case UnicodeCategory.SpacingCombiningMark: - case UnicodeCategory.DecimalDigitNumber: - case UnicodeCategory.ConnectorPunctuation: - return true; - default: - break; - } - return false; -} - -export function isWhiteSpace(ch: number): boolean { - return ch <= Char.Space || ch === 0x200b; // Unicode whitespace -} - -export function isLineBreak(ch: number): boolean { - return ch === Char.CarriageReturn || ch === Char.LineFeed; -} - -export function isNumber(ch: number): boolean { - return (ch >= Char._0 && ch <= Char._9) || ch === Char.Underscore; -} - -export function isDecimal(ch: number): boolean { - return (ch >= Char._0 && ch <= Char._9) || ch === Char.Underscore; -} - -export function isHex(ch: number): boolean { - return isDecimal(ch) || (ch >= Char.a && ch <= Char.f) || (ch >= Char.A && ch <= Char.F) || ch === Char.Underscore; -} - -export function isOctal(ch: number): boolean { - return (ch >= Char._0 && ch <= Char._7) || ch === Char.Underscore; -} - -export function isBinary(ch: number): boolean { - return ch === Char._0 || ch === Char._1 || ch === Char.Underscore; -} diff --git a/src/client/language/iterableTextRange.ts b/src/client/language/iterableTextRange.ts deleted file mode 100644 index c69c62132e73..000000000000 --- a/src/client/language/iterableTextRange.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { ITextRange, ITextRangeCollection } from './types'; - -export class IterableTextRange implements Iterable { - constructor(private textRangeCollection: ITextRangeCollection) {} - public [Symbol.iterator](): Iterator { - let index = -1; - - return { - next: (): IteratorResult => { - if (index < this.textRangeCollection.count - 1) { - return { - done: false, - value: this.textRangeCollection.getItemAt((index += 1)) - }; - } else { - return { - done: true, - // tslint:disable-next-line:no-any - value: undefined as any - }; - } - } - }; - } -} diff --git a/src/client/language/languageConfiguration.ts b/src/client/language/languageConfiguration.ts index 9e80d3a3b704..0fbcd29c645a 100644 --- a/src/client/language/languageConfiguration.ts +++ b/src/client/language/languageConfiguration.ts @@ -1,13 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + 'use strict'; import { IndentAction, LanguageConfiguration } from 'vscode'; import { verboseRegExp } from '../common/utils/regexp'; -// tslint:disable:no-multiline-string - -// tslint:disable-next-line:max-func-body-length export function getLanguageConfiguration(): LanguageConfiguration { return { onEnterRules: [ @@ -21,8 +19,8 @@ export function getLanguageConfiguration(): LanguageConfiguration { $ `), action: { - indentAction: IndentAction.Indent - } + indentAction: IndentAction.Indent, + }, }, // continue comments { @@ -30,8 +28,8 @@ export function getLanguageConfiguration(): LanguageConfiguration { afterText: /.+$/, action: { indentAction: IndentAction.None, - appendText: '# ' - } + appendText: '# ', + }, }, // indent on enter (block-beginning statements) { @@ -59,7 +57,9 @@ export function getLanguageConfiguration(): LanguageConfiguration { elif | while | with | - async \\s+ with + async \\s+ with | + match | + case ) \\b .* ) | @@ -74,19 +74,25 @@ export function getLanguageConfiguration(): LanguageConfiguration { $ `), action: { - indentAction: IndentAction.Indent - } + indentAction: IndentAction.Indent, + }, }, // outdent on enter (block-ending statements) { + /** + * This does not handle all cases. Notable omissions here are + * "return" and "raise" which are complicated by the need to + * only outdent when the cursor is at the end of an expression + * rather than, say, between the parentheses of a tail-call or + * exception construction. (see issue #10583) + */ beforeText: verboseRegExp(` ^ (?: (?: \\s* (?: - pass | - raise \\s+ [^#\\s] [^#]* + pass ) ) | (?: @@ -103,12 +109,12 @@ export function getLanguageConfiguration(): LanguageConfiguration { $ `), action: { - indentAction: IndentAction.Outdent - } - } + indentAction: IndentAction.Outdent, + }, + }, // Note that we do not currently have an auto-dedent // solution for "elif", "else", "except", and "finally". // We had one but had to remove it (see issue #6886). - ] + ], }; } diff --git a/src/client/language/textBuilder.ts b/src/client/language/textBuilder.ts deleted file mode 100644 index e11f2a1299c4..000000000000 --- a/src/client/language/textBuilder.ts +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { isWhiteSpace } from './characters'; - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -export class TextBuilder { - private segments: string[] = []; - - public getText(): string { - if (this.isLastWhiteSpace()) { - this.segments.pop(); - } - return this.segments.join(''); - } - - public softAppendSpace(count: number = 1): void { - if (this.segments.length === 0) { - return; - } - if (this.isLastWhiteSpace()) { - count = count - 1; - } - for (let i = 0; i < count; i += 1) { - this.segments.push(' '); - } - } - - public append(text: string): void { - this.segments.push(text); - } - - private isLastWhiteSpace(): boolean { - return this.segments.length > 0 && this.isWhitespace(this.segments[this.segments.length - 1]); - } - - private isWhitespace(s: string): boolean { - for (let i = 0; i < s.length; i += 1) { - if (!isWhiteSpace(s.charCodeAt(i))) { - return false; - } - } - return true; - } -} diff --git a/src/client/language/textIterator.ts b/src/client/language/textIterator.ts deleted file mode 100644 index 6037cbf67cb9..000000000000 --- a/src/client/language/textIterator.ts +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import { Position, Range, TextDocument } from 'vscode'; -import { ITextIterator } from './types'; - -export class TextIterator implements ITextIterator { - private text: string; - - constructor(text: string) { - this.text = text; - } - - public charCodeAt(index: number): number { - if (index >= 0 && index < this.text.length) { - return this.text.charCodeAt(index); - } - return 0; - } - - public get length(): number { - return this.text.length; - } - - public getText(): string { - return this.text; - } -} - -export class DocumentTextIterator implements ITextIterator { - public readonly length: number; - - private document: TextDocument; - - constructor(document: TextDocument) { - this.document = document; - - const lastIndex = this.document.lineCount - 1; - const lastLine = this.document.lineAt(lastIndex); - const end = new Position(lastIndex, lastLine.range.end.character); - this.length = this.document.offsetAt(end); - } - - public charCodeAt(index: number): number { - const position = this.document.positionAt(index); - return this.document.getText(new Range(position, position.translate(0, 1))).charCodeAt(position.character); - } - - public getText(): string { - return this.document.getText(); - } -} diff --git a/src/client/language/textRangeCollection.ts b/src/client/language/textRangeCollection.ts deleted file mode 100644 index 8ce5a744c9a6..000000000000 --- a/src/client/language/textRangeCollection.ts +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import { ITextRange, ITextRangeCollection } from './types'; - -export class TextRangeCollection implements ITextRangeCollection { - private items: T[]; - - constructor(items: T[]) { - this.items = items; - } - - public get start(): number { - return this.items.length > 0 ? this.items[0].start : 0; - } - - public get end(): number { - return this.items.length > 0 ? this.items[this.items.length - 1].end : 0; - } - - public get length(): number { - return this.end - this.start; - } - - public get count(): number { - return this.items.length; - } - - public contains(position: number) { - return position >= this.start && position < this.end; - } - - public getItemAt(index: number): T { - if (index < 0 || index >= this.items.length) { - throw new Error('index is out of range'); - } - return this.items[index] as T; - } - - public getItemAtPosition(position: number): number { - if (this.count === 0) { - return -1; - } - if (position < this.start) { - return -1; - } - if (position >= this.end) { - return -1; - } - - let min = 0; - let max = this.count - 1; - - while (min <= max) { - const mid = Math.floor(min + (max - min) / 2); - const item = this.items[mid]; - - if (item.start === position) { - return mid; - } - - if (position < item.start) { - max = mid - 1; - } else { - min = mid + 1; - } - } - return -1; - } - - public getItemContaining(position: number): number { - if (this.count === 0) { - return -1; - } - if (position < this.start) { - return -1; - } - if (position > this.end) { - return -1; - } - - let min = 0; - let max = this.count - 1; - - while (min <= max) { - const mid = Math.floor(min + (max - min) / 2); - const item = this.items[mid]; - - if (item.contains(position)) { - return mid; - } - if (mid < this.count - 1 && item.end <= position && position < this.items[mid + 1].start) { - return -1; - } - - if (position < item.start) { - max = mid - 1; - } else { - min = mid + 1; - } - } - return -1; - } -} diff --git a/src/client/language/tokenizer.ts b/src/client/language/tokenizer.ts deleted file mode 100644 index f51ceaa350c7..000000000000 --- a/src/client/language/tokenizer.ts +++ /dev/null @@ -1,514 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -// tslint:disable-next-line:import-name -import Char from 'typescript-char'; -import { isBinary, isDecimal, isHex, isIdentifierChar, isIdentifierStartChar, isOctal } from './characters'; -import { CharacterStream } from './characterStream'; -import { TextRangeCollection } from './textRangeCollection'; -import { - ICharacterStream, - ITextRangeCollection, - IToken, - ITokenizer, - TextRange, - TokenizerMode, - TokenType -} from './types'; - -enum QuoteType { - None, - Single, - Double, - TripleSingle, - TripleDouble -} - -class Token extends TextRange implements IToken { - public readonly type: TokenType; - - constructor(type: TokenType, start: number, length: number) { - super(start, length); - this.type = type; - } -} - -export class Tokenizer implements ITokenizer { - private cs: ICharacterStream = new CharacterStream(''); - private tokens: IToken[] = []; - private mode = TokenizerMode.Full; - - public tokenize(text: string): ITextRangeCollection; - public tokenize(text: string, start: number, length: number, mode: TokenizerMode): ITextRangeCollection; - - public tokenize(text: string, start?: number, length?: number, mode?: TokenizerMode): ITextRangeCollection { - if (start === undefined) { - start = 0; - } else if (start < 0 || start >= text.length) { - throw new Error('Invalid range start'); - } - - if (length === undefined) { - length = text.length; - } else if (length < 0 || start + length > text.length) { - throw new Error('Invalid range length'); - } - - this.mode = mode !== undefined ? mode : TokenizerMode.Full; - - this.cs = new CharacterStream(text); - this.cs.position = start; - - const end = start + length; - while (!this.cs.isEndOfStream()) { - this.AddNextToken(); - if (this.cs.position >= end) { - break; - } - } - return new TextRangeCollection(this.tokens); - } - - private AddNextToken(): void { - this.cs.skipWhitespace(); - if (this.cs.isEndOfStream()) { - return; - } - - if (!this.handleCharacter()) { - this.cs.moveNext(); - } - } - - // tslint:disable-next-line:cyclomatic-complexity - private handleCharacter(): boolean { - // f-strings, b-strings, etc - const stringPrefixLength = this.getStringPrefixLength(); - if (stringPrefixLength >= 0) { - // Indeed a string - this.cs.advance(stringPrefixLength); - - const quoteType = this.getQuoteType(); - if (quoteType !== QuoteType.None) { - this.handleString(quoteType, stringPrefixLength); - return true; - } - } - if (this.cs.currentChar === Char.Hash) { - this.handleComment(); - return true; - } - if (this.mode === TokenizerMode.CommentsAndStrings) { - return false; - } - - switch (this.cs.currentChar) { - case Char.OpenParenthesis: - this.tokens.push(new Token(TokenType.OpenBrace, this.cs.position, 1)); - break; - case Char.CloseParenthesis: - this.tokens.push(new Token(TokenType.CloseBrace, this.cs.position, 1)); - break; - case Char.OpenBracket: - this.tokens.push(new Token(TokenType.OpenBracket, this.cs.position, 1)); - break; - case Char.CloseBracket: - this.tokens.push(new Token(TokenType.CloseBracket, this.cs.position, 1)); - break; - case Char.OpenBrace: - this.tokens.push(new Token(TokenType.OpenCurly, this.cs.position, 1)); - break; - case Char.CloseBrace: - this.tokens.push(new Token(TokenType.CloseCurly, this.cs.position, 1)); - break; - case Char.Comma: - this.tokens.push(new Token(TokenType.Comma, this.cs.position, 1)); - break; - case Char.Semicolon: - this.tokens.push(new Token(TokenType.Semicolon, this.cs.position, 1)); - break; - case Char.Colon: - this.tokens.push(new Token(TokenType.Colon, this.cs.position, 1)); - break; - default: - if (this.isPossibleNumber()) { - if (this.tryNumber()) { - return true; - } - } - if (this.cs.currentChar === Char.Period) { - this.tokens.push(new Token(TokenType.Operator, this.cs.position, 1)); - break; - } - if (!this.tryIdentifier()) { - if (!this.tryOperator()) { - this.handleUnknown(); - } - } - return true; - } - return false; - } - - private tryIdentifier(): boolean { - const start = this.cs.position; - if (isIdentifierStartChar(this.cs.currentChar)) { - this.cs.moveNext(); - while (isIdentifierChar(this.cs.currentChar)) { - this.cs.moveNext(); - } - } - if (this.cs.position > start) { - // const text = this.cs.getText().substr(start, this.cs.position - start); - // const type = this.keywords.find((value, index) => value === text) ? TokenType.Keyword : TokenType.Identifier; - this.tokens.push(new Token(TokenType.Identifier, start, this.cs.position - start)); - return true; - } - return false; - } - - // tslint:disable-next-line:cyclomatic-complexity - private isPossibleNumber(): boolean { - if (isDecimal(this.cs.currentChar)) { - return true; - } - - if (this.cs.currentChar === Char.Period && isDecimal(this.cs.nextChar)) { - return true; - } - - const next = this.cs.currentChar === Char.Hyphen || this.cs.currentChar === Char.Plus ? 1 : 0; - // Next character must be decimal or a dot otherwise - // it is not a number. No whitespace is allowed. - if (isDecimal(this.cs.lookAhead(next)) || this.cs.lookAhead(next) === Char.Period) { - // Check what previous token is, if any - if (this.tokens.length === 0) { - // At the start of the file this can only be a number - return true; - } - - const prev = this.tokens[this.tokens.length - 1]; - if ( - prev.type === TokenType.OpenBrace || - prev.type === TokenType.OpenBracket || - prev.type === TokenType.Comma || - prev.type === TokenType.Colon || - prev.type === TokenType.Semicolon || - prev.type === TokenType.Operator - ) { - return true; - } - } - - if (this.cs.lookAhead(next) === Char._0) { - const nextNext = this.cs.lookAhead(next + 1); - if (nextNext === Char.x || nextNext === Char.X) { - return true; - } - if (nextNext === Char.b || nextNext === Char.B) { - return true; - } - if (nextNext === Char.o || nextNext === Char.O) { - return true; - } - } - - return false; - } - - // tslint:disable-next-line:cyclomatic-complexity - private tryNumber(): boolean { - const start = this.cs.position; - let leadingSign = 0; - - if (this.cs.currentChar === Char.Hyphen || this.cs.currentChar === Char.Plus) { - this.cs.moveNext(); // Skip leading +/- - leadingSign = 1; - } - - if (this.cs.currentChar === Char._0) { - let radix = 0; - // Try hex => hexinteger: "0" ("x" | "X") (["_"] hexdigit)+ - if ((this.cs.nextChar === Char.x || this.cs.nextChar === Char.X) && isHex(this.cs.lookAhead(2))) { - this.cs.advance(2); - while (isHex(this.cs.currentChar)) { - this.cs.moveNext(); - } - radix = 16; - } - // Try binary => bininteger: "0" ("b" | "B") (["_"] bindigit)+ - if ((this.cs.nextChar === Char.b || this.cs.nextChar === Char.B) && isBinary(this.cs.lookAhead(2))) { - this.cs.advance(2); - while (isBinary(this.cs.currentChar)) { - this.cs.moveNext(); - } - radix = 2; - } - // Try octal => octinteger: "0" ("o" | "O") (["_"] octdigit)+ - if ((this.cs.nextChar === Char.o || this.cs.nextChar === Char.O) && isOctal(this.cs.lookAhead(2))) { - this.cs.advance(2); - while (isOctal(this.cs.currentChar)) { - this.cs.moveNext(); - } - radix = 8; - } - if (radix > 0) { - const text = this.cs.getText().substr(start + leadingSign, this.cs.position - start - leadingSign); - if (!isNaN(parseInt(text, radix))) { - this.tokens.push(new Token(TokenType.Number, start, text.length + leadingSign)); - return true; - } - } - } - - let decimal = false; - // Try decimal int => - // decinteger: nonzerodigit (["_"] digit)* | "0" (["_"] "0")* - // nonzerodigit: "1"..."9" - // digit: "0"..."9" - if (this.cs.currentChar >= Char._1 && this.cs.currentChar <= Char._9) { - while (isDecimal(this.cs.currentChar)) { - this.cs.moveNext(); - } - decimal = - this.cs.currentChar !== Char.Period && this.cs.currentChar !== Char.e && this.cs.currentChar !== Char.E; - } - - if (this.cs.currentChar === Char._0) { - // "0" (["_"] "0")* - while (this.cs.currentChar === Char._0 || this.cs.currentChar === Char.Underscore) { - this.cs.moveNext(); - } - decimal = - this.cs.currentChar !== Char.Period && this.cs.currentChar !== Char.e && this.cs.currentChar !== Char.E; - } - - if (decimal) { - const text = this.cs.getText().substr(start + leadingSign, this.cs.position - start - leadingSign); - if (!isNaN(parseInt(text, 10))) { - this.tokens.push(new Token(TokenType.Number, start, text.length + leadingSign)); - return true; - } - } - - // Floating point. Sign was already skipped over. - if ( - (this.cs.currentChar >= Char._0 && this.cs.currentChar <= Char._9) || - (this.cs.currentChar === Char.Period && this.cs.nextChar >= Char._0 && this.cs.nextChar <= Char._9) - ) { - if (this.skipFloatingPointCandidate(false)) { - const text = this.cs.getText().substr(start, this.cs.position - start); - if (!isNaN(parseFloat(text))) { - this.tokens.push(new Token(TokenType.Number, start, this.cs.position - start)); - return true; - } - } - } - - this.cs.position = start; - return false; - } - - // tslint:disable-next-line:cyclomatic-complexity - private tryOperator(): boolean { - let length = 0; - const nextChar = this.cs.nextChar; - switch (this.cs.currentChar) { - case Char.Plus: - case Char.Ampersand: - case Char.Bar: - case Char.Caret: - case Char.Equal: - case Char.ExclamationMark: - case Char.Percent: - case Char.Tilde: - length = nextChar === Char.Equal ? 2 : 1; - break; - - case Char.Hyphen: - length = nextChar === Char.Equal || nextChar === Char.Greater ? 2 : 1; - break; - - case Char.Asterisk: - if (nextChar === Char.Asterisk) { - length = this.cs.lookAhead(2) === Char.Equal ? 3 : 2; - } else { - length = nextChar === Char.Equal ? 2 : 1; - } - break; - - case Char.Slash: - if (nextChar === Char.Slash) { - length = this.cs.lookAhead(2) === Char.Equal ? 3 : 2; - } else { - length = nextChar === Char.Equal ? 2 : 1; - } - break; - - case Char.Less: - if (nextChar === Char.Greater) { - length = 2; - } else if (nextChar === Char.Less) { - length = this.cs.lookAhead(2) === Char.Equal ? 3 : 2; - } else { - length = nextChar === Char.Equal ? 2 : 1; - } - break; - - case Char.Greater: - if (nextChar === Char.Greater) { - length = this.cs.lookAhead(2) === Char.Equal ? 3 : 2; - } else { - length = nextChar === Char.Equal ? 2 : 1; - } - break; - - case Char.At: - length = nextChar === Char.Equal ? 2 : 1; - break; - - default: - return false; - } - this.tokens.push(new Token(TokenType.Operator, this.cs.position, length)); - this.cs.advance(length); - return length > 0; - } - - private handleUnknown(): boolean { - const start = this.cs.position; - this.cs.skipToWhitespace(); - const length = this.cs.position - start; - if (length > 0) { - this.tokens.push(new Token(TokenType.Unknown, start, length)); - return true; - } - return false; - } - - private handleComment(): void { - const start = this.cs.position; - this.cs.skipToEol(); - this.tokens.push(new Token(TokenType.Comment, start, this.cs.position - start)); - } - - // tslint:disable-next-line:cyclomatic-complexity - private getStringPrefixLength(): number { - if (this.cs.currentChar === Char.SingleQuote || this.cs.currentChar === Char.DoubleQuote) { - return 0; // Simple string, no prefix - } - - if (this.cs.nextChar === Char.SingleQuote || this.cs.nextChar === Char.DoubleQuote) { - switch (this.cs.currentChar) { - case Char.f: - case Char.F: - case Char.r: - case Char.R: - case Char.b: - case Char.B: - case Char.u: - case Char.U: - return 1; // single-char prefix like u"" or r"" - default: - break; - } - } - - if (this.cs.lookAhead(2) === Char.SingleQuote || this.cs.lookAhead(2) === Char.DoubleQuote) { - const prefix = this.cs.getText().substr(this.cs.position, 2).toLowerCase(); - switch (prefix) { - case 'rf': - case 'ur': - case 'br': - return 2; - default: - break; - } - } - return -1; - } - - private getQuoteType(): QuoteType { - if (this.cs.currentChar === Char.SingleQuote) { - return this.cs.nextChar === Char.SingleQuote && this.cs.lookAhead(2) === Char.SingleQuote - ? QuoteType.TripleSingle - : QuoteType.Single; - } - if (this.cs.currentChar === Char.DoubleQuote) { - return this.cs.nextChar === Char.DoubleQuote && this.cs.lookAhead(2) === Char.DoubleQuote - ? QuoteType.TripleDouble - : QuoteType.Double; - } - return QuoteType.None; - } - - private handleString(quoteType: QuoteType, stringPrefixLength: number): void { - const start = this.cs.position - stringPrefixLength; - if (quoteType === QuoteType.Single || quoteType === QuoteType.Double) { - this.cs.moveNext(); - this.skipToSingleEndQuote(quoteType === QuoteType.Single ? Char.SingleQuote : Char.DoubleQuote); - } else { - this.cs.advance(3); - this.skipToTripleEndQuote(quoteType === QuoteType.TripleSingle ? Char.SingleQuote : Char.DoubleQuote); - } - this.tokens.push(new Token(TokenType.String, start, this.cs.position - start)); - } - - private skipToSingleEndQuote(quote: number): void { - while (!this.cs.isEndOfStream()) { - if (this.cs.currentChar === Char.LineFeed || this.cs.currentChar === Char.CarriageReturn) { - return; // Unterminated single-line string - } - if (this.cs.currentChar === Char.Backslash && this.cs.nextChar === quote) { - this.cs.advance(2); - continue; - } - if (this.cs.currentChar === quote) { - break; - } - this.cs.moveNext(); - } - this.cs.moveNext(); - } - - private skipToTripleEndQuote(quote: number): void { - while ( - !this.cs.isEndOfStream() && - (this.cs.currentChar !== quote || this.cs.nextChar !== quote || this.cs.lookAhead(2) !== quote) - ) { - this.cs.moveNext(); - } - this.cs.advance(3); - } - - private skipFloatingPointCandidate(allowSign: boolean): boolean { - // Determine end of the potential floating point number - const start = this.cs.position; - this.skipFractionalNumber(allowSign); - if (this.cs.position > start) { - if (this.cs.currentChar === Char.e || this.cs.currentChar === Char.E) { - this.cs.moveNext(); // Optional exponent sign - } - this.skipDecimalNumber(true); // skip exponent value - } - return this.cs.position > start; - } - - private skipFractionalNumber(allowSign: boolean): void { - this.skipDecimalNumber(allowSign); - if (this.cs.currentChar === Char.Period) { - this.cs.moveNext(); // Optional period - } - this.skipDecimalNumber(false); - } - - private skipDecimalNumber(allowSign: boolean): void { - if (allowSign && (this.cs.currentChar === Char.Hyphen || this.cs.currentChar === Char.Plus)) { - this.cs.moveNext(); // Optional sign - } - while (isDecimal(this.cs.currentChar)) { - this.cs.moveNext(); // skip integer part - } - } -} diff --git a/src/client/language/types.ts b/src/client/language/types.ts deleted file mode 100644 index 51618039a3d4..000000000000 --- a/src/client/language/types.ts +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -export interface ITextRange { - readonly start: number; - readonly end: number; - readonly length: number; - contains(position: number): boolean; -} - -export class TextRange implements ITextRange { - public static readonly empty = TextRange.fromBounds(0, 0); - - public readonly start: number; - public readonly length: number; - - constructor(start: number, length: number) { - if (start < 0) { - throw new Error('start must be non-negative'); - } - if (length < 0) { - throw new Error('length must be non-negative'); - } - this.start = start; - this.length = length; - } - - public static fromBounds(start: number, end: number) { - return new TextRange(start, end - start); - } - - public get end(): number { - return this.start + this.length; - } - - public contains(position: number): boolean { - return position >= this.start && position < this.end; - } -} - -export interface ITextRangeCollection extends ITextRange { - count: number; - getItemAt(index: number): T; - getItemAtPosition(position: number): number; - getItemContaining(position: number): number; -} - -export interface ITextIterator { - readonly length: number; - charCodeAt(index: number): number; - getText(): string; -} - -export interface ICharacterStream extends ITextIterator { - position: number; - readonly currentChar: number; - readonly nextChar: number; - readonly prevChar: number; - getText(): string; - isEndOfStream(): boolean; - lookAhead(offset: number): number; - advance(offset: number): void; - moveNext(): boolean; - isAtWhiteSpace(): boolean; - isAtLineBreak(): boolean; - isAtString(): boolean; - skipLineBreak(): void; - skipWhitespace(): void; - skipToEol(): void; - skipToWhitespace(): void; -} - -export enum TokenType { - Unknown, - String, - Comment, - Keyword, - Number, - Identifier, - Operator, - Colon, - Semicolon, - Comma, - OpenBrace, - CloseBrace, - OpenBracket, - CloseBracket, - OpenCurly, - CloseCurly -} - -export interface IToken extends ITextRange { - readonly type: TokenType; -} - -export enum TokenizerMode { - CommentsAndStrings, - Full -} - -export interface ITokenizer { - tokenize(text: string): ITextRangeCollection; - tokenize(text: string, start: number, length: number, mode: TokenizerMode): ITextRangeCollection; -} diff --git a/src/client/language/unicode.ts b/src/client/language/unicode.ts deleted file mode 100644 index 9b3ca0b15b25..000000000000 --- a/src/client/language/unicode.ts +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -// tslint:disable:no-require-imports no-var-requires - -export enum UnicodeCategory { - Unknown, - UppercaseLetter, - LowercaseLetter, - TitlecaseLetter, - ModifierLetter, - OtherLetter, - LetterNumber, - NonSpacingMark, - SpacingCombiningMark, - DecimalDigitNumber, - ConnectorPunctuation -} - -export function getUnicodeCategory(ch: number): UnicodeCategory { - const unicodeLu = require('unicode/category/Lu'); - const unicodeLl = require('unicode/category/Ll'); - const unicodeLt = require('unicode/category/Lt'); - const unicodeLo = require('unicode/category/Lo'); - const unicodeLm = require('unicode/category/Lm'); - const unicodeNl = require('unicode/category/Nl'); - const unicodeMn = require('unicode/category/Mn'); - const unicodeMc = require('unicode/category/Mc'); - const unicodeNd = require('unicode/category/Nd'); - const unicodePc = require('unicode/category/Pc'); - - if (unicodeLu[ch]) { - return UnicodeCategory.UppercaseLetter; - } - if (unicodeLl[ch]) { - return UnicodeCategory.LowercaseLetter; - } - if (unicodeLt[ch]) { - return UnicodeCategory.TitlecaseLetter; - } - if (unicodeLo[ch]) { - return UnicodeCategory.OtherLetter; - } - if (unicodeLm[ch]) { - return UnicodeCategory.ModifierLetter; - } - if (unicodeNl[ch]) { - return UnicodeCategory.LetterNumber; - } - if (unicodeMn[ch]) { - return UnicodeCategory.NonSpacingMark; - } - if (unicodeMc[ch]) { - return UnicodeCategory.SpacingCombiningMark; - } - if (unicodeNd[ch]) { - return UnicodeCategory.DecimalDigitNumber; - } - if (unicodePc[ch]) { - return UnicodeCategory.ConnectorPunctuation; - } - return UnicodeCategory.Unknown; -} diff --git a/src/client/languageServer/jediLSExtensionManager.ts b/src/client/languageServer/jediLSExtensionManager.ts new file mode 100644 index 000000000000..4cbfb6f33466 --- /dev/null +++ b/src/client/languageServer/jediLSExtensionManager.ts @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { JediLanguageServerAnalysisOptions } from '../activation/jedi/analysisOptions'; +import { JediLanguageClientFactory } from '../activation/jedi/languageClientFactory'; +import { JediLanguageServerProxy } from '../activation/jedi/languageServerProxy'; +import { JediLanguageServerManager } from '../activation/jedi/manager'; +import { ILanguageServerOutputChannel } from '../activation/types'; +import { IWorkspaceService, ICommandManager } from '../common/application/types'; +import { + IExperimentService, + IInterpreterPathService, + IConfigurationService, + Resource, + IDisposable, +} from '../common/types'; +import { IEnvironmentVariablesProvider } from '../common/variables/types'; +import { IInterpreterService } from '../interpreter/contracts'; +import { IServiceContainer } from '../ioc/types'; +import { traceError } from '../logging'; +import { PythonEnvironment } from '../pythonEnvironments/info'; +import { ILanguageServerExtensionManager } from './types'; + +export class JediLSExtensionManager implements IDisposable, ILanguageServerExtensionManager { + private serverProxy: JediLanguageServerProxy; + + serverManager: JediLanguageServerManager; + + clientFactory: JediLanguageClientFactory; + + analysisOptions: JediLanguageServerAnalysisOptions; + + constructor( + serviceContainer: IServiceContainer, + outputChannel: ILanguageServerOutputChannel, + _experimentService: IExperimentService, + workspaceService: IWorkspaceService, + configurationService: IConfigurationService, + _interpreterPathService: IInterpreterPathService, + interpreterService: IInterpreterService, + environmentService: IEnvironmentVariablesProvider, + commandManager: ICommandManager, + ) { + this.analysisOptions = new JediLanguageServerAnalysisOptions( + environmentService, + outputChannel, + configurationService, + workspaceService, + ); + this.clientFactory = new JediLanguageClientFactory(interpreterService); + this.serverProxy = new JediLanguageServerProxy(this.clientFactory); + this.serverManager = new JediLanguageServerManager( + serviceContainer, + this.analysisOptions, + this.serverProxy, + commandManager, + ); + } + + dispose(): void { + this.serverManager.disconnect(); + this.serverManager.dispose(); + this.serverProxy.dispose(); + this.analysisOptions.dispose(); + } + + async startLanguageServer(resource: Resource, interpreter?: PythonEnvironment): Promise { + await this.serverManager.start(resource, interpreter); + this.serverManager.connect(); + } + + async stopLanguageServer(): Promise { + this.serverManager.disconnect(); + await this.serverProxy.stop(); + } + + // eslint-disable-next-line class-methods-use-this + canStartLanguageServer(interpreter: PythonEnvironment | undefined): boolean { + if (!interpreter) { + traceError('Unable to start Jedi language server as a valid interpreter is not selected'); + return false; + } + // Otherwise return true for now since it's shipped with the extension. + // Update this when JediLSP is pulled in a separate extension. + return true; + } + + // eslint-disable-next-line class-methods-use-this + languageServerNotAvailable(): Promise { + // Nothing to do here. + // Update this when JediLSP is pulled in a separate extension. + return Promise.resolve(); + } +} diff --git a/src/client/languageServer/noneLSExtensionManager.ts b/src/client/languageServer/noneLSExtensionManager.ts new file mode 100644 index 000000000000..1d93ea50be51 --- /dev/null +++ b/src/client/languageServer/noneLSExtensionManager.ts @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/* eslint-disable class-methods-use-this */ + +import { ILanguageServerExtensionManager } from './types'; + +// This LS manager implements ILanguageServer directly +// instead of extending LanguageServerCapabilities because it doesn't need to do anything. +export class NoneLSExtensionManager implements ILanguageServerExtensionManager { + dispose(): void { + // Nothing to do here. + } + + startLanguageServer(): Promise { + return Promise.resolve(); + } + + stopLanguageServer(): Promise { + return Promise.resolve(); + } + + canStartLanguageServer(): boolean { + return true; + } + + languageServerNotAvailable(): Promise { + // Nothing to do here. + return Promise.resolve(); + } +} diff --git a/src/client/languageServer/pylanceLSExtensionManager.ts b/src/client/languageServer/pylanceLSExtensionManager.ts new file mode 100644 index 000000000000..7b03d909a512 --- /dev/null +++ b/src/client/languageServer/pylanceLSExtensionManager.ts @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { promptForPylanceInstall } from '../activation/common/languageServerChangeHandler'; +import { NodeLanguageServerAnalysisOptions } from '../activation/node/analysisOptions'; +import { NodeLanguageClientFactory } from '../activation/node/languageClientFactory'; +import { NodeLanguageServerProxy } from '../activation/node/languageServerProxy'; +import { NodeLanguageServerManager } from '../activation/node/manager'; +import { ILanguageServerOutputChannel } from '../activation/types'; +import { IApplicationShell, ICommandManager, IWorkspaceService } from '../common/application/types'; +import { PYLANCE_EXTENSION_ID } from '../common/constants'; +import { IFileSystem } from '../common/platform/types'; +import { + IConfigurationService, + IDisposable, + IExperimentService, + IExtensions, + IInterpreterPathService, + Resource, +} from '../common/types'; +import { Pylance } from '../common/utils/localize'; +import { IEnvironmentVariablesProvider } from '../common/variables/types'; +import { IInterpreterService } from '../interpreter/contracts'; +import { IServiceContainer } from '../ioc/types'; +import { traceLog } from '../logging'; +import { PythonEnvironment } from '../pythonEnvironments/info'; +import { ILanguageServerExtensionManager } from './types'; + +export class PylanceLSExtensionManager implements IDisposable, ILanguageServerExtensionManager { + private serverProxy: NodeLanguageServerProxy; + + serverManager: NodeLanguageServerManager; + + clientFactory: NodeLanguageClientFactory; + + analysisOptions: NodeLanguageServerAnalysisOptions; + + constructor( + serviceContainer: IServiceContainer, + outputChannel: ILanguageServerOutputChannel, + experimentService: IExperimentService, + readonly workspaceService: IWorkspaceService, + readonly configurationService: IConfigurationService, + interpreterPathService: IInterpreterPathService, + _interpreterService: IInterpreterService, + environmentService: IEnvironmentVariablesProvider, + readonly commandManager: ICommandManager, + fileSystem: IFileSystem, + private readonly extensions: IExtensions, + readonly applicationShell: IApplicationShell, + ) { + this.analysisOptions = new NodeLanguageServerAnalysisOptions(outputChannel, workspaceService); + this.clientFactory = new NodeLanguageClientFactory(fileSystem, extensions); + this.serverProxy = new NodeLanguageServerProxy( + this.clientFactory, + experimentService, + interpreterPathService, + environmentService, + workspaceService, + extensions, + ); + this.serverManager = new NodeLanguageServerManager( + serviceContainer, + this.analysisOptions, + this.serverProxy, + commandManager, + extensions, + ); + } + + dispose(): void { + this.serverManager.disconnect(); + this.serverManager.dispose(); + this.serverProxy.dispose(); + this.analysisOptions.dispose(); + } + + async startLanguageServer(resource: Resource, interpreter?: PythonEnvironment): Promise { + await this.serverManager.start(resource, interpreter); + this.serverManager.connect(); + } + + async stopLanguageServer(): Promise { + this.serverManager.disconnect(); + await this.serverProxy.stop(); + } + + canStartLanguageServer(): boolean { + const extension = this.extensions.getExtension(PYLANCE_EXTENSION_ID); + return !!extension; + } + + async languageServerNotAvailable(): Promise { + await promptForPylanceInstall( + this.applicationShell, + this.commandManager, + this.workspaceService, + this.configurationService, + ); + + traceLog(Pylance.pylanceNotInstalledMessage); + } +} diff --git a/src/client/languageServer/types.ts b/src/client/languageServer/types.ts new file mode 100644 index 000000000000..f7cad157fcef --- /dev/null +++ b/src/client/languageServer/types.ts @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { LanguageServerType } from '../activation/types'; +import { Resource } from '../common/types'; +import { PythonEnvironment } from '../pythonEnvironments/info'; + +export const ILanguageServerWatcher = Symbol('ILanguageServerWatcher'); +/** + * The language server watcher serves as a singleton that watches for changes to the language server setting, + * and instantiates the relevant language server extension manager. + */ +export interface ILanguageServerWatcher { + readonly languageServerExtensionManager: ILanguageServerExtensionManager | undefined; + readonly languageServerType: LanguageServerType; + startLanguageServer(languageServerType: LanguageServerType, resource?: Resource): Promise; + restartLanguageServers(): Promise; + get(resource: Resource, interpreter?: PythonEnvironment): Promise; +} + +/** + * `ILanguageServerExtensionManager` implementations act as wrappers for anything related to their specific language server extension. + * They are responsible for starting and stopping the language server provided by their LS extension. + * They also extend the `ILanguageServer` interface via `ILanguageServerCapabilities` to continue supporting the Jupyter integration. + */ +export interface ILanguageServerExtensionManager { + startLanguageServer(resource: Resource, interpreter?: PythonEnvironment): Promise; + stopLanguageServer(): Promise; + canStartLanguageServer(interpreter: PythonEnvironment | undefined): boolean; + languageServerNotAvailable(): Promise; + dispose(): void; +} diff --git a/src/client/languageServer/watcher.ts b/src/client/languageServer/watcher.ts new file mode 100644 index 000000000000..39e6e0bb1ece --- /dev/null +++ b/src/client/languageServer/watcher.ts @@ -0,0 +1,406 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { inject, injectable } from 'inversify'; +import { ConfigurationChangeEvent, l10n, Uri, WorkspaceFoldersChangeEvent } from 'vscode'; +import { LanguageServerChangeHandler } from '../activation/common/languageServerChangeHandler'; +import { IExtensionActivationService, ILanguageServerOutputChannel, LanguageServerType } from '../activation/types'; +import { IApplicationShell, ICommandManager, IWorkspaceService } from '../common/application/types'; +import { IFileSystem } from '../common/platform/types'; +import { + IConfigurationService, + IDisposableRegistry, + IExperimentService, + IExtensions, + IInterpreterPathService, + InterpreterConfigurationScope, + Resource, +} from '../common/types'; +import { LanguageService } from '../common/utils/localize'; +import { IEnvironmentVariablesProvider } from '../common/variables/types'; +import { IInterpreterHelper, IInterpreterService } from '../interpreter/contracts'; +import { IServiceContainer } from '../ioc/types'; +import { traceLog } from '../logging'; +import { PythonEnvironment } from '../pythonEnvironments/info'; +import { JediLSExtensionManager } from './jediLSExtensionManager'; +import { NoneLSExtensionManager } from './noneLSExtensionManager'; +import { PylanceLSExtensionManager } from './pylanceLSExtensionManager'; +import { ILanguageServerExtensionManager, ILanguageServerWatcher } from './types'; +import { sendTelemetryEvent } from '../telemetry'; +import { EventName } from '../telemetry/constants'; +import { StopWatch } from '../common/utils/stopWatch'; + +@injectable() +/** + * The Language Server Watcher class implements the ILanguageServerWatcher interface, which is the one-stop shop for language server activation. + */ +export class LanguageServerWatcher implements IExtensionActivationService, ILanguageServerWatcher { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: true, virtualWorkspace: true }; + + languageServerExtensionManager: ILanguageServerExtensionManager | undefined; + + languageServerType: LanguageServerType; + + private workspaceInterpreters: Map; + + // In a multiroot workspace scenario we may have multiple language servers running: + // When using Jedi, there will be one language server per workspace folder. + // When using Pylance, there will only be one language server for the project. + private workspaceLanguageServers: Map; + + private registered = false; + + constructor( + @inject(IServiceContainer) private readonly serviceContainer: IServiceContainer, + @inject(ILanguageServerOutputChannel) private readonly lsOutputChannel: ILanguageServerOutputChannel, + @inject(IConfigurationService) private readonly configurationService: IConfigurationService, + @inject(IExperimentService) private readonly experimentService: IExperimentService, + @inject(IInterpreterHelper) private readonly interpreterHelper: IInterpreterHelper, + @inject(IInterpreterPathService) private readonly interpreterPathService: IInterpreterPathService, + @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, + @inject(IEnvironmentVariablesProvider) private readonly environmentService: IEnvironmentVariablesProvider, + @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, + @inject(ICommandManager) private readonly commandManager: ICommandManager, + @inject(IFileSystem) private readonly fileSystem: IFileSystem, + @inject(IExtensions) private readonly extensions: IExtensions, + @inject(IApplicationShell) readonly applicationShell: IApplicationShell, + @inject(IDisposableRegistry) readonly disposables: IDisposableRegistry, + ) { + this.workspaceInterpreters = new Map(); + this.workspaceLanguageServers = new Map(); + this.languageServerType = this.configurationService.getSettings().languageServer; + } + + // IExtensionActivationService + + public async activate(resource?: Resource, startupStopWatch?: StopWatch): Promise { + this.register(); + await this.startLanguageServer(this.languageServerType, resource, startupStopWatch); + } + + // ILanguageServerWatcher + public async startLanguageServer( + languageServerType: LanguageServerType, + resource?: Resource, + startupStopWatch?: StopWatch, + ): Promise { + await this.startAndGetLanguageServer(languageServerType, resource, startupStopWatch); + } + + public register(): void { + if (!this.registered) { + this.registered = true; + this.disposables.push( + this.workspaceService.onDidChangeConfiguration(this.onDidChangeConfiguration.bind(this)), + ); + + this.disposables.push( + this.workspaceService.onDidChangeWorkspaceFolders(this.onDidChangeWorkspaceFolders.bind(this)), + ); + + this.disposables.push( + this.interpreterService.onDidChangeInterpreterInformation(this.onDidChangeInterpreterInformation, this), + ); + + if (this.workspaceService.isTrusted) { + this.disposables.push(this.interpreterPathService.onDidChange(this.onDidChangeInterpreter.bind(this))); + } + + this.disposables.push( + this.extensions.onDidChange(async () => { + await this.extensionsChangeHandler(); + }), + ); + + this.disposables.push( + new LanguageServerChangeHandler( + this.languageServerType, + this.extensions, + this.applicationShell, + this.commandManager, + this.workspaceService, + this.configurationService, + ), + ); + } + } + + private async startAndGetLanguageServer( + languageServerType: LanguageServerType, + resource?: Resource, + startupStopWatch?: StopWatch, + ): Promise { + const lsResource = this.getWorkspaceUri(resource); + const currentInterpreter = this.workspaceInterpreters.get(lsResource.fsPath); + const interpreter = await this.interpreterService?.getActiveInterpreter(resource); + + // Destroy the old language server if it's different. + if (currentInterpreter && interpreter !== currentInterpreter) { + await this.stopLanguageServer(lsResource); + } + + // If the interpreter is Python 2 and the LS setting is explicitly set to Jedi, turn it off. + // If set to Default, use Pylance. + let serverType = languageServerType; + if (interpreter && (interpreter.version?.major ?? 0) < 3) { + if (serverType === LanguageServerType.Jedi) { + serverType = LanguageServerType.None; + } else if (this.getCurrentLanguageServerTypeIsDefault()) { + serverType = LanguageServerType.Node; + } + } + + if ( + !this.workspaceService.isTrusted && + serverType !== LanguageServerType.Node && + serverType !== LanguageServerType.None + ) { + traceLog(LanguageService.untrustedWorkspaceMessage); + serverType = LanguageServerType.None; + } + + // If the language server type is Pylance or None, + // We only need to instantiate the language server once, even in multiroot workspace scenarios, + // so we only need one language server extension manager. + const key = this.getWorkspaceKey(resource, serverType); + const languageServer = this.workspaceLanguageServers.get(key); + if ((serverType === LanguageServerType.Node || serverType === LanguageServerType.None) && languageServer) { + logStartup(serverType, lsResource); + return languageServer; + } + + // Instantiate the language server extension manager. + const languageServerExtensionManager = this.createLanguageServer(serverType); + this.workspaceLanguageServers.set(key, languageServerExtensionManager); + + if (languageServerExtensionManager.canStartLanguageServer(interpreter)) { + // Start the language server. + if (startupStopWatch) { + // It means that startup is triggering this code, track time it takes since startup to activate this code. + sendTelemetryEvent(EventName.LANGUAGE_SERVER_TRIGGER_TIME, startupStopWatch.elapsedTime, { + triggerTime: startupStopWatch.elapsedTime, + }); + } + await languageServerExtensionManager.startLanguageServer(lsResource, interpreter); + + logStartup(languageServerType, lsResource); + this.languageServerType = languageServerType; + this.workspaceInterpreters.set(lsResource.fsPath, interpreter); + } else { + await languageServerExtensionManager.languageServerNotAvailable(); + } + + return languageServerExtensionManager; + } + + public async restartLanguageServers(): Promise { + this.workspaceLanguageServers.forEach(async (_, resourceString) => { + sendTelemetryEvent(EventName.LANGUAGE_SERVER_RESTART, undefined, { reason: 'notebooksExperiment' }); + const resource = Uri.parse(resourceString); + await this.stopLanguageServer(resource); + await this.startLanguageServer(this.languageServerType, resource); + }); + } + + public async get(resource?: Resource): Promise { + const key = this.getWorkspaceKey(resource, this.languageServerType); + let languageServerExtensionManager = this.workspaceLanguageServers.get(key); + + if (!languageServerExtensionManager) { + languageServerExtensionManager = await this.startAndGetLanguageServer(this.languageServerType, resource); + } + + return Promise.resolve(languageServerExtensionManager); + } + + // Private methods + + private async stopLanguageServer(resource?: Resource): Promise { + const key = this.getWorkspaceKey(resource, this.languageServerType); + const languageServerExtensionManager = this.workspaceLanguageServers.get(key); + + if (languageServerExtensionManager) { + await languageServerExtensionManager.stopLanguageServer(); + languageServerExtensionManager.dispose(); + this.workspaceLanguageServers.delete(key); + } + } + + private createLanguageServer(languageServerType: LanguageServerType): ILanguageServerExtensionManager { + let lsManager: ILanguageServerExtensionManager; + switch (languageServerType) { + case LanguageServerType.Jedi: + lsManager = new JediLSExtensionManager( + this.serviceContainer, + this.lsOutputChannel, + this.experimentService, + this.workspaceService, + this.configurationService, + this.interpreterPathService, + this.interpreterService, + this.environmentService, + this.commandManager, + ); + break; + case LanguageServerType.Node: + lsManager = new PylanceLSExtensionManager( + this.serviceContainer, + this.lsOutputChannel, + this.experimentService, + this.workspaceService, + this.configurationService, + this.interpreterPathService, + this.interpreterService, + this.environmentService, + this.commandManager, + this.fileSystem, + this.extensions, + this.applicationShell, + ); + break; + case LanguageServerType.None: + default: + lsManager = new NoneLSExtensionManager(); + break; + } + + this.disposables.push({ + dispose: async () => { + await lsManager.stopLanguageServer(); + lsManager.dispose(); + }, + }); + return lsManager; + } + + private async refreshLanguageServer(resource?: Resource, forced?: boolean): Promise { + const lsResource = this.getWorkspaceUri(resource); + const languageServerType = this.configurationService.getSettings(lsResource).languageServer; + + if (languageServerType !== this.languageServerType || forced) { + await this.stopLanguageServer(resource); + await this.startLanguageServer(languageServerType, lsResource); + } + } + + private getCurrentLanguageServerTypeIsDefault(): boolean { + return this.configurationService.getSettings().languageServerIsDefault; + } + + // Watch for settings changes. + private async onDidChangeConfiguration(event: ConfigurationChangeEvent): Promise { + const workspacesUris = this.workspaceService.workspaceFolders?.map((workspace) => workspace.uri) ?? []; + + workspacesUris.forEach(async (resource) => { + if (event.affectsConfiguration(`python.languageServer`, resource)) { + await this.refreshLanguageServer(resource); + } else if (event.affectsConfiguration(`python.analysis.pylanceLspClientEnabled`, resource)) { + await this.refreshLanguageServer(resource, /* forced */ true); + } + }); + } + + // Watch for interpreter changes. + private async onDidChangeInterpreter(event: InterpreterConfigurationScope): Promise { + if (this.languageServerType === LanguageServerType.Node) { + // Pylance client already handles interpreter changes, so restarting LS can be skipped. + return Promise.resolve(); + } + // Reactivate the language server (if in a multiroot workspace scenario, pick the correct one). + return this.activate(event.uri); + } + + // Watch for interpreter information changes. + private async onDidChangeInterpreterInformation(info: PythonEnvironment): Promise { + if (!info.envPath || info.envPath === '') { + return; + } + + // Find the interpreter and workspace that got updated (if any). + const iterator = this.workspaceInterpreters.entries(); + + let result = iterator.next(); + let done = result.done || false; + + while (!done) { + const [resourcePath, interpreter] = result.value as [string, PythonEnvironment | undefined]; + const resource = Uri.parse(resourcePath); + + // Restart the language server if the interpreter path changed (#18995). + if (info.envPath === interpreter?.envPath && info.path !== interpreter?.path) { + await this.activate(resource); + done = true; + } else { + result = iterator.next(); + done = result.done || false; + } + } + } + + // Watch for extension changes. + private async extensionsChangeHandler(): Promise { + const languageServerType = this.configurationService.getSettings().languageServer; + + if (languageServerType !== this.languageServerType) { + await this.refreshLanguageServer(); + } + } + + // Watch for workspace folder changes. + private async onDidChangeWorkspaceFolders(event: WorkspaceFoldersChangeEvent): Promise { + // Since Jedi is the only language server type where we instantiate multiple language servers, + // Make sure to dispose of them only in that scenario. + if (event.removed.length && this.languageServerType === LanguageServerType.Jedi) { + for (const workspace of event.removed) { + await this.stopLanguageServer(workspace.uri); + } + } + } + + // Get the workspace Uri for the given resource, in order to query this.workspaceInterpreters and this.workspaceLanguageServers. + private getWorkspaceUri(resource?: Resource): Uri { + let uri; + + if (resource) { + uri = this.workspaceService.getWorkspaceFolder(resource)?.uri; + } else { + uri = this.interpreterHelper.getActiveWorkspaceUri(resource)?.folderUri; + } + + return uri ?? Uri.parse('default'); + } + + // Get the key used to identify which language server extension manager is associated to which workspace. + // When using Pylance or having no LS enabled, we return a static key since there should only be one LS extension manager for these LS types. + private getWorkspaceKey(resource: Resource | undefined, languageServerType: LanguageServerType): string { + switch (languageServerType) { + case LanguageServerType.Node: + return 'Pylance'; + case LanguageServerType.None: + return 'None'; + default: + return this.getWorkspaceUri(resource).fsPath; + } + } +} + +function logStartup(languageServerType: LanguageServerType, resource: Uri): void { + let outputLine; + const basename = path.basename(resource.fsPath); + + switch (languageServerType) { + case LanguageServerType.Jedi: + outputLine = l10n.t('Starting Jedi language server for {0}.', basename); + break; + case LanguageServerType.Node: + outputLine = LanguageService.startingPylance; + break; + case LanguageServerType.None: + outputLine = LanguageService.startingNone; + break; + default: + throw new Error(`Unknown language server type: ${languageServerType}`); + } + traceLog(outputLine); +} diff --git a/src/client/languageServices/jediProxyFactory.ts b/src/client/languageServices/jediProxyFactory.ts deleted file mode 100644 index 9a491ecf3889..000000000000 --- a/src/client/languageServices/jediProxyFactory.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Disposable, Uri, workspace } from 'vscode'; - -import { IServiceContainer } from '../ioc/types'; -import { ICommandResult, JediProxy, JediProxyHandler } from '../providers/jediProxy'; -import { PythonEnvironment } from '../pythonEnvironments/info'; - -export class JediFactory implements Disposable { - private disposables: Disposable[]; - private jediProxyHandlers: Map>; - - constructor( - private interpreter: PythonEnvironment | undefined, - // This is passed through to JediProxy(). - private serviceContainer: IServiceContainer - ) { - this.disposables = []; - this.jediProxyHandlers = new Map>(); - } - - public dispose() { - this.disposables.forEach((disposable) => disposable.dispose()); - this.disposables = []; - } - - public getJediProxyHandler(resource?: Uri): JediProxyHandler { - const workspacePath = this.getWorkspacePath(resource); - if (!this.jediProxyHandlers.has(workspacePath)) { - const jediProxy = new JediProxy(workspacePath, this.interpreter, this.serviceContainer); - const jediProxyHandler = new JediProxyHandler(jediProxy); - this.disposables.push(jediProxy, jediProxyHandler); - this.jediProxyHandlers.set(workspacePath, jediProxyHandler); - } - return this.jediProxyHandlers.get(workspacePath)! as JediProxyHandler; - } - - private getWorkspacePath(resource?: Uri): string { - if (resource) { - const workspaceFolder = workspace.getWorkspaceFolder(resource); - if (workspaceFolder) { - return workspaceFolder.uri.fsPath; - } - } - - if (Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length > 0) { - return workspace.workspaceFolders[0].uri.fsPath; - } else { - return __dirname; - } - } -} diff --git a/src/client/languageServices/proposeLanguageServerBanner.ts b/src/client/languageServices/proposeLanguageServerBanner.ts deleted file mode 100644 index 59096edc2271..000000000000 --- a/src/client/languageServices/proposeLanguageServerBanner.ts +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { LanguageServerType } from '../activation/types'; -import { IApplicationEnvironment, IApplicationShell } from '../common/application/types'; -import { PYLANCE_EXTENSION_ID } from '../common/constants'; -import { TryPylance } from '../common/experiments/groups'; -import '../common/extensions'; -import { - IConfigurationService, - IExperimentService, - IExtensions, - IPersistentStateFactory, - IPythonExtensionBanner -} from '../common/types'; -import { Common, Pylance } from '../common/utils/localize'; -import { sendTelemetryEvent } from '../telemetry'; -import { EventName } from '../telemetry/constants'; - -export function getPylanceExtensionUri(appEnv: IApplicationEnvironment): string { - return `${appEnv.uriScheme}:extension/${PYLANCE_EXTENSION_ID}`; -} - -// persistent state names, exported to make use of in testing -export enum ProposeLSStateKeys { - ShowBanner = 'TryPylanceBanner' -} - -/* -This class represents a popup that propose that the user try out a new -feature of the extension, and optionally enable that new feature if they -choose to do so. It is meant to be shown only to a subset of our users, -and will show as soon as it is instructed to do so, if a random sample -function enables the popup for this user. -*/ -@injectable() -export class ProposePylanceBanner implements IPythonExtensionBanner { - private disabledInCurrentSession: boolean = false; - - constructor( - @inject(IApplicationShell) private appShell: IApplicationShell, - @inject(IApplicationEnvironment) private appEnv: IApplicationEnvironment, - @inject(IPersistentStateFactory) private persistentState: IPersistentStateFactory, - @inject(IConfigurationService) private configuration: IConfigurationService, - @inject(IExperimentService) private experiments: IExperimentService, - @inject(IExtensions) readonly extensions: IExtensions - ) {} - - public get enabled(): boolean { - const lsType = this.configuration.getSettings().languageServer ?? LanguageServerType.Jedi; - if (lsType === LanguageServerType.Jedi || lsType === LanguageServerType.Node) { - return false; - } - return this.persistentState.createGlobalPersistentState(ProposeLSStateKeys.ShowBanner, true).value; - } - - public async showBanner(): Promise { - if (!this.enabled) { - return; - } - - const show = await this.shouldShowBanner(); - if (!show) { - return; - } - - const response = await this.appShell.showInformationMessage( - Pylance.proposePylanceMessage(), - Pylance.tryItNow(), - Common.bannerLabelNo(), - Pylance.remindMeLater() - ); - - let userAction: string; - if (response === Pylance.tryItNow()) { - this.appShell.openUrl(getPylanceExtensionUri(this.appEnv)); - userAction = 'yes'; - await this.disable(); - } else if (response === Common.bannerLabelNo()) { - await this.disable(); - userAction = 'no'; - } else { - this.disabledInCurrentSession = true; - userAction = 'later'; - } - sendTelemetryEvent(EventName.LANGUAGE_SERVER_TRY_PYLANCE, undefined, { userAction }); - } - - public async shouldShowBanner(): Promise { - // Do not prompt if Pylance is already installed. - if (this.extensions.getExtension(PYLANCE_EXTENSION_ID)) { - return false; - } - // Only prompt for users in experiment. - const inExperiment = await this.experiments.inExperiment(TryPylance.experiment); - return inExperiment && this.enabled && !this.disabledInCurrentSession; - } - - public async disable(): Promise { - await this.persistentState - .createGlobalPersistentState(ProposeLSStateKeys.ShowBanner, false) - .updateValue(false); - } -} diff --git a/src/client/linters/bandit.ts b/src/client/linters/bandit.ts deleted file mode 100644 index 137d95d48275..000000000000 --- a/src/client/linters/bandit.ts +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { CancellationToken, OutputChannel, TextDocument } from 'vscode'; -import '../common/extensions'; -import { Product } from '../common/types'; -import { IServiceContainer } from '../ioc/types'; -import { BaseLinter } from './baseLinter'; -import { ILintMessage, LintMessageSeverity } from './types'; - -const severityMapping: Record = { - LOW: LintMessageSeverity.Information, - MEDIUM: LintMessageSeverity.Warning, - HIGH: LintMessageSeverity.Error -}; - -export class Bandit extends BaseLinter { - constructor(outputChannel: OutputChannel, serviceContainer: IServiceContainer) { - super(Product.bandit, outputChannel, serviceContainer); - } - - protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise { - // View all errors in bandit <= 1.5.1 (https://github.com/PyCQA/bandit/issues/371) - const messages = await this.run( - ['-f', 'custom', '--msg-template', '{line},0,{severity},{test_id}:{msg}', '-n', '-1', document.uri.fsPath], - document, - cancellation - ); - - messages.forEach((msg) => { - msg.severity = severityMapping[msg.type]; - }); - return messages; - } -} diff --git a/src/client/linters/baseLinter.ts b/src/client/linters/baseLinter.ts deleted file mode 100644 index 3799e31660b3..000000000000 --- a/src/client/linters/baseLinter.ts +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import * as path from 'path'; -import * as vscode from 'vscode'; -import { IWorkspaceService } from '../common/application/types'; -import { isTestExecution } from '../common/constants'; -import '../common/extensions'; -import { traceError } from '../common/logger'; -import { IPythonToolExecutionService } from '../common/process/types'; -import { ExecutionInfo, IConfigurationService, IPythonSettings, Product } from '../common/types'; -import { IServiceContainer } from '../ioc/types'; -import { ErrorHandler } from './errorHandlers/errorHandler'; -import { ILinter, ILinterInfo, ILinterManager, ILintMessage, LinterId, LintMessageSeverity } from './types'; - -// tslint:disable-next-line:no-require-imports no-var-requires no-any -const namedRegexp = require('named-js-regexp'); -// Allow negative column numbers (https://github.com/PyCQA/pylint/issues/1822) -// Allow codes with more than one letter (i.e. ABC123) -const REGEX = '(?\\d+),(?-?\\d+),(?\\w+),(?\\w+\\d+):(?.*)\\r?(\\n|$)'; - -export interface IRegexGroup { - line: number; - column: number; - code: string; - message: string; - type: string; -} - -export function matchNamedRegEx(data: string, regex: string): IRegexGroup | undefined { - const compiledRegexp = namedRegexp(regex, 'g'); - const rawMatch = compiledRegexp.exec(data); - if (rawMatch !== null) { - return rawMatch.groups(); - } - - return undefined; -} - -export function parseLine( - line: string, - regex: string, - linterID: LinterId, - colOffset: number = 0 -): ILintMessage | undefined { - const match = matchNamedRegEx(line, regex)!; - if (!match) { - return; - } - - // tslint:disable-next-line:no-any - match.line = Number(match.line); - // tslint:disable-next-line:no-any - match.column = Number(match.column); - - return { - code: match.code, - message: match.message, - column: isNaN(match.column) || match.column <= 0 ? 0 : match.column - colOffset, - line: match.line, - type: match.type, - provider: linterID - }; -} - -export abstract class BaseLinter implements ILinter { - protected readonly configService: IConfigurationService; - - private errorHandler: ErrorHandler; - private _pythonSettings!: IPythonSettings; - private _info: ILinterInfo; - private workspace: IWorkspaceService; - - protected get pythonSettings(): IPythonSettings { - return this._pythonSettings; - } - - constructor( - product: Product, - protected readonly outputChannel: vscode.OutputChannel, - protected readonly serviceContainer: IServiceContainer, - protected readonly columnOffset = 0 - ) { - this._info = serviceContainer.get(ILinterManager).getLinterInfo(product); - this.errorHandler = new ErrorHandler(this.info.product, outputChannel, serviceContainer); - this.configService = serviceContainer.get(IConfigurationService); - this.workspace = serviceContainer.get(IWorkspaceService); - } - - public get info(): ILinterInfo { - return this._info; - } - - public async lint(document: vscode.TextDocument, cancellation: vscode.CancellationToken): Promise { - this._pythonSettings = this.configService.getSettings(document.uri); - return this.runLinter(document, cancellation); - } - - protected getWorkspaceRootPath(document: vscode.TextDocument): string { - const workspaceFolder = this.workspace.getWorkspaceFolder(document.uri); - const workspaceRootPath = - workspaceFolder && typeof workspaceFolder.uri.fsPath === 'string' ? workspaceFolder.uri.fsPath : undefined; - return typeof workspaceRootPath === 'string' ? workspaceRootPath : path.dirname(document.uri.fsPath); - } - protected abstract runLinter( - document: vscode.TextDocument, - cancellation: vscode.CancellationToken - ): Promise; - - // tslint:disable-next-line:no-any - protected parseMessagesSeverity(error: string, categorySeverity: any): LintMessageSeverity { - if (categorySeverity[error]) { - const severityName = categorySeverity[error]; - switch (severityName) { - case 'Error': - return LintMessageSeverity.Error; - case 'Hint': - return LintMessageSeverity.Hint; - case 'Information': - return LintMessageSeverity.Information; - case 'Warning': - return LintMessageSeverity.Warning; - default: { - if (LintMessageSeverity[severityName]) { - // tslint:disable-next-line:no-any - return (LintMessageSeverity[severityName]); - } - } - } - } - return LintMessageSeverity.Information; - } - - protected async run( - args: string[], - document: vscode.TextDocument, - cancellation: vscode.CancellationToken, - regEx: string = REGEX - ): Promise { - if (!this.info.isEnabled(document.uri)) { - return []; - } - const executionInfo = this.info.getExecutionInfo(args, document.uri); - const cwd = this.getWorkspaceRootPath(document); - const pythonToolsExecutionService = this.serviceContainer.get( - IPythonToolExecutionService - ); - try { - const result = await pythonToolsExecutionService.exec( - executionInfo, - { cwd, token: cancellation, mergeStdOutErr: false }, - document.uri - ); - this.displayLinterResultHeader(result.stdout); - return await this.parseMessages(result.stdout, document, cancellation, regEx); - } catch (error) { - await this.handleError(error, document.uri, executionInfo); - return []; - } - } - - protected async parseMessages( - output: string, - _document: vscode.TextDocument, - _token: vscode.CancellationToken, - regEx: string - ) { - const outputLines = output.splitLines({ removeEmptyEntries: false, trim: false }); - return this.parseLines(outputLines, regEx); - } - - protected async handleError(error: Error, resource: vscode.Uri, execInfo: ExecutionInfo) { - if (isTestExecution()) { - this.errorHandler.handleError(error, resource, execInfo).ignoreErrors(); - } else { - this.errorHandler - .handleError(error, resource, execInfo) - .catch((ex) => traceError('Error in errorHandler.handleError', ex)) - .ignoreErrors(); - } - } - - private parseLine(line: string, regEx: string): ILintMessage | undefined { - return parseLine(line, regEx, this.info.id, this.columnOffset); - } - - private parseLines(outputLines: string[], regEx: string): ILintMessage[] { - const messages: ILintMessage[] = []; - for (const line of outputLines) { - try { - const msg = this.parseLine(line, regEx); - if (msg) { - messages.push(msg); - if (messages.length >= this.pythonSettings.linting.maxNumberOfProblems) { - break; - } - } - } catch (ex) { - traceError(`Linter '${this.info.id}' failed to parse the line '${line}.`, ex); - } - } - return messages; - } - - private displayLinterResultHeader(data: string) { - this.outputChannel.append(`${'#'.repeat(10)}Linting Output - ${this.info.id}${'#'.repeat(10)}\n`); - this.outputChannel.append(data); - } -} diff --git a/src/client/linters/constants.ts b/src/client/linters/constants.ts deleted file mode 100644 index 3f1bfa3f5dcc..000000000000 --- a/src/client/linters/constants.ts +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { Product } from '../common/types'; -import { LinterId } from './types'; - -// All supported linters must be in this map. -export const LINTERID_BY_PRODUCT = new Map([ - [Product.bandit, LinterId.Bandit], - [Product.flake8, LinterId.Flake8], - [Product.pylint, LinterId.PyLint], - [Product.mypy, LinterId.MyPy], - [Product.pycodestyle, LinterId.PyCodeStyle], - [Product.prospector, LinterId.Prospector], - [Product.pydocstyle, LinterId.PyDocStyle], - [Product.pylama, LinterId.PyLama] -]); diff --git a/src/client/linters/errorHandlers/baseErrorHandler.ts b/src/client/linters/errorHandlers/baseErrorHandler.ts deleted file mode 100644 index 7ef13f4e615c..000000000000 --- a/src/client/linters/errorHandlers/baseErrorHandler.ts +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { OutputChannel, Uri } from 'vscode'; -import { ExecutionInfo, IInstaller, Product } from '../../common/types'; -import { IServiceContainer } from '../../ioc/types'; -import { IErrorHandler } from '../types'; - -export abstract class BaseErrorHandler implements IErrorHandler { - protected installer: IInstaller; - - private handler?: IErrorHandler; - - constructor( - protected product: Product, - protected outputChannel: OutputChannel, - protected serviceContainer: IServiceContainer - ) { - this.installer = this.serviceContainer.get(IInstaller); - } - protected get nextHandler(): IErrorHandler | undefined { - return this.handler; - } - public setNextHandler(handler: IErrorHandler): void { - this.handler = handler; - } - public abstract handleError(error: Error, resource: Uri, execInfo: ExecutionInfo): Promise; -} diff --git a/src/client/linters/errorHandlers/errorHandler.ts b/src/client/linters/errorHandlers/errorHandler.ts deleted file mode 100644 index ea4009ddd85d..000000000000 --- a/src/client/linters/errorHandlers/errorHandler.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { OutputChannel, Uri } from 'vscode'; -import { ExecutionInfo, Product } from '../../common/types'; -import { IServiceContainer } from '../../ioc/types'; -import { IErrorHandler } from '../types'; -import { BaseErrorHandler } from './baseErrorHandler'; -import { NotInstalledErrorHandler } from './notInstalled'; -import { StandardErrorHandler } from './standard'; - -export class ErrorHandler implements IErrorHandler { - private handler: BaseErrorHandler; - constructor(product: Product, outputChannel: OutputChannel, serviceContainer: IServiceContainer) { - // Create chain of handlers. - const standardErrorHandler = new StandardErrorHandler(product, outputChannel, serviceContainer); - this.handler = new NotInstalledErrorHandler(product, outputChannel, serviceContainer); - this.handler.setNextHandler(standardErrorHandler); - } - - public handleError(error: Error, resource: Uri, execInfo: ExecutionInfo): Promise { - return this.handler.handleError(error, resource, execInfo); - } -} diff --git a/src/client/linters/errorHandlers/notInstalled.ts b/src/client/linters/errorHandlers/notInstalled.ts deleted file mode 100644 index 16871e7ee71f..000000000000 --- a/src/client/linters/errorHandlers/notInstalled.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { OutputChannel, Uri } from 'vscode'; -import { traceError, traceWarning } from '../../common/logger'; -import { IPythonExecutionFactory } from '../../common/process/types'; -import { ExecutionInfo, Product } from '../../common/types'; -import { IServiceContainer } from '../../ioc/types'; -import { ILinterManager } from '../types'; -import { BaseErrorHandler } from './baseErrorHandler'; - -export class NotInstalledErrorHandler extends BaseErrorHandler { - constructor(product: Product, outputChannel: OutputChannel, serviceContainer: IServiceContainer) { - super(product, outputChannel, serviceContainer); - } - public async handleError(error: Error, resource: Uri, execInfo: ExecutionInfo): Promise { - const pythonExecutionService = await this.serviceContainer - .get(IPythonExecutionFactory) - .create({ resource }); - const isModuleInstalled = await pythonExecutionService.isModuleInstalled(execInfo.moduleName!); - if (isModuleInstalled) { - return this.nextHandler ? this.nextHandler.handleError(error, resource, execInfo) : false; - } - - this.installer - .promptToInstall(this.product, resource) - .catch((ex) => traceError('NotInstalledErrorHandler.promptToInstall', ex)); - - const linterManager = this.serviceContainer.get(ILinterManager); - const info = linterManager.getLinterInfo(execInfo.product!); - const customError = `Linter '${info.id}' is not installed. Please install it or select another linter".`; - this.outputChannel.appendLine(`\n${customError}\n${error}`); - traceWarning(customError, error); - return true; - } -} diff --git a/src/client/linters/errorHandlers/standard.ts b/src/client/linters/errorHandlers/standard.ts deleted file mode 100644 index 088206269c8f..000000000000 --- a/src/client/linters/errorHandlers/standard.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { OutputChannel, Uri } from 'vscode'; -import { IApplicationShell } from '../../common/application/types'; -import { traceError } from '../../common/logger'; -import { ExecutionInfo, Product } from '../../common/types'; -import { IServiceContainer } from '../../ioc/types'; -import { ILinterManager, LinterId } from '../types'; -import { BaseErrorHandler } from './baseErrorHandler'; - -export class StandardErrorHandler extends BaseErrorHandler { - constructor(product: Product, outputChannel: OutputChannel, serviceContainer: IServiceContainer) { - super(product, outputChannel, serviceContainer); - } - public async handleError(error: Error, resource: Uri, execInfo: ExecutionInfo): Promise { - if ( - typeof error === 'string' && - (error as string).indexOf("OSError: [Errno 2] No such file or directory: '/") > 0 - ) { - return this.nextHandler ? this.nextHandler.handleError(error, resource, execInfo) : Promise.resolve(false); - } - - const linterManager = this.serviceContainer.get(ILinterManager); - const info = linterManager.getLinterInfo(execInfo.product!); - - traceError(`There was an error in running the linter ${info.id}`, error); - this.outputChannel.appendLine(`Linting with ${info.id} failed.`); - this.outputChannel.appendLine(error.toString()); - - this.displayLinterError(info.id).ignoreErrors(); - return true; - } - private async displayLinterError(linterId: LinterId) { - const message = `There was an error in running the linter '${linterId}'`; - const appShell = this.serviceContainer.get(IApplicationShell); - await appShell.showErrorMessage(message, 'View Errors'); - this.outputChannel.show(); - } -} diff --git a/src/client/linters/flake8.ts b/src/client/linters/flake8.ts deleted file mode 100644 index 5d2cd0d4e6c1..000000000000 --- a/src/client/linters/flake8.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { CancellationToken, OutputChannel, TextDocument } from 'vscode'; -import '../common/extensions'; -import { Product } from '../common/types'; -import { IServiceContainer } from '../ioc/types'; -import { BaseLinter } from './baseLinter'; -import { ILintMessage } from './types'; - -const COLUMN_OFF_SET = 1; - -export class Flake8 extends BaseLinter { - constructor(outputChannel: OutputChannel, serviceContainer: IServiceContainer) { - super(Product.flake8, outputChannel, serviceContainer, COLUMN_OFF_SET); - } - - protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise { - const messages = await this.run( - ['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', document.uri.fsPath], - document, - cancellation - ); - messages.forEach((msg) => { - msg.severity = this.parseMessagesSeverity(msg.type, this.pythonSettings.linting.flake8CategorySeverity); - // flake8 uses 0th line for some file-wide problems - // but diagnostics expects positive line numbers. - if (msg.line === 0) { - msg.line = 1; - } - }); - return messages; - } -} diff --git a/src/client/linters/linterAvailability.ts b/src/client/linters/linterAvailability.ts deleted file mode 100644 index 834a86284610..000000000000 --- a/src/client/linters/linterAvailability.ts +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { Uri } from 'vscode'; -import { LanguageServerType } from '../activation/types'; -import { IApplicationShell, IWorkspaceService } from '../common/application/types'; -import '../common/extensions'; -import { IFileSystem } from '../common/platform/types'; -import { IConfigurationService, IPersistentStateFactory, Resource } from '../common/types'; -import { Common, Linters } from '../common/utils/localize'; -import { sendTelemetryEvent } from '../telemetry'; -import { EventName } from '../telemetry/constants'; -import { IAvailableLinterActivator, ILinterInfo } from './types'; - -const doNotDisplayPromptStateKey = 'MESSAGE_KEY_FOR_CONFIGURE_AVAILABLE_LINTER_PROMPT'; -@injectable() -export class AvailableLinterActivator implements IAvailableLinterActivator { - constructor( - @inject(IApplicationShell) private appShell: IApplicationShell, - @inject(IFileSystem) private fs: IFileSystem, - @inject(IWorkspaceService) private workspaceService: IWorkspaceService, - @inject(IConfigurationService) private configService: IConfigurationService, - @inject(IPersistentStateFactory) private persistentStateFactory: IPersistentStateFactory - ) {} - - /** - * Check if it is possible to enable an otherwise-unconfigured linter in - * the current workspace, and if so ask the user if they want that linter - * configured explicitly. - * - * @param linterInfo The linter to check installation status. - * @param resource Context for the operation (required when in multi-root workspaces). - * - * @returns true if configuration was updated in any way, false otherwise. - */ - public async promptIfLinterAvailable(linterInfo: ILinterInfo, resource?: Uri): Promise { - // Has the feature been enabled yet? - if (!this.isFeatureEnabled) { - return false; - } - - // Has the linter in question has been configured explicitly? If so, no need to continue. - if (!this.isLinterUsingDefaultConfiguration(linterInfo, resource)) { - return false; - } - - // Is the linter available in the current workspace? - if (await this.isLinterAvailable(linterInfo, resource)) { - // great, it is - ask the user if they'd like to enable it. - return this.promptToConfigureAvailableLinter(linterInfo); - } - return false; - } - - /** - * Raise a dialog asking the user if they would like to explicitly configure a - * linter or not in their current workspace. - * - * @param linterInfo The linter to ask the user to enable or not. - * - * @returns true if the user requested a configuration change, false otherwise. - */ - public async promptToConfigureAvailableLinter(linterInfo: ILinterInfo): Promise { - const notificationPromptEnabled = this.persistentStateFactory.createWorkspacePersistentState( - doNotDisplayPromptStateKey, - true - ); - if (!notificationPromptEnabled.value) { - return false; - } - const optButtons = [Linters.enableLinter().format(linterInfo.id), Common.notNow(), Common.doNotShowAgain()]; - - const telemetrySelections: ['enable', 'ignore', 'disablePrompt'] = ['enable', 'ignore', 'disablePrompt']; - const pick = await this.appShell.showInformationMessage( - Linters.enablePylint().format(linterInfo.id), - ...optButtons - ); - sendTelemetryEvent(EventName.CONFIGURE_AVAILABLE_LINTER_PROMPT, undefined, { - tool: linterInfo.id, - action: pick ? telemetrySelections[optButtons.indexOf(pick)] : undefined - }); - if (pick === optButtons[0]) { - await linterInfo.enableAsync(true); - return true; - } else if (pick === optButtons[2]) { - await notificationPromptEnabled.updateValue(false); - } - return false; - } - - /** - * Check if the linter itself is available in the workspace's Python environment or - * not. - * - * @param linterInfo Linter to check in the current workspace environment. - * @param resource Context information for workspace. - */ - public async isLinterAvailable(linterInfo: ILinterInfo, resource: Resource): Promise { - if (!this.workspaceService.hasWorkspaceFolders) { - return false; - } - const workspaceFolder = - this.workspaceService.getWorkspaceFolder(resource) || this.workspaceService.workspaceFolders![0]; - let isAvailable = false; - for (const configName of linterInfo.configFileNames) { - const configPath = path.join(workspaceFolder.uri.fsPath, configName); - isAvailable = isAvailable || (await this.fs.fileExists(configPath)); - } - return isAvailable; - } - - /** - * Check if the given linter has been configured by the user in this workspace or not. - * - * @param linterInfo Linter to check for configuration status. - * @param resource Context information. - * - * @returns true if the linter has not been configured at the user, workspace, or workspace-folder scope. false otherwise. - */ - public isLinterUsingDefaultConfiguration(linterInfo: ILinterInfo, resource?: Uri): boolean { - const ws = this.workspaceService.getConfiguration('python.linting', resource); - const pe = ws!.inspect(linterInfo.enabledSettingName); - return ( - pe!.globalValue === undefined && pe!.workspaceValue === undefined && pe!.workspaceFolderValue === undefined - ); - } - - /** - * Check if this feature is enabled yet. - * - * This is a feature of the vscode-python extension that will become enabled once the - * Python Language Server becomes the default, replacing Jedi as the default. Testing - * the global default setting for `"python.languageServer": !Jedi` enables it. - * - * @returns true if the global default for python.languageServer is not Jedi. - */ - public get isFeatureEnabled(): boolean { - return this.configService.getSettings().languageServer !== LanguageServerType.Jedi; - } -} diff --git a/src/client/linters/linterCommands.ts b/src/client/linters/linterCommands.ts deleted file mode 100644 index 3f7f11dcc206..000000000000 --- a/src/client/linters/linterCommands.ts +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import { DiagnosticCollection, Disposable, QuickPickOptions, Uri } from 'vscode'; -import { IApplicationShell, ICommandManager, IDocumentManager } from '../common/application/types'; -import { Commands } from '../common/constants'; -import { IDisposable } from '../common/types'; -import { Linters } from '../common/utils/localize'; -import { IServiceContainer } from '../ioc/types'; -import { sendTelemetryEvent } from '../telemetry'; -import { EventName } from '../telemetry/constants'; -import { ILinterManager, ILintingEngine, LinterId } from './types'; - -export class LinterCommands implements IDisposable { - private disposables: Disposable[] = []; - private linterManager: ILinterManager; - private readonly appShell: IApplicationShell; - private readonly documentManager: IDocumentManager; - - constructor(private serviceContainer: IServiceContainer) { - this.linterManager = this.serviceContainer.get(ILinterManager); - this.appShell = this.serviceContainer.get(IApplicationShell); - this.documentManager = this.serviceContainer.get(IDocumentManager); - - const commandManager = this.serviceContainer.get(ICommandManager); - commandManager.registerCommand(Commands.Set_Linter, this.setLinterAsync.bind(this)); - commandManager.registerCommand(Commands.Enable_Linter, this.enableLintingAsync.bind(this)); - commandManager.registerCommand(Commands.Run_Linter, this.runLinting.bind(this)); - } - public dispose() { - this.disposables.forEach((disposable) => disposable.dispose()); - } - - public async setLinterAsync(): Promise { - const linters = this.linterManager.getAllLinterInfos(); - const suggestions = linters.map((x) => x.id).sort(); - const linterList = ['Disable Linting', ...suggestions]; - const activeLinters = await this.linterManager.getActiveLinters(true, this.settingsUri); - - let current: string; - switch (activeLinters.length) { - case 0: - current = 'none'; - break; - case 1: - current = activeLinters[0].id; - break; - default: - current = 'multiple selected'; - break; - } - - const quickPickOptions: QuickPickOptions = { - matchOnDetail: true, - matchOnDescription: true, - placeHolder: `current: ${current}` - }; - - const selection = await this.appShell.showQuickPick(linterList, quickPickOptions); - if (selection !== undefined) { - if (selection === 'Disable Linting') { - await this.linterManager.enableLintingAsync(false); - sendTelemetryEvent(EventName.SELECT_LINTER, undefined, { enabled: false }); - } else { - const index = linters.findIndex((x) => x.id === selection); - if (activeLinters.length > 1) { - const response = await this.appShell.showWarningMessage( - Linters.replaceWithSelectedLinter().format(selection), - 'Yes', - 'No' - ); - if (response !== 'Yes') { - return; - } - } - await this.linterManager.setActiveLintersAsync([linters[index].product], this.settingsUri); - sendTelemetryEvent(EventName.SELECT_LINTER, undefined, { tool: selection as LinterId, enabled: true }); - } - } - } - - public async enableLintingAsync(): Promise { - const options = ['on', 'off']; - const current = (await this.linterManager.isLintingEnabled(true, this.settingsUri)) ? options[0] : options[1]; - - const quickPickOptions: QuickPickOptions = { - matchOnDetail: true, - matchOnDescription: true, - placeHolder: `current: ${current}` - }; - - const selection = await this.appShell.showQuickPick(options, quickPickOptions); - if (selection !== undefined) { - const enable = selection === options[0]; - await this.linterManager.enableLintingAsync(enable, this.settingsUri); - } - } - - public runLinting(): Promise { - const engine = this.serviceContainer.get(ILintingEngine); - return engine.lintOpenPythonFiles(); - } - - private get settingsUri(): Uri | undefined { - return this.documentManager.activeTextEditor ? this.documentManager.activeTextEditor.document.uri : undefined; - } -} diff --git a/src/client/linters/linterInfo.ts b/src/client/linters/linterInfo.ts deleted file mode 100644 index 307afe4c0cc2..000000000000 --- a/src/client/linters/linterInfo.ts +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import * as path from 'path'; -import { Uri } from 'vscode'; -import { LanguageServerType } from '../activation/types'; -import { IWorkspaceService } from '../common/application/types'; -import { ExecutionInfo, IConfigurationService, Product } from '../common/types'; -import { ILinterInfo, LinterId } from './types'; - -// tslint:disable:no-any - -export class LinterInfo implements ILinterInfo { - private _id: LinterId; - private _product: Product; - private _configFileNames: string[]; - - constructor( - product: Product, - id: LinterId, - protected configService: IConfigurationService, - configFileNames: string[] = [] - ) { - this._product = product; - this._id = id; - this._configFileNames = configFileNames; - } - - public get id(): LinterId { - return this._id; - } - public get product(): Product { - return this._product; - } - - public get pathSettingName(): string { - return `${this.id}Path`; - } - public get argsSettingName(): string { - return `${this.id}Args`; - } - public get enabledSettingName(): string { - return `${this.id}Enabled`; - } - public get configFileNames(): string[] { - return this._configFileNames; - } - - public async enableAsync(enabled: boolean, resource?: Uri): Promise { - return this.configService.updateSetting(`linting.${this.enabledSettingName}`, enabled, resource); - } - public isEnabled(resource?: Uri): boolean { - const settings = this.configService.getSettings(resource); - return (settings.linting as any)[this.enabledSettingName] as boolean; - } - - public pathName(resource?: Uri): string { - const settings = this.configService.getSettings(resource); - return (settings.linting as any)[this.pathSettingName] as string; - } - public linterArgs(resource?: Uri): string[] { - const settings = this.configService.getSettings(resource); - const args = (settings.linting as any)[this.argsSettingName]; - return Array.isArray(args) ? (args as string[]) : []; - } - public getExecutionInfo(customArgs: string[], resource?: Uri): ExecutionInfo { - const execPath = this.pathName(resource); - const args = this.linterArgs(resource).concat(customArgs); - let moduleName: string | undefined; - - // If path information is not available, then treat it as a module, - if (path.basename(execPath) === execPath) { - moduleName = execPath; - } - - return { execPath, moduleName, args, product: this.product }; - } -} - -export class PylintLinterInfo extends LinterInfo { - constructor( - configService: IConfigurationService, - private readonly workspaceService: IWorkspaceService, - configFileNames: string[] = [] - ) { - super(Product.pylint, LinterId.PyLint, configService, configFileNames); - } - public isEnabled(resource?: Uri): boolean { - // We want to be sure the setting is not default since default is `true` and hence - // missing setting yields `true`. When setting is missing and LS is non-Jedi, - // we want default to be `false`. So inspection here makes sure we are not getting - // `true` because there is no setting and LS is active. - const enabled = super.isEnabled(resource); // Is it enabled by settings? - const usingJedi = this.configService.getSettings(resource).languageServer === LanguageServerType.Jedi; - if (usingJedi) { - // In Jedi case adhere to default behavior. Missing setting means `enabled`. - return enabled; - } - // If we're using LS, then by default Pylint is disabled unless user provided - // the value. We have to resort to direct inspection of settings here. - const configuration = this.workspaceService.getConfiguration('python', resource); - const inspection = configuration.inspect(`linting.${this.enabledSettingName}`); - if ( - !inspection || - (inspection.globalValue === undefined && - inspection.workspaceFolderValue === undefined && - inspection.workspaceValue === undefined) - ) { - return false; - } - return enabled; - } -} diff --git a/src/client/linters/linterManager.ts b/src/client/linters/linterManager.ts deleted file mode 100644 index a9dd8e1c9c70..000000000000 --- a/src/client/linters/linterManager.ts +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { CancellationToken, OutputChannel, TextDocument, Uri } from 'vscode'; -import { IWorkspaceService } from '../common/application/types'; -import { traceError } from '../common/logger'; -import { IConfigurationService, Product } from '../common/types'; -import { IServiceContainer } from '../ioc/types'; -import { Bandit } from './bandit'; -import { Flake8 } from './flake8'; -import { LinterInfo, PylintLinterInfo } from './linterInfo'; -import { MyPy } from './mypy'; -import { Prospector } from './prospector'; -import { Pycodestyle } from './pycodestyle'; -import { PyDocStyle } from './pydocstyle'; -import { PyLama } from './pylama'; -import { Pylint } from './pylint'; -import { IAvailableLinterActivator, ILinter, ILinterInfo, ILinterManager, ILintMessage, LinterId } from './types'; - -class DisabledLinter implements ILinter { - constructor(private configService: IConfigurationService) {} - public get info() { - return new LinterInfo(Product.pylint, LinterId.PyLint, this.configService); - } - public async lint(_document: TextDocument, _cancellation: CancellationToken): Promise { - return []; - } -} - -@injectable() -export class LinterManager implements ILinterManager { - protected linters: ILinterInfo[]; - private configService: IConfigurationService; - private checkedForInstalledLinters = new Set(); - - constructor( - @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService - ) { - this.configService = serviceContainer.get(IConfigurationService); - // Note that we use unit tests to ensure all the linters are here. - this.linters = [ - new LinterInfo(Product.bandit, LinterId.Bandit, this.configService), - new LinterInfo(Product.flake8, LinterId.Flake8, this.configService), - new PylintLinterInfo(this.configService, this.workspaceService, ['.pylintrc', 'pylintrc']), - new LinterInfo(Product.mypy, LinterId.MyPy, this.configService), - new LinterInfo(Product.pycodestyle, LinterId.PyCodeStyle, this.configService), - new LinterInfo(Product.prospector, LinterId.Prospector, this.configService), - new LinterInfo(Product.pydocstyle, LinterId.PyDocStyle, this.configService), - new LinterInfo(Product.pylama, LinterId.PyLama, this.configService) - ]; - } - - public getAllLinterInfos(): ILinterInfo[] { - return this.linters; - } - - public getLinterInfo(product: Product): ILinterInfo { - const x = this.linters.findIndex((value, _index, _obj) => value.product === product); - if (x >= 0) { - return this.linters[x]; - } - throw new Error(`Invalid linter '${Product[product]}'`); - } - - public async isLintingEnabled(silent: boolean, resource?: Uri): Promise { - const settings = this.configService.getSettings(resource); - const activeLintersPresent = await this.getActiveLinters(silent, resource); - return settings.linting.enabled && activeLintersPresent.length > 0; - } - - public async enableLintingAsync(enable: boolean, resource?: Uri): Promise { - await this.configService.updateSetting('linting.enabled', enable, resource); - } - - public async getActiveLinters(silent: boolean, resource?: Uri): Promise { - if (!silent) { - await this.enableUnconfiguredLinters(resource); - } - return this.linters.filter((x) => x.isEnabled(resource)); - } - - public async setActiveLintersAsync(products: Product[], resource?: Uri): Promise { - // ensure we only allow valid linters to be set, otherwise leave things alone. - // filter out any invalid products: - const validProducts = products.filter((product) => { - const foundIndex = this.linters.findIndex((validLinter) => validLinter.product === product); - return foundIndex !== -1; - }); - - // if we have valid linter product(s), enable only those - if (validProducts.length > 0) { - const active = await this.getActiveLinters(true, resource); - for (const x of active) { - await x.enableAsync(false, resource); - } - if (products.length > 0) { - const toActivate = this.linters.filter((x) => products.findIndex((p) => x.product === p) >= 0); - for (const x of toActivate) { - await x.enableAsync(true, resource); - } - await this.enableLintingAsync(true, resource); - } - } - } - - public async createLinter( - product: Product, - outputChannel: OutputChannel, - serviceContainer: IServiceContainer, - resource?: Uri - ): Promise { - if (!(await this.isLintingEnabled(true, resource))) { - return new DisabledLinter(this.configService); - } - const error = 'Linter manager: Unknown linter'; - switch (product) { - case Product.bandit: - return new Bandit(outputChannel, serviceContainer); - case Product.flake8: - return new Flake8(outputChannel, serviceContainer); - case Product.pylint: - return new Pylint(outputChannel, serviceContainer); - case Product.mypy: - return new MyPy(outputChannel, serviceContainer); - case Product.prospector: - return new Prospector(outputChannel, serviceContainer); - case Product.pylama: - return new PyLama(outputChannel, serviceContainer); - case Product.pydocstyle: - return new PyDocStyle(outputChannel, serviceContainer); - case Product.pycodestyle: - return new Pycodestyle(outputChannel, serviceContainer); - default: - traceError(error); - break; - } - throw new Error(error); - } - - protected async enableUnconfiguredLinters(resource?: Uri): Promise { - const settings = this.configService.getSettings(resource); - if (!settings.linting.pylintEnabled || !settings.linting.enabled) { - return; - } - // If we've already checked during this session for the same workspace and Python path, then don't bother again. - const workspaceKey = `${this.workspaceService.getWorkspaceFolderIdentifier(resource)}${settings.pythonPath}`; - if (this.checkedForInstalledLinters.has(workspaceKey)) { - return; - } - this.checkedForInstalledLinters.add(workspaceKey); - - // only check & ask the user if they'd like to enable pylint - const pylintInfo = this.linters.find((linter) => linter.id === 'pylint'); - const activator = this.serviceContainer.get(IAvailableLinterActivator); - await activator.promptIfLinterAvailable(pylintInfo!, resource); - } -} diff --git a/src/client/linters/lintingEngine.ts b/src/client/linters/lintingEngine.ts deleted file mode 100644 index c9bf05ff8e74..000000000000 --- a/src/client/linters/lintingEngine.ts +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { Minimatch } from 'minimatch'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import { IDocumentManager, IWorkspaceService } from '../common/application/types'; -import { STANDARD_OUTPUT_CHANNEL } from '../common/constants'; -import { IFileSystem } from '../common/platform/types'; -import { IConfigurationService, IOutputChannel } from '../common/types'; -import { isNotebookCell } from '../common/utils/misc'; -import { StopWatch } from '../common/utils/stopWatch'; -import { IServiceContainer } from '../ioc/types'; -import { sendTelemetryWhenDone } from '../telemetry'; -import { EventName } from '../telemetry/constants'; -import { LinterTrigger, LintingTelemetry } from '../telemetry/types'; -import { ILinterInfo, ILinterManager, ILintingEngine, ILintMessage, LintMessageSeverity } from './types'; - -const PYTHON: vscode.DocumentFilter = { language: 'python' }; - -const lintSeverityToVSSeverity = new Map(); -lintSeverityToVSSeverity.set(LintMessageSeverity.Error, vscode.DiagnosticSeverity.Error); -lintSeverityToVSSeverity.set(LintMessageSeverity.Hint, vscode.DiagnosticSeverity.Hint); -lintSeverityToVSSeverity.set(LintMessageSeverity.Information, vscode.DiagnosticSeverity.Information); -lintSeverityToVSSeverity.set(LintMessageSeverity.Warning, vscode.DiagnosticSeverity.Warning); - -@injectable() -export class LintingEngine implements ILintingEngine { - private workspace: IWorkspaceService; - private documents: IDocumentManager; - private configurationService: IConfigurationService; - private linterManager: ILinterManager; - private diagnosticCollection: vscode.DiagnosticCollection; - private pendingLintings = new Map(); - private outputChannel: vscode.OutputChannel; - private fileSystem: IFileSystem; - - constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { - this.documents = serviceContainer.get(IDocumentManager); - this.workspace = serviceContainer.get(IWorkspaceService); - this.configurationService = serviceContainer.get(IConfigurationService); - this.outputChannel = serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); - this.linterManager = serviceContainer.get(ILinterManager); - this.fileSystem = serviceContainer.get(IFileSystem); - this.diagnosticCollection = vscode.languages.createDiagnosticCollection('python'); - } - - public get diagnostics(): vscode.DiagnosticCollection { - return this.diagnosticCollection; - } - - public clearDiagnostics(document: vscode.TextDocument): void { - if (this.diagnosticCollection.has(document.uri)) { - this.diagnosticCollection.delete(document.uri); - } - } - - public async lintOpenPythonFiles(): Promise { - this.diagnosticCollection.clear(); - const promises = this.documents.textDocuments.map(async (document) => this.lintDocument(document, 'auto')); - await Promise.all(promises); - return this.diagnosticCollection; - } - - public async lintDocument(document: vscode.TextDocument, trigger: LinterTrigger): Promise { - if (isNotebookCell(document)) { - return; - } - this.diagnosticCollection.set(document.uri, []); - - // Check if we need to lint this document - if (!(await this.shouldLintDocument(document))) { - return; - } - - if (this.pendingLintings.has(document.uri.fsPath)) { - this.pendingLintings.get(document.uri.fsPath)!.cancel(); - this.pendingLintings.delete(document.uri.fsPath); - } - - const cancelToken = new vscode.CancellationTokenSource(); - cancelToken.token.onCancellationRequested(() => { - if (this.pendingLintings.has(document.uri.fsPath)) { - this.pendingLintings.delete(document.uri.fsPath); - } - }); - - this.pendingLintings.set(document.uri.fsPath, cancelToken); - - const activeLinters = await this.linterManager.getActiveLinters(false, document.uri); - const promises: Promise[] = activeLinters.map(async (info: ILinterInfo) => { - const stopWatch = new StopWatch(); - const linter = await this.linterManager.createLinter( - info.product, - this.outputChannel, - this.serviceContainer, - document.uri - ); - const promise = linter.lint(document, cancelToken.token); - this.sendLinterRunTelemetry(info, document.uri, promise, stopWatch, trigger); - return promise; - }); - - // linters will resolve asynchronously - keep a track of all - // diagnostics reported as them come in. - let diagnostics: vscode.Diagnostic[] = []; - const settings = this.configurationService.getSettings(document.uri); - - for (const p of promises) { - const msgs = await p; - if (cancelToken.token.isCancellationRequested) { - break; - } - - if (this.isDocumentOpen(document.uri)) { - // Build the message and suffix the message with the name of the linter used. - for (const m of msgs) { - diagnostics.push(this.createDiagnostics(m, document)); - } - // Limit the number of messages to the max value. - diagnostics = diagnostics.filter((_value, index) => index <= settings.linting.maxNumberOfProblems); - } - } - // Set all diagnostics found in this pass, as this method always clears existing diagnostics. - this.diagnosticCollection.set(document.uri, diagnostics); - } - - private sendLinterRunTelemetry( - info: ILinterInfo, - resource: vscode.Uri, - promise: Promise, - stopWatch: StopWatch, - trigger: LinterTrigger - ): void { - const linterExecutablePathName = info.pathName(resource); - const properties: LintingTelemetry = { - tool: info.id, - hasCustomArgs: info.linterArgs(resource).length > 0, - trigger, - executableSpecified: linterExecutablePathName.length > 0 - }; - sendTelemetryWhenDone(EventName.LINTING, promise, stopWatch, properties); - } - - private isDocumentOpen(uri: vscode.Uri): boolean { - return this.documents.textDocuments.some((document) => document.uri.fsPath === uri.fsPath); - } - - private createDiagnostics(message: ILintMessage, _document: vscode.TextDocument): vscode.Diagnostic { - const position = new vscode.Position(message.line - 1, message.column); - const range = new vscode.Range(position, position); - - const severity = lintSeverityToVSSeverity.get(message.severity!)!; - const diagnostic = new vscode.Diagnostic(range, message.message, severity); - diagnostic.code = message.code; - diagnostic.source = message.provider; - return diagnostic; - } - - private async shouldLintDocument(document: vscode.TextDocument): Promise { - if (!(await this.linterManager.isLintingEnabled(false, document.uri))) { - this.diagnosticCollection.set(document.uri, []); - return false; - } - - if (document.languageId !== PYTHON.language) { - return false; - } - - const workspaceFolder = this.workspace.getWorkspaceFolder(document.uri); - const workspaceRootPath = - workspaceFolder && typeof workspaceFolder.uri.fsPath === 'string' ? workspaceFolder.uri.fsPath : undefined; - const relativeFileName = - typeof workspaceRootPath === 'string' - ? path.relative(workspaceRootPath, document.fileName) - : document.fileName; - - const settings = this.configurationService.getSettings(document.uri); - // { dot: true } is important so dirs like `.venv` will be matched by globs - const ignoreMinmatches = settings.linting.ignorePatterns.map( - (pattern) => new Minimatch(pattern, { dot: true }) - ); - if (ignoreMinmatches.some((matcher) => matcher.match(document.fileName) || matcher.match(relativeFileName))) { - return false; - } - if (document.uri.scheme !== 'file' || !document.uri.fsPath) { - return false; - } - return this.fileSystem.fileExists(document.uri.fsPath); - } -} diff --git a/src/client/linters/mypy.ts b/src/client/linters/mypy.ts deleted file mode 100644 index eff5c71be37a..000000000000 --- a/src/client/linters/mypy.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { CancellationToken, OutputChannel, TextDocument } from 'vscode'; -import '../common/extensions'; -import { Product } from '../common/types'; -import { IServiceContainer } from '../ioc/types'; -import { BaseLinter } from './baseLinter'; -import { ILintMessage } from './types'; - -export const REGEX = '(?[^:]+):(?\\d+)(:(?\\d+))?: (?\\w+): (?.*)\\r?(\\n|$)'; - -export class MyPy extends BaseLinter { - constructor(outputChannel: OutputChannel, serviceContainer: IServiceContainer) { - super(Product.mypy, outputChannel, serviceContainer); - } - - protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise { - const messages = await this.run([document.uri.fsPath], document, cancellation, REGEX); - messages.forEach((msg) => { - msg.severity = this.parseMessagesSeverity(msg.type, this.pythonSettings.linting.mypyCategorySeverity); - msg.code = msg.type; - }); - return messages; - } -} diff --git a/src/client/linters/prospector.ts b/src/client/linters/prospector.ts deleted file mode 100644 index 2341b49e155b..000000000000 --- a/src/client/linters/prospector.ts +++ /dev/null @@ -1,62 +0,0 @@ -import * as path from 'path'; -import { CancellationToken, OutputChannel, TextDocument } from 'vscode'; -import '../common/extensions'; -import { traceError } from '../common/logger'; -import { Product } from '../common/types'; -import { IServiceContainer } from '../ioc/types'; -import { BaseLinter } from './baseLinter'; -import { ILintMessage } from './types'; - -interface IProspectorResponse { - messages: IProspectorMessage[]; -} -interface IProspectorMessage { - source: string; - message: string; - code: string; - location: IProspectorLocation; -} -interface IProspectorLocation { - function: string; - path: string; - line: number; - character: number; - module: 'beforeFormat'; -} - -export class Prospector extends BaseLinter { - constructor(outputChannel: OutputChannel, serviceContainer: IServiceContainer) { - super(Product.prospector, outputChannel, serviceContainer); - } - - protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise { - const cwd = this.getWorkspaceRootPath(document); - const relativePath = path.relative(cwd, document.uri.fsPath); - return this.run(['--absolute-paths', '--output-format=json', relativePath], document, cancellation); - } - protected async parseMessages(output: string, _document: TextDocument, _token: CancellationToken, _regEx: string) { - let parsedData: IProspectorResponse; - try { - parsedData = JSON.parse(output); - } catch (ex) { - this.outputChannel.appendLine(`${'#'.repeat(10)}Linting Output - ${this.info.id}${'#'.repeat(10)}`); - this.outputChannel.append(output); - traceError('Failed to parse Prospector output', ex); - return []; - } - return parsedData.messages - .filter((_value, index) => index <= this.pythonSettings.linting.maxNumberOfProblems) - .map((msg) => { - const lineNumber = msg.location.line === null || isNaN(msg.location.line) ? 1 : msg.location.line; - - return { - code: msg.code, - message: msg.message, - column: msg.location.character, - line: lineNumber, - type: msg.code, - provider: `${this.info.id} - ${msg.source}` - }; - }); - } -} diff --git a/src/client/linters/pycodestyle.ts b/src/client/linters/pycodestyle.ts deleted file mode 100644 index 2d425ab29b18..000000000000 --- a/src/client/linters/pycodestyle.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { CancellationToken, OutputChannel, TextDocument } from 'vscode'; -import '../common/extensions'; -import { Product } from '../common/types'; -import { IServiceContainer } from '../ioc/types'; -import { BaseLinter } from './baseLinter'; -import { ILintMessage } from './types'; - -const COLUMN_OFF_SET = 1; - -export class Pycodestyle extends BaseLinter { - constructor(outputChannel: OutputChannel, serviceContainer: IServiceContainer) { - super(Product.pycodestyle, outputChannel, serviceContainer, COLUMN_OFF_SET); - } - - protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise { - const messages = await this.run( - ['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', document.uri.fsPath], - document, - cancellation - ); - messages.forEach((msg) => { - msg.severity = this.parseMessagesSeverity( - msg.type, - this.pythonSettings.linting.pycodestyleCategorySeverity - ); - }); - return messages; - } -} diff --git a/src/client/linters/pydocstyle.ts b/src/client/linters/pydocstyle.ts deleted file mode 100644 index 718e8b359b21..000000000000 --- a/src/client/linters/pydocstyle.ts +++ /dev/null @@ -1,84 +0,0 @@ -import * as path from 'path'; -import { CancellationToken, OutputChannel, TextDocument } from 'vscode'; -import '../common/extensions'; -import { traceError } from '../common/logger'; -import { Product } from '../common/types'; -import { IServiceContainer } from '../ioc/types'; -import { IS_WINDOWS } from './../common/platform/constants'; -import { BaseLinter } from './baseLinter'; -import { ILintMessage, LintMessageSeverity } from './types'; - -export class PyDocStyle extends BaseLinter { - constructor(outputChannel: OutputChannel, serviceContainer: IServiceContainer) { - super(Product.pydocstyle, outputChannel, serviceContainer); - } - - protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise { - const messages = await this.run([document.uri.fsPath], document, cancellation); - // All messages in pep8 are treated as warnings for now. - messages.forEach((msg) => { - msg.severity = LintMessageSeverity.Warning; - }); - - return messages; - } - - protected async parseMessages(output: string, document: TextDocument, _token: CancellationToken, _regEx: string) { - let outputLines = output.split(/\r?\n/g); - const baseFileName = path.basename(document.uri.fsPath); - - // Remember, the first line of the response contains the file name and line number, the next line contains the error message. - // So we have two lines per message, hence we need to take lines in pairs. - const maxLines = this.pythonSettings.linting.maxNumberOfProblems * 2; - // First line is almost always empty. - const oldOutputLines = outputLines.filter((line) => line.length > 0); - outputLines = []; - for (let counter = 0; counter < oldOutputLines.length / 2; counter += 1) { - outputLines.push(oldOutputLines[2 * counter] + oldOutputLines[2 * counter + 1]); - } - - return ( - outputLines - .filter((value, index) => index < maxLines && value.indexOf(':') >= 0) - .map((line) => { - // Windows will have a : after the drive letter (e.g. c:\). - if (IS_WINDOWS) { - return line.substring(line.indexOf(`${baseFileName}:`) + baseFileName.length + 1).trim(); - } - return line.substring(line.indexOf(':') + 1).trim(); - }) - // Iterate through the lines (skipping the messages). - // So, just iterate the response in pairs. - .map((line) => { - try { - if (line.trim().length === 0) { - return; - } - const lineNumber = parseInt(line.substring(0, line.indexOf(' ')), 10); - const part = line.substring(line.indexOf(':') + 1).trim(); - const code = part.substring(0, part.indexOf(':')).trim(); - const message = part.substring(part.indexOf(':') + 1).trim(); - - const sourceLine = document.lineAt(lineNumber - 1).text; - const trmmedSourceLine = sourceLine.trim(); - const sourceStart = sourceLine.indexOf(trmmedSourceLine); - - // tslint:disable-next-line:no-object-literal-type-assertion - return { - code: code, - message: message, - column: sourceStart, - line: lineNumber, - type: '', - provider: this.info.id - } as ILintMessage; - } catch (ex) { - traceError(`Failed to parse pydocstyle line '${line}'`, ex); - return; - } - }) - .filter((item) => item !== undefined) - .map((item) => item!) - ); - } -} diff --git a/src/client/linters/pylama.ts b/src/client/linters/pylama.ts deleted file mode 100644 index c1159b2d9544..000000000000 --- a/src/client/linters/pylama.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { CancellationToken, OutputChannel, TextDocument } from 'vscode'; -import '../common/extensions'; -import { Product } from '../common/types'; -import { IServiceContainer } from '../ioc/types'; -import { BaseLinter } from './baseLinter'; -import { ILintMessage, LintMessageSeverity } from './types'; - -const REGEX = - '(?.py):(?\\d+):(?\\d+): \\[(?\\w+)\\] (?\\w\\d+):? (?.*)\\r?(\\n|$)'; -const COLUMN_OFF_SET = 1; - -export class PyLama extends BaseLinter { - constructor(outputChannel: OutputChannel, serviceContainer: IServiceContainer) { - super(Product.pylama, outputChannel, serviceContainer, COLUMN_OFF_SET); - } - - protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise { - const messages = await this.run(['--format=parsable', document.uri.fsPath], document, cancellation, REGEX); - // All messages in pylama are treated as warnings for now. - messages.forEach((msg) => { - msg.severity = LintMessageSeverity.Warning; - }); - - return messages; - } -} diff --git a/src/client/linters/pylint.ts b/src/client/linters/pylint.ts deleted file mode 100644 index cf3474427dd7..000000000000 --- a/src/client/linters/pylint.ts +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import * as os from 'os'; -import * as path from 'path'; -import { CancellationToken, OutputChannel, TextDocument } from 'vscode'; -import '../common/extensions'; -import { IFileSystem, IPlatformService } from '../common/platform/types'; -import { Product } from '../common/types'; -import { IServiceContainer } from '../ioc/types'; -import { BaseLinter } from './baseLinter'; -import { ILintMessage } from './types'; - -const pylintrc = 'pylintrc'; -const dotPylintrc = '.pylintrc'; - -const REGEX = '(?\\d+),(?-?\\d+),(?\\w+),(?[\\w-]+):(?.*)\\r?(\\n|$)'; - -export class Pylint extends BaseLinter { - private fileSystem: IFileSystem; - private platformService: IPlatformService; - - constructor(outputChannel: OutputChannel, serviceContainer: IServiceContainer) { - super(Product.pylint, outputChannel, serviceContainer); - this.fileSystem = serviceContainer.get(IFileSystem); - this.platformService = serviceContainer.get(IPlatformService); - } - - protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise { - let minArgs: string[] = []; - // Only use minimal checkers if - // a) there are no custom arguments and - // b) there is no pylintrc file next to the file or at the workspace root - const uri = document.uri; - const workspaceRoot = this.getWorkspaceRootPath(document); - const settings = this.configService.getSettings(uri); - if ( - settings.linting.pylintUseMinimalCheckers && - this.info.linterArgs(uri).length === 0 && - // Check pylintrc next to the file or above up to and including the workspace root - !(await Pylint.hasConfigurationFileInWorkspace(this.fileSystem, path.dirname(uri.fsPath), workspaceRoot)) && - // Check for pylintrc at the root and above - !(await Pylint.hasConfigurationFile( - this.fileSystem, - this.getWorkspaceRootPath(document), - this.platformService - )) - ) { - // Disable all checkers up front and then selectively add back in: - // - All F checkers - // - Select W checkers - // - All E checkers _manually_ - // (see https://github.com/Microsoft/vscode-python/issues/722 for - // why; see - // https://gist.github.com/brettcannon/eff7f38a60af48d39814cbb2f33b3d1d - // for a script to regenerate the list of E checkers) - minArgs = [ - '--disable=all', - '--enable=F' + - ',unreachable,duplicate-key,unnecessary-semicolon' + - ',global-variable-not-assigned,unused-variable' + - ',unused-wildcard-import,binary-op-exception' + - ',bad-format-string,anomalous-backslash-in-string' + - ',bad-open-mode' + - ',E0001,E0011,E0012,E0100,E0101,E0102,E0103,E0104,E0105,E0107' + - ',E0108,E0110,E0111,E0112,E0113,E0114,E0115,E0116,E0117,E0118' + - ',E0202,E0203,E0211,E0213,E0236,E0237,E0238,E0239,E0240,E0241' + - ',E0301,E0302,E0303,E0401,E0402,E0601,E0602,E0603,E0604,E0611' + - ',E0632,E0633,E0701,E0702,E0703,E0704,E0710,E0711,E0712,E1003' + - ',E1101,E1102,E1111,E1120,E1121,E1123,E1124,E1125,E1126,E1127' + - ',E1128,E1129,E1130,E1131,E1132,E1133,E1134,E1135,E1136,E1137' + - ',E1138,E1139,E1200,E1201,E1205,E1206,E1300,E1301,E1302,E1303' + - ',E1304,E1305,E1306,E1310,E1700,E1701' - ]; - } - const args = [ - "--msg-template='{line},{column},{category},{symbol}:{msg}'", - '--reports=n', - '--output-format=text', - uri.fsPath - ]; - const messages = await this.run(minArgs.concat(args), document, cancellation, REGEX); - messages.forEach((msg) => { - msg.severity = this.parseMessagesSeverity(msg.type, settings.linting.pylintCategorySeverity); - }); - - return messages; - } - - // tslint:disable-next-line:member-ordering - public static async hasConfigurationFile( - fs: IFileSystem, - folder: string, - platformService: IPlatformService - ): Promise { - // https://pylint.readthedocs.io/en/latest/user_guide/run.html - // https://github.com/PyCQA/pylint/blob/975e08148c0faa79958b459303c47be1a2e1500a/pylint/config.py - // 1. pylintrc in the current working directory - // 2. .pylintrc in the current working directory - // 3. If the current working directory is in a Python module, Pylint searches - // up the hierarchy of Python modules until it finds a pylintrc file. - // This allows you to specify coding standards on a module by module basis. - // A directory is judged to be a Python module if it contains an __init__.py file. - // 4. The file named by environment variable PYLINTRC - // 5. if you have a home directory which isn’t /root: - // a) .pylintrc in your home directory - // b) .config/pylintrc in your home directory - // 6. /etc/pylintrc - if (process.env.PYLINTRC) { - return true; - } - - if ( - (await fs.fileExists(path.join(folder, pylintrc))) || - (await fs.fileExists(path.join(folder, dotPylintrc))) - ) { - return true; - } - - let current = folder; - let above = path.dirname(folder); - do { - if (!(await fs.fileExists(path.join(current, '__init__.py')))) { - break; - } - if ( - (await fs.fileExists(path.join(current, pylintrc))) || - (await fs.fileExists(path.join(current, dotPylintrc))) - ) { - return true; - } - current = above; - above = path.dirname(above); - } while (!fs.arePathsSame(current, above)); - - const home = os.homedir(); - if (await fs.fileExists(path.join(home, dotPylintrc))) { - return true; - } - if (await fs.fileExists(path.join(home, '.config', pylintrc))) { - return true; - } - - if (!platformService.isWindows) { - if (await fs.fileExists(path.join('/etc', pylintrc))) { - return true; - } - } - return false; - } - - // tslint:disable-next-line:member-ordering - public static async hasConfigurationFileInWorkspace( - fs: IFileSystem, - folder: string, - root: string - ): Promise { - // Search up from file location to the workspace root - let current = folder; - let above = path.dirname(current); - do { - if ( - (await fs.fileExists(path.join(current, pylintrc))) || - (await fs.fileExists(path.join(current, dotPylintrc))) - ) { - return true; - } - current = above; - above = path.dirname(above); - } while (!fs.arePathsSame(current, root) && !fs.arePathsSame(current, above)); - return false; - } -} diff --git a/src/client/linters/serviceRegistry.ts b/src/client/linters/serviceRegistry.ts deleted file mode 100644 index 89ea2da1c10e..000000000000 --- a/src/client/linters/serviceRegistry.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import { IExtensionActivationService } from '../activation/types'; -import { IServiceManager } from '../ioc/types'; -import { LinterProvider } from '../providers/linterProvider'; -import { AvailableLinterActivator } from './linterAvailability'; -import { LinterManager } from './linterManager'; -import { LintingEngine } from './lintingEngine'; -import { IAvailableLinterActivator, ILinterManager, ILintingEngine } from './types'; - -export function registerTypes(serviceManager: IServiceManager) { - serviceManager.addSingleton(ILintingEngine, LintingEngine); - serviceManager.addSingleton(ILinterManager, LinterManager); - serviceManager.add(IAvailableLinterActivator, AvailableLinterActivator); - serviceManager.addSingleton(IExtensionActivationService, LinterProvider); -} diff --git a/src/client/linters/types.ts b/src/client/linters/types.ts deleted file mode 100644 index 6dba6e9d15ac..000000000000 --- a/src/client/linters/types.ts +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import * as vscode from 'vscode'; -import { ExecutionInfo, Product } from '../common/types'; -import { IServiceContainer } from '../ioc/types'; -import { LinterTrigger } from '../telemetry/types'; - -export interface IErrorHandler { - handleError(error: Error, resource: vscode.Uri, execInfo: ExecutionInfo): Promise; -} - -export enum LinterId { - Flake8 = 'flake8', - MyPy = 'mypy', - PyCodeStyle = 'pycodestyle', - Prospector = 'prospector', - PyDocStyle = 'pydocstyle', - PyLama = 'pylama', - PyLint = 'pylint', - Bandit = 'bandit' -} - -export interface ILinterInfo { - readonly id: LinterId; - readonly product: Product; - readonly pathSettingName: string; - readonly argsSettingName: string; - readonly enabledSettingName: string; - readonly configFileNames: string[]; - enableAsync(enabled: boolean, resource?: vscode.Uri): Promise; - isEnabled(resource?: vscode.Uri): boolean; - pathName(resource?: vscode.Uri): string; - linterArgs(resource?: vscode.Uri): string[]; - getExecutionInfo(customArgs: string[], resource?: vscode.Uri): ExecutionInfo; -} - -export interface ILinter { - readonly info: ILinterInfo; - lint(document: vscode.TextDocument, cancellation: vscode.CancellationToken): Promise; -} - -export const IAvailableLinterActivator = Symbol('IAvailableLinterActivator'); -export interface IAvailableLinterActivator { - promptIfLinterAvailable(linter: ILinterInfo, resource?: vscode.Uri): Promise; -} - -export const ILinterManager = Symbol('ILinterManager'); -export interface ILinterManager { - getAllLinterInfos(): ILinterInfo[]; - getLinterInfo(product: Product): ILinterInfo; - getActiveLinters(silent: boolean, resource?: vscode.Uri): Promise; - isLintingEnabled(silent: boolean, resource?: vscode.Uri): Promise; - enableLintingAsync(enable: boolean, resource?: vscode.Uri): Promise; - setActiveLintersAsync(products: Product[], resource?: vscode.Uri): Promise; - createLinter( - product: Product, - outputChannel: vscode.OutputChannel, - serviceContainer: IServiceContainer, - resource?: vscode.Uri - ): Promise; -} - -export interface ILintMessage { - line: number; - column: number; - code: string | undefined; - message: string; - type: string; - severity?: LintMessageSeverity; - provider: string; -} -export enum LintMessageSeverity { - Hint, - Error, - Warning, - Information -} - -export const ILintingEngine = Symbol('ILintingEngine'); -export interface ILintingEngine { - readonly diagnostics: vscode.DiagnosticCollection; - lintOpenPythonFiles(): Promise; - lintDocument(document: vscode.TextDocument, trigger: LinterTrigger): Promise; - clearDiagnostics(document: vscode.TextDocument): void; -} diff --git a/src/client/logging/_global.ts b/src/client/logging/_global.ts deleted file mode 100644 index dabd864551f3..000000000000 --- a/src/client/logging/_global.ts +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import * as winston from 'winston'; -import { IOutputChannel } from '../common/types'; -import { CallInfo } from '../common/utils/decorators'; -import { getFormatter } from './formatters'; -import { LogLevel, resolveLevelName } from './levels'; -import { configureLogger, createLogger, getPreDefinedConfiguration, logToAll } from './logger'; -import { createTracingDecorator, LogInfo, TraceOptions, tracing as _tracing } from './trace'; -import { getPythonOutputChannelTransport } from './transports'; -import { Arguments } from './util'; - -const globalLogger = createLogger(); -initialize(); - -/** - * Initialize the logger. - * - * For console we do two things here: - * - Anything written to the logger will be displayed in the console - * window as well This is the behavior of the extension when running - * it. When running tests on CI, we might not want this behavior, as - * it'll pollute the test output with logging (as mentioned this is - * optional). Messages logged using our logger will be prefixed with - * `Python Extension: ....` for console window. This way, its easy - * to identify messages specific to the python extension. - * - Monkey patch the console.log and similar methods to send messages - * to the file logger. When running UI tests or similar, and we want - * to see everything that was dumped into `console window`, then we - * need to hijack the console logger. To do this we need to monkey - * patch the console methods. This is optional (generally done when - * running tests on CI). - * - * For the logfile: - * - we send all logging output to a log file. We log to the file - * only if a file has been specified as an env variable. Currently - * this is setup on CI servers. - */ -function initialize() { - configureLogger(globalLogger, getPreDefinedConfiguration()); -} - -// Set the logging level the extension logs at. -export function setLoggingLevel(level: LogLevel | 'off') { - if (level === 'off') { - // For now we disable all logging. One alternative would be - // to only disable logging to the output channel (by removing - // the transport from the logger). - globalLogger.clear(); - } else { - const levelName = resolveLevelName(level, winston.config.npm.levels); - if (levelName) { - globalLogger.level = levelName; - } - } -} - -// Register the output channel transport the logger will log into. -export function addOutputChannelLogging(channel: IOutputChannel) { - const formatter = getFormatter(); - const transport = getPythonOutputChannelTransport(channel, formatter); - globalLogger.add(transport); -} - -// Emit a log message derived from the args to all enabled transports. -export function log(logLevel: LogLevel, ...args: Arguments) { - logToAll([globalLogger], logLevel, args); -} - -// tslint:disable-next-line:no-any -export function logVerbose(...args: any[]) { - log(LogLevel.Info, ...args); -} - -// tslint:disable-next-line:no-any -export function logError(...args: any[]) { - log(LogLevel.Error, ...args); -} - -// tslint:disable-next-line:no-any -export function logInfo(...args: any[]) { - log(LogLevel.Info, ...args); -} - -// tslint:disable-next-line:no-any -export function logWarning(...args: any[]) { - log(LogLevel.Warn, ...args); -} - -// This is like a "context manager" that logs tracing info. -export function tracing(info: LogInfo, run: () => T, call?: CallInfo): T { - return _tracing([globalLogger], info, run, call); -} - -export namespace traceDecorators { - const DEFAULT_OPTS: TraceOptions = TraceOptions.Arguments | TraceOptions.ReturnValue; - - export function verbose(message: string, opts: TraceOptions = DEFAULT_OPTS) { - return createTracingDecorator([globalLogger], { message, opts }); - } - export function error(message: string) { - const opts = DEFAULT_OPTS; - const level = LogLevel.Error; - return createTracingDecorator([globalLogger], { message, opts, level }); - } - export function info(message: string) { - const opts = TraceOptions.None; - return createTracingDecorator([globalLogger], { message, opts }); - } - export function warn(message: string) { - const opts = DEFAULT_OPTS; - const level = LogLevel.Warn; - return createTracingDecorator([globalLogger], { message, opts, level }); - } -} diff --git a/src/client/logging/fileLogger.ts b/src/client/logging/fileLogger.ts new file mode 100644 index 000000000000..47e77f18d802 --- /dev/null +++ b/src/client/logging/fileLogger.ts @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { WriteStream } from 'fs-extra'; +import * as util from 'util'; +import { Disposable } from 'vscode-jsonrpc'; +import { Arguments, ILogging } from './types'; +import { getTimeForLogging } from './util'; + +function formatMessage(level?: string, ...data: Arguments): string { + return level + ? `[${level.toUpperCase()} ${getTimeForLogging()}]: ${util.format(...data)}\r\n` + : `${util.format(...data)}\r\n`; +} + +export class FileLogger implements ILogging, Disposable { + constructor(private readonly stream: WriteStream) {} + + public traceLog(...data: Arguments): void { + this.stream.write(formatMessage(undefined, ...data)); + } + + public traceError(...data: Arguments): void { + this.stream.write(formatMessage('error', ...data)); + } + + public traceWarn(...data: Arguments): void { + this.stream.write(formatMessage('warn', ...data)); + } + + public traceInfo(...data: Arguments): void { + this.stream.write(formatMessage('info', ...data)); + } + + public traceVerbose(...data: Arguments): void { + this.stream.write(formatMessage('debug', ...data)); + } + + public dispose(): void { + try { + this.stream.close(); + } catch (ex) { + /** do nothing */ + } + } +} diff --git a/src/client/logging/formatters.ts b/src/client/logging/formatters.ts deleted file mode 100644 index b3dd4e52761f..000000000000 --- a/src/client/logging/formatters.ts +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import { format } from 'winston'; -import { getLevel, LogLevel, LogLevelName } from './levels'; - -const TIMESTAMP = 'YYYY-MM-DD HH:mm:ss'; - -// Knobs used when creating a formatter. -export type FormatterOptions = { - label?: string; -}; - -// Pascal casing is used so log files get highlighted when viewing -// in VSC and other editors. -const formattedLogLevels: { [K in LogLevel]: string } = { - [LogLevel.Error]: 'Error', - [LogLevel.Warn]: 'Warn', - [LogLevel.Info]: 'Info', - [LogLevel.Debug]: 'Debug', - [LogLevel.Trace]: 'Trace' -}; - -// Return a consistent representation of the given log level. -function normalizeLevel(name: LogLevelName): string { - const level = getLevel(name); - if (level) { - const norm = formattedLogLevels[level]; - if (norm) { - return norm; - } - } - return `${name.substring(0, 1).toUpperCase()}${name.substring(1).toLowerCase()}`; -} - -// Return a log entry that can be emitted as-is. -function formatMessage(level: LogLevelName, timestamp: string, message: string): string { - const levelFormatted = normalizeLevel(level); - return `${levelFormatted} ${timestamp}: ${message}`; -} - -// Return a log entry that can be emitted as-is. -function formatLabeledMessage(level: LogLevelName, timestamp: string, label: string, message: string): string { - const levelFormatted = normalizeLevel(level); - return `${levelFormatted} ${label} ${timestamp}: ${message}`; -} - -// Return a minimal format object that can be used with a "winston" -// logging transport. -function getMinimalFormatter() { - return format.combine( - format.timestamp({ format: TIMESTAMP }), - format.printf( - // This relies on the timestamp formatter we added above: - ({ level, message, timestamp }) => formatMessage(level as LogLevelName, timestamp, message) - ) - ); -} - -// Return a minimal format object that can be used with a "winston" -// logging transport. -function getLabeledFormatter(label_: string) { - return format.combine( - format.label({ label: label_ }), - format.timestamp({ format: TIMESTAMP }), - format.printf( - // This relies on the label and timestamp formatters we added above: - ({ level, message, label, timestamp }) => - formatLabeledMessage(level as LogLevelName, timestamp, label, message) - ) - ); -} - -// Return a format object that can be used with a "winston" logging transport. -export function getFormatter(opts: FormatterOptions = {}) { - if (opts.label) { - return getLabeledFormatter(opts.label); - } - return getMinimalFormatter(); -} diff --git a/src/client/logging/index.ts b/src/client/logging/index.ts index 861c98f38a41..39d5652e100a 100644 --- a/src/client/logging/index.ts +++ b/src/client/logging/index.ts @@ -1,16 +1,215 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -'use strict'; - -export { - // aliases - // (for convenience) - setLoggingLevel, - addOutputChannelLogging, - logError, - logInfo, - logVerbose, - logWarning, - traceDecorators, - tracing -} from './_global'; + +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { createWriteStream } from 'fs-extra'; +import { isPromise } from 'rxjs/internal-compatibility'; +import { Disposable } from 'vscode'; +import { StopWatch } from '../common/utils/stopWatch'; +import { sendTelemetryEvent } from '../telemetry'; +import { EventName } from '../telemetry/constants'; +import { FileLogger } from './fileLogger'; +import { Arguments, ILogging, LogLevel, TraceDecoratorType, TraceOptions } from './types'; +import { argsToLogString, returnValueToLogString } from './util'; + +const DEFAULT_OPTS: TraceOptions = TraceOptions.Arguments | TraceOptions.ReturnValue; + +let loggers: ILogging[] = []; +export function registerLogger(logger: ILogging): Disposable { + loggers.push(logger); + return { + dispose: () => { + loggers = loggers.filter((l) => l !== logger); + }, + }; +} + +export function initializeFileLogging(disposables: Disposable[]): void { + if (process.env.VSC_PYTHON_LOG_FILE) { + const fileLogger = new FileLogger(createWriteStream(process.env.VSC_PYTHON_LOG_FILE)); + disposables.push(fileLogger); + disposables.push(registerLogger(fileLogger)); + } +} + +export function traceLog(...args: Arguments): void { + loggers.forEach((l) => l.traceLog(...args)); +} + +export function traceError(...args: Arguments): void { + loggers.forEach((l) => l.traceError(...args)); +} + +export function traceWarn(...args: Arguments): void { + loggers.forEach((l) => l.traceWarn(...args)); +} + +export function traceInfo(...args: Arguments): void { + loggers.forEach((l) => l.traceInfo(...args)); +} + +export function traceVerbose(...args: Arguments): void { + loggers.forEach((l) => l.traceVerbose(...args)); +} + +/** Logging Decorators go here */ + +export function traceDecoratorVerbose(message: string, opts: TraceOptions = DEFAULT_OPTS): TraceDecoratorType { + return createTracingDecorator({ message, opts, level: LogLevel.Debug }); +} +export function traceDecoratorError(message: string): TraceDecoratorType { + return createTracingDecorator({ message, opts: DEFAULT_OPTS, level: LogLevel.Error }); +} +export function traceDecoratorInfo(message: string): TraceDecoratorType { + return createTracingDecorator({ message, opts: DEFAULT_OPTS, level: LogLevel.Info }); +} +export function traceDecoratorWarn(message: string): TraceDecoratorType { + return createTracingDecorator({ message, opts: DEFAULT_OPTS, level: LogLevel.Warning }); +} + +// Information about a function/method call. +type CallInfo = { + kind: string; // "Class", etc. + name: string; + + args: unknown[]; +}; + +// Information about a traced function/method call. +type TraceInfo = { + elapsed: number; // milliseconds + // Either returnValue or err will be set. + + returnValue?: any; + err?: Error; +}; + +type LogInfo = { + opts: TraceOptions; + message: string; + level?: LogLevel; +}; + +// Return a decorator that traces the decorated function. +function traceDecorator(log: (c: CallInfo, t: TraceInfo) => void): TraceDecoratorType { + return function (_: Object, __: string, descriptor: TypedPropertyDescriptor) { + const originalMethod = descriptor.value; + + descriptor.value = function (...args: unknown[]) { + const call = { + kind: 'Class', + name: _ && _.constructor ? _.constructor.name : '', + args, + }; + + // eslint-disable-next-line @typescript-eslint/no-this-alias + const scope = this; + return tracing( + // "log()" + (t) => log(call, t), + // "run()" + () => originalMethod.apply(scope, args), + ); + }; + + return descriptor; + }; +} + +// Call run(), call log() with the trace info, and return the result. +function tracing(log: (t: TraceInfo) => void, run: () => T): T { + const timer = new StopWatch(); + try { + const result = run(); + + // If method being wrapped returns a promise then wait for it. + if (isPromise(result)) { + ((result as unknown) as Promise) + .then((data) => { + log({ elapsed: timer.elapsedTime, returnValue: data }); + return data; + }) + .catch((ex) => { + log({ elapsed: timer.elapsedTime, err: ex }); + + // TODO(GH-11645) Re-throw the error like we do + // in the non-Promise case. + }); + } else { + log({ elapsed: timer.elapsedTime, returnValue: result }); + } + return result; + } catch (ex) { + log({ elapsed: timer.elapsedTime, err: ex as Error | undefined }); + throw ex; + } +} + +function createTracingDecorator(logInfo: LogInfo): TraceDecoratorType { + return traceDecorator((call, traced) => logResult(logInfo, traced, call)); +} + +function normalizeCall(call: CallInfo): CallInfo { + let { kind, name, args } = call; + if (!kind || kind === '') { + kind = 'Function'; + } + if (!name || name === '') { + name = ''; + } + if (!args) { + args = []; + } + return { kind, name, args }; +} + +function formatMessages(logInfo: LogInfo, traced: TraceInfo, call?: CallInfo): string { + call = normalizeCall(call!); + const messages = [logInfo.message]; + messages.push( + `${call.kind} name = ${call.name}`.trim(), + `completed in ${traced.elapsed}ms`, + `has a ${traced.returnValue ? 'truthy' : 'falsy'} return value`, + ); + if ((logInfo.opts & TraceOptions.Arguments) === TraceOptions.Arguments) { + messages.push(argsToLogString(call.args)); + } + if ((logInfo.opts & TraceOptions.ReturnValue) === TraceOptions.ReturnValue) { + messages.push(returnValueToLogString(traced.returnValue)); + } + return messages.join(', '); +} + +function logResult(logInfo: LogInfo, traced: TraceInfo, call?: CallInfo) { + const formatted = formatMessages(logInfo, traced, call); + if (traced.err === undefined) { + // The call did not fail. + if (!logInfo.level || logInfo.level > LogLevel.Error) { + logTo(LogLevel.Info, [formatted]); + } + } else { + logTo(LogLevel.Error, [formatted, traced.err]); + sendTelemetryEvent(('ERROR' as unknown) as EventName, undefined, undefined, traced.err); + } +} + +export function logTo(logLevel: LogLevel, ...args: Arguments): void { + switch (logLevel) { + case LogLevel.Error: + traceError(...args); + break; + case LogLevel.Warning: + traceWarn(...args); + break; + case LogLevel.Info: + traceInfo(...args); + break; + case LogLevel.Debug: + traceVerbose(...args); + break; + default: + break; + } +} diff --git a/src/client/logging/levels.ts b/src/client/logging/levels.ts deleted file mode 100644 index c0982dd3c282..000000000000 --- a/src/client/logging/levels.ts +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -// IMPORTANT: This file should only be importing from the '../client/logging' directory, as we -// delete everything in '../client' except for '../client/logging' before running smoke tests. - -import * as winston from 'winston'; - -//====================== -// Our custom log levels - -export enum LogLevel { - // Larger numbers are higher priority. - Error = 40, - Warn = 30, - Info = 20, - Debug = 10, - Trace = 5 -} -export type LogLevelName = 'ERROR' | 'WARNING' | 'INFORMATION' | 'DEBUG' | 'DEBUG-TRACE'; -const logLevelMap: { [K in LogLevel]: LogLevelName } = { - [LogLevel.Error]: 'ERROR', - [LogLevel.Warn]: 'WARNING', - [LogLevel.Info]: 'INFORMATION', - [LogLevel.Debug]: 'DEBUG', - [LogLevel.Trace]: 'DEBUG-TRACE' -}; -// This can be used for winston.LoggerOptions.levels. -export const configLevels: winston.config.AbstractConfigSetLevels = { - ERROR: 0, - WARNING: 1, - INFORMATION: 2, - DEBUG: 4, - 'DEBUG-TRACE': 5 -}; - -//====================== -// Other log levels - -// The level names from winston/config.npm. -// See: https://www.npmjs.com/package/winston#logging-levels -type NPMLogLevelName = 'error' | 'warn' | 'info' | 'verbose' | 'debug' | 'silly'; -const npmLogLevelMap: { [K in LogLevel]: NPMLogLevelName } = { - [LogLevel.Error]: 'error', - [LogLevel.Warn]: 'warn', - [LogLevel.Info]: 'info', - [LogLevel.Debug]: 'debug', - [LogLevel.Trace]: 'silly' -}; - -//====================== -// lookup functions - -// Convert from LogLevel enum to the proper level name. -export function resolveLevelName( - level: LogLevel, - // Default to configLevels. - levels?: winston.config.AbstractConfigSetLevels -): string | undefined { - if (levels === undefined) { - return getLevelName(level); - } else if (levels === configLevels) { - return getLevelName(level); - } else if (levels === winston.config.npm.levels) { - return npmLogLevelMap[level]; - } else { - return undefined; - } -} -export function getLevelName(level: LogLevel): LogLevelName | undefined { - return logLevelMap[level]; -} - -// Convert from a level name to the actual level. -export function resolveLevel( - levelName: string, - // Default to configLevels. - levels?: winston.config.AbstractConfigSetLevels -): LogLevel | undefined { - let levelMap: { [K in LogLevel]: string }; - if (levels === undefined) { - levelMap = logLevelMap; - } else if (levels === configLevels) { - levelMap = logLevelMap; - } else if (levels === winston.config.npm.levels) { - levelMap = npmLogLevelMap; - } else { - return undefined; - } - for (const level of Object.keys(levelMap)) { - if (typeof level === 'string') { - continue; - } - if (logLevelMap[level] === levelName) { - return level; - } - } - return undefined; -} -export function getLevel(name: LogLevelName): LogLevel | undefined { - return resolveLevel(name); -} diff --git a/src/client/logging/logger.ts b/src/client/logging/logger.ts deleted file mode 100644 index ab5ba55cb519..000000000000 --- a/src/client/logging/logger.ts +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -// IMPORTANT: This file should only be importing from the '../client/logging' directory, as we -// delete everything in '../client' except for '../client/logging' before running smoke tests. - -import * as util from 'util'; -import * as winston from 'winston'; -import * as Transport from 'winston-transport'; -import { getFormatter } from './formatters'; -import { LogLevel, resolveLevelName } from './levels'; -import { getConsoleTransport, getFileTransport, isConsoleTransport } from './transports'; -import { Arguments } from './util'; - -export type LoggerConfig = { - level?: LogLevel; - file?: { - logfile: string; - }; - console?: { - label?: string; - }; -}; - -// Create a logger just the way we like it. -export function createLogger(config?: LoggerConfig) { - const logger = winston.createLogger({ - // We would also set "levels" here. - }); - if (config) { - configureLogger(logger, config); - } - return logger; -} - -interface IConfigurableLogger { - level: string; - add(transport: Transport): void; -} - -// tslint:disable-next-line: no-suspicious-comment -/** - * TODO: We should actually have this method in `./_global.ts` as this is exported globally. - * But for some reason, importing '../client/logging/_global' fails when launching the tests. - * More details in the comment https://github.com/microsoft/vscode-python/pull/11897#discussion_r433954993 - * https://github.com/microsoft/vscode-python/issues/12137 - */ -export function getPreDefinedConfiguration(): LoggerConfig { - const config: LoggerConfig = {}; - - // Do not log to console if running tests and we're not - // asked to do so. - if (process.env.VSC_PYTHON_FORCE_LOGGING) { - config.console = {}; - // In CI there's no need for the label. - const isCI = process.env.TRAVIS === 'true' || process.env.TF_BUILD !== undefined; - if (!isCI) { - config.console.label = 'Python Extension:'; - } - } - if (process.env.VSC_PYTHON_LOG_FILE) { - config.file = { - logfile: process.env.VSC_PYTHON_LOG_FILE - }; - } - return config; -} - -// Set up a logger just the way we like it. -export function configureLogger(logger: IConfigurableLogger, config: LoggerConfig) { - if (config.level) { - const levelName = resolveLevelName(config.level); - if (levelName) { - logger.level = levelName; - } - } - - if (config.file) { - const formatter = getFormatter(); - const transport = getFileTransport(config.file.logfile, formatter); - logger.add(transport); - } - if (config.console) { - const formatter = getFormatter({ label: config.console.label }); - const transport = getConsoleTransport(formatter); - logger.add(transport); - } -} - -export interface ILogger { - transports: unknown[]; - levels: winston.config.AbstractConfigSetLevels; - log(level: string, message: string): void; -} - -// Emit a log message derived from the args to all enabled transports. -export function logToAll(loggers: ILogger[], logLevel: LogLevel, args: Arguments) { - const message = args.length === 0 ? '' : util.format(args[0], ...args.slice(1)); - for (const logger of loggers) { - if (logger.transports.length > 0) { - const levelName = getLevelName(logLevel, logger.levels, isConsoleLogger(logger)); - logger.log(levelName, message); - } - } -} - -function isConsoleLogger(logger: ILogger): boolean { - for (const transport of logger.transports) { - if (isConsoleTransport(transport)) { - return true; - } - } - return false; -} - -function getLevelName(level: LogLevel, levels: winston.config.AbstractConfigSetLevels, isConsole?: boolean): string { - const levelName = resolveLevelName(level, levels); - if (levelName) { - return levelName; - } else if (isConsole) { - // XXX Hard-coding this is fragile: - return 'silly'; - } else { - return resolveLevelName(LogLevel.Info, levels) || 'info'; - } -} diff --git a/src/client/logging/outputChannelLogger.ts b/src/client/logging/outputChannelLogger.ts new file mode 100644 index 000000000000..40505d33a735 --- /dev/null +++ b/src/client/logging/outputChannelLogger.ts @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as util from 'util'; +import { LogOutputChannel } from 'vscode'; +import { Arguments, ILogging } from './types'; + +export class OutputChannelLogger implements ILogging { + constructor(private readonly channel: LogOutputChannel) {} + + public traceLog(...data: Arguments): void { + this.channel.appendLine(util.format(...data)); + } + + public traceError(...data: Arguments): void { + this.channel.error(util.format(...data)); + } + + public traceWarn(...data: Arguments): void { + this.channel.warn(util.format(...data)); + } + + public traceInfo(...data: Arguments): void { + this.channel.info(util.format(...data)); + } + + public traceVerbose(...data: Arguments): void { + this.channel.debug(util.format(...data)); + } +} diff --git a/src/client/logging/trace.ts b/src/client/logging/trace.ts deleted file mode 100644 index 3191df5bd9d7..000000000000 --- a/src/client/logging/trace.ts +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import { CallInfo, trace as traceDecorator } from '../common/utils/decorators'; -import { TraceInfo, tracing as _tracing } from '../common/utils/misc'; -import { sendTelemetryEvent } from '../telemetry'; -import { LogLevel } from './levels'; -import { ILogger, logToAll } from './logger'; -import { argsToLogString, returnValueToLogString } from './util'; - -// The information we want to log. -export enum TraceOptions { - None = 0, - Arguments = 1, - ReturnValue = 2 -} - -export function createTracingDecorator(loggers: ILogger[], logInfo: LogInfo) { - return traceDecorator((call, traced) => logResult(loggers, logInfo, traced, call)); -} - -// This is like a "context manager" that logs tracing info. -export function tracing(loggers: ILogger[], logInfo: LogInfo, run: () => T, call?: CallInfo): T { - return _tracing((traced) => logResult(loggers, logInfo, traced, call), run); -} - -export type LogInfo = { - opts: TraceOptions; - message: string; - level?: LogLevel; -}; - -function normalizeCall(call: CallInfo): CallInfo { - let { kind, name, args } = call; - if (!kind || kind === '') { - kind = 'Function'; - } - if (!name || name === '') { - name = ''; - } - if (!args) { - args = []; - } - return { kind, name, args }; -} - -function formatMessages(info: LogInfo, traced: TraceInfo, call?: CallInfo): string { - call = normalizeCall(call!); - const messages = [info.message]; - messages.push( - `${call.kind} name = ${call.name}`.trim(), - `completed in ${traced.elapsed}ms`, - `has a ${traced.returnValue ? 'truthy' : 'falsy'} return value` - ); - if ((info.opts & TraceOptions.Arguments) === TraceOptions.Arguments) { - messages.push(argsToLogString(call.args)); - } - if ((info.opts & TraceOptions.ReturnValue) === TraceOptions.ReturnValue) { - messages.push(returnValueToLogString(traced.returnValue)); - } - return messages.join(', '); -} - -function logResult(loggers: ILogger[], info: LogInfo, traced: TraceInfo, call?: CallInfo) { - const formatted = formatMessages(info, traced, call); - if (traced.err === undefined) { - // The call did not fail. - if (!info.level || info.level > LogLevel.Error) { - logToAll(loggers, LogLevel.Info, [formatted]); - } - } else { - logToAll(loggers, LogLevel.Error, [formatted, traced.err]); - // tslint:disable-next-line:no-any - sendTelemetryEvent('ERROR' as any, undefined, undefined, traced.err); - } -} diff --git a/src/client/logging/transports.ts b/src/client/logging/transports.ts deleted file mode 100644 index 2a8e83a5fa52..000000000000 --- a/src/client/logging/transports.ts +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -// IMPORTANT: This file should only be importing from the '../client/logging' directory, as we -// delete everything in '../client' except for '../client/logging' before running smoke tests. - -import * as logform from 'logform'; -import * as path from 'path'; -import { OutputChannel } from 'vscode'; -import * as winston from 'winston'; -import * as Transport from 'winston-transport'; -import { LogLevel, resolveLevel } from './levels'; -import { Arguments } from './util'; - -const folderPath = path.dirname(__dirname); -const folderName = path.basename(folderPath); -const EXTENSION_ROOT_DIR = - folderName === 'client' ? path.join(folderPath, '..', '..') : path.join(folderPath, '..', '..', '..', '..'); -const formattedMessage = Symbol.for('message'); - -export function isConsoleTransport(transport: unknown): boolean { - // tslint:disable-next-line:no-any - return (transport as any).isConsole; -} - -// A winston-compatible transport type. -// We do not use transports.ConsoleTransport because it cannot -// adapt to our custom log levels very well. -class ConsoleTransport extends Transport { - // tslint:disable:no-console - private static funcByLevel: { [K in LogLevel]: (...args: Arguments) => void } = { - [LogLevel.Error]: console.error, - [LogLevel.Warn]: console.warn, - [LogLevel.Info]: console.info, - [LogLevel.Debug]: console.debug, - [LogLevel.Trace]: console.trace - }; - private static defaultFunc = console.log; - // tslint:enable:no-console - - // This is used to identify the type. - public readonly isConsole = true; - - constructor( - // tslint:disable-next-line:no-any - options?: any, - private readonly levels?: winston.config.AbstractConfigSetLevels - ) { - super(options); - } - - // tslint:disable-next-line:no-any - public log?(info: { level: string; message: string; [formattedMessage]: string }, next: () => void): any { - setImmediate(() => this.emit('logged', info)); - const level = resolveLevel(info.level, this.levels); - const msg = info[formattedMessage] || info.message; - this.logToConsole(level, msg); - if (next) { - next(); - } - } - - private logToConsole(level?: LogLevel, msg?: string) { - let func = ConsoleTransport.defaultFunc; - if (level) { - func = ConsoleTransport.funcByLevel[level] || func; - } - func(msg); - } -} - -// Create a console-targeting transport that can be added to a winston logger. -export function getConsoleTransport(formatter: logform.Format): Transport { - return new ConsoleTransport({ - // We minimize customization. - format: formatter - }); -} - -class PythonOutputChannelTransport extends Transport { - // tslint:disable-next-line: no-any - constructor(private readonly channel: OutputChannel, options?: any) { - super(options); - } - // tslint:disable-next-line: no-any - public log?(info: { message: string; [formattedMessage]: string }, next: () => void): any { - setImmediate(() => this.emit('logged', info)); - this.channel.appendLine(info[formattedMessage] || info.message); - if (next) { - next(); - } - } -} - -// Create a Python output channel targeting transport that can be added to a winston logger. -export function getPythonOutputChannelTransport(channel: OutputChannel, formatter: logform.Format) { - return new PythonOutputChannelTransport(channel, { - // We minimize customization. - format: formatter - }); -} - -// Create a file-targeting transport that can be added to a winston logger. -export function getFileTransport(logfile: string, formatter: logform.Format): Transport { - if (!path.isAbsolute(logfile)) { - logfile = path.join(EXTENSION_ROOT_DIR, logfile); - } - return new winston.transports.File({ - format: formatter, - filename: logfile, - handleExceptions: true - }); -} diff --git a/src/client/logging/types.ts b/src/client/logging/types.ts new file mode 100644 index 000000000000..c05800868512 --- /dev/null +++ b/src/client/logging/types.ts @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export type Arguments = unknown[]; + +export enum LogLevel { + Off = 0, + Trace = 1, + Debug = 2, + Info = 3, + Warning = 4, + Error = 5, +} + +export interface ILogging { + traceLog(...data: Arguments): void; + traceError(...data: Arguments): void; + traceWarn(...data: Arguments): void; + traceInfo(...data: Arguments): void; + traceVerbose(...data: Arguments): void; +} + +export type TraceDecoratorType = ( + _: Object, + __: string, + descriptor: TypedPropertyDescriptor, +) => TypedPropertyDescriptor; + +// The information we want to log. +export enum TraceOptions { + None = 0, + Arguments = 1, + ReturnValue = 2, +} diff --git a/src/client/logging/util.ts b/src/client/logging/util.ts index 79d977136a89..4229fd7976a5 100644 --- a/src/client/logging/util.ts +++ b/src/client/logging/util.ts @@ -1,9 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + 'use strict'; -// tslint:disable-next-line:no-any -export type Arguments = any[]; +import { Uri } from 'vscode'; + +export type Arguments = unknown[]; function valueToLogString(value: unknown, kind: string): string { if (value === undefined) { @@ -13,10 +15,8 @@ function valueToLogString(value: unknown, kind: string): string { return 'null'; } try { - // tslint:disable-next-line:no-any - if (value && (value as any).fsPath) { - // tslint:disable-next-line:no-any - return ``; + if (value && (value as Uri).fsPath) { + return ``; } return JSON.stringify(value); } catch { @@ -47,3 +47,8 @@ export function returnValueToLogString(returnValue: unknown): string { const valueString = valueToLogString(returnValue, 'Return value'); return `Return Value: ${valueString}`; } + +export function getTimeForLogging(): string { + const date = new Date(); + return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}.${date.getMilliseconds()}`; +} diff --git a/src/client/proposedApi.ts b/src/client/proposedApi.ts new file mode 100644 index 000000000000..22d53b0201ef --- /dev/null +++ b/src/client/proposedApi.ts @@ -0,0 +1,31 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { IServiceContainer } from './ioc/types'; +import { ProposedExtensionAPI } from './proposedApiTypes'; +import { IDiscoveryAPI } from './pythonEnvironments/base/locator'; +import { buildDeprecatedProposedApi } from './deprecatedProposedApi'; +import { DeprecatedProposedAPI } from './deprecatedProposedApiTypes'; + +export function buildProposedApi( + discoveryApi: IDiscoveryAPI, + serviceContainer: IServiceContainer, +): ProposedExtensionAPI { + /** + * @deprecated Will be removed soon. + */ + let deprecatedProposedApi; + try { + deprecatedProposedApi = { ...buildDeprecatedProposedApi(discoveryApi, serviceContainer) }; + } catch (ex) { + deprecatedProposedApi = {} as DeprecatedProposedAPI; + // Errors out only in case of testing. + // Also, these APIs no longer supported, no need to log error. + } + + const proposed: ProposedExtensionAPI & DeprecatedProposedAPI = { + ...deprecatedProposedApi, + }; + return proposed; +} diff --git a/src/client/proposedApiTypes.ts b/src/client/proposedApiTypes.ts new file mode 100644 index 000000000000..13ad5af543ec --- /dev/null +++ b/src/client/proposedApiTypes.ts @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +export interface ProposedExtensionAPI { + /** + * Top level proposed APIs should go here. + */ +} diff --git a/src/client/providers/codeActionProvider/launchJsonCodeActionProvider.ts b/src/client/providers/codeActionProvider/launchJsonCodeActionProvider.ts index 805614794bd7..e90e6ea97fd2 100644 --- a/src/client/providers/codeActionProvider/launchJsonCodeActionProvider.ts +++ b/src/client/providers/codeActionProvider/launchJsonCodeActionProvider.ts @@ -9,7 +9,7 @@ import { Diagnostic, Range, TextDocument, - WorkspaceEdit + WorkspaceEdit, } from 'vscode'; /** @@ -22,6 +22,7 @@ export class LaunchJsonCodeActionProvider implements CodeActionProvider { .map((diagnostic) => this.createFix(document, diagnostic)); } + // eslint-disable-next-line class-methods-use-this private createFix(document: TextDocument, diagnostic: Diagnostic): CodeAction { const finalText = `"${document.getText(diagnostic.range)}"`; const fix = new CodeAction(`Convert to ${finalText}`, CodeActionKind.QuickFix); diff --git a/src/client/providers/codeActionProvider/main.ts b/src/client/providers/codeActionProvider/main.ts index 375c9986d949..259f42848606 100644 --- a/src/client/providers/codeActionProvider/main.ts +++ b/src/client/providers/codeActionProvider/main.ts @@ -9,19 +9,22 @@ import { LaunchJsonCodeActionProvider } from './launchJsonCodeActionProvider'; @injectable() export class CodeActionProviderService implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; + constructor(@inject(IDisposableRegistry) private disposableRegistry: IDisposableRegistry) {} + public async activate(): Promise { - // tslint:disable-next-line:no-require-imports + // eslint-disable-next-line global-require const vscode = require('vscode') as typeof vscodeTypes; const documentSelector: vscodeTypes.DocumentFilter = { scheme: 'file', language: 'jsonc', - pattern: '**/launch.json' + pattern: '**/launch.json', }; this.disposableRegistry.push( vscode.languages.registerCodeActionsProvider(documentSelector, new LaunchJsonCodeActionProvider(), { - providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] - }) + providedCodeActionKinds: [vscode.CodeActionKind.QuickFix], + }), ); } } diff --git a/src/client/providers/codeActionProvider/pythonCodeActionProvider.ts b/src/client/providers/codeActionProvider/pythonCodeActionProvider.ts deleted file mode 100644 index d1c45dd8610e..000000000000 --- a/src/client/providers/codeActionProvider/pythonCodeActionProvider.ts +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as vscode from 'vscode'; -import { isNotebookCell } from '../../common/utils/misc'; - -export class PythonCodeActionProvider implements vscode.CodeActionProvider { - public provideCodeActions( - document: vscode.TextDocument, - _range: vscode.Range, - _context: vscode.CodeActionContext, - _token: vscode.CancellationToken - ): vscode.ProviderResult { - if (isNotebookCell(document)) { - return []; - } - const sortImports = new vscode.CodeAction('Sort imports', vscode.CodeActionKind.SourceOrganizeImports); - sortImports.command = { - title: 'Sort imports', - command: 'python.sortImports' - }; - - return [sortImports]; - } -} diff --git a/src/client/providers/completionProvider.ts b/src/client/providers/completionProvider.ts deleted file mode 100644 index 8426cff4ae6d..000000000000 --- a/src/client/providers/completionProvider.ts +++ /dev/null @@ -1,48 +0,0 @@ -'use strict'; - -import * as vscode from 'vscode'; -import { IConfigurationService } from '../common/types'; -import { IServiceContainer } from '../ioc/types'; -import { JediFactory } from '../languageServices/jediProxyFactory'; -import { captureTelemetry } from '../telemetry'; -import { EventName } from '../telemetry/constants'; -import { CompletionSource } from './completionSource'; -import { ItemInfoSource } from './itemInfoSource'; - -export class PythonCompletionItemProvider implements vscode.CompletionItemProvider { - private completionSource: CompletionSource; - private configService: IConfigurationService; - - constructor(jediFactory: JediFactory, serviceContainer: IServiceContainer) { - this.completionSource = new CompletionSource(jediFactory, serviceContainer, new ItemInfoSource(jediFactory)); - this.configService = serviceContainer.get(IConfigurationService); - } - - @captureTelemetry(EventName.COMPLETION) - public async provideCompletionItems( - document: vscode.TextDocument, - position: vscode.Position, - token: vscode.CancellationToken - ): Promise { - const items = await this.completionSource.getVsCodeCompletionItems(document, position, token); - if (this.configService.isTestExecution()) { - for (let i = 0; i < Math.min(3, items.length); i += 1) { - items[i] = await this.resolveCompletionItem(items[i], token); - } - } - return items; - } - - public async resolveCompletionItem( - item: vscode.CompletionItem, - token: vscode.CancellationToken - ): Promise { - if (!item.documentation) { - const itemInfos = await this.completionSource.getDocumentation(item, token); - if (itemInfos && itemInfos.length > 0) { - item.documentation = itemInfos[0].tooltip; - } - } - return item; - } -} diff --git a/src/client/providers/completionSource.ts b/src/client/providers/completionSource.ts deleted file mode 100644 index e135ba18be7b..000000000000 --- a/src/client/providers/completionSource.ts +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import * as vscode from 'vscode'; -import { IConfigurationService } from '../common/types'; -import { IServiceContainer } from '../ioc/types'; -import { JediFactory } from '../languageServices/jediProxyFactory'; -import { IItemInfoSource, LanguageItemInfo } from './itemInfoSource'; -import * as proxy from './jediProxy'; -import { isPositionInsideStringOrComment } from './providerUtilities'; - -class DocumentPosition { - constructor(public document: vscode.TextDocument, public position: vscode.Position) {} - - public static fromObject(item: object): DocumentPosition { - // tslint:disable-next-line:no-any - return (item as any)._documentPosition as DocumentPosition; - } - - public attachTo(item: object): void { - // tslint:disable-next-line:no-any - (item as any)._documentPosition = this; - } -} - -export class CompletionSource { - private jediFactory: JediFactory; - - constructor( - jediFactory: JediFactory, - private serviceContainer: IServiceContainer, - private itemInfoSource: IItemInfoSource - ) { - this.jediFactory = jediFactory; - } - - public async getVsCodeCompletionItems( - document: vscode.TextDocument, - position: vscode.Position, - token: vscode.CancellationToken - ): Promise { - const result = await this.getCompletionResult(document, position, token); - if (result === undefined) { - return Promise.resolve([]); - } - return this.toVsCodeCompletions(new DocumentPosition(document, position), result, document.uri); - } - - public async getDocumentation( - completionItem: vscode.CompletionItem, - token: vscode.CancellationToken - ): Promise { - const documentPosition = DocumentPosition.fromObject(completionItem); - if (documentPosition === undefined) { - return; - } - - // Supply hover source with simulated document text where item in question was 'already typed'. - const document = documentPosition.document; - const position = documentPosition.position; - const wordRange = document.getWordRangeAtPosition(position); - - const leadingRange = - wordRange !== undefined - ? new vscode.Range(new vscode.Position(0, 0), wordRange.start) - : new vscode.Range(new vscode.Position(0, 0), position); - - const itemString = completionItem.label; - const sourceText = `${document.getText(leadingRange)}${itemString}`; - const range = new vscode.Range(leadingRange.end, leadingRange.end.translate(0, itemString.length)); - - return this.itemInfoSource.getItemInfoFromText(document.uri, document.fileName, range, sourceText, token); - } - - private async getCompletionResult( - document: vscode.TextDocument, - position: vscode.Position, - token: vscode.CancellationToken - ): Promise { - if (position.character <= 0 || isPositionInsideStringOrComment(document, position)) { - return undefined; - } - - const type = proxy.CommandType.Completions; - const columnIndex = position.character; - - const source = document.getText(); - const cmd: proxy.ICommand = { - command: type, - fileName: document.fileName, - columnIndex: columnIndex, - lineIndex: position.line, - source: source - }; - - return this.jediFactory.getJediProxyHandler(document.uri).sendCommand(cmd, token); - } - - private toVsCodeCompletions( - documentPosition: DocumentPosition, - data: proxy.ICompletionResult, - resource: vscode.Uri - ): vscode.CompletionItem[] { - return data && data.items.length > 0 - ? data.items.map((item) => this.toVsCodeCompletion(documentPosition, item, resource)) - : []; - } - - private toVsCodeCompletion( - documentPosition: DocumentPosition, - item: proxy.IAutoCompleteItem, - resource: vscode.Uri - ): vscode.CompletionItem { - const completionItem = new vscode.CompletionItem(item.text); - completionItem.kind = item.type; - const configurationService = this.serviceContainer.get(IConfigurationService); - const pythonSettings = configurationService.getSettings(resource); - if ( - pythonSettings.autoComplete.addBrackets === true && - (item.kind === vscode.SymbolKind.Function || item.kind === vscode.SymbolKind.Method) - ) { - completionItem.insertText = new vscode.SnippetString(item.text) - .appendText('(') - .appendTabstop() - .appendText(')'); - } - // Ensure the built in members are at the bottom. - completionItem.sortText = - (completionItem.label.startsWith('__') ? 'z' : completionItem.label.startsWith('_') ? 'y' : '__') + - completionItem.label; - documentPosition.attachTo(completionItem); - return completionItem; - } -} diff --git a/src/client/providers/definitionProvider.ts b/src/client/providers/definitionProvider.ts deleted file mode 100644 index 9b3d8f1a7ce6..000000000000 --- a/src/client/providers/definitionProvider.ts +++ /dev/null @@ -1,59 +0,0 @@ -'use strict'; - -import * as vscode from 'vscode'; -import { JediFactory } from '../languageServices/jediProxyFactory'; -import { captureTelemetry } from '../telemetry'; -import { EventName } from '../telemetry/constants'; -import * as proxy from './jediProxy'; - -export class PythonDefinitionProvider implements vscode.DefinitionProvider { - public constructor(private jediFactory: JediFactory) {} - private static parseData(data: proxy.IDefinitionResult, possibleWord: string): vscode.Definition | undefined { - if (data && Array.isArray(data.definitions) && data.definitions.length > 0) { - const definitions = data.definitions.filter((d) => d.text === possibleWord); - const definition = definitions.length > 0 ? definitions[0] : data.definitions[data.definitions.length - 1]; - const definitionResource = vscode.Uri.file(definition.fileName); - const range = new vscode.Range( - definition.range.startLine, - definition.range.startColumn, - definition.range.endLine, - definition.range.endColumn - ); - return new vscode.Location(definitionResource, range); - } - } - @captureTelemetry(EventName.DEFINITION) - public async provideDefinition( - document: vscode.TextDocument, - position: vscode.Position, - token: vscode.CancellationToken - ): Promise { - const filename = document.fileName; - if (document.lineAt(position.line).text.match(/^\s*\/\//)) { - return; - } - if (position.character <= 0) { - return; - } - - const range = document.getWordRangeAtPosition(position); - if (!range) { - return; - } - const columnIndex = range.isEmpty ? position.character : range.end.character; - const cmd: proxy.ICommand = { - command: proxy.CommandType.Definitions, - fileName: filename, - columnIndex: columnIndex, - lineIndex: position.line - }; - if (document.isDirty) { - cmd.source = document.getText(); - } - const possibleWord = document.getText(range); - const data = await this.jediFactory - .getJediProxyHandler(document.uri) - .sendCommand(cmd, token); - return data ? PythonDefinitionProvider.parseData(data, possibleWord) : undefined; - } -} diff --git a/src/client/providers/docStringFoldingProvider.ts b/src/client/providers/docStringFoldingProvider.ts deleted file mode 100644 index 424b9b6a74ec..000000000000 --- a/src/client/providers/docStringFoldingProvider.ts +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { - CancellationToken, - FoldingContext, - FoldingRange, - FoldingRangeKind, - FoldingRangeProvider, - ProviderResult, - Range, - TextDocument -} from 'vscode'; -import { IterableTextRange } from '../language/iterableTextRange'; -import { IToken, TokenizerMode, TokenType } from '../language/types'; -import { getDocumentTokens } from './providerUtilities'; - -export class DocStringFoldingProvider implements FoldingRangeProvider { - public provideFoldingRanges( - document: TextDocument, - _context: FoldingContext, - _token: CancellationToken - ): ProviderResult { - return this.getFoldingRanges(document); - } - - private getFoldingRanges(document: TextDocument) { - const tokenCollection = getDocumentTokens( - document, - document.lineAt(document.lineCount - 1).range.end, - TokenizerMode.CommentsAndStrings - ); - const tokens = new IterableTextRange(tokenCollection); - - const docStringRanges: FoldingRange[] = []; - const commentRanges: FoldingRange[] = []; - - for (const token of tokens) { - const docstringRange = this.getDocStringFoldingRange(document, token); - if (docstringRange) { - docStringRanges.push(docstringRange); - continue; - } - - const commentRange = this.getSingleLineCommentRange(document, token); - if (commentRange) { - this.buildMultiLineCommentRange(commentRange, commentRanges); - } - } - - this.removeLastSingleLineComment(commentRanges); - return docStringRanges.concat(commentRanges); - } - private buildMultiLineCommentRange(commentRange: FoldingRange, commentRanges: FoldingRange[]) { - if (commentRanges.length === 0) { - commentRanges.push(commentRange); - return; - } - const previousComment = commentRanges[commentRanges.length - 1]; - if (previousComment.end + 1 === commentRange.start) { - previousComment.end = commentRange.end; - return; - } - if (previousComment.start === previousComment.end) { - commentRanges[commentRanges.length - 1] = commentRange; - return; - } - commentRanges.push(commentRange); - } - private removeLastSingleLineComment(commentRanges: FoldingRange[]) { - // Remove last comment folding range if its a single line entry. - if (commentRanges.length === 0) { - return; - } - const lastComment = commentRanges[commentRanges.length - 1]; - if (lastComment.start === lastComment.end) { - commentRanges.pop(); - } - } - private getDocStringFoldingRange(document: TextDocument, token: IToken) { - if (token.type !== TokenType.String) { - return; - } - - const startPosition = document.positionAt(token.start); - const endPosition = document.positionAt(token.end); - if (startPosition.line === endPosition.line) { - return; - } - - const startLine = document.lineAt(startPosition); - if (startLine.firstNonWhitespaceCharacterIndex !== startPosition.character) { - return; - } - const startIndex1 = startLine.text.indexOf("'''"); - const startIndex2 = startLine.text.indexOf('"""'); - if (startIndex1 !== startPosition.character && startIndex2 !== startPosition.character) { - return; - } - - const range = new Range(startPosition, endPosition); - - return new FoldingRange(range.start.line, range.end.line); - } - private getSingleLineCommentRange(document: TextDocument, token: IToken) { - if (token.type !== TokenType.Comment) { - return; - } - - const startPosition = document.positionAt(token.start); - const endPosition = document.positionAt(token.end); - if (startPosition.line !== endPosition.line) { - return; - } - if (document.lineAt(startPosition).firstNonWhitespaceCharacterIndex !== startPosition.character) { - return; - } - - const range = new Range(startPosition, endPosition); - return new FoldingRange(range.start.line, range.end.line, FoldingRangeKind.Comment); - } -} diff --git a/src/client/providers/formatProvider.ts b/src/client/providers/formatProvider.ts deleted file mode 100644 index cd4d8f3956c0..000000000000 --- a/src/client/providers/formatProvider.ts +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import * as vscode from 'vscode'; -import { ICommandManager, IDocumentManager, IWorkspaceService } from '../common/application/types'; -import { IConfigurationService } from '../common/types'; -import { IInterpreterService } from '../interpreter/contracts'; -import { IServiceContainer } from '../ioc/types'; -import { AutoPep8Formatter } from './../formatters/autoPep8Formatter'; -import { BaseFormatter } from './../formatters/baseFormatter'; -import { BlackFormatter } from './../formatters/blackFormatter'; -import { DummyFormatter } from './../formatters/dummyFormatter'; -import { YapfFormatter } from './../formatters/yapfFormatter'; - -export class PythonFormattingEditProvider - implements vscode.DocumentFormattingEditProvider, vscode.DocumentRangeFormattingEditProvider, vscode.Disposable { - private readonly config: IConfigurationService; - private readonly workspace: IWorkspaceService; - private readonly documentManager: IDocumentManager; - private readonly commands: ICommandManager; - private formatters = new Map(); - private disposables: vscode.Disposable[] = []; - - // Workaround for https://github.com/Microsoft/vscode/issues/41194 - private documentVersionBeforeFormatting = -1; - private formatterMadeChanges = false; - private saving = false; - - public constructor(_context: vscode.ExtensionContext, serviceContainer: IServiceContainer) { - const yapfFormatter = new YapfFormatter(serviceContainer); - const autoPep8 = new AutoPep8Formatter(serviceContainer); - const black = new BlackFormatter(serviceContainer); - const dummy = new DummyFormatter(serviceContainer); - this.formatters.set(yapfFormatter.Id, yapfFormatter); - this.formatters.set(black.Id, black); - this.formatters.set(autoPep8.Id, autoPep8); - this.formatters.set(dummy.Id, dummy); - - this.commands = serviceContainer.get(ICommandManager); - this.workspace = serviceContainer.get(IWorkspaceService); - this.documentManager = serviceContainer.get(IDocumentManager); - this.config = serviceContainer.get(IConfigurationService); - const interpreterService = serviceContainer.get(IInterpreterService); - this.disposables.push( - this.documentManager.onDidSaveTextDocument(async (document) => this.onSaveDocument(document)) - ); - this.disposables.push( - interpreterService.onDidChangeInterpreter(async () => { - if (this.documentManager.activeTextEditor) { - return this.onSaveDocument(this.documentManager.activeTextEditor.document); - } - }) - ); - } - - public dispose() { - this.disposables.forEach((d) => d.dispose()); - } - - public provideDocumentFormattingEdits( - document: vscode.TextDocument, - options: vscode.FormattingOptions, - token: vscode.CancellationToken - ): Promise { - return this.provideDocumentRangeFormattingEdits(document, undefined, options, token); - } - - public async provideDocumentRangeFormattingEdits( - document: vscode.TextDocument, - range: vscode.Range | undefined, - options: vscode.FormattingOptions, - token: vscode.CancellationToken - ): Promise { - // Workaround for https://github.com/Microsoft/vscode/issues/41194 - // VSC rejects 'format on save' promise in 750 ms. Python formatting may take quite a bit longer. - // Workaround is to resolve promise to nothing here, then execute format document and force new save. - // However, we need to know if this is 'format document' or formatting on save. - - if (this.saving) { - // We are saving after formatting (see onSaveDocument below) - // so we do not want to format again. - return []; - } - - // Remember content before formatting so we can detect if - // formatting edits have been really applied - const editorConfig = this.workspace.getConfiguration('editor', document.uri); - if (editorConfig.get('formatOnSave') === true) { - this.documentVersionBeforeFormatting = document.version; - } - - const settings = this.config.getSettings(document.uri); - const formatter = this.formatters.get(settings.formatting.provider)!; - const edits = await formatter.formatDocument(document, options, token, range); - - this.formatterMadeChanges = edits.length > 0; - return edits; - } - - private async onSaveDocument(document: vscode.TextDocument): Promise { - // Promise was rejected = formatting took too long. - // Don't format inside the event handler, do it on timeout - setTimeout(() => { - try { - if ( - this.formatterMadeChanges && - !document.isDirty && - document.version === this.documentVersionBeforeFormatting - ) { - // Formatter changes were not actually applied due to the timeout on save. - // Force formatting now and then save the document. - this.commands.executeCommand('editor.action.formatDocument').then(async () => { - this.saving = true; - await document.save(); - this.saving = false; - }); - } - } finally { - this.documentVersionBeforeFormatting = -1; - this.saving = false; - this.formatterMadeChanges = false; - } - }, 50); - } -} diff --git a/src/client/providers/hoverProvider.ts b/src/client/providers/hoverProvider.ts deleted file mode 100644 index 13a7ed7e205f..000000000000 --- a/src/client/providers/hoverProvider.ts +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -import * as vscode from 'vscode'; -import { JediFactory } from '../languageServices/jediProxyFactory'; -import { captureTelemetry } from '../telemetry'; -import { EventName } from '../telemetry/constants'; -import { ItemInfoSource } from './itemInfoSource'; - -export class PythonHoverProvider implements vscode.HoverProvider { - private itemInfoSource: ItemInfoSource; - - constructor(jediFactory: JediFactory) { - this.itemInfoSource = new ItemInfoSource(jediFactory); - } - - @captureTelemetry(EventName.HOVER_DEFINITION) - public async provideHover( - document: vscode.TextDocument, - position: vscode.Position, - token: vscode.CancellationToken - ): Promise { - const itemInfos = await this.itemInfoSource.getItemInfoFromDocument(document, position, token); - if (itemInfos) { - return new vscode.Hover(itemInfos.map((item) => item.tooltip)); - } - } -} diff --git a/src/client/providers/importSortProvider.ts b/src/client/providers/importSortProvider.ts deleted file mode 100644 index c55b20fcc49f..000000000000 --- a/src/client/providers/importSortProvider.ts +++ /dev/null @@ -1,248 +0,0 @@ -import { inject, injectable } from 'inversify'; -import { EOL } from 'os'; -import * as path from 'path'; -import { CancellationToken, CancellationTokenSource, TextDocument, Uri, WorkspaceEdit } from 'vscode'; -import { IApplicationShell, ICommandManager, IDocumentManager } from '../common/application/types'; -import { Commands, PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; -import { traceError } from '../common/logger'; -import * as internalScripts from '../common/process/internal/scripts'; -import { IProcessServiceFactory, IPythonExecutionFactory, ObservableExecutionResult } from '../common/process/types'; -import { - IConfigurationService, - IDisposableRegistry, - IEditorUtils, - IOutputChannel, - IPersistentStateFactory -} from '../common/types'; -import { createDeferred, createDeferredFromPromise, Deferred } from '../common/utils/async'; -import { Common, Diagnostics } from '../common/utils/localize'; -import { noop } from '../common/utils/misc'; -import { IServiceContainer } from '../ioc/types'; -import { captureTelemetry } from '../telemetry'; -import { EventName } from '../telemetry/constants'; -import { ISortImportsEditingProvider } from './types'; - -const doNotDisplayPromptStateKey = 'ISORT5_UPGRADE_WARNING_KEY'; - -@injectable() -export class SortImportsEditingProvider implements ISortImportsEditingProvider { - private readonly isortPromises = new Map< - string, - { deferred: Deferred; tokenSource: CancellationTokenSource } - >(); - private readonly processServiceFactory: IProcessServiceFactory; - private readonly pythonExecutionFactory: IPythonExecutionFactory; - private readonly shell: IApplicationShell; - private readonly persistentStateFactory: IPersistentStateFactory; - private readonly documentManager: IDocumentManager; - private readonly configurationService: IConfigurationService; - private readonly editorUtils: IEditorUtils; - private readonly output: IOutputChannel; - - public constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { - this.shell = serviceContainer.get(IApplicationShell); - this.documentManager = serviceContainer.get(IDocumentManager); - this.configurationService = serviceContainer.get(IConfigurationService); - this.pythonExecutionFactory = serviceContainer.get(IPythonExecutionFactory); - this.processServiceFactory = serviceContainer.get(IProcessServiceFactory); - this.editorUtils = serviceContainer.get(IEditorUtils); - this.output = serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); - this.persistentStateFactory = serviceContainer.get(IPersistentStateFactory); - } - - @captureTelemetry(EventName.FORMAT_SORT_IMPORTS) - public async provideDocumentSortImportsEdits(uri: Uri): Promise { - if (this.isortPromises.has(uri.fsPath)) { - const isortPromise = this.isortPromises.get(uri.fsPath)!; - if (!isortPromise.deferred.completed) { - // Cancelling the token will kill the previous isort process & discard its result. - isortPromise.tokenSource.cancel(); - } - } - const tokenSource = new CancellationTokenSource(); - const promise = this._provideDocumentSortImportsEdits(uri, tokenSource.token); - const deferred = createDeferredFromPromise(promise); - this.isortPromises.set(uri.fsPath, { deferred, tokenSource }); - // If token has been cancelled discard the result. - return promise.then((edit) => (tokenSource.token.isCancellationRequested ? undefined : edit)); - } - - public async _provideDocumentSortImportsEdits( - uri: Uri, - token?: CancellationToken - ): Promise { - const document = await this.documentManager.openTextDocument(uri); - if (!document) { - return; - } - if (document.lineCount <= 1) { - return; - } - - const execIsort = await this.getExecIsort(document, uri, token); - if (token && token.isCancellationRequested) { - return; - } - const diffPatch = await execIsort(document.getText()); - - return diffPatch - ? this.editorUtils.getWorkspaceEditsFromPatch(document.getText(), diffPatch, document.uri) - : undefined; - } - - public registerCommands() { - const cmdManager = this.serviceContainer.get(ICommandManager); - const disposable = cmdManager.registerCommand(Commands.Sort_Imports, this.sortImports, this); - this.serviceContainer.get(IDisposableRegistry).push(disposable); - } - - public async sortImports(uri?: Uri): Promise { - if (!uri) { - const activeEditor = this.documentManager.activeTextEditor; - if (!activeEditor || activeEditor.document.languageId !== PYTHON_LANGUAGE) { - this.shell.showErrorMessage('Please open a Python file to sort the imports.').then(noop, noop); - return; - } - uri = activeEditor.document.uri; - } - - const document = await this.documentManager.openTextDocument(uri); - if (document.lineCount <= 1) { - return; - } - - // Hack, if the document doesn't contain an empty line at the end, then add it - // Else the library strips off the last line - const lastLine = document.lineAt(document.lineCount - 1); - if (lastLine.text.trim().length > 0) { - const edit = new WorkspaceEdit(); - edit.insert(uri, lastLine.range.end, EOL); - await this.documentManager.applyEdit(edit); - } - - try { - const changes = await this.provideDocumentSortImportsEdits(uri); - if (!changes || changes.entries().length === 0) { - return; - } - await this.documentManager.applyEdit(changes); - } catch (error) { - const message = typeof error === 'string' ? error : error.message ? error.message : error; - const outputChannel = this.serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); - outputChannel.appendLine(error); - traceError(`Failed to format imports for '${uri.fsPath}'.`, error); - this.shell.showErrorMessage(message).then(noop, noop); - } - } - - public async _showWarningAndOptionallyShowOutput() { - const neverShowAgain = this.persistentStateFactory.createGlobalPersistentState( - doNotDisplayPromptStateKey, - false - ); - if (neverShowAgain.value) { - return; - } - const selection = await this.shell.showWarningMessage( - Diagnostics.checkIsort5UpgradeGuide(), - Common.openOutputPanel(), - Common.doNotShowAgain() - ); - if (selection === Common.openOutputPanel()) { - this.output.show(true); - } else if (selection === Common.doNotShowAgain()) { - await neverShowAgain.updateValue(true); - } - } - - private async getExecIsort( - document: TextDocument, - uri: Uri, - token?: CancellationToken - ): Promise<(documentText: string) => Promise> { - const settings = this.configurationService.getSettings(uri); - const _isort = settings.sortImports.path; - const isort = typeof _isort === 'string' && _isort.length > 0 ? _isort : undefined; - const isortArgs = settings.sortImports.args; - - // We pass the content of the file to be sorted via stdin. This avoids - // saving the file (as well as a potential temporary file), but does - // mean that we need another way to tell `isort` where to look for - // configuration. We do that by setting the working directory to the - // directory which contains the file. - const filename = '-'; - - const spawnOptions = { - token, - cwd: path.dirname(uri.fsPath) - }; - - if (isort) { - const procService = await this.processServiceFactory.create(document.uri); - // Use isort directly instead of the internal script. - return async (documentText: string) => { - const args = getIsortArgs(filename, isortArgs); - const result = procService.execObservable(isort, args, spawnOptions); - return this.communicateWithIsortProcess(result, documentText); - }; - } else { - const procService = await this.pythonExecutionFactory.create({ resource: document.uri }); - return async (documentText: string) => { - const [args, parse] = internalScripts.sortImports(filename, isortArgs); - const result = procService.execObservable(args, spawnOptions); - return parse(await this.communicateWithIsortProcess(result, documentText)); - }; - } - } - - private async communicateWithIsortProcess( - observableResult: ObservableExecutionResult, - inputText: string - ): Promise { - // Configure our listening to the output from isort ... - let outputBuffer = ''; - let isAnyErrorRelatedToUpgradeGuide = false; - const isortOutput = createDeferred(); - observableResult.out.subscribe({ - next: (output) => { - if (output.source === 'stdout') { - outputBuffer += output.out; - } else { - // All the W0500 warning codes point to isort5 upgrade guide: https://pycqa.github.io/isort/docs/warning_and_error_codes/W0500/ - // Do not throw error on these types of stdErrors - isAnyErrorRelatedToUpgradeGuide = isAnyErrorRelatedToUpgradeGuide || output.out.includes('W050'); - traceError(output.out); - if (!output.out.includes('W050')) { - isortOutput.reject(output.out); - } - } - }, - complete: () => { - isortOutput.resolve(outputBuffer); - } - }); - - // ... then send isort the document content ... - observableResult.proc?.stdin.write(inputText); - observableResult.proc?.stdin.end(); - - // .. and finally wait for isort to do its thing - await isortOutput.promise; - - if (isAnyErrorRelatedToUpgradeGuide) { - this._showWarningAndOptionallyShowOutput().ignoreErrors(); - } - return outputBuffer; - } -} - -function getIsortArgs(filename: string, extraArgs?: string[]): string[] { - // We could just adapt internalScripts.sortImports(). However, - // the following is simpler and the alternative doesn't offer - // any signficant benefit. - const args = [filename, '--diff']; - if (extraArgs) { - args.push(...extraArgs); - } - return args; -} diff --git a/src/client/providers/itemInfoSource.ts b/src/client/providers/itemInfoSource.ts deleted file mode 100644 index b7a38a65a884..000000000000 --- a/src/client/providers/itemInfoSource.ts +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import { EOL } from 'os'; -import * as vscode from 'vscode'; -import { RestTextConverter } from '../common/markdown/restTextConverter'; -import { JediFactory } from '../languageServices/jediProxyFactory'; -import * as proxy from './jediProxy'; - -export class LanguageItemInfo { - constructor( - public tooltip: vscode.MarkdownString, - public detail: string, - public signature: vscode.MarkdownString - ) {} -} - -export interface IItemInfoSource { - getItemInfoFromText( - documentUri: vscode.Uri, - fileName: string, - range: vscode.Range, - sourceText: string, - token: vscode.CancellationToken - ): Promise; - getItemInfoFromDocument( - document: vscode.TextDocument, - position: vscode.Position, - token: vscode.CancellationToken - ): Promise; -} - -export class ItemInfoSource implements IItemInfoSource { - private textConverter = new RestTextConverter(); - constructor(private jediFactory: JediFactory) {} - - public async getItemInfoFromText( - documentUri: vscode.Uri, - fileName: string, - range: vscode.Range, - sourceText: string, - token: vscode.CancellationToken - ): Promise { - const result = await this.getHoverResultFromTextRange(documentUri, fileName, range, sourceText, token); - if (!result || !result.items.length) { - return; - } - return this.getItemInfoFromHoverResult(result, ''); - } - - public async getItemInfoFromDocument( - document: vscode.TextDocument, - position: vscode.Position, - token: vscode.CancellationToken - ): Promise { - const range = document.getWordRangeAtPosition(position); - if (!range || range.isEmpty) { - return; - } - const result = await this.getHoverResultFromDocument(document, position, token); - if (!result || !result.items.length) { - return; - } - const word = document.getText(range); - return this.getItemInfoFromHoverResult(result, word); - } - - private async getHoverResultFromDocument( - document: vscode.TextDocument, - position: vscode.Position, - token: vscode.CancellationToken - ): Promise { - if (position.character <= 0 || document.lineAt(position.line).text.match(/^\s*\/\//)) { - return; - } - const range = document.getWordRangeAtPosition(position); - if (!range || range.isEmpty) { - return; - } - return this.getHoverResultFromDocumentRange(document, range, token); - } - - private async getHoverResultFromDocumentRange( - document: vscode.TextDocument, - range: vscode.Range, - token: vscode.CancellationToken - ): Promise { - const cmd: proxy.ICommand = { - command: proxy.CommandType.Hover, - fileName: document.fileName, - columnIndex: range.end.character, - lineIndex: range.end.line - }; - if (document.isDirty) { - cmd.source = document.getText(); - } - return this.jediFactory.getJediProxyHandler(document.uri).sendCommand(cmd, token); - } - - private async getHoverResultFromTextRange( - documentUri: vscode.Uri, - fileName: string, - range: vscode.Range, - sourceText: string, - token: vscode.CancellationToken - ): Promise { - const cmd: proxy.ICommand = { - command: proxy.CommandType.Hover, - fileName: fileName, - columnIndex: range.end.character, - lineIndex: range.end.line, - source: sourceText - }; - return this.jediFactory.getJediProxyHandler(documentUri).sendCommand(cmd, token); - } - - private getItemInfoFromHoverResult(data: proxy.IHoverResult, currentWord: string): LanguageItemInfo[] { - const infos: LanguageItemInfo[] = []; - - data.items.forEach((item) => { - const signature = this.getSignature(item, currentWord); - let tooltip = new vscode.MarkdownString(); - if (item.docstring) { - let lines = item.docstring.split(/\r?\n/); - - // If the docstring starts with the signature, then remove those lines from the docstring. - if (lines.length > 0 && item.signature.indexOf(lines[0]) === 0) { - lines.shift(); - const endIndex = lines.findIndex((line) => item.signature.endsWith(line)); - if (endIndex >= 0) { - lines = lines.filter((_line, index) => index > endIndex); - } - } - if ( - lines.length > 0 && - currentWord.length > 0 && - item.signature.startsWith(currentWord) && - lines[0].startsWith(currentWord) && - lines[0].endsWith(')') - ) { - lines.shift(); - } - - if (signature.length > 0) { - tooltip = tooltip.appendMarkdown(['```python', signature, '```', ''].join(EOL)); - } - - const description = this.textConverter.toMarkdown(lines.join(EOL)); - tooltip = tooltip.appendMarkdown(description); - - infos.push(new LanguageItemInfo(tooltip, item.description, new vscode.MarkdownString(signature))); - return; - } - - if (item.description) { - if (signature.length > 0) { - tooltip.appendMarkdown(['```python', signature, '```', ''].join(EOL)); - } - const description = this.textConverter.toMarkdown(item.description); - tooltip.appendMarkdown(description); - infos.push(new LanguageItemInfo(tooltip, item.description, new vscode.MarkdownString(signature))); - return; - } - - if (item.text) { - // Most probably variable type - const code = currentWord && currentWord.length > 0 ? `${currentWord}: ${item.text}` : item.text; - tooltip.appendMarkdown(['```python', code, '```', ''].join(EOL)); - infos.push(new LanguageItemInfo(tooltip, '', new vscode.MarkdownString())); - } - }); - return infos; - } - - private getSignature(item: proxy.IHoverItem, currentWord: string): string { - let { signature } = item; - switch (item.kind) { - case vscode.SymbolKind.Constructor: - case vscode.SymbolKind.Function: - case vscode.SymbolKind.Method: { - signature = `def ${signature}`; - break; - } - case vscode.SymbolKind.Class: { - signature = `class ${signature}`; - break; - } - case vscode.SymbolKind.Module: { - if (signature.length > 0) { - signature = `module ${signature}`; - } - break; - } - default: { - signature = typeof item.text === 'string' && item.text.length > 0 ? item.text : currentWord; - } - } - return signature; - } -} diff --git a/src/client/providers/jediProxy.ts b/src/client/providers/jediProxy.ts deleted file mode 100644 index 0841bc6fbf47..000000000000 --- a/src/client/providers/jediProxy.ts +++ /dev/null @@ -1,918 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// tslint:disable:no-var-requires no-require-imports no-any -import { ChildProcess } from 'child_process'; -import * as path from 'path'; -// @ts-ignore -import * as pidusage from 'pidusage'; -import { CancellationToken, CancellationTokenSource, CompletionItemKind, Disposable, SymbolKind, Uri } from 'vscode'; -import '../common/extensions'; -import { IS_WINDOWS } from '../common/platform/constants'; -import { IFileSystem } from '../common/platform/types'; -import * as internalPython from '../common/process/internal/python'; -import * as internalScripts from '../common/process/internal/scripts'; -import { IPythonExecutionFactory } from '../common/process/types'; -import { IConfigurationService, IPythonSettings } from '../common/types'; -import { createDeferred, Deferred } from '../common/utils/async'; -import { swallowExceptions } from '../common/utils/decorators'; -import { StopWatch } from '../common/utils/stopWatch'; -import { IEnvironmentVariablesProvider } from '../common/variables/types'; -import { IServiceContainer } from '../ioc/types'; -import { PythonEnvironment } from '../pythonEnvironments/info'; -import { sendTelemetryEvent } from '../telemetry'; -import { EventName } from '../telemetry/constants'; -import { traceError, traceWarning } from './../common/logger'; - -const pythonVSCodeTypeMappings = new Map(); -pythonVSCodeTypeMappings.set('none', CompletionItemKind.Value); -pythonVSCodeTypeMappings.set('type', CompletionItemKind.Class); -pythonVSCodeTypeMappings.set('tuple', CompletionItemKind.Class); -pythonVSCodeTypeMappings.set('dict', CompletionItemKind.Class); -pythonVSCodeTypeMappings.set('dictionary', CompletionItemKind.Class); -pythonVSCodeTypeMappings.set('function', CompletionItemKind.Function); -pythonVSCodeTypeMappings.set('lambda', CompletionItemKind.Function); -pythonVSCodeTypeMappings.set('generator', CompletionItemKind.Function); -pythonVSCodeTypeMappings.set('class', CompletionItemKind.Class); -pythonVSCodeTypeMappings.set('instance', CompletionItemKind.Reference); -pythonVSCodeTypeMappings.set('method', CompletionItemKind.Method); -pythonVSCodeTypeMappings.set('builtin', CompletionItemKind.Class); -pythonVSCodeTypeMappings.set('builtinfunction', CompletionItemKind.Function); -pythonVSCodeTypeMappings.set('module', CompletionItemKind.Module); -pythonVSCodeTypeMappings.set('file', CompletionItemKind.File); -pythonVSCodeTypeMappings.set('xrange', CompletionItemKind.Class); -pythonVSCodeTypeMappings.set('slice', CompletionItemKind.Class); -pythonVSCodeTypeMappings.set('traceback', CompletionItemKind.Class); -pythonVSCodeTypeMappings.set('frame', CompletionItemKind.Class); -pythonVSCodeTypeMappings.set('buffer', CompletionItemKind.Class); -pythonVSCodeTypeMappings.set('dictproxy', CompletionItemKind.Class); -pythonVSCodeTypeMappings.set('funcdef', CompletionItemKind.Function); -pythonVSCodeTypeMappings.set('property', CompletionItemKind.Property); -pythonVSCodeTypeMappings.set('import', CompletionItemKind.Module); -pythonVSCodeTypeMappings.set('keyword', CompletionItemKind.Keyword); -pythonVSCodeTypeMappings.set('constant', CompletionItemKind.Variable); -pythonVSCodeTypeMappings.set('variable', CompletionItemKind.Variable); -pythonVSCodeTypeMappings.set('value', CompletionItemKind.Value); -pythonVSCodeTypeMappings.set('param', CompletionItemKind.Variable); -pythonVSCodeTypeMappings.set('statement', CompletionItemKind.Keyword); - -const pythonVSCodeSymbolMappings = new Map(); -pythonVSCodeSymbolMappings.set('none', SymbolKind.Variable); -pythonVSCodeSymbolMappings.set('type', SymbolKind.Class); -pythonVSCodeSymbolMappings.set('tuple', SymbolKind.Class); -pythonVSCodeSymbolMappings.set('dict', SymbolKind.Class); -pythonVSCodeSymbolMappings.set('dictionary', SymbolKind.Class); -pythonVSCodeSymbolMappings.set('function', SymbolKind.Function); -pythonVSCodeSymbolMappings.set('lambda', SymbolKind.Function); -pythonVSCodeSymbolMappings.set('generator', SymbolKind.Function); -pythonVSCodeSymbolMappings.set('class', SymbolKind.Class); -pythonVSCodeSymbolMappings.set('instance', SymbolKind.Class); -pythonVSCodeSymbolMappings.set('method', SymbolKind.Method); -pythonVSCodeSymbolMappings.set('builtin', SymbolKind.Class); -pythonVSCodeSymbolMappings.set('builtinfunction', SymbolKind.Function); -pythonVSCodeSymbolMappings.set('module', SymbolKind.Module); -pythonVSCodeSymbolMappings.set('file', SymbolKind.File); -pythonVSCodeSymbolMappings.set('xrange', SymbolKind.Array); -pythonVSCodeSymbolMappings.set('slice', SymbolKind.Class); -pythonVSCodeSymbolMappings.set('traceback', SymbolKind.Class); -pythonVSCodeSymbolMappings.set('frame', SymbolKind.Class); -pythonVSCodeSymbolMappings.set('buffer', SymbolKind.Array); -pythonVSCodeSymbolMappings.set('dictproxy', SymbolKind.Class); -pythonVSCodeSymbolMappings.set('funcdef', SymbolKind.Function); -pythonVSCodeSymbolMappings.set('property', SymbolKind.Property); -pythonVSCodeSymbolMappings.set('import', SymbolKind.Module); -pythonVSCodeSymbolMappings.set('keyword', SymbolKind.Variable); -pythonVSCodeSymbolMappings.set('constant', SymbolKind.Constant); -pythonVSCodeSymbolMappings.set('variable', SymbolKind.Variable); -pythonVSCodeSymbolMappings.set('value', SymbolKind.Variable); -pythonVSCodeSymbolMappings.set('param', SymbolKind.Variable); -pythonVSCodeSymbolMappings.set('statement', SymbolKind.Variable); -pythonVSCodeSymbolMappings.set('boolean', SymbolKind.Boolean); -pythonVSCodeSymbolMappings.set('int', SymbolKind.Number); -pythonVSCodeSymbolMappings.set('longlean', SymbolKind.Number); -pythonVSCodeSymbolMappings.set('float', SymbolKind.Number); -pythonVSCodeSymbolMappings.set('complex', SymbolKind.Number); -pythonVSCodeSymbolMappings.set('string', SymbolKind.String); -pythonVSCodeSymbolMappings.set('unicode', SymbolKind.String); -pythonVSCodeSymbolMappings.set('list', SymbolKind.Array); - -function getMappedVSCodeType(pythonType: string): CompletionItemKind { - if (pythonVSCodeTypeMappings.has(pythonType)) { - const value = pythonVSCodeTypeMappings.get(pythonType); - if (value) { - return value; - } - } - return CompletionItemKind.Keyword; -} - -function getMappedVSCodeSymbol(pythonType: string): SymbolKind { - if (pythonVSCodeSymbolMappings.has(pythonType)) { - const value = pythonVSCodeSymbolMappings.get(pythonType); - if (value) { - return value; - } - } - return SymbolKind.Variable; -} - -export enum CommandType { - Arguments, - Completions, - Hover, - Usages, - Definitions, - Symbols -} - -const commandNames = new Map(); -commandNames.set(CommandType.Arguments, 'arguments'); -commandNames.set(CommandType.Completions, 'completions'); -commandNames.set(CommandType.Definitions, 'definitions'); -commandNames.set(CommandType.Hover, 'tooltip'); -commandNames.set(CommandType.Usages, 'usages'); -commandNames.set(CommandType.Symbols, 'names'); - -type JediProxyConfig = { - extraPaths: string[]; - useSnippets: boolean; - caseInsensitiveCompletion: boolean; - showDescriptions: boolean; - fuzzyMatcher: boolean; -}; - -type JediProxyPayload = { - id: number; - prefix: string; - lookup?: string; - path: string; - source?: string; - line?: number; - column?: number; - config: JediProxyConfig; -}; - -export class JediProxy implements Disposable { - private proc?: ChildProcess; - private pythonSettings: IPythonSettings; - private cmdId: number = 0; - private lastKnownPythonInterpreter: string; - private previousData = ''; - private commands = new Map>(); - private commandQueue: number[] = []; - private spawnRetryAttempts = 0; - private additionalAutoCompletePaths: string[] = []; - private workspacePath: string; - private languageServerStarted!: Deferred; - private initialized: Deferred; - private environmentVariablesProvider!: IEnvironmentVariablesProvider; - private ignoreJediMemoryFootprint: boolean = false; - private pidUsageFailures = { timer: new StopWatch(), counter: 0 }; - private lastCmdIdProcessed?: number; - private lastCmdIdProcessedForPidUsage?: number; - private readonly disposables: Disposable[] = []; - private timer?: NodeJS.Timer | number; - - public constructor( - workspacePath: string, - interpreter: PythonEnvironment | undefined, - private serviceContainer: IServiceContainer - ) { - this.workspacePath = workspacePath; - const configurationService = serviceContainer.get(IConfigurationService); - this.pythonSettings = configurationService.getSettings(Uri.file(workspacePath)); - this.lastKnownPythonInterpreter = interpreter ? interpreter.path : this.pythonSettings.pythonPath; - this.initialized = createDeferred(); - this.startLanguageServer() - .then(() => this.initialized.resolve()) - .ignoreErrors(); - this.checkJediMemoryFootprint().ignoreErrors(); - } - - private static getProperty(o: object, name: string): T { - return (o as any)[name]; - } - - public dispose() { - while (this.disposables.length > 0) { - const disposable = this.disposables.pop(); - if (disposable) { - disposable.dispose(); - } - } - if (this.timer) { - clearTimeout(this.timer as any); - } - this.killProcess(); - } - - public getNextCommandId(): number { - const result = this.cmdId; - this.cmdId += 1; - return result; - } - - public async sendCommand(cmd: ICommand): Promise { - await this.initialized.promise; - await this.languageServerStarted.promise; - if (!this.proc) { - return Promise.reject(new Error('Python proc not initialized')); - } - - const executionCmd = >cmd; - const payload = this.createPayload(executionCmd); - executionCmd.deferred = createDeferred(); - try { - this.proc.stdin.write(`${JSON.stringify(payload)}\n`); - this.commands.set(executionCmd.id, executionCmd); - this.commandQueue.push(executionCmd.id); - } catch (ex) { - traceError(ex); - //If 'This socket is closed.' that means process didn't start at all (at least not properly). - if (ex.message === 'This socket is closed.') { - this.killProcess(); - } else { - this.handleError('sendCommand', ex.message); - } - return Promise.reject(ex); - } - return executionCmd.deferred.promise; - } - - // keep track of the directory so we can re-spawn the process. - private initialize(): Promise { - return this.spawnProcess().catch((ex) => { - if (this.languageServerStarted) { - this.languageServerStarted.reject(ex); - } - this.handleError('spawnProcess', ex); - }); - } - private shouldCheckJediMemoryFootprint() { - if (this.ignoreJediMemoryFootprint || this.pythonSettings.jediMemoryLimit === -1) { - return false; - } - if ( - this.lastCmdIdProcessedForPidUsage && - this.lastCmdIdProcessed && - this.lastCmdIdProcessedForPidUsage === this.lastCmdIdProcessed - ) { - // If no more commands were processed since the last time, - // then there's no need to check again. - return false; - } - return true; - } - private async checkJediMemoryFootprint() { - // Check memory footprint periodically. Do not check on every request due to - // the performance impact. See https://github.com/soyuka/pidusage - on Windows - // it is using wmic which means spawning cmd.exe process on every request. - if (this.pythonSettings.jediMemoryLimit === -1) { - return; - } - - await this.checkJediMemoryFootprintImpl(); - if (this.timer) { - clearTimeout(this.timer as any); - } - this.timer = setTimeout(() => this.checkJediMemoryFootprint(), 15 * 1000); - } - private async checkJediMemoryFootprintImpl(): Promise { - if (!this.proc || this.proc.killed) { - return; - } - if (!this.shouldCheckJediMemoryFootprint()) { - return; - } - this.lastCmdIdProcessedForPidUsage = this.lastCmdIdProcessed; - - // Do not run pidusage over and over, wait for it to finish. - const deferred = createDeferred(); - (pidusage as any).stat(this.proc.pid, async (err: any, result: any) => { - if (err) { - this.pidUsageFailures.counter += 1; - // If this function fails 2 times in the last 60 seconds, lets not try ever again. - if (this.pidUsageFailures.timer.elapsedTime > 60 * 1000) { - this.ignoreJediMemoryFootprint = this.pidUsageFailures.counter > 2; - this.pidUsageFailures.counter = 0; - this.pidUsageFailures.timer.reset(); - } - traceError('Python Extension: (pidusage)', err); - } else { - const limit = Math.min(Math.max(this.pythonSettings.jediMemoryLimit, 1024), 8192); - let restartJedi = false; - if (result && result.memory) { - restartJedi = result.memory > limit * 1024 * 1024; - const props = { - mem_use: result.memory, - limit: limit * 1024 * 1024, - isUserDefinedLimit: limit !== 1024, - restart: restartJedi - }; - sendTelemetryEvent(EventName.JEDI_MEMORY, undefined, props); - } - if (restartJedi) { - traceWarning( - `IntelliSense process memory consumption exceeded limit of ${limit} MB and process will be restarted.\nThe limit is controlled by the 'python.jediMemoryLimit' setting.` - ); - await this.restartLanguageServer(); - } - } - - deferred.resolve(); - }); - - return deferred.promise; - } - - // @debounce(1500) - @swallowExceptions('JediProxy') - private async environmentVariablesChangeHandler() { - const newAutoComletePaths = await this.buildAutoCompletePaths(); - if (this.additionalAutoCompletePaths.join(',') !== newAutoComletePaths.join(',')) { - this.additionalAutoCompletePaths = newAutoComletePaths; - this.restartLanguageServer().ignoreErrors(); - } - } - @swallowExceptions('JediProxy') - private async startLanguageServer(): Promise { - const newAutoComletePaths = await this.buildAutoCompletePaths(); - this.additionalAutoCompletePaths = newAutoComletePaths; - return this.restartLanguageServer(); - } - private restartLanguageServer(): Promise { - this.killProcess(); - this.clearPendingRequests(); - return this.initialize(); - } - - private clearPendingRequests() { - this.commandQueue = []; - this.commands.forEach((item) => { - if (item.deferred !== undefined) { - item.deferred.resolve(); - } - }); - this.commands.clear(); - } - - private killProcess() { - try { - if (this.proc) { - this.proc.kill(); - } - // tslint:disable-next-line:no-empty - } catch (ex) {} - this.proc = undefined; - } - - private handleError(source: string, errorMessage: string) { - traceError(`${source} jediProxy`, `Error (${source}) ${errorMessage}`); - } - - // tslint:disable-next-line:max-func-body-length - private async spawnProcess() { - if (this.languageServerStarted && !this.languageServerStarted.completed) { - this.languageServerStarted.reject(new Error('Language server not started.')); - } - this.languageServerStarted = createDeferred(); - const pythonProcess = await this.serviceContainer - .get(IPythonExecutionFactory) - .create({ resource: Uri.file(this.workspacePath), pythonPath: this.lastKnownPythonInterpreter }); - // Check if the python path is valid. - if ((await pythonProcess.getExecutablePath().catch(() => '')).length === 0) { - return; - } - const [args, parse] = internalScripts.completion(this.pythonSettings.jediPath); - const result = pythonProcess.execObservable(args, {}); - this.proc = result.proc; - this.languageServerStarted.resolve(); - this.proc!.on('end', (end) => { - traceError('spawnProcess.end', `End - ${end}`); - }); - this.proc!.on('error', (error) => { - this.handleError('error', `${error}`); - this.spawnRetryAttempts += 1; - if ( - this.spawnRetryAttempts < 10 && - error && - error.message && - error.message.indexOf('This socket has been ended by the other party') >= 0 - ) { - this.spawnProcess().catch((ex) => { - if (this.languageServerStarted) { - this.languageServerStarted.reject(ex); - } - this.handleError('spawnProcess', ex); - }); - } - }); - result.out.subscribe( - (output) => { - if (output.source === 'stderr') { - this.handleError('stderr', output.out); - } else { - const data = output.out; - // Possible there was an exception in parsing the data returned, - // so append the data and then parse it. - const dataStr = (this.previousData = `${this.previousData}${data}`); - // tslint:disable-next-line:no-any - let responses: any[]; - try { - responses = parse(dataStr); - this.previousData = ''; - } catch (ex) { - // Possible we've only received part of the data, hence don't clear previousData. - // Don't log errors when we haven't received the entire response. - if ( - ex.message.indexOf('Unexpected end of input') === -1 && - ex.message.indexOf('Unexpected end of JSON input') === -1 && - ex.message.indexOf('Unexpected token') === -1 - ) { - this.handleError('stdout', ex.message); - } - return; - } - - responses.forEach((response) => { - if (!response) { - return; - } - const responseId = JediProxy.getProperty(response, 'id'); - if (!this.commands.has(responseId)) { - return; - } - const cmd = this.commands.get(responseId); - if (!cmd) { - return; - } - this.lastCmdIdProcessed = cmd.id; - if (JediProxy.getProperty(response, 'arguments')) { - this.commandQueue.splice(this.commandQueue.indexOf(cmd.id), 1); - return; - } - - this.commands.delete(responseId); - const index = this.commandQueue.indexOf(cmd.id); - if (index) { - this.commandQueue.splice(index, 1); - } - - // Check if this command has expired. - if (cmd.token.isCancellationRequested) { - this.safeResolve(cmd, undefined); - return; - } - - const handler = this.getCommandHandler(cmd.command); - if (handler) { - handler.call(this, cmd, response); - } - // Check if too many pending requests. - this.checkQueueLength(); - }); - } - }, - (error) => this.handleError('subscription.error', `${error}`) - ); - } - private getCommandHandler( - command: CommandType - ): undefined | ((command: IExecutionCommand, response: object) => void) { - switch (command) { - case CommandType.Completions: - return this.onCompletion; - case CommandType.Definitions: - return this.onDefinition; - case CommandType.Hover: - return this.onHover; - case CommandType.Symbols: - return this.onSymbols; - case CommandType.Usages: - return this.onUsages; - case CommandType.Arguments: - return this.onArguments; - default: - return; - } - } - private onCompletion(command: IExecutionCommand, response: object): void { - let results = JediProxy.getProperty(response, 'results'); - results = Array.isArray(results) ? results : []; - results.forEach((item) => { - // tslint:disable-next-line:no-any - const originalType = (item.type); - item.type = getMappedVSCodeType(originalType); - item.kind = getMappedVSCodeSymbol(originalType); - item.rawType = getMappedVSCodeType(originalType); - }); - const completionResult: ICompletionResult = { - items: results, - requestId: command.id - }; - this.safeResolve(command, completionResult); - } - - private onDefinition(command: IExecutionCommand, response: object): void { - // tslint:disable-next-line:no-any - const defs = JediProxy.getProperty(response, 'results'); - const defResult: IDefinitionResult = { - requestId: command.id, - definitions: [] - }; - if (defs.length > 0) { - defResult.definitions = defs.map((def) => { - const originalType = def.type as string; - return { - fileName: def.fileName, - text: def.text, - rawType: originalType, - type: getMappedVSCodeType(originalType), - kind: getMappedVSCodeSymbol(originalType), - container: def.container, - range: { - startLine: def.range.start_line, - startColumn: def.range.start_column, - endLine: def.range.end_line, - endColumn: def.range.end_column - } - }; - }); - } - this.safeResolve(command, defResult); - } - - private onHover(command: IExecutionCommand, response: object): void { - // tslint:disable-next-line:no-any - const defs = JediProxy.getProperty(response, 'results'); - const defResult: IHoverResult = { - requestId: command.id, - items: defs.map((def) => { - return { - kind: getMappedVSCodeSymbol(def.type), - description: def.description, - signature: def.signature, - docstring: def.docstring, - text: def.text - }; - }) - }; - this.safeResolve(command, defResult); - } - - private onSymbols(command: IExecutionCommand, response: object): void { - // tslint:disable-next-line:no-any - let defs = JediProxy.getProperty(response, 'results'); - defs = Array.isArray(defs) ? defs : []; - const defResults: ISymbolResult = { - requestId: command.id, - definitions: [] - }; - defResults.definitions = defs.map((def) => { - const originalType = def.type as string; - return { - fileName: def.fileName, - text: def.text, - rawType: originalType, - type: getMappedVSCodeType(originalType), - kind: getMappedVSCodeSymbol(originalType), - container: def.container, - range: { - startLine: def.range.start_line, - startColumn: def.range.start_column, - endLine: def.range.end_line, - endColumn: def.range.end_column - } - }; - }); - this.safeResolve(command, defResults); - } - - private onUsages(command: IExecutionCommand, response: object): void { - // tslint:disable-next-line:no-any - let defs = JediProxy.getProperty(response, 'results'); - defs = Array.isArray(defs) ? defs : []; - const refResult: IReferenceResult = { - requestId: command.id, - references: defs.map((item) => { - return { - columnIndex: item.column, - fileName: item.fileName, - lineIndex: item.line - 1, - moduleName: item.moduleName, - name: item.name - }; - }) - }; - this.safeResolve(command, refResult); - } - - private onArguments(command: IExecutionCommand, response: object): void { - // tslint:disable-next-line:no-any - const defs = JediProxy.getProperty(response, 'results'); - // tslint:disable-next-line:no-object-literal-type-assertion - this.safeResolve(command, { - requestId: command.id, - definitions: defs - }); - } - - private checkQueueLength(): void { - if (this.commandQueue.length > 10) { - const items = this.commandQueue.splice(0, this.commandQueue.length - 10); - items.forEach((id) => { - if (this.commands.has(id)) { - const cmd1 = this.commands.get(id); - try { - this.safeResolve(cmd1, undefined); - // tslint:disable-next-line:no-empty - } catch (ex) { - } finally { - this.commands.delete(id); - } - } - }); - } - } - - private createPayload(cmd: IExecutionCommand): JediProxyPayload { - const payload: JediProxyPayload = { - id: cmd.id, - prefix: '', - lookup: commandNames.get(cmd.command), - path: cmd.fileName, - source: cmd.source, - line: cmd.lineIndex, - column: cmd.columnIndex, - config: this.getConfig() - }; - - if (cmd.command === CommandType.Symbols) { - delete payload.column; - delete payload.line; - } - - return payload; - } - - private async getPathFromPython(getArgs = internalPython.getExecutable): Promise { - const [args, parse] = getArgs(); - try { - const pythonProcess = await this.serviceContainer - .get(IPythonExecutionFactory) - .create({ resource: Uri.file(this.workspacePath), pythonPath: this.lastKnownPythonInterpreter }); - const result = await pythonProcess.exec(args, { cwd: this.workspacePath }); - const lines = parse(result.stdout).splitLines(); - if (lines.length === 0) { - return ''; - } - const fs = this.serviceContainer.get(IFileSystem); - const exists = await fs.fileExists(lines[0]); - return exists ? lines[0] : ''; - } catch { - return ''; - } - } - private async buildAutoCompletePaths(): Promise { - const filePathPromises = [ - // Sysprefix. - this.getPathFromPython(internalPython.getSysPrefix).catch(() => ''), - // exeucutable path. - this.getPathFromPython(internalPython.getExecutable) - .then((execPath) => path.dirname(execPath)) - .catch(() => ''), - // Python specific site packages. - this.getPathFromPython(internalPython.getSitePackages) - .then((libPath) => { - // On windows we also need the libs path (second item will return c:\xxx\lib\site-packages). - // This is returned by "from distutils.sysconfig import get_python_lib; print(get_python_lib())". - return IS_WINDOWS && libPath.length > 0 ? path.join(libPath, '..') : libPath; - }) - .catch(() => ''), - // Python global site packages, as a fallback in case user hasn't installed them in custom environment. - this.getPathFromPython(internalPython.getUserSitePackages).catch(() => '') - ]; - - try { - const pythonPaths = await this.getEnvironmentVariablesProvider() - .getEnvironmentVariables(Uri.file(this.workspacePath)) - .then((customEnvironmentVars) => - customEnvironmentVars ? JediProxy.getProperty(customEnvironmentVars, 'PYTHONPATH') : '' - ) - .then((pythonPath) => - typeof pythonPath === 'string' && pythonPath.trim().length > 0 ? pythonPath.trim() : '' - ) - .then((pythonPath) => pythonPath.split(path.delimiter).filter((item) => item.trim().length > 0)); - const resolvedPaths = pythonPaths - .filter((pythonPath) => !path.isAbsolute(pythonPath)) - .map((pythonPath) => path.resolve(this.workspacePath, pythonPath)); - const filePaths = await Promise.all(filePathPromises); - return filePaths.concat(...pythonPaths, ...resolvedPaths).filter((p) => p.length > 0); - } catch (ex) { - traceError('Python Extension: jediProxy.filePaths', ex); - return []; - } - } - private getEnvironmentVariablesProvider() { - if (!this.environmentVariablesProvider) { - this.environmentVariablesProvider = this.serviceContainer.get( - IEnvironmentVariablesProvider - ); - this.environmentVariablesProvider.onDidEnvironmentVariablesChange( - this.environmentVariablesChangeHandler.bind(this) - ); - } - return this.environmentVariablesProvider; - } - private getConfig(): JediProxyConfig { - // Add support for paths relative to workspace. - const extraPaths = this.pythonSettings.autoComplete - ? this.pythonSettings.autoComplete.extraPaths.map((extraPath) => { - if (path.isAbsolute(extraPath)) { - return extraPath; - } - if (typeof this.workspacePath !== 'string') { - return ''; - } - return path.join(this.workspacePath, extraPath); - }) - : []; - - // Always add workspace path into extra paths. - if (typeof this.workspacePath === 'string') { - extraPaths.unshift(this.workspacePath); - } - - const distinctExtraPaths = extraPaths - .concat(this.additionalAutoCompletePaths) - .filter((value) => value.length > 0) - .filter((value, index, self) => self.indexOf(value) === index); - - return { - extraPaths: distinctExtraPaths, - useSnippets: false, - caseInsensitiveCompletion: true, - showDescriptions: true, - fuzzyMatcher: true - }; - } - - private safeResolve( - command: IExecutionCommand | undefined | null, - result: ICommandResult | PromiseLike | undefined - ): void { - if (command && command.deferred) { - command.deferred.resolve(result); - } - } -} - -// tslint:disable-next-line:no-unused-variable -export interface ICommand { - telemetryEvent?: string; - command: CommandType; - source?: string; - fileName: string; - lineIndex: number; - columnIndex: number; -} - -interface IExecutionCommand extends ICommand { - id: number; - deferred?: Deferred; - token: CancellationToken; - delay?: number; -} - -export interface ICommandError { - message: string; -} - -export interface ICommandResult { - requestId: number; -} -export interface ICompletionResult extends ICommandResult { - items: IAutoCompleteItem[]; -} -export interface IHoverResult extends ICommandResult { - items: IHoverItem[]; -} -export interface IDefinitionResult extends ICommandResult { - definitions: IDefinition[]; -} -export interface IReferenceResult extends ICommandResult { - references: IReference[]; -} -export interface ISymbolResult extends ICommandResult { - definitions: IDefinition[]; -} -export interface IArgumentsResult extends ICommandResult { - definitions: ISignature[]; -} - -export interface ISignature { - name: string; - docstring: string; - description: string; - paramindex: number; - params: IArgument[]; -} -export interface IArgument { - name: string; - value: string; - docstring: string; - description: string; -} - -export interface IReference { - name: string; - fileName: string; - columnIndex: number; - lineIndex: number; - moduleName: string; -} - -export interface IAutoCompleteItem { - type: CompletionItemKind; - rawType: CompletionItemKind; - kind: SymbolKind; - text: string; - description: string; - raw_docstring: string; - rightLabel: string; -} -export interface IDefinitionRange { - startLine: number; - startColumn: number; - endLine: number; - endColumn: number; -} -export interface IDefinition { - rawType: string; - type: CompletionItemKind; - kind: SymbolKind; - text: string; - fileName: string; - container: string; - range: IDefinitionRange; -} - -export interface IHoverItem { - kind: SymbolKind; - text: string; - description: string; - docstring: string; - signature: string; -} - -export class JediProxyHandler implements Disposable { - private commandCancellationTokenSources: Map; - - public constructor(private jediProxy: JediProxy) { - this.commandCancellationTokenSources = new Map(); - } - - public get JediProxy(): JediProxy { - return this.jediProxy; - } - - public dispose() { - if (this.jediProxy) { - this.jediProxy.dispose(); - } - } - - public sendCommand(cmd: ICommand, _token?: CancellationToken): Promise { - const executionCmd = >cmd; - executionCmd.id = executionCmd.id || this.jediProxy.getNextCommandId(); - - if (this.commandCancellationTokenSources.has(cmd.command)) { - const ct = this.commandCancellationTokenSources.get(cmd.command); - if (ct) { - ct.cancel(); - } - } - - const cancellation = new CancellationTokenSource(); - this.commandCancellationTokenSources.set(cmd.command, cancellation); - executionCmd.token = cancellation.token; - - return this.jediProxy.sendCommand(executionCmd).catch((reason) => { - traceError(reason); - return undefined; - }); - } - - public sendCommandNonCancellableCommand(cmd: ICommand, token?: CancellationToken): Promise { - const executionCmd = >cmd; - executionCmd.id = executionCmd.id || this.jediProxy.getNextCommandId(); - if (token) { - executionCmd.token = token; - } - - return this.jediProxy.sendCommand(executionCmd).catch((reason) => { - traceError(reason); - return undefined; - }); - } -} diff --git a/src/client/providers/linterProvider.ts b/src/client/providers/linterProvider.ts deleted file mode 100644 index 8bfde47e192b..000000000000 --- a/src/client/providers/linterProvider.ts +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { ConfigurationChangeEvent, Disposable, TextDocument, Uri, workspace } from 'vscode'; -import { IExtensionActivationService } from '../activation/types'; -import { IDocumentManager, IWorkspaceService } from '../common/application/types'; -import { isTestExecution } from '../common/constants'; -import '../common/extensions'; -import { IFileSystem } from '../common/platform/types'; -import { IConfigurationService, IDisposable } from '../common/types'; -import { IInterpreterService } from '../interpreter/contracts'; -import { IServiceContainer } from '../ioc/types'; -import { ILinterManager, ILintingEngine } from '../linters/types'; - -@injectable() -export class LinterProvider implements IExtensionActivationService, Disposable { - private interpreterService: IInterpreterService; - private documents: IDocumentManager; - private configuration: IConfigurationService; - private linterManager: ILinterManager; - private engine: ILintingEngine; - private fs: IFileSystem; - private readonly disposables: IDisposable[] = []; - private workspaceService: IWorkspaceService; - private activatedOnce: boolean = false; - - constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { - this.serviceContainer = serviceContainer; - this.fs = this.serviceContainer.get(IFileSystem); - this.engine = this.serviceContainer.get(ILintingEngine); - this.linterManager = this.serviceContainer.get(ILinterManager); - this.interpreterService = this.serviceContainer.get(IInterpreterService); - this.documents = this.serviceContainer.get(IDocumentManager); - this.configuration = this.serviceContainer.get(IConfigurationService); - this.workspaceService = this.serviceContainer.get(IWorkspaceService); - } - - public async activate(): Promise { - if (this.activatedOnce) { - return; - } - this.activatedOnce = true; - this.disposables.push(this.interpreterService.onDidChangeInterpreter(() => this.engine.lintOpenPythonFiles())); - - this.documents.onDidOpenTextDocument((e) => this.onDocumentOpened(e), this.disposables); - this.documents.onDidCloseTextDocument((e) => this.onDocumentClosed(e), this.disposables); - this.documents.onDidSaveTextDocument((e) => this.onDocumentSaved(e), this.disposables); - - const disposable = this.workspaceService.onDidChangeConfiguration(this.lintSettingsChangedHandler.bind(this)); - this.disposables.push(disposable); - - // On workspace reopen we don't get `onDocumentOpened` since it is first opened - // and then the extension is activated. So schedule linting pass now. - if (!isTestExecution()) { - const timer = setTimeout(() => this.engine.lintOpenPythonFiles().ignoreErrors(), 1200); - this.disposables.push({ dispose: () => clearTimeout(timer) }); - } - } - - public dispose() { - this.disposables.forEach((d) => d.dispose()); - } - - private isDocumentOpen(uri: Uri): boolean { - return this.documents.textDocuments.some((document) => this.fs.arePathsSame(document.uri.fsPath, uri.fsPath)); - } - - private lintSettingsChangedHandler(e: ConfigurationChangeEvent) { - // Look for python files that belong to the specified workspace folder. - workspace.textDocuments.forEach((document) => { - if (e.affectsConfiguration('python.linting', document.uri)) { - this.engine.lintDocument(document, 'auto').ignoreErrors(); - } - }); - } - - private onDocumentOpened(document: TextDocument): void { - this.engine.lintDocument(document, 'auto').ignoreErrors(); - } - - private onDocumentSaved(document: TextDocument): void { - const settings = this.configuration.getSettings(document.uri); - if (document.languageId === 'python' && settings.linting.enabled && settings.linting.lintOnSave) { - this.engine.lintDocument(document, 'save').ignoreErrors(); - return; - } - - this.linterManager - .getActiveLinters(false, document.uri) - .then((linters) => { - const fileName = path.basename(document.uri.fsPath).toLowerCase(); - const watchers = linters.filter((info) => info.configFileNames.indexOf(fileName) >= 0); - if (watchers.length > 0) { - setTimeout(() => this.engine.lintOpenPythonFiles(), 1000); - } - }) - .ignoreErrors(); - } - - private onDocumentClosed(document: TextDocument) { - if (!document || !document.fileName || !document.uri) { - return; - } - // Check if this document is still open as a duplicate editor. - if (!this.isDocumentOpen(document.uri)) { - this.engine.clearDiagnostics(document); - } - } -} diff --git a/src/client/providers/objectDefinitionProvider.ts b/src/client/providers/objectDefinitionProvider.ts deleted file mode 100644 index fa886cfe05ef..000000000000 --- a/src/client/providers/objectDefinitionProvider.ts +++ /dev/null @@ -1,87 +0,0 @@ -'use strict'; - -import * as vscode from 'vscode'; -import { JediFactory } from '../languageServices/jediProxyFactory'; -import { captureTelemetry } from '../telemetry'; -import { EventName } from '../telemetry/constants'; -import * as defProvider from './definitionProvider'; - -export class PythonObjectDefinitionProvider { - private readonly _defProvider: defProvider.PythonDefinitionProvider; - public constructor(jediFactory: JediFactory) { - this._defProvider = new defProvider.PythonDefinitionProvider(jediFactory); - } - - @captureTelemetry(EventName.GO_TO_OBJECT_DEFINITION) - public async goToObjectDefinition() { - const pathDef = await this.getObjectDefinition(); - if (typeof pathDef !== 'string' || pathDef.length === 0) { - return; - } - - const parts = pathDef.split('.'); - let source = ''; - let startColumn = 0; - if (parts.length === 1) { - source = `import ${parts[0]}`; - startColumn = 'import '.length; - } else { - const mod = parts.shift(); - source = `from ${mod} import ${parts.join('.')}`; - startColumn = `from ${mod} import `.length; - } - const range = new vscode.Range(0, startColumn, 0, source.length - 1); - // tslint:disable-next-line:no-any - const doc = ({ - fileName: 'test.py', - lineAt: (_line: number) => { - return { text: source }; - }, - getWordRangeAtPosition: (_position: vscode.Position) => range, - isDirty: true, - getText: () => source - }); - - const tokenSource = new vscode.CancellationTokenSource(); - const defs = await this._defProvider.provideDefinition(doc, range.start, tokenSource.token); - - if (defs === null) { - await vscode.window.showInformationMessage(`Definition not found for '${pathDef}'`); - return; - } - - let uri: vscode.Uri | undefined; - let lineNumber: number; - if (Array.isArray(defs) && defs.length > 0) { - uri = defs[0].uri; - lineNumber = defs[0].range.start.line; - } - if (defs && !Array.isArray(defs) && defs.uri) { - uri = defs.uri; - lineNumber = defs.range.start.line; - } - - if (uri) { - const openedDoc = await vscode.workspace.openTextDocument(uri); - await vscode.window.showTextDocument(openedDoc); - await vscode.commands.executeCommand('revealLine', { lineNumber: lineNumber!, at: 'top' }); - } else { - await vscode.window.showInformationMessage(`Definition not found for '${pathDef}'`); - } - } - - private intputValidation(value: string): string | undefined | null { - if (typeof value !== 'string') { - return ''; - } - value = value.trim(); - if (value.length === 0) { - return ''; - } - - return null; - } - private async getObjectDefinition(): Promise { - return vscode.window.showInputBox({ prompt: 'Enter Object Path', validateInput: this.intputValidation }); - } -} diff --git a/src/client/providers/providerUtilities.ts b/src/client/providers/providerUtilities.ts deleted file mode 100644 index 7a330b51eec0..000000000000 --- a/src/client/providers/providerUtilities.ts +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { Position, Range, TextDocument } from 'vscode'; -import { Tokenizer } from '../language/tokenizer'; -import { ITextRangeCollection, IToken, TokenizerMode, TokenType } from '../language/types'; - -export function getDocumentTokens( - document: TextDocument, - tokenizeTo: Position, - mode: TokenizerMode -): ITextRangeCollection { - const text = document.getText(new Range(new Position(0, 0), tokenizeTo)); - return new Tokenizer().tokenize(text, 0, text.length, mode); -} - -export function isPositionInsideStringOrComment(document: TextDocument, position: Position): boolean { - const tokenizeTo = position.translate(1, 0); - const tokens = getDocumentTokens(document, tokenizeTo, TokenizerMode.CommentsAndStrings); - const offset = document.offsetAt(position); - const index = tokens.getItemContaining(offset - 1); - if (index >= 0) { - const token = tokens.getItemAt(index); - return token.type === TokenType.String || token.type === TokenType.Comment; - } - if (offset > 0 && index >= 0) { - // In case position is at the every end of the comment or unterminated string - const token = tokens.getItemAt(index); - return token.end === offset && token.type === TokenType.Comment; - } - return false; -} diff --git a/src/client/providers/referenceProvider.ts b/src/client/providers/referenceProvider.ts deleted file mode 100644 index 1a392dcb79d3..000000000000 --- a/src/client/providers/referenceProvider.ts +++ /dev/null @@ -1,77 +0,0 @@ -'use strict'; - -import * as vscode from 'vscode'; -import { JediFactory } from '../languageServices/jediProxyFactory'; -import { captureTelemetry } from '../telemetry'; -import { EventName } from '../telemetry/constants'; -import * as proxy from './jediProxy'; - -export class PythonReferenceProvider implements vscode.ReferenceProvider { - public constructor(private jediFactory: JediFactory) {} - private static parseData(data: proxy.IReferenceResult): vscode.Location[] { - if (data && data.references.length > 0) { - // tslint:disable-next-line:no-unnecessary-local-variable - const references = data.references - .filter((ref) => { - if ( - !ref || - typeof ref.columnIndex !== 'number' || - typeof ref.lineIndex !== 'number' || - typeof ref.fileName !== 'string' || - ref.columnIndex === -1 || - ref.lineIndex === -1 || - ref.fileName.length === 0 - ) { - return false; - } - return true; - }) - .map((ref) => { - const definitionResource = vscode.Uri.file(ref.fileName); - const range = new vscode.Range(ref.lineIndex, ref.columnIndex, ref.lineIndex, ref.columnIndex); - - return new vscode.Location(definitionResource, range); - }); - - return references; - } - return []; - } - - @captureTelemetry(EventName.REFERENCE) - public async provideReferences( - document: vscode.TextDocument, - position: vscode.Position, - _context: vscode.ReferenceContext, - token: vscode.CancellationToken - ): Promise { - const filename = document.fileName; - if (document.lineAt(position.line).text.match(/^\s*\/\//)) { - return; - } - if (position.character <= 0) { - return; - } - - const range = document.getWordRangeAtPosition(position); - if (!range) { - return; - } - const columnIndex = range.isEmpty ? position.character : range.end.character; - const cmd: proxy.ICommand = { - command: proxy.CommandType.Usages, - fileName: filename, - columnIndex: columnIndex, - lineIndex: position.line - }; - - if (document.isDirty) { - cmd.source = document.getText(); - } - - const data = await this.jediFactory - .getJediProxyHandler(document.uri) - .sendCommand(cmd, token); - return data ? PythonReferenceProvider.parseData(data) : undefined; - } -} diff --git a/src/client/providers/renameProvider.ts b/src/client/providers/renameProvider.ts deleted file mode 100644 index 86037f894c60..000000000000 --- a/src/client/providers/renameProvider.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { - CancellationToken, - OutputChannel, - Position, - ProviderResult, - RenameProvider, - TextDocument, - Uri, - window, - workspace, - WorkspaceEdit -} from 'vscode'; -import { STANDARD_OUTPUT_CHANNEL } from '../common/constants'; -import { getWorkspaceEditsFromPatch } from '../common/editor'; -import { traceError } from '../common/logger'; -import { IFileSystem } from '../common/platform/types'; -import { IPythonExecutionFactory } from '../common/process/types'; -import { IInstaller, IOutputChannel, Product } from '../common/types'; -import { IServiceContainer } from '../ioc/types'; -import { RefactorProxy } from '../refactor/proxy'; -import { captureTelemetry } from '../telemetry'; -import { EventName } from '../telemetry/constants'; - -type RenameResponse = { - results: [{ diff: string }]; -}; - -export class PythonRenameProvider implements RenameProvider { - private readonly outputChannel: OutputChannel; - constructor(private serviceContainer: IServiceContainer) { - this.outputChannel = serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); - } - @captureTelemetry(EventName.REFACTOR_RENAME) - public provideRenameEdits( - document: TextDocument, - position: Position, - newName: string, - _token: CancellationToken - ): ProviderResult { - return workspace.saveAll(false).then(() => { - return this.doRename(document, position, newName); - }); - } - - private doRename(document: TextDocument, position: Position, newName: string): ProviderResult { - if (document.lineAt(position.line).text.match(/^\s*\/\//)) { - return; - } - if (position.character <= 0) { - return; - } - - const range = document.getWordRangeAtPosition(position); - if (!range || range.isEmpty) { - return; - } - const oldName = document.getText(range); - if (oldName === newName) { - return; - } - - let workspaceFolder = workspace.getWorkspaceFolder(document.uri); - if (!workspaceFolder && Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length > 0) { - workspaceFolder = workspace.workspaceFolders[0]; - } - const workspaceRoot = workspaceFolder ? workspaceFolder.uri.fsPath : __dirname; - - const proxy = new RefactorProxy(workspaceRoot, async () => { - const factory = this.serviceContainer.get(IPythonExecutionFactory); - return factory.create({ resource: Uri.file(workspaceRoot) }); - }); - return proxy - .rename(document, newName, document.uri.fsPath, range) - .then((response) => { - const fileDiffs = response.results.map((fileChanges) => fileChanges.diff); - const fs = this.serviceContainer.get(IFileSystem); - return getWorkspaceEditsFromPatch(fileDiffs, workspaceRoot, fs); - }) - .catch((reason) => { - if (reason === 'Not installed') { - const installer = this.serviceContainer.get(IInstaller); - installer - .promptToInstall(Product.rope, document.uri) - .catch((ex) => traceError('Python Extension: promptToInstall', ex)); - return Promise.reject(''); - } else { - window.showErrorMessage(reason); - this.outputChannel.appendLine(reason); - } - return Promise.reject(reason); - }); - } -} diff --git a/src/client/providers/replProvider.ts b/src/client/providers/replProvider.ts index 5753df8d1d83..dd9df89a78a3 100644 --- a/src/client/providers/replProvider.ts +++ b/src/client/providers/replProvider.ts @@ -1,30 +1,43 @@ import { Disposable } from 'vscode'; import { IActiveResourceService, ICommandManager } from '../common/application/types'; import { Commands } from '../common/constants'; +import { noop } from '../common/utils/misc'; +import { IInterpreterService } from '../interpreter/contracts'; import { IServiceContainer } from '../ioc/types'; -import { captureTelemetry } from '../telemetry'; -import { EventName } from '../telemetry/constants'; import { ICodeExecutionService } from '../terminals/types'; export class ReplProvider implements Disposable { private readonly disposables: Disposable[] = []; + private activeResourceService: IActiveResourceService; + constructor(private serviceContainer: IServiceContainer) { this.activeResourceService = this.serviceContainer.get(IActiveResourceService); this.registerCommand(); } - public dispose() { + + public dispose(): void { this.disposables.forEach((disposable) => disposable.dispose()); } + private registerCommand() { const commandManager = this.serviceContainer.get(ICommandManager); const disposable = commandManager.registerCommand(Commands.Start_REPL, this.commandHandler, this); this.disposables.push(disposable); } - @captureTelemetry(EventName.REPL) + private async commandHandler() { const resource = this.activeResourceService.getActiveResource(); - const replProvider = this.serviceContainer.get(ICodeExecutionService, 'repl'); + const interpreterService = this.serviceContainer.get(IInterpreterService); + const interpreter = await interpreterService.getActiveInterpreter(resource); + if (!interpreter) { + this.serviceContainer + .get(ICommandManager) + .executeCommand(Commands.TriggerEnvironmentSelection, resource) + .then(noop, noop); + return; + } + const replProvider = this.serviceContainer.get(ICodeExecutionService, 'standard'); await replProvider.initializeRepl(resource); } } diff --git a/src/client/providers/serviceRegistry.ts b/src/client/providers/serviceRegistry.ts index 66640455f08a..a96ec14ff5e9 100644 --- a/src/client/providers/serviceRegistry.ts +++ b/src/client/providers/serviceRegistry.ts @@ -6,13 +6,10 @@ import { IExtensionSingleActivationService } from '../activation/types'; import { IServiceManager } from '../ioc/types'; import { CodeActionProviderService } from './codeActionProvider/main'; -import { SortImportsEditingProvider } from './importSortProvider'; -import { ISortImportsEditingProvider } from './types'; -export function registerTypes(serviceManager: IServiceManager) { - serviceManager.addSingleton(ISortImportsEditingProvider, SortImportsEditingProvider); +export function registerTypes(serviceManager: IServiceManager): void { serviceManager.addSingleton( IExtensionSingleActivationService, - CodeActionProviderService + CodeActionProviderService, ); } diff --git a/src/client/providers/signatureProvider.ts b/src/client/providers/signatureProvider.ts deleted file mode 100644 index df10f120b21a..000000000000 --- a/src/client/providers/signatureProvider.ts +++ /dev/null @@ -1,139 +0,0 @@ -'use strict'; - -import { EOL } from 'os'; -import { - CancellationToken, - ParameterInformation, - Position, - SignatureHelp, - SignatureHelpProvider, - SignatureInformation, - TextDocument -} from 'vscode'; -import { JediFactory } from '../languageServices/jediProxyFactory'; -import { captureTelemetry } from '../telemetry'; -import { EventName } from '../telemetry/constants'; -import * as proxy from './jediProxy'; -import { isPositionInsideStringOrComment } from './providerUtilities'; - -const DOCSTRING_PARAM_PATTERNS = [ - '\\s*:type\\s*PARAMNAME:\\s*([^\\n, ]+)', // Sphinx - '\\s*:param\\s*(\\w?)\\s*PARAMNAME:[^\\n]+', // Sphinx param with type - '\\s*@type\\s*PARAMNAME:\\s*([^\\n, ]+)' // Epydoc -]; - -/** - * Extract the documentation for parameters from a given docstring. - * @param {string} paramName Name of the parameter - * @param {string} docString The docstring for the function - * @returns {string} Docstring for the parameter - */ -function extractParamDocString(paramName: string, docString: string): string { - let paramDocString = ''; - // In docstring the '*' is escaped with a backslash - paramName = paramName.replace(new RegExp('\\*', 'g'), '\\\\\\*'); - - DOCSTRING_PARAM_PATTERNS.forEach((pattern) => { - if (paramDocString.length > 0) { - return; - } - pattern = pattern.replace('PARAMNAME', paramName); - const regExp = new RegExp(pattern); - const matches = regExp.exec(docString); - if (matches && matches.length > 0) { - paramDocString = matches[0]; - if (paramDocString.indexOf(':') >= 0) { - paramDocString = paramDocString.substring(paramDocString.indexOf(':') + 1); - } - if (paramDocString.indexOf(':') >= 0) { - paramDocString = paramDocString.substring(paramDocString.indexOf(':') + 1); - } - } - }); - - return paramDocString.trim(); -} -export class PythonSignatureProvider implements SignatureHelpProvider { - public constructor(private jediFactory: JediFactory) {} - private static parseData(data: proxy.IArgumentsResult): SignatureHelp { - if (data && Array.isArray(data.definitions) && data.definitions.length > 0) { - const signature = new SignatureHelp(); - signature.activeSignature = 0; - - data.definitions.forEach((def) => { - signature.activeParameter = def.paramindex; - // Don't display the documentation, as vs code doesn't format the documentation. - // i.e. line feeds are not respected, long content is stripped. - - // Some functions do not come with parameter docs - let label: string; - let documentation: string; - const validParamInfo = - def.params && def.params.length > 0 && def.docstring && def.docstring.startsWith(`${def.name}(`); - - if (validParamInfo) { - const docLines = def.docstring.splitLines(); - label = docLines.shift()!.trim(); - documentation = docLines.join(EOL).trim(); - } else { - if (def.params && def.params.length > 0) { - label = `${def.name}(${def.params.map((p) => p.name).join(', ')})`; - documentation = def.docstring; - } else { - label = def.description; - documentation = def.docstring; - } - } - - // tslint:disable-next-line:no-object-literal-type-assertion - const sig = { - label, - documentation, - parameters: [] - }; - - if (def.params && def.params.length) { - sig.parameters = def.params.map((arg) => { - if (arg.docstring.length === 0) { - arg.docstring = extractParamDocString(arg.name, def.docstring); - } - // tslint:disable-next-line:no-object-literal-type-assertion - return { - documentation: arg.docstring.length > 0 ? arg.docstring : arg.description, - label: arg.name.trim() - }; - }); - } - signature.signatures.push(sig); - }); - return signature; - } - - return new SignatureHelp(); - } - @captureTelemetry(EventName.SIGNATURE) - public provideSignatureHelp( - document: TextDocument, - position: Position, - token: CancellationToken - ): Thenable { - // early exit if we're in a string or comment (or in an undefined position) - if (position.character <= 0 || isPositionInsideStringOrComment(document, position)) { - return Promise.resolve(new SignatureHelp()); - } - - const cmd: proxy.ICommand = { - command: proxy.CommandType.Arguments, - fileName: document.fileName, - columnIndex: position.character, - lineIndex: position.line, - source: document.getText() - }; - return this.jediFactory - .getJediProxyHandler(document.uri) - .sendCommand(cmd, token) - .then((data) => { - return data ? PythonSignatureProvider.parseData(data) : new SignatureHelp(); - }); - } -} diff --git a/src/client/providers/simpleRefactorProvider.ts b/src/client/providers/simpleRefactorProvider.ts deleted file mode 100644 index 185c22522539..000000000000 --- a/src/client/providers/simpleRefactorProvider.ts +++ /dev/null @@ -1,230 +0,0 @@ -import * as vscode from 'vscode'; -import { Commands } from '../common/constants'; -import { getTextEditsFromPatch } from '../common/editor'; -import { traceError } from '../common/logger'; -import { IPythonExecutionFactory } from '../common/process/types'; -import { IInstaller, Product } from '../common/types'; -import { StopWatch } from '../common/utils/stopWatch'; -import { IServiceContainer } from '../ioc/types'; -import { RefactorProxy } from '../refactor/proxy'; -import { sendTelemetryWhenDone } from '../telemetry'; -import { EventName } from '../telemetry/constants'; - -type RenameResponse = { - results: [{ diff: string }]; -}; - -let installer: IInstaller; - -export function activateSimplePythonRefactorProvider( - context: vscode.ExtensionContext, - outputChannel: vscode.OutputChannel, - serviceContainer: IServiceContainer -) { - installer = serviceContainer.get(IInstaller); - let disposable = vscode.commands.registerCommand(Commands.Refactor_Extract_Variable, () => { - const stopWatch = new StopWatch(); - const promise = extractVariable( - vscode.window.activeTextEditor!, - vscode.window.activeTextEditor!.selection, - outputChannel, - serviceContainer - // tslint:disable-next-line:no-empty - ).catch(() => {}); - sendTelemetryWhenDone(EventName.REFACTOR_EXTRACT_VAR, promise, stopWatch); - }); - context.subscriptions.push(disposable); - - disposable = vscode.commands.registerCommand(Commands.Refactor_Extract_Method, () => { - const stopWatch = new StopWatch(); - const promise = extractMethod( - vscode.window.activeTextEditor!, - vscode.window.activeTextEditor!.selection, - outputChannel, - serviceContainer - // tslint:disable-next-line:no-empty - ).catch(() => {}); - sendTelemetryWhenDone(EventName.REFACTOR_EXTRACT_FUNCTION, promise, stopWatch); - }); - context.subscriptions.push(disposable); -} - -// Exported for unit testing -export function extractVariable( - textEditor: vscode.TextEditor, - range: vscode.Range, - outputChannel: vscode.OutputChannel, - serviceContainer: IServiceContainer - // tslint:disable-next-line:no-any -): Promise { - let workspaceFolder = vscode.workspace.getWorkspaceFolder(textEditor.document.uri); - if ( - !workspaceFolder && - Array.isArray(vscode.workspace.workspaceFolders) && - vscode.workspace.workspaceFolders.length > 0 - ) { - workspaceFolder = vscode.workspace.workspaceFolders[0]; - } - const workspaceRoot = workspaceFolder ? workspaceFolder.uri.fsPath : __dirname; - - return validateDocumentForRefactor(textEditor).then(() => { - const newName = `newvariable${new Date().getMilliseconds().toString()}`; - const proxy = new RefactorProxy(workspaceRoot, async () => { - const factory = serviceContainer.get(IPythonExecutionFactory); - return factory.create({ resource: vscode.Uri.file(workspaceRoot) }); - }); - const rename = proxy - .extractVariable( - textEditor.document, - newName, - textEditor.document.uri.fsPath, - range, - textEditor.options - ) - .then((response) => { - return response.results[0].diff; - }); - - return extractName(textEditor, newName, rename, outputChannel); - }); -} - -// Exported for unit testing -export function extractMethod( - textEditor: vscode.TextEditor, - range: vscode.Range, - outputChannel: vscode.OutputChannel, - serviceContainer: IServiceContainer - // tslint:disable-next-line:no-any -): Promise { - let workspaceFolder = vscode.workspace.getWorkspaceFolder(textEditor.document.uri); - if ( - !workspaceFolder && - Array.isArray(vscode.workspace.workspaceFolders) && - vscode.workspace.workspaceFolders.length > 0 - ) { - workspaceFolder = vscode.workspace.workspaceFolders[0]; - } - const workspaceRoot = workspaceFolder ? workspaceFolder.uri.fsPath : __dirname; - - return validateDocumentForRefactor(textEditor).then(() => { - const newName = `newmethod${new Date().getMilliseconds().toString()}`; - const proxy = new RefactorProxy(workspaceRoot, async () => { - const factory = serviceContainer.get(IPythonExecutionFactory); - return factory.create({ resource: vscode.Uri.file(workspaceRoot) }); - }); - const rename = proxy - .extractMethod( - textEditor.document, - newName, - textEditor.document.uri.fsPath, - range, - textEditor.options - ) - .then((response) => { - return response.results[0].diff; - }); - - return extractName(textEditor, newName, rename, outputChannel); - }); -} - -// tslint:disable-next-line:no-any -function validateDocumentForRefactor(textEditor: vscode.TextEditor): Promise { - if (!textEditor.document.isDirty) { - return Promise.resolve(); - } - - // tslint:disable-next-line:no-any - return new Promise((resolve, reject) => { - vscode.window.showInformationMessage('Please save changes before refactoring', 'Save').then((item) => { - if (item === 'Save') { - textEditor.document.save().then(resolve, reject); - } else { - return reject(); - } - }); - }); -} - -function extractName( - textEditor: vscode.TextEditor, - newName: string, - renameResponse: Promise, - outputChannel: vscode.OutputChannel - // tslint:disable-next-line:no-any -): Promise { - let changeStartsAtLine = -1; - return renameResponse - .then((diff) => { - if (diff.length === 0) { - return []; - } - return getTextEditsFromPatch(textEditor.document.getText(), diff); - }) - .then((edits) => { - return textEditor.edit((editBuilder) => { - edits.forEach((edit) => { - if (changeStartsAtLine === -1 || changeStartsAtLine > edit.range.start.line) { - changeStartsAtLine = edit.range.start.line; - } - editBuilder.replace(edit.range, edit.newText); - }); - }); - }) - .then((done) => { - if (done && changeStartsAtLine >= 0) { - let newWordPosition: vscode.Position | undefined; - for (let lineNumber = changeStartsAtLine; lineNumber < textEditor.document.lineCount; lineNumber += 1) { - const line = textEditor.document.lineAt(lineNumber); - const indexOfWord = line.text.indexOf(newName); - if (indexOfWord >= 0) { - newWordPosition = new vscode.Position(line.range.start.line, indexOfWord); - break; - } - } - - if (newWordPosition) { - textEditor.selections = [ - new vscode.Selection( - newWordPosition, - new vscode.Position(newWordPosition.line, newWordPosition.character + newName.length) - ) - ]; - textEditor.revealRange( - new vscode.Range(textEditor.selection.start, textEditor.selection.end), - vscode.TextEditorRevealType.Default - ); - } - return newWordPosition; - } - return null; - }) - .then((newWordPosition) => { - if (newWordPosition) { - return textEditor.document.save().then(() => { - // Now that we have selected the new variable, lets invoke the rename command - return vscode.commands.executeCommand('editor.action.rename'); - }); - } - }) - .catch((error) => { - if (error === 'Not installed') { - installer - .promptToInstall(Product.rope, textEditor.document.uri) - .catch((ex) => traceError('Python Extension: simpleRefactorProvider.promptToInstall', ex)); - return Promise.reject(''); - } - let errorMessage = `${error}`; - if (typeof error === 'string') { - errorMessage = error; - } - if (typeof error === 'object' && error.message) { - errorMessage = error.message; - } - outputChannel.appendLine(`${'#'.repeat(10)}Refactor Output${'#'.repeat(10)}`); - outputChannel.appendLine(`Error in refactoring:\n${errorMessage}`); - vscode.window.showErrorMessage(`Cannot perform refactoring using selected element(s). (${errorMessage})`); - return Promise.reject(error); - }); -} diff --git a/src/client/providers/symbolProvider.ts b/src/client/providers/symbolProvider.ts deleted file mode 100644 index 07327b97f3a4..000000000000 --- a/src/client/providers/symbolProvider.ts +++ /dev/null @@ -1,202 +0,0 @@ -'use strict'; - -import { - CancellationToken, - DocumentSymbol, - DocumentSymbolProvider, - Location, - Range, - SymbolInformation, - SymbolKind, - TextDocument, - Uri -} from 'vscode'; -import { LanguageClient } from 'vscode-languageclient/node'; -import { IFileSystem } from '../common/platform/types'; -import { createDeferred, Deferred } from '../common/utils/async'; -import { IServiceContainer } from '../ioc/types'; -import { JediFactory } from '../languageServices/jediProxyFactory'; -import { captureTelemetry } from '../telemetry'; -import { EventName } from '../telemetry/constants'; -import * as proxy from './jediProxy'; - -function flattenSymbolTree(tree: DocumentSymbol, uri: Uri, containerName: string = ''): SymbolInformation[] { - const flattened: SymbolInformation[] = []; - - const range = new Range( - tree.range.start.line, - tree.range.start.character, - tree.range.end.line, - tree.range.end.character - ); - // For whatever reason, the values of VS Code's SymbolKind enum - // are off-by-one relative to the LSP: - // https://microsoft.github.io/language-server-protocol/specification#document-symbols-request-leftwards_arrow_with_hook - const kind: SymbolKind = tree.kind - 1; - const info = new SymbolInformation( - tree.name, - // Type coercion is a bit fuzzy when it comes to enums, so we - // play it safe by explicitly converting. - // tslint:disable-next-line:no-any - (SymbolKind as any)[(SymbolKind as any)[kind]], - containerName, - new Location(uri, range) - ); - flattened.push(info); - - if (tree.children && tree.children.length > 0) { - // FYI: Jedi doesn't fully-qualify the container name so we - // don't bother here either. - //const fullName = `${containerName}.${tree.name}`; - for (const child of tree.children) { - const flattenedChild = flattenSymbolTree(child, uri, tree.name); - flattened.push(...flattenedChild); - } - } - - return flattened; -} - -/** - * Provides Python symbols to VS Code (from the language server). - * - * See: - * https://code.visualstudio.com/docs/extensionAPI/vscode-api#DocumentSymbolProvider - */ -export class LanguageServerSymbolProvider implements DocumentSymbolProvider { - constructor(private readonly languageClient: LanguageClient) {} - - public async provideDocumentSymbols( - document: TextDocument, - token: CancellationToken - ): Promise { - const uri = document.uri; - const args = { textDocument: { uri: uri.toString() } }; - const raw = await this.languageClient.sendRequest('textDocument/documentSymbol', args, token); - const symbols: SymbolInformation[] = []; - for (const tree of raw) { - const flattened = flattenSymbolTree(tree, uri); - symbols.push(...flattened); - } - return Promise.resolve(symbols); - } -} - -/** - * Provides Python symbols to VS Code (from Jedi). - * - * See: - * https://code.visualstudio.com/docs/extensionAPI/vscode-api#DocumentSymbolProvider - */ -export class JediSymbolProvider implements DocumentSymbolProvider { - private debounceRequest: Map }>; - private readonly fs: IFileSystem; - - public constructor( - serviceContainer: IServiceContainer, - private jediFactory: JediFactory, - private readonly debounceTimeoutMs = 500 - ) { - this.debounceRequest = new Map< - string, - { timer: NodeJS.Timer | number; deferred: Deferred } - >(); - this.fs = serviceContainer.get(IFileSystem); - } - - @captureTelemetry(EventName.SYMBOL) - public provideDocumentSymbols(document: TextDocument, token: CancellationToken): Thenable { - return this.provideDocumentSymbolsThrottled(document, token); - } - - private provideDocumentSymbolsThrottled( - document: TextDocument, - token: CancellationToken - ): Thenable { - const key = `${document.uri.fsPath}`; - if (this.debounceRequest.has(key)) { - const item = this.debounceRequest.get(key)!; - // tslint:disable-next-line: no-any - clearTimeout(item.timer as any); - item.deferred.resolve([]); - } - - const deferred = createDeferred(); - const timer: NodeJS.Timer | number = setTimeout(() => { - if (token.isCancellationRequested) { - return deferred.resolve([]); - } - - const filename = document.fileName; - const cmd: proxy.ICommand = { - command: proxy.CommandType.Symbols, - fileName: filename, - columnIndex: 0, - lineIndex: 0 - }; - - if (document.isDirty) { - cmd.source = document.getText(); - } - - this.jediFactory - .getJediProxyHandler(document.uri) - .sendCommand(cmd, token) - .then((data) => this.parseData(document, data)) - .then((items) => deferred.resolve(items)) - .catch((ex) => deferred.reject(ex)); - }, this.debounceTimeoutMs); - - token.onCancellationRequested(() => { - clearTimeout(timer); - deferred.resolve([]); - this.debounceRequest.delete(key); - }); - - // When a document is not saved on FS, we cannot uniquely identify it, so lets not debounce, but delay the symbol provider. - if (!document.isUntitled) { - this.debounceRequest.set(key, { timer, deferred }); - } - - return deferred.promise; - } - - // This does not appear to be used anywhere currently... - // tslint:disable-next-line:no-unused-variable - // private provideDocumentSymbolsUnthrottled(document: TextDocument, token: CancellationToken): Thenable { - // const filename = document.fileName; - - // const cmd: proxy.ICommand = { - // command: proxy.CommandType.Symbols, - // fileName: filename, - // columnIndex: 0, - // lineIndex: 0 - // }; - - // if (document.isDirty) { - // cmd.source = document.getText(); - // } - - // return this.jediFactory.getJediProxyHandler(document.uri).sendCommandNonCancellableCommand(cmd, token) - // .then(data => this.parseData(document, data)); - // } - - private parseData(document: TextDocument, data?: proxy.ISymbolResult): SymbolInformation[] { - if (data) { - const symbols = data.definitions.filter((sym) => this.fs.arePathsSame(sym.fileName, document.fileName)); - return symbols.map((sym) => { - const symbol = sym.kind; - const range = new Range( - sym.range.startLine, - sym.range.startColumn, - sym.range.endLine, - sym.range.endColumn - ); - const uri = Uri.file(sym.fileName); - const location = new Location(uri, range); - return new SymbolInformation(sym.text, symbol, sym.container, location); - }); - } - return []; - } -} diff --git a/src/client/providers/terminalProvider.ts b/src/client/providers/terminalProvider.ts index 15f1241d88ce..f68f151110ec 100644 --- a/src/client/providers/terminalProvider.ts +++ b/src/client/providers/terminalProvider.ts @@ -4,27 +4,37 @@ import { Disposable, Terminal } from 'vscode'; import { IActiveResourceService, ICommandManager } from '../common/application/types'; import { Commands } from '../common/constants'; +import { inTerminalEnvVarExperiment } from '../common/experiments/helpers'; import { ITerminalActivator, ITerminalServiceFactory } from '../common/terminal/types'; -import { IConfigurationService } from '../common/types'; +import { IConfigurationService, IExperimentService } from '../common/types'; import { swallowExceptions } from '../common/utils/decorators'; import { IServiceContainer } from '../ioc/types'; import { captureTelemetry, sendTelemetryEvent } from '../telemetry'; import { EventName } from '../telemetry/constants'; +import { useEnvExtension, shouldEnvExtHandleActivation } from '../envExt/api.internal'; export class TerminalProvider implements Disposable { private disposables: Disposable[] = []; + private activeResourceService: IActiveResourceService; + constructor(private serviceContainer: IServiceContainer) { this.registerCommands(); this.activeResourceService = this.serviceContainer.get(IActiveResourceService); } @swallowExceptions('Failed to initialize terminal provider') - public async initialize(currentTerminal: Terminal | undefined) { + public async initialize(currentTerminal: Terminal | undefined): Promise { const configuration = this.serviceContainer.get(IConfigurationService); + const experimentService = this.serviceContainer.get(IExperimentService); const pythonSettings = configuration.getSettings(this.activeResourceService.getActiveResource()); - if (currentTerminal && pythonSettings.terminal.activateEnvInCurrentTerminal) { + if ( + currentTerminal && + pythonSettings.terminal.activateEnvInCurrentTerminal && + !inTerminalEnvVarExperiment(experimentService) && + !shouldEnvExtHandleActivation() + ) { const hideFromUser = 'hideFromUser' in currentTerminal.creationOptions && currentTerminal.creationOptions.hideFromUser; if (!hideFromUser) { @@ -32,23 +42,31 @@ export class TerminalProvider implements Disposable { await terminalActivator.activateEnvironmentInTerminal(currentTerminal, { preserveFocus: true }); } sendTelemetryEvent(EventName.ACTIVATE_ENV_IN_CURRENT_TERMINAL, undefined, { - isTerminalVisible: !hideFromUser + isTerminalVisible: !hideFromUser, }); } } - public dispose() { + + public dispose(): void { this.disposables.forEach((disposable) => disposable.dispose()); } + private registerCommands() { const commandManager = this.serviceContainer.get(ICommandManager); const disposable = commandManager.registerCommand(Commands.Create_Terminal, this.onCreateTerminal, this); this.disposables.push(disposable); } + @captureTelemetry(EventName.TERMINAL_CREATE, { triggeredBy: 'commandpalette' }) private async onCreateTerminal() { - const terminalService = this.serviceContainer.get(ITerminalServiceFactory); const activeResource = this.activeResourceService.getActiveResource(); + if (useEnvExtension()) { + const commandManager = this.serviceContainer.get(ICommandManager); + await commandManager.executeCommand('python-envs.createTerminal', activeResource); + } + + const terminalService = this.serviceContainer.get(ITerminalServiceFactory); await terminalService.createTerminalService(activeResource, 'Python').show(false); } } diff --git a/src/client/providers/types.ts b/src/client/providers/types.ts deleted file mode 100644 index f2d1bc6eea3a..000000000000 --- a/src/client/providers/types.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { CancellationToken, Uri, WorkspaceEdit } from 'vscode'; - -export const ISortImportsEditingProvider = Symbol('ISortImportsEditingProvider'); -export interface ISortImportsEditingProvider { - provideDocumentSortImportsEdits(uri: Uri, token?: CancellationToken): Promise; - sortImports(uri?: Uri): Promise; - registerCommands(): void; -} diff --git a/src/client/pylanceApi.ts b/src/client/pylanceApi.ts new file mode 100644 index 000000000000..b839d0d9c2b7 --- /dev/null +++ b/src/client/pylanceApi.ts @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { TelemetryEventMeasurements, TelemetryEventProperties } from '@vscode/extension-telemetry'; +import { BaseLanguageClient } from 'vscode-languageclient'; + +export interface TelemetryReporter { + sendTelemetryEvent( + eventName: string, + properties?: TelemetryEventProperties, + measurements?: TelemetryEventMeasurements, + ): void; + sendTelemetryErrorEvent( + eventName: string, + properties?: TelemetryEventProperties, + measurements?: TelemetryEventMeasurements, + ): void; +} + +export interface ApiForPylance { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + createClient(...args: any[]): BaseLanguageClient; + start(client: BaseLanguageClient): Promise; + stop(client: BaseLanguageClient): Promise; + getTelemetryReporter(): TelemetryReporter; +} diff --git a/src/client/pythonEnvironments/api.ts b/src/client/pythonEnvironments/api.ts new file mode 100644 index 000000000000..a2065c30b740 --- /dev/null +++ b/src/client/pythonEnvironments/api.ts @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { Event } from 'vscode'; +import { + GetRefreshEnvironmentsOptions, + IDiscoveryAPI, + ProgressNotificationEvent, + ProgressReportStage, + PythonLocatorQuery, + TriggerRefreshOptions, +} from './base/locator'; + +export type GetLocatorFunc = () => Promise; + +/** + * The public API for the Python environments component. + * + * Note that this is composed of sub-components. + */ +class PythonEnvironments implements IDiscoveryAPI { + private locator!: IDiscoveryAPI; + + constructor( + // These are factories for the sub-components the full component is composed of: + private readonly getLocator: GetLocatorFunc, + ) {} + + public async activate(): Promise { + this.locator = await this.getLocator(); + } + + public get onProgress(): Event { + return this.locator.onProgress; + } + + public get refreshState(): ProgressReportStage { + return this.locator.refreshState; + } + + public getRefreshPromise(options?: GetRefreshEnvironmentsOptions) { + return this.locator.getRefreshPromise(options); + } + + public get onChanged() { + return this.locator.onChanged; + } + + public getEnvs(query?: PythonLocatorQuery) { + return this.locator.getEnvs(query); + } + + public async resolveEnv(env: string) { + return this.locator.resolveEnv(env); + } + + public async triggerRefresh(query?: PythonLocatorQuery, options?: TriggerRefreshOptions) { + return this.locator.triggerRefresh(query, options); + } +} + +export async function createPythonEnvironments(getLocator: GetLocatorFunc): Promise { + const api = new PythonEnvironments(getLocator); + await api.activate(); + return api; +} diff --git a/src/client/pythonEnvironments/base/envsCache.ts b/src/client/pythonEnvironments/base/envsCache.ts deleted file mode 100644 index f0de585b3eff..000000000000 --- a/src/client/pythonEnvironments/base/envsCache.ts +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { cloneDeep } from 'lodash'; -import { getGlobalPersistentStore, IPersistentStore } from '../common/externalDependencies'; -import { PythonEnvInfo } from './info'; -import { areSameEnv } from './info/env'; - -/** - * Represents the environment info cache to be used by the cache locator. - */ -export interface IEnvsCache { - /** - * Initialization logic to be done outside of the constructor, for example reading from persistent storage. - */ - initialize(): void; - - /** - * Return all environment info currently in memory for this session. - * - * @return An array of cached environment info, or `undefined` if there are none. - */ - getAllEnvs(): PythonEnvInfo[] | undefined; - - /** - * Replace all environment info currently in memory for this session. - * - * @param envs The array of environment info to store in the in-memory cache. - */ - setAllEnvs(envs: PythonEnvInfo[]): void; - - /** - * If the cache has been initialized, return environmnent info objects that match a query object. - * If none of the environments in the cache match the query data, return an empty array. - * If the in-memory cache has not been initialized prior to calling `filterEnvs`, return `undefined`. - * - * @param env The environment info data that will be used to look for - * environment info objects in the cache, or a unique environment key. - * If passing an environment info object, it may contain incomplete environment info. - * @return The environment info objects matching the `env` param, - * or `undefined` if the in-memory cache is not initialized. - */ - filterEnvs(env: PythonEnvInfo | string): PythonEnvInfo[] | undefined; - - /** - * Writes the content of the in-memory cache to persistent storage. - */ - flush(): Promise; -} - -type CompleteEnvInfoFunction = (envInfo: PythonEnvInfo) => boolean; - -/** - * Environment info cache using persistent storage to save and retrieve pre-cached env info. - */ -export class PythonEnvInfoCache implements IEnvsCache { - private initialized = false; - - private envsList: PythonEnvInfo[] | undefined; - - private persistentStorage: IPersistentStore | undefined; - - constructor(private readonly isComplete: CompleteEnvInfoFunction) {} - - public initialize(): void { - if (this.initialized) { - return; - } - - this.initialized = true; - this.persistentStorage = getGlobalPersistentStore('PYTHON_ENV_INFO_CACHE'); - this.envsList = this.persistentStorage?.get(); - } - - public getAllEnvs(): PythonEnvInfo[] | undefined { - return cloneDeep(this.envsList); - } - - public setAllEnvs(envs: PythonEnvInfo[]): void { - this.envsList = cloneDeep(envs); - } - - public filterEnvs(env: PythonEnvInfo | string): PythonEnvInfo[] | undefined { - const result = this.envsList?.filter((info) => areSameEnv(info, env)); - - if (result) { - return cloneDeep(result); - } - - return undefined; - } - - public async flush(): Promise { - const completeEnvs = this.envsList?.filter(this.isComplete); - - if (completeEnvs?.length) { - await this.persistentStorage?.set(completeEnvs); - } - } -} diff --git a/src/client/pythonEnvironments/base/info/env.ts b/src/client/pythonEnvironments/base/info/env.ts index bb72186ca7a5..5c5b9317e169 100644 --- a/src/client/pythonEnvironments/base/info/env.ts +++ b/src/client/pythonEnvironments/base/info/env.ts @@ -1,20 +1,27 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { cloneDeep } from 'lodash'; +import { cloneDeep, isEqual } from 'lodash'; import * as path from 'path'; +import { Uri } from 'vscode'; +import { getArchitectureDisplayName } from '../../../common/platform/registry'; import { Architecture } from '../../../common/utils/platform'; -import { arePathsSame } from '../../common/externalDependencies'; -import { areEqualVersions, areEquivalentVersions } from './pythonVersion'; +import { arePathsSame, isParentPath, normCasePath } from '../../common/externalDependencies'; +import { getKindDisplayName } from './envKind'; +import { areIdenticalVersion, areSimilarVersions, getVersionDisplayString, isVersionEmpty } from './pythonVersion'; import { - FileInfo, - PythonDistroInfo, + EnvPathType, + globallyInstalledEnvKinds, PythonEnvInfo, PythonEnvKind, + PythonEnvSource, + PythonEnvType, PythonReleaseLevel, PythonVersion, + virtualEnvKinds, } from '.'; +import { BasicEnvInfo } from '../locator'; /** * Create a new info object with all values empty. @@ -24,19 +31,36 @@ import { export function buildEnvInfo(init?: { kind?: PythonEnvKind; executable?: string; + name?: string; location?: string; version?: PythonVersion; + org?: string; + arch?: Architecture; + fileInfo?: { ctime: number; mtime: number }; + source?: PythonEnvSource[]; + display?: string; + sysPrefix?: string; + searchLocation?: Uri; + type?: PythonEnvType; + /** + * Command used to run Python in this environment. + * E.g. `conda run -n envName python` or `python.exe` + */ + pythonRunCommand?: string[]; + identifiedUsingNativeLocator?: boolean; }): PythonEnvInfo { - const env = { + const env: PythonEnvInfo = { + name: init?.name ?? '', + location: '', kind: PythonEnvKind.Unknown, executable: { filename: '', - sysPrefix: '', - ctime: -1, - mtime: -1, + sysPrefix: init?.sysPrefix ?? '', + ctime: init?.fileInfo?.ctime ?? -1, + mtime: init?.fileInfo?.mtime ?? -1, }, - name: '', - location: '', + searchLocation: undefined, + display: init?.display, version: { major: -1, minor: -1, @@ -46,17 +70,40 @@ export function buildEnvInfo(init?: { serial: 0, }, }, - arch: Architecture.Unknown, + arch: init?.arch ?? Architecture.Unknown, distro: { - org: '', + org: init?.org ?? '', }, + source: init?.source ?? [], + pythonRunCommand: init?.pythonRunCommand, + identifiedUsingNativeLocator: init?.identifiedUsingNativeLocator, }; if (init !== undefined) { updateEnv(env, init); } + env.id = getEnvID(env.executable.filename, env.location); return env; } +export function areEnvsDeepEqual(env1: PythonEnvInfo, env2: PythonEnvInfo): boolean { + const env1Clone = cloneDeep(env1); + const env2Clone = cloneDeep(env2); + // Cannot compare searchLocation as they are Uri objects. + delete env1Clone.searchLocation; + delete env2Clone.searchLocation; + env1Clone.source = env1Clone.source.sort(); + env2Clone.source = env2Clone.source.sort(); + const searchLocation1 = env1.searchLocation?.fsPath ?? ''; + const searchLocation2 = env2.searchLocation?.fsPath ?? ''; + const searchLocation1Scheme = env1.searchLocation?.scheme ?? ''; + const searchLocation2Scheme = env2.searchLocation?.scheme ?? ''; + return ( + isEqual(env1Clone, env2Clone) && + arePathsSame(searchLocation1, searchLocation2) && + searchLocation1Scheme === searchLocation2Scheme + ); +} + /** * Return a deep copy of the given env info. * @@ -65,7 +112,7 @@ export function buildEnvInfo(init?: { export function copyEnvInfo( env: PythonEnvInfo, updates?: { - kind?: PythonEnvKind, + kind?: PythonEnvKind; }, ): PythonEnvInfo { // We don't care whether or not extra/hidden properties @@ -77,12 +124,17 @@ export function copyEnvInfo( return copied; } -function updateEnv(env: PythonEnvInfo, updates: { - kind?: PythonEnvKind; - executable?: string; - location?: string; - version?: PythonVersion; -}): void { +function updateEnv( + env: PythonEnvInfo, + updates: { + kind?: PythonEnvKind; + executable?: string; + location?: string; + version?: PythonVersion; + searchLocation?: Uri; + type?: PythonEnvType; + }, +): void { if (updates.kind !== undefined) { env.kind = updates.kind; } @@ -95,6 +147,62 @@ function updateEnv(env: PythonEnvInfo, updates: { if (updates.version !== undefined) { env.version = updates.version; } + if (updates.searchLocation !== undefined) { + env.searchLocation = updates.searchLocation; + } + if (updates.type !== undefined) { + env.type = updates.type; + } +} + +/** + * Convert the env info to a user-facing representation. + * + * The format is `Python (: )` + * E.g. `Python 3.5.1 32-bit (myenv2: virtualenv)` + */ +export function setEnvDisplayString(env: PythonEnvInfo): void { + env.display = buildEnvDisplayString(env); + env.detailedDisplayName = buildEnvDisplayString(env, true); +} + +function buildEnvDisplayString(env: PythonEnvInfo, getAllDetails = false): string { + // main parts + const shouldDisplayKind = getAllDetails || globallyInstalledEnvKinds.includes(env.kind); + const shouldDisplayArch = !virtualEnvKinds.includes(env.kind); + const displayNameParts: string[] = ['Python']; + if (env.version && !isVersionEmpty(env.version)) { + displayNameParts.push(getVersionDisplayString(env.version)); + } + if (shouldDisplayArch) { + const archName = getArchitectureDisplayName(env.arch); + if (archName !== '') { + displayNameParts.push(archName); + } + } + + // Note that currently we do not use env.distro in the display name. + + // "suffix" + const envSuffixParts: string[] = []; + if (env.name && env.name !== '') { + envSuffixParts.push(`'${env.name}'`); + } else if (env.location && env.location !== '') { + if (env.kind === PythonEnvKind.Conda) { + const condaEnvName = path.basename(env.location); + envSuffixParts.push(`'${condaEnvName}'`); + } + } + if (shouldDisplayKind) { + const kindName = getKindDisplayName(env.kind); + if (kindName !== '') { + envSuffixParts.push(kindName); + } + } + const envSuffix = envSuffixParts.length === 0 ? '' : `(${envSuffixParts.join(': ')})`; + + // Pull it all together. + return `${displayNameParts.join(' ')} ${envSuffix}`.trim(); } /** @@ -103,24 +211,57 @@ function updateEnv(env: PythonEnvInfo, updates: { * If insufficient data is provided to generate a minimal object, such * that it is not identifiable, then `undefined` is returned. */ -export function getMinimalPartialInfo(env: string | Partial): Partial | undefined { +function getMinimalPartialInfo(env: string | PythonEnvInfo | BasicEnvInfo): Partial | undefined { if (typeof env === 'string') { if (env === '') { return undefined; } return { - executable: { filename: env, sysPrefix: '', ctime: -1, mtime: -1 }, + id: '', + executable: { + filename: env, + sysPrefix: '', + ctime: -1, + mtime: -1, + }, }; } - if (env.executable === undefined) { - return undefined; - } - if (env.executable.filename === '') { - return undefined; + if ('executablePath' in env) { + return { + id: '', + executable: { + filename: env.executablePath, + sysPrefix: '', + ctime: -1, + mtime: -1, + }, + location: env.envPath, + kind: env.kind, + source: env.source, + }; } return env; } +/** + * Returns path to environment folder or path to interpreter that uniquely identifies an environment. + */ +export function getEnvPath(interpreterPath: string, envFolderPath?: string): EnvPathType { + let envPath: EnvPathType = { path: interpreterPath, pathType: 'interpreterPath' }; + if (envFolderPath && !isParentPath(interpreterPath, envFolderPath)) { + // Executable is not inside the environment folder, env folder is the ID. + envPath = { path: envFolderPath, pathType: 'envFolderPath' }; + } + return envPath; +} + +/** + * Gets general unique identifier for most environments. + */ +export function getEnvID(interpreterPath: string, envFolderPath?: string): string { + return normCasePath(getEnvPath(interpreterPath, envFolderPath).path); +} + /** * Checks if two environments are same. * @param {string | PythonEnvInfo} left: environment to compare. @@ -130,36 +271,52 @@ export function getMinimalPartialInfo(env: string | Partial): Par * Remarks: The current comparison assumes that if the path to the executables are the same * then it is the same environment. Additionally, if the paths are not same but executables * are in the same directory and the version of python is the same than we can assume it - * to be same environment. This later case is needed for comparing windows store python, + * to be same environment. This later case is needed for comparing microsoft store python, * where multiple versions of python executables are all put in the same directory. */ export function areSameEnv( - left: string | Partial, - right: string | Partial, - allowPartialMatch?: boolean, + left: string | PythonEnvInfo | BasicEnvInfo, + right: string | PythonEnvInfo | BasicEnvInfo, + allowPartialMatch = true, ): boolean | undefined { const leftInfo = getMinimalPartialInfo(left); const rightInfo = getMinimalPartialInfo(right); if (leftInfo === undefined || rightInfo === undefined) { return undefined; } + if ( + (leftInfo.executable?.filename && !rightInfo.executable?.filename) || + (!leftInfo.executable?.filename && rightInfo.executable?.filename) + ) { + return false; + } + if (leftInfo.id && leftInfo.id === rightInfo.id) { + // In case IDs are available, use it. + return true; + } + const leftFilename = leftInfo.executable!.filename; const rightFilename = rightInfo.executable!.filename; - // For now we assume that matching executable means they are the same. - if (arePathsSame(leftFilename, rightFilename)) { + if (getEnvID(leftFilename, leftInfo.location) === getEnvID(rightFilename, rightInfo.location)) { + // Otherwise use ID function to get the ID. Note ID returned by function may itself change if executable of + // an environment changes, for eg. when conda installs python into the env. So only use it as a fallback if + // ID is not available. return true; } - if (arePathsSame(path.dirname(leftFilename), path.dirname(rightFilename))) { - const leftVersion = typeof left === 'string' ? undefined : left.version; - const rightVersion = typeof right === 'string' ? undefined : right.version; - if (leftVersion && rightVersion) { - if ( - areEqualVersions(leftVersion, rightVersion) - || (allowPartialMatch && areEquivalentVersions(leftVersion, rightVersion)) - ) { - return true; + if (allowPartialMatch) { + const isSameDirectory = + leftFilename !== 'python' && + rightFilename !== 'python' && + arePathsSame(path.dirname(leftFilename), path.dirname(rightFilename)); + if (isSameDirectory) { + const leftVersion = typeof left === 'string' ? undefined : leftInfo.version; + const rightVersion = typeof right === 'string' ? undefined : rightInfo.version; + if (leftVersion && rightVersion) { + if (areIdenticalVersion(leftVersion, rightVersion) || areSimilarVersions(leftVersion, rightVersion)) { + return true; + } } } } @@ -173,7 +330,7 @@ export function areSameEnv( * weighted by most important to least important fields. * Wn > Wn-1 + Wn-2 + ... W0 */ -function getPythonVersionInfoHeuristic(version:PythonVersion): number { +function getPythonVersionSpecificity(version: PythonVersion): number { let infoLevel = 0; if (version.major > 0) { infoLevel += 20; // W4 @@ -199,152 +356,10 @@ function getPythonVersionInfoHeuristic(version:PythonVersion): number { } /** - * Returns a heuristic value on how much information is available in the given executable object. - * @param {FileInfo} executable executable object to generate heuristic from. - * @returns A heuristic value indicating the amount of info available in the object - * weighted by most important to least important fields. - * Wn > Wn-1 + Wn-2 + ... W0 - */ -function getFileInfoHeuristic(file:FileInfo): number { - let infoLevel = 0; - if (file.filename.length > 0) { - infoLevel += 5; // W2 - } - - if (file.mtime) { - infoLevel += 2; // W1 - } - - if (file.ctime) { - infoLevel += 1; // W0 - } - - return infoLevel; -} - -/** - * Returns a heuristic value on how much information is available in the given distro object. - * @param {PythonDistroInfo} distro distro object to generate heuristic from. - * @returns A heuristic value indicating the amount of info available in the object - * weighted by most important to least important fields. - * Wn > Wn-1 + Wn-2 + ... W0 + * Compares two python versions, based on the amount of data each object has. If versionA has + * less information then the returned value is negative. If it is same then 0. If versionA has + * more information then positive. */ -function getDistroInfoHeuristic(distro:PythonDistroInfo):number { - let infoLevel = 0; - if (distro.org.length > 0) { - infoLevel += 20; // W3 - } - - if (distro.defaultDisplayName) { - infoLevel += 10; // W2 - } - - if (distro.binDir) { - infoLevel += 5; // W1 - } - - if (distro.version) { - infoLevel += 2; - } - - return infoLevel; -} - -/** - * Gets a prioritized list of environment types for identification. - * @returns {PythonEnvKind[]} : List of environments ordered by identification priority - * - * Remarks: This is the order of detection based on how the various distributions and tools - * configure the environment, and the fall back for identification. - * Top level we have the following environment types, since they leave a unique signature - * in the environment or * use a unique path for the environments they create. - * 1. Conda - * 2. Windows Store - * 3. PipEnv - * 4. Pyenv - * 5. Poetry - * - * Next level we have the following virtual environment tools. The are here because they - * are consumed by the tools above, and can also be used independently. - * 1. venv - * 2. virtualenvwrapper - * 3. virtualenv - * - * Last category is globally installed python, or system python. - */ -export function getPrioritizedEnvironmentKind(): PythonEnvKind[] { - return [ - PythonEnvKind.CondaBase, - PythonEnvKind.Conda, - PythonEnvKind.WindowsStore, - PythonEnvKind.Pipenv, - PythonEnvKind.Pyenv, - PythonEnvKind.Poetry, - PythonEnvKind.Venv, - PythonEnvKind.VirtualEnvWrapper, - PythonEnvKind.VirtualEnv, - PythonEnvKind.OtherVirtual, - PythonEnvKind.OtherGlobal, - PythonEnvKind.MacDefault, - PythonEnvKind.System, - PythonEnvKind.Custom, - PythonEnvKind.Unknown, - ]; -} - -/** - * Selects an environment based on the environment selection priority. This should - * match the priority in the environment identifier. - */ -export function sortEnvInfoByPriority(...envs: PythonEnvInfo[]): PythonEnvInfo[] { - // tslint:disable-next-line: no-suspicious-comment - // TODO: When we consolidate the PythonEnvKind and EnvironmentType we should have - // one location where we define priority and - const envKindByPriority:PythonEnvKind[] = getPrioritizedEnvironmentKind(); - return envs.sort( - (a:PythonEnvInfo, b:PythonEnvInfo) => envKindByPriority.indexOf(a.kind) - envKindByPriority.indexOf(b.kind), - ); -} - -/** - * Merges properties of the `target` environment and `other` environment and returns the merged environment. - * if the value in the `target` environment is not defined or has less information. This does not mutate - * the `target` instead it returns a new object that contains the merged results. - * @param {PythonEnvInfo} target : Properties of this object are favored. - * @param {PythonEnvInfo} other : Properties of this object are used to fill the gaps in the merged result. - */ -export function mergeEnvironments(target: PythonEnvInfo, other: PythonEnvInfo): PythonEnvInfo { - const merged = cloneDeep(target); - - const version = cloneDeep( - getPythonVersionInfoHeuristic(target.version) > getPythonVersionInfoHeuristic(other.version) - ? target.version : other.version, - ); - - const executable = cloneDeep( - getFileInfoHeuristic(target.executable) > getFileInfoHeuristic(other.executable) - ? target.executable : other.executable, - ); - executable.sysPrefix = target.executable.sysPrefix ?? other.executable.sysPrefix; - - const distro = cloneDeep( - getDistroInfoHeuristic(target.distro) > getDistroInfoHeuristic(other.distro) - ? target.distro : other.distro, - ); - - merged.arch = merged.arch === Architecture.Unknown ? other.arch : target.arch; - merged.defaultDisplayName = merged.defaultDisplayName ?? other.defaultDisplayName; - merged.distro = distro; - merged.executable = executable; - - // No need to check this just use preferred kind. Since the first thing we do is figure out the - // preferred env based on kind. - merged.kind = target.kind; - - merged.location = merged.location ?? other.location; - merged.name = merged.name ?? other.name; - merged.searchLocation = merged.searchLocation ?? other.searchLocation; - merged.version = version; - - return merged; +export function comparePythonVersionSpecificity(versionA: PythonVersion, versionB: PythonVersion): number { + return Math.sign(getPythonVersionSpecificity(versionA) - getPythonVersionSpecificity(versionB)); } diff --git a/src/client/pythonEnvironments/base/info/envKind.ts b/src/client/pythonEnvironments/base/info/envKind.ts new file mode 100644 index 000000000000..08f4ce55d464 --- /dev/null +++ b/src/client/pythonEnvironments/base/info/envKind.ts @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { PythonEnvKind } from '.'; + +/** + * Get the given kind's user-facing representation. + * + * If it doesn't have one then the empty string is returned. + */ +export function getKindDisplayName(kind: PythonEnvKind): string { + for (const [candidate, value] of [ + // Note that Unknown is excluded here. + [PythonEnvKind.System, 'system'], + [PythonEnvKind.MicrosoftStore, 'Microsoft Store'], + [PythonEnvKind.Pyenv, 'pyenv'], + [PythonEnvKind.Poetry, 'Poetry'], + [PythonEnvKind.Hatch, 'Hatch'], + [PythonEnvKind.Pixi, 'Pixi'], + [PythonEnvKind.Custom, 'custom'], + // For now we treat OtherGlobal like Unknown. + [PythonEnvKind.Venv, 'venv'], + [PythonEnvKind.VirtualEnv, 'virtualenv'], + [PythonEnvKind.VirtualEnvWrapper, 'virtualenv'], + [PythonEnvKind.Pipenv, 'Pipenv'], + [PythonEnvKind.Conda, 'conda'], + [PythonEnvKind.ActiveState, 'ActiveState'], + // For now we treat OtherVirtual like Unknown. + ] as [PythonEnvKind, string][]) { + if (kind === candidate) { + return value; + } + } + return ''; +} + +/** + * Gets a prioritized list of environment types for identification. + * @returns {PythonEnvKind[]} : List of environments ordered by identification priority + * + * Remarks: This is the order of detection based on how the various distributions and tools + * configure the environment, and the fall back for identification. + * Top level we have the following environment types, since they leave a unique signature + * in the environment or use a unique path for the environments they create. + * 1. Conda + * 2. Microsoft Store + * 3. PipEnv + * 4. Pyenv + * 5. Poetry + * 6. Hatch + * 7. Pixi + * + * Next level we have the following virtual environment tools. The are here because they + * are consumed by the tools above, and can also be used independently. + * 1. venv + * 2. virtualenvwrapper + * 3. virtualenv + * + * Last category is globally installed python, or system python. + */ +export function getPrioritizedEnvKinds(): PythonEnvKind[] { + return [ + PythonEnvKind.Pyenv, + PythonEnvKind.Pixi, // Placed here since Pixi environments are essentially Conda envs + PythonEnvKind.Conda, + PythonEnvKind.MicrosoftStore, + PythonEnvKind.Pipenv, + PythonEnvKind.Poetry, + PythonEnvKind.Hatch, + PythonEnvKind.Venv, + PythonEnvKind.VirtualEnvWrapper, + PythonEnvKind.VirtualEnv, + PythonEnvKind.ActiveState, + PythonEnvKind.OtherVirtual, + PythonEnvKind.OtherGlobal, + PythonEnvKind.System, + PythonEnvKind.Custom, + PythonEnvKind.Unknown, + ]; +} diff --git a/src/client/pythonEnvironments/base/info/environmentInfoService.ts b/src/client/pythonEnvironments/base/info/environmentInfoService.ts new file mode 100644 index 000000000000..6a981d21b6df --- /dev/null +++ b/src/client/pythonEnvironments/base/info/environmentInfoService.ts @@ -0,0 +1,233 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { Uri } from 'vscode'; +import { IDisposableRegistry } from '../../../common/types'; +import { createDeferred, Deferred, sleep } from '../../../common/utils/async'; +import { createRunningWorkerPool, IWorkerPool, QueuePosition } from '../../../common/utils/workerPool'; +import { getInterpreterInfo, InterpreterInformation } from './interpreter'; +import { buildPythonExecInfo } from '../../exec'; +import { traceError, traceVerbose, traceWarn } from '../../../logging'; +import { Conda, CONDA_ACTIVATION_TIMEOUT, isCondaEnvironment } from '../../common/environmentManagers/conda'; +import { PythonEnvInfo, PythonEnvKind } from '.'; +import { normCasePath } from '../../common/externalDependencies'; +import { OUTPUT_MARKER_SCRIPT } from '../../../common/process/internal/scripts'; +import { Architecture } from '../../../common/utils/platform'; +import { getEmptyVersion } from './pythonVersion'; + +export enum EnvironmentInfoServiceQueuePriority { + Default, + High, +} + +export interface IEnvironmentInfoService { + /** + * Get the interpreter information for the given environment. + * @param env The environment to get the interpreter information for. + * @param priority The priority of the request. + */ + getEnvironmentInfo( + env: PythonEnvInfo, + priority?: EnvironmentInfoServiceQueuePriority, + ): Promise; + /** + * Reset any stored interpreter information for the given environment. + * @param searchLocation Search location of the environment. + */ + resetInfo(searchLocation: Uri): void; +} + +async function buildEnvironmentInfo( + env: PythonEnvInfo, + useIsolated = true, +): Promise { + const python = [env.executable.filename]; + if (useIsolated) { + python.push(...['-I', OUTPUT_MARKER_SCRIPT]); + } else { + python.push(...[OUTPUT_MARKER_SCRIPT]); + } + const interpreterInfo = await getInterpreterInfo(buildPythonExecInfo(python, undefined, env.executable.filename)); + return interpreterInfo; +} + +async function buildEnvironmentInfoUsingCondaRun(env: PythonEnvInfo): Promise { + const conda = await Conda.getConda(); + const path = env.location.length ? env.location : env.executable.filename; + const condaEnv = await conda?.getCondaEnvironment(path); + if (!condaEnv) { + return undefined; + } + const python = await conda?.getRunPythonArgs(condaEnv, true, true); + if (!python) { + return undefined; + } + const interpreterInfo = await getInterpreterInfo( + buildPythonExecInfo(python, undefined, env.executable.filename), + CONDA_ACTIVATION_TIMEOUT, + ); + return interpreterInfo; +} + +class EnvironmentInfoService implements IEnvironmentInfoService { + // Caching environment here in-memory. This is so that we don't have to run this on the same + // path again and again in a given session. This information will likely not change in a given + // session. There are definitely cases where this will change. But a simple reload should address + // those. + private readonly cache: Map> = new Map< + string, + Deferred + >(); + + private workerPool?: IWorkerPool; + + private condaRunWorkerPool?: IWorkerPool; + + public dispose(): void { + if (this.workerPool !== undefined) { + this.workerPool.stop(); + this.workerPool = undefined; + } + if (this.condaRunWorkerPool !== undefined) { + this.condaRunWorkerPool.stop(); + this.condaRunWorkerPool = undefined; + } + } + + public async getEnvironmentInfo( + env: PythonEnvInfo, + priority?: EnvironmentInfoServiceQueuePriority, + ): Promise { + const interpreterPath = env.executable.filename; + const result = this.cache.get(normCasePath(interpreterPath)); + if (result !== undefined) { + // Another call for this environment has already been made, return its result. + return result.promise; + } + + const deferred = createDeferred(); + this.cache.set(normCasePath(interpreterPath), deferred); + this._getEnvironmentInfo(env, priority) + .then((r) => { + deferred.resolve(r); + }) + .catch((ex) => { + deferred.reject(ex); + }); + return deferred.promise; + } + + public async _getEnvironmentInfo( + env: PythonEnvInfo, + priority?: EnvironmentInfoServiceQueuePriority, + retryOnce = true, + ): Promise { + if (env.kind === PythonEnvKind.Conda && env.executable.filename === 'python') { + const emptyInterpreterInfo: InterpreterInformation = { + arch: Architecture.Unknown, + executable: { + filename: 'python', + ctime: -1, + mtime: -1, + sysPrefix: '', + }, + version: getEmptyVersion(), + }; + + return emptyInterpreterInfo; + } + if (this.workerPool === undefined) { + this.workerPool = createRunningWorkerPool( + buildEnvironmentInfo, + ); + } + + let reason: Error | undefined; + let r = await addToQueue(this.workerPool, env, priority).catch((err) => { + reason = err; + return undefined; + }); + + if (r === undefined) { + // Even though env kind is not conda, it can still be a conda environment + // as complete env info may not be available at this time. + const isCondaEnv = env.kind === PythonEnvKind.Conda || (await isCondaEnvironment(env.executable.filename)); + if (isCondaEnv) { + traceVerbose( + `Validating ${env.executable.filename} normally failed with error, falling back to using conda run: (${reason})`, + ); + if (this.condaRunWorkerPool === undefined) { + // Create a separate queue for validation using conda, so getting environment info for + // other types of environment aren't blocked on conda. + this.condaRunWorkerPool = createRunningWorkerPool< + PythonEnvInfo, + InterpreterInformation | undefined + >(buildEnvironmentInfoUsingCondaRun); + } + r = await addToQueue(this.condaRunWorkerPool, env, priority).catch((err) => { + traceError(err); + return undefined; + }); + } else if (reason) { + if ( + reason.message.includes('Unknown option: -I') || + reason.message.includes("ModuleNotFoundError: No module named 'encodings'") + ) { + traceWarn(reason); + if (reason.message.includes('Unknown option: -I')) { + traceError( + 'Support for Python 2.7 has been dropped by the Python extension so certain features may not work, upgrade to using Python 3.', + ); + } + return buildEnvironmentInfo(env, false).catch((err) => { + traceError(err); + return undefined; + }); + } + traceError(reason); + } + } + if (r === undefined && retryOnce) { + // Retry once, in case the environment was not fully populated. Also observed in CI: + // https://github.com/microsoft/vscode-python/issues/20147 where running environment the first time + // failed due to unknown reasons. + return sleep(2000).then(() => this._getEnvironmentInfo(env, priority, false)); + } + return r; + } + + public resetInfo(searchLocation: Uri): void { + const searchLocationPath = searchLocation.fsPath; + const keys = Array.from(this.cache.keys()); + keys.forEach((key) => { + if (key.startsWith(normCasePath(searchLocationPath))) { + this.cache.delete(key); + } + }); + } +} + +function addToQueue( + workerPool: IWorkerPool, + env: PythonEnvInfo, + priority: EnvironmentInfoServiceQueuePriority | undefined, +) { + return priority === EnvironmentInfoServiceQueuePriority.High + ? workerPool.addToQueue(env, QueuePosition.Front) + : workerPool.addToQueue(env, QueuePosition.Back); +} + +let envInfoService: IEnvironmentInfoService | undefined; +export function getEnvironmentInfoService(disposables?: IDisposableRegistry): IEnvironmentInfoService { + if (envInfoService === undefined) { + const service = new EnvironmentInfoService(); + disposables?.push({ + dispose: () => { + service.dispose(); + envInfoService = undefined; + }, + }); + envInfoService = service; + } + return envInfoService; +} diff --git a/src/client/pythonEnvironments/base/info/executable.ts b/src/client/pythonEnvironments/base/info/executable.ts new file mode 100644 index 000000000000..ab5a67d79315 --- /dev/null +++ b/src/client/pythonEnvironments/base/info/executable.ts @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { getOSType, OSType } from '../../../common/utils/platform'; +import { getEmptyVersion, parseVersion } from './pythonVersion'; + +import { PythonVersion } from '.'; +import { normCasePath } from '../../common/externalDependencies'; + +/** + * Determine a best-effort Python version based on the given filename. + */ +export function parseVersionFromExecutable(filename: string): PythonVersion { + const version = parseBasename(path.basename(filename)); + + if (version.major === 2 && version.minor === -1) { + version.minor = 7; + } + + return version; +} + +function parseBasename(basename: string): PythonVersion { + basename = normCasePath(basename); + if (getOSType() === OSType.Windows) { + if (basename === 'python.exe') { + // On Windows we can't assume it is 2.7. + return getEmptyVersion(); + } + } else if (basename === 'python') { + // We can assume it is 2.7. (See PEP 394.) + return parseVersion('2.7'); + } + if (!basename.startsWith('python')) { + throw Error(`not a Python executable (expected "python..", got "${basename}")`); + } + // If we reach here then we expect it to have a version in the name. + return parseVersion(basename); +} diff --git a/src/client/pythonEnvironments/base/info/index.ts b/src/client/pythonEnvironments/base/info/index.ts index 4418860a12e5..4547e7606308 100644 --- a/src/client/pythonEnvironments/base/info/index.ts +++ b/src/client/pythonEnvironments/base/info/index.ts @@ -12,11 +12,12 @@ export enum PythonEnvKind { Unknown = 'unknown', // "global" System = 'global-system', - MacDefault = 'global-mac-default', - WindowsStore = 'global-windows-store', + MicrosoftStore = 'global-microsoft-store', Pyenv = 'global-pyenv', - CondaBase = 'global-conda-base', - Poetry = 'global-poetry', + Poetry = 'poetry', + Hatch = 'hatch', + Pixi = 'pixi', + ActiveState = 'activestate', Custom = 'global-custom', OtherGlobal = 'global-other', // "virtual" @@ -25,13 +26,42 @@ export enum PythonEnvKind { VirtualEnvWrapper = 'virt-virtualenvwrapper', Pipenv = 'virt-pipenv', Conda = 'virt-conda', - OtherVirtual = 'virt-other' + OtherVirtual = 'virt-other', } -/** - * A (system-global) unique ID for a single Python environment. - */ -export type PythonEnvID = string; +export enum PythonEnvType { + Conda = 'Conda', + Virtual = 'Virtual', +} + +export interface EnvPathType { + /** + * Path to environment folder or path to interpreter that uniquely identifies an environment. + * Virtual environments lacking an interpreter are identified by environment folder paths, + * whereas other envs can be identified using interpreter path. + */ + path: string; + pathType: 'envFolderPath' | 'interpreterPath'; +} + +export const virtualEnvKinds = [ + PythonEnvKind.Poetry, + PythonEnvKind.Hatch, + PythonEnvKind.Pixi, + PythonEnvKind.Pipenv, + PythonEnvKind.Venv, + PythonEnvKind.VirtualEnvWrapper, + PythonEnvKind.Conda, + PythonEnvKind.VirtualEnv, +]; + +export const globallyInstalledEnvKinds = [ + PythonEnvKind.OtherGlobal, + PythonEnvKind.Unknown, + PythonEnvKind.MicrosoftStore, + PythonEnvKind.System, + PythonEnvKind.Custom, +]; /** * Information about a file. @@ -49,6 +79,25 @@ export type PythonExecutableInfo = FileInfo & { sysPrefix: string; }; +/** + * Source types indicating how a particular environment was discovered. + * + * Notes: This is used in auto-selection to figure out which python to select. + * We added this field to support the existing mechanism in the extension to + * calculate the auto-select python. + */ +export enum PythonEnvSource { + /** + * Environment was found via PATH env variable + */ + PathEnvVar = 'path env var', + /** + * Environment was found in windows registry + */ + WindowsRegistry = 'windows registry', + // If source turns out to be useful we will expand this enum to contain more details sources. +} + /** * The most fundamental information about a Python environment. * @@ -61,9 +110,12 @@ export type PythonExecutableInfo = FileInfo & { * @prop executable - info about the env's Python binary * @prop name - the env's distro-specific name, if any * @prop location - the env's location (on disk), if relevant + * @prop source - the locator[s] which found the environment. */ -export type PythonEnvBaseInfo = { +type PythonEnvBaseInfo = { + id?: string; kind: PythonEnvKind; + type?: PythonEnvType; executable: PythonExecutableInfo; // One of (name, location) must be non-empty. name: string; @@ -72,6 +124,8 @@ export type PythonEnvBaseInfo = { // * managed: boolean (if the env is "managed") // * parent: PythonEnvBaseInfo (the env from which this one was created) // * binDir: string (where env-installed executables are found) + + source: PythonEnvSource[]; }; /** @@ -81,7 +135,7 @@ export enum PythonReleaseLevel { Alpha = 'alpha', Beta = 'beta', Candidate = 'candidate', - Final = 'final' + Final = 'final', } /** @@ -105,7 +159,7 @@ export type PythonVersion = BasicVersionInfo & { /** * Information for a Python build/installation. */ -export type PythonBuildInfo = { +type PythonBuildInfo = { version: PythonVersion; // incl. raw, AKA sys.version arch: Architecture; }; @@ -116,7 +170,7 @@ export type PythonBuildInfo = { * @prop org - the name of the distro's creator/publisher * @prop defaultDisplayName - the text to use when showing the distro to users */ -export type PythonDistroMetaInfo = { +type PythonDistroMetaInfo = { org: string; defaultDisplayName?: string; }; @@ -142,11 +196,34 @@ type _PythonEnvInfo = PythonEnvBaseInfo & PythonBuildInfo; * they will usually be able to provide the version as well. * * @prop distro - the installed Python distro that this env is using or belongs to - * @prop defaultDisplayName - the text to use when showing the env to users - * @prop searchLocation - the root under which a locator found this env, if any + * @prop display - the text to use when showing the env to users + * @prop detailedDisplayName - display name containing all details + * @prop searchLocation - the project to which this env is related to, if any */ export type PythonEnvInfo = _PythonEnvInfo & { distro: PythonDistroInfo; - defaultDisplayName?: string; + display?: string; + detailedDisplayName?: string; searchLocation?: Uri; + /** + * Command used to run Python in this environment. + * E.g. `conda run -n envName python` or `python.exe` + */ + pythonRunCommand?: string[]; + identifiedUsingNativeLocator?: boolean; +}; + +/** + * A dummy python version object containing default fields. + * + * Note this object is immutable. So if it is assigned to another object, the properties of the other object + * also cannot be modified by reference. For eg. `otherVersionObject.major = 3` won't work. + */ +export const UNKNOWN_PYTHON_VERSION: PythonVersion = { + major: -1, + minor: -1, + micro: -1, + release: { level: PythonReleaseLevel.Final, serial: -1 }, + sysVersion: undefined, }; +Object.freeze(UNKNOWN_PYTHON_VERSION); diff --git a/src/client/pythonEnvironments/base/info/interpreter.ts b/src/client/pythonEnvironments/base/info/interpreter.ts index 16d82d3280b5..e19e1f0d45c2 100644 --- a/src/client/pythonEnvironments/base/info/interpreter.ts +++ b/src/client/pythonEnvironments/base/info/interpreter.ts @@ -2,8 +2,14 @@ // Licensed under the MIT License. import { PythonExecutableInfo, PythonVersion } from '.'; -import { interpreterInfo as getInterpreterInfoCommand, InterpreterInfoJson } from '../../../common/process/internal/scripts'; +import { isCI } from '../../../common/constants'; +import { + interpreterInfo as getInterpreterInfoCommand, + InterpreterInfoJson, +} from '../../../common/process/internal/scripts'; import { Architecture } from '../../../common/utils/platform'; +import { traceError, traceInfo, traceVerbose } from '../../../logging'; +import { shellExecute } from '../../common/externalDependencies'; import { copyPythonExecInfo, PythonExecInfo } from '../../exec'; import { parseVersion } from './pythonVersion'; @@ -22,7 +28,24 @@ export type InterpreterInformation = { * @param raw - the information returned by the `interpreterInfo.py` script */ function extractInterpreterInfo(python: string, raw: InterpreterInfoJson): InterpreterInformation { - const rawVersion = `${raw.versionInfo.slice(0, 3).join('.')}-${raw.versionInfo[3]}`; + let rawVersion = `${raw.versionInfo.slice(0, 3).join('.')}`; + + // We only need additional version details if the version is 'alpha', 'beta' or 'candidate'. + // This restriction is needed to avoid sending any PII if this data is used with telemetry. + // With custom builds of python it is possible that release level and values after that can + // contain PII. + if (raw.versionInfo[3] !== undefined && ['final', 'alpha', 'beta', 'candidate'].includes(raw.versionInfo[3])) { + rawVersion = `${rawVersion}-${raw.versionInfo[3]}`; + if (raw.versionInfo[4] !== undefined) { + let serial = -1; + try { + serial = parseInt(`${raw.versionInfo[4]}`, 10); + } catch (ex) { + serial = -1; + } + rawVersion = serial >= 0 ? `${rawVersion}${serial}` : rawVersion; + } + } return { arch: raw.is64Bit ? Architecture.x64 : Architecture.x86, executable: { @@ -38,36 +61,33 @@ function extractInterpreterInfo(python: string, raw: InterpreterInfoJson): Inter }; } -type ShellExecResult = { - stdout: string; - stderr?: string; -}; -type ShellExecFunc = (command: string, timeout: number) => Promise; - -type Logger = { - info(msg: string): void; - - error(msg: string): void; -}; - /** * Collect full interpreter information from the given Python executable. * * @param python - the information to use when running Python - * @param shellExec - the function to use to exec Python - * @param logger - if provided, used to log failures or other info + * @param timeout - any specific timeouts to use for getting info. */ export async function getInterpreterInfo( python: PythonExecInfo, - shellExec: ShellExecFunc, - logger?: Logger, + timeout?: number, ): Promise { const [args, parse] = getInterpreterInfoCommand(); const info = copyPythonExecInfo(python, args); const argv = [info.command, ...info.args]; // Concat these together to make a set of quoted strings - const quoted = argv.reduce((p, c) => (p ? `${p} "${c}"` : `"${c.replace('\\', '\\\\')}"`), ''); + const quoted = argv.reduce( + (p, c) => (p ? `${p} ${c.toCommandArgumentForPythonExt()}` : `${c.toCommandArgumentForPythonExt()}`), + '', + ); + + // Sometimes on CI, the python process takes a long time to start up. This is a workaround for that. + let standardTimeout = isCI ? 30000 : 15000; + if (process.env.VSC_PYTHON_INTERPRETER_INFO_TIMEOUT !== undefined) { + // Custom override for setups where the initial Python setup process may take longer than the standard timeout. + standardTimeout = parseInt(process.env.VSC_PYTHON_INTERPRETER_INFO_TIMEOUT, 10); + traceInfo(`Custom interpreter discovery timeout: ${standardTimeout}`); + } // Try shell execing the command, followed by the arguments. This will make node kill the process if it // takes too long. @@ -75,16 +95,19 @@ export async function getInterpreterInfo( // See these two bugs: // https://github.com/microsoft/vscode-python/issues/7569 // https://github.com/microsoft/vscode-python/issues/7760 - const result = await shellExec(quoted, 15000); + const result = await shellExecute(quoted, { timeout: timeout ?? standardTimeout }); if (result.stderr) { - if (logger) { - logger.error(`Failed to parse interpreter information for ${argv} stderr: ${result.stderr}`); - } - return undefined; + traceError( + `Stderr when executing script with >> ${quoted} << stderr: ${result.stderr}, still attempting to parse output`, + ); } - const json = parse(result.stdout); - if (logger) { - logger.info(`Found interpreter for ${argv}`); + let json: InterpreterInfoJson; + try { + json = parse(result.stdout); + } catch (ex) { + traceError(`Failed to parse interpreter information for >> ${quoted} << with ${ex}`); + return undefined; } + traceVerbose(`Found interpreter for >> ${quoted} <<: ${JSON.stringify(json)}`); return extractInterpreterInfo(python.pythonExecutable, json); } diff --git a/src/client/pythonEnvironments/base/info/pythonVersion.ts b/src/client/pythonEnvironments/base/info/pythonVersion.ts index 0e281eb4662c..589bf4c7b7af 100644 --- a/src/client/pythonEnvironments/base/info/pythonVersion.ts +++ b/src/client/pythonEnvironments/base/info/pythonVersion.ts @@ -1,70 +1,290 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { EMPTY_VERSION, parseBasicVersionInfo } from '../../../common/utils/version'; +import { cloneDeep } from 'lodash'; +import * as path from 'path'; +import * as basic from '../../../common/utils/version'; -import { PythonReleaseLevel, PythonVersion } from '.'; +import { PythonReleaseLevel, PythonVersion, PythonVersionRelease, UNKNOWN_PYTHON_VERSION } from '.'; +import { traceError } from '../../../logging'; + +// XXX getPythonVersionFromPath() should go away in favor of parseVersionFromExecutable(). + +export function getPythonVersionFromPath(exe: string): PythonVersion { + let version = UNKNOWN_PYTHON_VERSION; + try { + version = parseVersion(path.basename(exe)); + } catch (ex) { + traceError(`Failed to parse version from path: ${exe}`, ex); + } + return version; +} /** * Convert the given string into the corresponding Python version object. + * + * Example: + * 3.9.0 + * 3.9.0a1 + * 3.9.0b2 + * 3.9.0rc1 + * 3.9.0-beta2 + * 3.9.0.beta.2 + * 3.9.0.final.0 + * 39 */ export function parseVersion(versionStr: string): PythonVersion { - const parsed = parseBasicVersionInfo(versionStr); + const [version, after] = parseBasicVersion(versionStr); + if (version.micro === -1) { + return version; + } + const [release] = parseRelease(after); + version.release = release; + return version; +} + +export function parseRelease(text: string): [PythonVersionRelease | undefined, string] { + let after: string; + + let alpha: string | undefined; + let beta: string | undefined; + let rc: string | undefined; + let fin: string | undefined; + let serialStr: string; + + let match = text.match(/^(?:-?final|\.final(?:\.0)?)(.*)$/); + if (match) { + [, after] = match; + fin = 'final'; + serialStr = '0'; + } else { + for (const regex of [ + /^(?:(a)|(b)|(rc))([1-9]\d*)(.*)$/, + /^-(?:(?:(alpha)|(beta)|(candidate))([1-9]\d*))(.*)$/, + /^\.(?:(?:(alpha)|(beta)|(candidate))\.([1-9]\d*))(.*)$/, + ]) { + match = text.match(regex); + if (match) { + [, alpha, beta, rc, serialStr, after] = match; + break; + } + } + } + + let level: PythonReleaseLevel; + if (fin) { + level = PythonReleaseLevel.Final; + } else if (rc) { + level = PythonReleaseLevel.Candidate; + } else if (beta) { + level = PythonReleaseLevel.Beta; + } else if (alpha) { + level = PythonReleaseLevel.Alpha; + } else { + // We didn't find release info. + return [undefined, text]; + } + const serial = parseInt(serialStr!, 10); + return [{ level, serial }, after!]; +} + +/** + * Convert the given string into the corresponding Python version object. + */ +export function parseBasicVersion(versionStr: string): [PythonVersion, string] { + // We set a prefix (which will be ignored) to make sure "plain" + // versions are fully parsed. + const parsed = basic.parseBasicVersionInfo(`ignored-${versionStr}`); if (!parsed) { if (versionStr === '') { - return EMPTY_VERSION as PythonVersion; + return [getEmptyVersion(), '']; } throw Error(`invalid version ${versionStr}`); } + // We ignore any "before" text. const { version, after } = parsed; - const match = after.match(/^(a|b|rc)(\d+)$/); - if (match) { - const [, levelStr, serialStr] = match; - let level: PythonReleaseLevel; - if (levelStr === 'a') { - level = PythonReleaseLevel.Alpha; - } else if (levelStr === 'b') { - level = PythonReleaseLevel.Beta; - } else if (levelStr === 'rc') { - level = PythonReleaseLevel.Candidate; - } else { - throw Error('unreachable!'); + version.release = undefined; + + if (version.minor === -1) { + // We trust that the major version is always single-digit. + if (version.major > 9) { + const numdigits = version.major.toString().length - 1; + const factor = 10 ** numdigits; + version.minor = version.major % factor; + version.major = Math.floor(version.major / factor); } - version.release = { - level, - serial: parseInt(serialStr, 10), - }; } - return version; + + return [version, after]; } /** - * Checks if all the fields in the version object match. - * @param {PythonVersion} left - * @param {PythonVersion} right - * @returns {boolean} + * Get a new version object with all properties "zeroed out". */ -export function areEqualVersions(left: PythonVersion, right:PythonVersion): boolean { - return left === right; +export function getEmptyVersion(): PythonVersion { + return cloneDeep(basic.EMPTY_VERSION); } /** - * Checks if major and minor version fields match. True here means that the python ABI is the - * same, but the micro version could be different. But for the purpose this is being used - * it does not matter. - * @param {PythonVersion} left - * @param {PythonVersion} right - * @returns {boolean} + * Determine if the version is effectively a blank one. */ -export function areEquivalentVersions(left: PythonVersion, right:PythonVersion): boolean { - if (left.major === 2 && right.major === 2) { - // We are going to assume that if the major version is 2 then the version is 2.7 +export function isVersionEmpty(version: PythonVersion): boolean { + // We really only care the `version.major` is -1. However, using + // generic util is better in the long run. + return basic.isVersionInfoEmpty(version); +} +/** + * Convert the info to a user-facing representation. + */ +export function getVersionDisplayString(ver: PythonVersion): string { + if (isVersionEmpty(ver)) { + return ''; + } + if (ver.micro !== -1) { + return getShortVersionString(ver); + } + return `${getShortVersionString(ver)}.x`; +} + +/** + * Convert the info to a simple string. + */ +export function getShortVersionString(ver: PythonVersion): string { + let verStr = basic.getVersionString(ver); + if (ver.release === undefined) { + return verStr; + } + if (ver.release.level === PythonReleaseLevel.Final) { + return verStr; + } + if (ver.release.level === PythonReleaseLevel.Candidate) { + verStr = `${verStr}rc${ver.release.serial}`; + } else if (ver.release.level === PythonReleaseLevel.Beta) { + verStr = `${verStr}b${ver.release.serial}`; + } else if (ver.release.level === PythonReleaseLevel.Alpha) { + verStr = `${verStr}a${ver.release.serial}`; + } else { + throw Error(`unsupported release level ${ver.release.level}`); + } + return verStr; +} + +/** + * Checks if all the important properties of the version objects match. + * + * Only major, minor, micro, and release are compared. + */ +export function areIdenticalVersion(left: PythonVersion, right: PythonVersion): boolean { + return basic.areIdenticalVersion(left, right, compareVersionRelease); +} + +/** + * Checks if the versions are identical or one is more complete than other (and otherwise the same). + * + * A `true` result means the Python executables are strictly compatible. + * For Python 3+, at least the minor version must be set. `(2, -1, -1)` + * implies 2.7, so in that case only the major version must be set (to 2). + */ +export function areSimilarVersions(left: PythonVersion, right: PythonVersion): boolean { + if (!basic.areSimilarVersions(left, right, compareVersionRelease)) { + return false; + } + if (left.major === 2) { return true; } + return left.minor > -1 && right.minor > -1; +} + +function compareVersionRelease(left: PythonVersion, right: PythonVersion): [number, string] { + if (left.release === undefined) { + if (right.release === undefined) { + return [0, '']; + } + return [1, 'level']; + } + if (right.release === undefined) { + return [-1, 'level']; + } + + // Compare the level. + if (left.release.level < right.release.level) { + return [1, 'level']; + } + if (left.release.level > right.release.level) { + return [-1, 'level']; + } + if (left.release.level === PythonReleaseLevel.Final) { + // We ignore "serial". + return [0, '']; + } - // In the case of 3.* if major and minor match we assume that they are equivalent versions - return ( - left.major === right.major - && left.minor === right.minor - ); + // Compare the serial. + if (left.release.serial < right.release.serial) { + return [1, 'serial']; + } + if (left.release.serial > right.release.serial) { + return [-1, 'serial']; + } + + return [0, '']; +} + +/** + * Convert Python version to semver like version object. + * + * Remarks: primarily used to convert to old type of environment info. + * @deprecated + */ +export function toSemverLikeVersion( + version: PythonVersion, +): { + raw: string; + major: number; + minor: number; + patch: number; + build: string[]; + prerelease: string[]; +} { + const versionPrefix = basic.getVersionString(version); + let preRelease: string[] = []; + if (version.release) { + preRelease = + version.release.serial < 0 + ? [`${version.release.level}`] + : [`${version.release.level}`, `${version.release.serial}`]; + } + return { + raw: versionPrefix, + major: version.major, + minor: version.minor, + patch: version.micro, + build: [], + prerelease: preRelease, + }; +} + +/** + * Compares major, minor, patch for two versions of python + * @param v1 : semVer like version object + * @param v2 : semVer like version object + * @returns {1 | 0 | -1} : 0 if v1 === v2, + * 1 if v1 > v2, + * -1 if v1 < v2 + * Remarks: primarily used compare to old type of version info. + * @deprecated + */ +export function compareSemVerLikeVersions( + v1: { major: number; minor: number; patch: number }, + v2: { major: number; minor: number; patch: number }, +): 1 | 0 | -1 { + if (v1.major === v2.major) { + if (v1.minor === v2.minor) { + if (v1.patch === v2.patch) { + return 0; + } + return v1.patch > v2.patch ? 1 : -1; + } + return v1.minor > v2.minor ? 1 : -1; + } + return v1.major > v2.major ? 1 : -1; } diff --git a/src/client/pythonEnvironments/base/locator.ts b/src/client/pythonEnvironments/base/locator.ts index a1b372fb6988..0c15f8b27e5f 100644 --- a/src/client/pythonEnvironments/base/locator.ts +++ b/src/client/pythonEnvironments/base/locator.ts @@ -1,20 +1,23 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +/* eslint-disable max-classes-per-file */ + import { Event, Uri } from 'vscode'; -import { iterEmpty } from '../../common/utils/async'; -import { PythonEnvInfo, PythonEnvKind } from './info'; +import { IAsyncIterableIterator, iterEmpty } from '../../common/utils/async'; +import { PythonEnvInfo, PythonEnvKind, PythonEnvSource, PythonVersion } from './info'; import { - BasicPythonEnvsChangedEvent, IPythonEnvsWatcher, + PythonEnvCollectionChangedEvent, PythonEnvsChangedEvent, - PythonEnvsWatcher + PythonEnvsWatcher, } from './watcher'; +import type { Architecture } from '../../common/utils/platform'; /** * A single update to a previously provided Python env object. */ -export type PythonEnvUpdatedEvent = { +export type PythonEnvUpdatedEvent = { /** * The iteration index of The env info that was previously provided. */ @@ -22,11 +25,12 @@ export type PythonEnvUpdatedEvent = { /** * The env info that was previously provided. */ - old?: PythonEnvInfo; + old?: I; /** * The env info that replaces the old info. + * Update is sent as `undefined` if we find out that the environment is no longer valid. */ - update: PythonEnvInfo; + update: I | undefined; }; /** @@ -51,7 +55,7 @@ export type PythonEnvUpdatedEvent = { * Callers can usually ignore the update event entirely and rely on * the locator to provide sufficiently complete information. */ -export interface IPythonEnvsIterator extends AsyncIterator { +export interface IPythonEnvsIterator extends IAsyncIterableIterator { /** * Provides possible updates for already-iterated envs. * @@ -60,7 +64,24 @@ export interface IPythonEnvsIterator extends AsyncIterator * If this property is not provided then it means the iterator does * not support updates. */ - onUpdated?: Event; + onUpdated?: Event | ProgressNotificationEvent>; +} + +export enum ProgressReportStage { + idle = 'idle', + discoveryStarted = 'discoveryStarted', + allPathsDiscovered = 'allPathsDiscovered', + discoveryFinished = 'discoveryFinished', +} + +export type ProgressNotificationEvent = { + stage: ProgressReportStage; +}; + +export function isProgressEvent( + event: PythonEnvUpdatedEvent | ProgressNotificationEvent, +): event is ProgressNotificationEvent { + return 'stage' in event; } /** @@ -73,27 +94,28 @@ export const NOOP_ITERATOR: IPythonEnvsIterator = iterEmpty(); * * This is directly correlated with the `BasicPythonEnvsChangedEvent` * emitted by watchers. - * - * @prop kinds - if provided, results should be limited to these env - * kinds; if not provided, the kind of each evnironment - * is not considered when filtering */ -export type BasicPythonLocatorQuery = { +type BasicPythonLocatorQuery = { + /** + * If provided, results should be limited to these env + * kinds; if not provided, the kind of each environment + * is not considered when filtering + */ kinds?: PythonEnvKind[]; }; /** * The portion of a query related to env search locations. */ -export type SearchLocations = { +type SearchLocations = { /** * The locations under which to look for environments. */ roots: Uri[]; /** - * If true, also look for environments that do not have a search location. + * If true, only query for workspace related envs, i.e do not look for environments that do not have a search location. */ - includeNonRooted?: boolean; + doNotIncludeNonRooted?: boolean; }; /** @@ -107,10 +129,41 @@ export type PythonLocatorQuery = BasicPythonLocatorQuery & { * If provided, results should be limited to within these locations. */ searchLocations?: SearchLocations; + /** + * If provided, results should be limited envs provided by these locators. + */ + providerId?: string; + /** + * If provided, results are limited to this env. + */ + envPath?: string; }; type QueryForEvent = E extends PythonEnvsChangedEvent ? PythonLocatorQuery : BasicPythonLocatorQuery; +export type BasicEnvInfo = { + kind: PythonEnvKind; + executablePath: string; + source?: PythonEnvSource[]; + envPath?: string; + /** + * The project to which this env is related to, if any + * E.g. the project directory when dealing with pipenv virtual environments. + */ + searchLocation?: Uri; + version?: PythonVersion; + name?: string; + /** + * Display name provided by locators, not generated by us. + * E.g. display name as provided by Windows Registry or Windows Store, etc + */ + displayName?: string; + identifiedUsingNativeLocator?: boolean; + arch?: Architecture; + ctime?: number; + mtime?: number; +}; + /** * A single Python environment locator. * @@ -125,8 +178,8 @@ type QueryForEvent = E extends PythonEnvsChangedEvent ? PythonLocatorQuery : * events emitted via `onChanged` do not need to provide information * for the specific environments that changed. */ -export interface ILocator - extends IPythonEnvsWatcher { +export interface ILocator extends IPythonEnvsWatcher { + readonly providerId: string; /** * Iterate over the enviroments known tos this locator. * @@ -143,26 +196,72 @@ export interface ILocator): IPythonEnvsIterator; + iterEnvs(query?: QueryForEvent): IPythonEnvsIterator; +} + +export type ICompositeLocator = Omit, 'providerId'>; +interface IResolver { /** - * Find the given Python environment and fill in as much missing info as possible. - * - * If the locator can find the environment then the result is as - * much info about that env as the locator has. At the least this - * will include all the `PythonEnvBaseInfo` data. If a `PythonEnvInfo` - * was provided then the result will be a copy with any updates or - * extra info applied. + * Find as much info about the given Python environment as possible. + * If path passed is invalid, then `undefined` is returned. * - * If the locator could not find the environment then `undefined` - * is returned. + * @param path - Python executable path or environment path to resolve more information about + */ + resolveEnv(path: string): Promise; +} + +export interface IResolvingLocator extends IResolver, ICompositeLocator {} + +export interface GetRefreshEnvironmentsOptions { + /** + * Get refresh promise which resolves once the following stage has been reached for the list of known environments. + */ + stage?: ProgressReportStage; +} + +export type TriggerRefreshOptions = { + /** + * Only trigger a refresh if it hasn't already been triggered for this session. + */ + ifNotTriggerredAlready?: boolean; +}; + +export interface IDiscoveryAPI { + readonly refreshState: ProgressReportStage; + /** + * Tracks discovery progress for current list of known environments, i.e when it starts, finishes or any other relevant + * stage. Note the progress for a particular query is currently not tracked or reported, this only indicates progress of + * the entire collection. + */ + readonly onProgress: Event; + /** + * Fires with details if the known list changes. + */ + readonly onChanged: Event; + /** + * Resolves once environment list has finished refreshing, i.e all environments are + * discovered. Carries `undefined` if there is no refresh currently going on. + */ + getRefreshPromise(options?: GetRefreshEnvironmentsOptions): Promise | undefined; + /** + * Triggers a new refresh for query if there isn't any already running. + */ + triggerRefresh(query?: PythonLocatorQuery, options?: TriggerRefreshOptions): Promise; + /** + * Get current list of known environments. + */ + getEnvs(query?: PythonLocatorQuery): PythonEnvInfo[]; + /** + * Find as much info about the given Python environment as possible. + * If path passed is invalid, then `undefined` is returned. * - * @param env - the Python executable path or partial env info to find and update + * @param path - Full path of Python executable or environment folder to resolve more information about */ - resolveEnv(env: string | PythonEnvInfo): Promise; + resolveEnv(path: string): Promise; } -interface IEmitter { +export interface IEmitter { fire(e: E): void; } @@ -178,19 +277,20 @@ interface IEmitter { * should be used. Only in low-level cases should you consider using * `BasicPythonEnvsChangedEvent`. */ -export abstract class LocatorBase implements ILocator { +abstract class LocatorBase implements ILocator { public readonly onChanged: Event; + + public abstract readonly providerId: string; + protected readonly emitter: IEmitter; + constructor(watcher: IPythonEnvsWatcher & IEmitter) { this.emitter = watcher; this.onChanged = watcher.onChanged; } - public abstract iterEnvs(query?: QueryForEvent): IPythonEnvsIterator; - - public async resolveEnv(_env: string | PythonEnvInfo): Promise { - return undefined; - } + // eslint-disable-next-line class-methods-use-this + public abstract iterEnvs(query?: QueryForEvent): IPythonEnvsIterator; } /** @@ -205,7 +305,7 @@ export abstract class LocatorBase extends LocatorBase { constructor() { super(new PythonEnvsWatcher()); } diff --git a/src/client/pythonEnvironments/base/locatorUtils.ts b/src/client/pythonEnvironments/base/locatorUtils.ts index 25c4815c79c0..6af8c0ee1b69 100644 --- a/src/client/pythonEnvironments/base/locatorUtils.ts +++ b/src/client/pythonEnvironments/base/locatorUtils.ts @@ -4,9 +4,13 @@ import { Uri } from 'vscode'; import { createDeferred } from '../../common/utils/async'; import { getURIFilter } from '../../common/utils/misc'; +import { traceVerbose } from '../../logging'; import { PythonEnvInfo } from './info'; import { IPythonEnvsIterator, + isProgressEvent, + ProgressNotificationEvent, + ProgressReportStage, PythonEnvUpdatedEvent, PythonLocatorQuery, } from './locator'; @@ -15,18 +19,8 @@ import { * Create a filter function to match the given query. */ export function getQueryFilter(query: PythonLocatorQuery): (env: PythonEnvInfo) => boolean { - const kinds = (query.kinds !== undefined && query.kinds.length > 0) - ? query.kinds - : undefined; - let includeNonRooted = true; - if (query.searchLocations !== undefined) { - if (query.searchLocations.includeNonRooted !== undefined) { - includeNonRooted = query.searchLocations.includeNonRooted; - } else { - // We default to `false`. - includeNonRooted = false; - } - } + const kinds = query.kinds !== undefined && query.kinds.length > 0 ? query.kinds : undefined; + const includeNonRooted = !query.searchLocations?.doNotIncludeNonRooted; // We default to `true`. const locationFilters = getSearchLocationFilters(query); function checkKind(env: PythonEnvInfo): boolean { if (kinds === undefined) { @@ -65,10 +59,11 @@ function getSearchLocationFilters(query: PythonLocatorQuery): ((u: Uri) => boole if (query.searchLocations.roots.length === 0) { return []; } - return query.searchLocations.roots.map((loc) => getURIFilter(loc, { - checkParent: true, - checkExact: true, - })); + return query.searchLocations.roots.map((loc) => + getURIFilter(loc, { + checkParent: true, + }), + ); } /** @@ -76,33 +71,44 @@ function getSearchLocationFilters(query: PythonLocatorQuery): ((u: Uri) => boole * * This includes applying any received updates. */ -export async function getEnvs(iterator: IPythonEnvsIterator): Promise { - const envs: PythonEnvInfo[] = []; +export async function getEnvs(iterator: IPythonEnvsIterator): Promise { + const envs: (I | undefined)[] = []; const updatesDone = createDeferred(); if (iterator.onUpdated === undefined) { updatesDone.resolve(); } else { - iterator.onUpdated((event: PythonEnvUpdatedEvent | null) => { - if (event === null) { + const listener = iterator.onUpdated((event: PythonEnvUpdatedEvent | ProgressNotificationEvent) => { + if (isProgressEvent(event)) { + if (event.stage !== ProgressReportStage.discoveryFinished) { + return; + } updatesDone.resolve(); - return; - } - const oldEnv = envs[event.index]; - if (oldEnv === undefined) { - // XXX log or fail - } else { - envs[event.index] = event.update; + listener.dispose(); + } else if (event.index !== undefined) { + const { index, update } = event; + if (envs[index] === undefined) { + const json = JSON.stringify(update); + traceVerbose( + `Updates sent for an env which was classified as invalid earlier, currently not expected, ${json}`, + ); + } + // We don't worry about if envs[index] is set already. + envs[index] = update; } }); } - let result = await iterator.next(); - while (!result.done) { - envs.push(result.value); - result = await iterator.next(); + let itemIndex = 0; + for await (const env of iterator) { + // We can't just push because updates might get emitted early. + if (envs[itemIndex] === undefined) { + envs[itemIndex] = env; + } + itemIndex += 1; } - await updatesDone.promise; - return envs; + + // Do not return invalid environments + return envs.filter((e) => e !== undefined).map((e) => e!); } diff --git a/src/client/pythonEnvironments/base/locators.ts b/src/client/pythonEnvironments/base/locators.ts index 15a67d83b734..10be15c27bf1 100644 --- a/src/client/pythonEnvironments/base/locators.ts +++ b/src/client/pythonEnvironments/base/locators.ts @@ -1,45 +1,57 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { EventEmitter } from 'vscode'; import { chain } from '../../common/utils/async'; +import { Disposables } from '../../common/utils/resourceLifecycle'; import { PythonEnvInfo } from './info'; import { + ICompositeLocator, ILocator, IPythonEnvsIterator, - NOOP_ITERATOR, + isProgressEvent, + ProgressNotificationEvent, + ProgressReportStage, PythonEnvUpdatedEvent, - PythonLocatorQuery + PythonLocatorQuery, } from './locator'; -import { DisableableEnvsWatcher, PythonEnvsWatchers } from './watchers'; +import { PythonEnvsWatchers } from './watchers'; /** * Combine the `onUpdated` event of the given iterators into a single event. */ -export function combineIterators(iterators: IPythonEnvsIterator[]): IPythonEnvsIterator { - const result: IPythonEnvsIterator = chain(iterators); +export function combineIterators(iterators: IPythonEnvsIterator[]): IPythonEnvsIterator { + const result: IPythonEnvsIterator = chain(iterators); const events = iterators.map((it) => it.onUpdated).filter((v) => v); if (!events || events.length === 0) { // There are no sub-events, so we leave `onUpdated` undefined. return result; } - const emitter = new EventEmitter(); - let numActive = events.length; - events.forEach((event) => { - event!((e: PythonEnvUpdatedEvent | null) => { - if (e === null) { - numActive -= 1; - if (numActive === 0) { - // All the sub-events are done so we're done. - emitter.fire(null); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + result.onUpdated = (handleEvent: (e: PythonEnvUpdatedEvent | ProgressNotificationEvent) => any) => { + const disposables = new Disposables(); + let numActive = events.length; + events.forEach((event) => { + const disposable = event!((e: PythonEnvUpdatedEvent | ProgressNotificationEvent) => { + // NOSONAR + if (isProgressEvent(e)) { + if (e.stage === ProgressReportStage.discoveryFinished) { + numActive -= 1; + if (numActive === 0) { + // All the sub-events are done so we're done. + handleEvent({ stage: ProgressReportStage.discoveryFinished }); + } + } else { + handleEvent({ stage: e.stage }); + } + } else { + handleEvent(e); } - } else { - emitter.fire(e); - } + }); + disposables.push(disposable); }); - }); - result.onUpdated = emitter.event; + return disposables; + }; return result; } @@ -48,56 +60,19 @@ export function combineIterators(iterators: IPythonEnvsIterator[]): IPythonEnvsI * * Events and iterator results are combined. */ -export class Locators extends PythonEnvsWatchers implements ILocator { +export class Locators extends PythonEnvsWatchers implements ICompositeLocator { + public readonly providerId: string; + constructor( // The locators will be watched as well as iterated. - private readonly locators: ReadonlyArray + private readonly locators: ReadonlyArray>, ) { super(locators); + this.providerId = locators.map((loc) => loc.providerId).join('+'); } - public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { + public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { const iterators = this.locators.map((loc) => loc.iterEnvs(query)); return combineIterators(iterators); } - - public async resolveEnv(env: string | PythonEnvInfo): Promise { - for (const locator of this.locators) { - const resolved = await locator.resolveEnv(env); - if (resolved !== undefined) { - return resolved; - } - } - return undefined; - } -} - -/** - * A locator wrapper that can be disabled. - * - * If disabled, events emitted by the wrapped locator are discarded, - * `iterEnvs()` yields nothing, and `resolveEnv()` already returns - * `undefined`. - */ -export class DisableableLocator extends DisableableEnvsWatcher implements ILocator { - constructor( - // To wrapp more than one use `Locators`. - private readonly locator: ILocator - ) { - super(locator); - } - - public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { - if (!this.enabled) { - return NOOP_ITERATOR; - } - return this.locator.iterEnvs(query); - } - - public async resolveEnv(env: string | PythonEnvInfo): Promise { - if (!this.enabled) { - return undefined; - } - return this.locator.resolveEnv(env); - } } diff --git a/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts new file mode 100644 index 000000000000..ea0d63cd7552 --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts @@ -0,0 +1,541 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { Disposable, EventEmitter, Event, Uri } from 'vscode'; +import * as ch from 'child_process'; +import * as path from 'path'; +import * as rpc from 'vscode-jsonrpc/node'; +import { PassThrough } from 'stream'; +import * as fs from '../../../../common/platform/fs-paths'; +import { isWindows, getUserHomeDir } from '../../../../common/utils/platform'; +import { EXTENSION_ROOT_DIR } from '../../../../constants'; +import { createDeferred, createDeferredFrom } from '../../../../common/utils/async'; +import { DisposableBase, DisposableStore } from '../../../../common/utils/resourceLifecycle'; +import { noop } from '../../../../common/utils/misc'; +import { getConfiguration, getWorkspaceFolderPaths, isTrusted } from '../../../../common/vscodeApis/workspaceApis'; +import { CONDAPATH_SETTING_KEY } from '../../../common/environmentManagers/conda'; +import { VENVFOLDERS_SETTING_KEY, VENVPATH_SETTING_KEY } from '../lowLevel/customVirtualEnvLocator'; +import { createLogOutputChannel, showWarningMessage } from '../../../../common/vscodeApis/windowApis'; +import { sendNativeTelemetry, NativePythonTelemetry } from './nativePythonTelemetry'; +import { NativePythonEnvironmentKind } from './nativePythonUtils'; +import type { IExtensionContext } from '../../../../common/types'; +import { StopWatch } from '../../../../common/utils/stopWatch'; +import { untildify } from '../../../../common/helpers'; +import { traceError } from '../../../../logging'; +import { Common, PythonLocator } from '../../../../common/utils/localize'; +import { Commands } from '../../../../common/constants'; +import { executeCommand } from '../../../../common/vscodeApis/commandApis'; +import { getGlobalStorage, IPersistentStorage } from '../../../../common/persistentState'; + +const PYTHON_ENV_TOOLS_PATH = isWindows() + ? path.join(EXTENSION_ROOT_DIR, 'python-env-tools', 'bin', 'pet.exe') + : path.join(EXTENSION_ROOT_DIR, 'python-env-tools', 'bin', 'pet'); + +const DONT_SHOW_SPAWN_ERROR_AGAIN = 'DONT_SHOW_NATIVE_FINDER_SPAWN_ERROR_AGAIN'; + +export interface NativeEnvInfo { + displayName?: string; + name?: string; + executable?: string; + kind?: NativePythonEnvironmentKind; + version?: string; + prefix?: string; + manager?: NativeEnvManagerInfo; + /** + * Path to the project directory when dealing with pipenv virtual environments. + */ + project?: string; + arch?: 'x64' | 'x86'; + symlinks?: string[]; +} + +export interface NativeEnvManagerInfo { + tool: string; + executable: string; + version?: string; +} + +export function isNativeEnvInfo(info: NativeEnvInfo | NativeEnvManagerInfo): info is NativeEnvInfo { + if ((info as NativeEnvManagerInfo).tool) { + return false; + } + return true; +} + +export type NativeCondaInfo = { + canSpawnConda: boolean; + userProvidedEnvFound?: boolean; + condaRcs: string[]; + envDirs: string[]; + environmentsTxt?: string; + environmentsTxtExists?: boolean; + environmentsFromTxt: string[]; +}; + +export interface NativePythonFinder extends Disposable { + /** + * Refresh the list of python environments. + * Returns an async iterable that can be used to iterate over the list of python environments. + * Internally this will take all of the current workspace folders and search for python environments. + * + * If a Uri is provided, then it will search for python environments in that location (ignoring workspaces). + * Uri can be a file or a folder. + * If a NativePythonEnvironmentKind is provided, then it will search for python environments of that kind (ignoring workspaces). + */ + refresh(options?: NativePythonEnvironmentKind | Uri[]): AsyncIterable; + /** + * Will spawn the provided Python executable and return information about the environment. + * @param executable + */ + resolve(executable: string): Promise; + /** + * Used only for telemetry. + */ + getCondaInfo(): Promise; +} + +interface NativeLog { + level: string; + message: string; +} + +class NativePythonFinderImpl extends DisposableBase implements NativePythonFinder { + private readonly connection: rpc.MessageConnection; + + private firstRefreshResults: undefined | (() => AsyncGenerator); + + private readonly outputChannel = this._register(createLogOutputChannel('Python Locator', { log: true })); + + private initialRefreshMetrics = { + timeToSpawn: 0, + timeToConfigure: 0, + timeToRefresh: 0, + }; + + private readonly suppressErrorNotification: IPersistentStorage; + + constructor(private readonly cacheDirectory?: Uri, private readonly context?: IExtensionContext) { + super(); + this.suppressErrorNotification = this.context + ? getGlobalStorage(this.context, DONT_SHOW_SPAWN_ERROR_AGAIN, false) + : ({ get: () => false, set: async () => {} } as IPersistentStorage); + this.connection = this.start(); + void this.configure(); + this.firstRefreshResults = this.refreshFirstTime(); + } + + public async resolve(executable: string): Promise { + await this.configure(); + const environment = await this.connection.sendRequest('resolve', { + executable, + }); + + this.outputChannel.info(`Resolved Python Environment ${environment.executable}`); + return environment; + } + + async *refresh(options?: NativePythonEnvironmentKind | Uri[]): AsyncIterable { + if (this.firstRefreshResults) { + // If this is the first time we are refreshing, + // Then get the results from the first refresh. + // Those would have started earlier and cached in memory. + const results = this.firstRefreshResults(); + this.firstRefreshResults = undefined; + yield* results; + } else { + const result = this.doRefresh(options); + let completed = false; + void result.completed.finally(() => { + completed = true; + }); + const envs: (NativeEnvInfo | NativeEnvManagerInfo)[] = []; + let discovered = createDeferred(); + const disposable = result.discovered((data) => { + envs.push(data); + discovered.resolve(); + }); + do { + if (!envs.length) { + await Promise.race([result.completed, discovered.promise]); + } + if (envs.length) { + const dataToSend = [...envs]; + envs.length = 0; + for (const data of dataToSend) { + yield data; + } + } + if (!completed) { + discovered = createDeferred(); + } + } while (!completed); + disposable.dispose(); + } + } + + refreshFirstTime() { + const result = this.doRefresh(); + const completed = createDeferredFrom(result.completed); + const envs: NativeEnvInfo[] = []; + let discovered = createDeferred(); + const disposable = result.discovered((data) => { + envs.push(data); + discovered.resolve(); + }); + + const iterable = async function* () { + do { + if (!envs.length) { + await Promise.race([completed.promise, discovered.promise]); + } + if (envs.length) { + const dataToSend = [...envs]; + envs.length = 0; + for (const data of dataToSend) { + yield data; + } + } + if (!completed.completed) { + discovered = createDeferred(); + } + } while (!completed.completed); + disposable.dispose(); + }; + + return iterable.bind(this); + } + + // eslint-disable-next-line class-methods-use-this + private start(): rpc.MessageConnection { + this.outputChannel.info(`Starting Python Locator ${PYTHON_ENV_TOOLS_PATH} server`); + + // jsonrpc package cannot handle messages coming through too quickly. + // Lets handle the messages and close the stream only when + // we have got the exit event. + const readable = new PassThrough(); + const writable = new PassThrough(); + const disposables: Disposable[] = []; + try { + const stopWatch = new StopWatch(); + const proc = ch.spawn(PYTHON_ENV_TOOLS_PATH, ['server'], { env: process.env }); + this.initialRefreshMetrics.timeToSpawn = stopWatch.elapsedTime; + proc.stdout.pipe(readable, { end: false }); + proc.stderr.on('data', (data) => this.outputChannel.error(data.toString())); + writable.pipe(proc.stdin, { end: false }); + + // Handle spawn errors (e.g., missing DLLs on Windows) + proc.on('error', (error) => { + this.outputChannel.error(`Python Locator process error: ${error.message}`); + this.outputChannel.error(`Error details: ${JSON.stringify(error)}`); + this.handleSpawnError(error.message); + }); + + // Handle immediate exits with error codes + let hasStarted = false; + setTimeout(() => { + hasStarted = true; + }, 1000); + + proc.on('exit', (code, signal) => { + if (!hasStarted && code !== null && code !== 0) { + const errorMessage = `Python Locator process exited immediately with code ${code}`; + this.outputChannel.error(errorMessage); + if (signal) { + this.outputChannel.error(`Exit signal: ${signal}`); + } + this.handleSpawnError(errorMessage); + } + }); + + disposables.push({ + dispose: () => { + try { + if (proc.exitCode === null) { + proc.kill(); + } + } catch (ex) { + this.outputChannel.error('Error disposing finder', ex); + } + }, + }); + } catch (ex) { + this.outputChannel.error(`Error starting Python Finder ${PYTHON_ENV_TOOLS_PATH} server`, ex); + } + const disposeStreams = new Disposable(() => { + readable.end(); + writable.end(); + }); + const connection = rpc.createMessageConnection( + new rpc.StreamMessageReader(readable), + new rpc.StreamMessageWriter(writable), + ); + disposables.push( + connection, + disposeStreams, + connection.onError((ex) => { + disposeStreams.dispose(); + this.outputChannel.error('Connection Error:', ex); + }), + connection.onNotification('log', (data: NativeLog) => { + switch (data.level) { + case 'info': + this.outputChannel.info(data.message); + break; + case 'warning': + this.outputChannel.warn(data.message); + break; + case 'error': + this.outputChannel.error(data.message); + break; + case 'debug': + this.outputChannel.debug(data.message); + break; + default: + this.outputChannel.trace(data.message); + } + }), + connection.onNotification('telemetry', (data: NativePythonTelemetry) => + sendNativeTelemetry(data, this.initialRefreshMetrics), + ), + connection.onClose(() => { + disposables.forEach((d) => d.dispose()); + }), + ); + + connection.listen(); + this._register(Disposable.from(...disposables)); + return connection; + } + + private doRefresh( + options?: NativePythonEnvironmentKind | Uri[], + ): { completed: Promise; discovered: Event } { + const disposable = this._register(new DisposableStore()); + const discovered = disposable.add(new EventEmitter()); + const completed = createDeferred(); + const pendingPromises: Promise[] = []; + const stopWatch = new StopWatch(); + + const notifyUponCompletion = () => { + const initialCount = pendingPromises.length; + Promise.all(pendingPromises) + .then(() => { + if (initialCount === pendingPromises.length) { + completed.resolve(); + } else { + setTimeout(notifyUponCompletion, 0); + } + }) + .catch(noop); + }; + const trackPromiseAndNotifyOnCompletion = (promise: Promise) => { + pendingPromises.push(promise); + notifyUponCompletion(); + }; + + // Assumption is server will ensure there's only one refresh at a time. + // Perhaps we should have a request Id or the like to map the results back to the `refresh` request. + disposable.add( + this.connection.onNotification('environment', (data: NativeEnvInfo) => { + this.outputChannel.info(`Discovered env: ${data.executable || data.prefix}`); + // We know that in the Python extension if either Version of Prefix is not provided by locator + // Then we end up resolving the information. + // Lets do that here, + // This is a hack, as the other part of the code that resolves the version information + // doesn't work as expected, as its still a WIP. + if (data.executable && (!data.version || !data.prefix)) { + // HACK = TEMPORARY WORK AROUND, TO GET STUFF WORKING + // HACK = TEMPORARY WORK AROUND, TO GET STUFF WORKING + // HACK = TEMPORARY WORK AROUND, TO GET STUFF WORKING + // HACK = TEMPORARY WORK AROUND, TO GET STUFF WORKING + const promise = this.connection + .sendRequest('resolve', { + executable: data.executable, + }) + .then((environment) => { + this.outputChannel.info(`Resolved ${environment.executable}`); + discovered.fire(environment); + }) + .catch((ex) => this.outputChannel.error(`Error in Resolving ${JSON.stringify(data)}`, ex)); + trackPromiseAndNotifyOnCompletion(promise); + } else { + discovered.fire(data); + } + }), + ); + disposable.add( + this.connection.onNotification('manager', (data: NativeEnvManagerInfo) => { + this.outputChannel.info(`Discovered manager: (${data.tool}) ${data.executable}`); + discovered.fire(data); + }), + ); + + type RefreshOptions = { + searchKind?: NativePythonEnvironmentKind; + searchPaths?: string[]; + }; + + const refreshOptions: RefreshOptions = {}; + if (options && Array.isArray(options) && options.length > 0) { + refreshOptions.searchPaths = options.map((item) => item.fsPath); + } else if (options && typeof options === 'string') { + refreshOptions.searchKind = options; + } + trackPromiseAndNotifyOnCompletion( + this.configure().then(() => + this.connection + .sendRequest<{ duration: number }>('refresh', refreshOptions) + .then(({ duration }) => { + this.outputChannel.info(`Refresh completed in ${duration}ms`); + this.initialRefreshMetrics.timeToRefresh = stopWatch.elapsedTime; + }) + .catch((ex) => this.outputChannel.error('Refresh error', ex)), + ), + ); + + completed.promise.finally(() => disposable.dispose()); + return { + completed: completed.promise, + discovered: discovered.event, + }; + } + + private lastConfiguration?: ConfigurationOptions; + + /** + * Configuration request, this must always be invoked before any other request. + * Must be invoked when ever there are changes to any data related to the configuration details. + */ + private async configure() { + const options: ConfigurationOptions = { + workspaceDirectories: getWorkspaceFolderPaths(), + // We do not want to mix this with `search_paths` + environmentDirectories: getCustomVirtualEnvDirs(), + condaExecutable: getPythonSettingAndUntildify(CONDAPATH_SETTING_KEY), + poetryExecutable: getPythonSettingAndUntildify('poetryPath'), + cacheDirectory: this.cacheDirectory?.fsPath, + }; + // No need to send a configuration request, is there are no changes. + if (JSON.stringify(options) === JSON.stringify(this.lastConfiguration || {})) { + return; + } + try { + const stopWatch = new StopWatch(); + this.lastConfiguration = options; + await this.connection.sendRequest('configure', options); + this.initialRefreshMetrics.timeToConfigure = stopWatch.elapsedTime; + } catch (ex) { + this.outputChannel.error('Refresh error', ex); + } + } + + async getCondaInfo(): Promise { + return this.connection.sendRequest('condaInfo'); + } + + private async handleSpawnError(errorMessage: string): Promise { + // Check if user has chosen to not see this error again + if (this.suppressErrorNotification.get()) { + return; + } + + // Check for Windows runtime DLL issues + if (isWindows() && errorMessage.toLowerCase().includes('vcruntime')) { + this.outputChannel.error(PythonLocator.windowsRuntimeMissing); + } else if (isWindows()) { + this.outputChannel.error(PythonLocator.windowsStartupFailed); + } + + // Show notification to user + const selection = await showWarningMessage( + PythonLocator.startupFailedNotification, + Common.openOutputPanel, + Common.doNotShowAgain, + ); + + if (selection === Common.openOutputPanel) { + await executeCommand(Commands.ViewOutput); + } else if (selection === Common.doNotShowAgain) { + await this.suppressErrorNotification.set(true); + } + } +} + +type ConfigurationOptions = { + workspaceDirectories: string[]; + /** + * Place where virtual envs and the like are stored + * Should not contain workspace folders. + */ + environmentDirectories: string[]; + condaExecutable: string | undefined; + poetryExecutable: string | undefined; + cacheDirectory?: string; +}; +/** + * Gets all custom virtual environment locations to look for environments. + */ +function getCustomVirtualEnvDirs(): string[] { + const venvDirs: string[] = []; + const venvPath = getPythonSettingAndUntildify(VENVPATH_SETTING_KEY); + if (venvPath) { + venvDirs.push(untildify(venvPath)); + } + const venvFolders = getPythonSettingAndUntildify(VENVFOLDERS_SETTING_KEY) ?? []; + const homeDir = getUserHomeDir(); + if (homeDir) { + venvFolders + .map((item) => (item.startsWith(homeDir) ? item : path.join(homeDir, item))) + .forEach((d) => venvDirs.push(d)); + venvFolders.forEach((item) => venvDirs.push(untildify(item))); + } + return Array.from(new Set(venvDirs)); +} + +function getPythonSettingAndUntildify(name: string, scope?: Uri): T | undefined { + const value = getConfiguration('python', scope).get(name); + if (typeof value === 'string') { + return value ? ((untildify(value as string) as unknown) as T) : undefined; + } + return value; +} + +let _finder: NativePythonFinder | undefined; +export function getNativePythonFinder(context?: IExtensionContext): NativePythonFinder { + if (!isTrusted()) { + return { + async *refresh() { + traceError('Python discovery not supported in untrusted workspace'); + yield* []; + }, + async resolve() { + traceError('Python discovery not supported in untrusted workspace'); + return {}; + }, + async getCondaInfo() { + traceError('Python discovery not supported in untrusted workspace'); + return ({} as unknown) as NativeCondaInfo; + }, + dispose() { + // do nothing + }, + }; + } + if (!_finder) { + const cacheDirectory = context ? getCacheDirectory(context) : undefined; + _finder = new NativePythonFinderImpl(cacheDirectory, context); + if (context) { + context.subscriptions.push(_finder); + } + } + return _finder; +} + +export function getCacheDirectory(context: IExtensionContext): Uri { + return Uri.joinPath(context.globalStorageUri, 'pythonLocator'); +} + +export async function clearCacheDirectory(context: IExtensionContext): Promise { + const cacheDirectory = getCacheDirectory(context); + await fs.emptyDir(cacheDirectory.fsPath).catch(noop); +} diff --git a/src/client/pythonEnvironments/base/locators/common/nativePythonTelemetry.ts b/src/client/pythonEnvironments/base/locators/common/nativePythonTelemetry.ts new file mode 100644 index 000000000000..703fdfca01c3 --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/common/nativePythonTelemetry.ts @@ -0,0 +1,147 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { traceError } from '../../../../logging'; +import { sendTelemetryEvent } from '../../../../telemetry'; +import { EventName } from '../../../../telemetry/constants'; + +export type NativePythonTelemetry = MissingCondaEnvironments | MissingPoetryEnvironments | RefreshPerformance; + +export type MissingCondaEnvironments = { + event: 'MissingCondaEnvironments'; + data: { + missingCondaEnvironments: { + missing: number; + envDirsNotFound?: number; + userProvidedCondaExe?: boolean; + rootPrefixNotFound?: boolean; + condaPrefixNotFound?: boolean; + condaManagerNotFound?: boolean; + sysRcNotFound?: boolean; + userRcNotFound?: boolean; + otherRcNotFound?: boolean; + missingEnvDirsFromSysRc?: number; + missingEnvDirsFromUserRc?: number; + missingEnvDirsFromOtherRc?: number; + missingFromSysRcEnvDirs?: number; + missingFromUserRcEnvDirs?: number; + missingFromOtherRcEnvDirs?: number; + }; + }; +}; + +export type MissingPoetryEnvironments = { + event: 'MissingPoetryEnvironments'; + data: { + missingPoetryEnvironments: { + missing: number; + missingInPath: number; + userProvidedPoetryExe?: boolean; + poetryExeNotFound?: boolean; + globalConfigNotFound?: boolean; + cacheDirNotFound?: boolean; + cacheDirIsDifferent?: boolean; + virtualenvsPathNotFound?: boolean; + virtualenvsPathIsDifferent?: boolean; + inProjectIsDifferent?: boolean; + }; + }; +}; + +export type RefreshPerformance = { + event: 'RefreshPerformance'; + data: { + refreshPerformance: { + total: number; + breakdown: { + Locators: number; + Path: number; + GlobalVirtualEnvs: number; + Workspaces: number; + }; + locators: { + Conda?: number; + Homebrew?: number; + LinuxGlobalPython?: number; + MacCmdLineTools?: number; + MacPythonOrg?: number; + MacXCode?: number; + PipEnv?: number; + PixiEnv?: number; + Poetry?: number; + PyEnv?: number; + Venv?: number; + VirtualEnv?: number; + VirtualEnvWrapper?: number; + WindowsRegistry?: number; + WindowsStore?: number; + }; + }; + }; +}; + +let refreshTelemetrySent = false; + +export function sendNativeTelemetry( + data: NativePythonTelemetry, + initialRefreshMetrics: { + timeToSpawn: number; + timeToConfigure: number; + timeToRefresh: number; + }, +): void { + switch (data.event) { + case 'MissingCondaEnvironments': { + sendTelemetryEvent( + EventName.NATIVE_FINDER_MISSING_CONDA_ENVS, + undefined, + data.data.missingCondaEnvironments, + ); + break; + } + case 'MissingPoetryEnvironments': { + sendTelemetryEvent( + EventName.NATIVE_FINDER_MISSING_POETRY_ENVS, + undefined, + data.data.missingPoetryEnvironments, + ); + break; + } + case 'RefreshPerformance': { + if (refreshTelemetrySent) { + break; + } + refreshTelemetrySent = true; + sendTelemetryEvent(EventName.NATIVE_FINDER_PERF, { + duration: data.data.refreshPerformance.total, + totalDuration: data.data.refreshPerformance.total, + breakdownGlobalVirtualEnvs: data.data.refreshPerformance.breakdown.GlobalVirtualEnvs, + breakdownLocators: data.data.refreshPerformance.breakdown.Locators, + breakdownPath: data.data.refreshPerformance.breakdown.Path, + breakdownWorkspaces: data.data.refreshPerformance.breakdown.Workspaces, + locatorConda: data.data.refreshPerformance.locators.Conda || 0, + locatorHomebrew: data.data.refreshPerformance.locators.Homebrew || 0, + locatorLinuxGlobalPython: data.data.refreshPerformance.locators.LinuxGlobalPython || 0, + locatorMacCmdLineTools: data.data.refreshPerformance.locators.MacCmdLineTools || 0, + locatorMacPythonOrg: data.data.refreshPerformance.locators.MacPythonOrg || 0, + locatorMacXCode: data.data.refreshPerformance.locators.MacXCode || 0, + locatorPipEnv: data.data.refreshPerformance.locators.PipEnv || 0, + locatorPixiEnv: data.data.refreshPerformance.locators.PixiEnv || 0, + locatorPoetry: data.data.refreshPerformance.locators.Poetry || 0, + locatorPyEnv: data.data.refreshPerformance.locators.PyEnv || 0, + locatorVenv: data.data.refreshPerformance.locators.Venv || 0, + locatorVirtualEnv: data.data.refreshPerformance.locators.VirtualEnv || 0, + locatorVirtualEnvWrapper: data.data.refreshPerformance.locators.VirtualEnvWrapper || 0, + locatorWindowsRegistry: data.data.refreshPerformance.locators.WindowsRegistry || 0, + locatorWindowsStore: data.data.refreshPerformance.locators.WindowsStore || 0, + timeToSpawn: initialRefreshMetrics.timeToSpawn, + timeToConfigure: initialRefreshMetrics.timeToConfigure, + timeToRefresh: initialRefreshMetrics.timeToRefresh, + }); + break; + } + default: { + traceError(`Unhandled Telemetry Event type ${JSON.stringify(data)}`); + } + } +} diff --git a/src/client/pythonEnvironments/base/locators/common/nativePythonUtils.ts b/src/client/pythonEnvironments/base/locators/common/nativePythonUtils.ts new file mode 100644 index 000000000000..716bdd444633 --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/common/nativePythonUtils.ts @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { LogOutputChannel } from 'vscode'; +import { PythonEnvKind } from '../../info'; +import { traceError } from '../../../../logging'; + +export enum NativePythonEnvironmentKind { + Conda = 'Conda', + Pixi = 'Pixi', + Homebrew = 'Homebrew', + Pyenv = 'Pyenv', + GlobalPaths = 'GlobalPaths', + PyenvVirtualEnv = 'PyenvVirtualEnv', + Pipenv = 'Pipenv', + Poetry = 'Poetry', + MacPythonOrg = 'MacPythonOrg', + MacCommandLineTools = 'MacCommandLineTools', + LinuxGlobal = 'LinuxGlobal', + MacXCode = 'MacXCode', + Venv = 'Venv', + VirtualEnv = 'VirtualEnv', + VirtualEnvWrapper = 'VirtualEnvWrapper', + WindowsStore = 'WindowsStore', + WindowsRegistry = 'WindowsRegistry', + VenvUv = 'Uv', +} + +const mapping = new Map([ + [NativePythonEnvironmentKind.Conda, PythonEnvKind.Conda], + [NativePythonEnvironmentKind.Pixi, PythonEnvKind.Pixi], + [NativePythonEnvironmentKind.GlobalPaths, PythonEnvKind.OtherGlobal], + [NativePythonEnvironmentKind.Pyenv, PythonEnvKind.Pyenv], + [NativePythonEnvironmentKind.PyenvVirtualEnv, PythonEnvKind.Pyenv], + [NativePythonEnvironmentKind.Pipenv, PythonEnvKind.Pipenv], + [NativePythonEnvironmentKind.Poetry, PythonEnvKind.Poetry], + [NativePythonEnvironmentKind.VirtualEnv, PythonEnvKind.VirtualEnv], + [NativePythonEnvironmentKind.VirtualEnvWrapper, PythonEnvKind.VirtualEnvWrapper], + [NativePythonEnvironmentKind.Venv, PythonEnvKind.Venv], + [NativePythonEnvironmentKind.VenvUv, PythonEnvKind.Venv], + [NativePythonEnvironmentKind.WindowsRegistry, PythonEnvKind.System], + [NativePythonEnvironmentKind.WindowsStore, PythonEnvKind.MicrosoftStore], + [NativePythonEnvironmentKind.Homebrew, PythonEnvKind.System], + [NativePythonEnvironmentKind.LinuxGlobal, PythonEnvKind.System], + [NativePythonEnvironmentKind.MacCommandLineTools, PythonEnvKind.System], + [NativePythonEnvironmentKind.MacPythonOrg, PythonEnvKind.System], + [NativePythonEnvironmentKind.MacXCode, PythonEnvKind.System], +]); + +export function categoryToKind(category?: NativePythonEnvironmentKind, logger?: LogOutputChannel): PythonEnvKind { + if (!category) { + return PythonEnvKind.Unknown; + } + const kind = mapping.get(category); + if (kind) { + return kind; + } + + if (logger) { + logger.error(`Unknown Python Environment category '${category}' from Native Locator.`); + } else { + traceError(`Unknown Python Environment category '${category}' from Native Locator.`); + } + return PythonEnvKind.Unknown; +} diff --git a/src/client/pythonEnvironments/base/locators/common/pythonWatcher.ts b/src/client/pythonEnvironments/base/locators/common/pythonWatcher.ts new file mode 100644 index 000000000000..378a0d6c521e --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/common/pythonWatcher.ts @@ -0,0 +1,146 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { Disposable, Event, EventEmitter, GlobPattern, RelativePattern, Uri, WorkspaceFolder } from 'vscode'; +import { createFileSystemWatcher, getWorkspaceFolder } from '../../../../common/vscodeApis/workspaceApis'; +import { isWindows } from '../../../../common/utils/platform'; +import { arePathsSame } from '../../../common/externalDependencies'; +import { FileChangeType } from '../../../../common/platform/fileSystemWatcher'; + +export interface PythonWorkspaceEnvEvent { + type: FileChangeType; + workspaceFolder: WorkspaceFolder; + executable: string; +} + +export interface PythonGlobalEnvEvent { + type: FileChangeType; + uri: Uri; +} + +export interface PythonWatcher extends Disposable { + watchWorkspace(wf: WorkspaceFolder): void; + unwatchWorkspace(wf: WorkspaceFolder): void; + onDidWorkspaceEnvChanged: Event; + + watchPath(uri: Uri, pattern?: string): void; + unwatchPath(uri: Uri): void; + onDidGlobalEnvChanged: Event; +} + +/* + * The pattern to search for python executables in the workspace. + * project + * ├── python or python.exe <--- This is what we are looking for. + * ├── .conda + * │ └── python or python.exe <--- This is what we are looking for. + * └── .venv + * │ └── Scripts or bin + * │ └── python or python.exe <--- This is what we are looking for. + */ +const WORKSPACE_PATTERN = isWindows() ? '**/python.exe' : '**/python'; + +class PythonWatcherImpl implements PythonWatcher { + private disposables: Disposable[] = []; + + private readonly _onDidWorkspaceEnvChanged = new EventEmitter(); + + private readonly _onDidGlobalEnvChanged = new EventEmitter(); + + private readonly _disposeMap: Map = new Map(); + + constructor() { + this.disposables.push(this._onDidWorkspaceEnvChanged, this._onDidGlobalEnvChanged); + } + + onDidGlobalEnvChanged: Event = this._onDidGlobalEnvChanged.event; + + onDidWorkspaceEnvChanged: Event = this._onDidWorkspaceEnvChanged.event; + + watchWorkspace(wf: WorkspaceFolder): void { + if (this._disposeMap.has(wf.uri.fsPath)) { + const disposer = this._disposeMap.get(wf.uri.fsPath); + disposer?.dispose(); + } + + const disposables: Disposable[] = []; + const watcher = createFileSystemWatcher(new RelativePattern(wf, WORKSPACE_PATTERN)); + disposables.push( + watcher, + watcher.onDidChange((uri) => { + this.fireWorkspaceEvent(FileChangeType.Changed, wf, uri); + }), + watcher.onDidCreate((uri) => { + this.fireWorkspaceEvent(FileChangeType.Created, wf, uri); + }), + watcher.onDidDelete((uri) => { + this.fireWorkspaceEvent(FileChangeType.Deleted, wf, uri); + }), + ); + + const disposable = { + dispose: () => { + disposables.forEach((d) => d.dispose()); + this._disposeMap.delete(wf.uri.fsPath); + }, + }; + this._disposeMap.set(wf.uri.fsPath, disposable); + } + + unwatchWorkspace(wf: WorkspaceFolder): void { + const disposable = this._disposeMap.get(wf.uri.fsPath); + disposable?.dispose(); + } + + private fireWorkspaceEvent(type: FileChangeType, wf: WorkspaceFolder, uri: Uri) { + const uriWorkspace = getWorkspaceFolder(uri); + if (uriWorkspace && arePathsSame(uriWorkspace.uri.fsPath, wf.uri.fsPath)) { + this._onDidWorkspaceEnvChanged.fire({ type, workspaceFolder: wf, executable: uri.fsPath }); + } + } + + watchPath(uri: Uri, pattern?: string): void { + if (this._disposeMap.has(uri.fsPath)) { + const disposer = this._disposeMap.get(uri.fsPath); + disposer?.dispose(); + } + + const glob: GlobPattern = pattern ? new RelativePattern(uri, pattern) : uri.fsPath; + const disposables: Disposable[] = []; + const watcher = createFileSystemWatcher(glob); + disposables.push( + watcher, + watcher.onDidChange(() => { + this._onDidGlobalEnvChanged.fire({ type: FileChangeType.Changed, uri }); + }), + watcher.onDidCreate(() => { + this._onDidGlobalEnvChanged.fire({ type: FileChangeType.Created, uri }); + }), + watcher.onDidDelete(() => { + this._onDidGlobalEnvChanged.fire({ type: FileChangeType.Deleted, uri }); + }), + ); + + const disposable = { + dispose: () => { + disposables.forEach((d) => d.dispose()); + this._disposeMap.delete(uri.fsPath); + }, + }; + this._disposeMap.set(uri.fsPath, disposable); + } + + unwatchPath(uri: Uri): void { + const disposable = this._disposeMap.get(uri.fsPath); + disposable?.dispose(); + } + + dispose() { + this.disposables.forEach((d) => d.dispose()); + this._disposeMap.forEach((d) => d.dispose()); + } +} + +export function createPythonWatcher(): PythonWatcher { + return new PythonWatcherImpl(); +} diff --git a/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts b/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts new file mode 100644 index 000000000000..8b56b4c7b8c1 --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/common/resourceBasedLocator.ts @@ -0,0 +1,140 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { IDisposable } from '../../../../common/types'; +import { createDeferred, Deferred } from '../../../../common/utils/async'; +import { Disposables } from '../../../../common/utils/resourceLifecycle'; +import { traceError, traceWarn } from '../../../../logging'; +import { arePathsSame, isVirtualWorkspace } from '../../../common/externalDependencies'; +import { getEnvPath } from '../../info/env'; +import { BasicEnvInfo, IPythonEnvsIterator, Locator, PythonLocatorQuery } from '../../locator'; + +/** + * A base locator class that manages the lifecycle of resources. + * + * The resources are not initialized until needed. + * + * It is critical that each subclass properly add its resources + * to the list: + * + * this.disposables.push(someResource); + * + * Otherwise it will leak (and we have no leak detection). + */ +export abstract class LazyResourceBasedLocator extends Locator implements IDisposable { + protected readonly disposables = new Disposables(); + + // This will be set only once we have to create necessary resources + // and resolves once those resources are ready. + private resourcesReady?: Deferred; + + private watchersReady?: Deferred; + + /** + * This can be used to initialize resources when subclasses are created. + */ + protected async activate(): Promise { + await this.ensureResourcesReady(); + // There is not need to wait for the watchers to get started. + try { + this.ensureWatchersReady(); + } catch (ex) { + traceWarn(`Failed to ensure watchers are ready for locator ${this.constructor.name}`, ex); + } + } + + public async dispose(): Promise { + await this.disposables.dispose(); + } + + public async *iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { + await this.activate(); + const iterator = this.doIterEnvs(query); + if (query?.envPath) { + let result = await iterator.next(); + while (!result.done) { + const currEnv = result.value; + const { path } = getEnvPath(currEnv.executablePath, currEnv.envPath); + if (arePathsSame(path, query.envPath)) { + yield currEnv; + break; + } + result = await iterator.next(); + } + } else { + yield* iterator; + } + } + + /** + * The subclass implementation of iterEnvs(). + */ + protected abstract doIterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator; + + /** + * This is where subclasses get their resources ready. + * + * It is only called once resources are needed. + * + * Each subclass is responsible to add its resources to the list + * (otherwise it leaks): + * + * this.disposables.push(someResource); + * + * Not all locators have resources other than watchers so a default + * implementation is provided. + */ + // eslint-disable-next-line class-methods-use-this + protected async initResources(): Promise { + // No resources! + } + + /** + * This is where subclasses get their watchers ready. + * + * It is only called with the first `iterEnvs()` call, + * after `initResources()` has been called. + * + * Each subclass is responsible to add its resources to the list + * (otherwise it leaks): + * + * this.disposables.push(someResource); + * + * Not all locators have watchers to init so a default + * implementation is provided. + */ + // eslint-disable-next-line class-methods-use-this + protected async initWatchers(): Promise { + // No watchers! + } + + protected async ensureResourcesReady(): Promise { + if (this.resourcesReady !== undefined) { + await this.resourcesReady.promise; + return; + } + this.resourcesReady = createDeferred(); + await this.initResources().catch((ex) => { + traceError(ex); + this.resourcesReady?.reject(ex); + }); + this.resourcesReady.resolve(); + } + + private async ensureWatchersReady(): Promise { + if (this.watchersReady !== undefined) { + await this.watchersReady.promise; + return; + } + this.watchersReady = createDeferred(); + + // Don't create any file watchers in a virtual workspace. + if (!isVirtualWorkspace()) { + await this.initWatchers().catch((ex) => { + traceError(ex); + this.watchersReady?.reject(ex); + }); + } + this.watchersReady.resolve(); + } +} diff --git a/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts b/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts deleted file mode 100644 index 8e10c163b666..000000000000 --- a/src/client/pythonEnvironments/base/locators/composite/environmentsReducer.ts +++ /dev/null @@ -1,162 +0,0 @@ -// 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 { PythonEnvInfo, PythonEnvKind } from '../../info'; -import { areSameEnv } from '../../info/env'; -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 && areSameEnv(environment, event.update)) { - environment = event.update; - } - }); - let result = await iterator.next(); - while (!result.done) { - if (areSameEnv(result.value, env)) { - environment = result.value; - } - 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 if (seen[event.index] !== undefined) { - state.pending += 1; - resolveDifferencesInBackground(event.index, event.update, state, didUpdate, seen) - .ignoreErrors(); - } else { - // This implies a problem in a downstream locator - traceVerbose(`Expected already iterated env, got ${event.old} (#${event.index})`); - } - }); - } - - let result = await iterator.next(); - while (!result.done) { - const currEnv = result.value; - const oldIndex = seen.findIndex((s) => areSameEnv(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); - } - 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)) { - seen[oldIndex] = merged; - didUpdate.fire({ index: oldIndex, old: oldEnv, update: 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 deleted file mode 100644 index 6d59e4c02a43..000000000000 --- a/src/client/pythonEnvironments/base/locators/composite/environmentsResolver.ts +++ /dev/null @@ -1,134 +0,0 @@ -// 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 { 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 if (seen[event.index] !== undefined) { - seen[event.index] = event.update; - state.pending += 1; - this.resolveInBackground(event.index, state, didUpdate, seen) - .ignoreErrors(); - } else { - // This implies a problem in a downstream locator - traceVerbose(`Expected already iterated env, got ${event.old} (#${event.index})`); - } - }); - } - - 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(); - 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]); - const old = seen[envIndex]; - seen[envIndex] = resolvedEnv; - didUpdate.fire({ old, index: envIndex, update: 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/client/pythonEnvironments/base/locators/composite/envsCollectionCache.ts b/src/client/pythonEnvironments/base/locators/composite/envsCollectionCache.ts new file mode 100644 index 000000000000..456e8adfa9a4 --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/composite/envsCollectionCache.ts @@ -0,0 +1,279 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { Event } from 'vscode'; +import { isTestExecution } from '../../../../common/constants'; +import { traceVerbose } from '../../../../logging'; +import { arePathsSame, getFileInfo, pathExists } from '../../../common/externalDependencies'; +import { PythonEnvInfo, PythonEnvKind } from '../../info'; +import { areEnvsDeepEqual, areSameEnv, getEnvPath } from '../../info/env'; +import { + BasicPythonEnvCollectionChangedEvent, + PythonEnvCollectionChangedEvent, + PythonEnvsWatcher, +} from '../../watcher'; +import { getCondaInterpreterPath } from '../../../common/environmentManagers/conda'; + +export interface IEnvsCollectionCache { + /** + * Return all environment info currently in memory for this session. + */ + getAllEnvs(): PythonEnvInfo[]; + + /** + * Updates environment in cache using the value provided. + * If no new value is provided, remove the existing value from cache. + */ + updateEnv(oldValue: PythonEnvInfo, newValue: PythonEnvInfo | undefined): void; + + /** + * Fires with details if the cache changes. + */ + onChanged: Event; + + /** + * Adds environment to cache. + */ + addEnv(env: PythonEnvInfo, hasLatestInfo?: boolean): void; + + /** + * Return cached environment information for a given path if it exists and + * is up to date, otherwise return `undefined`. + * + * @param path - Python executable path or path to environment + */ + getLatestInfo(path: string): Promise; + + /** + * Writes the content of the in-memory cache to persistent storage. It is assumed + * all envs have upto date info when this is called. + */ + flush(): Promise; + + /** + * Removes invalid envs from cache. Note this does not check for outdated info when + * validating cache. + * @param envs Carries list of envs for the latest refresh. + * @param isCompleteList Carries whether the list of envs is complete or not. + */ + validateCache(envs?: PythonEnvInfo[], isCompleteList?: boolean): Promise; +} + +interface IPersistentStorage { + get(): PythonEnvInfo[]; + store(envs: PythonEnvInfo[]): Promise; +} + +/** + * Environment info cache using persistent storage to save and retrieve pre-cached env info. + */ +export class PythonEnvInfoCache extends PythonEnvsWatcher + implements IEnvsCollectionCache { + private envs: PythonEnvInfo[] = []; + + /** + * Carries the list of envs which have been validated to have latest info. + */ + private validatedEnvs = new Set(); + + /** + * Carries the list of envs which have been flushed to persistent storage. + * It signifies that the env info is likely up-to-date. + */ + private flushedEnvs = new Set(); + + constructor(private readonly persistentStorage: IPersistentStorage) { + super(); + } + + public async validateCache(envs?: PythonEnvInfo[], isCompleteList?: boolean): Promise { + /** + * We do check if an env has updated as we already run discovery in background + * which means env cache will have up-to-date envs eventually. This also means + * we avoid the cost of running lstat. So simply remove envs which are no longer + * valid. + */ + const areEnvsValid = await Promise.all( + this.envs.map(async (cachedEnv) => { + const { path } = getEnvPath(cachedEnv.executable.filename, cachedEnv.location); + if (await pathExists(path)) { + if (envs && isCompleteList) { + /** + * Only consider a cached env to be valid if it's relevant. That means: + * * It is relevant for some other workspace folder which is not opened currently. + * * It is either reported in the latest complete discovery for this session. + * * It is provided by the consumer themselves. + */ + if (cachedEnv.searchLocation) { + return true; + } + if (envs.some((env) => cachedEnv.id === env.id)) { + return true; + } + if (Array.from(this.validatedEnvs.keys()).some((envId) => cachedEnv.id === envId)) { + // These envs are provided by the consumer themselves, consider them valid. + return true; + } + } else { + return true; + } + } + return false; + }), + ); + const invalidIndexes = areEnvsValid + .map((isValid, index) => (isValid ? -1 : index)) + .filter((i) => i !== -1) + .reverse(); // Reversed so indexes do not change when deleting + invalidIndexes.forEach((index) => { + const env = this.envs.splice(index, 1)[0]; + traceVerbose(`Removing invalid env from cache ${env.id}`); + this.fire({ old: env, new: undefined }); + }); + if (envs) { + // See if any env has updated after the last refresh and fire events. + envs.forEach((env) => { + const cachedEnv = this.envs.find((e) => e.id === env.id); + if (cachedEnv && !areEnvsDeepEqual(cachedEnv, env)) { + this.updateEnv(cachedEnv, env, true); + } + }); + } + } + + public getAllEnvs(): PythonEnvInfo[] { + return this.envs; + } + + public addEnv(env: PythonEnvInfo, hasLatestInfo?: boolean): void { + const found = this.envs.find((e) => areSameEnv(e, env)); + if (!found) { + this.envs.push(env); + this.fire({ new: env }); + } else if (hasLatestInfo && !this.validatedEnvs.has(env.id!)) { + // Update cache if we have latest info and the env is not already validated. + this.updateEnv(found, env, true); + } + if (hasLatestInfo) { + traceVerbose(`Flushing env to cache ${env.id}`); + this.validatedEnvs.add(env.id!); + this.flush(env).ignoreErrors(); // If we have latest info, flush it so it can be saved. + } + } + + public updateEnv(oldValue: PythonEnvInfo, newValue: PythonEnvInfo | undefined, forceUpdate = false): void { + if (this.flushedEnvs.has(oldValue.id!) && !forceUpdate) { + // We have already flushed this env to persistent storage, so it likely has upto date info. + // If we have latest info, then we do not need to update the cache. + return; + } + const index = this.envs.findIndex((e) => areSameEnv(e, oldValue)); + if (index !== -1) { + if (newValue === undefined) { + this.envs.splice(index, 1); + } else { + this.envs[index] = newValue; + } + this.fire({ old: oldValue, new: newValue }); + } + } + + public async getLatestInfo(path: string): Promise { + // `path` can either be path to environment or executable path + const env = this.envs.find((e) => arePathsSame(e.location, path)) ?? this.envs.find((e) => areSameEnv(e, path)); + if ( + env?.kind === PythonEnvKind.Conda && + getEnvPath(env.executable.filename, env.location).pathType === 'envFolderPath' + ) { + if (await pathExists(getCondaInterpreterPath(env.location))) { + // This is a conda env without python in cache which actually now has a valid python, so return + // `undefined` and delete value from cache as cached value is not the latest anymore. + this.validatedEnvs.delete(env.id!); + return undefined; + } + // Do not attempt to validate these envs as they lack an executable, and consider them as validated by default. + this.validatedEnvs.add(env.id!); + return env; + } + if (env) { + if (this.validatedEnvs.has(env.id!)) { + traceVerbose(`Found cached env for ${path}`); + return env; + } + if (await this.validateInfo(env)) { + traceVerbose(`Needed to validate ${path} with latest info`); + this.validatedEnvs.add(env.id!); + return env; + } + } + traceVerbose(`No cached env found for ${path}`); + return undefined; + } + + public clearAndReloadFromStorage(): void { + this.envs = this.persistentStorage.get(); + this.markAllEnvsAsFlushed(); + } + + public async flush(env?: PythonEnvInfo): Promise { + if (env) { + // Flush only the given env. + const envs = this.persistentStorage.get(); + const index = envs.findIndex((e) => e.id === env.id); + envs[index] = env; + this.flushedEnvs.add(env.id!); + await this.persistentStorage.store(envs); + return; + } + traceVerbose('Environments added to cache', JSON.stringify(this.envs)); + this.markAllEnvsAsFlushed(); + await this.persistentStorage.store(this.envs); + } + + private markAllEnvsAsFlushed(): void { + this.envs.forEach((e) => { + this.flushedEnvs.add(e.id!); + }); + } + + /** + * Ensure environment has complete and latest information. + */ + private async validateInfo(env: PythonEnvInfo) { + // Make sure any previously flushed information is upto date by ensuring environment did not change. + if (!this.flushedEnvs.has(env.id!)) { + // Any environment with complete information is flushed, so this env does not contain complete info. + return false; + } + if (env.version.micro === -1 || env.version.major === -1 || env.version.minor === -1) { + // Env should not contain incomplete versions. + return false; + } + const { ctime, mtime } = await getFileInfo(env.executable.filename); + if (ctime !== -1 && mtime !== -1 && ctime === env.executable.ctime && mtime === env.executable.mtime) { + return true; + } + env.executable.ctime = ctime; + env.executable.mtime = mtime; + return false; + } +} + +/** + * Build a cache of PythonEnvInfo that is ready to use. + */ +export async function createCollectionCache(storage: IPersistentStorage): Promise { + const cache = new PythonEnvInfoCache(storage); + cache.clearAndReloadFromStorage(); + await validateCache(cache); + return cache; +} + +async function validateCache(cache: PythonEnvInfoCache) { + if (isTestExecution()) { + // For purposes for test execution, block on validation so that we can determinally know when it finishes. + return cache.validateCache(); + } + // Validate in background so it doesn't block on returning the API object. + return cache.validateCache().ignoreErrors(); +} diff --git a/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts b/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts new file mode 100644 index 000000000000..25ceb267da85 --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/composite/envsCollectionService.ts @@ -0,0 +1,309 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { Event, EventEmitter } from 'vscode'; +import '../../../../common/extensions'; +import { createDeferred, Deferred } from '../../../../common/utils/async'; +import { StopWatch } from '../../../../common/utils/stopWatch'; +import { traceError, traceInfo, traceVerbose } from '../../../../logging'; +import { sendTelemetryEvent } from '../../../../telemetry'; +import { EventName } from '../../../../telemetry/constants'; +import { normalizePath } from '../../../common/externalDependencies'; +import { PythonEnvInfo, PythonEnvKind } from '../../info'; +import { getEnvPath } from '../../info/env'; +import { + GetRefreshEnvironmentsOptions, + IDiscoveryAPI, + IResolvingLocator, + isProgressEvent, + ProgressNotificationEvent, + ProgressReportStage, + PythonLocatorQuery, + TriggerRefreshOptions, +} from '../../locator'; +import { getQueryFilter } from '../../locatorUtils'; +import { PythonEnvCollectionChangedEvent, PythonEnvsWatcher } from '../../watcher'; +import { IEnvsCollectionCache } from './envsCollectionCache'; + +/** + * A service which maintains the collection of known environments. + */ +export class EnvsCollectionService extends PythonEnvsWatcher implements IDiscoveryAPI { + /** Keeps track of ongoing refreshes for various queries. */ + private refreshesPerQuery = new Map>(); + + /** Keeps track of scheduled refreshes other than the ongoing one for various queries. */ + private scheduledRefreshesPerQuery = new Map>(); + + /** Keeps track of promises which resolves when a stage has been reached */ + private progressPromises = new Map>(); + + /** Keeps track of whether a refresh has been triggered for various queries. */ + private hasRefreshFinishedForQuery = new Map(); + + private readonly progress = new EventEmitter(); + + public refreshState = ProgressReportStage.discoveryFinished; + + public get onProgress(): Event { + return this.progress.event; + } + + public getRefreshPromise(options?: GetRefreshEnvironmentsOptions): Promise | undefined { + const stage = options?.stage ?? ProgressReportStage.discoveryFinished; + return this.progressPromises.get(stage)?.promise; + } + + constructor( + private readonly cache: IEnvsCollectionCache, + private readonly locator: IResolvingLocator, + private readonly usingNativeLocator: boolean, + ) { + super(); + this.locator.onChanged((event) => { + const query: PythonLocatorQuery | undefined = event.providerId + ? { providerId: event.providerId, envPath: event.envPath } + : undefined; // We can also form a query based on the event, but skip that for simplicity. + let scheduledRefresh = this.scheduledRefreshesPerQuery.get(query); + // If there is no refresh scheduled for the query, start a new one. + if (!scheduledRefresh) { + scheduledRefresh = this.scheduleNewRefresh(query); + } + scheduledRefresh.then(() => { + // Once refresh of cache is complete, notify changes. + this.fire(event); + }); + }); + this.cache.onChanged((e) => { + this.fire(e); + }); + this.onProgress((event) => { + this.refreshState = event.stage; + // Resolve progress promise indicating the stage has been reached. + this.progressPromises.get(event.stage)?.resolve(); + this.progressPromises.delete(event.stage); + }); + } + + public async resolveEnv(path: string): Promise { + path = normalizePath(path); + // Note cache may have incomplete info when a refresh is happening. + // This API is supposed to return complete info by definition, so + // only use cache if it has complete info on an environment. + const cachedEnv = await this.cache.getLatestInfo(path); + if (cachedEnv) { + return cachedEnv; + } + const resolved = await this.locator.resolveEnv(path).catch((ex) => { + traceError(`Failed to resolve ${path}`, ex); + return undefined; + }); + traceVerbose(`Resolved ${path} using downstream locator`); + if (resolved) { + this.cache.addEnv(resolved, true); + } + return resolved; + } + + public getEnvs(query?: PythonLocatorQuery): PythonEnvInfo[] { + const cachedEnvs = this.cache.getAllEnvs(); + return query ? cachedEnvs.filter(getQueryFilter(query)) : cachedEnvs; + } + + public triggerRefresh(query?: PythonLocatorQuery, options?: TriggerRefreshOptions): Promise { + let refreshPromise = this.getRefreshPromiseForQuery(query); + if (!refreshPromise) { + if (options?.ifNotTriggerredAlready && this.hasRefreshFinished(query)) { + // Do not trigger another refresh if a refresh has previously finished. + return Promise.resolve(); + } + const stopWatch = new StopWatch(); + traceInfo(`Starting Environment refresh`); + refreshPromise = this.startRefresh(query).then(() => { + this.sendTelemetry(query, stopWatch); + traceInfo(`Environment refresh took ${stopWatch.elapsedTime} milliseconds`); + }); + } + return refreshPromise; + } + + private startRefresh(query: PythonLocatorQuery | undefined): Promise { + this.createProgressStates(query); + const promise = this.addEnvsToCacheForQuery(query); + return promise + .then(async () => { + this.resolveProgressStates(query); + }) + .catch((ex) => { + this.rejectProgressStates(query, ex); + }); + } + + private async addEnvsToCacheForQuery(query: PythonLocatorQuery | undefined) { + const iterator = this.locator.iterEnvs(query); + const seen: PythonEnvInfo[] = []; + const state = { + done: false, + pending: 0, + }; + const updatesDone = createDeferred(); + const stopWatch = new StopWatch(); + if (iterator.onUpdated !== undefined) { + const listener = iterator.onUpdated(async (event) => { + if (isProgressEvent(event)) { + switch (event.stage) { + case ProgressReportStage.discoveryFinished: + state.done = true; + listener.dispose(); + traceInfo(`Environments refresh finished (event): ${stopWatch.elapsedTime} milliseconds`); + break; + case ProgressReportStage.allPathsDiscovered: + if (!query) { + traceInfo( + `Environments refresh paths discovered (event): ${stopWatch.elapsedTime} milliseconds`, + ); + // Only mark as all paths discovered when querying for all envs. + this.progress.fire(event); + } + break; + default: + this.progress.fire(event); + } + } else if (event.index !== undefined) { + state.pending += 1; + this.cache.updateEnv(seen[event.index], event.update); + if (event.update) { + seen[event.index] = event.update; + } + state.pending -= 1; + } + if (state.done && state.pending === 0) { + updatesDone.resolve(); + } + }); + } else { + this.progress.fire({ stage: ProgressReportStage.discoveryStarted }); + updatesDone.resolve(); + } + + for await (const env of iterator) { + seen.push(env); + this.cache.addEnv(env); + } + traceInfo(`Environments refresh paths discovered: ${stopWatch.elapsedTime} milliseconds`); + await updatesDone.promise; + // If query for all envs is done, `seen` should contain the list of all envs. + await this.cache.validateCache(seen, query === undefined); + this.cache.flush().ignoreErrors(); + } + + /** + * See if we already have a refresh promise for the query going on and return it. + */ + private getRefreshPromiseForQuery(query?: PythonLocatorQuery) { + // Even if no refresh is running for this exact query, there might be other + // refreshes running for a superset of this query. For eg. the `undefined` query + // is a superset for every other query, only consider that for simplicity. + return this.refreshesPerQuery.get(query)?.promise ?? this.refreshesPerQuery.get(undefined)?.promise; + } + + private hasRefreshFinished(query?: PythonLocatorQuery) { + return this.hasRefreshFinishedForQuery.get(query) ?? this.hasRefreshFinishedForQuery.get(undefined); + } + + /** + * Ensure we trigger a fresh refresh for the query after the current refresh (if any) is done. + */ + private async scheduleNewRefresh(query?: PythonLocatorQuery): Promise { + const refreshPromise = this.getRefreshPromiseForQuery(query); + let nextRefreshPromise: Promise; + if (!refreshPromise) { + nextRefreshPromise = this.startRefresh(query); + } else { + nextRefreshPromise = refreshPromise.then(() => { + // No more scheduled refreshes for this query as we're about to start the scheduled one. + this.scheduledRefreshesPerQuery.delete(query); + this.startRefresh(query); + }); + this.scheduledRefreshesPerQuery.set(query, nextRefreshPromise); + } + return nextRefreshPromise; + } + + private createProgressStates(query: PythonLocatorQuery | undefined) { + this.refreshesPerQuery.set(query, createDeferred()); + Object.values(ProgressReportStage).forEach((stage) => { + this.progressPromises.set(stage, createDeferred()); + }); + if (ProgressReportStage.allPathsDiscovered && query) { + // Only mark as all paths discovered when querying for all envs. + this.progressPromises.delete(ProgressReportStage.allPathsDiscovered); + } + } + + private rejectProgressStates(query: PythonLocatorQuery | undefined, ex: Error) { + this.refreshesPerQuery.get(query)?.reject(ex); + this.refreshesPerQuery.delete(query); + Object.values(ProgressReportStage).forEach((stage) => { + this.progressPromises.get(stage)?.reject(ex); + this.progressPromises.delete(stage); + }); + } + + private resolveProgressStates(query: PythonLocatorQuery | undefined) { + this.refreshesPerQuery.get(query)?.resolve(); + this.refreshesPerQuery.delete(query); + // Refreshes per stage are resolved using progress events instead. + const isRefreshComplete = Array.from(this.refreshesPerQuery.values()).every((d) => d.completed); + if (isRefreshComplete) { + this.progress.fire({ stage: ProgressReportStage.discoveryFinished }); + } + } + + private sendTelemetry(query: PythonLocatorQuery | undefined, stopWatch: StopWatch) { + if (!query && !this.hasRefreshFinished(query)) { + const envs = this.cache.getAllEnvs(); + const environmentsWithoutPython = envs.filter( + (e) => getEnvPath(e.executable.filename, e.location).pathType === 'envFolderPath', + ).length; + const activeStateEnvs = envs.filter((e) => e.kind === PythonEnvKind.ActiveState).length; + const condaEnvs = envs.filter((e) => e.kind === PythonEnvKind.Conda).length; + const customEnvs = envs.filter((e) => e.kind === PythonEnvKind.Custom).length; + const hatchEnvs = envs.filter((e) => e.kind === PythonEnvKind.Hatch).length; + const microsoftStoreEnvs = envs.filter((e) => e.kind === PythonEnvKind.MicrosoftStore).length; + const otherGlobalEnvs = envs.filter((e) => e.kind === PythonEnvKind.OtherGlobal).length; + const otherVirtualEnvs = envs.filter((e) => e.kind === PythonEnvKind.OtherVirtual).length; + const pipEnvEnvs = envs.filter((e) => e.kind === PythonEnvKind.Pipenv).length; + const poetryEnvs = envs.filter((e) => e.kind === PythonEnvKind.Poetry).length; + const pyenvEnvs = envs.filter((e) => e.kind === PythonEnvKind.Pyenv).length; + const systemEnvs = envs.filter((e) => e.kind === PythonEnvKind.System).length; + const unknownEnvs = envs.filter((e) => e.kind === PythonEnvKind.Unknown).length; + const venvEnvs = envs.filter((e) => e.kind === PythonEnvKind.Venv).length; + const virtualEnvEnvs = envs.filter((e) => e.kind === PythonEnvKind.VirtualEnv).length; + const virtualEnvWrapperEnvs = envs.filter((e) => e.kind === PythonEnvKind.VirtualEnvWrapper).length; + + // Intent is to capture time taken for discovery of all envs to complete the first time. + sendTelemetryEvent(EventName.PYTHON_INTERPRETER_DISCOVERY, stopWatch.elapsedTime, { + interpreters: this.cache.getAllEnvs().length, + usingNativeLocator: this.usingNativeLocator, + environmentsWithoutPython, + activeStateEnvs, + condaEnvs, + customEnvs, + hatchEnvs, + microsoftStoreEnvs, + otherGlobalEnvs, + otherVirtualEnvs, + pipEnvEnvs, + poetryEnvs, + pyenvEnvs, + systemEnvs, + unknownEnvs, + venvEnvs, + virtualEnvEnvs, + virtualEnvWrapperEnvs, + }); + } + this.hasRefreshFinishedForQuery.set(query, true); + } +} diff --git a/src/client/pythonEnvironments/base/locators/composite/envsReducer.ts b/src/client/pythonEnvironments/base/locators/composite/envsReducer.ts new file mode 100644 index 000000000000..c3a523b2d086 --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/composite/envsReducer.ts @@ -0,0 +1,169 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { cloneDeep, isEqual, uniq } from 'lodash'; +import { Event, EventEmitter, Uri } from 'vscode'; +import { traceVerbose } from '../../../../logging'; +import { isParentPath } from '../../../common/externalDependencies'; +import { PythonEnvKind } from '../../info'; +import { areSameEnv } from '../../info/env'; +import { getPrioritizedEnvKinds } from '../../info/envKind'; +import { + BasicEnvInfo, + ICompositeLocator, + ILocator, + IPythonEnvsIterator, + isProgressEvent, + ProgressNotificationEvent, + ProgressReportStage, + 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 ICompositeLocator { + public get onChanged(): Event { + return this.parentLocator.onChanged; + } + + constructor(private readonly parentLocator: ILocator) {} + + public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { + const didUpdate = new EventEmitter | ProgressNotificationEvent>(); + const incomingIterator = this.parentLocator.iterEnvs(query); + const iterator = iterEnvsIterator(incomingIterator, didUpdate); + iterator.onUpdated = didUpdate.event; + return iterator; + } +} + +async function* iterEnvsIterator( + iterator: IPythonEnvsIterator, + didUpdate: EventEmitter | ProgressNotificationEvent>, +): IPythonEnvsIterator { + const state = { + done: false, + pending: 0, + }; + const seen: BasicEnvInfo[] = []; + + if (iterator.onUpdated !== undefined) { + const listener = iterator.onUpdated((event) => { + if (isProgressEvent(event)) { + if (event.stage === ProgressReportStage.discoveryFinished) { + state.done = true; + listener.dispose(); + } else { + didUpdate.fire(event); + } + } else if (event.update === undefined) { + throw new Error( + 'Unsupported behavior: `undefined` environment updates are not supported from downstream locators in reducer', + ); + } else if (event.index !== undefined && seen[event.index] !== undefined) { + const oldEnv = seen[event.index]; + seen[event.index] = event.update; + didUpdate.fire({ index: event.index, old: oldEnv, update: event.update }); + } else { + // This implies a problem in a downstream locator + traceVerbose(`Expected already iterated env, got ${event.old} (#${event.index})`); + } + state.pending -= 1; + checkIfFinishedAndNotify(state, didUpdate); + }); + } else { + didUpdate.fire({ stage: ProgressReportStage.discoveryStarted }); + } + + let result = await iterator.next(); + while (!result.done) { + const currEnv = result.value; + const oldIndex = seen.findIndex((s) => areSameEnv(s, currEnv)); + if (oldIndex !== -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); + } + result = await iterator.next(); + } + if (iterator.onUpdated === undefined) { + state.done = true; + checkIfFinishedAndNotify(state, didUpdate); + } +} + +async function resolveDifferencesInBackground( + oldIndex: number, + newEnv: BasicEnvInfo, + state: { done: boolean; pending: number }, + didUpdate: EventEmitter | ProgressNotificationEvent>, + seen: BasicEnvInfo[], +) { + state.pending += 1; + // It's essential we increment the pending call count before any asynchronus calls in this method. + // We want this to be run even when `resolveInBackground` is called in background. + const oldEnv = seen[oldIndex]; + const merged = resolveEnvCollision(oldEnv, newEnv); + if (!isEqual(oldEnv, merged)) { + seen[oldIndex] = merged; + didUpdate.fire({ index: oldIndex, old: oldEnv, update: 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 | ProgressNotificationEvent>, +) { + if (state.done && state.pending === 0) { + didUpdate.fire({ stage: ProgressReportStage.discoveryFinished }); + didUpdate.dispose(); + traceVerbose(`Finished with environment reducer`); + } +} + +function resolveEnvCollision(oldEnv: BasicEnvInfo, newEnv: BasicEnvInfo): BasicEnvInfo { + const [env] = sortEnvInfoByPriority(oldEnv, newEnv); + const merged = cloneDeep(env); + merged.source = uniq((oldEnv.source ?? []).concat(newEnv.source ?? [])); + merged.searchLocation = getMergedSearchLocation(oldEnv, newEnv); + return merged; +} + +function getMergedSearchLocation(oldEnv: BasicEnvInfo, newEnv: BasicEnvInfo): Uri | undefined { + if (oldEnv.searchLocation && newEnv.searchLocation) { + // Choose the deeper project path of the two, as that can be used to signify + // that the environment is related to both the projects. + if (isParentPath(oldEnv.searchLocation.fsPath, newEnv.searchLocation.fsPath)) { + return oldEnv.searchLocation; + } + if (isParentPath(newEnv.searchLocation.fsPath, oldEnv.searchLocation.fsPath)) { + return newEnv.searchLocation; + } + } + return oldEnv.searchLocation ?? newEnv.searchLocation; +} + +/** + * Selects an environment based on the environment selection priority. This should + * match the priority in the environment identifier. + */ +function sortEnvInfoByPriority(...envs: BasicEnvInfo[]): BasicEnvInfo[] { + // TODO: When we consolidate the PythonEnvKind and EnvironmentType we should have + // one location where we define priority. + const envKindByPriority: PythonEnvKind[] = getPrioritizedEnvKinds(); + return envs.sort( + (a: BasicEnvInfo, b: BasicEnvInfo) => envKindByPriority.indexOf(a.kind) - envKindByPriority.indexOf(b.kind), + ); +} diff --git a/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts new file mode 100644 index 000000000000..6bd342d14d9c --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/composite/envsResolver.ts @@ -0,0 +1,238 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { cloneDeep } from 'lodash'; +import { Event, EventEmitter } from 'vscode'; +import { isIdentifierRegistered, identifyEnvironment } from '../../../common/environmentIdentifier'; +import { IEnvironmentInfoService } from '../../info/environmentInfoService'; +import { PythonEnvInfo, PythonEnvKind } from '../../info'; +import { getEnvPath, setEnvDisplayString } from '../../info/env'; +import { InterpreterInformation } from '../../info/interpreter'; +import { + BasicEnvInfo, + ICompositeLocator, + IPythonEnvsIterator, + IResolvingLocator, + isProgressEvent, + ProgressNotificationEvent, + ProgressReportStage, + PythonEnvUpdatedEvent, + PythonLocatorQuery, +} from '../../locator'; +import { PythonEnvsChangedEvent } from '../../watcher'; +import { resolveBasicEnv } from './resolverUtils'; +import { traceVerbose, traceWarn } from '../../../../logging'; +import { getEnvironmentDirFromPath, getInterpreterPathFromDir, isPythonExecutable } from '../../../common/commonUtils'; +import { getEmptyVersion } from '../../info/pythonVersion'; + +/** + * 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 IResolvingLocator { + public get onChanged(): Event { + return this.parentLocator.onChanged; + } + + constructor( + private readonly parentLocator: ICompositeLocator, + private readonly environmentInfoService: IEnvironmentInfoService, + ) { + this.parentLocator.onChanged((event) => { + if (event.type && event.searchLocation !== undefined) { + // We detect an environment changed, reset any stored info for it so it can be re-run. + this.environmentInfoService.resetInfo(event.searchLocation); + } + }); + } + + public async resolveEnv(path: string): Promise { + const [executablePath, envPath] = await getExecutablePathAndEnvPath(path); + path = executablePath.length ? executablePath : envPath; + const kind = await identifyEnvironment(path); + const environment = await resolveBasicEnv({ kind, executablePath, envPath }); + const info = await this.environmentInfoService.getEnvironmentInfo(environment); + traceVerbose( + `Environment resolver resolved ${path} for ${JSON.stringify(environment)} to ${JSON.stringify(info)}`, + ); + if (!info) { + return undefined; + } + return getResolvedEnv(info, environment); + } + + public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { + const didUpdate = new EventEmitter(); + const incomingIterator = this.parentLocator.iterEnvs(query); + const iterator = this.iterEnvsIterator(incomingIterator, didUpdate); + iterator.onUpdated = didUpdate.event; + return iterator; + } + + private async *iterEnvsIterator( + iterator: IPythonEnvsIterator, + didUpdate: EventEmitter, + ): IPythonEnvsIterator { + const environmentKinds = new Map(); + const state = { + done: false, + pending: 0, + }; + const seen: PythonEnvInfo[] = []; + + if (iterator.onUpdated !== undefined) { + const listener = iterator.onUpdated(async (event) => { + state.pending += 1; + if (isProgressEvent(event)) { + if (event.stage === ProgressReportStage.discoveryFinished) { + didUpdate.fire({ stage: ProgressReportStage.allPathsDiscovered }); + state.done = true; + listener.dispose(); + } else { + didUpdate.fire(event); + } + } else if (event.update === undefined) { + throw new Error( + 'Unsupported behavior: `undefined` environment updates are not supported from downstream locators in resolver', + ); + } else if (event.index !== undefined && seen[event.index] !== undefined) { + const old = seen[event.index]; + await setKind(event.update, environmentKinds); + seen[event.index] = await resolveBasicEnv(event.update); + didUpdate.fire({ old, index: event.index, update: seen[event.index] }); + this.resolveInBackground(event.index, state, didUpdate, seen).ignoreErrors(); + } else { + // This implies a problem in a downstream locator + traceVerbose(`Expected already iterated env, got ${event.old} (#${event.index})`); + } + state.pending -= 1; + checkIfFinishedAndNotify(state, didUpdate); + }); + } else { + didUpdate.fire({ stage: ProgressReportStage.discoveryStarted }); + } + + let result = await iterator.next(); + while (!result.done) { + // Use cache from the current refresh where possible. + await setKind(result.value, environmentKinds); + const currEnv = await resolveBasicEnv(result.value); + seen.push(currEnv); + yield currEnv; + this.resolveInBackground(seen.indexOf(currEnv), state, didUpdate, seen).ignoreErrors(); + 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[], + ) { + state.pending += 1; + // It's essential we increment the pending call count before any asynchronus calls in this method. + // We want this to be run even when `resolveInBackground` is called in background. + const info = await this.environmentInfoService.getEnvironmentInfo(seen[envIndex]); + const old = seen[envIndex]; + if (info) { + const resolvedEnv = getResolvedEnv(info, seen[envIndex], old.identifiedUsingNativeLocator); + seen[envIndex] = resolvedEnv; + didUpdate.fire({ old, index: envIndex, update: resolvedEnv }); + } else { + // Send update that the environment is not valid. + didUpdate.fire({ old, index: envIndex, update: undefined }); + } + state.pending -= 1; + checkIfFinishedAndNotify(state, didUpdate); + } +} + +async function setKind(env: BasicEnvInfo, environmentKinds: Map) { + const { path } = getEnvPath(env.executablePath, env.envPath); + // For native locators, do not try to identify the environment kind. + // its already set by the native locator & thats accurate. + if (env.identifiedUsingNativeLocator) { + environmentKinds.set(path, env.kind); + return; + } + let kind = environmentKinds.get(path); + if (!kind) { + if (!isIdentifierRegistered(env.kind)) { + // If identifier is not registered, skip setting env kind. + return; + } + kind = await identifyEnvironment(path); + environmentKinds.set(path, kind); + } + env.kind = kind; +} + +/** + * 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({ stage: ProgressReportStage.discoveryFinished }); + didUpdate.dispose(); + traceVerbose(`Finished with environment resolver`); + } +} + +function getResolvedEnv( + interpreterInfo: InterpreterInformation, + environment: PythonEnvInfo, + identifiedUsingNativeLocator = false, +) { + // Deep copy into a new object + const resolvedEnv = cloneDeep(environment); + resolvedEnv.executable.sysPrefix = interpreterInfo.executable.sysPrefix; + const isEnvLackingPython = + getEnvPath(resolvedEnv.executable.filename, resolvedEnv.location).pathType === 'envFolderPath'; + // TODO: Shouldn't this only apply to conda, how else can we have an environment and not have Python in it? + // If thats the case, then this should be gated on environment.kind === PythonEnvKind.Conda + // For non-native do not blow away the versions returned by native locator. + // Windows Store and Home brew have exe and sysprefix in different locations, + // Thus above check is not valid for these envs. + if (isEnvLackingPython && environment.kind !== PythonEnvKind.MicrosoftStore && !identifiedUsingNativeLocator) { + // Install python later into these envs might change the version, which can be confusing for users. + // So avoid displaying any version until it is installed. + resolvedEnv.version = getEmptyVersion(); + } else { + resolvedEnv.version = interpreterInfo.version; + } + resolvedEnv.arch = interpreterInfo.arch; + // Display name should be set after all the properties as we need other properties to build display name. + setEnvDisplayString(resolvedEnv); + return resolvedEnv; +} + +async function getExecutablePathAndEnvPath(path: string) { + let executablePath: string; + let envPath: string; + const isPathAnExecutable = await isPythonExecutable(path).catch((ex) => { + traceWarn('Failed to check if', path, 'is an executable', ex); + // This could happen if the path doesn't exist on a file system, but + // it still maybe the case that it's a valid file when run using a + // shell, as shells may resolve the file extensions before running it, + // so assume it to be an executable. + return true; + }); + if (isPathAnExecutable) { + executablePath = path; + envPath = getEnvironmentDirFromPath(executablePath); + } else { + envPath = path; + executablePath = (await getInterpreterPathFromDir(envPath)) ?? ''; + } + return [executablePath, envPath]; +} diff --git a/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts new file mode 100644 index 000000000000..088ae9cc97c1 --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/composite/resolverUtils.ts @@ -0,0 +1,377 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { Uri } from 'vscode'; +import { uniq } from 'lodash'; +import { + PythonEnvInfo, + PythonEnvKind, + PythonEnvSource, + PythonEnvType, + UNKNOWN_PYTHON_VERSION, + virtualEnvKinds, +} from '../../info'; +import { buildEnvInfo, comparePythonVersionSpecificity, setEnvDisplayString, getEnvID } from '../../info/env'; +import { getEnvironmentDirFromPath, getPythonVersionFromPath } from '../../../common/commonUtils'; +import { arePathsSame, getFileInfo, isParentPath } from '../../../common/externalDependencies'; +import { + AnacondaCompanyName, + Conda, + getCondaInterpreterPath, + getPythonVersionFromConda, + isCondaEnvironment, +} from '../../../common/environmentManagers/conda'; +import { getPyenvVersionsDir, parsePyenvVersion } from '../../../common/environmentManagers/pyenv'; +import { Architecture, getOSType, OSType } from '../../../../common/utils/platform'; +import { getPythonVersionFromPath as parsePythonVersionFromPath, parseVersion } from '../../info/pythonVersion'; +import { getRegistryInterpreters, getRegistryInterpretersSync } from '../../../common/windowsUtils'; +import { BasicEnvInfo } from '../../locator'; +import { parseVersionFromExecutable } from '../../info/executable'; +import { traceError, traceWarn } from '../../../../logging'; +import { isVirtualEnvironment } from '../../../common/environmentManagers/simplevirtualenvs'; +import { getWorkspaceFolderPaths } from '../../../../common/vscodeApis/workspaceApis'; +import { ActiveState } from '../../../common/environmentManagers/activestate'; + +function getResolvers(): Map Promise> { + const resolvers = new Map Promise>(); + Object.values(PythonEnvKind).forEach((k) => { + resolvers.set(k, resolveGloballyInstalledEnv); + }); + virtualEnvKinds.forEach((k) => { + resolvers.set(k, resolveSimpleEnv); + }); + resolvers.set(PythonEnvKind.Conda, resolveCondaEnv); + resolvers.set(PythonEnvKind.MicrosoftStore, resolveMicrosoftStoreEnv); + resolvers.set(PythonEnvKind.Pyenv, resolvePyenvEnv); + resolvers.set(PythonEnvKind.ActiveState, resolveActiveStateEnv); + return resolvers; +} + +/** + * Find as much info about the given Basic Python env as possible without running the + * executable and returns it. Notice `undefined` is never returned, so environment + * returned could still be invalid. + */ +export async function resolveBasicEnv(env: BasicEnvInfo): Promise { + const { kind, source, searchLocation } = env; + const resolvers = getResolvers(); + const resolverForKind = resolvers.get(kind)!; + const resolvedEnv = await resolverForKind(env); + resolvedEnv.searchLocation = getSearchLocation(resolvedEnv, searchLocation); + resolvedEnv.source = uniq(resolvedEnv.source.concat(source ?? [])); + if ( + !env.identifiedUsingNativeLocator && + getOSType() === OSType.Windows && + resolvedEnv.source?.includes(PythonEnvSource.WindowsRegistry) + ) { + // We can update env further using information we can get from the Windows registry. + await updateEnvUsingRegistry(resolvedEnv); + } + setEnvDisplayString(resolvedEnv); + if (env.arch && !resolvedEnv.arch) { + resolvedEnv.arch = env.arch; + } + if (env.ctime && env.mtime) { + resolvedEnv.executable.ctime = env.ctime; + resolvedEnv.executable.mtime = env.mtime; + } else { + const { ctime, mtime } = await getFileInfo(resolvedEnv.executable.filename); + resolvedEnv.executable.ctime = ctime; + resolvedEnv.executable.mtime = mtime; + } + if (!env.identifiedUsingNativeLocator) { + const type = await getEnvType(resolvedEnv); + if (type) { + resolvedEnv.type = type; + } + } + return resolvedEnv; +} + +async function getEnvType(env: PythonEnvInfo) { + if (env.type) { + return env.type; + } + if (await isVirtualEnvironment(env.executable.filename)) { + return PythonEnvType.Virtual; + } + if (await isCondaEnvironment(env.executable.filename)) { + return PythonEnvType.Conda; + } + return undefined; +} + +function getSearchLocation(env: PythonEnvInfo, searchLocation: Uri | undefined): Uri | undefined { + if (searchLocation) { + // A search location has already been established by the downstream locators, simply use that. + return searchLocation; + } + const folders = getWorkspaceFolderPaths(); + const isRootedEnv = folders.some((f) => isParentPath(env.executable.filename, f) || isParentPath(env.location, f)); + if (isRootedEnv) { + // For environments inside roots, we need to set search location so they can be queried accordingly. + // In certain usecases environment directory can itself be a root, for eg. `python -m venv .`. + // So choose folder to environment path to search for this env. + // + // |__ env <--- Default search location directory + // |__ bin or Scripts + // |__ python <--- executable + return Uri.file(env.location); + } + return undefined; +} + +async function updateEnvUsingRegistry(env: PythonEnvInfo): Promise { + // Environment source has already been identified as windows registry, so we expect windows registry + // cache to already be populated. Call sync function which relies on cache. + let interpreters = getRegistryInterpretersSync(); + if (!interpreters) { + traceError('Expected registry interpreter cache to be initialized already'); + interpreters = await getRegistryInterpreters(); + } + const data = interpreters.find((i) => arePathsSame(i.interpreterPath, env.executable.filename)); + if (data) { + const versionStr = data.versionStr ?? data.sysVersionStr ?? data.interpreterPath; + let version; + try { + version = parseVersion(versionStr); + } catch (ex) { + version = UNKNOWN_PYTHON_VERSION; + } + env.kind = env.kind === PythonEnvKind.Unknown ? PythonEnvKind.OtherGlobal : env.kind; + env.version = comparePythonVersionSpecificity(version, env.version) > 0 ? version : env.version; + env.distro.defaultDisplayName = data.companyDisplayName; + env.arch = data.bitnessStr === '32bit' ? Architecture.x86 : Architecture.x64; + env.distro.org = data.distroOrgName ?? env.distro.org; + env.source = uniq(env.source.concat(PythonEnvSource.WindowsRegistry)); + } else { + traceWarn('Expected registry to find the interpreter as source was set'); + } +} + +async function resolveGloballyInstalledEnv(env: BasicEnvInfo): Promise { + const { executablePath } = env; + let version; + try { + version = env.identifiedUsingNativeLocator ? env.version : parseVersionFromExecutable(executablePath); + } catch { + version = UNKNOWN_PYTHON_VERSION; + } + const envInfo = buildEnvInfo({ + kind: env.kind, + name: env.name, + display: env.displayName, + sysPrefix: env.envPath, + location: env.envPath, + searchLocation: env.searchLocation, + version, + executable: executablePath, + identifiedUsingNativeLocator: env.identifiedUsingNativeLocator, + }); + return envInfo; +} + +async function resolveSimpleEnv(env: BasicEnvInfo): Promise { + const { executablePath, kind } = env; + const envInfo = buildEnvInfo({ + kind, + version: env.identifiedUsingNativeLocator ? env.version : await getPythonVersionFromPath(executablePath), + executable: executablePath, + sysPrefix: env.envPath, + location: env.envPath, + display: env.displayName, + searchLocation: env.searchLocation, + identifiedUsingNativeLocator: env.identifiedUsingNativeLocator, + name: env.name, + type: PythonEnvType.Virtual, + }); + const location = env.envPath ?? getEnvironmentDirFromPath(executablePath); + envInfo.location = location; + envInfo.name = path.basename(location); + return envInfo; +} + +async function resolveCondaEnv(env: BasicEnvInfo): Promise { + if (env.identifiedUsingNativeLocator) { + // New approach using native locator. + const executable = env.executablePath; + const envPath = env.envPath ?? getEnvironmentDirFromPath(executable); + // TODO: Hacky, `executable` is never undefined in the typedef, + // However, in reality with native locator this can be undefined. + const version = env.version ?? (executable ? await getPythonVersionFromPath(executable) : undefined); + const info = buildEnvInfo({ + executable, + kind: PythonEnvKind.Conda, + org: AnacondaCompanyName, + location: envPath, + sysPrefix: envPath, + display: env.displayName, + identifiedUsingNativeLocator: env.identifiedUsingNativeLocator, + searchLocation: env.searchLocation, + source: [], + version, + type: PythonEnvType.Conda, + name: env.name, + }); + + if (env.envPath && executable && path.basename(executable) === executable) { + // For environments without python, set ID using the predicted executable path after python is installed. + // Another alternative could've been to set ID of all conda environments to the environment path, as that + // remains constant even after python installation. + const predictedExecutable = getCondaInterpreterPath(env.envPath); + info.id = getEnvID(predictedExecutable, env.envPath); + } + return info; + } + + // Old approach (without native locator). + // In this approach we need to find conda. + const { executablePath } = env; + const conda = await Conda.getConda(); + if (conda === undefined) { + traceWarn(`${executablePath} identified as Conda environment even though Conda is not found`); + // Environment could still be valid, resolve as a simple env. + env.kind = PythonEnvKind.Unknown; + const envInfo = await resolveSimpleEnv(env); + envInfo.type = PythonEnvType.Conda; + // Assume it's a prefixed env by default because prefixed CLIs work even for named environments. + envInfo.name = ''; + return envInfo; + } + + const envPath = env.envPath ?? getEnvironmentDirFromPath(env.executablePath); + let executable: string; + if (env.executablePath.length > 0) { + executable = env.executablePath; + } else { + executable = await conda.getInterpreterPathForEnvironment({ prefix: envPath }); + } + const version = executable ? await getPythonVersionFromConda(executable) : undefined; + const info = buildEnvInfo({ + executable, + kind: PythonEnvKind.Conda, + org: AnacondaCompanyName, + location: envPath, + source: [], + version, + type: PythonEnvType.Conda, + name: env.name ?? (await conda?.getName(envPath)), + }); + + if (env.envPath && path.basename(executable) === executable) { + // For environments without python, set ID using the predicted executable path after python is installed. + // Another alternative could've been to set ID of all conda environments to the environment path, as that + // remains constant even after python installation. + const predictedExecutable = getCondaInterpreterPath(env.envPath); + info.id = getEnvID(predictedExecutable, env.envPath); + } + return info; +} + +async function resolvePyenvEnv(env: BasicEnvInfo): Promise { + const { executablePath } = env; + const location = env.envPath ?? getEnvironmentDirFromPath(executablePath); + const name = path.basename(location); + + // The sub-directory name sometimes can contain distro and python versions. + // here we attempt to extract the texts out of the name. + const versionStrings = parsePyenvVersion(name); + + const envInfo = buildEnvInfo({ + // If using native resolver, then we can get the kind from the native resolver. + // E.g. pyenv can have conda environments as well. + kind: env.identifiedUsingNativeLocator && env.kind ? env.kind : PythonEnvKind.Pyenv, + executable: executablePath, + source: [], + location, + searchLocation: env.searchLocation, + sysPrefix: env.envPath, + display: env.displayName, + name: env.name, + identifiedUsingNativeLocator: env.identifiedUsingNativeLocator, + // Pyenv environments can fall in to these three categories: + // 1. Global Installs : These are environments that are created when you install + // a supported python distribution using `pyenv install ` command. + // These behave similar to globally installed version of python or distribution. + // + // 2. Virtual Envs : These are environments that are created when you use + // `pyenv virtualenv `. These are similar to environments + // created using `python -m venv `. + // + // 3. Conda Envs : These are environments that are created when you use + // `pyenv virtualenv `. These are similar to + // environments created using `conda create -n . + // + // All these environments are fully handled by `pyenv` and should be activated using + // `pyenv local|global ` or `pyenv shell ` + // + // Here we look for near by files, or config files to see if we can get python version info + // without running python itself. + version: env.version ?? (await getPythonVersionFromPath(executablePath, versionStrings?.pythonVer)), + org: versionStrings && versionStrings.distro ? versionStrings.distro : '', + }); + + // Do this only for the old approach, when not using native locators. + if (!env.identifiedUsingNativeLocator) { + if (await isBaseCondaPyenvEnvironment(executablePath)) { + envInfo.name = 'base'; + } else { + envInfo.name = name; + } + } + return envInfo; +} + +async function resolveActiveStateEnv(env: BasicEnvInfo): Promise { + const info = buildEnvInfo({ + kind: env.kind, + executable: env.executablePath, + display: env.displayName, + version: env.version, + identifiedUsingNativeLocator: env.identifiedUsingNativeLocator, + location: env.envPath, + name: env.name, + searchLocation: env.searchLocation, + sysPrefix: env.envPath, + }); + const projects = await ActiveState.getState().then((v) => v?.getProjects()); + if (projects) { + for (const project of projects) { + for (const dir of project.executables) { + if (arePathsSame(dir, path.dirname(env.executablePath))) { + info.name = `${project.organization}/${project.name}`; + return info; + } + } + } + } + return info; +} + +async function isBaseCondaPyenvEnvironment(executablePath: string) { + if (!(await isCondaEnvironment(executablePath))) { + return false; + } + const location = getEnvironmentDirFromPath(executablePath); + const pyenvVersionDir = getPyenvVersionsDir(); + return arePathsSame(path.dirname(location), pyenvVersionDir); +} + +async function resolveMicrosoftStoreEnv(env: BasicEnvInfo): Promise { + const { executablePath } = env; + return buildEnvInfo({ + kind: PythonEnvKind.MicrosoftStore, + executable: executablePath, + version: env.version ?? parsePythonVersionFromPath(executablePath), + org: 'Microsoft', + display: env.displayName, + location: env.envPath, + sysPrefix: env.envPath, + searchLocation: env.searchLocation, + name: env.name, + identifiedUsingNativeLocator: env.identifiedUsingNativeLocator, + arch: Architecture.x64, + source: [PythonEnvSource.PathEnvVar], + }); +} diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/activeStateLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/activeStateLocator.ts new file mode 100644 index 000000000000..3fbdacc639a5 --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/lowLevel/activeStateLocator.ts @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { ActiveState } from '../../../common/environmentManagers/activestate'; +import { PythonEnvKind } from '../../info'; +import { BasicEnvInfo, IPythonEnvsIterator } from '../../locator'; +import { traceError, traceInfo, traceVerbose } from '../../../../logging'; +import { LazyResourceBasedLocator } from '../common/resourceBasedLocator'; +import { findInterpretersInDir } from '../../../common/commonUtils'; +import { StopWatch } from '../../../../common/utils/stopWatch'; + +export class ActiveStateLocator extends LazyResourceBasedLocator { + public readonly providerId: string = 'activestate'; + + // eslint-disable-next-line class-methods-use-this + public async *doIterEnvs(): IPythonEnvsIterator { + const stopWatch = new StopWatch(); + const state = await ActiveState.getState(); + if (state === undefined) { + traceVerbose(`Couldn't locate the state binary.`); + return; + } + traceInfo(`Searching for active state environments`); + const projects = await state.getProjects(); + if (projects === undefined) { + traceVerbose(`Couldn't fetch State Tool projects.`); + return; + } + for (const project of projects) { + if (project.executables) { + for (const dir of project.executables) { + try { + traceVerbose(`Looking for Python in: ${project.name}`); + for await (const exe of findInterpretersInDir(dir)) { + traceVerbose(`Found Python executable: ${exe.filename}`); + yield { kind: PythonEnvKind.ActiveState, executablePath: exe.filename }; + } + } catch (ex) { + traceError(`Failed to process State Tool project: ${JSON.stringify(project)}`, ex); + } + } + } + } + traceInfo(`Finished searching for active state environments: ${stopWatch.elapsedTime} milliseconds`); + } +} diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/condaLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/condaLocator.ts new file mode 100644 index 000000000000..bb48ba75b9dd --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/lowLevel/condaLocator.ts @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +import '../../../../common/extensions'; +import { PythonEnvKind } from '../../info'; +import { BasicEnvInfo, IPythonEnvsIterator } from '../../locator'; +import { Conda, getCondaEnvironmentsTxt } from '../../../common/environmentManagers/conda'; +import { traceError, traceInfo, traceVerbose } from '../../../../logging'; +import { FSWatchingLocator } from './fsWatchingLocator'; +import { StopWatch } from '../../../../common/utils/stopWatch'; + +export class CondaEnvironmentLocator extends FSWatchingLocator { + public readonly providerId: string = 'conda-envs'; + + public constructor() { + super( + () => getCondaEnvironmentsTxt(), + async () => PythonEnvKind.Conda, + { isFile: true }, + ); + } + + // eslint-disable-next-line class-methods-use-this + public async *doIterEnvs(_: unknown): IPythonEnvsIterator { + const stopWatch = new StopWatch(); + traceInfo('Searching for conda environments'); + const conda = await Conda.getConda(); + if (conda === undefined) { + traceVerbose(`Couldn't locate the conda binary.`); + return; + } + traceVerbose(`Searching for conda environments using ${conda.command}`); + + const envs = await conda.getEnvList(); + for (const env of envs) { + try { + traceVerbose(`Looking into conda env for executable: ${JSON.stringify(env)}`); + const executablePath = await conda.getInterpreterPathForEnvironment(env); + traceVerbose(`Found conda executable: ${executablePath}`); + yield { kind: PythonEnvKind.Conda, executablePath, envPath: env.prefix }; + } catch (ex) { + traceError(`Failed to process conda env: ${JSON.stringify(env)}`, ex); + } + } + traceInfo(`Finished searching for conda environments: ${stopWatch.elapsedTime} milliseconds`); + } +} diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts new file mode 100644 index 000000000000..6aa83bbc376b --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/lowLevel/customVirtualEnvLocator.ts @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { uniq } from 'lodash'; +import * as path from 'path'; +import { chain, iterable } from '../../../../common/utils/async'; +import { getUserHomeDir } from '../../../../common/utils/platform'; +import { PythonEnvKind } from '../../info'; +import { BasicEnvInfo, IPythonEnvsIterator } from '../../locator'; +import { FSWatchingLocator } from './fsWatchingLocator'; +import { findInterpretersInDir, looksLikeBasicVirtualPython } from '../../../common/commonUtils'; +import { getPythonSetting, onDidChangePythonSetting, pathExists } from '../../../common/externalDependencies'; +import { isPipenvEnvironment } from '../../../common/environmentManagers/pipenv'; +import { + isVenvEnvironment, + isVirtualenvEnvironment, + isVirtualenvwrapperEnvironment, +} from '../../../common/environmentManagers/simplevirtualenvs'; +import '../../../../common/extensions'; +import { asyncFilter } from '../../../../common/utils/arrayUtils'; +import { traceError, traceInfo, traceVerbose } from '../../../../logging'; +import { StopWatch } from '../../../../common/utils/stopWatch'; +import { untildify } from '../../../../common/helpers'; +/** + * Default number of levels of sub-directories to recurse when looking for interpreters. + */ +const DEFAULT_SEARCH_DEPTH = 2; + +export const VENVPATH_SETTING_KEY = 'venvPath'; +export const VENVFOLDERS_SETTING_KEY = 'venvFolders'; + +/** + * Gets all custom virtual environment locations to look for environments. + */ +async function getCustomVirtualEnvDirs(): Promise { + const venvDirs: string[] = []; + const venvPath = getPythonSetting(VENVPATH_SETTING_KEY); + if (venvPath) { + venvDirs.push(untildify(venvPath)); + } + const venvFolders = getPythonSetting(VENVFOLDERS_SETTING_KEY) ?? []; + const homeDir = getUserHomeDir(); + if (homeDir && (await pathExists(homeDir))) { + venvFolders + .map((item) => (item.startsWith(homeDir) ? item : path.join(homeDir, item))) + .forEach((d) => venvDirs.push(d)); + venvFolders.forEach((item) => venvDirs.push(untildify(item))); + } + return asyncFilter(uniq(venvDirs), pathExists); +} + +/** + * Gets the virtual environment kind for a given interpreter path. + * This only checks for environments created using venv, virtualenv, + * and virtualenvwrapper based environments. + * @param interpreterPath: Absolute path to the interpreter paths. + */ +async function getVirtualEnvKind(interpreterPath: string): Promise { + if (await isPipenvEnvironment(interpreterPath)) { + return PythonEnvKind.Pipenv; + } + + if (await isVirtualenvwrapperEnvironment(interpreterPath)) { + return PythonEnvKind.VirtualEnvWrapper; + } + + if (await isVenvEnvironment(interpreterPath)) { + return PythonEnvKind.Venv; + } + + if (await isVirtualenvEnvironment(interpreterPath)) { + return PythonEnvKind.VirtualEnv; + } + + return PythonEnvKind.Unknown; +} + +/** + * Finds and resolves custom virtual environments that users have provided. + */ +export class CustomVirtualEnvironmentLocator extends FSWatchingLocator { + public readonly providerId: string = 'custom-virtual-envs'; + + constructor() { + super(getCustomVirtualEnvDirs, getVirtualEnvKind, { + // Note detecting kind of virtual env depends on the file structure around the + // executable, so we need to wait before attempting to detect it. However even + // if the type detected is incorrect, it doesn't do any practical harm as kinds + // in this locator are used in the same way (same activation commands etc.) + delayOnCreated: 1000, + }); + } + + protected async initResources(): Promise { + this.disposables.push(onDidChangePythonSetting(VENVPATH_SETTING_KEY, () => this.fire())); + this.disposables.push(onDidChangePythonSetting(VENVFOLDERS_SETTING_KEY, () => this.fire())); + } + + // eslint-disable-next-line class-methods-use-this + protected doIterEnvs(): IPythonEnvsIterator { + async function* iterator() { + const stopWatch = new StopWatch(); + traceInfo('Searching for custom virtual environments'); + const envRootDirs = await getCustomVirtualEnvDirs(); + const envGenerators = envRootDirs.map((envRootDir) => { + async function* generator() { + traceVerbose(`Searching for custom virtual envs in: ${envRootDir}`); + + const executables = findInterpretersInDir(envRootDir, DEFAULT_SEARCH_DEPTH); + + for await (const entry of executables) { + const { filename } = entry; + // We only care about python.exe (on windows) and python (on linux/mac) + // Other version like python3.exe or python3.8 are often symlinks to + // python.exe or python in the same directory in the case of virtual + // environments. + if (await looksLikeBasicVirtualPython(entry)) { + try { + // We should extract the kind here to avoid doing is*Environment() + // check multiple times. Those checks are file system heavy and + // we can use the kind to determine this anyway. + const kind = await getVirtualEnvKind(filename); + yield { kind, executablePath: filename }; + traceVerbose(`Custom Virtual Environment: [added] ${filename}`); + } catch (ex) { + traceError(`Failed to process environment: ${filename}`, ex); + } + } else { + traceVerbose(`Custom Virtual Environment: [skipped] ${filename}`); + } + } + } + return generator(); + }); + + yield* iterable(chain(envGenerators)); + traceInfo(`Finished searching for custom virtual envs: ${stopWatch.elapsedTime} milliseconds`); + } + + return iterator(); + } +} diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/customWorkspaceLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/customWorkspaceLocator.ts new file mode 100644 index 000000000000..8a2b857d496a --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/lowLevel/customWorkspaceLocator.ts @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { PythonEnvKind } from '../../info'; +import { BasicEnvInfo, IPythonEnvsIterator } from '../../locator'; +import { FSWatchingLocator } from './fsWatchingLocator'; +import { getPythonSetting, onDidChangePythonSetting } from '../../../common/externalDependencies'; +import '../../../../common/extensions'; +import { traceVerbose } from '../../../../logging'; +import { DEFAULT_INTERPRETER_SETTING } from '../../../../common/constants'; + +export const DEFAULT_INTERPRETER_PATH_SETTING_KEY = 'defaultInterpreterPath'; + +/** + * Finds and resolves custom virtual environments that users have provided. + */ +export class CustomWorkspaceLocator extends FSWatchingLocator { + public readonly providerId: string = 'custom-workspace-locator'; + + constructor(private readonly root: string) { + super( + () => [], + async () => PythonEnvKind.Unknown, + ); + } + + protected async initResources(): Promise { + this.disposables.push( + onDidChangePythonSetting(DEFAULT_INTERPRETER_PATH_SETTING_KEY, () => this.fire(), this.root), + ); + } + + // eslint-disable-next-line class-methods-use-this + protected doIterEnvs(): IPythonEnvsIterator { + const iterator = async function* (root: string) { + traceVerbose('Searching for custom workspace envs'); + const filename = getPythonSetting(DEFAULT_INTERPRETER_PATH_SETTING_KEY, root); + if (!filename || filename === DEFAULT_INTERPRETER_SETTING) { + // If the user has not set a custom interpreter, our job is done. + return; + } + yield { kind: PythonEnvKind.Unknown, executablePath: filename }; + traceVerbose(`Finished searching for custom workspace envs`); + }; + return iterator(this.root); + } +} diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/filesLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/filesLocator.ts new file mode 100644 index 000000000000..e5ed206650ca --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/lowLevel/filesLocator.ts @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/* eslint-disable max-classes-per-file */ + +import { Event } from 'vscode'; +import { iterPythonExecutablesInDir } from '../../../common/commonUtils'; +import { PythonEnvKind, PythonEnvSource } from '../../info'; +import { BasicEnvInfo, ILocator, IPythonEnvsIterator, PythonLocatorQuery } from '../../locator'; +import { PythonEnvsChangedEvent, PythonEnvsWatcher } from '../../watcher'; + +type GetExecutablesFunc = () => AsyncIterableIterator; + +/** + * A naive locator the wraps a function that finds Python executables. + */ +abstract class FoundFilesLocator implements ILocator { + public abstract readonly providerId: string; + + public readonly onChanged: Event; + + protected readonly watcher = new PythonEnvsWatcher(); + + constructor( + private readonly kind: PythonEnvKind, + private readonly getExecutables: GetExecutablesFunc, + private readonly source?: PythonEnvSource[], + ) { + this.onChanged = this.watcher.onChanged; + } + + public iterEnvs(_query?: PythonLocatorQuery): IPythonEnvsIterator { + const executables = this.getExecutables(); + async function* generator(kind: PythonEnvKind, source?: PythonEnvSource[]): IPythonEnvsIterator { + for await (const executablePath of executables) { + yield { executablePath, kind, source }; + } + } + const iterator = generator(this.kind, this.source); + return iterator; + } +} + +type GetDirExecutablesFunc = (dir: string) => AsyncIterableIterator; + +/** + * A locator for executables in a single directory. + */ +export class DirFilesLocator extends FoundFilesLocator { + public readonly providerId: string; + + constructor( + dirname: string, + defaultKind: PythonEnvKind, + // This is put in a closure and otherwise passed through as-is. + getExecutables: GetDirExecutablesFunc = getExecutablesDefault, + source?: PythonEnvSource[], + ) { + super(defaultKind, () => getExecutables(dirname), source); + this.providerId = `dir-files-${dirname}`; + } +} + +// For now we do not have a DirFilesWatchingLocator. It would be +// a subclass of FSWatchingLocator that wraps a DirFilesLocator +// instance. + +async function* getExecutablesDefault(dirname: string): AsyncIterableIterator { + for await (const entry of iterPythonExecutablesInDir(dirname)) { + yield entry.filename; + } +} diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts new file mode 100644 index 000000000000..dd7db5538565 --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/lowLevel/fsWatchingLocator.ts @@ -0,0 +1,182 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as fs from 'fs'; +import * as path from 'path'; +import { Uri } from 'vscode'; +import { FileChangeType, watchLocationForPattern } from '../../../../common/platform/fileSystemWatcher'; +import { sleep } from '../../../../common/utils/async'; +import { traceVerbose, traceWarn } from '../../../../logging'; +import { getEnvironmentDirFromPath } from '../../../common/commonUtils'; +import { + PythonEnvStructure, + resolvePythonExeGlobs, + watchLocationForPythonBinaries, +} from '../../../common/pythonBinariesWatcher'; +import { PythonEnvKind } from '../../info'; +import { LazyResourceBasedLocator } from '../common/resourceBasedLocator'; + +export enum FSWatcherKind { + Global, // Watcher observes a global location such as ~/.envs, %LOCALAPPDATA%/Microsoft/WindowsApps. + Workspace, // Watchers observes directory in the user's currently open workspace. +} + +type DirUnwatchableReason = 'directory does not exist' | 'too many files' | undefined; + +/** + * Determine if the directory is watchable. + */ +function checkDirWatchable(dirname: string): DirUnwatchableReason { + let names: string[]; + try { + names = fs.readdirSync(dirname); + } catch (err) { + const exception = err as NodeJS.ErrnoException; + traceVerbose('Reading directory failed', exception); + if (exception.code === 'ENOENT') { + // Treat a missing directory as unwatchable since it can lead to CPU load issues: + // https://github.com/microsoft/vscode-python/issues/18459 + return 'directory does not exist'; + } + return undefined; + } + // The limit here is an educated guess. + if (names.length > 200) { + return 'too many files'; + } + return undefined; +} + +type LocationWatchOptions = { + /** + * Glob which represents basename of the executable or directory to watch. + */ + baseGlob?: string; + /** + * Time to wait before handling an environment-created event. + */ + delayOnCreated?: number; // milliseconds + /** + * Location affected by the event. If not provided, a default search location is used. + */ + searchLocation?: string; + /** + * The Python env structure to watch. + */ + envStructure?: PythonEnvStructure; +}; + +type FileWatchOptions = { + /** + * If the provided root is a file instead. In this case the file is directly watched instead for + * looking for python binaries inside a root. + */ + isFile: boolean; +}; + +/** + * The base for Python envs locators who watch the file system. + * Most low-level locators should be using this. + * + * Subclasses can call `this.emitter.fire()` * to emit events. + */ +export abstract class FSWatchingLocator extends LazyResourceBasedLocator { + constructor( + /** + * Location(s) to watch for python binaries. + */ + private readonly getRoots: () => Promise | string | string[], + /** + * Returns the kind of environment specific to locator given the path to executable. + */ + private readonly getKind: (executable: string) => Promise, + private readonly creationOptions: LocationWatchOptions | FileWatchOptions = {}, + private readonly watcherKind: FSWatcherKind = FSWatcherKind.Global, + ) { + super(); + this.activate().ignoreErrors(); + } + + protected async initWatchers(): Promise { + // Enable all workspace watchers. + if (this.watcherKind === FSWatcherKind.Global && !isWatchingAFile(this.creationOptions)) { + // Do not allow global location watchers for now. + return; + } + + // Start the FS watchers. + let roots = await this.getRoots(); + if (typeof roots === 'string') { + roots = [roots]; + } + const promises = roots.map(async (root) => { + if (isWatchingAFile(this.creationOptions)) { + return root; + } + // Note that we only check the root dir. Any directories + // that might be watched due to a glob are not checked. + const unwatchable = await checkDirWatchable(root); + if (unwatchable) { + traceWarn(`Dir "${root}" is not watchable (${unwatchable})`); + return undefined; + } + return root; + }); + const watchableRoots = (await Promise.all(promises)).filter((root) => !!root) as string[]; + watchableRoots.forEach((root) => this.startWatchers(root)); + } + + protected fire(args = {}): void { + this.emitter.fire({ ...args, providerId: this.providerId }); + } + + private startWatchers(root: string): void { + const opts = this.creationOptions; + if (isWatchingAFile(opts)) { + traceVerbose('Start watching file for changes', root); + this.disposables.push( + watchLocationForPattern(path.dirname(root), path.basename(root), () => { + traceVerbose('Detected change in file: ', root, 'initiating a refresh'); + this.emitter.fire({ providerId: this.providerId }); + }), + ); + return; + } + const callback = async (type: FileChangeType, executable: string) => { + if (type === FileChangeType.Created) { + if (opts.delayOnCreated !== undefined) { + // Note detecting kind of env depends on the file structure around the + // executable, so we need to wait before attempting to detect it. + await sleep(opts.delayOnCreated); + } + } + // Fetching kind after deletion normally fails because the file structure around the + // executable is no longer available, so ignore the errors. + const kind = await this.getKind(executable).catch(() => undefined); + // By default, search location particularly for virtual environments is intended as the + // directory in which the environment was found in. For eg. the default search location + // for an env containing 'bin' or 'Scripts' directory is: + // + // searchLocation <--- Default search location directory + // |__ env + // |__ bin or Scripts + // |__ python <--- executable + const searchLocation = Uri.file(opts.searchLocation ?? path.dirname(getEnvironmentDirFromPath(executable))); + traceVerbose('Fired event ', JSON.stringify({ type, kind, searchLocation }), 'from locator'); + this.emitter.fire({ type, kind, searchLocation, providerId: this.providerId, envPath: executable }); + }; + + const globs = resolvePythonExeGlobs( + opts.baseGlob, + // The structure determines which globs are returned. + opts.envStructure, + ); + traceVerbose('Start watching root', root, 'for globs', JSON.stringify(globs)); + const watchers = globs.map((g) => watchLocationForPythonBinaries(root, callback, g)); + this.disposables.push(...watchers); + } +} + +function isWatchingAFile(options: LocationWatchOptions | FileWatchOptions): options is FileWatchOptions { + return 'isFile' in options && options.isFile; +} diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvronmentLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvronmentLocator.ts new file mode 100644 index 000000000000..86fbbed55043 --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/lowLevel/globalVirtualEnvronmentLocator.ts @@ -0,0 +1,164 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { toLower, uniq, uniqBy } from 'lodash'; +import * as path from 'path'; +import { Uri } from 'vscode'; +import { chain, iterable } from '../../../../common/utils/async'; +import { getEnvironmentVariable, getOSType, getUserHomeDir, OSType } from '../../../../common/utils/platform'; +import { PythonEnvKind } from '../../info'; +import { BasicEnvInfo, IPythonEnvsIterator } from '../../locator'; +import { FSWatchingLocator } from './fsWatchingLocator'; +import { findInterpretersInDir, looksLikeBasicVirtualPython } from '../../../common/commonUtils'; +import { pathExists } from '../../../common/externalDependencies'; +import { getProjectDir, isPipenvEnvironment } from '../../../common/environmentManagers/pipenv'; +import { + isVenvEnvironment, + isVirtualenvEnvironment, + isVirtualenvwrapperEnvironment, +} from '../../../common/environmentManagers/simplevirtualenvs'; +import '../../../../common/extensions'; +import { asyncFilter } from '../../../../common/utils/arrayUtils'; +import { traceError, traceInfo, traceVerbose } from '../../../../logging'; +import { StopWatch } from '../../../../common/utils/stopWatch'; +import { untildify } from '../../../../common/helpers'; + +const DEFAULT_SEARCH_DEPTH = 2; +/** + * Gets all default virtual environment locations. This uses WORKON_HOME, + * and user home directory to find some known locations where global virtual + * environments are often created. + */ +async function getGlobalVirtualEnvDirs(): Promise { + const venvDirs: string[] = []; + + let workOnHome = getEnvironmentVariable('WORKON_HOME'); + if (workOnHome) { + workOnHome = untildify(workOnHome); + if (await pathExists(workOnHome)) { + venvDirs.push(workOnHome); + } + } + + const homeDir = getUserHomeDir(); + if (homeDir && (await pathExists(homeDir))) { + const subDirs = [ + 'envs', + 'Envs', + '.direnv', + '.venvs', + '.virtualenvs', + path.join('.local', 'share', 'virtualenvs'), + ]; + const filtered = await asyncFilter( + subDirs.map((d) => path.join(homeDir, d)), + pathExists, + ); + filtered.forEach((d) => venvDirs.push(d)); + } + + return [OSType.Windows, OSType.OSX].includes(getOSType()) ? uniqBy(venvDirs, toLower) : uniq(venvDirs); +} + +async function getSearchLocation(env: BasicEnvInfo): Promise { + if (env.kind === PythonEnvKind.Pipenv) { + // Pipenv environments are created only for a specific project, so they must only + // appear if that particular project is being queried. + const project = await getProjectDir(path.dirname(path.dirname(env.executablePath))); + if (project) { + return Uri.file(project); + } + } + return undefined; +} + +/** + * Gets the virtual environment kind for a given interpreter path. + * This only checks for environments created using venv, virtualenv, + * and virtualenvwrapper based environments. + * @param interpreterPath: Absolute path to the interpreter paths. + */ +async function getVirtualEnvKind(interpreterPath: string): Promise { + if (await isPipenvEnvironment(interpreterPath)) { + return PythonEnvKind.Pipenv; + } + + if (await isVirtualenvwrapperEnvironment(interpreterPath)) { + return PythonEnvKind.VirtualEnvWrapper; + } + + if (await isVenvEnvironment(interpreterPath)) { + return PythonEnvKind.Venv; + } + + if (await isVirtualenvEnvironment(interpreterPath)) { + return PythonEnvKind.VirtualEnv; + } + + return PythonEnvKind.Unknown; +} + +/** + * Finds and resolves virtual environments created in known global locations. + */ +export class GlobalVirtualEnvironmentLocator extends FSWatchingLocator { + public readonly providerId: string = 'global-virtual-env'; + + constructor(private readonly searchDepth?: number) { + super(getGlobalVirtualEnvDirs, getVirtualEnvKind, { + // Note detecting kind of virtual env depends on the file structure around the + // executable, so we need to wait before attempting to detect it. However even + // if the type detected is incorrect, it doesn't do any practical harm as kinds + // in this locator are used in the same way (same activation commands etc.) + delayOnCreated: 1000, + }); + } + + protected doIterEnvs(): IPythonEnvsIterator { + // Number of levels of sub-directories to recurse when looking for + // interpreters + const searchDepth = this.searchDepth ?? DEFAULT_SEARCH_DEPTH; + + async function* iterator() { + const stopWatch = new StopWatch(); + traceInfo('Searching for global virtual environments'); + const envRootDirs = await getGlobalVirtualEnvDirs(); + const envGenerators = envRootDirs.map((envRootDir) => { + async function* generator() { + traceVerbose(`Searching for global virtual envs in: ${envRootDir}`); + + const executables = findInterpretersInDir(envRootDir, searchDepth); + + for await (const entry of executables) { + const { filename } = entry; + // We only care about python.exe (on windows) and python (on linux/mac) + // Other version like python3.exe or python3.8 are often symlinks to + // python.exe or python in the same directory in the case of virtual + // environments. + if (await looksLikeBasicVirtualPython(entry)) { + // We should extract the kind here to avoid doing is*Environment() + // check multiple times. Those checks are file system heavy and + // we can use the kind to determine this anyway. + const kind = await getVirtualEnvKind(filename); + const searchLocation = await getSearchLocation({ kind, executablePath: filename }); + try { + yield { kind, executablePath: filename, searchLocation }; + traceVerbose(`Global Virtual Environment: [added] ${filename}`); + } catch (ex) { + traceError(`Failed to process environment: ${filename}`, ex); + } + } else { + traceVerbose(`Global Virtual Environment: [skipped] ${filename}`); + } + } + } + return generator(); + }); + + yield* iterable(chain(envGenerators)); + traceInfo(`Finished searching for global virtual envs: ${stopWatch.elapsedTime} milliseconds`); + } + + return iterator(); + } +} diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/hatchLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/hatchLocator.ts new file mode 100644 index 000000000000..f7746a8c5a2e --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/lowLevel/hatchLocator.ts @@ -0,0 +1,57 @@ +'use strict'; + +import { PythonEnvKind } from '../../info'; +import { BasicEnvInfo, IPythonEnvsIterator } from '../../locator'; +import { LazyResourceBasedLocator } from '../common/resourceBasedLocator'; +import { Hatch } from '../../../common/environmentManagers/hatch'; +import { asyncFilter } from '../../../../common/utils/arrayUtils'; +import { pathExists } from '../../../common/externalDependencies'; +import { traceError, traceVerbose } from '../../../../logging'; +import { chain, iterable } from '../../../../common/utils/async'; +import { getInterpreterPathFromDir } from '../../../common/commonUtils'; + +/** + * Gets all default virtual environment locations to look for in a workspace. + */ +async function getVirtualEnvDirs(root: string): Promise { + const hatch = await Hatch.getHatch(root); + const envDirs = (await hatch?.getEnvList()) ?? []; + return asyncFilter(envDirs, pathExists); +} + +/** + * Finds and resolves virtual environments created using Hatch. + */ +export class HatchLocator extends LazyResourceBasedLocator { + public readonly providerId: string = 'hatch'; + + public constructor(private readonly root: string) { + super(); + } + + protected doIterEnvs(): IPythonEnvsIterator { + async function* iterator(root: string) { + const envDirs = await getVirtualEnvDirs(root); + const envGenerators = envDirs.map((envDir) => { + async function* generator() { + traceVerbose(`Searching for Hatch virtual envs in: ${envDir}`); + const filename = await getInterpreterPathFromDir(envDir); + if (filename !== undefined) { + try { + yield { executablePath: filename, kind: PythonEnvKind.Hatch }; + traceVerbose(`Hatch Virtual Environment: [added] ${filename}`); + } catch (ex) { + traceError(`Failed to process environment: ${filename}`, ex); + } + } + } + return generator(); + }); + + yield* iterable(chain(envGenerators)); + traceVerbose(`Finished searching for Hatch envs`); + } + + return iterator(this.root); + } +} diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/microsoftStoreLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/microsoftStoreLocator.ts new file mode 100644 index 000000000000..2068a05f3a69 --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/lowLevel/microsoftStoreLocator.ts @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as minimatch from 'minimatch'; +import * as path from 'path'; +import * as fsapi from '../../../../common/platform/fs-paths'; +import { PythonEnvKind } from '../../info'; +import { IPythonEnvsIterator, BasicEnvInfo } from '../../locator'; +import { FSWatchingLocator } from './fsWatchingLocator'; +import { PythonEnvStructure } from '../../../common/pythonBinariesWatcher'; +import { + isStorePythonInstalled, + getMicrosoftStoreAppsRoot, +} from '../../../common/environmentManagers/microsoftStoreEnv'; +import { traceInfo } from '../../../../logging'; +import { StopWatch } from '../../../../common/utils/stopWatch'; + +/** + * This is a glob pattern which matches following file names: + * python3.8.exe + * python3.9.exe + * python3.10.exe + * This pattern does not match: + * python.exe + * python2.7.exe + * python3.exe + * python38.exe + */ +const pythonExeGlob = 'python3.{[0-9],[0-9][0-9]}.exe'; + +/** + * Checks if a given path ends with python3.*.exe. Not all python executables are matched as + * we do not want to return duplicate executables. + * @param {string} interpreterPath : Path to python interpreter. + * @returns {boolean} : Returns true if the path matches pattern for windows python executable. + */ +function isMicrosoftStorePythonExePattern(interpreterPath: string): boolean { + return minimatch.default(path.basename(interpreterPath), pythonExeGlob, { nocase: true }); +} + +/** + * Gets paths to the Python executable under Microsoft Store apps. + * @returns: Returns python*.exe for the microsoft store app root directory. + * + * Remarks: We don't need to find the path to the interpreter under the specific application + * directory. Such as: + * `%LOCALAPPDATA%/Microsoft/WindowsApps/PythonSoftwareFoundation.Python.3.7_qbz5n2kfra8p0` + * The same python executable is also available at: + * `%LOCALAPPDATA%/Microsoft/WindowsApps` + * It would be a duplicate. + * + * All python executable under `%LOCALAPPDATA%/Microsoft/WindowsApps` or the sub-directories + * are 'reparse points' that point to the real executable at `%PROGRAMFILES%/WindowsApps`. + * However, that directory is off limits to users. So no need to populate interpreters from + * that location. + */ +export async function getMicrosoftStorePythonExes(): Promise { + if (await isStorePythonInstalled()) { + const windowsAppsRoot = getMicrosoftStoreAppsRoot(); + + // Collect python*.exe directly under %LOCALAPPDATA%/Microsoft/WindowsApps + const files = await fsapi.readdir(windowsAppsRoot); + return files + .map((filename: string) => path.join(windowsAppsRoot, filename)) + .filter(isMicrosoftStorePythonExePattern); + } + return []; +} + +export class MicrosoftStoreLocator extends FSWatchingLocator { + public readonly providerId: string = 'microsoft-store'; + + private readonly kind: PythonEnvKind = PythonEnvKind.MicrosoftStore; + + constructor() { + // We have to watch the directory instead of the executable here because + // FS events are not triggered for `*.exe` in the WindowsApps folder. The + // .exe files here are reparse points and not real files. Watching the + // PythonSoftwareFoundation directory will trigger both for new install + // and update case. Update is handled by deleting and recreating the + // PythonSoftwareFoundation directory. + super(getMicrosoftStoreAppsRoot, async () => this.kind, { + baseGlob: pythonExeGlob, + searchLocation: getMicrosoftStoreAppsRoot(), + envStructure: PythonEnvStructure.Flat, + }); + } + + protected doIterEnvs(): IPythonEnvsIterator { + const iterator = async function* (kind: PythonEnvKind) { + const stopWatch = new StopWatch(); + traceInfo('Searching for windows store envs'); + const exes = await getMicrosoftStorePythonExes(); + yield* exes.map(async (executablePath: string) => ({ + kind, + executablePath, + })); + traceInfo(`Finished searching for windows store envs: ${stopWatch.elapsedTime} milliseconds`); + }; + return iterator(this.kind); + } +} diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/pixiLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/pixiLocator.ts new file mode 100644 index 000000000000..f4a3886a2120 --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/lowLevel/pixiLocator.ts @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { asyncFilter } from '../../../../common/utils/arrayUtils'; +import { chain, iterable } from '../../../../common/utils/async'; +import { traceError, traceVerbose } from '../../../../logging'; +import { getCondaInterpreterPath } from '../../../common/environmentManagers/conda'; +import { pathExists } from '../../../common/externalDependencies'; +import { PythonEnvKind } from '../../info'; +import { IPythonEnvsIterator, BasicEnvInfo } from '../../locator'; +import { FSWatcherKind, FSWatchingLocator } from './fsWatchingLocator'; +import { getPixi } from '../../../common/environmentManagers/pixi'; + +/** + * Returns all virtual environment locations to look for in a workspace. + */ +async function getVirtualEnvDirs(root: string): Promise { + const pixi = await getPixi(); + const envDirs = (await pixi?.getEnvList(root)) ?? []; + return asyncFilter(envDirs, pathExists); +} + +/** + * Returns all virtual environment locations to look for in a workspace. + */ +function getVirtualEnvRootDirs(root: string): string[] { + return [path.join(path.join(root, '.pixi'), 'envs')]; +} + +export class PixiLocator extends FSWatchingLocator { + public readonly providerId: string = 'pixi'; + + public constructor(private readonly root: string) { + super( + async () => getVirtualEnvRootDirs(this.root), + async () => PythonEnvKind.Pixi, + { + // Note detecting kind of virtual env depends on the file structure around the + // executable, so we need to wait before attempting to detect it. + delayOnCreated: 1000, + }, + FSWatcherKind.Workspace, + ); + } + + protected doIterEnvs(): IPythonEnvsIterator { + async function* iterator(root: string) { + const envDirs = await getVirtualEnvDirs(root); + const envGenerators = envDirs.map((envDir) => { + async function* generator() { + traceVerbose(`Searching for Pixi virtual envs in: ${envDir}`); + const filename = await getCondaInterpreterPath(envDir); + if (filename !== undefined) { + try { + yield { + executablePath: filename, + kind: PythonEnvKind.Pixi, + envPath: envDir, + }; + + traceVerbose(`Pixi Virtual Environment: [added] ${filename}`); + } catch (ex) { + traceError(`Failed to process environment: ${filename}`, ex); + } + } + } + return generator(); + }); + + yield* iterable(chain(envGenerators)); + traceVerbose(`Finished searching for Pixi envs`); + } + + return iterator(this.root); + } +} diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/poetryLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/poetryLocator.ts new file mode 100644 index 000000000000..ab1a8cf77444 --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/lowLevel/poetryLocator.ts @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as path from 'path'; +import { Uri } from 'vscode'; +import { chain, iterable } from '../../../../common/utils/async'; +import { PythonEnvKind } from '../../info'; +import { BasicEnvInfo, IPythonEnvsIterator } from '../../locator'; +import { getInterpreterPathFromDir } from '../../../common/commonUtils'; +import { pathExists } from '../../../common/externalDependencies'; +import { isPoetryEnvironment, localPoetryEnvDirName, Poetry } from '../../../common/environmentManagers/poetry'; +import '../../../../common/extensions'; +import { asyncFilter } from '../../../../common/utils/arrayUtils'; +import { traceError, traceVerbose } from '../../../../logging'; +import { LazyResourceBasedLocator } from '../common/resourceBasedLocator'; + +/** + * Gets all default virtual environment locations to look for in a workspace. + */ +async function getVirtualEnvDirs(root: string): Promise { + const envDirs = [path.join(root, localPoetryEnvDirName)]; + const poetry = await Poetry.getPoetry(root); + const virtualenvs = await poetry?.getEnvList(); + if (virtualenvs) { + envDirs.push(...virtualenvs); + } + return asyncFilter(envDirs, pathExists); +} + +async function getVirtualEnvKind(interpreterPath: string): Promise { + if (await isPoetryEnvironment(interpreterPath)) { + return PythonEnvKind.Poetry; + } + + return PythonEnvKind.Unknown; +} + +/** + * Finds and resolves virtual environments created using poetry. + */ +export class PoetryLocator extends LazyResourceBasedLocator { + public readonly providerId: string = 'poetry'; + + public constructor(private readonly root: string) { + super(); + } + + protected doIterEnvs(): IPythonEnvsIterator { + async function* iterator(root: string) { + const envDirs = await getVirtualEnvDirs(root); + const envGenerators = envDirs.map((envDir) => { + async function* generator() { + traceVerbose(`Searching for poetry virtual envs in: ${envDir}`); + const filename = await getInterpreterPathFromDir(envDir); + if (filename !== undefined) { + const kind = await getVirtualEnvKind(filename); + try { + // We should extract the kind here to avoid doing is*Environment() + // check multiple times. Those checks are file system heavy and + // we can use the kind to determine this anyway. + yield { executablePath: filename, kind, searchLocation: Uri.file(root) }; + traceVerbose(`Poetry Virtual Environment: [added] ${filename}`); + } catch (ex) { + traceError(`Failed to process environment: ${filename}`, ex); + } + } + } + return generator(); + }); + + yield* iterable(chain(envGenerators)); + traceVerbose(`Finished searching for poetry envs`); + } + + return iterator(this.root); + } +} diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/posixKnownPathsLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/posixKnownPathsLocator.ts new file mode 100644 index 000000000000..daca4b860907 --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/lowLevel/posixKnownPathsLocator.ts @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as os from 'os'; +import { gte } from 'semver'; +import { PythonEnvKind, PythonEnvSource } from '../../info'; +import { BasicEnvInfo, IPythonEnvsIterator, Locator } from '../../locator'; +import { commonPosixBinPaths, getPythonBinFromPosixPaths } from '../../../common/posixUtils'; +import { isPyenvShimDir } from '../../../common/environmentManagers/pyenv'; +import { getOSType, OSType } from '../../../../common/utils/platform'; +import { isMacDefaultPythonPath } from '../../../common/environmentManagers/macDefault'; +import { traceError, traceInfo, traceVerbose } from '../../../../logging'; +import { StopWatch } from '../../../../common/utils/stopWatch'; + +export class PosixKnownPathsLocator extends Locator { + public readonly providerId = 'posixKnownPaths'; + + private kind: PythonEnvKind = PythonEnvKind.OtherGlobal; + + public iterEnvs(): IPythonEnvsIterator { + // Flag to remove system installs of Python 2 from the list of discovered interpreters + // If on macOS Monterey or later. + // See https://github.com/microsoft/vscode-python/issues/17870. + let isMacPython2Deprecated = false; + if (getOSType() === OSType.OSX && gte(os.release(), '21.0.0')) { + isMacPython2Deprecated = true; + } + + const iterator = async function* (kind: PythonEnvKind) { + const stopWatch = new StopWatch(); + traceInfo('Searching for interpreters in posix paths locator'); + try { + // Filter out pyenv shims. They are not actual python binaries, they are used to launch + // the binaries specified in .python-version file in the cwd. We should not be reporting + // those binaries as environments. + const knownDirs = (await commonPosixBinPaths()).filter((dirname) => !isPyenvShimDir(dirname)); + let pythonBinaries = await getPythonBinFromPosixPaths(knownDirs); + traceVerbose(`Found ${pythonBinaries.length} python binaries in posix paths`); + + // Filter out MacOS system installs of Python 2 if necessary. + if (isMacPython2Deprecated) { + pythonBinaries = pythonBinaries.filter((binary) => !isMacDefaultPythonPath(binary)); + } + + for (const bin of pythonBinaries) { + try { + yield { executablePath: bin, kind, source: [PythonEnvSource.PathEnvVar] }; + } catch (ex) { + traceError(`Failed to process environment: ${bin}`, ex); + } + } + } catch (ex) { + traceError('Failed to process posix paths', ex); + } + traceInfo( + `Finished searching for interpreters in posix paths locator: ${stopWatch.elapsedTime} milliseconds`, + ); + }; + return iterator(this.kind); + } +} diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/pyenvLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/pyenvLocator.ts new file mode 100644 index 000000000000..e97b69c6b882 --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/lowLevel/pyenvLocator.ts @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { PythonEnvKind } from '../../info'; +import { BasicEnvInfo, IPythonEnvsIterator } from '../../locator'; +import { FSWatchingLocator } from './fsWatchingLocator'; +import { getInterpreterPathFromDir } from '../../../common/commonUtils'; +import { getSubDirs } from '../../../common/externalDependencies'; +import { getPyenvVersionsDir } from '../../../common/environmentManagers/pyenv'; +import { traceError, traceInfo } from '../../../../logging'; +import { StopWatch } from '../../../../common/utils/stopWatch'; + +/** + * Gets all the pyenv environments. + * + * Remarks: This function looks at the /versions directory and gets + * all the environments (global or virtual) in that directory. + */ +async function* getPyenvEnvironments(): AsyncIterableIterator { + const stopWatch = new StopWatch(); + traceInfo('Searching for pyenv environments'); + try { + const pyenvVersionDir = getPyenvVersionsDir(); + + const subDirs = getSubDirs(pyenvVersionDir, { resolveSymlinks: true }); + for await (const subDirPath of subDirs) { + const interpreterPath = await getInterpreterPathFromDir(subDirPath); + + if (interpreterPath) { + try { + yield { + kind: PythonEnvKind.Pyenv, + executablePath: interpreterPath, + }; + } catch (ex) { + traceError(`Failed to process environment: ${interpreterPath}`, ex); + } + } + } + } catch (ex) { + // This is expected when pyenv is not installed + traceInfo(`pyenv is not installed`); + } + traceInfo(`Finished searching for pyenv environments: ${stopWatch.elapsedTime} milliseconds`); +} + +export class PyenvLocator extends FSWatchingLocator { + public readonly providerId: string = 'pyenv'; + + constructor() { + super(getPyenvVersionsDir, async () => PythonEnvKind.Pyenv); + } + + // eslint-disable-next-line class-methods-use-this + public doIterEnvs(): IPythonEnvsIterator { + return getPyenvEnvironments(); + } +} diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator.ts new file mode 100644 index 000000000000..440d075b4071 --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/lowLevel/windowsKnownPathsLocator.ts @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/* eslint-disable max-classes-per-file */ + +import { Event } from 'vscode'; +import * as path from 'path'; +import { IDisposable } from '../../../../common/types'; +import { getSearchPathEntries } from '../../../../common/utils/exec'; +import { Disposables } from '../../../../common/utils/resourceLifecycle'; +import { isPyenvShimDir } from '../../../common/environmentManagers/pyenv'; +import { isMicrosoftStoreDir } from '../../../common/environmentManagers/microsoftStoreEnv'; +import { PythonEnvKind, PythonEnvSource } from '../../info'; +import { BasicEnvInfo, ILocator, IPythonEnvsIterator, PythonLocatorQuery } from '../../locator'; +import { Locators } from '../../locators'; +import { getEnvs } from '../../locatorUtils'; +import { PythonEnvsChangedEvent } from '../../watcher'; +import { DirFilesLocator } from './filesLocator'; +import { traceInfo } from '../../../../logging'; +import { inExperiment, pathExists } from '../../../common/externalDependencies'; +import { DiscoveryUsingWorkers } from '../../../../common/experiments/groups'; +import { iterPythonExecutablesInDir, looksLikeBasicGlobalPython } from '../../../common/commonUtils'; +import { StopWatch } from '../../../../common/utils/stopWatch'; + +/** + * A locator for Windows locators found under the $PATH env var. + * + * Note that we assume $PATH won't change, so we don't need to watch + * it for changes. + */ +export class WindowsPathEnvVarLocator implements ILocator, IDisposable { + public readonly providerId: string = 'windows-path-env-var-locator'; + + public readonly onChanged: Event; + + private readonly locators: Locators; + + private readonly disposables = new Disposables(); + + constructor() { + const inExp = inExperiment(DiscoveryUsingWorkers.experiment); + const dirLocators: (ILocator & IDisposable)[] = getSearchPathEntries() + .filter( + (dirname) => + // Filter out following directories: + // 1. Microsoft Store app directories: We have a store app locator that handles this. The + // python.exe available in these directories might not be python. It can be a store + // install shortcut that takes you to microsoft store. + // + // 2. Filter out pyenv shims: They are not actual python binaries, they are used to launch + // the binaries specified in .python-version file in the cwd. We should not be reporting + // those binaries as environments. + !isMicrosoftStoreDir(dirname) && !isPyenvShimDir(dirname), + ) + // Build a locator for each directory. + .map((dirname) => getDirFilesLocator(dirname, PythonEnvKind.System, [PythonEnvSource.PathEnvVar], inExp)); + this.disposables.push(...dirLocators); + this.locators = new Locators(dirLocators); + this.onChanged = this.locators.onChanged; + } + + public async dispose(): Promise { + this.locators.dispose(); + await this.disposables.dispose(); + } + + public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { + // Note that we do no filtering here, including to check if files + // are valid executables. That is left to callers (e.g. composite + // locators). + async function* iterator(it: IPythonEnvsIterator) { + const stopWatch = new StopWatch(); + traceInfo(`Searching windows known paths locator`); + for await (const env of it) { + yield env; + } + traceInfo(`Finished searching windows known paths locator: ${stopWatch.elapsedTime} milliseconds`); + } + return iterator(this.locators.iterEnvs(query)); + } +} + +async function* oldGetExecutables(dirname: string): AsyncIterableIterator { + for await (const entry of iterPythonExecutablesInDir(dirname)) { + if (await looksLikeBasicGlobalPython(entry)) { + yield entry.filename; + } + } +} + +async function* getExecutables(dirname: string): AsyncIterableIterator { + const executable = path.join(dirname, 'python.exe'); + if (await pathExists(executable)) { + yield executable; + } +} + +function getDirFilesLocator( + // These are passed through to DirFilesLocator. + dirname: string, + kind: PythonEnvKind, + source?: PythonEnvSource[], + inExp?: boolean, +): ILocator & IDisposable { + // For now we do not bother using a locator that watches for changes + // in the directory. If we did then we would use + // `DirFilesWatchingLocator`, but only if not \\windows\system32 and + // the `isDirWatchable()` (from fsWatchingLocator.ts) returns true. + const executableFunc = inExp ? getExecutables : oldGetExecutables; + const locator = new DirFilesLocator(dirname, kind, executableFunc, source); + const dispose = async () => undefined; + + // Really we should be checking for symlinks or something more + // sophisticated. Also, this should be done in ReducingLocator + // rather than in each low-level locator. In the meantime we + // take a naive approach. + async function* iterEnvs(query: PythonLocatorQuery): IPythonEnvsIterator { + yield* await getEnvs(locator.iterEnvs(query)).then((res) => res); + } + return { + providerId: locator.providerId, + iterEnvs, + dispose, + onChanged: locator.onChanged, + }; +} diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.ts new file mode 100644 index 000000000000..1447c2a90767 --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/lowLevel/windowsRegistryLocator.ts @@ -0,0 +1,75 @@ +/* eslint-disable require-yield */ +/* eslint-disable no-continue */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { PythonEnvKind, PythonEnvSource } from '../../info'; +import { BasicEnvInfo, IPythonEnvsIterator, Locator, PythonLocatorQuery, IEmitter } from '../../locator'; +import { getRegistryInterpreters } from '../../../common/windowsUtils'; +import { traceError, traceInfo } from '../../../../logging'; +import { isMicrosoftStoreDir } from '../../../common/environmentManagers/microsoftStoreEnv'; +import { PythonEnvsChangedEvent } from '../../watcher'; +import { DiscoveryUsingWorkers } from '../../../../common/experiments/groups'; +import { inExperiment } from '../../../common/externalDependencies'; +import { StopWatch } from '../../../../common/utils/stopWatch'; + +export const WINDOWS_REG_PROVIDER_ID = 'windows-registry'; + +export class WindowsRegistryLocator extends Locator { + public readonly providerId: string = WINDOWS_REG_PROVIDER_ID; + + // eslint-disable-next-line class-methods-use-this + public iterEnvs( + query?: PythonLocatorQuery, + useWorkerThreads = inExperiment(DiscoveryUsingWorkers.experiment), + ): IPythonEnvsIterator { + if (useWorkerThreads) { + /** + * Windows registry is slow and often not necessary, so notify completion immediately, but use watcher + * change events to signal for any new envs which are found. + */ + if (query?.providerId === this.providerId) { + // Query via change event, so iterate all envs. + return iterateEnvs(); + } + return iterateEnvsLazily(this.emitter); + } + return iterateEnvs(); + } +} + +async function* iterateEnvsLazily(changed: IEmitter): IPythonEnvsIterator { + loadAllEnvs(changed).ignoreErrors(); +} + +async function loadAllEnvs(changed: IEmitter) { + const stopWatch = new StopWatch(); + traceInfo('Searching for windows registry interpreters'); + changed.fire({ providerId: WINDOWS_REG_PROVIDER_ID }); + traceInfo(`Finished searching for windows registry interpreters: ${stopWatch.elapsedTime} milliseconds`); +} + +async function* iterateEnvs(): IPythonEnvsIterator { + const stopWatch = new StopWatch(); + traceInfo('Searching for windows registry interpreters'); + const interpreters = await getRegistryInterpreters(); // Value should already be loaded at this point, so this returns immediately. + for (const interpreter of interpreters) { + try { + // Filter out Microsoft Store app directories. We have a store app locator that handles this. + // The python.exe available in these directories might not be python. It can be a store install + // shortcut that takes you to microsoft store. + if (isMicrosoftStoreDir(interpreter.interpreterPath)) { + continue; + } + const env: BasicEnvInfo = { + kind: PythonEnvKind.OtherGlobal, + executablePath: interpreter.interpreterPath, + source: [PythonEnvSource.WindowsRegistry], + }; + yield env; + } catch (ex) { + traceError(`Failed to process environment: ${interpreter}`, ex); + } + } + traceInfo(`Finished searching for windows registry interpreters: ${stopWatch.elapsedTime} milliseconds`); +} diff --git a/src/client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.ts b/src/client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.ts new file mode 100644 index 000000000000..b815e1d30a89 --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/lowLevel/workspaceVirtualEnvLocator.ts @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { chain, iterable } from '../../../../common/utils/async'; +import { findInterpretersInDir, looksLikeBasicVirtualPython } from '../../../common/commonUtils'; +import { pathExists } from '../../../common/externalDependencies'; +import { isPipenvEnvironment } from '../../../common/environmentManagers/pipenv'; +import { isVenvEnvironment, isVirtualenvEnvironment } from '../../../common/environmentManagers/simplevirtualenvs'; +import { PythonEnvKind } from '../../info'; +import { BasicEnvInfo, IPythonEnvsIterator } from '../../locator'; +import { FSWatcherKind, FSWatchingLocator } from './fsWatchingLocator'; +import '../../../../common/extensions'; +import { asyncFilter } from '../../../../common/utils/arrayUtils'; +import { traceVerbose } from '../../../../logging'; + +/** + * Default number of levels of sub-directories to recurse when looking for interpreters. + */ +const DEFAULT_SEARCH_DEPTH = 2; + +/** + * Gets all default virtual environment locations to look for in a workspace. + */ +function getWorkspaceVirtualEnvDirs(root: string): Promise { + return asyncFilter([root, path.join(root, '.direnv')], pathExists); +} + +/** + * Gets the virtual environment kind for a given interpreter path. + * This only checks for environments created using venv, virtualenv, + * and virtualenvwrapper based environments. + * @param interpreterPath: Absolute path to the interpreter paths. + */ +async function getVirtualEnvKind(interpreterPath: string): Promise { + if (await isPipenvEnvironment(interpreterPath)) { + return PythonEnvKind.Pipenv; + } + + if (await isVenvEnvironment(interpreterPath)) { + return PythonEnvKind.Venv; + } + + if (await isVirtualenvEnvironment(interpreterPath)) { + return PythonEnvKind.VirtualEnv; + } + + return PythonEnvKind.Unknown; +} +/** + * Finds and resolves virtual environments created in workspace roots. + */ +export class WorkspaceVirtualEnvironmentLocator extends FSWatchingLocator { + public readonly providerId: string = 'workspaceVirtualEnvLocator'; + + public constructor(private readonly root: string) { + super( + () => getWorkspaceVirtualEnvDirs(this.root), + getVirtualEnvKind, + { + // Note detecting kind of virtual env depends on the file structure around the + // executable, so we need to wait before attempting to detect it. + delayOnCreated: 1000, + }, + FSWatcherKind.Workspace, + ); + } + + protected doIterEnvs(): IPythonEnvsIterator { + async function* iterator(root: string) { + const envRootDirs = await getWorkspaceVirtualEnvDirs(root); + const envGenerators = envRootDirs.map((envRootDir) => { + async function* generator() { + traceVerbose(`Searching for workspace virtual envs in: ${envRootDir}`); + + const executables = findInterpretersInDir(envRootDir, DEFAULT_SEARCH_DEPTH); + + for await (const entry of executables) { + const { filename } = entry; + // We only care about python.exe (on windows) and python (on linux/mac) + // Other version like python3.exe or python3.8 are often symlinks to + // python.exe or python in the same directory in the case of virtual + // environments. + if (await looksLikeBasicVirtualPython(entry)) { + // We should extract the kind here to avoid doing is*Environment() + // check multiple times. Those checks are file system heavy and + // we can use the kind to determine this anyway. + const kind = await getVirtualEnvKind(filename); + yield { kind, executablePath: filename }; + traceVerbose(`Workspace Virtual Environment: [added] ${filename}`); + } else { + traceVerbose(`Workspace Virtual Environment: [skipped] ${filename}`); + } + } + } + return generator(); + }); + + yield* iterable(chain(envGenerators)); + traceVerbose(`Finished searching for workspace virtual envs`); + } + + return iterator(this.root); + } +} diff --git a/src/client/pythonEnvironments/base/locators/wrappers.ts b/src/client/pythonEnvironments/base/locators/wrappers.ts new file mode 100644 index 000000000000..bfaede584f6f --- /dev/null +++ b/src/client/pythonEnvironments/base/locators/wrappers.ts @@ -0,0 +1,157 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// eslint-disable-next-line max-classes-per-file +import { Uri } from 'vscode'; +import { IDisposable } from '../../../common/types'; +import { iterEmpty } from '../../../common/utils/async'; +import { getURIFilter } from '../../../common/utils/misc'; +import { Disposables } from '../../../common/utils/resourceLifecycle'; +import { PythonEnvInfo } from '../info'; +import { BasicEnvInfo, ILocator, IPythonEnvsIterator, PythonLocatorQuery } from '../locator'; +import { combineIterators, Locators } from '../locators'; +import { LazyResourceBasedLocator } from './common/resourceBasedLocator'; + +/** + * A wrapper around all locators used by the extension. + */ + +export class ExtensionLocators extends Locators { + constructor( + // These are expected to be low-level locators (e.g. system). + private readonly nonWorkspace: ILocator[], + // This is expected to be a locator wrapping any found in + // the workspace (i.e. WorkspaceLocators). + private readonly workspace: ILocator, + ) { + super([...nonWorkspace, workspace]); + } + + public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { + const iterators: IPythonEnvsIterator[] = [this.workspace.iterEnvs(query)]; + if (!query?.searchLocations?.doNotIncludeNonRooted) { + const nonWorkspace = query?.providerId + ? this.nonWorkspace.filter((locator) => query.providerId === locator.providerId) + : this.nonWorkspace; + iterators.push(...nonWorkspace.map((loc) => loc.iterEnvs(query))); + } + return combineIterators(iterators); + } +} +type WorkspaceLocatorFactoryResult = ILocator & Partial; +type WorkspaceLocatorFactory = (root: Uri) => WorkspaceLocatorFactoryResult[]; +type RootURI = string; + +export type WatchRootsArgs = { + initRoot(root: Uri): void; + addRoot(root: Uri): void; + removeRoot(root: Uri): void; +}; +type WatchRootsFunc = (args: WatchRootsArgs) => IDisposable; +// XXX Factor out RootedLocators and MultiRootedLocators. +/** + * The collection of all workspace-specific locators used by the extension. + * + * The factories are used to produce the locators for each workspace folder. + */ + +export class WorkspaceLocators extends LazyResourceBasedLocator { + public readonly providerId: string = 'workspace-locators'; + + private readonly locators: Record, IDisposable]> = {}; + + private readonly roots: Record = {}; + + constructor(private readonly watchRoots: WatchRootsFunc, private readonly factories: WorkspaceLocatorFactory[]) { + super(); + this.activate().ignoreErrors(); + } + + public async dispose(): Promise { + await super.dispose(); + + // Clear all the roots. + const roots = Object.keys(this.roots).map((key) => this.roots[key]); + roots.forEach((root) => this.removeRoot(root)); + } + + protected doIterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { + const iterators = Object.keys(this.locators).map((key) => { + if (query?.searchLocations !== undefined) { + const root = this.roots[key]; + // Match any related search location. + const filter = getURIFilter(root, { checkParent: true, checkChild: true }); + // Ignore any requests for global envs. + if (!query.searchLocations.roots.some(filter)) { + // This workspace folder did not match the query, so skip it! + return iterEmpty(); + } + if (query.providerId && query.providerId !== this.providerId) { + // This is a request for a specific provider, so skip it. + return iterEmpty(); + } + } + // The query matches or was not location-specific. + const [locator] = this.locators[key]; + return locator.iterEnvs(query); + }); + return combineIterators(iterators); + } + + protected async initResources(): Promise { + const disposable = this.watchRoots({ + initRoot: (root: Uri) => this.addRoot(root), + addRoot: (root: Uri) => { + // Drop the old one, if necessary. + this.removeRoot(root); + this.addRoot(root); + this.emitter.fire({ searchLocation: root }); + }, + removeRoot: (root: Uri) => { + this.removeRoot(root); + this.emitter.fire({ searchLocation: root }); + }, + }); + this.disposables.push(disposable); + } + + private addRoot(root: Uri): void { + // Create the root's locator, wrapping each factory-generated locator. + const locators: ILocator[] = []; + const disposables = new Disposables(); + this.factories.forEach((create) => { + create(root).forEach((loc) => { + locators.push(loc); + if (loc.dispose !== undefined) { + disposables.push(loc as IDisposable); + } + }); + }); + const locator = new Locators(locators); + // Cache it. + const key = root.toString(); + this.locators[key] = [locator, disposables]; + this.roots[key] = root; + // Hook up the watchers. + disposables.push( + locator.onChanged((e) => { + if (e.searchLocation === undefined) { + e.searchLocation = root; + } + this.emitter.fire(e); + }), + ); + } + + private removeRoot(root: Uri): void { + const key = root.toString(); + const found = this.locators[key]; + if (found === undefined) { + return; + } + const [, disposables] = found; + delete this.locators[key]; + delete this.roots[key]; + disposables.dispose(); + } +} diff --git a/src/client/pythonEnvironments/base/watcher.ts b/src/client/pythonEnvironments/base/watcher.ts index c64a139d31c2..a9d0ef65595e 100644 --- a/src/client/pythonEnvironments/base/watcher.ts +++ b/src/client/pythonEnvironments/base/watcher.ts @@ -1,10 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -// tslint:disable:max-classes-per-file - import { Event, EventEmitter, Uri } from 'vscode'; -import { PythonEnvKind } from './info'; +import { FileChangeType } from '../../common/platform/fileSystemWatcher'; +import { PythonEnvInfo, PythonEnvKind } from './info'; // The use cases for `BasicPythonEnvsChangedEvent` are currently // hypothetical. However, there's a real chance they may prove @@ -18,17 +17,37 @@ import { PythonEnvKind } from './info'; */ export type BasicPythonEnvsChangedEvent = { kind?: PythonEnvKind; + type?: FileChangeType; }; /** * The full set of possible info for a Python environments event. - * - * @prop searchLocation - the location, if any, affected by the event */ export type PythonEnvsChangedEvent = BasicPythonEnvsChangedEvent & { + /** + * The location, if any, affected by the event. + */ + searchLocation?: Uri; + /** + * A specific provider, if any, affected by the event. + */ + providerId?: string; + /** + * The env, if any, affected by the event. + */ + envPath?: string; +}; + +export type PythonEnvCollectionChangedEvent = BasicPythonEnvCollectionChangedEvent & { + type?: FileChangeType; searchLocation?: Uri; }; +export type BasicPythonEnvCollectionChangedEvent = { + old?: PythonEnvInfo; + new?: PythonEnvInfo | undefined; +}; + /** * A "watcher" for events related to changes to Python environemts. * @@ -37,7 +56,7 @@ export type PythonEnvsChangedEvent = BasicPythonEnvsChangedEvent & { * events, their source, and the timing is entirely up to the watcher * implementation. */ -export interface IPythonEnvsWatcher { +export interface IPythonEnvsWatcher { /** * The hook for registering event listeners (callbacks). */ @@ -59,11 +78,12 @@ export interface IPythonEnvsWatcher implements IPythonEnvsWatcher { +export class PythonEnvsWatcher implements IPythonEnvsWatcher { /** * The hook for registering event listeners (callbacks). */ public readonly onChanged: Event; + private readonly didChange = new EventEmitter(); constructor() { @@ -73,7 +93,7 @@ export class PythonEnvsWatcher; - private watcher = new PythonEnvsWatcher(); + + private readonly watcher = new PythonEnvsWatcher(); + + private readonly disposables = new Disposables(); constructor(watchers: ReadonlyArray) { this.onChanged = this.watcher.onChanged; watchers.forEach((w) => { - w.onChanged((e) => this.watcher.fire(e)); + const disposable = w.onChanged((e) => this.watcher.fire(e)); + this.disposables.push(disposable); }); } -} - -// This matches the `vscode.Event` arg. -type EnvsEventListener = (e: PythonEnvsChangedEvent) => unknown; - -/** - * A watcher wrapper that can be disabled. - * - * If disabled, events emitted by the wrapped watcher are discarded. - */ -export class DisableableEnvsWatcher implements IPythonEnvsWatcher { - protected enabled = true; - constructor( - // To wrap more than one use `PythonEnvWatchers`. - private readonly wrapped: IPythonEnvsWatcher - ) {} - - /** - * Ensure that the watcher is enabled. - */ - public enable() { - this.enabled = true; - } - - /** - * Ensure that the watcher is disabled. - */ - public disable() { - this.enabled = false; - } - // This matches the signature of `vscode.Event`. - public onChanged(listener: EnvsEventListener, thisArgs?: unknown, disposables?: Disposable[]): Disposable { - return this.wrapped.onChanged( - (e: PythonEnvsChangedEvent) => { - if (this.enabled) { - listener(e); - } - }, - thisArgs, - disposables - ); + public async dispose(): Promise { + await this.disposables.dispose(); } } diff --git a/src/client/pythonEnvironments/common/commonUtils.ts b/src/client/pythonEnvironments/common/commonUtils.ts new file mode 100644 index 000000000000..4bd94e0402ab --- /dev/null +++ b/src/client/pythonEnvironments/common/commonUtils.ts @@ -0,0 +1,395 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as fs from 'fs'; +import * as path from 'path'; +import { convertFileType, DirEntry, FileType, getFileFilter, getFileType } from '../../common/utils/filesystem'; +import { getOSType, OSType } from '../../common/utils/platform'; +import { traceError, traceVerbose } from '../../logging'; +import { PythonVersion, UNKNOWN_PYTHON_VERSION } from '../base/info'; +import { comparePythonVersionSpecificity } from '../base/info/env'; +import { parseVersion } from '../base/info/pythonVersion'; +import { getPythonVersionFromConda } from './environmentManagers/conda'; +import { getPythonVersionFromPyvenvCfg } from './environmentManagers/simplevirtualenvs'; +import { isFile, normCasePath } from './externalDependencies'; +import * as posix from './posixUtils'; +import * as windows from './windowsUtils'; + +const matchStandardPythonBinFilename = + getOSType() === OSType.Windows ? windows.matchPythonBinFilename : posix.matchPythonBinFilename; +type FileFilterFunc = (filename: string) => boolean; + +/** + * Returns `true` if path provided is likely a python executable than a folder path. + */ +export async function isPythonExecutable(filePath: string): Promise { + const isMatch = matchStandardPythonBinFilename(filePath); + if (isMatch && getOSType() === OSType.Windows) { + // On Windows it's fair to assume a path ending with `.exe` denotes a file. + return true; + } + if (await isFile(filePath)) { + return true; + } + return false; +} + +/** + * Searches recursively under the given `root` directory for python interpreters. + * @param root : Directory where the search begins. + * @param recurseLevels : Number of levels to search for from the root directory. + * @param filter : Callback that identifies directories to ignore. + */ +export async function* findInterpretersInDir( + root: string, + recurseLevel?: number, + filterSubDir?: FileFilterFunc, + ignoreErrors = true, +): AsyncIterableIterator { + // "checkBin" is a local variable rather than global + // so we can stub out getOSType() during unit testing. + const checkBin = getOSType() === OSType.Windows ? windows.matchPythonBinFilename : posix.matchPythonBinFilename; + const cfg = { + ignoreErrors, + filterSubDir, + filterFile: checkBin, + // Make no-recursion the default for backward compatibility. + maxDepth: recurseLevel || 0, + }; + // We use an initial depth of 1. + for await (const entry of walkSubTree(root, 1, cfg)) { + const { filename, filetype } = entry; + if (filetype === FileType.File || filetype === FileType.SymbolicLink) { + if (matchFile(filename, checkBin, ignoreErrors)) { + yield entry; + } + } + // We ignore all other file types. + } +} + +/** + * Find all Python executables in the given directory. + */ +export async function* iterPythonExecutablesInDir( + dirname: string, + opts: { + ignoreErrors: boolean; + } = { ignoreErrors: true }, +): AsyncIterableIterator { + const readDirOpts = { + ...opts, + filterFile: matchStandardPythonBinFilename, + }; + const entries = await readDirEntries(dirname, readDirOpts); + for (const entry of entries) { + const { filetype } = entry; + if (filetype === FileType.File || filetype === FileType.SymbolicLink) { + yield entry; + } + // We ignore all other file types. + } +} + +// This function helps simplify the recursion case. +async function* walkSubTree( + subRoot: string, + // "currentDepth" is the depth of the current level of recursion. + currentDepth: number, + cfg: { + filterSubDir: FileFilterFunc | undefined; + maxDepth: number; + ignoreErrors: boolean; + }, +): AsyncIterableIterator { + const entries = await readDirEntries(subRoot, cfg); + for (const entry of entries) { + yield entry; + + const { filename, filetype } = entry; + if (filetype === FileType.Directory) { + if (cfg.maxDepth < 0 || currentDepth <= cfg.maxDepth) { + if (matchFile(filename, cfg.filterSubDir, cfg.ignoreErrors)) { + yield* walkSubTree(filename, currentDepth + 1, cfg); + } + } + } + } +} + +async function readDirEntries( + dirname: string, + opts: { + filterFilename?: FileFilterFunc; + ignoreErrors: boolean; + } = { ignoreErrors: true }, +): Promise { + const ignoreErrors = opts.ignoreErrors || false; + if (opts.filterFilename && getOSType() === OSType.Windows) { + // Since `readdir()` using "withFileTypes" is not efficient + // on Windows, we take advantage of the filter. + let basenames: string[]; + try { + basenames = await fs.promises.readdir(dirname); + } catch (err) { + const exception = err as NodeJS.ErrnoException; + // Treat a missing directory as empty. + if (exception.code === 'ENOENT') { + return []; + } + if (ignoreErrors) { + traceError(`readdir() failed for "${dirname}" (${err})`); + return []; + } + throw err; // re-throw + } + const filenames = basenames + .map((b) => path.join(dirname, b)) + .filter((f) => matchFile(f, opts.filterFilename, ignoreErrors)); + return Promise.all( + filenames.map(async (filename) => { + const filetype = (await getFileType(filename, opts)) || FileType.Unknown; + return { filename, filetype }; + }), + ); + } + + let raw: fs.Dirent[]; + try { + raw = await fs.promises.readdir(dirname, { withFileTypes: true }); + } catch (err) { + const exception = err as NodeJS.ErrnoException; + // Treat a missing directory as empty. + if (exception.code === 'ENOENT') { + return []; + } + if (ignoreErrors) { + traceError(`readdir() failed for "${dirname}" (${err})`); + return []; + } + throw err; // re-throw + } + // (FYI) + // Normally we would have to do an extra (expensive) `fs.lstat()` + // here for each file to determine its file type. However, we + // avoid this by using the "withFileTypes" option to `readdir()` + // above. On non-Windows the file type of each entry is preserved + // for free. Unfortunately, on Windows it actually does an + // `lstat()` under the hood, so it isn't a win. Regardless, + // if we needed more information than just the file type + // then we would be forced to incur the extra cost + // of `lstat()` anyway. + const entries = raw.map((entry) => { + const filename = path.join(dirname, entry.name); + const filetype = convertFileType(entry); + return { filename, filetype }; + }); + if (opts.filterFilename) { + return entries.filter((e) => matchFile(e.filename, opts.filterFilename, ignoreErrors)); + } + return entries; +} + +function matchFile( + filename: string, + filterFile: FileFilterFunc | undefined, + // If "ignoreErrors" is true then we treat a failed filter + // as though it returned `false`. + ignoreErrors = true, +): boolean { + if (filterFile === undefined) { + return true; + } + try { + return filterFile(filename); + } catch (err) { + if (ignoreErrors) { + traceError(`filter failed for "${filename}" (${err})`); + return false; + } + throw err; // re-throw + } +} + +/** + * Looks for files in the same directory which might have version in their name. + * @param interpreterPath + */ +async function getPythonVersionFromNearByFiles(interpreterPath: string): Promise { + const root = path.dirname(interpreterPath); + let version = UNKNOWN_PYTHON_VERSION; + for await (const entry of findInterpretersInDir(root)) { + const { filename } = entry; + try { + const curVersion = parseVersion(path.basename(filename)); + if (comparePythonVersionSpecificity(curVersion, version) > 0) { + version = curVersion; + } + } catch (ex) { + // Ignore any parse errors + } + } + return version; +} + +/** + * This function does the best effort of finding version of python without running the + * python binary. + * @param interpreterPath Absolute path to the interpreter. + * @param hint Any string that might contain version info. + */ +export async function getPythonVersionFromPath(interpreterPath: string, hint?: string): Promise { + let versionA; + try { + versionA = hint ? parseVersion(hint) : UNKNOWN_PYTHON_VERSION; + } catch (ex) { + versionA = UNKNOWN_PYTHON_VERSION; + } + const versionB = interpreterPath ? await getPythonVersionFromNearByFiles(interpreterPath) : UNKNOWN_PYTHON_VERSION; + traceVerbose('Best effort version B for', interpreterPath, JSON.stringify(versionB)); + const versionC = interpreterPath ? await getPythonVersionFromPyvenvCfg(interpreterPath) : UNKNOWN_PYTHON_VERSION; + traceVerbose('Best effort version C for', interpreterPath, JSON.stringify(versionC)); + const versionD = interpreterPath ? await getPythonVersionFromConda(interpreterPath) : UNKNOWN_PYTHON_VERSION; + traceVerbose('Best effort version D for', interpreterPath, JSON.stringify(versionD)); + + let version = UNKNOWN_PYTHON_VERSION; + for (const v of [versionA, versionB, versionC, versionD]) { + version = comparePythonVersionSpecificity(version, v) > 0 ? version : v; + } + return version; +} + +/** + * Decide if the file is meets the given criteria for a Python executable. + */ +async function checkPythonExecutable( + executable: string | DirEntry, + opts: { + matchFilename?: (f: string) => boolean; + filterFile?: (f: string | DirEntry) => Promise; + }, +): Promise { + const matchFilename = opts.matchFilename || matchStandardPythonBinFilename; + const filename = typeof executable === 'string' ? executable : executable.filename; + + if (!matchFilename(filename)) { + return false; + } + + // This should occur after we match file names. This is to avoid doing potential + // `lstat` calls on too many files which can slow things down. + if (opts.filterFile && !(await opts.filterFile(executable))) { + return false; + } + + // For some use cases it would also be a good idea to verify that + // the file is executable. That is a relatively expensive operation + // (a stat on linux and actually executing the file on Windows), so + // at best it should be an optional check. If we went down this + // route then it would be worth supporting `fs.Stats` as a type + // for the "executable" arg. + // + // Regardless, currently there is no code that would use such + // an option, so for now we don't bother supporting it. + + return true; +} + +const filterGlobalExecutable = getFileFilter({ ignoreFileType: FileType.SymbolicLink })!; + +/** + * Decide if the file is a typical Python executable. + * + * This is a best effort operation with a focus on the common cases + * and on efficiency. The filename must be basic (python/python.exe). + * For global envs, symlinks are ignored. + */ +export async function looksLikeBasicGlobalPython(executable: string | DirEntry): Promise { + // "matchBasic" is a local variable rather than global + // so we can stub out getOSType() during unit testing. + const matchBasic = + getOSType() === OSType.Windows ? windows.matchBasicPythonBinFilename : posix.matchBasicPythonBinFilename; + + // We could be more permissive here by using matchPythonBinFilename(). + // Originally one key motivation for the "basic" check was to avoid + // symlinks (which often look like python3.exe, etc., particularly + // on Windows). However, the symbolic link check here eliminates + // that rationale to an extent. + // (See: https://github.com/microsoft/vscode-python/issues/15447) + const matchFilename = matchBasic; + const filterFile = filterGlobalExecutable; + return checkPythonExecutable(executable, { matchFilename, filterFile }); +} + +/** + * Decide if the file is a typical Python executable. + * + * This is a best effort operation with a focus on the common cases + * and on efficiency. The filename must be basic (python/python.exe). + * For global envs, symlinks are ignored. + */ +export async function looksLikeBasicVirtualPython(executable: string | DirEntry): Promise { + // "matchBasic" is a local variable rather than global + // so we can stub out getOSType() during unit testing. + const matchBasic = + getOSType() === OSType.Windows ? windows.matchBasicPythonBinFilename : posix.matchBasicPythonBinFilename; + + // With virtual environments, we match only the simplest name + // (e.g. `python`) and we do not ignore symlinks. + const matchFilename = matchBasic; + const filterFile = undefined; + return checkPythonExecutable(executable, { matchFilename, filterFile }); +} + +/** + * This function looks specifically for 'python' or 'python.exe' binary in the sub folders of a given + * environment directory. + * @param envDir Absolute path to the environment directory + */ +export async function getInterpreterPathFromDir( + envDir: string, + opts: { + global?: boolean; + ignoreErrors?: boolean; + } = {}, +): Promise { + const recurseLevel = 2; + + // Ignore any folders or files that not directly python binary related. + function filterDir(dirname: string): boolean { + const lower = path.basename(dirname).toLowerCase(); + return ['bin', 'scripts'].includes(lower); + } + + // Search in the sub-directories for python binary + const matchExecutable = opts.global ? looksLikeBasicGlobalPython : looksLikeBasicVirtualPython; + const executables = findInterpretersInDir(envDir, recurseLevel, filterDir, opts.ignoreErrors); + for await (const entry of executables) { + if (await matchExecutable(entry)) { + return entry.filename; + } + } + return undefined; +} + +/** + * Gets the root environment directory based on the absolute path to the python + * interpreter binary. + * @param interpreterPath Absolute path to the python interpreter + */ +export function getEnvironmentDirFromPath(interpreterPath: string): string { + const skipDirs = ['bin', 'scripts']; + + // env <--- Return this directory if it is not 'bin' or 'scripts' + // |__ python <--- interpreterPath + const dir = path.basename(path.dirname(interpreterPath)); + if (!skipDirs.map((e) => normCasePath(e)).includes(normCasePath(dir))) { + return path.dirname(interpreterPath); + } + + // This is the best next guess. + // env <--- Return this directory if it is not 'bin' or 'scripts' + // |__ bin or Scripts + // |__ python <--- interpreterPath + return path.dirname(path.dirname(interpreterPath)); +} diff --git a/src/client/pythonEnvironments/common/environmentIdentifier.ts b/src/client/pythonEnvironments/common/environmentIdentifier.ts index 64d62721986f..89ff84823673 100644 --- a/src/client/pythonEnvironments/common/environmentIdentifier.ts +++ b/src/client/pythonEnvironments/common/environmentIdentifier.ts @@ -1,85 +1,75 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { isCondaEnvironment } from '../discovery/locators/services/condaLocator'; -import { isPipenvEnvironment } from '../discovery/locators/services/pipEnvHelper'; -import { isPyenvEnvironment } from '../discovery/locators/services/pyenvLocator'; -import { isVenvEnvironment } from '../discovery/locators/services/venvLocator'; -import { isVirtualenvEnvironment } from '../discovery/locators/services/virtualenvLocator'; -import { isVirtualenvwrapperEnvironment } from '../discovery/locators/services/virtualenvwrapperLocator'; -import { isWindowsStoreEnvironment } from '../discovery/locators/services/windowsStoreLocator'; -import { EnvironmentType } from '../info'; +import { traceWarn } from '../../logging'; +import { PythonEnvKind } from '../base/info'; +import { getPrioritizedEnvKinds } from '../base/info/envKind'; +import { isCondaEnvironment } from './environmentManagers/conda'; +import { isGloballyInstalledEnv } from './environmentManagers/globalInstalledEnvs'; +import { isPipenvEnvironment } from './environmentManagers/pipenv'; +import { isPoetryEnvironment } from './environmentManagers/poetry'; +import { isPyenvEnvironment } from './environmentManagers/pyenv'; +import { + isVenvEnvironment, + isVirtualenvEnvironment as isVirtualEnvEnvironment, + isVirtualenvwrapperEnvironment as isVirtualEnvWrapperEnvironment, +} from './environmentManagers/simplevirtualenvs'; +import { isMicrosoftStoreEnvironment } from './environmentManagers/microsoftStoreEnv'; +import { isActiveStateEnvironment } from './environmentManagers/activestate'; +import { isPixiEnvironment } from './environmentManagers/pixi'; -/** - * Gets a prioritized list of environment types for identification. - * @deprecated - * - * Remarks: This is the order of detection based on how the various distributions and tools - * configure the environment, and the fall back for identification. - * Top level we have the following environment types, since they leave a unique signature - * in the environment or * use a unique path for the environments they create. - * 1. Conda - * 2. Windows Store - * 3. PipEnv - * 4. Pyenv - * 5. Poetry - * - * Next level we have the following virtual environment tools. The are here because they - * are consumed by the tools above, and can also be used independently. - * 1. venv - * 2. virtualenvwrapper - * 3. virtualenv - * - * Last category is globally installed python, or system python. - */ -export function getPrioritizedEnvironmentType():EnvironmentType[] { - return [ - EnvironmentType.Conda, - EnvironmentType.WindowsStore, - EnvironmentType.Pipenv, - EnvironmentType.Pyenv, - EnvironmentType.Poetry, - EnvironmentType.Venv, - EnvironmentType.VirtualEnvWrapper, - EnvironmentType.VirtualEnv, - EnvironmentType.Global, - EnvironmentType.System, - EnvironmentType.Unknown, - ]; -} +const notImplemented = () => Promise.resolve(false); -function getIdentifiers(): Map Promise> { - const notImplemented = () => Promise.resolve(false); +function getIdentifiers(): Map Promise> { const defaultTrue = () => Promise.resolve(true); - const identifier: Map Promise> = new Map(); - Object.keys(EnvironmentType).forEach((k:string) => { - identifier.set(k as EnvironmentType, notImplemented); + const identifier: Map Promise> = new Map(); + Object.values(PythonEnvKind).forEach((k) => { + identifier.set(k, notImplemented); }); - identifier.set(EnvironmentType.Conda, isCondaEnvironment); - identifier.set(EnvironmentType.WindowsStore, isWindowsStoreEnvironment); - identifier.set(EnvironmentType.Pipenv, isPipenvEnvironment); - identifier.set(EnvironmentType.Pyenv, isPyenvEnvironment); - identifier.set(EnvironmentType.Venv, isVenvEnvironment); - identifier.set(EnvironmentType.VirtualEnvWrapper, isVirtualenvwrapperEnvironment); - identifier.set(EnvironmentType.VirtualEnv, isVirtualenvEnvironment); - identifier.set(EnvironmentType.Unknown, defaultTrue); + identifier.set(PythonEnvKind.Conda, isCondaEnvironment); + identifier.set(PythonEnvKind.MicrosoftStore, isMicrosoftStoreEnvironment); + identifier.set(PythonEnvKind.Pipenv, isPipenvEnvironment); + identifier.set(PythonEnvKind.Pyenv, isPyenvEnvironment); + identifier.set(PythonEnvKind.Poetry, isPoetryEnvironment); + identifier.set(PythonEnvKind.Pixi, isPixiEnvironment); + identifier.set(PythonEnvKind.Venv, isVenvEnvironment); + identifier.set(PythonEnvKind.VirtualEnvWrapper, isVirtualEnvWrapperEnvironment); + identifier.set(PythonEnvKind.VirtualEnv, isVirtualEnvEnvironment); + identifier.set(PythonEnvKind.ActiveState, isActiveStateEnvironment); + identifier.set(PythonEnvKind.Unknown, defaultTrue); + identifier.set(PythonEnvKind.OtherGlobal, isGloballyInstalledEnv); return identifier; } +export function isIdentifierRegistered(kind: PythonEnvKind): boolean { + const identifiers = getIdentifiers(); + const identifier = identifiers.get(kind); + if (identifier === notImplemented) { + return false; + } + return true; +} + /** * Returns environment type. - * @param {string} interpreterPath : Absolute path to the python interpreter binary. - * @returns {EnvironmentType} + * @param {string} path : Absolute path to the python interpreter binary or path to environment. + * @returns {PythonEnvKind} */ -export async function identifyEnvironment(interpreterPath: string): Promise { +export async function identifyEnvironment(path: string): Promise { const identifiers = getIdentifiers(); - const prioritizedEnvTypes = getPrioritizedEnvironmentType(); + const prioritizedEnvTypes = getPrioritizedEnvKinds(); for (const e of prioritizedEnvTypes) { const identifier = identifiers.get(e); - if (identifier && await identifier(interpreterPath)) { + if ( + identifier && + (await identifier(path).catch((ex) => { + traceWarn(`Identifier for ${e} failed to identify ${path}`, ex); + return false; + })) + ) { return e; } } - return EnvironmentType.Unknown; + return PythonEnvKind.Unknown; } diff --git a/src/client/pythonEnvironments/common/environmentManagers/activestate.ts b/src/client/pythonEnvironments/common/environmentManagers/activestate.ts new file mode 100644 index 000000000000..5f22a96e4f83 --- /dev/null +++ b/src/client/pythonEnvironments/common/environmentManagers/activestate.ts @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as path from 'path'; +import { dirname } from 'path'; +import { + arePathsSame, + getPythonSetting, + onDidChangePythonSetting, + pathExists, + shellExecute, +} from '../externalDependencies'; +import { cache } from '../../../common/utils/decorators'; +import { traceError, traceVerbose } from '../../../logging'; +import { getOSType, getUserHomeDir, OSType } from '../../../common/utils/platform'; + +export const ACTIVESTATETOOLPATH_SETTING_KEY = 'activeStateToolPath'; + +const STATE_GENERAL_TIMEOUT = 5000; + +export type ProjectInfo = { + name: string; + organization: string; + local_checkouts: string[]; // eslint-disable-line camelcase + executables: string[]; +}; + +export async function isActiveStateEnvironment(interpreterPath: string): Promise { + const execDir = path.dirname(interpreterPath); + const runtimeDir = path.dirname(execDir); + return pathExists(path.join(runtimeDir, '_runtime_store')); +} + +export class ActiveState { + private static statePromise: Promise | undefined; + + public static async getState(): Promise { + if (ActiveState.statePromise === undefined) { + ActiveState.statePromise = ActiveState.locate(); + } + return ActiveState.statePromise; + } + + constructor() { + onDidChangePythonSetting(ACTIVESTATETOOLPATH_SETTING_KEY, () => { + ActiveState.statePromise = undefined; + }); + } + + public static getStateToolDir(): string | undefined { + const home = getUserHomeDir(); + if (!home) { + return undefined; + } + return getOSType() === OSType.Windows + ? path.join(home, 'AppData', 'Local', 'ActiveState', 'StateTool') + : path.join(home, '.local', 'ActiveState', 'StateTool'); + } + + private static async locate(): Promise { + const stateToolDir = this.getStateToolDir(); + const stateCommand = + getPythonSetting(ACTIVESTATETOOLPATH_SETTING_KEY) ?? ActiveState.defaultStateCommand; + if (stateToolDir && ((await pathExists(stateToolDir)) || stateCommand !== this.defaultStateCommand)) { + return new ActiveState(); + } + return undefined; + } + + public async getProjects(): Promise { + return this.getProjectsCached(); + } + + private static readonly defaultStateCommand: string = 'state'; + + // eslint-disable-next-line class-methods-use-this + @cache(30_000, true, 10_000) + private async getProjectsCached(): Promise { + try { + const stateCommand = + getPythonSetting(ACTIVESTATETOOLPATH_SETTING_KEY) ?? ActiveState.defaultStateCommand; + const result = await shellExecute(`${stateCommand} projects -o editor`, { + timeout: STATE_GENERAL_TIMEOUT, + }); + if (!result) { + return undefined; + } + let output = result.stdout.trimEnd(); + if (output[output.length - 1] === '\0') { + // '\0' is a record separator. + output = output.substring(0, output.length - 1); + } + traceVerbose(`${stateCommand} projects -o editor: ${output}`); + const projects = JSON.parse(output); + ActiveState.setCachedProjectInfo(projects); + return projects; + } catch (ex) { + traceError(ex); + return undefined; + } + } + + // Stored copy of known projects. isActiveStateEnvironmentForWorkspace() is + // not async, so getProjects() cannot be used. ActiveStateLocator sets this + // when it resolves project info. + private static cachedProjectInfo: ProjectInfo[] = []; + + public static getCachedProjectInfo(): ProjectInfo[] { + return this.cachedProjectInfo; + } + + private static setCachedProjectInfo(projects: ProjectInfo[]): void { + this.cachedProjectInfo = projects; + } +} + +export function isActiveStateEnvironmentForWorkspace(interpreterPath: string, workspacePath: string): boolean { + const interpreterDir = dirname(interpreterPath); + for (const project of ActiveState.getCachedProjectInfo()) { + if (project.executables) { + for (const [i, dir] of project.executables.entries()) { + // Note multiple checkouts for the same interpreter may exist. + // Check them all. + if (arePathsSame(dir, interpreterDir) && arePathsSame(workspacePath, project.local_checkouts[i])) { + return true; + } + } + } + } + return false; +} diff --git a/src/client/pythonEnvironments/common/environmentManagers/conda.ts b/src/client/pythonEnvironments/common/environmentManagers/conda.ts new file mode 100644 index 000000000000..c1bfd7d68bc2 --- /dev/null +++ b/src/client/pythonEnvironments/common/environmentManagers/conda.ts @@ -0,0 +1,647 @@ +import * as path from 'path'; +import { lt, SemVer } from 'semver'; +import * as fsapi from '../../../common/platform/fs-paths'; +import { getEnvironmentVariable, getOSType, getUserHomeDir, OSType } from '../../../common/utils/platform'; +import { + arePathsSame, + getPythonSetting, + isParentPath, + pathExists, + readFile, + onDidChangePythonSetting, + exec, +} from '../externalDependencies'; + +import { PythonVersion, UNKNOWN_PYTHON_VERSION } from '../../base/info'; +import { parseVersion } from '../../base/info/pythonVersion'; + +import { getRegistryInterpreters } from '../windowsUtils'; +import { EnvironmentType, PythonEnvironment } from '../../info'; +import { cache } from '../../../common/utils/decorators'; +import { isTestExecution } from '../../../common/constants'; +import { traceError, traceVerbose } from '../../../logging'; +import { OUTPUT_MARKER_SCRIPT } from '../../../common/process/internal/scripts'; +import { splitLines } from '../../../common/stringUtils'; +import { SpawnOptions } from '../../../common/process/types'; +import { sleep } from '../../../common/utils/async'; +import { getConfiguration } from '../../../common/vscodeApis/workspaceApis'; + +export const AnacondaCompanyName = 'Anaconda, Inc.'; +export const CONDAPATH_SETTING_KEY = 'condaPath'; +export type CondaEnvironmentInfo = { + name: string; + path: string; +}; + +// This type corresponds to the output of "conda info --json", and property +// names must be spelled exactly as they are in order to match the schema. +export type CondaInfo = { + envs?: string[]; + envs_dirs?: string[]; // eslint-disable-line camelcase + 'sys.version'?: string; + 'sys.prefix'?: string; + python_version?: string; // eslint-disable-line camelcase + default_prefix?: string; // eslint-disable-line camelcase + root_prefix?: string; // eslint-disable-line camelcase + conda_version?: string; // eslint-disable-line camelcase + conda_shlvl?: number; // eslint-disable-line camelcase + config_files?: string[]; // eslint-disable-line camelcase + rc_path?: string; // eslint-disable-line camelcase + sys_rc_path?: string; // eslint-disable-line camelcase + user_rc_path?: string; // eslint-disable-line camelcase +}; + +type CondaEnvInfo = { + prefix: string; + name?: string; +}; + +/** + * Return the list of conda env interpreters. + */ +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export async function parseCondaInfo( + info: CondaInfo, + getPythonPath: (condaEnv: string) => string, + fileExists: (filename: string) => Promise, + getPythonInfo: (python: string) => Promise | undefined>, +) { + // The root of the conda environment is itself a Python interpreter + // envs reported as e.g.: /Users/bob/miniconda3/envs/someEnv. + const envs = Array.isArray(info.envs) ? info.envs : []; + if (info.default_prefix && info.default_prefix.length > 0) { + envs.push(info.default_prefix); + } + + const promises = envs.map(async (envPath) => { + const pythonPath = getPythonPath(envPath); + + if (!(await fileExists(pythonPath))) { + return undefined; + } + const details = await getPythonInfo(pythonPath); + if (!details) { + return undefined; + } + + return { + ...(details as PythonEnvironment), + path: pythonPath, + companyDisplayName: AnacondaCompanyName, + envType: EnvironmentType.Conda, + envPath, + }; + }); + + return Promise.all(promises) + .then((interpreters) => interpreters.filter((interpreter) => interpreter !== null && interpreter !== undefined)) + + .then((interpreters) => interpreters.map((interpreter) => interpreter!)); +} + +export function getCondaMetaPaths(interpreterPathOrEnvPath: string): string[] { + const condaMetaDir = 'conda-meta'; + + // Check if the conda-meta directory is in the same directory as the interpreter. + // This layout is common in Windows. + // env + // |__ conda-meta <--- check if this directory exists + // |__ python.exe <--- interpreterPath + const condaEnvDir1 = path.join(path.dirname(interpreterPathOrEnvPath), condaMetaDir); + + // Check if the conda-meta directory is in the parent directory relative to the interpreter. + // This layout is common on linux/Mac. + // env + // |__ conda-meta <--- check if this directory exists + // |__ bin + // |__ python <--- interpreterPath + const condaEnvDir2 = path.join(path.dirname(path.dirname(interpreterPathOrEnvPath)), condaMetaDir); + + const condaEnvDir3 = path.join(interpreterPathOrEnvPath, condaMetaDir); + + // The paths are ordered in the most common to least common + return [condaEnvDir1, condaEnvDir2, condaEnvDir3]; +} + +/** + * Checks if the given interpreter path belongs to a conda environment. Using + * known folder layout, and presence of 'conda-meta' directory. + * @param {string} interpreterPathOrEnvPath: Absolute path to any python interpreter. + * + * Remarks: This is what we will use to begin with. Another approach we can take + * here is to parse ~/.conda/environments.txt. This file will have list of conda + * environments. We can compare the interpreter path against the paths in that file. + * We don't want to rely on this file because it is an implementation detail of + * conda. If it turns out that the layout based identification is not sufficient + * that is the next alternative that is cheap. + * + * sample content of the ~/.conda/environments.txt: + * C:\envs\myenv + * C:\ProgramData\Miniconda3 + * + * Yet another approach is to use `conda env list --json` and compare the returned env + * list to see if the given interpreter path belongs to any of the returned environments. + * This approach is heavy, and involves running a binary. For now we decided not to + * take this approach, since it does not look like we need it. + * + * sample output from `conda env list --json`: + * conda env list --json + * { + * "envs": [ + * "C:\\envs\\myenv", + * "C:\\ProgramData\\Miniconda3" + * ] + * } + */ +export async function isCondaEnvironment(interpreterPathOrEnvPath: string): Promise { + const condaMetaPaths = getCondaMetaPaths(interpreterPathOrEnvPath); + // We don't need to test all at once, testing each one here + for (const condaMeta of condaMetaPaths) { + if (await pathExists(condaMeta)) { + return true; + } + } + return false; +} + +/** + * Gets path to conda's `environments.txt` file. More info https://github.com/conda/conda/issues/11845. + */ +export async function getCondaEnvironmentsTxt(): Promise { + const homeDir = getUserHomeDir(); + if (!homeDir) { + return []; + } + const environmentsTxt = path.join(homeDir, '.conda', 'environments.txt'); + return [environmentsTxt]; +} + +/** + * Extracts version information from `conda-meta/history` near a given interpreter. + * @param interpreterPath Absolute path to the interpreter + * + * Remarks: This function looks for `conda-meta/history` usually in the same or parent directory. + * Reads the `conda-meta/history` and finds the line that contains 'python-3.9.0`. Gets the + * version string from that lines and parses it. + */ +export async function getPythonVersionFromConda(interpreterPath: string): Promise { + const configPaths = getCondaMetaPaths(interpreterPath).map((p) => path.join(p, 'history')); + const pattern = /\:python-(([\d\.a-z]?)+)/; + + // We want to check each of those locations in the order. There is no need to look at + // all of them in parallel. + for (const configPath of configPaths) { + if (await pathExists(configPath)) { + try { + const lines = splitLines(await readFile(configPath)); + + // Sample data: + // +defaults/linux-64::pip-20.2.4-py38_0 + // +defaults/linux-64::python-3.8.5-h7579374_1 + // +defaults/linux-64::readline-8.0-h7b6447c_0 + const pythonVersionStrings = lines + .map((line) => { + // Here we should have only lines with 'python-' in it. + // +defaults/linux-64::python-3.8.5-h7579374_1 + + const matches = pattern.exec(line); + // Typically there will be 3 matches + // 0: "python-3.8.5" + // 1: "3.8.5" + // 2: "5" + + // we only need the second one + return matches ? matches[1] : ''; + }) + .filter((v) => v.length > 0); + + if (pythonVersionStrings.length > 0) { + const last = pythonVersionStrings.length - 1; + return parseVersion(pythonVersionStrings[last].trim()); + } + } catch (ex) { + // There is usually only one `conda-meta/history`. If we found, it but + // failed to parse it, then just return here. No need to look for versions + // any further. + return UNKNOWN_PYTHON_VERSION; + } + } + } + + return UNKNOWN_PYTHON_VERSION; +} + +/** + * Return the interpreter's filename for the given environment. + */ +export function getCondaInterpreterPath(condaEnvironmentPath: string): string { + // where to find the Python binary within a conda env. + const relativePath = getOSType() === OSType.Windows ? 'python.exe' : path.join('bin', 'python'); + const filePath = path.join(condaEnvironmentPath, relativePath); + return filePath; +} + +// Minimum version number of conda required to be able to use 'conda run' with '--no-capture-output' flag. +export const CONDA_RUN_VERSION = '4.9.0'; +export const CONDA_ACTIVATION_TIMEOUT = 45000; +const CONDA_GENERAL_TIMEOUT = 45000; + +/** Wraps the "conda" utility, and exposes its functionality. + */ +export class Conda { + /** + * Locating conda binary is expensive, since it potentially involves spawning or + * trying to spawn processes; so it's done lazily and asynchronously. Methods that + * need a Conda instance should use getConda() to obtain it, and should never access + * this property directly. + */ + private static condaPromise = new Map>(); + + private condaInfoCached = new Map | undefined>(); + + /** + * Carries path to conda binary to be used for shell execution. + */ + public readonly shellCommand: string; + + /** + * Creates a Conda service corresponding to the corresponding "conda" command. + * + * @param command - Command used to spawn conda. This has the same meaning as the + * first argument of spawn() - i.e. it can be a full path, or just a binary name. + */ + constructor( + readonly command: string, + shellCommand?: string, + private readonly shellPath?: string, + private readonly useWorkerThreads?: boolean, + ) { + if (this.useWorkerThreads === undefined) { + this.useWorkerThreads = false; + } + this.shellCommand = shellCommand ?? command; + onDidChangePythonSetting(CONDAPATH_SETTING_KEY, () => { + Conda.condaPromise = new Map>(); + }); + } + + public static async getConda(shellPath?: string): Promise { + if (Conda.condaPromise.get(shellPath) === undefined || isTestExecution()) { + Conda.condaPromise.set(shellPath, Conda.locate(shellPath)); + } + return Conda.condaPromise.get(shellPath); + } + + public static setConda(condaPath: string): void { + Conda.condaPromise.set(undefined, Promise.resolve(new Conda(condaPath))); + } + + /** + * Locates the preferred "conda" utility on this system by considering user settings, + * binaries on PATH, Python interpreters in the registry, and known install locations. + * + * @return A Conda instance corresponding to the binary, if successful; otherwise, undefined. + */ + private static async locate(shellPath?: string): Promise { + traceVerbose(`Searching for conda.`); + const home = getUserHomeDir(); + let customCondaPath: string | undefined = 'conda'; + try { + customCondaPath = getPythonSetting(CONDAPATH_SETTING_KEY); + } catch (ex) { + traceError(`Failed to get conda path setting, ${ex}`); + } + const suffix = getOSType() === OSType.Windows ? 'Scripts\\conda.exe' : 'bin/conda'; + + // Produce a list of candidate binaries to be probed by exec'ing them. + async function* getCandidates() { + if (customCondaPath && customCondaPath !== 'conda') { + // If user has specified a custom conda path, use it first. + yield customCondaPath; + } + // Check unqualified filename first, in case it's on PATH. + yield 'conda'; + if (getOSType() === OSType.Windows) { + yield* getCandidatesFromRegistry(); + } + yield* getCandidatesFromKnownPaths(); + yield* getCandidatesFromEnvironmentsTxt(); + } + + async function* getCandidatesFromRegistry() { + const interps = await getRegistryInterpreters(); + const candidates = interps + .filter((interp) => interp.interpreterPath && interp.distroOrgName === 'ContinuumAnalytics') + .map((interp) => path.join(path.win32.dirname(interp.interpreterPath), suffix)); + yield* candidates; + } + + async function* getCandidatesFromKnownPaths() { + // Check common locations. We want to look up "/*conda*/", where prefix and suffix + // depend on the platform, to account for both Anaconda and Miniconda, and all possible variations. + // The check cannot use globs, because on Windows, prefixes are absolute paths with a drive letter, + // and the glob module doesn't understand globs with drive letters in them, producing wrong results + // for "C:/*" etc. + const prefixes: string[] = []; + if (getOSType() === OSType.Windows) { + const programData = getEnvironmentVariable('PROGRAMDATA') || 'C:\\ProgramData'; + prefixes.push(programData); + if (home) { + const localAppData = getEnvironmentVariable('LOCALAPPDATA') || path.join(home, 'AppData', 'Local'); + prefixes.push(home, path.join(localAppData, 'Continuum')); + } + } else { + prefixes.push('/usr/share', '/usr/local/share', '/opt', '/opt/homebrew/bin'); + if (home) { + prefixes.push(home, path.join(home, 'opt')); + } + } + + for (const prefix of prefixes) { + let items: string[] | undefined; + try { + items = await fsapi.readdir(prefix); + } catch (ex) { + // Directory doesn't exist or is not readable - not an error. + items = undefined; + } + if (items !== undefined) { + yield* items + .filter((fileName) => fileName.toLowerCase().includes('conda')) + .map((fileName) => path.join(prefix, fileName, suffix)); + } + } + } + + async function* getCandidatesFromEnvironmentsTxt() { + if (!home) { + return; + } + + let contents: string; + try { + contents = await fsapi.readFile(path.join(home, '.conda', 'environments.txt'), 'utf8'); + } catch (ex) { + // File doesn't exist or is not readable - not an error. + contents = ''; + } + + // Match conda behavior; see conda.gateways.disk.read.yield_lines(). + // Note that this precludes otherwise legal paths with trailing spaces. + yield* contents + .split(/\r?\n/g) + .map((line) => line.trim()) + .filter((line) => line !== '' && !line.startsWith('#')) + .map((line) => path.join(line, suffix)); + } + + async function getCondaBatFile(file: string) { + const fileDir = path.dirname(file); + const possibleBatch = path.join(fileDir, '..', 'condabin', 'conda.bat'); + if (await pathExists(possibleBatch)) { + return possibleBatch; + } + return undefined; + } + + // Probe the candidates, and pick the first one that exists and does what we need. + for await (const condaPath of getCandidates()) { + traceVerbose(`Probing conda binary: ${condaPath}`); + let conda = new Conda(condaPath, undefined, shellPath); + try { + await conda.getInfo(); + if (getOSType() === OSType.Windows && (isTestExecution() || condaPath !== customCondaPath)) { + // Prefer to use .bat files over .exe on windows as that is what cmd works best on. + // Do not translate to `.bat` file if the setting explicitly sets the executable. + const condaBatFile = await getCondaBatFile(condaPath); + try { + if (condaBatFile) { + const condaBat = new Conda(condaBatFile, undefined, shellPath); + await condaBat.getInfo(); + conda = new Conda(condaPath, condaBatFile, shellPath); + } + } catch (ex) { + traceVerbose('Failed to spawn conda bat file', condaBatFile, ex); + } + } + traceVerbose(`Found conda via filesystem probing: ${condaPath}`); + return conda; + } catch (ex) { + // Failed to spawn because the binary doesn't exist or isn't on PATH, or the current + // user doesn't have execute permissions for it, or this conda couldn't handle command + // line arguments that we passed (indicating an old version that we do not support). + traceVerbose('Failed to spawn conda binary', condaPath, ex); + } + } + + // Didn't find anything. + traceVerbose("Couldn't locate the conda binary."); + return undefined; + } + + /** + * Retrieves global information about this conda. + * Corresponds to "conda info --json". + */ + public async getInfo(useCache?: boolean): Promise { + let condaInfoCached = this.condaInfoCached.get(this.shellPath); + if (!useCache || !condaInfoCached) { + condaInfoCached = this.getInfoImpl(this.command, this.shellPath); + this.condaInfoCached.set(this.shellPath, condaInfoCached); + } + return condaInfoCached; + } + + /** + * Temporarily cache result for this particular command. + */ + @cache(30_000, true, 10_000) + // eslint-disable-next-line class-methods-use-this + private async getInfoImpl(command: string, shellPath: string | undefined): Promise { + const options: SpawnOptions = { timeout: CONDA_GENERAL_TIMEOUT }; + if (shellPath) { + options.shell = shellPath; + } + const resultPromise = exec(command, ['info', '--json'], options, this.useWorkerThreads); + // It has been observed that specifying a timeout is still not reliable to terminate the Conda process, see #27915. + // Hence explicitly continue execution after timeout has been reached. + const success = await Promise.race([ + resultPromise.then(() => true), + sleep(CONDA_GENERAL_TIMEOUT + 3000).then(() => false), + ]); + if (success) { + const result = await resultPromise; + traceVerbose(`${command} info --json: ${result.stdout}`); + return JSON.parse(result.stdout); + } + throw new Error(`Launching '${command} info --json' timed out`); + } + + /** + * Retrieves list of Python environments known to this conda. + * Corresponds to "conda env list --json", but also computes environment names. + */ + @cache(30_000, true, 10_000) + public async getEnvList(): Promise { + const info = await this.getInfo(); + const { envs } = info; + if (envs === undefined) { + return []; + } + return Promise.all( + envs.map(async (prefix) => ({ + prefix, + name: await this.getName(prefix, info), + })), + ); + } + + /** + * Retrieves list of directories where conda environments are stored. + */ + @cache(30_000, true, 10_000) + public async getEnvDirs(): Promise { + const info = await this.getInfo(); + return info.envs_dirs ?? []; + } + + public async getName(prefix: string, info?: CondaInfo): Promise { + info = info ?? (await this.getInfo(true)); + if (info.root_prefix && arePathsSame(prefix, info.root_prefix)) { + return 'base'; + } + const parentDir = path.dirname(prefix); + if (info.envs_dirs !== undefined) { + for (const envsDir of info.envs_dirs) { + if (arePathsSame(parentDir, envsDir)) { + return path.basename(prefix); + } + } + } + return undefined; + } + + /** + * Returns conda environment related to path provided. + * @param executableOrEnvPath Path to environment folder or path to interpreter that uniquely identifies an environment. + */ + public async getCondaEnvironment(executableOrEnvPath: string): Promise { + const envList = await this.getEnvList(); + // Assuming `executableOrEnvPath` is path to env. + const condaEnv = envList.find((e) => arePathsSame(executableOrEnvPath, e.prefix)); + if (condaEnv) { + return condaEnv; + } + // Assuming `executableOrEnvPath` is an executable. + return envList.find((e) => isParentPath(executableOrEnvPath, e.prefix)); + } + + /** + * Returns executable associated with the conda env, swallows exceptions. + */ + // eslint-disable-next-line class-methods-use-this + public async getInterpreterPathForEnvironment(condaEnv: CondaEnvInfo | { prefix: string }): Promise { + const executablePath = getCondaInterpreterPath(condaEnv.prefix); + if (await pathExists(executablePath)) { + traceVerbose('Found executable within conda env', JSON.stringify(condaEnv)); + return executablePath; + } + traceVerbose( + 'Executable does not exist within conda env, assume the executable to be `python`', + JSON.stringify(condaEnv), + ); + return 'python'; + } + + public async getRunPythonArgs( + env: CondaEnvInfo, + forShellExecution?: boolean, + isolatedFlag = false, + ): Promise { + const condaVersion = await this.getCondaVersion(); + if (condaVersion && lt(condaVersion, CONDA_RUN_VERSION)) { + traceError('`conda run` is not supported for conda version', condaVersion.raw); + return undefined; + } + const args = []; + args.push('-p', env.prefix); + + const python = [ + forShellExecution ? this.shellCommand : this.command, + 'run', + ...args, + '--no-capture-output', + 'python', + ]; + if (isolatedFlag) { + python.push('-I'); + } + return [...python, OUTPUT_MARKER_SCRIPT]; + } + + public async getListPythonPackagesArgs( + env: CondaEnvInfo, + forShellExecution?: boolean, + ): Promise { + const args = ['-p', env.prefix]; + + return [forShellExecution ? this.shellCommand : this.command, 'list', ...args]; + } + + /** + * Return the conda version. The version info is cached. + */ + @cache(-1, true) + public async getCondaVersion(): Promise { + const info = await this.getInfo(true).catch(() => undefined); + let versionString: string | undefined; + if (info && info.conda_version) { + versionString = info.conda_version; + } else { + const stdOut = await exec(this.command, ['--version'], { timeout: CONDA_GENERAL_TIMEOUT }) + .then((result) => result.stdout.trim()) + .catch(() => undefined); + + versionString = stdOut && stdOut.startsWith('conda ') ? stdOut.substring('conda '.length).trim() : stdOut; + } + if (!versionString) { + return undefined; + } + const pattern = /(?\d+)\.(?\d+)\.(?\d+)(?:.*)?/; + const match = versionString.match(pattern); + if (match && match.groups) { + const versionStringParsed = match.groups.major.concat('.', match.groups.minor, '.', match.groups.micro); + + const semVarVersion: SemVer = new SemVer(versionStringParsed); + if (semVarVersion) { + return semVarVersion; + } + } + // Use a bogus version, at least to indicate the fact that a version was returned. + // This ensures we still use conda for activation, installation etc. + traceError(`Unable to parse version of Conda, ${versionString}`); + return new SemVer('0.0.1'); + } + + public async isCondaRunSupported(): Promise { + const condaVersion = await this.getCondaVersion(); + if (condaVersion && lt(condaVersion, CONDA_RUN_VERSION)) { + return false; + } + return true; + } +} + +export function setCondaBinary(executable: string): void { + Conda.setConda(executable); +} + +export async function getCondaEnvDirs(): Promise { + const conda = await Conda.getConda(); + return conda?.getEnvDirs(); +} + +export function getCondaPathSetting(): string | undefined { + const config = getConfiguration('python'); + return config.get(CONDAPATH_SETTING_KEY, ''); +} diff --git a/src/client/pythonEnvironments/common/environmentManagers/condaService.ts b/src/client/pythonEnvironments/common/environmentManagers/condaService.ts new file mode 100644 index 000000000000..0aa91bdbfb45 --- /dev/null +++ b/src/client/pythonEnvironments/common/environmentManagers/condaService.ts @@ -0,0 +1,158 @@ +import { inject, injectable } from 'inversify'; +import * as path from 'path'; +import { SemVer } from 'semver'; +import { IFileSystem, IPlatformService } from '../../../common/platform/types'; +import { traceVerbose } from '../../../logging'; +import { cache } from '../../../common/utils/decorators'; +import { ICondaService } from '../../../interpreter/contracts'; +import { traceDecoratorVerbose } from '../../../logging'; +import { Conda, CondaEnvironmentInfo, CondaInfo } from './conda'; + +/** + * Injectable version of Conda utility. + */ +@injectable() +export class CondaService implements ICondaService { + private isAvailable: boolean | undefined; + + constructor( + @inject(IPlatformService) private platform: IPlatformService, + @inject(IFileSystem) private fileSystem: IFileSystem, + ) {} + + public async getActivationScriptFromInterpreter( + interpreterPath?: string, + envName?: string, + ): Promise<{ path: string | undefined; type: 'local' | 'global' } | undefined> { + traceVerbose(`Getting activation script for interpreter ${interpreterPath}, env ${envName}`); + const condaPath = await this.getCondaFileFromInterpreter(interpreterPath, envName); + traceVerbose(`Found conda path: ${condaPath}`); + + const activatePath = (condaPath + ? path.join(path.dirname(condaPath), 'activate') + : 'activate' + ).fileToCommandArgumentForPythonExt(); // maybe global activate? + traceVerbose(`Using activate path: ${activatePath}`); + + // try to find the activate script in the global conda root prefix. + if (this.platform.isLinux || this.platform.isMac) { + const condaInfo = await this.getCondaInfo(); + // eslint-disable-next-line camelcase + if (condaInfo?.root_prefix) { + const globalActivatePath = path + // eslint-disable-next-line camelcase + .join(condaInfo.root_prefix, this.platform.virtualEnvBinName, 'activate') + .fileToCommandArgumentForPythonExt(); + + if (activatePath === globalActivatePath || !(await this.fileSystem.fileExists(activatePath))) { + traceVerbose(`Using global activate path: ${globalActivatePath}`); + return { + path: globalActivatePath, + type: 'global', + }; + } + } + } + + return { path: activatePath, type: 'local' }; // return the default activate script wether it exists or not. + } + + /** + * Return the path to the "conda file". + */ + + // eslint-disable-next-line class-methods-use-this + public async getCondaFile(forShellExecution?: boolean): Promise { + return Conda.getConda().then((conda) => { + const command = forShellExecution ? conda?.shellCommand : conda?.command; + return command ?? 'conda'; + }); + } + + // eslint-disable-next-line class-methods-use-this + public async getInterpreterPathForEnvironment(condaEnv: CondaEnvironmentInfo): Promise { + const conda = await Conda.getConda(); + return conda?.getInterpreterPathForEnvironment({ name: condaEnv.name, prefix: condaEnv.path }); + } + + /** + * Is there a conda install to use? + */ + public async isCondaAvailable(): Promise { + if (typeof this.isAvailable === 'boolean') { + return this.isAvailable; + } + return this.getCondaVersion() + + .then((version) => (this.isAvailable = version !== undefined)) // eslint-disable-line no-return-assign + .catch(() => (this.isAvailable = false)); // eslint-disable-line no-return-assign + } + + /** + * Return the conda version. + */ + // eslint-disable-next-line class-methods-use-this + public async getCondaVersion(): Promise { + return Conda.getConda().then((conda) => conda?.getCondaVersion()); + } + + /** + * Get the conda exe from the path to an interpreter's python. This might be different than the + * globally registered conda.exe. + * + * The value is cached for a while. + * The only way this can change is if user installs conda into this same environment. + * Generally we expect that to happen the other way, the user creates a conda environment with conda in it. + */ + @traceDecoratorVerbose('Get Conda File from interpreter') + @cache(120_000) + public async getCondaFileFromInterpreter(interpreterPath?: string, envName?: string): Promise { + const condaExe = this.platform.isWindows ? 'conda.exe' : 'conda'; + const scriptsDir = this.platform.isWindows ? 'Scripts' : 'bin'; + const interpreterDir = interpreterPath ? path.dirname(interpreterPath) : ''; + + // Might be in a situation where this is not the default python env, but rather one running + // from a virtualenv + const envsPos = envName ? interpreterDir.indexOf(path.join('envs', envName)) : -1; + if (envsPos > 0) { + // This should be where the original python was run from when the environment was created. + const originalPath = interpreterDir.slice(0, envsPos); + let condaPath1 = path.join(originalPath, condaExe); + + if (await this.fileSystem.fileExists(condaPath1)) { + return condaPath1; + } + + // Also look in the scripts directory here too. + condaPath1 = path.join(originalPath, scriptsDir, condaExe); + if (await this.fileSystem.fileExists(condaPath1)) { + return condaPath1; + } + } + + let condaPath2 = path.join(interpreterDir, condaExe); + if (await this.fileSystem.fileExists(condaPath2)) { + return condaPath2; + } + // Conda path has changed locations, check the new location in the scripts directory after checking + // the old location + condaPath2 = path.join(interpreterDir, scriptsDir, condaExe); + if (await this.fileSystem.fileExists(condaPath2)) { + return condaPath2; + } + + return this.getCondaFile(); + } + + /** + * Return the info reported by the conda install. + * The result is cached for 30s. + */ + + // eslint-disable-next-line class-methods-use-this + @cache(60_000) + public async getCondaInfo(): Promise { + const conda = await Conda.getConda(); + return conda?.getInfo(); + } +} diff --git a/src/client/pythonEnvironments/common/environmentManagers/globalInstalledEnvs.ts b/src/client/pythonEnvironments/common/environmentManagers/globalInstalledEnvs.ts new file mode 100644 index 000000000000..eb52668a0c65 --- /dev/null +++ b/src/client/pythonEnvironments/common/environmentManagers/globalInstalledEnvs.ts @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { getSearchPathEntries } from '../../../common/utils/exec'; +import { getOSType, OSType } from '../../../common/utils/platform'; +import { isParentPath } from '../externalDependencies'; +import { commonPosixBinPaths } from '../posixUtils'; +import { isPyenvShimDir } from './pyenv'; + +/** + * Checks if the given interpreter belongs to known globally installed types. If an global + * executable is discoverable, we consider it as global type. + * @param {string} interpreterPath: Absolute path to the python interpreter. + * @returns {boolean} : Returns true if the interpreter belongs to a venv environment. + */ +export async function isGloballyInstalledEnv(executablePath: string): Promise { + // Identifying this type is not important, as the extension treats `Global` and `Unknown` + // types the same way. This is only required for telemetry. As windows registry is known + // to be slow, we do not want to unnecessarily block on that by default, hence skip this + // step. + // if (getOSType() === OSType.Windows) { + // if (await isFoundInWindowsRegistry(executablePath)) { + // return true; + // } + // } + return isFoundInPathEnvVar(executablePath); +} + +async function isFoundInPathEnvVar(executablePath: string): Promise { + let searchPathEntries: string[] = []; + if (getOSType() === OSType.Windows) { + searchPathEntries = getSearchPathEntries(); + } else { + searchPathEntries = await commonPosixBinPaths(); + } + // Filter out pyenv shims. They are not actual python binaries, they are used to launch + // the binaries specified in .python-version file in the cwd. We should not be reporting + // those binaries as environments. + searchPathEntries = searchPathEntries.filter((dirname) => !isPyenvShimDir(dirname)); + for (const searchPath of searchPathEntries) { + if (isParentPath(executablePath, searchPath)) { + return true; + } + } + return false; +} diff --git a/src/client/pythonEnvironments/common/environmentManagers/hatch.ts b/src/client/pythonEnvironments/common/environmentManagers/hatch.ts new file mode 100644 index 000000000000..6d7a13ea1557 --- /dev/null +++ b/src/client/pythonEnvironments/common/environmentManagers/hatch.ts @@ -0,0 +1,116 @@ +import { isTestExecution } from '../../../common/constants'; +import { exec, pathExists } from '../externalDependencies'; +import { traceVerbose } from '../../../logging'; +import { cache } from '../../../common/utils/decorators'; +import { getOSType, OSType } from '../../../common/utils/platform'; + +/** Wraps the "Hatch" utility, and exposes its functionality. + */ +export class Hatch { + /** + * Locating Hatch binary can be expensive, since it potentially involves spawning or + * trying to spawn processes; so we only do it once per session. + */ + private static hatchPromise: Map> = new Map< + string, + Promise + >(); + + /** + * Creates a Hatch service corresponding to the corresponding "hatch" command. + * + * @param command - Command used to run hatch. This has the same meaning as the + * first argument of spawn() - i.e. it can be a full path, or just a binary name. + * @param cwd - The working directory to use as cwd when running hatch. + */ + constructor(public readonly command: string, private cwd: string) { + this.fixCwd(); + } + + /** + * Returns a Hatch instance corresponding to the binary which can be used to run commands for the cwd. + * + * Every directory is a valid Hatch project, so this should always return a Hatch instance. + */ + public static async getHatch(cwd: string): Promise { + if (Hatch.hatchPromise.get(cwd) === undefined || isTestExecution()) { + Hatch.hatchPromise.set(cwd, Hatch.locate(cwd)); + } + return Hatch.hatchPromise.get(cwd); + } + + private static async locate(cwd: string): Promise { + // First thing this method awaits on should be hatch command execution, + // hence perform all operations before that synchronously. + const hatchPath = 'hatch'; + traceVerbose(`Probing Hatch binary ${hatchPath}`); + const hatch = new Hatch(hatchPath, cwd); + const virtualenvs = await hatch.getEnvList(); + if (virtualenvs !== undefined) { + traceVerbose(`Found hatch binary ${hatchPath}`); + return hatch; + } + traceVerbose(`Failed to find Hatch binary ${hatchPath}`); + + // Didn't find anything. + traceVerbose(`No Hatch binary found`); + return undefined; + } + + /** + * Retrieves list of Python environments known to Hatch for this working directory. + * Returns `undefined` if we failed to spawn in some way. + * + * Corresponds to "hatch env show --json". Swallows errors if any. + */ + public async getEnvList(): Promise { + return this.getEnvListCached(this.cwd); + } + + /** + * Method created to facilitate caching. The caching decorator uses function arguments as cache key, + * so pass in cwd on which we need to cache. + */ + @cache(30_000, true, 10_000) + private async getEnvListCached(_cwd: string): Promise { + const envInfoOutput = await exec(this.command, ['env', 'show', '--json'], { + cwd: this.cwd, + throwOnStdErr: true, + }).catch(traceVerbose); + if (!envInfoOutput) { + return undefined; + } + const envPaths = await Promise.all( + Object.keys(JSON.parse(envInfoOutput.stdout)).map(async (name) => { + const envPathOutput = await exec(this.command, ['env', 'find', name], { + cwd: this.cwd, + throwOnStdErr: true, + }).catch(traceVerbose); + if (!envPathOutput) return undefined; + const dir = envPathOutput.stdout.trim(); + return (await pathExists(dir)) ? dir : undefined; + }), + ); + return envPaths.flatMap((r) => (r ? [r] : [])); + } + + /** + * Due to an upstream hatch issue on Windows https://github.com/pypa/hatch/issues/1350, + * 'hatch env find default' does not handle case-insensitive paths as cwd, which are valid on Windows. + * So we need to pass the case-exact path as cwd. + * It has been observed that only the drive letter in `cwd` is lowercased here. Unfortunately, + * there's no good way to get case of the drive letter correctly without using Win32 APIs: + * https://stackoverflow.com/questions/33086985/how-to-obtain-case-exact-path-of-a-file-in-node-js-on-windows + * So we do it manually. + */ + private fixCwd(): void { + if (getOSType() === OSType.Windows) { + if (/^[a-z]:/.test(this.cwd)) { + // Replace first character by the upper case version of the character. + const a = this.cwd.split(':'); + a[0] = a[0].toUpperCase(); + this.cwd = a.join(':'); + } + } + } +} diff --git a/src/client/pythonEnvironments/common/environmentManagers/macDefault.ts b/src/client/pythonEnvironments/common/environmentManagers/macDefault.ts new file mode 100644 index 000000000000..931fbbba9eac --- /dev/null +++ b/src/client/pythonEnvironments/common/environmentManagers/macDefault.ts @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { getOSType, OSType } from '../../../common/utils/platform'; + +/** + * Decide if the given Python executable looks like the MacOS default Python. + */ +export function isMacDefaultPythonPath(pythonPath: string): boolean { + if (getOSType() !== OSType.OSX) { + return false; + } + + const defaultPaths = ['/usr/bin/python']; + + return defaultPaths.includes(pythonPath) || pythonPath.startsWith('/usr/bin/python2'); +} diff --git a/src/client/pythonEnvironments/common/environmentManagers/microsoftStoreEnv.ts b/src/client/pythonEnvironments/common/environmentManagers/microsoftStoreEnv.ts new file mode 100644 index 000000000000..2b8675d0bc0b --- /dev/null +++ b/src/client/pythonEnvironments/common/environmentManagers/microsoftStoreEnv.ts @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { getEnvironmentVariable } from '../../../common/utils/platform'; +import { traceWarn } from '../../../logging'; +import { pathExists } from '../externalDependencies'; + +/** + * Gets path to the Windows Apps directory. + * @returns {string} : Returns path to the Windows Apps directory under + * `%LOCALAPPDATA%/Microsoft/WindowsApps`. + */ +export function getMicrosoftStoreAppsRoot(): string { + const localAppData = getEnvironmentVariable('LOCALAPPDATA') || ''; + return path.join(localAppData, 'Microsoft', 'WindowsApps'); +} +/** + * Checks if a given path is under the forbidden microsoft store directory. + * @param {string} absPath : Absolute path to a file or directory. + * @returns {boolean} : Returns true if `interpreterPath` is under + * `%ProgramFiles%/WindowsApps`. + */ +function isForbiddenStorePath(absPath: string): boolean { + const programFilesStorePath = path + .join(getEnvironmentVariable('ProgramFiles') || 'Program Files', 'WindowsApps') + .normalize() + .toUpperCase(); + return path.normalize(absPath).toUpperCase().includes(programFilesStorePath); +} +/** + * Checks if a given directory is any one of the possible microsoft store directories, or + * its sub-directory. + * @param {string} dirPath : Absolute path to a directory. + * + * Remarks: + * These locations are tested: + * 1. %LOCALAPPDATA%/Microsoft/WindowsApps + * 2. %ProgramFiles%/WindowsApps + */ + +export function isMicrosoftStoreDir(dirPath: string): boolean { + const storeRootPath = path.normalize(getMicrosoftStoreAppsRoot()).toUpperCase(); + return path.normalize(dirPath).toUpperCase().includes(storeRootPath) || isForbiddenStorePath(dirPath); +} +/** + * Checks if store python is installed. + * @param {string} interpreterPath : Absolute path to a interpreter. + * Remarks: + * If store python was never installed then the store apps directory will not + * have idle.exe or pip.exe. We can use this as a way to identify the python.exe + * found in the store apps directory is a real python or a store install shortcut. + */ +export async function isStorePythonInstalled(interpreterPath?: string): Promise { + let results = await Promise.all([ + pathExists(path.join(getMicrosoftStoreAppsRoot(), 'idle.exe')), + pathExists(path.join(getMicrosoftStoreAppsRoot(), 'pip.exe')), + ]); + + if (results.includes(true)) { + return true; + } + + if (interpreterPath) { + results = await Promise.all([ + pathExists(path.join(path.dirname(interpreterPath), 'idle.exe')), + pathExists(path.join(path.dirname(interpreterPath), 'pip.exe')), + ]); + return results.includes(true); + } + return false; +} +/** + * Checks if the given interpreter belongs to Microsoft Store Python environment. + * @param interpreterPath: Absolute path to any python interpreter. + * + * Remarks: + * 1. Checking if the path includes `Microsoft\WindowsApps`, `Program Files\WindowsApps`, is + * NOT enough. In WSL, `/mnt/c/users/user/AppData/Local/Microsoft/WindowsApps` is available as a search + * path. It is possible to get a false positive for that path. So the comparison should check if the + * absolute path to 'WindowsApps' directory is present in the given interpreter path. The WSL path to + * 'WindowsApps' is not a valid path to access, Microsoft Store Python. + * + * 2. 'startsWith' comparison may not be right, user can provide '\\?\C:\users\' style long paths in windows. + * + * 3. A limitation of the checks here is that they don't handle 8.3 style windows paths. + * For example, + * `C:\Users\USER\AppData\Local\MICROS~1\WINDOW~1\PYTHON~2.EXE` + * is the shortened form of + * `C:\Users\USER\AppData\Local\Microsoft\WindowsApps\python3.7.exe` + * + * The correct way to compare these would be to always convert given paths to long path (or to short path). + * For either approach to work correctly you need actual file to exist, and accessible from the user's + * account. + * + * To convert to short path without using N-API in node would be to use this command. This is very expensive: + * `> cmd /c for %A in ("C:\Users\USER\AppData\Local\Microsoft\WindowsApps\python3.7.exe") do @echo %~sA` + * The above command will print out this: + * `C:\Users\USER\AppData\Local\MICROS~1\WINDOW~1\PYTHON~2.EXE` + * + * If we go down the N-API route, use node-ffi and either call GetShortPathNameW or GetLongPathNameW from, + * Kernel32 to convert between the two path variants. + * + */ + +export async function isMicrosoftStoreEnvironment(interpreterPath: string): Promise { + if (await isStorePythonInstalled(interpreterPath)) { + const pythonPathToCompare = path.normalize(interpreterPath).toUpperCase(); + const localAppDataStorePath = path.normalize(getMicrosoftStoreAppsRoot()).toUpperCase(); + if (pythonPathToCompare.includes(localAppDataStorePath)) { + return true; + } + + // Program Files store path is a forbidden path. Only admins and system has access this path. + // We should never have to look at this path or even execute python from this path. + if (isForbiddenStorePath(pythonPathToCompare)) { + traceWarn('isMicrosoftStoreEnvironment called with Program Files store path.'); + return true; + } + } + return false; +} diff --git a/src/client/pythonEnvironments/common/environmentManagers/pipenv.ts b/src/client/pythonEnvironments/common/environmentManagers/pipenv.ts new file mode 100644 index 000000000000..c8651533ed4c --- /dev/null +++ b/src/client/pythonEnvironments/common/environmentManagers/pipenv.ts @@ -0,0 +1,153 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { getEnvironmentVariable } from '../../../common/utils/platform'; +import { traceError, traceVerbose } from '../../../logging'; +import { arePathsSame, normCasePath, pathExists, readFile } from '../externalDependencies'; + +function getSearchHeight() { + // PIPENV_MAX_DEPTH tells pipenv the maximum number of directories to recursively search for + // a Pipfile, defaults to 3: https://pipenv.pypa.io/en/latest/advanced/#pipenv.environments.PIPENV_MAX_DEPTH + const maxDepthStr = getEnvironmentVariable('PIPENV_MAX_DEPTH'); + if (maxDepthStr === undefined) { + return 3; + } + const maxDepth = parseInt(maxDepthStr, 10); + // eslint-disable-next-line no-restricted-globals + if (isNaN(maxDepth)) { + traceError(`PIPENV_MAX_DEPTH is incorrectly set. Converting value '${maxDepthStr}' to number results in NaN`); + return 1; + } + return maxDepth; +} + +/** + * Returns the path to Pipfile associated with the provided directory. + * @param searchDir the directory to look into + * @param lookIntoParentDirectories set to true if we should also search for Pipfile in parent directory + */ +export async function _getAssociatedPipfile( + searchDir: string, + options: { lookIntoParentDirectories: boolean }, +): Promise { + const pipFileName = getEnvironmentVariable('PIPENV_PIPFILE') || 'Pipfile'; + let heightToSearch = options.lookIntoParentDirectories ? getSearchHeight() : 1; + while (heightToSearch > 0 && !arePathsSame(searchDir, path.dirname(searchDir))) { + const pipFile = path.join(searchDir, pipFileName); + if (await pathExists(pipFile)) { + return pipFile; + } + searchDir = path.dirname(searchDir); + heightToSearch -= 1; + } + return undefined; +} + +/** + * If interpreter path belongs to a pipenv environment which is located inside a project, return associated Pipfile, + * otherwise return `undefined`. + * @param interpreterPath Absolute path to any python interpreter. + */ +async function getPipfileIfLocal(interpreterPath: string): Promise { + // Local pipenv environments are created by setting PIPENV_VENV_IN_PROJECT to 1, which always names the environment + // folder '.venv': https://pipenv.pypa.io/en/latest/advanced/#pipenv.environments.PIPENV_VENV_IN_PROJECT + // This is the layout we wish to verify. + // project + // |__ Pipfile <--- check if Pipfile exists here + // |__ .venv <--- check if name of the folder is '.venv' + // |__ Scripts/bin + // |__ python <--- interpreterPath + const venvFolder = path.dirname(path.dirname(interpreterPath)); + if (path.basename(venvFolder) !== '.venv') { + return undefined; + } + const directoryWhereVenvResides = path.dirname(venvFolder); + return _getAssociatedPipfile(directoryWhereVenvResides, { lookIntoParentDirectories: false }); +} + +/** + * Returns the project directory for pipenv environments given the environment folder + * @param envFolder Path to the environment folder + */ +export async function getProjectDir(envFolder: string): Promise { + // Global pipenv environments have a .project file with the absolute path to the project + // See https://github.com/pypa/pipenv/blob/v2018.6.25/CHANGELOG.rst#features--improvements + // This is the layout we expect + // + // |__ .project <--- check if .project exists here + // |__ Scripts/bin + // |__ python <--- interpreterPath + // We get the project by reading the .project file + const dotProjectFile = path.join(envFolder, '.project'); + if (!(await pathExists(dotProjectFile))) { + return undefined; + } + const projectDir = (await readFile(dotProjectFile)).trim(); + if (!(await pathExists(projectDir))) { + traceVerbose( + `The .project file inside environment folder: ${envFolder} doesn't contain a valid path to the project`, + ); + return undefined; + } + return projectDir; +} + +/** + * If interpreter path belongs to a global pipenv environment, return associated Pipfile, otherwise return `undefined`. + * @param interpreterPath Absolute path to any python interpreter. + */ +async function getPipfileIfGlobal(interpreterPath: string): Promise { + const envFolder = path.dirname(path.dirname(interpreterPath)); + const projectDir = await getProjectDir(envFolder); + if (projectDir === undefined) { + return undefined; + } + + // This is the layout we expect to see. + // project + // |__ Pipfile <--- check if Pipfile exists here and return it + // The name of the project (directory where Pipfile resides) is used as a prefix in the environment folder + const envFolderName = path.basename(normCasePath(envFolder)); + if (!envFolderName.startsWith(`${path.basename(normCasePath(projectDir))}-`)) { + return undefined; + } + + return _getAssociatedPipfile(projectDir, { lookIntoParentDirectories: false }); +} + +/** + * Checks if the given interpreter path belongs to a pipenv environment, by locating the Pipfile which was used to + * create the environment. + * @param interpreterPath: Absolute path to any python interpreter. + */ +export async function isPipenvEnvironment(interpreterPath: string): Promise { + if (await getPipfileIfLocal(interpreterPath)) { + return true; + } + if (await getPipfileIfGlobal(interpreterPath)) { + return true; + } + return false; +} + +/** + * Returns true if interpreter path belongs to a global pipenv environment which is associated with a particular folder, + * false otherwise. + * @param interpreterPath Absolute path to any python interpreter. + */ +export async function isPipenvEnvironmentRelatedToFolder(interpreterPath: string, folder: string): Promise { + const pipFileAssociatedWithEnvironment = await getPipfileIfGlobal(interpreterPath); + if (!pipFileAssociatedWithEnvironment) { + return false; + } + + // PIPENV_NO_INHERIT is used to tell pipenv not to look for Pipfile in parent directories + // https://pipenv.pypa.io/en/latest/advanced/#pipenv.environments.PIPENV_NO_INHERIT + const lookIntoParentDirectories = getEnvironmentVariable('PIPENV_NO_INHERIT') === undefined; + const pipFileAssociatedWithFolder = await _getAssociatedPipfile(folder, { lookIntoParentDirectories }); + if (!pipFileAssociatedWithFolder) { + return false; + } + return arePathsSame(pipFileAssociatedWithEnvironment, pipFileAssociatedWithFolder); +} diff --git a/src/client/pythonEnvironments/common/environmentManagers/pixi.ts b/src/client/pythonEnvironments/common/environmentManagers/pixi.ts new file mode 100644 index 000000000000..6443e64f9ae8 --- /dev/null +++ b/src/client/pythonEnvironments/common/environmentManagers/pixi.ts @@ -0,0 +1,386 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as path from 'path'; +import { readJSON } from 'fs-extra'; +import which from 'which'; +import { getUserHomeDir, isWindows } from '../../../common/utils/platform'; +import { exec, getPythonSetting, onDidChangePythonSetting, pathExists } from '../externalDependencies'; +import { cache } from '../../../common/utils/decorators'; +import { traceVerbose, traceWarn } from '../../../logging'; +import { OUTPUT_MARKER_SCRIPT } from '../../../common/process/internal/scripts'; +import { IDisposableRegistry } from '../../../common/types'; +import { getWorkspaceFolderPaths } from '../../../common/vscodeApis/workspaceApis'; +import { isTestExecution } from '../../../common/constants'; +import { TerminalShellType } from '../../../common/terminal/types'; + +export const PIXITOOLPATH_SETTING_KEY = 'pixiToolPath'; + +// This type corresponds to the output of 'pixi info --json', and property +// names must be spelled exactly as they are in order to match the schema. +export type PixiInfo = { + platform: string; + virtual_packages: string[]; // eslint-disable-line camelcase + version: string; + cache_dir: string; // eslint-disable-line camelcase + cache_size?: number; // eslint-disable-line camelcase + auth_dir: string; // eslint-disable-line camelcase + + project_info?: PixiProjectInfo /* eslint-disable-line camelcase */; + + environments_info: /* eslint-disable-line camelcase */ { + name: string; + features: string[]; + solve_group: string; // eslint-disable-line camelcase + environment_size: number; // eslint-disable-line camelcase + dependencies: string[]; + tasks: string[]; + channels: string[]; + prefix: string; + }[]; +}; + +export type PixiProjectInfo = { + manifest_path: string; // eslint-disable-line camelcase + last_updated: string; // eslint-disable-line camelcase + pixi_folder_size?: number; // eslint-disable-line camelcase + version: string; +}; + +export type PixiEnvMetadata = { + manifest_path: string; // eslint-disable-line camelcase + pixi_version: string; // eslint-disable-line camelcase + environment_name: string; // eslint-disable-line camelcase +}; + +export async function isPixiEnvironment(interpreterPath: string): Promise { + const prefix = getPrefixFromInterpreterPath(interpreterPath); + return ( + pathExists(path.join(prefix, 'conda-meta/pixi')) || pathExists(path.join(prefix, 'conda-meta/pixi_env_prefix')) + ); +} + +/** + * Returns the path to the environment directory based on the interpreter path. + */ +export function getPrefixFromInterpreterPath(interpreterPath: string): string { + const interpreterDir = path.dirname(interpreterPath); + if (!interpreterDir.endsWith('bin') && !interpreterDir.endsWith('Scripts')) { + return interpreterDir; + } + return path.dirname(interpreterDir); +} + +async function findPixiOnPath(): Promise { + try { + return await which('pixi', { all: true }); + } catch { + // Ignore errors + } + return []; +} + +/** Wraps the "pixi" utility, and exposes its functionality. + */ +export class Pixi { + /** + * Creates a Pixi service corresponding to the corresponding "pixi" command. + * + * @param command - Command used to run pixi. This has the same meaning as the + * first argument of spawn() - i.e. it can be a full path, or just a binary name. + */ + constructor(public readonly command: string) {} + + /** + * Retrieves list of Python environments known to this pixi for the specified directory. + * + * Corresponds to "pixi info --json" and extracting the environments. Swallows errors if any. + */ + public async getEnvList(cwd: string): Promise { + const pixiInfo = await this.getPixiInfo(cwd); + // eslint-disable-next-line camelcase + return pixiInfo?.environments_info.map((env) => env.prefix); + } + + /** + * Method that runs `pixi info` and returns the result. The value is cached for "only" 1 second + * because the output changes if the project manifest is modified. + */ + @cache(1_000, true, 1_000) + public async getPixiInfo(cwd: string): Promise { + try { + const infoOutput = await exec(this.command, ['info', '--json'], { + cwd, + throwOnStdErr: false, + }); + + if (!infoOutput || !infoOutput.stdout) { + return undefined; + } + + const pixiInfo: PixiInfo = JSON.parse(infoOutput.stdout); + return pixiInfo; + } catch (error) { + traceWarn(`Failed to get pixi info for ${cwd}`, error); + return undefined; + } + } + + /** + * Returns the command line arguments to run `python` within a specific pixi environment. + * @param manifestPath The path to the manifest file used by pixi. + * @param envName The name of the environment in the pixi project + * @param isolatedFlag Whether to add `-I` to the python invocation. + * @returns A list of arguments that can be passed to exec. + */ + public getRunPythonArgs(manifestPath: string, envName?: string, isolatedFlag = false): string[] { + let python = [this.command, 'run', '--manifest-path', manifestPath]; + if (isNonDefaultPixiEnvironmentName(envName)) { + python = python.concat(['--environment', envName]); + } + + python.push('python'); + if (isolatedFlag) { + python.push('-I'); + } + return [...python, OUTPUT_MARKER_SCRIPT]; + } + + /** + * Starting from Pixi 0.24.0, each environment has a special file that records some information + * about which manifest created the environment. + * + * @param envDir The root directory (or prefix) of a conda environment + */ + + // eslint-disable-next-line class-methods-use-this + @cache(5_000, true, 10_000) + async getPixiEnvironmentMetadata(envDir: string): Promise { + const pixiPath = path.join(envDir, 'conda-meta/pixi'); + try { + const result: PixiEnvMetadata | undefined = await readJSON(pixiPath); + return result; + } catch (e) { + traceVerbose(`Failed to get pixi environment metadata for ${envDir}`, e); + } + return undefined; + } +} + +async function getPixiTool(): Promise { + let pixi = getPythonSetting(PIXITOOLPATH_SETTING_KEY); + + if (!pixi || pixi === 'pixi' || !(await pathExists(pixi))) { + pixi = undefined; + const paths = await findPixiOnPath(); + for (const p of paths) { + if (await pathExists(p)) { + pixi = p; + break; + } + } + } + + if (!pixi) { + // Check the default installation location + const home = getUserHomeDir(); + if (home) { + const pixiToolPath = path.join(home, '.pixi', 'bin', isWindows() ? 'pixi.exe' : 'pixi'); + if (await pathExists(pixiToolPath)) { + pixi = pixiToolPath; + } + } + } + + return pixi ? new Pixi(pixi) : undefined; +} + +/** + * Locating pixi binary can be expensive, since it potentially involves spawning or + * trying to spawn processes; so we only do it once per session. + */ +let _pixi: Promise | undefined; + +/** + * Returns a Pixi instance corresponding to the binary which can be used to run commands for the cwd. + * + * Pixi commands can be slow and so can be bottleneck to overall discovery time. So trigger command + * execution as soon as possible. To do that we need to ensure the operations before the command are + * performed synchronously. + */ +export function getPixi(): Promise { + if (_pixi === undefined || isTestExecution()) { + _pixi = getPixiTool(); + } + return _pixi; +} + +export type PixiEnvironmentInfo = { + interpreterPath: string; + pixi: Pixi; + pixiVersion: string; + manifestPath: string; + envName?: string; +}; + +function isPixiProjectDir(pixiProjectDir: string): boolean { + const paths = getWorkspaceFolderPaths().map((f) => path.normalize(f)); + const normalized = path.normalize(pixiProjectDir); + return paths.some((p) => p === normalized); +} + +/** + * Given the location of an interpreter, try to deduce information about the environment in which it + * resides. + * @param interpreterPath The full path to the interpreter. + * @param pixi Optionally a pixi instance. If this is not specified it will be located. + * @returns Information about the pixi environment. + */ +export async function getPixiEnvironmentFromInterpreter( + interpreterPath: string, +): Promise { + if (!interpreterPath) { + return undefined; + } + + const prefix = getPrefixFromInterpreterPath(interpreterPath); + const pixi = await getPixi(); + if (!pixi) { + traceVerbose(`could not find a pixi interpreter for the interpreter at ${interpreterPath}`); + return undefined; + } + + // Check if the environment has pixi metadata that we can source. + const metadata = await pixi.getPixiEnvironmentMetadata(prefix); + if (metadata !== undefined) { + return { + interpreterPath, + pixi, + pixiVersion: metadata.pixi_version, + manifestPath: metadata.manifest_path, + envName: metadata.environment_name, + }; + } + + // Otherwise, we'll have to try to deduce this information. + + // Usually the pixi environments are stored under `/.pixi/envs//`. So, + // we walk backwards to determine the project directory. + let envName: string | undefined; + let envsDir: string; + let dotPixiDir: string; + let pixiProjectDir: string; + let pixiInfo: PixiInfo | undefined; + + try { + envName = path.basename(prefix); + envsDir = path.dirname(prefix); + dotPixiDir = path.dirname(envsDir); + pixiProjectDir = path.dirname(dotPixiDir); + if (!isPixiProjectDir(pixiProjectDir)) { + traceVerbose(`could not determine the pixi project directory for the interpreter at ${interpreterPath}`); + return undefined; + } + + // Invoke pixi to get information about the pixi project + pixiInfo = await pixi.getPixiInfo(pixiProjectDir); + + if (!pixiInfo || !pixiInfo.project_info) { + traceWarn(`failed to determine pixi project information for the interpreter at ${interpreterPath}`); + return undefined; + } + + return { + interpreterPath, + pixi, + pixiVersion: pixiInfo.version, + manifestPath: pixiInfo.project_info.manifest_path, + envName, + }; + } catch (error) { + traceWarn('Error processing paths or getting Pixi Info:', error); + } + + return undefined; +} + +/** + * Returns true if the given environment name is *not* the default environment. + */ +export function isNonDefaultPixiEnvironmentName(envName?: string): envName is string { + return envName !== 'default'; +} + +export function registerPixiFeatures(disposables: IDisposableRegistry): void { + disposables.push( + onDidChangePythonSetting(PIXITOOLPATH_SETTING_KEY, () => { + _pixi = getPixiTool(); + }), + ); +} + +/** + * Returns the `pixi run` command + */ +export async function getRunPixiPythonCommand(pythonPath: string): Promise { + const pixiEnv = await getPixiEnvironmentFromInterpreter(pythonPath); + if (!pixiEnv) { + return undefined; + } + + const args = [ + pixiEnv.pixi.command.toCommandArgumentForPythonExt(), + 'run', + '--manifest-path', + pixiEnv.manifestPath.toCommandArgumentForPythonExt(), + ]; + if (isNonDefaultPixiEnvironmentName(pixiEnv.envName)) { + args.push('--environment'); + args.push(pixiEnv.envName.toCommandArgumentForPythonExt()); + } + + args.push('python'); + return args; +} + +export async function getPixiActivationCommands( + pythonPath: string, + _targetShell?: TerminalShellType, +): Promise { + const pixiEnv = await getPixiEnvironmentFromInterpreter(pythonPath); + if (!pixiEnv) { + return undefined; + } + + const args = [ + pixiEnv.pixi.command.toCommandArgumentForPythonExt(), + 'shell', + '--manifest-path', + pixiEnv.manifestPath.toCommandArgumentForPythonExt(), + ]; + if (isNonDefaultPixiEnvironmentName(pixiEnv.envName)) { + args.push('--environment'); + args.push(pixiEnv.envName.toCommandArgumentForPythonExt()); + } + + // const pixiTargetShell = shellTypeToPixiShell(targetShell); + // if (pixiTargetShell) { + // args.push('--shell'); + // args.push(pixiTargetShell); + // } + + // const shellHookOutput = await exec(pixiEnv.pixi.command, args, { + // throwOnStdErr: false, + // }).catch(traceError); + // if (!shellHookOutput) { + // return undefined; + // } + + // return splitLines(shellHookOutput.stdout, { + // removeEmptyEntries: true, + // trim: true, + // }); + return [args.join(' ')]; +} diff --git a/src/client/pythonEnvironments/common/environmentManagers/poetry.ts b/src/client/pythonEnvironments/common/environmentManagers/poetry.ts new file mode 100644 index 000000000000..5e5fa2416208 --- /dev/null +++ b/src/client/pythonEnvironments/common/environmentManagers/poetry.ts @@ -0,0 +1,343 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as path from 'path'; +import { getOSType, getUserHomeDir, OSType } from '../../../common/utils/platform'; +import { + getPythonSetting, + isParentPath, + pathExists, + pathExistsSync, + readFile, + shellExecute, +} from '../externalDependencies'; +import { getEnvironmentDirFromPath } from '../commonUtils'; +import { isVirtualenvEnvironment } from './simplevirtualenvs'; +import { StopWatch } from '../../../common/utils/stopWatch'; +import { cache } from '../../../common/utils/decorators'; +import { isTestExecution } from '../../../common/constants'; +import { traceError, traceVerbose } from '../../../logging'; +import { splitLines } from '../../../common/stringUtils'; + +/** + * Global virtual env dir for a project is named as: + * + * --py. + * + * Implementation details behind and are too + * much to rely upon, so for our purposes the best we can do is the following regex. + */ +const globalPoetryEnvDirRegex = /^(.+)-(.+)-py(\d).(\d){1,2}$/; + +/** + * Checks if the given interpreter belongs to a global poetry environment. + * @param {string} interpreterPath: Absolute path to the python interpreter. + * @returns {boolean} : Returns true if the interpreter belongs to a venv environment. + */ +async function isGlobalPoetryEnvironment(interpreterPath: string): Promise { + const envDir = getEnvironmentDirFromPath(interpreterPath); + return globalPoetryEnvDirRegex.test(path.basename(envDir)) ? isVirtualenvEnvironment(interpreterPath) : false; +} +/** + * Local poetry environments are created by the `virtualenvs.in-project` setting , which always names the environment + * folder '.venv': https://python-poetry.org/docs/configuration/#virtualenvsin-project-boolean + */ +export const localPoetryEnvDirName = '.venv'; + +/** + * Checks if the given interpreter belongs to a local poetry environment, i.e environment is located inside the project. + * @param {string} interpreterPath: Absolute path to the python interpreter. + * @returns {boolean} : Returns true if the interpreter belongs to a venv environment. + */ +async function isLocalPoetryEnvironment(interpreterPath: string): Promise { + // This is the layout we wish to verify. + // project + // |__ pyproject.toml <--- check if this exists + // |__ .venv <--- check if name of the folder is '.venv' + // |__ Scripts/bin + // |__ python <--- interpreterPath + const envDir = getEnvironmentDirFromPath(interpreterPath); + if (path.basename(envDir) !== localPoetryEnvDirName) { + return false; + } + const project = path.dirname(envDir); + if (!(await hasValidPyprojectToml(project))) { + return false; + } + // The assumption is that we need to be able to run poetry CLI for an environment in order to mark it as poetry. + // For that we can either further verify, + // - 'pyproject.toml' is valid toml + // - 'pyproject.toml' has a poetry section which contains the necessary fields + // - Poetry configuration allows local virtual environments + // ... possibly more + // Or we can try running poetry to find the related environment instead. Launching poetry binaries although + // reliable, can be expensive. So report the best effort type instead, i.e this is likely a poetry env. + return true; +} + +/** + * Checks if the given interpreter belongs to a poetry environment. + * @param {string} interpreterPath: Absolute path to the python interpreter. + * @returns {boolean} : Returns true if the interpreter belongs to a venv environment. + */ +export async function isPoetryEnvironment(interpreterPath: string): Promise { + if (await isGlobalPoetryEnvironment(interpreterPath)) { + return true; + } + if (await isLocalPoetryEnvironment(interpreterPath)) { + return true; + } + return false; +} + +const POETRY_TIMEOUT = 50000; + +/** Wraps the "poetry" utility, and exposes its functionality. + */ +export class Poetry { + /** + * Locating poetry binary can be expensive, since it potentially involves spawning or + * trying to spawn processes; so we only do it once per session. + */ + private static poetryPromise: Map> = new Map< + string, + Promise + >(); + + /** + * Creates a Poetry service corresponding to the corresponding "poetry" command. + * + * @param command - Command used to run poetry. This has the same meaning as the + * first argument of spawn() - i.e. it can be a full path, or just a binary name. + * @param cwd - The working directory to use as cwd when running poetry. + */ + constructor(public readonly command: string, private cwd: string) { + this.fixCwd(); + } + + /** + * Returns a Poetry instance corresponding to the binary which can be used to run commands for the cwd. + * + * Poetry commands can be slow and so can be bottleneck to overall discovery time. So trigger command + * execution as soon as possible. To do that we need to ensure the operations before the command are + * performed synchronously. + */ + public static async getPoetry(cwd: string): Promise { + // Following check should be performed synchronously so we trigger poetry execution as soon as possible. + if (!(await hasValidPyprojectToml(cwd))) { + // This check is not expensive and may change during a session, so we need not cache it. + return undefined; + } + if (Poetry.poetryPromise.get(cwd) === undefined || isTestExecution()) { + Poetry.poetryPromise.set(cwd, Poetry.locate(cwd)); + } + return Poetry.poetryPromise.get(cwd); + } + + private static async locate(cwd: string): Promise { + // First thing this method awaits on should be poetry command execution, hence perform all operations + // before that synchronously. + + traceVerbose(`Getting poetry for cwd ${cwd}`); + // Produce a list of candidate binaries to be probed by exec'ing them. + function* getCandidates() { + try { + const customPoetryPath = getPythonSetting('poetryPath'); + if (customPoetryPath && customPoetryPath !== 'poetry') { + // If user has specified a custom poetry path, use it first. + yield customPoetryPath; + } + } catch (ex) { + traceError(`Failed to get poetry setting`, ex); + } + // Check unqualified filename, in case it's on PATH. + yield 'poetry'; + const home = getUserHomeDir(); + if (home) { + const defaultPoetryPath = path.join(home, '.poetry', 'bin', 'poetry'); + if (pathExistsSync(defaultPoetryPath)) { + yield defaultPoetryPath; + } + } + } + + // Probe the candidates, and pick the first one that exists and does what we need. + for (const poetryPath of getCandidates()) { + traceVerbose(`Probing poetry binary for ${cwd}: ${poetryPath}`); + const poetry = new Poetry(poetryPath, cwd); + const virtualenvs = await poetry.getEnvList(); + if (virtualenvs !== undefined) { + traceVerbose(`Found poetry via filesystem probing for ${cwd}: ${poetryPath}`); + return poetry; + } + traceVerbose(`Failed to find poetry for ${cwd}: ${poetryPath}`); + } + + // Didn't find anything. + traceVerbose(`No poetry binary found for ${cwd}`); + return undefined; + } + + /** + * Retrieves list of Python environments known to this poetry for this working directory. + * Returns `undefined` if we failed to spawn because the binary doesn't exist or isn't on PATH, + * or the current user doesn't have execute permissions for it, or this poetry couldn't handle + * command line arguments that we passed (indicating an old version that we do not support, or + * poetry has not been setup properly for the cwd). + * + * Corresponds to "poetry env list --full-path". Swallows errors if any. + */ + public async getEnvList(): Promise { + return this.getEnvListCached(this.cwd); + } + + /** + * Method created to facilitate caching. The caching decorator uses function arguments as cache key, + * so pass in cwd on which we need to cache. + */ + @cache(30_000, true, 10_000) + private async getEnvListCached(_cwd: string): Promise { + const result = await this.safeShellExecute(`${this.command} env list --full-path`); + if (!result) { + return undefined; + } + /** + * We expect stdout to contain something like: + * + * \poetry_2-tutorial-project-6hnqYwvD-py3.7 + * \poetry_2-tutorial-project-6hnqYwvD-py3.8 + * \poetry_2-tutorial-project-6hnqYwvD-py3.9 (Activated) + * + * So we'll need to remove the string "(Activated)" after splitting lines to get the full path. + */ + const activated = '(Activated)'; + const res = await Promise.all( + splitLines(result.stdout).map(async (line) => { + if (line.endsWith(activated)) { + line = line.slice(0, -activated.length); + } + const folder = line.trim(); + return (await pathExists(folder)) ? folder : undefined; + }), + ); + return res.filter((r) => r !== undefined).map((r) => r!); + } + + /** + * Retrieves interpreter path of the currently activated virtual environment for this working directory. + * Corresponds to "poetry env info -p". Swallows errors if any. + */ + public async getActiveEnvPath(): Promise { + return this.getActiveEnvPathCached(this.cwd); + } + + /** + * Method created to facilitate caching. The caching decorator uses function arguments as cache key, + * so pass in cwd on which we need to cache. + */ + @cache(20_000, true, 10_000) + private async getActiveEnvPathCached(_cwd: string): Promise { + const result = await this.safeShellExecute(`${this.command} env info -p`, true); + if (!result) { + return undefined; + } + return result.stdout.trim(); + } + + /** + * Retrieves `virtualenvs.path` setting for this working directory. `virtualenvs.path` setting defines where virtual + * environments are created for the directory. Corresponds to "poetry config virtualenvs.path". Swallows errors if any. + */ + public async getVirtualenvsPathSetting(): Promise { + const result = await this.safeShellExecute(`${this.command} config virtualenvs.path`); + if (!result) { + return undefined; + } + return result.stdout.trim(); + } + + /** + * Due to an upstream poetry issue on Windows https://github.com/python-poetry/poetry/issues/3829, + * 'poetry env list' does not handle case-insensitive paths as cwd, which are valid on Windows. + * So we need to pass the case-exact path as cwd. + * It has been observed that only the drive letter in `cwd` is lowercased here. Unfortunately, + * there's no good way to get case of the drive letter correctly without using Win32 APIs: + * https://stackoverflow.com/questions/33086985/how-to-obtain-case-exact-path-of-a-file-in-node-js-on-windows + * So we do it manually. + */ + private fixCwd(): void { + if (getOSType() === OSType.Windows) { + if (/^[a-z]:/.test(this.cwd)) { + // Replace first character by the upper case version of the character. + const a = this.cwd.split(':'); + a[0] = a[0].toUpperCase(); + this.cwd = a.join(':'); + } + } + } + + private async safeShellExecute(command: string, logVerbose = false) { + // It has been observed that commands related to conda or poetry binary take upto 10-15 seconds unlike + // python binaries. So have a large timeout. + const stopWatch = new StopWatch(); + const result = await shellExecute(command, { + cwd: this.cwd, + throwOnStdErr: true, + timeout: POETRY_TIMEOUT, + }).catch((ex) => { + if (logVerbose) { + traceVerbose(ex); + } else { + traceError(ex); + } + return undefined; + }); + traceVerbose(`Time taken to run ${command} in ms`, stopWatch.elapsedTime); + return result; + } +} + +/** + * Returns true if interpreter path belongs to a poetry environment which is associated with a particular folder, + * false otherwise. + * @param interpreterPath Absolute path to any python interpreter. + * @param folder Absolute path to the folder. + * @param poetryPath Poetry command to use to calculate the result. + */ +export async function isPoetryEnvironmentRelatedToFolder( + interpreterPath: string, + folder: string, + poetryPath?: string, +): Promise { + const poetry = poetryPath ? new Poetry(poetryPath, folder) : await Poetry.getPoetry(folder); + const pathToEnv = await poetry?.getActiveEnvPath(); + if (!pathToEnv) { + return false; + } + return isParentPath(interpreterPath, pathToEnv); +} + +/** + * Does best effort to verify whether a folder has been setup for poetry, by looking for "valid" pyproject.toml file. + * Note "valid" is best effort here, i.e we only verify the minimal features. + * + * @param folder Folder to look for pyproject.toml file in. + */ +async function hasValidPyprojectToml(folder: string): Promise { + const pyprojectToml = path.join(folder, 'pyproject.toml'); + if (!pathExistsSync(pyprojectToml)) { + return false; + } + const content = await readFile(pyprojectToml); + if (!content.includes('[tool.poetry]')) { + return false; + } + // It may still be the case that. + // - pyproject.toml is not a valid toml file + // - Some fields are not setup properly for poetry or are missing + // ... possibly more + // But we only wish to verify the minimal features. + return true; +} diff --git a/src/client/pythonEnvironments/common/environmentManagers/pyenv.ts b/src/client/pythonEnvironments/common/environmentManagers/pyenv.ts new file mode 100644 index 000000000000..8556e6f19f90 --- /dev/null +++ b/src/client/pythonEnvironments/common/environmentManagers/pyenv.ts @@ -0,0 +1,264 @@ +import * as path from 'path'; +import { getEnvironmentVariable, getOSType, getUserHomeDir, OSType } from '../../../common/utils/platform'; +import { arePathsSame, isParentPath, pathExists, shellExecute } from '../externalDependencies'; +import { traceVerbose } from '../../../logging'; + +export function getPyenvDir(): string { + // Check if the pyenv environment variables exist: PYENV on Windows, PYENV_ROOT on Unix. + // They contain the path to pyenv's installation folder. + // If they don't exist, use the default path: ~/.pyenv/pyenv-win on Windows, ~/.pyenv on Unix. + // If the interpreter path starts with the path to the pyenv folder, then it is a pyenv environment. + // See https://github.com/pyenv/pyenv#locating-the-python-installation for general usage, + // And https://github.com/pyenv-win/pyenv-win for Windows specifics. + let pyenvDir = getEnvironmentVariable('PYENV_ROOT') ?? getEnvironmentVariable('PYENV'); + + if (!pyenvDir) { + const homeDir = getUserHomeDir() || ''; + pyenvDir = + getOSType() === OSType.Windows ? path.join(homeDir, '.pyenv', 'pyenv-win') : path.join(homeDir, '.pyenv'); + } + + return pyenvDir; +} + +let pyenvBinary: string | undefined; + +export function setPyEnvBinary(pyenvBin: string): void { + pyenvBinary = pyenvBin; +} + +async function getPyenvBinary(): Promise { + if (pyenvBinary && (await pathExists(pyenvBinary))) { + return pyenvBinary; + } + + const pyenvDir = getPyenvDir(); + const pyenvBin = path.join(pyenvDir, 'bin', 'pyenv'); + if (await pathExists(pyenvBin)) { + return pyenvBin; + } + return 'pyenv'; +} + +export async function getActivePyenvForDirectory(cwd: string): Promise { + const pyenvBin = await getPyenvBinary(); + try { + const pyenvInterpreterPath = await shellExecute(`${pyenvBin} which python`, { cwd }); + return pyenvInterpreterPath.stdout.trim(); + } catch (ex) { + traceVerbose(ex); + return undefined; + } +} + +export function getPyenvVersionsDir(): string { + return path.join(getPyenvDir(), 'versions'); +} + +/** + * Checks if a given directory path is same as `pyenv` shims path. This checks + * `~/.pyenv/shims` on posix and `~/.pyenv/pyenv-win/shims` on windows. + * @param {string} dirPath: Absolute path to any directory + * @returns {boolean}: Returns true if the patch is same as `pyenv` shims directory. + */ + +export function isPyenvShimDir(dirPath: string): boolean { + const shimPath = path.join(getPyenvDir(), 'shims'); + return arePathsSame(shimPath, dirPath) || arePathsSame(`${shimPath}${path.sep}`, dirPath); +} +/** + * Checks if the given interpreter belongs to a pyenv based environment. + * @param {string} interpreterPath: Absolute path to the python interpreter. + * @returns {boolean}: Returns true if the interpreter belongs to a pyenv environment. + */ + +export async function isPyenvEnvironment(interpreterPath: string): Promise { + const pathToCheck = interpreterPath; + const pyenvDir = getPyenvDir(); + + if (!(await pathExists(pyenvDir))) { + return false; + } + + return isParentPath(pathToCheck, pyenvDir); +} + +export interface IPyenvVersionStrings { + pythonVer?: string; + distro?: string; + distroVer?: string; +} +/** + * This function provides parsers for some of the common and known distributions + * supported by pyenv. To get the list of supported pyenv distributions, run + * `pyenv install --list` + * + * The parsers below were written based on the list obtained from pyenv version 1.2.21 + */ +function getKnownPyenvVersionParsers(): Map IPyenvVersionStrings | undefined> { + /** + * This function parses versions that are plain python versions. + * @param str string to parse + * + * Parses : + * 2.7.18 + * 3.9.0 + */ + function pythonOnly(str: string): IPyenvVersionStrings { + return { + pythonVer: str, + distro: undefined, + distroVer: undefined, + }; + } + + /** + * This function parses versions that are distro versions. + * @param str string to parse + * + * Examples: + * miniconda3-4.7.12 + * anaconda3-2020.07 + */ + function distroOnly(str: string): IPyenvVersionStrings | undefined { + const parts = str.split('-'); + if (parts.length === 3) { + return { + pythonVer: undefined, + distroVer: `${parts[1]}-${parts[2]}`, + distro: parts[0], + }; + } + + if (parts.length === 2) { + return { + pythonVer: undefined, + distroVer: parts[1], + distro: parts[0], + }; + } + + return { + pythonVer: undefined, + distroVer: undefined, + distro: str, + }; + } + + /** + * This function parser pypy environments supported by the pyenv install command + * @param str string to parse + * + * Examples: + * pypy-c-jit-latest + * pypy-c-nojit-latest + * pypy-dev + * pypy-stm-2.3 + * pypy-stm-2.5.1 + * pypy-1.5-src + * pypy-1.5 + * pypy3.5-5.7.1-beta-src + * pypy3.5-5.7.1-beta + * pypy3.5-5.8.0-src + * pypy3.5-5.8.0 + */ + function pypyParser(str: string): IPyenvVersionStrings | undefined { + const pattern = /[0-9\.]+/; + + const parts = str.split('-'); + const pythonVer = parts[0].search(pattern) > 0 ? parts[0].substr('pypy'.length) : undefined; + if (parts.length === 2) { + return { + pythonVer, + distroVer: parts[1], + distro: 'pypy', + }; + } + + if ( + parts.length === 3 && + (parts[2].startsWith('src') || + parts[2].startsWith('beta') || + parts[2].startsWith('alpha') || + parts[2].startsWith('win64')) + ) { + const part1 = parts[1].startsWith('v') ? parts[1].substr(1) : parts[1]; + return { + pythonVer, + distroVer: `${part1}-${parts[2]}`, + distro: 'pypy', + }; + } + + if (parts.length === 3 && parts[1] === 'stm') { + return { + pythonVer, + distroVer: parts[2], + distro: `${parts[0]}-${parts[1]}`, + }; + } + + if (parts.length === 4 && parts[1] === 'c') { + return { + pythonVer, + distroVer: parts[3], + distro: `pypy-${parts[1]}-${parts[2]}`, + }; + } + + if (parts.length === 4 && parts[3].startsWith('src')) { + return { + pythonVer, + distroVer: `${parts[1]}-${parts[2]}-${parts[3]}`, + distro: 'pypy', + }; + } + + return { + pythonVer, + distroVer: undefined, + distro: 'pypy', + }; + } + + const parsers: Map IPyenvVersionStrings | undefined> = new Map(); + parsers.set('activepython', distroOnly); + parsers.set('anaconda', distroOnly); + parsers.set('graalpython', distroOnly); + parsers.set('ironpython', distroOnly); + parsers.set('jython', distroOnly); + parsers.set('micropython', distroOnly); + parsers.set('miniconda', distroOnly); + parsers.set('miniforge', distroOnly); + parsers.set('pypy', pypyParser); + parsers.set('pyston', distroOnly); + parsers.set('stackless', distroOnly); + parsers.set('3', pythonOnly); + parsers.set('2', pythonOnly); + + return parsers; +} +/** + * This function parses the name of the commonly installed versions of pyenv based environments. + * @param str string to parse. + * + * Remarks: Depending on the environment, the name itself can contain distribution info like + * name and version. Sometimes it may also have python version as a part of the name. This function + * extracts the various strings. + */ + +export function parsePyenvVersion(str: string): IPyenvVersionStrings | undefined { + const allParsers = getKnownPyenvVersionParsers(); + const knownPrefixes = Array.from(allParsers.keys()); + + const parsers = knownPrefixes + .filter((k) => str.startsWith(k)) + .map((p) => allParsers.get(p)) + .filter((p) => p !== undefined); + + if (parsers.length > 0 && parsers[0]) { + return parsers[0](str); + } + + return undefined; +} diff --git a/src/client/pythonEnvironments/common/environmentManagers/simplevirtualenvs.ts b/src/client/pythonEnvironments/common/environmentManagers/simplevirtualenvs.ts new file mode 100644 index 000000000000..0ad24252f341 --- /dev/null +++ b/src/client/pythonEnvironments/common/environmentManagers/simplevirtualenvs.ts @@ -0,0 +1,212 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import * as fsapi from '../../../common/platform/fs-paths'; +import '../../../common/extensions'; +import { splitLines } from '../../../common/stringUtils'; +import { getEnvironmentVariable, getOSType, getUserHomeDir, OSType } from '../../../common/utils/platform'; +import { PythonVersion, UNKNOWN_PYTHON_VERSION } from '../../base/info'; +import { comparePythonVersionSpecificity } from '../../base/info/env'; +import { parseBasicVersion, parseRelease, parseVersion } from '../../base/info/pythonVersion'; +import { isParentPath, pathExists, readFile } from '../externalDependencies'; + +function getPyvenvConfigPathsFrom(interpreterPath: string): string[] { + const pyvenvConfigFile = 'pyvenv.cfg'; + + // Check if the pyvenv.cfg file is in the parent directory relative to the interpreter. + // env + // |__ pyvenv.cfg <--- check if this file exists + // |__ bin or Scripts + // |__ python <--- interpreterPath + const venvPath1 = path.join(path.dirname(path.dirname(interpreterPath)), pyvenvConfigFile); + + // Check if the pyvenv.cfg file is in the directory as the interpreter. + // env + // |__ pyvenv.cfg <--- check if this file exists + // |__ python <--- interpreterPath + const venvPath2 = path.join(path.dirname(interpreterPath), pyvenvConfigFile); + + // The paths are ordered in the most common to least common + return [venvPath1, venvPath2]; +} + +/** + * Checks if the given interpreter is a virtual environment. + * @param {string} interpreterPath: Absolute path to the python interpreter. + * @returns {boolean} : Returns true if the interpreter belongs to a venv environment. + */ +export async function isVirtualEnvironment(interpreterPath: string): Promise { + return isVenvEnvironment(interpreterPath); +} + +/** + * Checks if the given interpreter belongs to a venv based environment. + * @param {string} interpreterPath: Absolute path to the python interpreter. + * @returns {boolean} : Returns true if the interpreter belongs to a venv environment. + */ +export async function isVenvEnvironment(interpreterPath: string): Promise { + const venvPaths = getPyvenvConfigPathsFrom(interpreterPath); + + // We don't need to test all at once, testing each one here + for (const venvPath of venvPaths) { + if (await pathExists(venvPath)) { + return true; + } + } + return false; +} + +/** + * Checks if the given interpreter belongs to a virtualenv based environment. + * @param {string} interpreterPath: Absolute path to the python interpreter. + * @returns {boolean} : Returns true if the interpreter belongs to a virtualenv environment. + */ +export async function isVirtualenvEnvironment(interpreterPath: string): Promise { + // Check if there are any activate.* files in the same directory as the interpreter. + // + // env + // |__ activate, activate.* <--- check if any of these files exist + // |__ python <--- interpreterPath + const directory = path.dirname(interpreterPath); + const files = await fsapi.readdir(directory); + const regex = /^activate(\.([A-z]|\d)+)?$/i; + + return files.find((file) => regex.test(file)) !== undefined; +} + +async function getDefaultVirtualenvwrapperDir(): Promise { + const homeDir = getUserHomeDir() || ''; + + // In Windows, the default path for WORKON_HOME is %USERPROFILE%\Envs. + // If 'Envs' is not available we should default to '.virtualenvs'. Since that + // is also valid for windows. + if (getOSType() === OSType.Windows) { + // ~/Envs with uppercase 'E' is the default home dir for + // virtualEnvWrapper. + const envs = path.join(homeDir, 'Envs'); + if (await pathExists(envs)) { + return envs; + } + } + return path.join(homeDir, '.virtualenvs'); +} + +function getWorkOnHome(): Promise { + // The WORKON_HOME variable contains the path to the root directory of all virtualenvwrapper environments. + // If the interpreter path belongs to one of them then it is a virtualenvwrapper type of environment. + const workOnHome = getEnvironmentVariable('WORKON_HOME'); + if (workOnHome) { + return Promise.resolve(workOnHome); + } + return getDefaultVirtualenvwrapperDir(); +} + +/** + * Checks if the given interpreter belongs to a virtualenvWrapper based environment. + * @param {string} interpreterPath: Absolute path to the python interpreter. + * @returns {boolean}: Returns true if the interpreter belongs to a virtualenvWrapper environment. + */ +export async function isVirtualenvwrapperEnvironment(interpreterPath: string): Promise { + const workOnHomeDir = await getWorkOnHome(); + + // For environment to be a virtualenvwrapper based it has to follow these two rules: + // 1. It should be in a sub-directory under the WORKON_HOME + // 2. It should be a valid virtualenv environment + return ( + (await pathExists(workOnHomeDir)) && + isParentPath(interpreterPath, workOnHomeDir) && + isVirtualenvEnvironment(interpreterPath) + ); +} + +/** + * Extracts version information from pyvenv.cfg near a given interpreter. + * @param interpreterPath Absolute path to the interpreter + * + * Remarks: This function looks for pyvenv.cfg usually in the same or parent directory. + * Reads the pyvenv.cfg and finds the line that looks like 'version = 3.9.0`. Gets the + * version string from that lines and parses it. + */ +export async function getPythonVersionFromPyvenvCfg(interpreterPath: string): Promise { + const configPaths = getPyvenvConfigPathsFrom(interpreterPath); + let version = UNKNOWN_PYTHON_VERSION; + + // We want to check each of those locations in the order. There is no need to look at + // all of them in parallel. + for (const configPath of configPaths) { + if (await pathExists(configPath)) { + try { + const lines = splitLines(await readFile(configPath)); + + const pythonVersions = lines + .map((line) => { + const parts = line.split('='); + if (parts.length === 2) { + const name = parts[0].toLowerCase().trim(); + const value = parts[1].trim(); + if (name === 'version') { + try { + return parseVersion(value); + } catch (ex) { + return undefined; + } + } else if (name === 'version_info') { + try { + return parseVersionInfo(value); + } catch (ex) { + return undefined; + } + } + } + return undefined; + }) + .filter((v) => v !== undefined) + .map((v) => v!); + + if (pythonVersions.length > 0) { + for (const v of pythonVersions) { + if (comparePythonVersionSpecificity(v, version) > 0) { + version = v; + } + } + } + } catch (ex) { + // There is only ome pyvenv.cfg. If we found it but failed to parse it + // then just return here. No need to look for versions any further. + return UNKNOWN_PYTHON_VERSION; + } + } + } + + return version; +} + +/** + * Convert the given string into the corresponding Python version object. + * Example: + * 3.9.0.final.0 + * 3.9.0.alpha.1 + * 3.9.0.beta.2 + * 3.9.0.candidate.1 + * + * Does not parse: + * 3.9.0 + * 3.9.0a1 + * 3.9.0b2 + * 3.9.0rc1 + */ +function parseVersionInfo(versionInfoStr: string): PythonVersion { + let version: PythonVersion; + let after: string; + try { + [version, after] = parseBasicVersion(versionInfoStr); + } catch { + // XXX Use getEmptyVersion(). + return UNKNOWN_PYTHON_VERSION; + } + if (version.micro !== -1 && after.startsWith('.')) { + [version.release] = parseRelease(after); + } + return version; +} diff --git a/src/client/pythonEnvironments/common/externalDependencies.ts b/src/client/pythonEnvironments/common/externalDependencies.ts index 5b16eb1aa57f..b0922f8bab06 100644 --- a/src/client/pythonEnvironments/common/externalDependencies.ts +++ b/src/client/pythonEnvironments/common/externalDependencies.ts @@ -1,76 +1,207 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import * as fsapi from 'fs-extra'; import * as path from 'path'; -import { ExecutionResult, IProcessServiceFactory } from '../../common/process/types'; -import { IPersistentStateFactory } from '../../common/types'; +import * as vscode from 'vscode'; +import * as fsapi from '../../common/platform/fs-paths'; +import { IWorkspaceService } from '../../common/application/types'; +import { ExecutionResult, IProcessServiceFactory, ShellOptions, SpawnOptions } from '../../common/process/types'; +import { IDisposable, IConfigurationService, IExperimentService } from '../../common/types'; +import { chain, iterable } from '../../common/utils/async'; import { getOSType, OSType } from '../../common/utils/platform'; import { IServiceContainer } from '../../ioc/types'; +import { traceError, traceVerbose } from '../../logging'; let internalServiceContainer: IServiceContainer; export function initializeExternalDependencies(serviceContainer: IServiceContainer): void { internalServiceContainer = serviceContainer; } -function getProcessFactory(): IProcessServiceFactory { - return internalServiceContainer.get(IProcessServiceFactory); +// processes + +export async function shellExecute(command: string, options: ShellOptions = {}): Promise> { + const useWorker = false; + const service = await internalServiceContainer.get(IProcessServiceFactory).create(); + options = { ...options, useWorker }; + return service.shellExec(command, options); +} + +export async function exec( + file: string, + args: string[], + options: SpawnOptions = {}, + useWorker = false, +): Promise> { + const service = await internalServiceContainer.get(IProcessServiceFactory).create(); + options = { ...options, useWorker }; + return service.exec(file, args, options); +} + +export function inExperiment(experimentName: string): boolean { + const service = internalServiceContainer.get(IExperimentService); + return service.inExperimentSync(experimentName); } -export async function shellExecute(command: string, timeout: number): Promise> { - const proc = await getProcessFactory().create(); - return proc.shellExec(command, { timeout }); +// Workspace + +export function isVirtualWorkspace(): boolean { + const service = internalServiceContainer.get(IWorkspaceService); + return service.isVirtualWorkspace; } +// filesystem + export function pathExists(absPath: string): Promise { return fsapi.pathExists(absPath); } +export function pathExistsSync(absPath: string): boolean { + return fsapi.pathExistsSync(absPath); +} + export function readFile(filePath: string): Promise { return fsapi.readFile(filePath, 'utf-8'); } -export function arePathsSame(path1: string, path2: string): boolean { - path1 = path.normalize(path1); - path2 = path.normalize(path2); - if (getOSType() === OSType.Windows) { - return path1.toUpperCase() === path2.toUpperCase(); +export function readFileSync(filePath: string): string { + return fsapi.readFileSync(filePath, 'utf-8'); +} + +/** + * Returns true if given file path exists within the given parent directory, false otherwise. + * @param filePath File path to check for + * @param parentPath The potential parent path to check for + */ +export function isParentPath(filePath: string, parentPath: string): boolean { + if (!parentPath.endsWith(path.sep)) { + parentPath += path.sep; } - return path1 === path2; + if (!filePath.endsWith(path.sep)) { + filePath += path.sep; + } + return normCasePath(filePath).startsWith(normCasePath(parentPath)); +} + +export async function isDirectory(filename: string): Promise { + const stat = await fsapi.lstat(filename); + return stat.isDirectory(); } -function getPersistentStateFactory(): IPersistentStateFactory { - return internalServiceContainer.get(IPersistentStateFactory); +export function normalizePath(filename: string): string { + return path.normalize(filename); } -export interface IPersistentStore { - get(): T | undefined; - set(value: T): Promise; +export function resolvePath(filename: string): string { + return path.resolve(filename); } -export function getGlobalPersistentStore(key: string): IPersistentStore { - const factory = getPersistentStateFactory(); - const state = factory.createGlobalPersistentState(key, undefined); +export function normCasePath(filePath: string): string { + return getOSType() === OSType.Windows ? path.normalize(filePath).toUpperCase() : path.normalize(filePath); +} + +export function arePathsSame(path1: string, path2: string): boolean { + return normCasePath(path1) === normCasePath(path2); +} - return { - get() { return state.value; }, - set(value: T) { return state.updateValue(value); }, - }; +export async function resolveSymbolicLink(absPath: string, stats?: fsapi.Stats, count?: number): Promise { + stats = stats ?? (await fsapi.lstat(absPath)); + if (stats.isSymbolicLink()) { + if (count && count > 5) { + traceError(`Detected a potential symbolic link loop at ${absPath}, terminating resolution.`); + return absPath; + } + const link = await fsapi.readlink(absPath); + // Result from readlink is not guaranteed to be an absolute path. For eg. on Mac it resolves + // /usr/local/bin/python3.9 -> ../../../Library/Frameworks/Python.framework/Versions/3.9/bin/python3.9 + // + // The resultant path is reported relative to the symlink directory we resolve. Convert that to absolute path. + const absLinkPath = path.isAbsolute(link) ? link : path.resolve(path.dirname(absPath), link); + count = count ? count + 1 : 1; + return resolveSymbolicLink(absLinkPath, undefined, count); + } + return absPath; } -export async function getFileInfo(filePath: string): Promise<{ctime:number, mtime:number}> { - const data = await fsapi.lstat(filePath); - return { - ctime: data.ctime.valueOf(), - mtime: data.mtime.valueOf(), - }; +export async function getFileInfo(filePath: string): Promise<{ ctime: number; mtime: number }> { + try { + const data = await fsapi.lstat(filePath); + return { + ctime: data.ctime.valueOf(), + mtime: data.mtime.valueOf(), + }; + } catch (ex) { + // This can fail on some cases, such as, `reparse points` on windows. So, return the + // time as -1. Which we treat as not set in the extension. + traceVerbose(`Failed to get file info for ${filePath}`, ex); + return { ctime: -1, mtime: -1 }; + } } -export async function resolveSymbolicLink(filepath:string): Promise { - const stats = await fsapi.lstat(filepath); +export async function isFile(filePath: string): Promise { + const stats = await fsapi.lstat(filePath); if (stats.isSymbolicLink()) { - const link = await fsapi.readlink(filepath); - return resolveSymbolicLink(link); + const resolvedPath = await resolveSymbolicLink(filePath, stats); + const resolvedStats = await fsapi.lstat(resolvedPath); + return resolvedStats.isFile(); } - return filepath; + return stats.isFile(); +} + +/** + * Returns full path to sub directories of a given directory. + * @param {string} root : path to get sub-directories from. + * @param options : If called with `resolveSymlinks: true`, then symlinks found in + * the directory are resolved and if they resolve to directories + * then resolved values are returned. + */ +export async function* getSubDirs( + root: string, + options?: { resolveSymlinks?: boolean }, +): AsyncIterableIterator { + const dirContents = await fsapi.readdir(root, { withFileTypes: true }); + const generators = dirContents.map((item) => { + async function* generator() { + const fullPath = path.join(root, item.name); + if (item.isDirectory()) { + yield fullPath; + } else if (options?.resolveSymlinks && item.isSymbolicLink()) { + // The current FS item is a symlink. It can potentially be a file + // or a directory. Resolve it first and then check if it is a directory. + const resolvedPath = await resolveSymbolicLink(fullPath); + const resolvedPathStat = await fsapi.lstat(resolvedPath); + if (resolvedPathStat.isDirectory()) { + yield resolvedPath; + } + } + } + + return generator(); + }); + + yield* iterable(chain(generators)); +} + +/** + * Returns the value for setting `python.`. + * @param name The name of the setting. + */ +export function getPythonSetting(name: string, root?: string): T | undefined { + const resource = root ? vscode.Uri.file(root) : undefined; + const settings = internalServiceContainer.get(IConfigurationService).getSettings(resource); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (settings as any)[name]; +} + +/** + * Registers the listener to be called when a particular setting changes. + * @param name The name of the setting. + * @param callback The listener function to be called when the setting changes. + */ +export function onDidChangePythonSetting(name: string, callback: () => void, root?: string): IDisposable { + return vscode.workspace.onDidChangeConfiguration((event: vscode.ConfigurationChangeEvent) => { + const scope = root ? vscode.Uri.file(root) : undefined; + if (event.affectsConfiguration(`python.${name}`, scope)) { + callback(); + } + }); } diff --git a/src/client/pythonEnvironments/common/posixUtils.ts b/src/client/pythonEnvironments/common/posixUtils.ts index 2b830c8ea42e..8149706a5707 100644 --- a/src/client/pythonEnvironments/common/posixUtils.ts +++ b/src/client/pythonEnvironments/common/posixUtils.ts @@ -1,16 +1,27 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import * as fsapi from 'fs-extra'; +import * as fs from 'fs'; import * as path from 'path'; -import { getPathEnvironmentVariable } from '../../common/utils/platform'; +import { uniq } from 'lodash'; +import * as fsapi from '../../common/platform/fs-paths'; +import { getSearchPathEntries } from '../../common/utils/exec'; +import { resolveSymbolicLink } from './externalDependencies'; +import { traceError, traceInfo, traceVerbose, traceWarn } from '../../logging'; /** - * Checks if a given path ends with python*.exe + * Determine if the given filename looks like the simplest Python executable. + */ +export function matchBasicPythonBinFilename(filename: string): boolean { + return path.basename(filename) === 'python'; +} + +/** + * Checks if a given path matches pattern for standard non-windows python binary. * @param {string} interpreterPath : Path to python interpreter. - * @returns {boolean} : Returns true if the path matches pattern for windows python executable. + * @returns {boolean} : Returns true if the path matches pattern for non-windows python binary. */ -export function isPosixPythonBin(interpreterPath:string): boolean { +export function matchPythonBinFilename(filename: string): boolean { /** * This Reg-ex matches following file names: * python @@ -20,42 +31,126 @@ export function isPosixPythonBin(interpreterPath:string): boolean { */ const posixPythonBinPattern = /^python(\d+(\.\d+)?)?$/; - return posixPythonBinPattern.test(path.basename(interpreterPath)); + return posixPythonBinPattern.test(path.basename(filename)); } export async function commonPosixBinPaths(): Promise { - const searchPaths = (getPathEnvironmentVariable() || '') - .split(path.delimiter) - .filter((p) => p.length > 0); - - const paths: string[] = Array.from(new Set( - [ - '/bin', - '/etc', - '/lib', - '/lib/x86_64-linux-gnu', - '/lib64', - '/sbin', - '/snap/bin', - '/usr/bin', - '/usr/games', - '/usr/include', - '/usr/lib', - '/usr/lib/x86_64-linux-gnu', - '/usr/lib64', - '/usr/libexec', - '/usr/local', - '/usr/local/bin', - '/usr/local/etc', - '/usr/local/games', - '/usr/local/lib', - '/usr/local/sbin', - '/usr/sbin', - '/usr/share', - '~/.local/bin', - ].concat(searchPaths), - )); + const searchPaths = getSearchPathEntries(); + + const paths: string[] = Array.from( + new Set( + [ + '/bin', + '/etc', + '/lib', + '/lib/x86_64-linux-gnu', + '/lib64', + '/sbin', + '/snap/bin', + '/usr/bin', + '/usr/games', + '/usr/include', + '/usr/lib', + '/usr/lib/x86_64-linux-gnu', + '/usr/lib64', + '/usr/libexec', + '/usr/local', + '/usr/local/bin', + '/usr/local/etc', + '/usr/local/games', + '/usr/local/lib', + '/usr/local/sbin', + '/usr/sbin', + '/usr/share', + '~/.local/bin', + ].concat(searchPaths), + ), + ); const exists = await Promise.all(paths.map((p) => fsapi.pathExists(p))); return paths.filter((_, index) => exists[index]); } + +/** + * Finds python interpreter binaries or symlinks in a given directory. + * @param searchDir : Directory to search in + * @returns : Paths to python binaries found in the search directory. + */ +async function findPythonBinariesInDir(searchDir: string) { + return (await fs.promises.readdir(searchDir, { withFileTypes: true })) + .filter((dirent: fs.Dirent) => !dirent.isDirectory()) + .map((dirent: fs.Dirent) => path.join(searchDir, dirent.name)) + .filter(matchPythonBinFilename); +} + +/** + * Pick the shortest versions of the paths. The paths could be + * the binary itself or its symlink, whichever path is shorter. + * + * E.g: + * /usr/bin/python -> /System/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7 + * /usr/bin/python3 -> /System/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7 + * /usr/bin/python3.7 -> /System/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7 + * + * Of the 4 possible paths to same binary (3 symlinks and 1 binary path), + * the code below will pick '/usr/bin/python'. + */ +function pickShortestPath(pythonPaths: string[]) { + let shortestLen = pythonPaths[0].length; + let shortestPath = pythonPaths[0]; + for (const p of pythonPaths) { + if (p.length <= shortestLen) { + shortestLen = p.length; + shortestPath = p; + } + } + return shortestPath; +} + +/** + * Finds python binaries in given directories. This function additionally reduces the + * found binaries to unique set be resolving symlinks, and returns the shortest paths + * to the said unique binaries. + * @param searchDirs : Directories to search for python binaries + * @returns : Unique paths to python interpreters found in the search dirs. + */ +export async function getPythonBinFromPosixPaths(searchDirs: string[]): Promise { + const binToLinkMap = new Map(); + for (const searchDir of searchDirs) { + const paths = await findPythonBinariesInDir(searchDir).catch((ex) => { + traceWarn('Looking for python binaries within', searchDir, 'failed with', ex); + return []; + }); + + for (const filepath of paths) { + // Ensure that we have a collection of unique global binaries by + // resolving all symlinks to the target binaries. + try { + traceVerbose(`Attempting to resolve symbolic link: ${filepath}`); + const resolvedBin = await resolveSymbolicLink(filepath); + if (binToLinkMap.has(resolvedBin)) { + binToLinkMap.get(resolvedBin)?.push(filepath); + } else { + binToLinkMap.set(resolvedBin, [filepath]); + } + traceInfo(`Found: ${filepath} --> ${resolvedBin}`); + } catch (ex) { + traceError('Failed to resolve symbolic link: ', ex); + } + } + } + + // Pick the shortest versions of the paths. The paths could be + // the binary itself or its symlink, whichever path is shorter. + // + // E.g: + // /usr/bin/python -> /System/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7 + // /usr/bin/python3 -> /System/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7 + // /usr/bin/python3.7 -> /System/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7 + // + // Of the 4 possible paths to same binary (3 symlinks and 1 binary path), + // the code below will pick '/usr/bin/python'. + const keys = Array.from(binToLinkMap.keys()); + const pythonPaths = keys.map((key) => pickShortestPath([key, ...(binToLinkMap.get(key) ?? [])])); + return uniq(pythonPaths); +} diff --git a/src/client/pythonEnvironments/common/pythonBinariesWatcher.ts b/src/client/pythonEnvironments/common/pythonBinariesWatcher.ts new file mode 100644 index 000000000000..efc7d56409c8 --- /dev/null +++ b/src/client/pythonEnvironments/common/pythonBinariesWatcher.ts @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as minimatch from 'minimatch'; +import * as path from 'path'; +import { FileChangeType, watchLocationForPattern } from '../../common/platform/fileSystemWatcher'; +import { IDisposable } from '../../common/types'; +import { getOSType, OSType } from '../../common/utils/platform'; +import { traceVerbose } from '../../logging'; + +const [executable, binName] = getOSType() === OSType.Windows ? ['python.exe', 'Scripts'] : ['python', 'bin']; + +/** + * Start watching the given directory for changes to files matching the glob. + * + * @param baseDir - the root to which the glob is applied while watching + * @param callback - called when the event happens + * @param executableGlob - matches the executable under the directory + */ +export function watchLocationForPythonBinaries( + baseDir: string, + callback: (type: FileChangeType, absPath: string) => void, + executableGlob: string = executable, +): IDisposable { + const resolvedGlob = path.posix.normalize(executableGlob); + const [baseGlob] = resolvedGlob.split('/').slice(-1); + function callbackClosure(type: FileChangeType, e: string) { + traceVerbose('Received event', type, JSON.stringify(e), 'for baseglob', baseGlob); + const isMatch = minimatch.default(path.basename(e), baseGlob, { nocase: getOSType() === OSType.Windows }); + if (!isMatch) { + // When deleting the file for some reason path to all directories leading up to python are reported + // Skip those events + return; + } + callback(type, e); + } + return watchLocationForPattern(baseDir, resolvedGlob, callbackClosure); +} + +// eslint-disable-next-line no-shadow +export enum PythonEnvStructure { + Standard = 'standard', + Flat = 'flat', +} + +/** + * Generate the globs to use when watching a directory for Python executables. + */ +export function resolvePythonExeGlobs( + basenameGlob = executable, + // Be default we always expect a "standard" structure. + structure = PythonEnvStructure.Standard, +): string[] { + if (path.posix.normalize(basenameGlob).includes('/')) { + throw Error(`invalid basename glob "${basenameGlob}"`); + } + const globs: string[] = []; + if (structure === PythonEnvStructure.Standard) { + globs.push( + // Check the directory. + basenameGlob, + // Check in all subdirectories. + `*/${basenameGlob}`, + // Check in the "bin" directory of all subdirectories. + `*/${binName}/${basenameGlob}`, + ); + } else if (structure === PythonEnvStructure.Flat) { + // Check only the directory. + globs.push(basenameGlob); + } + return globs; +} diff --git a/src/client/pythonEnvironments/common/registryKeys.worker.ts b/src/client/pythonEnvironments/common/registryKeys.worker.ts new file mode 100644 index 000000000000..05996d057f11 --- /dev/null +++ b/src/client/pythonEnvironments/common/registryKeys.worker.ts @@ -0,0 +1,24 @@ +import { Registry } from 'winreg'; +import { parentPort, workerData } from 'worker_threads'; +import { IRegistryKey } from './windowsRegistry'; + +const WinReg = require('winreg'); + +const regKey = new WinReg(workerData); + +function copyRegistryKeys(keys: IRegistryKey[]): IRegistryKey[] { + // Use the map function to create a new array with copies of the specified properties. + return keys.map((key) => ({ + hive: key.hive, + arch: key.arch, + key: key.key, + })); +} + +regKey.keys((err: Error, res: Registry[]) => { + if (!parentPort) { + throw new Error('Not in a worker thread'); + } + const messageRes = copyRegistryKeys(res); + parentPort.postMessage({ err, res: messageRes }); +}); diff --git a/src/client/pythonEnvironments/common/registryValues.worker.ts b/src/client/pythonEnvironments/common/registryValues.worker.ts new file mode 100644 index 000000000000..eaef7cbd58a7 --- /dev/null +++ b/src/client/pythonEnvironments/common/registryValues.worker.ts @@ -0,0 +1,27 @@ +import { RegistryItem } from 'winreg'; +import { parentPort, workerData } from 'worker_threads'; +import { IRegistryValue } from './windowsRegistry'; + +const WinReg = require('winreg'); + +const regKey = new WinReg(workerData); + +function copyRegistryValues(values: IRegistryValue[]): IRegistryValue[] { + // Use the map function to create a new array with copies of the specified properties. + return values.map((value) => ({ + hive: value.hive, + arch: value.arch, + key: value.key, + name: value.name, + type: value.type, + value: value.value, + })); +} + +regKey.values((err: Error, res: RegistryItem[]) => { + if (!parentPort) { + throw new Error('Not in a worker thread'); + } + const messageRes = copyRegistryValues(res); + parentPort.postMessage({ err, res: messageRes }); +}); diff --git a/src/client/pythonEnvironments/common/virtualenvwrapperUtils.ts b/src/client/pythonEnvironments/common/virtualenvwrapperUtils.ts deleted file mode 100644 index 1849934ff59e..000000000000 --- a/src/client/pythonEnvironments/common/virtualenvwrapperUtils.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import * as path from 'path'; -import { getOSType, getUserHomeDir, OSType } from '../../common/utils/platform'; - -export function getDefaultVirtualenvwrapperDir(): string { - const homeDir = getUserHomeDir() || ''; - - // In Windows, the default path for WORKON_HOME is %USERPROFILE%\Envs. - if (getOSType() === OSType.Windows) { - return path.join(homeDir, 'Envs'); - } - return path.join(homeDir, '.virtualenvs'); -} diff --git a/src/client/pythonEnvironments/common/windowsRegistry.ts b/src/client/pythonEnvironments/common/windowsRegistry.ts new file mode 100644 index 000000000000..801ef0c907b1 --- /dev/null +++ b/src/client/pythonEnvironments/common/windowsRegistry.ts @@ -0,0 +1,60 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { HKCU, HKLM, Options, REG_SZ, Registry, RegistryItem } from 'winreg'; +import * as path from 'path'; +import { createDeferred } from '../../common/utils/async'; +import { executeWorkerFile } from '../../common/process/worker/main'; + +export { HKCU, HKLM, REG_SZ, Options }; + +export interface IRegistryKey { + hive: string; + arch: string; + key: string; + parentKey?: IRegistryKey; +} + +export interface IRegistryValue { + hive: string; + arch: string; + key: string; + name: string; + type: string; + value: string; +} + +export async function readRegistryValues(options: Options, useWorkerThreads: boolean): Promise { + if (!useWorkerThreads) { + // eslint-disable-next-line global-require + const WinReg = require('winreg'); + const regKey = new WinReg(options); + const deferred = createDeferred(); + regKey.values((err: Error, res: RegistryItem[]) => { + if (err) { + deferred.reject(err); + } + deferred.resolve(res); + }); + return deferred.promise; + } + return executeWorkerFile(path.join(__dirname, 'registryValues.worker.js'), options); +} + +export async function readRegistryKeys(options: Options, useWorkerThreads: boolean): Promise { + if (!useWorkerThreads) { + // eslint-disable-next-line global-require + const WinReg = require('winreg'); + const regKey = new WinReg(options); + const deferred = createDeferred(); + regKey.keys((err: Error, res: Registry[]) => { + if (err) { + deferred.reject(err); + } + deferred.resolve(res); + }); + return deferred.promise; + } + return executeWorkerFile(path.join(__dirname, 'registryKeys.worker.js'), options); +} diff --git a/src/client/pythonEnvironments/common/windowsUtils.ts b/src/client/pythonEnvironments/common/windowsUtils.ts index 881525b8d6bc..fe15f71522a5 100644 --- a/src/client/pythonEnvironments/common/windowsUtils.ts +++ b/src/client/pythonEnvironments/common/windowsUtils.ts @@ -1,22 +1,35 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { uniqBy } from 'lodash'; import * as path from 'path'; +import { isTestExecution } from '../../common/constants'; +import { traceError, traceVerbose } from '../../logging'; import { - Options, REG_SZ, Registry, RegistryItem, -} from 'winreg'; -import { traceVerbose } from '../../common/logger'; -import { createDeferred } from '../../common/utils/async'; + HKCU, + HKLM, + IRegistryKey, + IRegistryValue, + readRegistryKeys, + readRegistryValues, + REG_SZ, +} from './windowsRegistry'; -// tslint:disable-next-line: no-single-line-block-comment /* eslint-disable global-require */ +/** + * Determine if the given filename looks like the simplest Python executable. + */ +export function matchBasicPythonBinFilename(filename: string): boolean { + return path.basename(filename).toLowerCase() === 'python.exe'; +} + /** * Checks if a given path ends with python*.exe * @param {string} interpreterPath : Path to python interpreter. * @returns {boolean} : Returns true if the path matches pattern for windows python executable. */ -export function isWindowsPythonExe(interpreterPath:string): boolean { +export function matchPythonBinFilename(filename: string): boolean { /** * This Reg-ex matches following file names: * python.exe @@ -26,56 +39,29 @@ export function isWindowsPythonExe(interpreterPath:string): boolean { */ const windowsPythonExes = /^python(\d+(.\d+)?)?\.exe$/; - return windowsPythonExes.test(path.basename(interpreterPath)); -} - -export async function readRegistryValues(options: Options): Promise { - // tslint:disable-next-line:no-require-imports - const WinReg = require('winreg'); - const regKey = new WinReg(options); - const deferred = createDeferred(); - regKey.values((err:Error, res:RegistryItem[]) => { - if (err) { - deferred.reject(err); - } - deferred.resolve(res); - }); - return deferred.promise; -} - -export async function readRegistryKeys(options: Options): Promise { - // tslint:disable-next-line:no-require-imports - const WinReg = require('winreg'); - const regKey = new WinReg(options); - const deferred = createDeferred(); - regKey.keys((err:Error, res:Registry[]) => { - if (err) { - deferred.reject(err); - } - deferred.resolve(res); - }); - return deferred.promise; + return windowsPythonExes.test(path.basename(filename)); } -export interface IRegistryInterpreterData{ +export interface IRegistryInterpreterData { interpreterPath: string; versionStr?: string; - sysVersionStr?:string; + sysVersionStr?: string; bitnessStr?: string; - displayName?: string; + companyDisplayName?: string; distroOrgName?: string; } async function getInterpreterDataFromKey( - { arch, hive, key }:Registry, - distroOrgName:string, + { arch, hive, key }: IRegistryKey, + distroOrgName: string, + useWorkerThreads: boolean, ): Promise { - const result:IRegistryInterpreterData = { + const result: IRegistryInterpreterData = { interpreterPath: '', distroOrgName, }; - const values:RegistryItem[] = await readRegistryValues({ arch, hive, key }); + const values: IRegistryValue[] = await readRegistryValues({ arch, hive, key }, useWorkerThreads); for (const value of values) { switch (value.name) { case 'SysArchitecture': @@ -88,17 +74,17 @@ async function getInterpreterDataFromKey( result.versionStr = value.value; break; case 'DisplayName': - result.displayName = value.value; + result.companyDisplayName = value.value; break; default: break; } } - const subKeys:Registry[] = await readRegistryKeys({ arch, hive, key }); + const subKeys: IRegistryKey[] = await readRegistryKeys({ arch, hive, key }, useWorkerThreads); const subKey = subKeys.map((s) => s.key).find((s) => s.endsWith('InstallPath')); if (subKey) { - const subKeyValues:RegistryItem[] = await readRegistryValues({ arch, hive, key: subKey }); + const subKeyValues: IRegistryValue[] = await readRegistryValues({ arch, hive, key: subKey }, useWorkerThreads); const value = subKeyValues.find((v) => v.name === 'ExecutablePath'); if (value) { result.interpreterPath = value.value; @@ -115,12 +101,59 @@ async function getInterpreterDataFromKey( } export async function getInterpreterDataFromRegistry( - arch:string, - hive:string, - key:string, + arch: string, + hive: string, + key: string, + useWorkerThreads: boolean, ): Promise { - const subKeys = await readRegistryKeys({ arch, hive, key }); + const subKeys = await readRegistryKeys({ arch, hive, key }, useWorkerThreads); const distroOrgName = key.substr(key.lastIndexOf('\\') + 1); - const allData = await Promise.all(subKeys.map((subKey) => getInterpreterDataFromKey(subKey, distroOrgName))); + const allData = await Promise.all( + subKeys.map((subKey) => getInterpreterDataFromKey(subKey, distroOrgName, useWorkerThreads)), + ); return (allData.filter((data) => data !== undefined) || []) as IRegistryInterpreterData[]; } + +let registryInterpretersCache: IRegistryInterpreterData[] | undefined; + +/** + * Returns windows registry interpreters from memory, returns undefined if memory is empty. + * getRegistryInterpreters() must be called prior to this to populate memory. + */ +export function getRegistryInterpretersSync(): IRegistryInterpreterData[] | undefined { + return !isTestExecution() ? registryInterpretersCache : undefined; +} + +let registryInterpretersPromise: Promise | undefined; + +export async function getRegistryInterpreters(): Promise { + if (!isTestExecution() && registryInterpretersPromise !== undefined) { + return registryInterpretersPromise; + } + registryInterpretersPromise = getRegistryInterpretersImpl(); + return registryInterpretersPromise; +} + +async function getRegistryInterpretersImpl(useWorkerThreads = false): Promise { + let registryData: IRegistryInterpreterData[] = []; + + for (const arch of ['x64', 'x86']) { + for (const hive of [HKLM, HKCU]) { + const root = '\\SOFTWARE\\Python'; + let keys: string[] = []; + try { + keys = (await readRegistryKeys({ arch, hive, key: root }, useWorkerThreads)).map((k) => k.key); + } catch (ex) { + traceError(`Failed to access Registry: ${arch}\\${hive}\\${root}`, ex); + } + + for (const key of keys) { + registryData = registryData.concat( + await getInterpreterDataFromRegistry(arch, hive, key, useWorkerThreads), + ); + } + } + } + registryInterpretersCache = uniqBy(registryData, (r: IRegistryInterpreterData) => r.interpreterPath); + return registryInterpretersCache; +} diff --git a/src/client/pythonEnvironments/creation/common/commonUtils.ts b/src/client/pythonEnvironments/creation/common/commonUtils.ts new file mode 100644 index 000000000000..8b6ffe1af450 --- /dev/null +++ b/src/client/pythonEnvironments/creation/common/commonUtils.ts @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +import * as path from 'path'; +import { WorkspaceFolder } from 'vscode'; +import * as fs from '../../../common/platform/fs-paths'; +import { Commands } from '../../../common/constants'; +import { Common } from '../../../common/utils/localize'; +import { executeCommand } from '../../../common/vscodeApis/commandApis'; +import { showErrorMessage } from '../../../common/vscodeApis/windowApis'; +import { isWindows } from '../../../common/utils/platform'; + +export async function showErrorMessageWithLogs(message: string): Promise { + const result = await showErrorMessage(message, Common.openOutputPanel, Common.selectPythonInterpreter); + if (result === Common.openOutputPanel) { + await executeCommand(Commands.ViewOutput); + } else if (result === Common.selectPythonInterpreter) { + await executeCommand(Commands.Set_Interpreter); + } +} + +export function getVenvPath(workspaceFolder: WorkspaceFolder): string { + return path.join(workspaceFolder.uri.fsPath, '.venv'); +} + +export async function hasVenv(workspaceFolder: WorkspaceFolder): Promise { + return fs.pathExists(path.join(getVenvPath(workspaceFolder), 'pyvenv.cfg')); +} + +export function getVenvExecutable(workspaceFolder: WorkspaceFolder): string { + if (isWindows()) { + return path.join(getVenvPath(workspaceFolder), 'Scripts', 'python.exe'); + } + return path.join(getVenvPath(workspaceFolder), 'bin', 'python'); +} + +export function getPrefixCondaEnvPath(workspaceFolder: WorkspaceFolder): string { + return path.join(workspaceFolder.uri.fsPath, '.conda'); +} + +export async function hasPrefixCondaEnv(workspaceFolder: WorkspaceFolder): Promise { + return fs.pathExists(getPrefixCondaEnvPath(workspaceFolder)); +} diff --git a/src/client/pythonEnvironments/creation/common/createEnvTriggerUtils.ts b/src/client/pythonEnvironments/creation/common/createEnvTriggerUtils.ts new file mode 100644 index 000000000000..eccbf64a7866 --- /dev/null +++ b/src/client/pythonEnvironments/creation/common/createEnvTriggerUtils.ts @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { ConfigurationTarget, Uri, WorkspaceFolder } from 'vscode'; +import * as fsapi from '../../../common/platform/fs-paths'; +import { getPipRequirementsFiles } from '../provider/venvUtils'; +import { getExtension } from '../../../common/vscodeApis/extensionsApi'; +import { PVSC_EXTENSION_ID } from '../../../common/constants'; +import { PythonExtension } from '../../../api/types'; +import { traceVerbose } from '../../../logging'; +import { getConfiguration } from '../../../common/vscodeApis/workspaceApis'; +import { getWorkspaceStateValue } from '../../../common/persistentState'; + +export const CREATE_ENV_TRIGGER_SETTING_PART = 'createEnvironment.trigger'; +export const CREATE_ENV_TRIGGER_SETTING = `python.${CREATE_ENV_TRIGGER_SETTING_PART}`; + +export async function fileContainsInlineDependencies(_uri: Uri): Promise { + // This is a placeholder for the real implementation of inline dependencies support + // For now we don't detect anything. Once PEP-722/PEP-723 are accepted we can implement + // this properly. + return false; +} + +export async function hasRequirementFiles(workspace: WorkspaceFolder): Promise { + const files = await getPipRequirementsFiles(workspace); + const found = (files?.length ?? 0) > 0; + if (found) { + traceVerbose(`Found requirement files: ${workspace.uri.fsPath}`); + } + return found; +} + +export async function hasKnownFiles(workspace: WorkspaceFolder): Promise { + const filePaths: string[] = [ + 'poetry.lock', + 'conda.yaml', + 'environment.yaml', + 'conda.yml', + 'environment.yml', + 'Pipfile', + 'Pipfile.lock', + ].map((fileName) => path.join(workspace.uri.fsPath, fileName)); + const result = await Promise.all(filePaths.map((f) => fsapi.pathExists(f))); + const found = result.some((r) => r); + if (found) { + traceVerbose(`Found known files: ${workspace.uri.fsPath}`); + } + return found; +} + +export async function isGlobalPythonSelected(workspace: WorkspaceFolder): Promise { + const extension = getExtension(PVSC_EXTENSION_ID); + if (!extension) { + return false; + } + const extensionApi: PythonExtension = extension.exports as PythonExtension; + const interpreter = extensionApi.environments.getActiveEnvironmentPath(workspace.uri); + const details = await extensionApi.environments.resolveEnvironment(interpreter); + const isGlobal = details?.environment === undefined; + if (isGlobal) { + traceVerbose(`Selected python for [${workspace.uri.fsPath}] is [global] type: ${interpreter.path}`); + } + return isGlobal; +} + +/** + * Checks the setting `python.createEnvironment.trigger` to see if we should perform the checks + * to prompt to create an environment. + * Returns True if we should prompt to create an environment. + */ +export function shouldPromptToCreateEnv(): boolean { + const config = getConfiguration('python'); + if (config) { + const value = config.get(CREATE_ENV_TRIGGER_SETTING_PART, 'off'); + return value !== 'off'; + } + + return getWorkspaceStateValue(CREATE_ENV_TRIGGER_SETTING, 'off') !== 'off'; +} + +/** + * Sets `python.createEnvironment.trigger` to 'off' in the user settings. + */ +export function disableCreateEnvironmentTrigger(): void { + const config = getConfiguration('python'); + if (config) { + config.update('createEnvironment.trigger', 'off', ConfigurationTarget.Global); + } +} + +let _alreadyCreateEnvCriteriaCheck = false; +/** + * Run-once wrapper function for the workspace check to prompt to create an environment. + * @returns : True if we should prompt to c environment. + */ +export function isCreateEnvWorkspaceCheckNotRun(): boolean { + if (_alreadyCreateEnvCriteriaCheck) { + return false; + } + _alreadyCreateEnvCriteriaCheck = true; + return true; +} diff --git a/src/client/pythonEnvironments/creation/common/installCheckUtils.ts b/src/client/pythonEnvironments/creation/common/installCheckUtils.ts new file mode 100644 index 000000000000..2d8925cc05f6 --- /dev/null +++ b/src/client/pythonEnvironments/creation/common/installCheckUtils.ts @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License + +import { Diagnostic, DiagnosticSeverity, l10n, Range, TextDocument, Uri } from 'vscode'; +import { installedCheckScript } from '../../../common/process/internal/scripts'; +import { plainExec } from '../../../common/process/rawProcessApis'; +import { traceInfo, traceVerbose, traceError } from '../../../logging'; +import { getConfiguration } from '../../../common/vscodeApis/workspaceApis'; +import { IInterpreterService } from '../../../interpreter/contracts'; + +interface PackageDiagnostic { + package: string; + line: number; + character: number; + endLine: number; + endCharacter: number; + code: string; + severity: DiagnosticSeverity; +} + +export const INSTALL_CHECKER_SOURCE = 'Python-InstalledPackagesChecker'; + +function parseDiagnostics(data: string): Diagnostic[] { + let diagnostics: Diagnostic[] = []; + try { + const raw = JSON.parse(data) as PackageDiagnostic[]; + diagnostics = raw.map((item) => { + const d = new Diagnostic( + new Range(item.line, item.character, item.endLine, item.endCharacter), + l10n.t('Package `{0}` is not installed in the selected environment.', item.package), + item.severity, + ); + d.code = { value: item.code, target: Uri.parse(`https://pypi.org/p/${item.package}`) }; + d.source = INSTALL_CHECKER_SOURCE; + return d; + }); + } catch { + diagnostics = []; + } + return diagnostics; +} + +function getMissingPackageSeverity(doc: TextDocument): number { + const config = getConfiguration('python', doc.uri); + const severity: string = config.get('missingPackage.severity', 'Hint'); + if (severity === 'Error') { + return DiagnosticSeverity.Error; + } + if (severity === 'Warning') { + return DiagnosticSeverity.Warning; + } + if (severity === 'Information') { + return DiagnosticSeverity.Information; + } + return DiagnosticSeverity.Hint; +} + +export async function getInstalledPackagesDiagnostics( + interpreterService: IInterpreterService, + doc: TextDocument, +): Promise { + const interpreter = await interpreterService.getActiveInterpreter(doc.uri); + if (!interpreter) { + return []; + } + const scriptPath = installedCheckScript(); + try { + traceInfo('Running installed packages checker: ', interpreter, scriptPath, doc.uri.fsPath); + const envCopy = { ...process.env, VSCODE_MISSING_PGK_SEVERITY: `${getMissingPackageSeverity(doc)}` }; + const result = await plainExec(interpreter.path, [scriptPath, doc.uri.fsPath], { + env: envCopy, + }); + traceVerbose('Installed packages check result:\n', result.stdout); + if (result.stderr) { + traceError('Installed packages check error:\n', result.stderr); + } + return parseDiagnostics(result.stdout); + } catch (ex) { + traceError('Error while getting installed packages check result:\n', ex); + } + return []; +} diff --git a/src/client/pythonEnvironments/creation/common/workspaceSelection.ts b/src/client/pythonEnvironments/creation/common/workspaceSelection.ts new file mode 100644 index 000000000000..3ebab1c67fb4 --- /dev/null +++ b/src/client/pythonEnvironments/creation/common/workspaceSelection.ts @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { CancellationToken, QuickPickItem, WorkspaceFolder } from 'vscode'; +import * as fsapi from '../../../common/platform/fs-paths'; +import { MultiStepAction, showErrorMessage, showQuickPickWithBack } from '../../../common/vscodeApis/windowApis'; +import { getWorkspaceFolders } from '../../../common/vscodeApis/workspaceApis'; +import { Common, CreateEnv } from '../../../common/utils/localize'; +import { executeCommand } from '../../../common/vscodeApis/commandApis'; + +function hasVirtualEnv(workspace: WorkspaceFolder): Promise { + return Promise.race([ + fsapi.pathExists(path.join(workspace.uri.fsPath, '.venv')), + fsapi.pathExists(path.join(workspace.uri.fsPath, '.conda')), + ]); +} + +async function getWorkspacesForQuickPick(workspaces: readonly WorkspaceFolder[]): Promise { + const items: QuickPickItem[] = []; + for (const workspace of workspaces) { + items.push({ + label: workspace.name, + detail: workspace.uri.fsPath, + description: (await hasVirtualEnv(workspace)) ? CreateEnv.hasVirtualEnv : undefined, + }); + } + + return items; +} + +export interface PickWorkspaceFolderOptions { + allowMultiSelect?: boolean; + token?: CancellationToken; + preSelectedWorkspace?: WorkspaceFolder; +} + +export async function pickWorkspaceFolder( + options?: PickWorkspaceFolderOptions, + context?: MultiStepAction, +): Promise { + const workspaces = getWorkspaceFolders(); + + if (!workspaces || workspaces.length === 0) { + if (context === MultiStepAction.Back) { + // No workspaces and nothing to show, should just go to previous + throw MultiStepAction.Back; + } + const result = await showErrorMessage(CreateEnv.noWorkspace, Common.openFolder); + if (result === Common.openFolder) { + await executeCommand('vscode.openFolder'); + } + return undefined; + } + + if (options?.preSelectedWorkspace) { + if (context === MultiStepAction.Back) { + // In this case there is no Quick Pick shown, should just go to previous + throw MultiStepAction.Back; + } + + return options.preSelectedWorkspace; + } + + if (workspaces.length === 1) { + if (context === MultiStepAction.Back) { + // In this case there is no Quick Pick shown, should just go to previous + throw MultiStepAction.Back; + } + + return workspaces[0]; + } + + // This is multi-root scenario. + const selected = await showQuickPickWithBack( + await getWorkspacesForQuickPick(workspaces), + { + placeHolder: CreateEnv.pickWorkspacePlaceholder, + ignoreFocusOut: true, + canPickMany: options?.allowMultiSelect, + matchOnDescription: true, + matchOnDetail: true, + }, + options?.token, + ); + + if (selected) { + if (Array.isArray(selected)) { + const details = selected.map((s: QuickPickItem) => s.detail).filter((s) => s !== undefined); + return workspaces.filter((w) => details.includes(w.uri.fsPath)); + } + return workspaces.filter((w) => w.uri.fsPath === (selected as QuickPickItem).detail)[0]; + } + + return undefined; +} diff --git a/src/client/pythonEnvironments/creation/createEnvApi.ts b/src/client/pythonEnvironments/creation/createEnvApi.ts new file mode 100644 index 000000000000..899f57728804 --- /dev/null +++ b/src/client/pythonEnvironments/creation/createEnvApi.ts @@ -0,0 +1,160 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { ConfigurationTarget, Disposable, QuickInputButtons } from 'vscode'; +import { Commands } from '../../common/constants'; +import { IDisposableRegistry, IPathUtils } from '../../common/types'; +import { executeCommand, registerCommand } from '../../common/vscodeApis/commandApis'; +import { IInterpreterQuickPick, IPythonPathUpdaterServiceManager } from '../../interpreter/configuration/types'; +import { getCreationEvents, handleCreateEnvironmentCommand } from './createEnvironment'; +import { condaCreationProvider } from './provider/condaCreationProvider'; +import { VenvCreationProvider, VenvCreationProviderId } from './provider/venvCreationProvider'; +import { showInformationMessage } from '../../common/vscodeApis/windowApis'; +import { CreateEnv } from '../../common/utils/localize'; +import { + CreateEnvironmentProvider, + CreateEnvironmentOptions, + CreateEnvironmentResult, + ProposedCreateEnvironmentAPI, + EnvironmentDidCreateEvent, +} from './proposed.createEnvApis'; +import { sendTelemetryEvent } from '../../telemetry'; +import { EventName } from '../../telemetry/constants'; +import { CreateEnvironmentOptionsInternal } from './types'; +import { useEnvExtension } from '../../envExt/api.internal'; +import { PythonEnvironment } from '../../envExt/types'; + +class CreateEnvironmentProviders { + private _createEnvProviders: CreateEnvironmentProvider[] = []; + + constructor() { + this._createEnvProviders = []; + } + + public add(provider: CreateEnvironmentProvider) { + if (this._createEnvProviders.filter((p) => p.id === provider.id).length > 0) { + throw new Error(`Create Environment provider with id ${provider.id} already registered`); + } + this._createEnvProviders.push(provider); + } + + public remove(provider: CreateEnvironmentProvider) { + this._createEnvProviders = this._createEnvProviders.filter((p) => p !== provider); + } + + public getAll(): readonly CreateEnvironmentProvider[] { + return this._createEnvProviders; + } +} + +const _createEnvironmentProviders: CreateEnvironmentProviders = new CreateEnvironmentProviders(); + +export function registerCreateEnvironmentProvider(provider: CreateEnvironmentProvider): Disposable { + _createEnvironmentProviders.add(provider); + return new Disposable(() => { + _createEnvironmentProviders.remove(provider); + }); +} + +export const { onCreateEnvironmentStarted, onCreateEnvironmentExited, isCreatingEnvironment } = getCreationEvents(); + +export function registerCreateEnvironmentFeatures( + disposables: IDisposableRegistry, + interpreterQuickPick: IInterpreterQuickPick, + pythonPathUpdater: IPythonPathUpdaterServiceManager, + pathUtils: IPathUtils, +): void { + disposables.push( + registerCommand( + Commands.Create_Environment, + async ( + options?: CreateEnvironmentOptions & CreateEnvironmentOptionsInternal, + ): Promise => { + if (useEnvExtension()) { + try { + sendTelemetryEvent(EventName.ENVIRONMENT_CREATING, undefined, { + environmentType: undefined, + pythonVersion: undefined, + }); + const result = await executeCommand( + 'python-envs.createAny', + options, + ); + if (result) { + const managerId = result.envId.managerId; + if (managerId === 'ms-python.python:venv') { + sendTelemetryEvent(EventName.ENVIRONMENT_CREATED, undefined, { + environmentType: 'venv', + reason: 'created', + }); + } + if (managerId === 'ms-python.python:conda') { + sendTelemetryEvent(EventName.ENVIRONMENT_CREATED, undefined, { + environmentType: 'conda', + reason: 'created', + }); + } + return { path: result.environmentPath.path }; + } + } catch (err) { + if (err === QuickInputButtons.Back) { + return { workspaceFolder: undefined, action: 'Back' }; + } + throw err; + } + } else { + const providers = _createEnvironmentProviders.getAll(); + return handleCreateEnvironmentCommand(providers, options); + } + return undefined; + }, + ), + registerCommand( + Commands.Create_Environment_Button, + async (): Promise => { + sendTelemetryEvent(EventName.ENVIRONMENT_BUTTON, undefined, undefined); + await executeCommand(Commands.Create_Environment); + }, + ), + registerCreateEnvironmentProvider(new VenvCreationProvider(interpreterQuickPick)), + registerCreateEnvironmentProvider(condaCreationProvider()), + onCreateEnvironmentExited(async (e: EnvironmentDidCreateEvent) => { + if (e.path && e.options?.selectEnvironment) { + await pythonPathUpdater.updatePythonPath( + e.path, + ConfigurationTarget.WorkspaceFolder, + 'ui', + e.workspaceFolder?.uri, + ); + showInformationMessage(`${CreateEnv.informEnvCreation} ${pathUtils.getDisplayName(e.path)}`); + } + }), + ); +} + +export function buildEnvironmentCreationApi(): ProposedCreateEnvironmentAPI { + return { + onWillCreateEnvironment: onCreateEnvironmentStarted, + onDidCreateEnvironment: onCreateEnvironmentExited, + createEnvironment: async ( + options?: CreateEnvironmentOptions | undefined, + ): Promise => { + const providers = _createEnvironmentProviders.getAll(); + try { + return await handleCreateEnvironmentCommand(providers, options); + } catch (err) { + return { path: undefined, workspaceFolder: undefined, action: undefined, error: err as Error }; + } + }, + registerCreateEnvironmentProvider: (provider: CreateEnvironmentProvider) => + registerCreateEnvironmentProvider(provider), + }; +} + +export async function createVirtualEnvironment(options?: CreateEnvironmentOptions & CreateEnvironmentOptionsInternal) { + const provider = _createEnvironmentProviders.getAll().find((p) => p.id === VenvCreationProviderId); + if (!provider) { + return; + } + return handleCreateEnvironmentCommand([provider], { ...options, providerId: provider.id }); +} diff --git a/src/client/pythonEnvironments/creation/createEnvButtonContext.ts b/src/client/pythonEnvironments/creation/createEnvButtonContext.ts new file mode 100644 index 000000000000..4ce7d07ad69d --- /dev/null +++ b/src/client/pythonEnvironments/creation/createEnvButtonContext.ts @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { IDisposableRegistry } from '../../common/types'; +import { executeCommand } from '../../common/vscodeApis/commandApis'; +import { getConfiguration, onDidChangeConfiguration } from '../../common/vscodeApis/workspaceApis'; + +async function setShowCreateEnvButtonContextKey(): Promise { + const config = getConfiguration('python'); + const showCreateEnvButton = config.get('createEnvironment.contentButton', 'show') === 'show'; + await executeCommand('setContext', 'showCreateEnvButton', showCreateEnvButton); +} + +export function registerCreateEnvironmentButtonFeatures(disposables: IDisposableRegistry): void { + disposables.push( + onDidChangeConfiguration(async () => { + await setShowCreateEnvButtonContextKey(); + }), + ); + + setShowCreateEnvButtonContextKey(); +} diff --git a/src/client/pythonEnvironments/creation/createEnvironment.ts b/src/client/pythonEnvironments/creation/createEnvironment.ts new file mode 100644 index 000000000000..c7c4e84f445c --- /dev/null +++ b/src/client/pythonEnvironments/creation/createEnvironment.ts @@ -0,0 +1,210 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License + +import { Event, EventEmitter, QuickInputButtons, QuickPickItem } from 'vscode'; +import { CreateEnv } from '../../common/utils/localize'; +import { + MultiStepAction, + MultiStepNode, + showQuickPick, + showQuickPickWithBack, +} from '../../common/vscodeApis/windowApis'; +import { traceError, traceVerbose } from '../../logging'; +import { + CreateEnvironmentOptions, + CreateEnvironmentResult, + CreateEnvironmentProvider, + EnvironmentWillCreateEvent, + EnvironmentDidCreateEvent, +} from './proposed.createEnvApis'; +import { CreateEnvironmentOptionsInternal } from './types'; + +const onCreateEnvironmentStartedEvent = new EventEmitter(); +const onCreateEnvironmentExitedEvent = new EventEmitter(); + +let startedEventCount = 0; + +function isBusyCreatingEnvironment(): boolean { + return startedEventCount > 0; +} + +function fireStartedEvent(options?: CreateEnvironmentOptions): void { + onCreateEnvironmentStartedEvent.fire({ options }); + startedEventCount += 1; +} + +function fireExitedEvent(result?: CreateEnvironmentResult, options?: CreateEnvironmentOptions, error?: Error): void { + startedEventCount -= 1; + if (result) { + onCreateEnvironmentExitedEvent.fire({ options, ...result }); + } else if (error) { + onCreateEnvironmentExitedEvent.fire({ options, error }); + } +} + +export function getCreationEvents(): { + onCreateEnvironmentStarted: Event; + onCreateEnvironmentExited: Event; + isCreatingEnvironment: () => boolean; +} { + return { + onCreateEnvironmentStarted: onCreateEnvironmentStartedEvent.event, + onCreateEnvironmentExited: onCreateEnvironmentExitedEvent.event, + isCreatingEnvironment: isBusyCreatingEnvironment, + }; +} + +async function createEnvironment( + provider: CreateEnvironmentProvider, + options: CreateEnvironmentOptions & CreateEnvironmentOptionsInternal, +): Promise { + let result: CreateEnvironmentResult | undefined; + let err: Error | undefined; + try { + fireStartedEvent(options); + result = await provider.createEnvironment(options); + } catch (ex) { + if (ex === QuickInputButtons.Back) { + traceVerbose('Create Env: User clicked back button during environment creation'); + if (!options.showBackButton) { + return undefined; + } + } + err = ex as Error; + throw err; + } finally { + fireExitedEvent(result, options, err); + } + return result; +} + +interface CreateEnvironmentProviderQuickPickItem extends QuickPickItem { + id: string; +} + +async function showCreateEnvironmentQuickPick( + providers: readonly CreateEnvironmentProvider[], + options?: CreateEnvironmentOptions & CreateEnvironmentOptionsInternal, +): Promise { + const items: CreateEnvironmentProviderQuickPickItem[] = providers.map((p) => ({ + label: p.name, + description: p.description, + id: p.id, + })); + + if (options?.providerId) { + const provider = providers.find((p) => p.id === options.providerId); + if (provider) { + return provider; + } + } + + let selectedItem: CreateEnvironmentProviderQuickPickItem | CreateEnvironmentProviderQuickPickItem[] | undefined; + + if (options?.showBackButton) { + selectedItem = await showQuickPickWithBack(items, { + placeHolder: CreateEnv.providersQuickPickPlaceholder, + matchOnDescription: true, + ignoreFocusOut: true, + }); + } else { + selectedItem = await showQuickPick(items, { + placeHolder: CreateEnv.providersQuickPickPlaceholder, + matchOnDescription: true, + ignoreFocusOut: true, + }); + } + + if (selectedItem) { + const selected = Array.isArray(selectedItem) ? selectedItem[0] : selectedItem; + if (selected) { + const selections = providers.filter((p) => p.id === selected.id); + if (selections.length > 0) { + return selections[0]; + } + } + } + return undefined; +} + +function getOptionsWithDefaults( + options?: CreateEnvironmentOptions & CreateEnvironmentOptionsInternal, +): CreateEnvironmentOptions & CreateEnvironmentOptionsInternal { + return { + installPackages: true, + ignoreSourceControl: true, + showBackButton: false, + selectEnvironment: true, + ...options, + }; +} + +export async function handleCreateEnvironmentCommand( + providers: readonly CreateEnvironmentProvider[], + options?: CreateEnvironmentOptions & CreateEnvironmentOptionsInternal, +): Promise { + const optionsWithDefaults = getOptionsWithDefaults(options); + let selectedProvider: CreateEnvironmentProvider | undefined; + const envTypeStep = new MultiStepNode( + undefined, + async (context?: MultiStepAction) => { + if (providers.length > 0) { + try { + selectedProvider = await showCreateEnvironmentQuickPick(providers, optionsWithDefaults); + } catch (ex) { + if (ex === MultiStepAction.Back || ex === MultiStepAction.Cancel) { + return ex; + } + throw ex; + } + if (!selectedProvider) { + return MultiStepAction.Cancel; + } + } else { + traceError('No Environment Creation providers were registered.'); + if (context === MultiStepAction.Back) { + // There are no providers to select, so just step back. + return MultiStepAction.Back; + } + } + return MultiStepAction.Continue; + }, + undefined, + ); + + let result: CreateEnvironmentResult | undefined; + const createStep = new MultiStepNode( + envTypeStep, + async (context?: MultiStepAction) => { + if (context === MultiStepAction.Back) { + // This step is to trigger creation, which can go into other extension. + return MultiStepAction.Back; + } + if (selectedProvider) { + try { + result = await createEnvironment(selectedProvider, optionsWithDefaults); + } catch (ex) { + if (ex === MultiStepAction.Back || ex === MultiStepAction.Cancel) { + return ex; + } + throw ex; + } + } + return MultiStepAction.Continue; + }, + undefined, + ); + envTypeStep.next = createStep; + + const action = await MultiStepNode.run(envTypeStep); + if (options?.showBackButton) { + if (action === MultiStepAction.Back || action === MultiStepAction.Cancel) { + result = { action, workspaceFolder: undefined, path: undefined, error: undefined }; + } + } + + if (result) { + return Object.freeze(result); + } + return undefined; +} diff --git a/src/client/pythonEnvironments/creation/createEnvironmentTrigger.ts b/src/client/pythonEnvironments/creation/createEnvironmentTrigger.ts new file mode 100644 index 000000000000..5119290a0c2d --- /dev/null +++ b/src/client/pythonEnvironments/creation/createEnvironmentTrigger.ts @@ -0,0 +1,156 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { Disposable, Uri, WorkspaceFolder } from 'vscode'; +import { + fileContainsInlineDependencies, + hasKnownFiles, + hasRequirementFiles, + isGlobalPythonSelected, + shouldPromptToCreateEnv, + isCreateEnvWorkspaceCheckNotRun, + disableCreateEnvironmentTrigger, +} from './common/createEnvTriggerUtils'; +import { getWorkspaceFolder } from '../../common/vscodeApis/workspaceApis'; +import { traceError, traceInfo, traceVerbose } from '../../logging'; +import { hasPrefixCondaEnv, hasVenv } from './common/commonUtils'; +import { showInformationMessage } from '../../common/vscodeApis/windowApis'; +import { Common, CreateEnv } from '../../common/utils/localize'; +import { executeCommand, registerCommand } from '../../common/vscodeApis/commandApis'; +import { Commands } from '../../common/constants'; +import { Resource } from '../../common/types'; +import { sendTelemetryEvent } from '../../telemetry'; +import { EventName } from '../../telemetry/constants'; + +export enum CreateEnvironmentCheckKind { + /** + * Checks if environment creation is needed based on file location and content. + */ + File = 'file', + + /** + * Checks if environment creation is needed based on workspace contents. + */ + Workspace = 'workspace', +} + +export interface CreateEnvironmentTriggerOptions { + force?: boolean; +} + +async function createEnvironmentCheckForWorkspace(uri: Uri): Promise { + const workspace = getWorkspaceFolder(uri); + if (!workspace) { + traceInfo(`CreateEnv Trigger - Workspace not found for ${uri.fsPath}`); + return; + } + + const missingRequirements = async (workspaceFolder: WorkspaceFolder) => + !(await hasRequirementFiles(workspaceFolder)); + + const isNonGlobalPythonSelected = async (workspaceFolder: WorkspaceFolder) => + !(await isGlobalPythonSelected(workspaceFolder)); + + // Skip showing the Create Environment prompt if one of the following is True: + // 1. The workspace already has a ".venv" or ".conda" env + // 2. The workspace does NOT have "requirements.txt" or "requirements/*.txt" files + // 3. The workspace has known files for other environment types like environment.yml, conda.yml, poetry.lock, etc. + // 4. The selected python is NOT classified as a global python interpreter + const skipPrompt: boolean = ( + await Promise.all([ + hasVenv(workspace), + hasPrefixCondaEnv(workspace), + missingRequirements(workspace), + hasKnownFiles(workspace), + isNonGlobalPythonSelected(workspace), + ]) + ).some((r) => r); + + if (skipPrompt) { + sendTelemetryEvent(EventName.ENVIRONMENT_CHECK_RESULT, undefined, { result: 'criteria-not-met' }); + traceInfo(`CreateEnv Trigger - Skipping for ${uri.fsPath}`); + return; + } + + sendTelemetryEvent(EventName.ENVIRONMENT_CHECK_RESULT, undefined, { result: 'criteria-met' }); + const selection = await showInformationMessage( + CreateEnv.Trigger.workspaceTriggerMessage, + CreateEnv.Trigger.createEnvironment, + Common.doNotShowAgain, + ); + + if (selection === CreateEnv.Trigger.createEnvironment) { + try { + await executeCommand(Commands.Create_Environment); + } catch (error) { + traceError('CreateEnv Trigger - Error while creating environment: ', error); + } + } else if (selection === Common.doNotShowAgain) { + disableCreateEnvironmentTrigger(); + } +} + +function runOnceWorkspaceCheck(uri: Uri, options: CreateEnvironmentTriggerOptions = {}): Promise { + if (isCreateEnvWorkspaceCheckNotRun() || options?.force) { + return createEnvironmentCheckForWorkspace(uri); + } + sendTelemetryEvent(EventName.ENVIRONMENT_CHECK_RESULT, undefined, { result: 'already-ran' }); + traceVerbose('CreateEnv Trigger - skipping this because it was already run'); + return Promise.resolve(); +} + +async function createEnvironmentCheckForFile(uri: Uri, options?: CreateEnvironmentTriggerOptions): Promise { + if (await fileContainsInlineDependencies(uri)) { + // TODO: Handle create environment for each file here. + // pending acceptance of PEP-722/PEP-723 + + // For now we do the same thing as for workspace. + await runOnceWorkspaceCheck(uri, options); + } + + // If the file does not have any inline dependencies, then we do the same thing + // as for workspace. + await runOnceWorkspaceCheck(uri, options); +} + +export async function triggerCreateEnvironmentCheck( + kind: CreateEnvironmentCheckKind, + uri: Resource, + options?: CreateEnvironmentTriggerOptions, +): Promise { + if (!uri) { + sendTelemetryEvent(EventName.ENVIRONMENT_CHECK_RESULT, undefined, { result: 'no-uri' }); + traceVerbose('CreateEnv Trigger - Skipping No URI provided'); + return; + } + + if (shouldPromptToCreateEnv()) { + if (kind === CreateEnvironmentCheckKind.File) { + await createEnvironmentCheckForFile(uri, options); + } else { + await runOnceWorkspaceCheck(uri, options); + } + } else { + sendTelemetryEvent(EventName.ENVIRONMENT_CHECK_RESULT, undefined, { result: 'turned-off' }); + traceVerbose('CreateEnv Trigger - turned off in settings'); + } +} + +export function triggerCreateEnvironmentCheckNonBlocking( + kind: CreateEnvironmentCheckKind, + uri: Resource, + options?: CreateEnvironmentTriggerOptions, +): void { + // The Event loop for Node.js runs functions with setTimeout() with lower priority than setImmediate. + // This is done to intentionally avoid blocking anything that the user wants to do. + setTimeout(() => triggerCreateEnvironmentCheck(kind, uri, options).ignoreErrors(), 0); +} + +export function registerCreateEnvironmentTriggers(disposables: Disposable[]): void { + disposables.push( + registerCommand(Commands.Create_Environment_Check, (file: Resource) => { + sendTelemetryEvent(EventName.ENVIRONMENT_CHECK_TRIGGER, undefined, { trigger: 'as-command' }); + triggerCreateEnvironmentCheckNonBlocking(CreateEnvironmentCheckKind.File, file, { force: true }); + }), + ); +} diff --git a/src/client/pythonEnvironments/creation/globalPipInTerminalTrigger.ts b/src/client/pythonEnvironments/creation/globalPipInTerminalTrigger.ts new file mode 100644 index 000000000000..76a55bea19a0 --- /dev/null +++ b/src/client/pythonEnvironments/creation/globalPipInTerminalTrigger.ts @@ -0,0 +1,85 @@ +import { Disposable, TerminalShellExecutionStartEvent } from 'vscode'; +import { + disableCreateEnvironmentTrigger, + isGlobalPythonSelected, + shouldPromptToCreateEnv, +} from './common/createEnvTriggerUtils'; +import { getWorkspaceFolder, getWorkspaceFolders } from '../../common/vscodeApis/workspaceApis'; +import { Common, CreateEnv } from '../../common/utils/localize'; +import { traceError, traceInfo } from '../../logging'; +import { executeCommand } from '../../common/vscodeApis/commandApis'; +import { Commands, PVSC_EXTENSION_ID } from '../../common/constants'; +import { CreateEnvironmentResult } from './proposed.createEnvApis'; +import { onDidStartTerminalShellExecution, showWarningMessage } from '../../common/vscodeApis/windowApis'; +import { sendTelemetryEvent } from '../../telemetry'; +import { EventName } from '../../telemetry/constants'; + +function checkCommand(command: string): boolean { + const lower = command.toLowerCase(); + return ( + lower.startsWith('pip install') || + lower.startsWith('pip3 install') || + lower.startsWith('python -m pip install') || + lower.startsWith('python3 -m pip install') + ); +} + +export function registerTriggerForPipInTerminal(disposables: Disposable[]): void { + if (!shouldPromptToCreateEnv()) { + return; + } + + const folders = getWorkspaceFolders(); + if (!folders || folders.length === 0) { + return; + } + + const createEnvironmentTriggered: Map = new Map(); + folders.forEach((workspaceFolder) => { + createEnvironmentTriggered.set(workspaceFolder.uri.fsPath, false); + }); + + disposables.push( + onDidStartTerminalShellExecution(async (e: TerminalShellExecutionStartEvent) => { + const workspaceFolder = getWorkspaceFolder(e.shellIntegration.cwd); + if ( + workspaceFolder && + !createEnvironmentTriggered.get(workspaceFolder.uri.fsPath) && + (await isGlobalPythonSelected(workspaceFolder)) + ) { + if (e.execution.commandLine.isTrusted && checkCommand(e.execution.commandLine.value)) { + createEnvironmentTriggered.set(workspaceFolder.uri.fsPath, true); + sendTelemetryEvent(EventName.ENVIRONMENT_TERMINAL_GLOBAL_PIP); + const selection = await showWarningMessage( + CreateEnv.Trigger.globalPipInstallTriggerMessage, + CreateEnv.Trigger.createEnvironment, + Common.doNotShowAgain, + ); + if (selection === CreateEnv.Trigger.createEnvironment) { + try { + const result: CreateEnvironmentResult = await executeCommand(Commands.Create_Environment, { + workspaceFolder, + providerId: `${PVSC_EXTENSION_ID}:venv`, + }); + if (result.path) { + traceInfo('CreateEnv Trigger - Environment created: ', result.path); + traceInfo( + `CreateEnv Trigger - Running: ${ + result.path + } -m ${e.execution.commandLine.value.trim()}`, + ); + e.shellIntegration.executeCommand( + `${result.path} -m ${e.execution.commandLine.value}`.trim(), + ); + } + } catch (error) { + traceError('CreateEnv Trigger - Error while creating environment: ', error); + } + } else if (selection === Common.doNotShowAgain) { + disableCreateEnvironmentTrigger(); + } + } + } + }), + ); +} diff --git a/src/client/pythonEnvironments/creation/installedPackagesDiagnostic.ts b/src/client/pythonEnvironments/creation/installedPackagesDiagnostic.ts new file mode 100644 index 000000000000..0b55e1ec5ce1 --- /dev/null +++ b/src/client/pythonEnvironments/creation/installedPackagesDiagnostic.ts @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License + +import { Diagnostic, DiagnosticCollection, TextDocument, Uri } from 'vscode'; +import { IDisposableRegistry } from '../../common/types'; +import { executeCommand } from '../../common/vscodeApis/commandApis'; +import { createDiagnosticCollection, onDidChangeDiagnostics } from '../../common/vscodeApis/languageApis'; +import { getActiveTextEditor, onDidChangeActiveTextEditor } from '../../common/vscodeApis/windowApis'; +import { + getOpenTextDocuments, + onDidCloseTextDocument, + onDidOpenTextDocument, + onDidSaveTextDocument, +} from '../../common/vscodeApis/workspaceApis'; +import { traceVerbose } from '../../logging'; +import { getInstalledPackagesDiagnostics, INSTALL_CHECKER_SOURCE } from './common/installCheckUtils'; +import { IInterpreterService } from '../../interpreter/contracts'; + +export const DEPS_NOT_INSTALLED_KEY = 'pythonDepsNotInstalled'; + +async function setContextForActiveEditor(diagnosticCollection: DiagnosticCollection): Promise { + const doc = getActiveTextEditor()?.document; + if (doc && (doc.languageId === 'pip-requirements' || doc.fileName.endsWith('pyproject.toml'))) { + const diagnostics = diagnosticCollection.get(doc.uri); + if (diagnostics && diagnostics.length > 0) { + traceVerbose(`Setting context for python dependencies not installed: ${doc.uri.fsPath}`); + await executeCommand('setContext', DEPS_NOT_INSTALLED_KEY, true); + return; + } + } + + // undefined here in the logs means no file was selected + await executeCommand('setContext', DEPS_NOT_INSTALLED_KEY, false); +} + +export function registerInstalledPackagesDiagnosticsProvider( + disposables: IDisposableRegistry, + interpreterService: IInterpreterService, +): void { + const diagnosticCollection = createDiagnosticCollection(INSTALL_CHECKER_SOURCE); + const updateDiagnostics = (uri: Uri, diagnostics: Diagnostic[]) => { + if (diagnostics.length > 0) { + diagnosticCollection.set(uri, diagnostics); + } else if (diagnosticCollection.has(uri)) { + diagnosticCollection.delete(uri); + } + }; + + disposables.push(diagnosticCollection); + disposables.push( + onDidOpenTextDocument(async (doc: TextDocument) => { + if (doc.languageId === 'pip-requirements' || doc.fileName.endsWith('pyproject.toml')) { + const diagnostics = await getInstalledPackagesDiagnostics(interpreterService, doc); + updateDiagnostics(doc.uri, diagnostics); + } + }), + onDidSaveTextDocument(async (doc: TextDocument) => { + if (doc.languageId === 'pip-requirements' || doc.fileName.endsWith('pyproject.toml')) { + const diagnostics = await getInstalledPackagesDiagnostics(interpreterService, doc); + updateDiagnostics(doc.uri, diagnostics); + } + }), + onDidCloseTextDocument((e: TextDocument) => { + updateDiagnostics(e.uri, []); + }), + onDidChangeDiagnostics(async () => { + await setContextForActiveEditor(diagnosticCollection); + }), + onDidChangeActiveTextEditor(async () => { + await setContextForActiveEditor(diagnosticCollection); + }), + interpreterService.onDidChangeInterpreter(() => { + getOpenTextDocuments().forEach(async (doc: TextDocument) => { + if (doc.languageId === 'pip-requirements' || doc.fileName.endsWith('pyproject.toml')) { + const diagnostics = await getInstalledPackagesDiagnostics(interpreterService, doc); + updateDiagnostics(doc.uri, diagnostics); + } + }); + }), + ); + + getOpenTextDocuments().forEach(async (doc: TextDocument) => { + if (doc.languageId === 'pip-requirements' || doc.fileName.endsWith('pyproject.toml')) { + const diagnostics = await getInstalledPackagesDiagnostics(interpreterService, doc); + updateDiagnostics(doc.uri, diagnostics); + } + }); +} diff --git a/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts b/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts new file mode 100644 index 000000000000..ea520fdd27e2 --- /dev/null +++ b/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts @@ -0,0 +1,186 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License + +import { Event, Disposable, WorkspaceFolder } from 'vscode'; +import { EnvironmentTools } from '../../api/types'; + +export type CreateEnvironmentUserActions = 'Back' | 'Cancel'; +export type EnvironmentProviderId = string; + +/** + * Options used when creating a Python environment. + */ +export interface CreateEnvironmentOptions { + /** + * Default `true`. If `true`, the environment creation handler is expected to install packages. + */ + installPackages?: boolean; + + /** + * Default `true`. If `true`, the environment creation provider is expected to add the environment to ignore list + * for the source control. + */ + ignoreSourceControl?: boolean; + + /** + * Default `false`. If `true` the creation provider should show back button when showing QuickPick or QuickInput. + */ + showBackButton?: boolean; + + /** + * Default `true`. If `true`, the environment after creation will be selected. + */ + selectEnvironment?: boolean; +} + +/** + * Params passed on `onWillCreateEnvironment` event handler. + */ +export interface EnvironmentWillCreateEvent { + /** + * Options used to create a Python environment. + */ + readonly options: CreateEnvironmentOptions | undefined; +} + +export type CreateEnvironmentResult = + | { + /** + * Workspace folder associated with the environment. + */ + readonly workspaceFolder?: WorkspaceFolder; + + /** + * Path to the executable python in the environment + */ + readonly path: string; + + /** + * User action that resulted in exit from the create environment flow. + */ + readonly action?: CreateEnvironmentUserActions; + + /** + * Error if any occurred during environment creation. + */ + readonly error?: Error; + } + | { + /** + * Workspace folder associated with the environment. + */ + readonly workspaceFolder?: WorkspaceFolder; + + /** + * Path to the executable python in the environment + */ + readonly path?: string; + + /** + * User action that resulted in exit from the create environment flow. + */ + readonly action: CreateEnvironmentUserActions; + + /** + * Error if any occurred during environment creation. + */ + readonly error?: Error; + } + | { + /** + * Workspace folder associated with the environment. + */ + readonly workspaceFolder?: WorkspaceFolder; + + /** + * Path to the executable python in the environment + */ + readonly path?: string; + + /** + * User action that resulted in exit from the create environment flow. + */ + readonly action?: CreateEnvironmentUserActions; + + /** + * Error if any occurred during environment creation. + */ + readonly error: Error; + }; + +/** + * Params passed on `onDidCreateEnvironment` event handler. + */ +export type EnvironmentDidCreateEvent = CreateEnvironmentResult & { + /** + * Options used to create the Python environment. + */ + readonly options: CreateEnvironmentOptions | undefined; +}; + +/** + * Extensions that want to contribute their own environment creation can do that by registering an object + * that implements this interface. + */ +export interface CreateEnvironmentProvider { + /** + * This API is called when user selects this provider from a QuickPick to select the type of environment + * user wants. This API is expected to show a QuickPick or QuickInput to get the user input and return + * the path to the Python executable in the environment. + * + * @param {CreateEnvironmentOptions} [options] Options used to create a Python environment. + * + * @returns a promise that resolves to the path to the + * Python executable in the environment. Or any action taken by the user, such as back or cancel. + */ + createEnvironment(options?: CreateEnvironmentOptions): Promise; + + /** + * Unique ID for the creation provider, typically : + */ + id: EnvironmentProviderId; + + /** + * Display name for the creation provider. + */ + name: string; + + /** + * Description displayed to the user in the QuickPick to select environment provider. + */ + description: string; + + /** + * Tools used to manage this environment. e.g., ['conda']. In the most to least priority order + * for resolving and working with the environment. + */ + tools: EnvironmentTools[]; +} + +export interface ProposedCreateEnvironmentAPI { + /** + * This API can be used to detect when the environment creation starts for any registered + * provider (including internal providers). This will also receive any options passed in + * or defaults used to create environment. + */ + readonly onWillCreateEnvironment: Event; + + /** + * This API can be used to detect when the environment provider exits for any registered + * provider (including internal providers). This will also receive created environment path, + * any errors, or user actions taken from the provider. + */ + readonly onDidCreateEnvironment: Event; + + /** + * This API will show a QuickPick to select an environment provider from available list of + * providers. Based on the selection the `createEnvironment` will be called on the provider. + */ + createEnvironment(options?: CreateEnvironmentOptions): Promise; + + /** + * This API should be called to register an environment creation provider. It returns + * a (@link Disposable} which can be used to remove the registration. + */ + registerCreateEnvironmentProvider(provider: CreateEnvironmentProvider): Disposable; +} diff --git a/src/client/pythonEnvironments/creation/provider/condaCreationProvider.ts b/src/client/pythonEnvironments/creation/provider/condaCreationProvider.ts new file mode 100644 index 000000000000..a7e4e9a21cd1 --- /dev/null +++ b/src/client/pythonEnvironments/creation/provider/condaCreationProvider.ts @@ -0,0 +1,334 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { CancellationToken, CancellationTokenSource, ProgressLocation, WorkspaceFolder } from 'vscode'; +import * as path from 'path'; +import { Commands, PVSC_EXTENSION_ID } from '../../../common/constants'; +import { traceError, traceInfo, traceLog } from '../../../logging'; +import { CreateEnvironmentProgress } from '../types'; +import { pickWorkspaceFolder } from '../common/workspaceSelection'; +import { execObservable } from '../../../common/process/rawProcessApis'; +import { createDeferred } from '../../../common/utils/async'; +import { getOSType, OSType } from '../../../common/utils/platform'; +import { createCondaScript } from '../../../common/process/internal/scripts'; +import { Common, CreateEnv } from '../../../common/utils/localize'; +import { + ExistingCondaAction, + deleteEnvironment, + getCondaBaseEnv, + getPathEnvVariableForConda, + pickExistingCondaAction, + pickPythonVersion, +} from './condaUtils'; +import { getPrefixCondaEnvPath, showErrorMessageWithLogs } from '../common/commonUtils'; +import { MultiStepAction, MultiStepNode, withProgress } from '../../../common/vscodeApis/windowApis'; +import { EventName } from '../../../telemetry/constants'; +import { sendTelemetryEvent } from '../../../telemetry'; +import { + CondaProgressAndTelemetry, + CONDA_ENV_CREATED_MARKER, + CONDA_ENV_EXISTING_MARKER, +} from './condaProgressAndTelemetry'; +import { splitLines } from '../../../common/stringUtils'; +import { + CreateEnvironmentOptions, + CreateEnvironmentResult, + CreateEnvironmentProvider, +} from '../proposed.createEnvApis'; +import { shouldDisplayEnvCreationProgress } from './hideEnvCreation'; +import { noop } from '../../../common/utils/misc'; + +function generateCommandArgs(version?: string, options?: CreateEnvironmentOptions): string[] { + let addGitIgnore = true; + let installPackages = true; + if (options) { + addGitIgnore = options?.ignoreSourceControl !== undefined ? options.ignoreSourceControl : true; + installPackages = options?.installPackages !== undefined ? options.installPackages : true; + } + + const command: string[] = [createCondaScript()]; + + if (addGitIgnore) { + command.push('--git-ignore'); + } + + if (installPackages) { + command.push('--install'); + } + + if (version) { + command.push('--python'); + command.push(version); + } + + return command; +} + +function getCondaEnvFromOutput(output: string): string | undefined { + try { + const envPath = output + .split(/\r?\n/g) + .map((s) => s.trim()) + .filter((s) => s.startsWith(CONDA_ENV_CREATED_MARKER) || s.startsWith(CONDA_ENV_EXISTING_MARKER))[0]; + if (envPath.includes(CONDA_ENV_CREATED_MARKER)) { + return envPath.substring(CONDA_ENV_CREATED_MARKER.length); + } + return envPath.substring(CONDA_ENV_EXISTING_MARKER.length); + } catch (ex) { + traceError('Parsing out environment path failed.'); + return undefined; + } +} + +async function createCondaEnv( + workspace: WorkspaceFolder, + command: string, + args: string[], + progress: CreateEnvironmentProgress, + token?: CancellationToken, +): Promise { + progress.report({ + message: CreateEnv.Conda.creating, + }); + + const deferred = createDeferred(); + const pathEnv = getPathEnvVariableForConda(command); + traceLog('Running Conda Env creation script: ', [command, ...args]); + const { proc, out, dispose } = execObservable(command, args, { + mergeStdOutErr: true, + token, + cwd: workspace.uri.fsPath, + env: { + PATH: pathEnv, + }, + }); + + const progressAndTelemetry = new CondaProgressAndTelemetry(progress); + let condaEnvPath: string | undefined; + out.subscribe( + (value) => { + const output = splitLines(value.out).join('\r\n'); + traceLog(output.trimEnd()); + if (output.includes(CONDA_ENV_CREATED_MARKER) || output.includes(CONDA_ENV_EXISTING_MARKER)) { + condaEnvPath = getCondaEnvFromOutput(output); + } + progressAndTelemetry.process(output); + }, + async (error) => { + traceError('Error while running conda env creation script: ', error); + deferred.reject(error); + }, + () => { + dispose(); + if (proc?.exitCode !== 0) { + traceError('Error while running venv creation script: ', progressAndTelemetry.getLastError()); + deferred.reject( + progressAndTelemetry.getLastError() || `Conda env creation failed with exitCode: ${proc?.exitCode}`, + ); + } else { + deferred.resolve(condaEnvPath); + } + }, + ); + return deferred.promise; +} + +function getExecutableCommand(condaBaseEnvPath: string): string { + if (getOSType() === OSType.Windows) { + // Both Miniconda3 and Anaconda3 have the following structure: + // Miniconda3 (or Anaconda3) + // |- python.exe <--- this is the python that we want. + return path.join(condaBaseEnvPath, 'python.exe'); + } + // On non-windows machines: + // miniconda (or miniforge or anaconda3) + // |- bin + // |- python <--- this is the python that we want. + return path.join(condaBaseEnvPath, 'bin', 'python'); +} + +async function createEnvironment(options?: CreateEnvironmentOptions): Promise { + const conda = await getCondaBaseEnv(); + if (!conda) { + return undefined; + } + + let workspace: WorkspaceFolder | undefined; + const workspaceStep = new MultiStepNode( + undefined, + async (context?: MultiStepAction) => { + try { + workspace = (await pickWorkspaceFolder(undefined, context)) as WorkspaceFolder | undefined; + } catch (ex) { + if (ex === MultiStepAction.Back || ex === MultiStepAction.Cancel) { + return ex; + } + throw ex; + } + + if (workspace === undefined) { + traceError('Workspace was not selected or found for creating conda environment.'); + return MultiStepAction.Cancel; + } + traceInfo(`Selected workspace ${workspace.uri.fsPath} for creating conda environment.`); + return MultiStepAction.Continue; + }, + undefined, + ); + + let existingCondaAction: ExistingCondaAction | undefined; + const existingEnvStep = new MultiStepNode( + workspaceStep, + async (context?: MultiStepAction) => { + if (workspace && context === MultiStepAction.Continue) { + try { + existingCondaAction = await pickExistingCondaAction(workspace); + return MultiStepAction.Continue; + } catch (ex) { + if (ex === MultiStepAction.Back || ex === MultiStepAction.Cancel) { + return ex; + } + throw ex; + } + } else if (context === MultiStepAction.Back) { + return MultiStepAction.Back; + } + return MultiStepAction.Continue; + }, + undefined, + ); + workspaceStep.next = existingEnvStep; + + let version: string | undefined; + const versionStep = new MultiStepNode( + workspaceStep, + async (context) => { + if ( + existingCondaAction === ExistingCondaAction.Recreate || + existingCondaAction === ExistingCondaAction.Create + ) { + try { + version = await pickPythonVersion(); + } catch (ex) { + if (ex === MultiStepAction.Back || ex === MultiStepAction.Cancel) { + return ex; + } + throw ex; + } + if (version === undefined) { + traceError('Python version was not selected for creating conda environment.'); + return MultiStepAction.Cancel; + } + traceInfo(`Selected Python version ${version} for creating conda environment.`); + } else if (existingCondaAction === ExistingCondaAction.UseExisting) { + if (context === MultiStepAction.Back) { + return MultiStepAction.Back; + } + } + + return MultiStepAction.Continue; + }, + undefined, + ); + existingEnvStep.next = versionStep; + + const action = await MultiStepNode.run(workspaceStep); + if (action === MultiStepAction.Back || action === MultiStepAction.Cancel) { + throw action; + } + + if (workspace) { + if (existingCondaAction === ExistingCondaAction.Recreate) { + sendTelemetryEvent(EventName.ENVIRONMENT_DELETE, undefined, { + environmentType: 'conda', + status: 'triggered', + }); + if (await deleteEnvironment(workspace, getExecutableCommand(conda))) { + sendTelemetryEvent(EventName.ENVIRONMENT_DELETE, undefined, { + environmentType: 'conda', + status: 'deleted', + }); + } else { + sendTelemetryEvent(EventName.ENVIRONMENT_DELETE, undefined, { + environmentType: 'conda', + status: 'failed', + }); + throw MultiStepAction.Cancel; + } + } else if (existingCondaAction === ExistingCondaAction.UseExisting) { + sendTelemetryEvent(EventName.ENVIRONMENT_REUSE, undefined, { + environmentType: 'conda', + }); + return { path: getPrefixCondaEnvPath(workspace), workspaceFolder: workspace }; + } + } + + const createEnvInternal = async (progress: CreateEnvironmentProgress, token: CancellationToken) => { + progress.report({ + message: CreateEnv.statusStarting, + }); + + let envPath: string | undefined; + try { + sendTelemetryEvent(EventName.ENVIRONMENT_CREATING, undefined, { + environmentType: 'conda', + pythonVersion: version, + }); + if (workspace) { + envPath = await createCondaEnv( + workspace, + getExecutableCommand(conda), + generateCommandArgs(version, options), + progress, + token, + ); + + if (envPath) { + return { path: envPath, workspaceFolder: workspace }; + } + + throw new Error('Failed to create conda environment. See Output > Python for more info.'); + } else { + throw new Error('A workspace is needed to create conda environment'); + } + } catch (ex) { + traceError(ex); + showErrorMessageWithLogs(CreateEnv.Conda.errorCreatingEnvironment); + return { error: ex as Error }; + } + }; + + if (!shouldDisplayEnvCreationProgress()) { + const token = new CancellationTokenSource(); + try { + return await createEnvInternal({ report: noop }, token.token); + } finally { + token.dispose(); + } + } + + return withProgress( + { + location: ProgressLocation.Notification, + title: `${CreateEnv.statusTitle} ([${Common.showLogs}](command:${Commands.ViewOutput}))`, + cancellable: true, + }, + async ( + progress: CreateEnvironmentProgress, + token: CancellationToken, + ): Promise => createEnvInternal(progress, token), + ); +} + +export function condaCreationProvider(): CreateEnvironmentProvider { + return { + createEnvironment, + name: 'Conda', + + description: CreateEnv.Conda.providerDescription, + + id: `${PVSC_EXTENSION_ID}:conda`, + + tools: ['Conda'], + }; +} diff --git a/src/client/pythonEnvironments/creation/provider/condaDeleteUtils.ts b/src/client/pythonEnvironments/creation/provider/condaDeleteUtils.ts new file mode 100644 index 000000000000..e4f4784f15c8 --- /dev/null +++ b/src/client/pythonEnvironments/creation/provider/condaDeleteUtils.ts @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { WorkspaceFolder } from 'vscode'; +import { plainExec } from '../../../common/process/rawProcessApis'; +import { CreateEnv } from '../../../common/utils/localize'; +import { traceError, traceInfo } from '../../../logging'; +import { getPrefixCondaEnvPath, hasPrefixCondaEnv, showErrorMessageWithLogs } from '../common/commonUtils'; + +export async function deleteCondaEnvironment( + workspace: WorkspaceFolder, + interpreter: string, + pathEnvVar: string, +): Promise { + const condaEnvPath = getPrefixCondaEnvPath(workspace); + const command = interpreter; + const args = ['-m', 'conda', 'env', 'remove', '--prefix', condaEnvPath, '--yes']; + try { + traceInfo(`Deleting conda environment: ${condaEnvPath}`); + traceInfo(`Running command: ${command} ${args.join(' ')}`); + const result = await plainExec(command, args, { mergeStdOutErr: true }, { ...process.env, PATH: pathEnvVar }); + traceInfo(result.stdout); + if (await hasPrefixCondaEnv(workspace)) { + // If conda cannot delete files it will name the files as .conda_trash. + // These need to be deleted manually. + traceError(`Conda environment ${condaEnvPath} could not be deleted.`); + traceError(`Please delete the environment manually: ${condaEnvPath}`); + showErrorMessageWithLogs(CreateEnv.Conda.errorDeletingEnvironment); + return false; + } + } catch (err) { + showErrorMessageWithLogs(CreateEnv.Conda.errorDeletingEnvironment); + traceError(`Deleting conda environment ${condaEnvPath} Failed with error: `, err); + return false; + } + return true; +} diff --git a/src/client/pythonEnvironments/creation/provider/condaProgressAndTelemetry.ts b/src/client/pythonEnvironments/creation/provider/condaProgressAndTelemetry.ts new file mode 100644 index 000000000000..304e90aec84f --- /dev/null +++ b/src/client/pythonEnvironments/creation/provider/condaProgressAndTelemetry.ts @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { CreateEnv } from '../../../common/utils/localize'; +import { sendTelemetryEvent } from '../../../telemetry'; +import { EventName } from '../../../telemetry/constants'; +import { CreateEnvironmentProgress } from '../types'; + +export const CONDA_ENV_CREATED_MARKER = 'CREATED_CONDA_ENV:'; +export const CONDA_ENV_EXISTING_MARKER = 'EXISTING_CONDA_ENV:'; +export const CONDA_INSTALLING_YML = 'CONDA_INSTALLING_YML:'; +export const CREATE_CONDA_FAILED_MARKER = 'CREATE_CONDA.ENV_FAILED_CREATION'; +export const CREATE_CONDA_INSTALLED_YML = 'CREATE_CONDA.INSTALLED_YML'; +export const CREATE_FAILED_INSTALL_YML = 'CREATE_CONDA.FAILED_INSTALL_YML'; + +export class CondaProgressAndTelemetry { + private condaCreatedReported = false; + + private condaFailedReported = false; + + private condaInstallingPackagesReported = false; + + private condaInstallingPackagesFailedReported = false; + + private condaInstalledPackagesReported = false; + + private lastError: string | undefined = undefined; + + constructor(private readonly progress: CreateEnvironmentProgress) {} + + public process(output: string): void { + if (!this.condaCreatedReported && output.includes(CONDA_ENV_CREATED_MARKER)) { + this.condaCreatedReported = true; + this.progress.report({ + message: CreateEnv.Conda.created, + }); + sendTelemetryEvent(EventName.ENVIRONMENT_CREATED, undefined, { + environmentType: 'conda', + reason: 'created', + }); + } else if (!this.condaCreatedReported && output.includes(CONDA_ENV_EXISTING_MARKER)) { + this.condaCreatedReported = true; + this.progress.report({ + message: CreateEnv.Conda.created, + }); + sendTelemetryEvent(EventName.ENVIRONMENT_CREATED, undefined, { + environmentType: 'conda', + reason: 'existing', + }); + } else if (!this.condaFailedReported && output.includes(CREATE_CONDA_FAILED_MARKER)) { + this.condaFailedReported = true; + sendTelemetryEvent(EventName.ENVIRONMENT_FAILED, undefined, { + environmentType: 'conda', + reason: 'other', + }); + this.lastError = CREATE_CONDA_FAILED_MARKER; + } else if (!this.condaInstallingPackagesReported && output.includes(CONDA_INSTALLING_YML)) { + this.condaInstallingPackagesReported = true; + this.progress.report({ + message: CreateEnv.Conda.installingPackages, + }); + sendTelemetryEvent(EventName.ENVIRONMENT_INSTALLING_PACKAGES, undefined, { + environmentType: 'conda', + using: 'environment.yml', + }); + } else if (!this.condaInstallingPackagesFailedReported && output.includes(CREATE_FAILED_INSTALL_YML)) { + this.condaInstallingPackagesFailedReported = true; + sendTelemetryEvent(EventName.ENVIRONMENT_INSTALLING_PACKAGES_FAILED, undefined, { + environmentType: 'conda', + using: 'environment.yml', + }); + this.lastError = CREATE_FAILED_INSTALL_YML; + } else if (!this.condaInstalledPackagesReported && output.includes(CREATE_CONDA_INSTALLED_YML)) { + this.condaInstalledPackagesReported = true; + sendTelemetryEvent(EventName.ENVIRONMENT_INSTALLED_PACKAGES, undefined, { + environmentType: 'conda', + using: 'environment.yml', + }); + } + } + + public getLastError(): string | undefined { + return this.lastError; + } +} diff --git a/src/client/pythonEnvironments/creation/provider/condaUtils.ts b/src/client/pythonEnvironments/creation/provider/condaUtils.ts new file mode 100644 index 000000000000..617a2996801e --- /dev/null +++ b/src/client/pythonEnvironments/creation/provider/condaUtils.ts @@ -0,0 +1,144 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { CancellationToken, ProgressLocation, QuickPickItem, Uri, WorkspaceFolder } from 'vscode'; +import { Commands, Octicons } from '../../../common/constants'; +import { Common, CreateEnv } from '../../../common/utils/localize'; +import { executeCommand } from '../../../common/vscodeApis/commandApis'; +import { + MultiStepAction, + showErrorMessage, + showQuickPickWithBack, + withProgress, +} from '../../../common/vscodeApis/windowApis'; +import { traceLog } from '../../../logging'; +import { Conda } from '../../common/environmentManagers/conda'; +import { getPrefixCondaEnvPath, hasPrefixCondaEnv } from '../common/commonUtils'; +import { OSType, getEnvironmentVariable, getOSType } from '../../../common/utils/platform'; +import { deleteCondaEnvironment } from './condaDeleteUtils'; + +const RECOMMENDED_CONDA_PYTHON = '3.11'; + +export async function getCondaBaseEnv(): Promise { + const conda = await Conda.getConda(); + + if (!conda) { + const response = await showErrorMessage(CreateEnv.Conda.condaMissing, Common.learnMore); + if (response === Common.learnMore) { + await executeCommand('vscode.open', Uri.parse('https://docs.anaconda.com/anaconda/install/')); + } + return undefined; + } + + const envs = (await conda.getEnvList()).filter((e) => e.name === 'base'); + if (envs.length === 1) { + return envs[0].prefix; + } + if (envs.length > 1) { + traceLog( + 'Multiple conda base envs detected: ', + envs.map((e) => e.prefix), + ); + return undefined; + } + + return undefined; +} + +export async function pickPythonVersion(token?: CancellationToken): Promise { + const items: QuickPickItem[] = ['3.11', '3.12', '3.10', '3.9', '3.8'].map((v) => ({ + label: v === RECOMMENDED_CONDA_PYTHON ? `${Octicons.Star} Python` : 'Python', + description: v, + })); + const selection = await showQuickPickWithBack( + items, + { + placeHolder: CreateEnv.Conda.selectPythonQuickPickPlaceholder, + matchOnDescription: true, + ignoreFocusOut: true, + }, + token, + ); + + if (selection) { + return (selection as QuickPickItem).description; + } + + return undefined; +} + +export function getPathEnvVariableForConda(condaBasePythonPath: string): string { + const pathEnv = getEnvironmentVariable('PATH') || getEnvironmentVariable('Path') || ''; + if (getOSType() === OSType.Windows) { + // On windows `conda.bat` is used, which adds the following bin directories to PATH + // then launches `conda.exe` which is a stub to `python.exe -m conda`. Here, we are + // instead using the `python.exe` that ships with conda to run a python script that + // handles conda env creation and package installation. + // See conda issue: https://github.com/conda/conda/issues/11399 + const root = path.dirname(condaBasePythonPath); + const libPath1 = path.join(root, 'Library', 'bin'); + const libPath2 = path.join(root, 'Library', 'mingw-w64', 'bin'); + const libPath3 = path.join(root, 'Library', 'usr', 'bin'); + const libPath4 = path.join(root, 'bin'); + const libPath5 = path.join(root, 'Scripts'); + const libPath = [libPath1, libPath2, libPath3, libPath4, libPath5].join(path.delimiter); + return `${libPath}${path.delimiter}${pathEnv}`; + } + return pathEnv; +} + +export async function deleteEnvironment(workspaceFolder: WorkspaceFolder, interpreter: string): Promise { + const condaEnvPath = getPrefixCondaEnvPath(workspaceFolder); + return withProgress( + { + location: ProgressLocation.Notification, + title: `${CreateEnv.Conda.deletingEnvironmentProgress} ([${Common.showLogs}](command:${Commands.ViewOutput})): ${condaEnvPath}`, + cancellable: false, + }, + async () => deleteCondaEnvironment(workspaceFolder, interpreter, getPathEnvVariableForConda(interpreter)), + ); +} + +export enum ExistingCondaAction { + Recreate, + UseExisting, + Create, +} + +export async function pickExistingCondaAction( + workspaceFolder: WorkspaceFolder | undefined, +): Promise { + if (workspaceFolder) { + if (await hasPrefixCondaEnv(workspaceFolder)) { + const items: QuickPickItem[] = [ + { label: CreateEnv.Conda.recreate, description: CreateEnv.Conda.recreateDescription }, + { + label: CreateEnv.Conda.useExisting, + description: CreateEnv.Conda.useExistingDescription, + }, + ]; + + const selection = (await showQuickPickWithBack( + items, + { + placeHolder: CreateEnv.Conda.existingCondaQuickPickPlaceholder, + ignoreFocusOut: true, + }, + undefined, + )) as QuickPickItem | undefined; + + if (selection?.label === CreateEnv.Conda.recreate) { + return ExistingCondaAction.Recreate; + } + + if (selection?.label === CreateEnv.Conda.useExisting) { + return ExistingCondaAction.UseExisting; + } + } else { + return ExistingCondaAction.Create; + } + } + + throw MultiStepAction.Cancel; +} diff --git a/src/client/pythonEnvironments/creation/provider/hideEnvCreation.ts b/src/client/pythonEnvironments/creation/provider/hideEnvCreation.ts new file mode 100644 index 000000000000..5c29a8d7128d --- /dev/null +++ b/src/client/pythonEnvironments/creation/provider/hideEnvCreation.ts @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { Disposable } from 'vscode'; + +const envCreationTracker: Disposable[] = []; + +export function hideEnvCreation(): Disposable { + const disposable = new Disposable(() => { + const index = envCreationTracker.indexOf(disposable); + if (index > -1) { + envCreationTracker.splice(index, 1); + } + }); + envCreationTracker.push(disposable); + return disposable; +} + +export function shouldDisplayEnvCreationProgress(): boolean { + return envCreationTracker.length === 0; +} diff --git a/src/client/pythonEnvironments/creation/provider/venvCreationProvider.ts b/src/client/pythonEnvironments/creation/provider/venvCreationProvider.ts new file mode 100644 index 000000000000..c5c82b85357f --- /dev/null +++ b/src/client/pythonEnvironments/creation/provider/venvCreationProvider.ts @@ -0,0 +1,389 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as os from 'os'; +import { CancellationToken, CancellationTokenSource, ProgressLocation, WorkspaceFolder } from 'vscode'; +import { Commands, PVSC_EXTENSION_ID } from '../../../common/constants'; +import { createVenvScript } from '../../../common/process/internal/scripts'; +import { execObservable } from '../../../common/process/rawProcessApis'; +import { createDeferred } from '../../../common/utils/async'; +import { Common, CreateEnv } from '../../../common/utils/localize'; +import { traceError, traceInfo, traceLog, traceVerbose } from '../../../logging'; +import { CreateEnvironmentOptionsInternal, CreateEnvironmentProgress } from '../types'; +import { pickWorkspaceFolder } from '../common/workspaceSelection'; +import { IInterpreterQuickPick } from '../../../interpreter/configuration/types'; +import { EnvironmentType, PythonEnvironment } from '../../info'; +import { MultiStepAction, MultiStepNode, withProgress } from '../../../common/vscodeApis/windowApis'; +import { sendTelemetryEvent } from '../../../telemetry'; +import { EventName } from '../../../telemetry/constants'; +import { VenvProgressAndTelemetry, VENV_CREATED_MARKER, VENV_EXISTING_MARKER } from './venvProgressAndTelemetry'; +import { getVenvExecutable, showErrorMessageWithLogs } from '../common/commonUtils'; +import { + ExistingVenvAction, + IPackageInstallSelection, + deleteEnvironment, + pickExistingVenvAction, + pickPackagesToInstall, +} from './venvUtils'; +import { InputFlowAction } from '../../../common/utils/multiStepInput'; +import { + CreateEnvironmentProvider, + CreateEnvironmentOptions, + CreateEnvironmentResult, +} from '../proposed.createEnvApis'; +import { shouldDisplayEnvCreationProgress } from './hideEnvCreation'; +import { noop } from '../../../common/utils/misc'; + +interface IVenvCommandArgs { + argv: string[]; + stdin: string | undefined; +} + +function generateCommandArgs(installInfo?: IPackageInstallSelection[], addGitIgnore?: boolean): IVenvCommandArgs { + const command: string[] = [createVenvScript()]; + let stdin: string | undefined; + + if (addGitIgnore) { + command.push('--git-ignore'); + } + + if (installInfo) { + if (installInfo.some((i) => i.installType === 'toml')) { + const source = installInfo.find((i) => i.installType === 'toml')?.source; + command.push('--toml', source?.fileToCommandArgumentForPythonExt() || 'pyproject.toml'); + } + const extras = installInfo.filter((i) => i.installType === 'toml').map((i) => i.installItem); + extras.forEach((r) => { + if (r) { + command.push('--extras', r); + } + }); + + const requirements = installInfo.filter((i) => i.installType === 'requirements').map((i) => i.installItem); + + if (requirements.length < 10) { + requirements.forEach((r) => { + if (r) { + command.push('--requirements', r); + } + }); + } else { + command.push('--stdin'); + // Too many requirements can cause the command line to be too long error. + stdin = JSON.stringify({ requirements }); + } + } + + return { argv: command, stdin }; +} + +function getVenvFromOutput(output: string): string | undefined { + try { + const envPath = output + .split(/\r?\n/g) + .map((s) => s.trim()) + .filter((s) => s.startsWith(VENV_CREATED_MARKER) || s.startsWith(VENV_EXISTING_MARKER))[0]; + if (envPath.includes(VENV_CREATED_MARKER)) { + return envPath.substring(VENV_CREATED_MARKER.length); + } + return envPath.substring(VENV_EXISTING_MARKER.length); + } catch (ex) { + traceError('Parsing out environment path failed.'); + return undefined; + } +} + +async function createVenv( + workspace: WorkspaceFolder, + command: string, + args: IVenvCommandArgs, + progress: CreateEnvironmentProgress, + token?: CancellationToken, +): Promise { + progress.report({ + message: CreateEnv.Venv.creating, + }); + sendTelemetryEvent(EventName.ENVIRONMENT_CREATING, undefined, { + environmentType: 'venv', + pythonVersion: undefined, + }); + + const deferred = createDeferred(); + traceLog('Running Env creation script: ', [command, ...args.argv]); + if (args.stdin) { + traceLog('Requirements passed in via stdin: ', args.stdin); + } + const { proc, out, dispose } = execObservable(command, args.argv, { + mergeStdOutErr: true, + token, + cwd: workspace.uri.fsPath, + stdinStr: args.stdin, + }); + + const progressAndTelemetry = new VenvProgressAndTelemetry(progress); + let venvPath: string | undefined; + out.subscribe( + (value) => { + const output = value.out.split(/\r?\n/g).join(os.EOL); + traceLog(output.trimEnd()); + if (output.includes(VENV_CREATED_MARKER) || output.includes(VENV_EXISTING_MARKER)) { + venvPath = getVenvFromOutput(output); + } + progressAndTelemetry.process(output); + }, + (error) => { + traceError('Error while running venv creation script: ', error); + deferred.reject(error); + }, + () => { + dispose(); + if (proc?.exitCode !== 0) { + traceError('Error while running venv creation script: ', progressAndTelemetry.getLastError()); + deferred.reject( + progressAndTelemetry.getLastError() || + `Failed to create virtual environment with exitCode: ${proc?.exitCode}`, + ); + } else { + deferred.resolve(venvPath); + } + }, + ); + return deferred.promise; +} + +export const VenvCreationProviderId = `${PVSC_EXTENSION_ID}:venv`; +export class VenvCreationProvider implements CreateEnvironmentProvider { + constructor(private readonly interpreterQuickPick: IInterpreterQuickPick) {} + + public async createEnvironment( + options?: CreateEnvironmentOptions & CreateEnvironmentOptionsInternal, + ): Promise { + let workspace = options?.workspaceFolder; + const bypassQuickPicks = options?.workspaceFolder && options.interpreter && options.providerId ? true : false; + const workspaceStep = new MultiStepNode( + undefined, + async (context?: MultiStepAction) => { + try { + workspace = + workspace && bypassQuickPicks + ? workspace + : ((await pickWorkspaceFolder( + { preSelectedWorkspace: options?.workspaceFolder }, + context, + )) as WorkspaceFolder | undefined); + } catch (ex) { + if (ex === MultiStepAction.Back || ex === MultiStepAction.Cancel) { + return ex; + } + throw ex; + } + + if (workspace === undefined) { + traceError('Workspace was not selected or found for creating virtual environment.'); + return MultiStepAction.Cancel; + } + traceInfo(`Selected workspace ${workspace.uri.fsPath} for creating virtual environment.`); + return MultiStepAction.Continue; + }, + undefined, + ); + + let existingVenvAction: ExistingVenvAction | undefined; + if (bypassQuickPicks) { + existingVenvAction = ExistingVenvAction.Create; + } + const existingEnvStep = new MultiStepNode( + workspaceStep, + async (context?: MultiStepAction) => { + if (workspace && context === MultiStepAction.Continue) { + try { + existingVenvAction = await pickExistingVenvAction(workspace); + return MultiStepAction.Continue; + } catch (ex) { + if (ex === MultiStepAction.Back || ex === MultiStepAction.Cancel) { + return ex; + } + throw ex; + } + } else if (context === MultiStepAction.Back) { + return MultiStepAction.Back; + } + return MultiStepAction.Continue; + }, + undefined, + ); + workspaceStep.next = existingEnvStep; + + let interpreter = options?.interpreter; + const interpreterStep = new MultiStepNode( + existingEnvStep, + async (context?: MultiStepAction) => { + if (workspace) { + if ( + existingVenvAction === ExistingVenvAction.Recreate || + existingVenvAction === ExistingVenvAction.Create + ) { + try { + interpreter = + interpreter && bypassQuickPicks + ? interpreter + : await this.interpreterQuickPick.getInterpreterViaQuickPick( + workspace.uri, + (i: PythonEnvironment) => + [ + EnvironmentType.System, + EnvironmentType.MicrosoftStore, + EnvironmentType.Global, + EnvironmentType.Pyenv, + ].includes(i.envType) && i.type === undefined, // only global intepreters + { + skipRecommended: true, + showBackButton: true, + placeholder: CreateEnv.Venv.selectPythonPlaceHolder, + title: null, + }, + ); + } catch (ex) { + if (ex === InputFlowAction.back) { + return MultiStepAction.Back; + } + interpreter = undefined; + } + } else if (existingVenvAction === ExistingVenvAction.UseExisting) { + if (context === MultiStepAction.Back) { + return MultiStepAction.Back; + } + interpreter = getVenvExecutable(workspace); + } + } + + if (!interpreter) { + traceError('Virtual env creation requires an interpreter.'); + return MultiStepAction.Cancel; + } + traceInfo(`Selected interpreter ${interpreter} for creating virtual environment.`); + return MultiStepAction.Continue; + }, + undefined, + ); + existingEnvStep.next = interpreterStep; + + let addGitIgnore = true; + let installPackages = true; + if (options) { + addGitIgnore = options?.ignoreSourceControl !== undefined ? options.ignoreSourceControl : true; + installPackages = options?.installPackages !== undefined ? options.installPackages : true; + } + let installInfo: IPackageInstallSelection[] | undefined; + const packagesStep = new MultiStepNode( + interpreterStep, + async (context?: MultiStepAction) => { + if (workspace && installPackages) { + if (existingVenvAction !== ExistingVenvAction.UseExisting) { + try { + installInfo = await pickPackagesToInstall(workspace); + } catch (ex) { + if (ex === MultiStepAction.Back || ex === MultiStepAction.Cancel) { + return ex; + } + throw ex; + } + if (!installInfo) { + traceVerbose('Virtual env creation exited during dependencies selection.'); + return MultiStepAction.Cancel; + } + } else if (context === MultiStepAction.Back) { + return MultiStepAction.Back; + } + } + + return MultiStepAction.Continue; + }, + undefined, + ); + interpreterStep.next = packagesStep; + + const action = await MultiStepNode.run(workspaceStep); + if (action === MultiStepAction.Back || action === MultiStepAction.Cancel) { + throw action; + } + + if (workspace) { + if (existingVenvAction === ExistingVenvAction.Recreate) { + sendTelemetryEvent(EventName.ENVIRONMENT_DELETE, undefined, { + environmentType: 'venv', + status: 'triggered', + }); + if (await deleteEnvironment(workspace, interpreter)) { + sendTelemetryEvent(EventName.ENVIRONMENT_DELETE, undefined, { + environmentType: 'venv', + status: 'deleted', + }); + } else { + sendTelemetryEvent(EventName.ENVIRONMENT_DELETE, undefined, { + environmentType: 'venv', + status: 'failed', + }); + throw MultiStepAction.Cancel; + } + } else if (existingVenvAction === ExistingVenvAction.UseExisting) { + sendTelemetryEvent(EventName.ENVIRONMENT_REUSE, undefined, { + environmentType: 'venv', + }); + return { path: getVenvExecutable(workspace), workspaceFolder: workspace }; + } + } + + const args = generateCommandArgs(installInfo, addGitIgnore); + const createEnvInternal = async (progress: CreateEnvironmentProgress, token: CancellationToken) => { + progress.report({ + message: CreateEnv.statusStarting, + }); + + let envPath: string | undefined; + try { + if (interpreter && workspace) { + envPath = await createVenv(workspace, interpreter, args, progress, token); + if (envPath) { + return { path: envPath, workspaceFolder: workspace }; + } + throw new Error('Failed to create virtual environment. See Output > Python for more info.'); + } + throw new Error('Failed to create virtual environment. Either interpreter or workspace is undefined.'); + } catch (ex) { + traceError(ex); + showErrorMessageWithLogs(CreateEnv.Venv.errorCreatingEnvironment); + return { error: ex as Error }; + } + }; + + if (!shouldDisplayEnvCreationProgress()) { + const token = new CancellationTokenSource(); + try { + return await createEnvInternal({ report: noop }, token.token); + } finally { + token.dispose(); + } + } + + return withProgress( + { + location: ProgressLocation.Notification, + title: `${CreateEnv.statusTitle} ([${Common.showLogs}](command:${Commands.ViewOutput}))`, + cancellable: true, + }, + async ( + progress: CreateEnvironmentProgress, + token: CancellationToken, + ): Promise => createEnvInternal(progress, token), + ); + } + + name = 'Venv'; + + description: string = CreateEnv.Venv.providerDescription; + + id = VenvCreationProviderId; + + tools = ['Venv']; +} diff --git a/src/client/pythonEnvironments/creation/provider/venvDeleteUtils.ts b/src/client/pythonEnvironments/creation/provider/venvDeleteUtils.ts new file mode 100644 index 000000000000..9bd410c09f51 --- /dev/null +++ b/src/client/pythonEnvironments/creation/provider/venvDeleteUtils.ts @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { WorkspaceFolder } from 'vscode'; +import * as fs from '../../../common/platform/fs-paths'; +import { traceError, traceInfo } from '../../../logging'; +import { getVenvPath, showErrorMessageWithLogs } from '../common/commonUtils'; +import { CreateEnv } from '../../../common/utils/localize'; +import { sleep } from '../../../common/utils/async'; +import { switchSelectedPython } from './venvSwitchPython'; + +async function tryDeleteFile(file: string): Promise { + try { + if (!(await fs.pathExists(file))) { + return true; + } + await fs.unlink(file); + return true; + } catch (err) { + traceError(`Failed to delete file [${file}]:`, err); + return false; + } +} + +async function tryDeleteDir(dir: string): Promise { + try { + if (!(await fs.pathExists(dir))) { + return true; + } + await fs.rmdir(dir, { + recursive: true, + maxRetries: 10, + retryDelay: 200, + }); + return true; + } catch (err) { + traceError(`Failed to delete directory [${dir}]:`, err); + return false; + } +} + +export async function deleteEnvironmentNonWindows(workspaceFolder: WorkspaceFolder): Promise { + const venvPath = getVenvPath(workspaceFolder); + if (await tryDeleteDir(venvPath)) { + traceInfo(`Deleted venv dir: ${venvPath}`); + return true; + } + showErrorMessageWithLogs(CreateEnv.Venv.errorDeletingEnvironment); + return false; +} + +export async function deleteEnvironmentWindows( + workspaceFolder: WorkspaceFolder, + interpreter: string | undefined, +): Promise { + const venvPath = getVenvPath(workspaceFolder); + const venvPythonPath = path.join(venvPath, 'Scripts', 'python.exe'); + + if (await tryDeleteFile(venvPythonPath)) { + traceInfo(`Deleted python executable: ${venvPythonPath}`); + if (await tryDeleteDir(venvPath)) { + traceInfo(`Deleted ".venv" dir: ${venvPath}`); + return true; + } + + traceError(`Failed to delete ".venv" dir: ${venvPath}`); + traceError( + 'This happens if the virtual environment is still in use, or some binary in the venv is still running.', + ); + traceError(`Please delete the ".venv" manually: [${venvPath}]`); + showErrorMessageWithLogs(CreateEnv.Venv.errorDeletingEnvironment); + return false; + } + traceError(`Failed to delete python executable: ${venvPythonPath}`); + traceError('This happens if the virtual environment is still in use.'); + + if (interpreter) { + traceError('We will attempt to switch python temporarily to delete the ".venv"'); + + await switchSelectedPython(interpreter, workspaceFolder.uri, 'temporarily to delete the ".venv"'); + + traceInfo(`Attempting to delete ".venv" again: ${venvPath}`); + const ms = 500; + for (let i = 0; i < 5; i = i + 1) { + traceInfo(`Waiting for ${ms}ms to let processes exit, before a delete attempt.`); + await sleep(ms); + if (await tryDeleteDir(venvPath)) { + traceInfo(`Deleted ".venv" dir: ${venvPath}`); + return true; + } + traceError(`Failed to delete ".venv" dir [${venvPath}] (attempt ${i + 1}/5).`); + } + } else { + traceError(`Please delete the ".venv" dir manually: [${venvPath}]`); + } + showErrorMessageWithLogs(CreateEnv.Venv.errorDeletingEnvironment); + return false; +} diff --git a/src/client/pythonEnvironments/creation/provider/venvProgressAndTelemetry.ts b/src/client/pythonEnvironments/creation/provider/venvProgressAndTelemetry.ts new file mode 100644 index 000000000000..e092c40c3fe0 --- /dev/null +++ b/src/client/pythonEnvironments/creation/provider/venvProgressAndTelemetry.ts @@ -0,0 +1,312 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { CreateEnv } from '../../../common/utils/localize'; +import { sendTelemetryEvent } from '../../../telemetry'; +import { EventName } from '../../../telemetry/constants'; +import { CreateEnvironmentProgress } from '../types'; + +export const VENV_CREATED_MARKER = 'CREATED_VENV:'; +export const VENV_EXISTING_MARKER = 'EXISTING_VENV:'; +const INSTALLING_REQUIREMENTS = 'VENV_INSTALLING_REQUIREMENTS:'; +const INSTALLING_PYPROJECT = 'VENV_INSTALLING_PYPROJECT:'; +const PIP_NOT_INSTALLED_MARKER = 'CREATE_VENV.PIP_NOT_FOUND'; +const VENV_NOT_INSTALLED_MARKER = 'CREATE_VENV.VENV_NOT_FOUND'; +const INSTALL_REQUIREMENTS_FAILED_MARKER = 'CREATE_VENV.PIP_FAILED_INSTALL_REQUIREMENTS'; +const INSTALL_PYPROJECT_FAILED_MARKER = 'CREATE_VENV.PIP_FAILED_INSTALL_PYPROJECT'; +const CREATE_VENV_FAILED_MARKER = 'CREATE_VENV.VENV_FAILED_CREATION'; +const VENV_ALREADY_EXISTS_MARKER = 'CREATE_VENV.VENV_ALREADY_EXISTS'; +const INSTALLED_REQUIREMENTS_MARKER = 'CREATE_VENV.PIP_INSTALLED_REQUIREMENTS'; +const INSTALLED_PYPROJECT_MARKER = 'CREATE_VENV.PIP_INSTALLED_PYPROJECT'; +const UPGRADE_PIP_FAILED_MARKER = 'CREATE_VENV.UPGRADE_PIP_FAILED'; +const UPGRADING_PIP_MARKER = 'CREATE_VENV.UPGRADING_PIP'; +const UPGRADED_PIP_MARKER = 'CREATE_VENV.UPGRADED_PIP'; +const CREATING_MICROVENV_MARKER = 'CREATE_MICROVENV.CREATING_MICROVENV'; +const CREATE_MICROVENV_FAILED_MARKER = 'CREATE_VENV.MICROVENV_FAILED_CREATION'; +const CREATE_MICROVENV_FAILED_MARKER2 = 'CREATE_MICROVENV.MICROVENV_FAILED_CREATION'; +const MICROVENV_CREATED_MARKER = 'CREATE_MICROVENV.CREATED_MICROVENV'; +const INSTALLING_PIP_MARKER = 'CREATE_VENV.INSTALLING_PIP'; +const INSTALL_PIP_FAILED_MARKER = 'CREATE_VENV.INSTALL_PIP_FAILED'; +const DOWNLOADING_PIP_MARKER = 'CREATE_VENV.DOWNLOADING_PIP'; +const DOWNLOAD_PIP_FAILED_MARKER = 'CREATE_VENV.DOWNLOAD_PIP_FAILED'; +const DISTUTILS_NOT_INSTALLED_MARKER = 'CREATE_VENV.DISTUTILS_NOT_INSTALLED'; + +export class VenvProgressAndTelemetry { + private readonly processed = new Set(); + + private readonly reportActions = new Map string | undefined>([ + [ + VENV_CREATED_MARKER, + (progress: CreateEnvironmentProgress) => { + progress.report({ message: CreateEnv.Venv.created }); + sendTelemetryEvent(EventName.ENVIRONMENT_CREATED, undefined, { + environmentType: 'venv', + reason: 'created', + }); + return undefined; + }, + ], + [ + VENV_EXISTING_MARKER, + (progress: CreateEnvironmentProgress) => { + progress.report({ message: CreateEnv.Venv.existing }); + sendTelemetryEvent(EventName.ENVIRONMENT_CREATED, undefined, { + environmentType: 'venv', + reason: 'existing', + }); + return undefined; + }, + ], + [ + INSTALLING_REQUIREMENTS, + (progress: CreateEnvironmentProgress) => { + progress.report({ message: CreateEnv.Venv.installingPackages }); + sendTelemetryEvent(EventName.ENVIRONMENT_INSTALLING_PACKAGES, undefined, { + environmentType: 'venv', + using: 'requirements.txt', + }); + return undefined; + }, + ], + [ + INSTALLING_PYPROJECT, + (progress: CreateEnvironmentProgress) => { + progress.report({ message: CreateEnv.Venv.installingPackages }); + sendTelemetryEvent(EventName.ENVIRONMENT_INSTALLING_PACKAGES, undefined, { + environmentType: 'venv', + using: 'pyproject.toml', + }); + return undefined; + }, + ], + [ + PIP_NOT_INSTALLED_MARKER, + (_progress: CreateEnvironmentProgress) => { + sendTelemetryEvent(EventName.ENVIRONMENT_FAILED, undefined, { + environmentType: 'venv', + reason: 'noPip', + }); + return PIP_NOT_INSTALLED_MARKER; + }, + ], + [ + DISTUTILS_NOT_INSTALLED_MARKER, + (_progress: CreateEnvironmentProgress) => { + sendTelemetryEvent(EventName.ENVIRONMENT_FAILED, undefined, { + environmentType: 'venv', + reason: 'noDistUtils', + }); + return VENV_NOT_INSTALLED_MARKER; + }, + ], + [ + VENV_NOT_INSTALLED_MARKER, + (_progress: CreateEnvironmentProgress) => { + sendTelemetryEvent(EventName.ENVIRONMENT_FAILED, undefined, { + environmentType: 'venv', + reason: 'noVenv', + }); + return VENV_NOT_INSTALLED_MARKER; + }, + ], + [ + INSTALL_REQUIREMENTS_FAILED_MARKER, + (_progress: CreateEnvironmentProgress) => { + sendTelemetryEvent(EventName.ENVIRONMENT_INSTALLING_PACKAGES_FAILED, undefined, { + environmentType: 'venv', + using: 'requirements.txt', + }); + return INSTALL_REQUIREMENTS_FAILED_MARKER; + }, + ], + [ + INSTALL_PYPROJECT_FAILED_MARKER, + (_progress: CreateEnvironmentProgress) => { + sendTelemetryEvent(EventName.ENVIRONMENT_INSTALLING_PACKAGES_FAILED, undefined, { + environmentType: 'venv', + using: 'pyproject.toml', + }); + return INSTALL_PYPROJECT_FAILED_MARKER; + }, + ], + [ + CREATE_VENV_FAILED_MARKER, + (_progress: CreateEnvironmentProgress) => { + sendTelemetryEvent(EventName.ENVIRONMENT_FAILED, undefined, { + environmentType: 'venv', + reason: 'other', + }); + return CREATE_VENV_FAILED_MARKER; + }, + ], + [ + VENV_ALREADY_EXISTS_MARKER, + (_progress: CreateEnvironmentProgress) => { + sendTelemetryEvent(EventName.ENVIRONMENT_CREATED, undefined, { + environmentType: 'venv', + reason: 'existing', + }); + return undefined; + }, + ], + [ + INSTALLED_REQUIREMENTS_MARKER, + (_progress: CreateEnvironmentProgress) => { + sendTelemetryEvent(EventName.ENVIRONMENT_INSTALLED_PACKAGES, undefined, { + environmentType: 'venv', + using: 'requirements.txt', + }); + return undefined; + }, + ], + [ + INSTALLED_PYPROJECT_MARKER, + (_progress: CreateEnvironmentProgress) => { + sendTelemetryEvent(EventName.ENVIRONMENT_INSTALLED_PACKAGES, undefined, { + environmentType: 'venv', + using: 'pyproject.toml', + }); + return undefined; + }, + ], + [ + UPGRADED_PIP_MARKER, + (_progress: CreateEnvironmentProgress) => { + sendTelemetryEvent(EventName.ENVIRONMENT_INSTALLED_PACKAGES, undefined, { + environmentType: 'venv', + using: 'pipUpgrade', + }); + return undefined; + }, + ], + [ + UPGRADE_PIP_FAILED_MARKER, + (_progress: CreateEnvironmentProgress) => { + sendTelemetryEvent(EventName.ENVIRONMENT_INSTALLING_PACKAGES_FAILED, undefined, { + environmentType: 'venv', + using: 'pipUpgrade', + }); + return UPGRADE_PIP_FAILED_MARKER; + }, + ], + [ + DOWNLOADING_PIP_MARKER, + (progress: CreateEnvironmentProgress) => { + progress.report({ message: CreateEnv.Venv.downloadingPip }); + sendTelemetryEvent(EventName.ENVIRONMENT_INSTALLING_PACKAGES, undefined, { + environmentType: 'venv', + using: 'pipDownload', + }); + return undefined; + }, + ], + [ + DOWNLOAD_PIP_FAILED_MARKER, + (_progress: CreateEnvironmentProgress) => { + sendTelemetryEvent(EventName.ENVIRONMENT_INSTALLING_PACKAGES_FAILED, undefined, { + environmentType: 'venv', + using: 'pipDownload', + }); + return DOWNLOAD_PIP_FAILED_MARKER; + }, + ], + [ + INSTALLING_PIP_MARKER, + (progress: CreateEnvironmentProgress) => { + progress.report({ message: CreateEnv.Venv.installingPip }); + sendTelemetryEvent(EventName.ENVIRONMENT_INSTALLING_PACKAGES, undefined, { + environmentType: 'venv', + using: 'pipInstall', + }); + return undefined; + }, + ], + [ + INSTALL_PIP_FAILED_MARKER, + (_progress: CreateEnvironmentProgress) => { + sendTelemetryEvent(EventName.ENVIRONMENT_INSTALLING_PACKAGES_FAILED, undefined, { + environmentType: 'venv', + using: 'pipInstall', + }); + return INSTALL_PIP_FAILED_MARKER; + }, + ], + [ + CREATING_MICROVENV_MARKER, + (progress: CreateEnvironmentProgress) => { + progress.report({ message: CreateEnv.Venv.creatingMicrovenv }); + sendTelemetryEvent(EventName.ENVIRONMENT_CREATING, undefined, { + environmentType: 'microvenv', + pythonVersion: undefined, + }); + return undefined; + }, + ], + [ + CREATE_MICROVENV_FAILED_MARKER, + (_progress: CreateEnvironmentProgress) => { + sendTelemetryEvent(EventName.ENVIRONMENT_FAILED, undefined, { + environmentType: 'microvenv', + reason: 'other', + }); + return CREATE_MICROVENV_FAILED_MARKER; + }, + ], + [ + CREATE_MICROVENV_FAILED_MARKER2, + (_progress: CreateEnvironmentProgress) => { + sendTelemetryEvent(EventName.ENVIRONMENT_FAILED, undefined, { + environmentType: 'microvenv', + reason: 'other', + }); + return CREATE_MICROVENV_FAILED_MARKER2; + }, + ], + [ + MICROVENV_CREATED_MARKER, + (_progress: CreateEnvironmentProgress) => { + sendTelemetryEvent(EventName.ENVIRONMENT_CREATED, undefined, { + environmentType: 'microvenv', + reason: 'created', + }); + return undefined; + }, + ], + [ + UPGRADING_PIP_MARKER, + (progress: CreateEnvironmentProgress) => { + progress.report({ message: CreateEnv.Venv.upgradingPip }); + sendTelemetryEvent(EventName.ENVIRONMENT_INSTALLING_PACKAGES, undefined, { + environmentType: 'venv', + using: 'pipUpgrade', + }); + return undefined; + }, + ], + ]); + + private lastError: string | undefined = undefined; + + constructor(private readonly progress: CreateEnvironmentProgress) {} + + public getLastError(): string | undefined { + return this.lastError; + } + + public process(output: string): void { + const keys: string[] = Array.from(this.reportActions.keys()); + + for (const key of keys) { + if (output.includes(key) && !this.processed.has(key)) { + const action = this.reportActions.get(key); + if (action) { + const err = action(this.progress); + if (err) { + this.lastError = err; + } + } + this.processed.add(key); + } + } + } +} diff --git a/src/client/pythonEnvironments/creation/provider/venvSwitchPython.ts b/src/client/pythonEnvironments/creation/provider/venvSwitchPython.ts new file mode 100644 index 000000000000..e2567dfd114b --- /dev/null +++ b/src/client/pythonEnvironments/creation/provider/venvSwitchPython.ts @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { Disposable, Uri } from 'vscode'; +import { createDeferred } from '../../../common/utils/async'; +import { getExtension } from '../../../common/vscodeApis/extensionsApi'; +import { PVSC_EXTENSION_ID, PythonExtension } from '../../../api/types'; +import { traceInfo } from '../../../logging'; + +export async function switchSelectedPython(interpreter: string, uri: Uri, purpose: string): Promise { + let dispose: Disposable | undefined; + try { + const deferred = createDeferred(); + const api: PythonExtension = getExtension(PVSC_EXTENSION_ID)?.exports as PythonExtension; + dispose = api.environments.onDidChangeActiveEnvironmentPath(async (e) => { + if (path.normalize(e.path) === path.normalize(interpreter)) { + traceInfo(`Switched to interpreter ${purpose}: ${interpreter}`); + deferred.resolve(); + } + }); + api.environments.updateActiveEnvironmentPath(interpreter, uri); + traceInfo(`Switching interpreter ${purpose}: ${interpreter}`); + await deferred.promise; + } finally { + dispose?.dispose(); + } +} diff --git a/src/client/pythonEnvironments/creation/provider/venvUtils.ts b/src/client/pythonEnvironments/creation/provider/venvUtils.ts new file mode 100644 index 000000000000..1bfb2c96f224 --- /dev/null +++ b/src/client/pythonEnvironments/creation/provider/venvUtils.ts @@ -0,0 +1,336 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License + +import * as tomljs from '@iarna/toml'; +import { flatten, isArray } from 'lodash'; +import * as path from 'path'; +import { + CancellationToken, + ProgressLocation, + QuickPickItem, + QuickPickItemButtonEvent, + RelativePattern, + ThemeIcon, + Uri, + WorkspaceFolder, +} from 'vscode'; +import * as fs from '../../../common/platform/fs-paths'; +import { Common, CreateEnv } from '../../../common/utils/localize'; +import { + MultiStepAction, + MultiStepNode, + showQuickPickWithBack, + showTextDocument, + withProgress, +} from '../../../common/vscodeApis/windowApis'; +import { findFiles } from '../../../common/vscodeApis/workspaceApis'; +import { traceError, traceVerbose } from '../../../logging'; +import { Commands } from '../../../common/constants'; +import { isWindows } from '../../../common/utils/platform'; +import { getVenvPath, hasVenv } from '../common/commonUtils'; +import { deleteEnvironmentNonWindows, deleteEnvironmentWindows } from './venvDeleteUtils'; + +export const OPEN_REQUIREMENTS_BUTTON = { + iconPath: new ThemeIcon('go-to-file'), + tooltip: CreateEnv.Venv.openRequirementsFile, +}; +const exclude = '**/{.venv*,.git,.nox,.tox,.conda,site-packages,__pypackages__}/**'; +export async function getPipRequirementsFiles( + workspaceFolder: WorkspaceFolder, + token?: CancellationToken, +): Promise { + const files = flatten( + await Promise.all([ + findFiles(new RelativePattern(workspaceFolder, '**/*requirement*.txt'), exclude, undefined, token), + findFiles(new RelativePattern(workspaceFolder, '**/requirements/*.txt'), exclude, undefined, token), + ]), + ).map((u) => u.fsPath); + return files; +} + +function tomlParse(content: string): tomljs.JsonMap { + try { + return tomljs.parse(content); + } catch (err) { + traceError('Failed to parse `pyproject.toml`:', err); + } + return {}; +} + +function tomlHasBuildSystem(toml: tomljs.JsonMap): boolean { + return toml['build-system'] !== undefined; +} + +function tomlHasProject(toml: tomljs.JsonMap): boolean { + return toml.project !== undefined; +} + +function getTomlOptionalDeps(toml: tomljs.JsonMap): string[] { + const extras: string[] = []; + if (toml.project && (toml.project as tomljs.JsonMap)['optional-dependencies']) { + const deps = (toml.project as tomljs.JsonMap)['optional-dependencies']; + for (const key of Object.keys(deps)) { + extras.push(key); + } + } + return extras; +} + +async function pickTomlExtras(extras: string[], token?: CancellationToken): Promise { + const items: QuickPickItem[] = extras.map((e) => ({ label: e })); + + const selection = await showQuickPickWithBack( + items, + { + placeHolder: CreateEnv.Venv.tomlExtrasQuickPickTitle, + canPickMany: true, + ignoreFocusOut: true, + }, + token, + ); + + if (selection && isArray(selection)) { + return selection.map((s) => s.label); + } + + return undefined; +} + +async function pickRequirementsFiles( + files: string[], + root: string, + token?: CancellationToken, +): Promise { + const items: QuickPickItem[] = files + .map((p) => path.relative(root, p)) + .sort((a, b) => { + const al: number = a.split(/[\\\/]/).length; + const bl: number = b.split(/[\\\/]/).length; + if (al === bl) { + if (a.length === b.length) { + return a.localeCompare(b); + } + return a.length - b.length; + } + return al - bl; + }) + .map((e) => ({ + label: e, + buttons: [OPEN_REQUIREMENTS_BUTTON], + })); + + const selection = await showQuickPickWithBack( + items, + { + placeHolder: CreateEnv.Venv.requirementsQuickPickTitle, + ignoreFocusOut: true, + canPickMany: true, + }, + token, + async (e: QuickPickItemButtonEvent) => { + if (e.item.label) { + await showTextDocument(Uri.file(path.join(root, e.item.label))); + } + }, + ); + + if (selection && isArray(selection)) { + return selection.map((s) => s.label); + } + + return undefined; +} + +export function isPipInstallableToml(tomlContent: string): boolean { + const toml = tomlParse(tomlContent); + return tomlHasBuildSystem(toml) && tomlHasProject(toml); +} + +export interface IPackageInstallSelection { + installType: 'toml' | 'requirements' | 'none'; + installItem?: string; + source?: string; +} + +export async function pickPackagesToInstall( + workspaceFolder: WorkspaceFolder, + token?: CancellationToken, +): Promise { + const tomlPath = path.join(workspaceFolder.uri.fsPath, 'pyproject.toml'); + const packages: IPackageInstallSelection[] = []; + + const tomlStep = new MultiStepNode( + undefined, + async (context?: MultiStepAction) => { + traceVerbose(`Looking for toml pyproject.toml with optional dependencies at: ${tomlPath}`); + + let extras: string[] = []; + let hasBuildSystem = false; + let hasProject = false; + + if (await fs.pathExists(tomlPath)) { + const toml = tomlParse(await fs.readFile(tomlPath, 'utf-8')); + extras = getTomlOptionalDeps(toml); + hasBuildSystem = tomlHasBuildSystem(toml); + hasProject = tomlHasProject(toml); + + if (!hasProject) { + traceVerbose('Create env: Found toml without project. So we will not use editable install.'); + } + if (!hasBuildSystem) { + traceVerbose('Create env: Found toml without build system. So we will not use editable install.'); + } + if (extras.length === 0) { + traceVerbose('Create env: Found toml without optional dependencies.'); + } + } else if (context === MultiStepAction.Back) { + // This step is not really used so just go back + return MultiStepAction.Back; + } + + if (hasBuildSystem && hasProject) { + if (extras.length > 0) { + traceVerbose('Create Env: Found toml with optional dependencies.'); + + try { + const installList = await pickTomlExtras(extras, token); + if (installList) { + if (installList.length > 0) { + installList.forEach((i) => { + packages.push({ installType: 'toml', installItem: i, source: tomlPath }); + }); + } + packages.push({ installType: 'toml', source: tomlPath }); + } else { + return MultiStepAction.Cancel; + } + } catch (ex) { + if (ex === MultiStepAction.Back || ex === MultiStepAction.Cancel) { + return ex; + } + throw ex; + } + } else if (context === MultiStepAction.Back) { + // This step is not really used so just go back + return MultiStepAction.Back; + } else { + // There are no extras to install and the context is to go to next step + packages.push({ installType: 'toml', source: tomlPath }); + } + } else if (context === MultiStepAction.Back) { + // This step is not really used because there is no build system in toml, so just go back + return MultiStepAction.Back; + } + + return MultiStepAction.Continue; + }, + undefined, + ); + + const requirementsStep = new MultiStepNode( + tomlStep, + async (context?: MultiStepAction) => { + traceVerbose('Looking for pip requirements.'); + const requirementFiles = await getPipRequirementsFiles(workspaceFolder, token); + if (requirementFiles && requirementFiles.length > 0) { + traceVerbose('Found pip requirements.'); + try { + const result = await pickRequirementsFiles(requirementFiles, workspaceFolder.uri.fsPath, token); + const installList = result?.map((p) => path.join(workspaceFolder.uri.fsPath, p)); + if (installList) { + installList.forEach((i) => { + packages.push({ installType: 'requirements', installItem: i }); + }); + } else { + return MultiStepAction.Cancel; + } + } catch (ex) { + if (ex === MultiStepAction.Back || ex === MultiStepAction.Cancel) { + return ex; + } + throw ex; + } + } else if (context === MultiStepAction.Back) { + // This step is not really used, because there were no requirement files, so just go back + return MultiStepAction.Back; + } + + return MultiStepAction.Continue; + }, + undefined, + ); + tomlStep.next = requirementsStep; + + const action = await MultiStepNode.run(tomlStep); + if (action === MultiStepAction.Back || action === MultiStepAction.Cancel) { + throw action; + } + + return packages; +} + +export async function deleteEnvironment( + workspaceFolder: WorkspaceFolder, + interpreter: string | undefined, +): Promise { + const venvPath = getVenvPath(workspaceFolder); + return withProgress( + { + location: ProgressLocation.Notification, + title: `${CreateEnv.Venv.deletingEnvironmentProgress} ([${Common.showLogs}](command:${Commands.ViewOutput})): ${venvPath}`, + cancellable: false, + }, + async () => { + if (isWindows()) { + return deleteEnvironmentWindows(workspaceFolder, interpreter); + } + return deleteEnvironmentNonWindows(workspaceFolder); + }, + ); +} + +export enum ExistingVenvAction { + Recreate, + UseExisting, + Create, +} + +export async function pickExistingVenvAction( + workspaceFolder: WorkspaceFolder | undefined, +): Promise { + if (workspaceFolder) { + if (await hasVenv(workspaceFolder)) { + const items: QuickPickItem[] = [ + { + label: CreateEnv.Venv.useExisting, + description: CreateEnv.Venv.useExistingDescription, + }, + { + label: CreateEnv.Venv.recreate, + description: CreateEnv.Venv.recreateDescription, + }, + ]; + + const selection = (await showQuickPickWithBack( + items, + { + placeHolder: CreateEnv.Venv.existingVenvQuickPickPlaceholder, + ignoreFocusOut: true, + }, + undefined, + )) as QuickPickItem | undefined; + + if (selection?.label === CreateEnv.Venv.recreate) { + return ExistingVenvAction.Recreate; + } + + if (selection?.label === CreateEnv.Venv.useExisting) { + return ExistingVenvAction.UseExisting; + } + } else { + return ExistingVenvAction.Create; + } + } + + throw MultiStepAction.Cancel; +} diff --git a/src/client/pythonEnvironments/creation/pyProjectTomlContext.ts b/src/client/pythonEnvironments/creation/pyProjectTomlContext.ts new file mode 100644 index 000000000000..5925b7641f45 --- /dev/null +++ b/src/client/pythonEnvironments/creation/pyProjectTomlContext.ts @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { TextDocument } from 'vscode'; +import { IDisposableRegistry } from '../../common/types'; +import { executeCommand } from '../../common/vscodeApis/commandApis'; +import { + onDidOpenTextDocument, + onDidSaveTextDocument, + getOpenTextDocuments, +} from '../../common/vscodeApis/workspaceApis'; +import { isPipInstallableToml } from './provider/venvUtils'; + +async function setPyProjectTomlContextKey(doc: TextDocument): Promise { + if (isPipInstallableToml(doc.getText())) { + await executeCommand('setContext', 'pipInstallableToml', true); + } else { + await executeCommand('setContext', 'pipInstallableToml', false); + } +} + +export function registerPyProjectTomlFeatures(disposables: IDisposableRegistry): void { + disposables.push( + onDidOpenTextDocument(async (doc: TextDocument) => { + if (doc.fileName.endsWith('pyproject.toml')) { + await setPyProjectTomlContextKey(doc); + } + }), + onDidSaveTextDocument(async (doc: TextDocument) => { + if (doc.fileName.endsWith('pyproject.toml')) { + await setPyProjectTomlContextKey(doc); + } + }), + ); + + const docs = getOpenTextDocuments().filter( + (doc) => doc.fileName.endsWith('pyproject.toml') && isPipInstallableToml(doc.getText()), + ); + if (docs.length > 0) { + executeCommand('setContext', 'pipInstallableToml', true); + } else { + executeCommand('setContext', 'pipInstallableToml', false); + } +} diff --git a/src/client/pythonEnvironments/creation/registrations.ts b/src/client/pythonEnvironments/creation/registrations.ts new file mode 100644 index 000000000000..25141cbec5ac --- /dev/null +++ b/src/client/pythonEnvironments/creation/registrations.ts @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { IDisposableRegistry, IPathUtils } from '../../common/types'; +import { IInterpreterQuickPick, IPythonPathUpdaterServiceManager } from '../../interpreter/configuration/types'; +import { IInterpreterService } from '../../interpreter/contracts'; +import { registerCreateEnvironmentFeatures } from './createEnvApi'; +import { registerCreateEnvironmentButtonFeatures } from './createEnvButtonContext'; +import { registerTriggerForPipInTerminal } from './globalPipInTerminalTrigger'; +import { registerInstalledPackagesDiagnosticsProvider } from './installedPackagesDiagnostic'; +import { registerPyProjectTomlFeatures } from './pyProjectTomlContext'; + +export function registerAllCreateEnvironmentFeatures( + disposables: IDisposableRegistry, + interpreterQuickPick: IInterpreterQuickPick, + pythonPathUpdater: IPythonPathUpdaterServiceManager, + interpreterService: IInterpreterService, + pathUtils: IPathUtils, +): void { + registerCreateEnvironmentFeatures(disposables, interpreterQuickPick, pythonPathUpdater, pathUtils); + registerCreateEnvironmentButtonFeatures(disposables); + registerPyProjectTomlFeatures(disposables); + registerInstalledPackagesDiagnosticsProvider(disposables, interpreterService); + registerTriggerForPipInTerminal(disposables); +} diff --git a/src/client/pythonEnvironments/creation/types.ts b/src/client/pythonEnvironments/creation/types.ts new file mode 100644 index 000000000000..0e400c2d90f3 --- /dev/null +++ b/src/client/pythonEnvironments/creation/types.ts @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License + +import { Progress, WorkspaceFolder } from 'vscode'; + +export interface CreateEnvironmentProgress extends Progress<{ message?: string; increment?: number }> {} + +/** + * The interpreter path to use for the environment creation. If not provided, will prompt the user to select one. + * If the value of `interpreter` & `workspaceFolder` & `providerId` are provided we will not prompt the user to select a provider, nor folder, nor an interpreter. + */ +export interface CreateEnvironmentOptionsInternal { + workspaceFolder?: WorkspaceFolder; + providerId?: string; + interpreter?: string; +} diff --git a/src/client/pythonEnvironments/discovery/globalenv.ts b/src/client/pythonEnvironments/discovery/globalenv.ts deleted file mode 100644 index 7859b174da38..000000000000 --- a/src/client/pythonEnvironments/discovery/globalenv.ts +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { logVerbose } from '../../logging'; -import { EnvironmentType } from '../info'; - -type ExecFunc = (cmd: string, args: string[]) => Promise<{ stdout: string }>; - -type TypeFinderFunc = (python: string) => Promise; -type RootFinderFunc = () => Promise; - -/** - * Build a "type finder" function that identifies pyenv environments. - * - * @param homedir - the user's home directory (e.g. `$HOME`) - * @param pathSep - the path separator to use (typically `path.sep`) - * @param pathJoin - typically `path.join` - * @param getEnvVar - a function to look up a process environment variable (i,e. `process.env[name]`) - * @param exec - the function to use to run pyenv - */ -export function getPyenvTypeFinder( - homedir: string, - // - pathSep: string, - pathJoin: (...parts: string[]) => string, - // - getEnvVar: (name: string) => string | undefined, - exec: ExecFunc, -): TypeFinderFunc { - const find = getPyenvRootFinder(homedir, pathJoin, getEnvVar, exec); - return async (python) => { - const root = await find(); - if (root && python.startsWith(`${root}${pathSep}`)) { - return EnvironmentType.Pyenv; - } - return undefined; - }; -} - -/** - * Build a "root finder" function that finds pyenv environments. - * - * @param homedir - the user's home directory (e.g. `$HOME`) - * @param pathJoin - typically `path.join` - * @param getEnvVar - a function to look up a process environment variable (i,e. `process.env[name]`) - * @param exec - the function to use to run pyenv - */ -export function getPyenvRootFinder( - homedir: string, - pathJoin: (...parts: string[]) => string, - getEnvVar: (name: string) => string | undefined, - exec: ExecFunc, -): RootFinderFunc { - return async () => { - const root = getEnvVar('PYENV_ROOT'); - if (root /* ...or empty... */) { - return root; - } - - try { - const result = await exec('pyenv', ['root']); - const text = result.stdout.trim(); - if (text.length > 0) { - return text; - } - } catch (err) { - // Ignore the error. - logVerbose(`"pyenv root" failed (${err})`); - } - return pathJoin(homedir, '.pyenv'); - }; -} diff --git a/src/client/pythonEnvironments/discovery/index.ts b/src/client/pythonEnvironments/discovery/index.ts deleted file mode 100644 index 512db04b3627..000000000000 --- a/src/client/pythonEnvironments/discovery/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -/** - * Decide if the given Python executable looks like the MacOS default Python. - */ -export function isMacDefaultPythonPath(pythonPath: string): boolean { - return pythonPath === 'python' || pythonPath === '/usr/bin/python'; -} diff --git a/src/client/pythonEnvironments/discovery/locators/helpers.ts b/src/client/pythonEnvironments/discovery/locators/helpers.ts deleted file mode 100644 index d2b64b20d895..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/helpers.ts +++ /dev/null @@ -1,100 +0,0 @@ -import * as fsapi from 'fs-extra'; -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { traceError } from '../../../common/logger'; -import { IS_WINDOWS } from '../../../common/platform/constants'; -import { IFileSystem } from '../../../common/platform/types'; -import { IInterpreterLocatorHelper } from '../../../interpreter/contracts'; -import { IPipEnvServiceHelper } from '../../../interpreter/locators/types'; -import { EnvironmentType, PythonEnvironment } from '../../info'; - -const CheckPythonInterpreterRegEx = IS_WINDOWS ? /^python(\d+(.\d+)?)?\.exe$/ : /^python(\d+(.\d+)?)?$/; - -export async function lookForInterpretersInDirectory(pathToCheck: string): Promise { - // Technically, we should be able to use fs.getFiles(). However, - // that breaks some tests. So we stick with the broader behavior. - try { - // tslint:disable-next-line: no-suspicious-comment - // TODO https://github.com/microsoft/vscode-python/issues/11338 - const files = await fsapi.readdir(pathToCheck); - return files - .map((filename) => path.join(pathToCheck, filename)) - .filter((fileName) => CheckPythonInterpreterRegEx.test(path.basename(fileName))); - } catch (err) { - traceError('Python Extension (lookForInterpretersInDirectory.fs.readdir):', err); - return [] as string[]; - } -} - -@injectable() -export class InterpreterLocatorHelper implements IInterpreterLocatorHelper { - constructor( - @inject(IFileSystem) private readonly fs: IFileSystem, - @inject(IPipEnvServiceHelper) private readonly pipEnvServiceHelper: IPipEnvServiceHelper, - ) {} - - public async mergeInterpreters(interpreters: PythonEnvironment[]): Promise { - const items = interpreters - .map((item) => ({ ...item })) - .map((item) => { - item.path = path.normalize(item.path); - return item; - }) - .reduce((accumulator, current:PythonEnvironment) => { - const currentVersion = current && current.version ? current.version.raw : undefined; - let existingItem = accumulator.find((item) => { - // If same version and same base path, then ignore. - // Could be Python 3.6 with path = python.exe, and Python 3.6 and path = python3.exe. - if ( - item.version - && item.version.raw === currentVersion - && item.path - && current.path - && this.fs.arePathsSame(path.dirname(item.path), path.dirname(current.path)) - ) { - return true; - } - return false; - }); - if (!existingItem) { - accumulator.push(current); - } else { - // Preserve type information. - // Possible we identified environment as unknown, but a later provider has identified env type. - if ( - existingItem.envType === EnvironmentType.Unknown - && current.envType !== EnvironmentType.Unknown - ) { - existingItem.envType = current.envType; - } - const props: (keyof PythonEnvironment)[] = [ - 'envName', - 'envPath', - 'path', - 'sysPrefix', - 'architecture', - 'sysVersion', - 'version', - ]; - props.forEach((prop) => { - if (existingItem && !existingItem[prop] && current[prop]) { - existingItem = { ...existingItem, [prop]: current[prop] }; - } - }); - } - return accumulator; - }, []); - // This stuff needs to be fast. - await Promise.all( - items.map(async (item) => { - const info = await this.pipEnvServiceHelper.getPipEnvInfo(item.path); - if (info) { - item.envType = EnvironmentType.Pipenv; - item.pipEnvWorkspaceFolder = info.workspaceFolder.fsPath; - item.envName = info.envName || item.envName; - } - }), - ); - return items; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/index.ts b/src/client/pythonEnvironments/discovery/locators/index.ts deleted file mode 100644 index 0d01341d6ecd..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/index.ts +++ /dev/null @@ -1,311 +0,0 @@ -// tslint:disable-next-line: no-single-line-block-comment -/* eslint-disable max-classes-per-file */ -import { inject, injectable } from 'inversify'; -import { flatten } from 'lodash'; -import { - Disposable, Event, EventEmitter, Uri, -} from 'vscode'; -import { traceDecorators } from '../../../common/logger'; -import { IPlatformService } from '../../../common/platform/types'; -import { IDisposableRegistry } from '../../../common/types'; -import { createDeferred, Deferred } from '../../../common/utils/async'; -import { getURIFilter } from '../../../common/utils/misc'; -import { OSType } from '../../../common/utils/platform'; -import { - CONDA_ENV_FILE_SERVICE, - CONDA_ENV_SERVICE, - CURRENT_PATH_SERVICE, - GLOBAL_VIRTUAL_ENV_SERVICE, - IComponentAdapter, - IInterpreterLocatorHelper, - IInterpreterLocatorService, - KNOWN_PATH_SERVICE, - PIPENV_SERVICE, - WINDOWS_REGISTRY_SERVICE, - WORKSPACE_VIRTUAL_ENV_SERVICE, -} from '../../../interpreter/contracts'; -import { IServiceContainer } from '../../../ioc/types'; -import { PythonEnvInfo } from '../../base/info'; -import { - ILocator, - IPythonEnvsIterator, - Locator, - NOOP_ITERATOR, - PythonLocatorQuery, -} from '../../base/locator'; -import { - combineIterators, - DisableableLocator, - Locators, -} from '../../base/locators'; -import { PythonEnvironment } from '../../info'; -import { isHiddenInterpreter } from './services/interpreterFilter'; -import { GetInterpreterLocatorOptions } from './types'; - -/** - * A wrapper around all locators used by the extension. - */ -export class ExtensionLocators extends Locators { - constructor( - // These are expected to be low-level locators (e.g. system). - nonWorkspace: ILocator[], - // This is expected to be a locator wrapping any found in - // the workspace (i.e. WorkspaceLocators). - workspace: ILocator, - ) { - super([...nonWorkspace, workspace]); - } -} - -type WorkspaceLocatorFactory = (root: Uri) => ILocator[]; - -interface IWorkspaceFolders { - readonly roots: ReadonlyArray; - readonly onAdded: Event; - readonly onRemoved: Event; -} - -type RootURI = string; - -/** - * The collection of all workspace-specific locators used by the extension. - * - * The factories are used to produce the locators for each workspace folder. - */ -export class WorkspaceLocators extends Locator { - private readonly locators: Record = {}; - - private readonly roots: Record = {}; - - constructor( - // used to produce the per-root locators: - private readonly factories: WorkspaceLocatorFactory[], - ) { - super(); - } - - /** - * Activate the locator. - * - * @param folders - the info used to keep track of the workspace folders - */ - public activate(folders: IWorkspaceFolders):void { - folders.roots.forEach((root) => { - this.addRoot(root); - }); - folders.onAdded((root: Uri) => this.addRoot(root)); - folders.onRemoved((root: Uri) => this.removeRoot(root)); - } - - public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { - if (query?.searchLocations === null) { - // Workspace envs all have searchLocation, so there's nothing to do. - return NOOP_ITERATOR; - } - const iterators = Object.keys(this.locators).map((key) => { - if (query?.searchLocations !== undefined) { - const root = this.roots[key]; - // Match any related search location. - const filter = getURIFilter(root, { checkParent: true, checkChild: true, checkExact: true }); - // Ignore any requests for global envs. - if (!query.searchLocations.roots.some(filter)) { - // This workspace folder did not match the query, so skip it! - return NOOP_ITERATOR; - } - } - // The query matches or was not location-specific. - const locator = this.locators[key]; - return locator.iterEnvs(query); - }); - return combineIterators(iterators); - } - - public async resolveEnv(env: string | PythonEnvInfo): Promise { - if (typeof env !== 'string' && env.searchLocation) { - const rootLocator = this.locators[env.searchLocation.toString()]; - if (rootLocator) { - return rootLocator.resolveEnv(env); - } - } - // Fall back to checking all the roots. - // The eslint disable below should be removed after we have a - // better solution for these. We need asyncFind for this. - for (const key of Object.keys(this.locators)) { - const resolved = await this.locators[key].resolveEnv(env); - if (resolved !== undefined) { - return resolved; - } - } - return undefined; - } - - private addRoot(root: Uri) { - // Drop the old one, if necessary. - this.removeRoot(root); - // Create the root's locator, wrapping each factory-generated locator. - const locators: ILocator[] = []; - this.factories.forEach((create) => { - locators.push(...create(root)); - }); - const locator = new DisableableLocator(new Locators(locators)); - // Cache it. - const key = root.toString(); - this.locators[key] = locator; - this.roots[key] = root; - this.emitter.fire({ searchLocation: root }); - // Hook up the watchers. - locator.onChanged((e) => { - if (e.searchLocation === undefined) { - e.searchLocation = root; - } - this.emitter.fire(e); - }); - } - - private removeRoot(root: Uri) { - const key = root.toString(); - const locator = this.locators[key]; - if (locator === undefined) { - return; - } - delete this.locators[key]; - delete this.roots[key]; - locator.disable(); - this.emitter.fire({ searchLocation: root }); - } -} - -// The parts of IComponentAdapter used here. -interface IComponent { - hasInterpreters: Promise; - getInterpreters(resource?: Uri, options?: GetInterpreterLocatorOptions): Promise; -} - -/** - * Facilitates locating Python interpreters. - */ -@injectable() -export class PythonInterpreterLocatorService implements IInterpreterLocatorService { - public didTriggerInterpreterSuggestions: boolean; - - private readonly disposables: Disposable[] = []; - - private readonly platform: IPlatformService; - - private readonly interpreterLocatorHelper: IInterpreterLocatorHelper; - - private readonly _hasInterpreters: Deferred; - - private readonly onLocatingEmitter:EventEmitter> = - new EventEmitter>(); - - constructor( - @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(IComponentAdapter) private readonly pyenvs: IComponent, - ) { - this._hasInterpreters = createDeferred(); - serviceContainer.get(IDisposableRegistry).push(this); - this.platform = serviceContainer.get(IPlatformService); - this.interpreterLocatorHelper = serviceContainer.get(IInterpreterLocatorHelper); - this.didTriggerInterpreterSuggestions = false; - } - - /** - * This class should never emit events when we're locating. - * The events will be fired by the individual locators retrieved in `getLocators`. - * - * @readonly - * @type {Event>} - * @memberof PythonInterpreterLocatorService - */ - public get onLocating(): Event> { - return this.onLocatingEmitter.event; - } - - public get hasInterpreters(): Promise { - return this.pyenvs.hasInterpreters.then((res) => { - if (res !== undefined) { - return res; - } - return this._hasInterpreters.completed ? this._hasInterpreters.promise : Promise.resolve(false); - }); - } - - /** - * Release any held resources. - * - * Called by VS Code to indicate it is done with the resource. - */ - public dispose():void { - this.disposables.forEach((disposable) => disposable.dispose()); - } - - /** - * Return the list of known Python interpreters. - * - * The optional resource arg may control where locators look for - * interpreters. - */ - @traceDecorators.verbose('Get Interpreters') - public async getInterpreters(resource?: Uri, options?: GetInterpreterLocatorOptions): Promise { - const envs = await this.pyenvs.getInterpreters(resource, options); - if (envs !== undefined) { - return envs; - } - const locators = this.getLocators(options); - const promises = locators.map(async (provider) => provider.getInterpreters(resource)); - locators.forEach((locator) => { - locator.hasInterpreters - .then((found) => { - if (found) { - this._hasInterpreters.resolve(true); - } - }) - .ignoreErrors(); - }); - const listOfInterpreters = await Promise.all(promises); - - const items = flatten(listOfInterpreters) - .filter((item) => !!item) - .map((item) => item!) - .filter((item) => !isHiddenInterpreter(item)); - this._hasInterpreters.resolve(items.length > 0); - return this.interpreterLocatorHelper.mergeInterpreters(items); - } - - /** - * Return the list of applicable interpreter locators. - * - * The locators are pulled from the registry. - */ - private getLocators(options?: GetInterpreterLocatorOptions): IInterpreterLocatorService[] { - // The order of the services is important. - // The order is important because the data sources at the bottom of the list do not contain all, - // the information about the interpreters (e.g. type, environment name, etc). - // This way, the items returned from the top of the list will win, when we combine the items returned. - const keys: [string, OSType | undefined][] = [ - [WINDOWS_REGISTRY_SERVICE, OSType.Windows], - [CONDA_ENV_SERVICE, undefined], - [CONDA_ENV_FILE_SERVICE, undefined], - [PIPENV_SERVICE, undefined], - [GLOBAL_VIRTUAL_ENV_SERVICE, undefined], - [WORKSPACE_VIRTUAL_ENV_SERVICE, undefined], - [KNOWN_PATH_SERVICE, undefined], - [CURRENT_PATH_SERVICE, undefined], - ]; - - const locators = keys - .filter((item) => item[1] === undefined || item[1] === this.platform.osType) - .map((item) => this.serviceContainer.get(IInterpreterLocatorService, item[0])); - - // Set it to true the first time the user selects an interpreter - if (!this.didTriggerInterpreterSuggestions && options?.onSuggestion === true) { - this.didTriggerInterpreterSuggestions = true; - locators.forEach((locator) => { - locator.didTriggerInterpreterSuggestions = true; - }); - } - - return locators; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/progressService.ts b/src/client/pythonEnvironments/discovery/locators/progressService.ts deleted file mode 100644 index 90005178d803..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/progressService.ts +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { Disposable, Event, EventEmitter } from 'vscode'; -import { traceDecorators } from '../../../common/logger'; -import { IDisposableRegistry } from '../../../common/types'; -import { createDeferredFrom, Deferred } from '../../../common/utils/async'; -import { noop } from '../../../common/utils/misc'; -import { IInterpreterLocatorProgressService, IInterpreterLocatorService } from '../../../interpreter/contracts'; -import { IServiceContainer } from '../../../ioc/types'; -import { PythonEnvironment } from '../../info'; - -@injectable() -export class InterpreterLocatorProgressService implements IInterpreterLocatorProgressService { - private deferreds: Deferred[] = []; - - private readonly refreshing = new EventEmitter(); - - private readonly refreshed = new EventEmitter(); - - private readonly locators: IInterpreterLocatorService[] = []; - - constructor( - @inject(IServiceContainer) serviceContainer: IServiceContainer, - @inject(IDisposableRegistry) private readonly disposables: Disposable[], - ) { - this.locators = serviceContainer.getAll(IInterpreterLocatorService); - } - - public get onRefreshing(): Event { - return this.refreshing.event; - } - - public get onRefreshed(): Event { - return this.refreshed.event; - } - - public register(): void { - this.locators.forEach((locator) => { - locator.onLocating(this.handleProgress, this, this.disposables); - }); - } - - @traceDecorators.verbose('Detected refreshing of Interpreters') - private handleProgress(promise: Promise) { - this.deferreds.push(createDeferredFrom(promise)); - this.notifyRefreshing(); - this.checkProgress(); - } - - @traceDecorators.verbose('All locators have completed locating') - private notifyCompleted() { - this.refreshed.fire(); - } - - @traceDecorators.verbose('Notify locators are locating') - private notifyRefreshing() { - this.refreshing.fire(); - } - - private checkProgress(): void { - if (this.deferreds.length === 0) { - return; - } - if (this.areAllItemsComplete()) { - this.notifyCompleted(); - return; - } - Promise.all(this.deferreds.map((item) => item.promise)) - .catch(noop) - .then(() => this.checkProgress()) - .ignoreErrors(); - } - - @traceDecorators.verbose('Checking whether locactors have completed locating') - private areAllItemsComplete() { - this.deferreds = this.deferreds.filter((item) => !item.completed); - return this.deferreds.length === 0; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/KnownPathsService.ts b/src/client/pythonEnvironments/discovery/locators/services/KnownPathsService.ts deleted file mode 100644 index 940bd21356f4..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/KnownPathsService.ts +++ /dev/null @@ -1,119 +0,0 @@ -// tslint:disable:no-require-imports no-var-requires no-unnecessary-callback-wrapper -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { Uri } from 'vscode'; -import { IFileSystem, IPlatformService } from '../../../../common/platform/types'; -import { ICurrentProcess, IPathUtils } from '../../../../common/types'; -import { IInterpreterHelper, IKnownSearchPathsForInterpreters } from '../../../../interpreter/contracts'; -import { IServiceContainer } from '../../../../ioc/types'; -import { EnvironmentType, PythonEnvironment } from '../../../info'; -import { lookForInterpretersInDirectory } from '../helpers'; -import { CacheableLocatorService } from './cacheableLocatorService'; - -const flatten = require('lodash/flatten') as typeof import('lodash/flatten'); - -/** - * Locates "known" paths. - */ -@injectable() -export class KnownPathsService extends CacheableLocatorService { - public constructor( - @inject(IKnownSearchPathsForInterpreters) private knownSearchPaths: IKnownSearchPathsForInterpreters, - @inject(IInterpreterHelper) private helper: IInterpreterHelper, - @inject(IServiceContainer) serviceContainer: IServiceContainer, - ) { - super('KnownPathsService', serviceContainer); - } - - /** - * Release any held resources. - * - * Called by VS Code to indicate it is done with the resource. - */ - // tslint:disable-next-line:no-empty - public dispose() {} - - /** - * Return the located interpreters. - * - * This is used by CacheableLocatorService.getInterpreters(). - */ - protected getInterpretersImplementation(_resource?: Uri): Promise { - return this.suggestionsFromKnownPaths(); - } - - /** - * Return the located interpreters. - */ - private suggestionsFromKnownPaths() { - const promises = this.knownSearchPaths.getSearchPaths().map((dir) => this.getInterpretersInDirectory(dir)); - return Promise.all(promises) - .then((listOfInterpreters) => flatten(listOfInterpreters)) - .then((interpreters) => interpreters.filter( - (item) => item.length > 0, - )) - .then((interpreters) => Promise.all( - interpreters.map((interpreter) => this.getInterpreterDetails(interpreter)), - )) - .then((interpreters) => interpreters.filter( - (interpreter) => !!interpreter, - ).map((interpreter) => interpreter!)); - } - - /** - * Return the information about the identified interpreter binary. - */ - private async getInterpreterDetails(interpreter: string) { - const details = await this.helper.getInterpreterInformation(interpreter); - if (!details) { - return; - } - this._hasInterpreters.resolve(true); - return { - ...(details as PythonEnvironment), - path: interpreter, - envType: EnvironmentType.Unknown, - }; - } - - /** - * Return the interpreters in the given directory. - */ - private getInterpretersInDirectory(dir: string) { - const fs = this.serviceContainer.get(IFileSystem); - return fs - .directoryExists(dir) - .then((exists) => (exists ? lookForInterpretersInDirectory(dir) : Promise.resolve([]))); - } -} - -@injectable() -export class KnownSearchPathsForInterpreters implements IKnownSearchPathsForInterpreters { - constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) {} - - /** - * Return the paths where Python interpreters might be found. - */ - public getSearchPaths(): string[] { - const currentProcess = this.serviceContainer.get(ICurrentProcess); - const platformService = this.serviceContainer.get(IPlatformService); - const pathUtils = this.serviceContainer.get(IPathUtils); - - const searchPaths = currentProcess.env[platformService.pathVariableName]!.split(pathUtils.delimiter) - .map((p) => p.trim()) - .filter((p) => p.length > 0); - - if (!platformService.isWindows) { - ['/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin', '/usr/local/sbin'].forEach((p) => { - searchPaths.push(p); - searchPaths.push(path.join(pathUtils.home, p)); - }); - // Add support for paths such as /Users/xxx/anaconda/bin. - if (process.env.HOME) { - searchPaths.push(path.join(pathUtils.home, 'anaconda', 'bin')); - searchPaths.push(path.join(pathUtils.home, 'python', 'bin')); - } - } - return searchPaths; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/baseVirtualEnvService.ts b/src/client/pythonEnvironments/discovery/locators/services/baseVirtualEnvService.ts deleted file mode 100644 index 121ffe0152b9..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/baseVirtualEnvService.ts +++ /dev/null @@ -1,108 +0,0 @@ -// tslint:disable:no-unnecessary-callback-wrapper no-require-imports no-var-requires - -import { injectable, unmanaged } from 'inversify'; -import { flatten, noop } from 'lodash'; -import * as path from 'path'; -import { Uri } from 'vscode'; -import { traceError } from '../../../../common/logger'; -import { IFileSystem, IPlatformService } from '../../../../common/platform/types'; -import { IInterpreterHelper, IVirtualEnvironmentsSearchPathProvider } from '../../../../interpreter/contracts'; -import { IVirtualEnvironmentManager } from '../../../../interpreter/virtualEnvs/types'; -import { IServiceContainer } from '../../../../ioc/types'; -import { EnvironmentType, PythonEnvironment } from '../../../info'; -import { lookForInterpretersInDirectory } from '../helpers'; -import { CacheableLocatorService } from './cacheableLocatorService'; - -@injectable() -export class BaseVirtualEnvService extends CacheableLocatorService { - private readonly virtualEnvMgr: IVirtualEnvironmentManager; - - private readonly helper: IInterpreterHelper; - - private readonly fileSystem: IFileSystem; - - public constructor( - @unmanaged() private searchPathsProvider: IVirtualEnvironmentsSearchPathProvider, - @unmanaged() serviceContainer: IServiceContainer, - @unmanaged() name: string, - @unmanaged() cachePerWorkspace = false, - ) { - super(name, serviceContainer, cachePerWorkspace); - this.virtualEnvMgr = serviceContainer.get(IVirtualEnvironmentManager); - this.helper = serviceContainer.get(IInterpreterHelper); - this.fileSystem = serviceContainer.get(IFileSystem); - } - - // eslint-disable-next-line class-methods-use-this - public dispose(): void { - noop(); - } - - protected getInterpretersImplementation(resource?: Uri): Promise { - return this.suggestionsFromKnownVenvs(resource); - } - - private async suggestionsFromKnownVenvs(resource?: Uri) { - const searchPaths = await this.searchPathsProvider.getSearchPaths(resource); - return Promise.all( - searchPaths.map((dir) => this.lookForInterpretersInVenvs(dir, resource)), - ).then((listOfInterpreters) => flatten(listOfInterpreters)); - } - - private async lookForInterpretersInVenvs(pathToCheck: string, resource?: Uri) { - return this.fileSystem - .getSubDirectories(pathToCheck) - .then((subDirs) => Promise.all(this.getProspectiveDirectoriesForLookup(subDirs))) - .then((dirs) => dirs.filter((dir) => dir.length > 0)) - .then((dirs) => Promise.all(dirs.map((d) => lookForInterpretersInDirectory(d)))) - .then((pathsWithInterpreters) => flatten(pathsWithInterpreters)) - .then((interpreters) => Promise.all( - interpreters.map((interpreter) => this.getVirtualEnvDetails(interpreter, resource)), - )) - .then((interpreters) => interpreters.filter( - (interpreter) => !!interpreter, - ).map((interpreter) => interpreter!)) - .catch((err) => { - traceError('Python Extension (lookForInterpretersInVenvs):', err); - // Ignore exceptions. - return [] as PythonEnvironment[]; - }); - } - - private getProspectiveDirectoriesForLookup(subDirs: string[]) { - const platform = this.serviceContainer.get(IPlatformService); - const dirToLookFor = platform.virtualEnvBinName; - return subDirs.map((subDir) => this.fileSystem - .getSubDirectories(subDir) - .then((dirs) => { - const scriptOrBinDirs = dirs.filter((dir) => { - const folderName = path.basename(dir); - return this.fileSystem.arePathsSame(folderName, dirToLookFor); - }); - return scriptOrBinDirs.length === 1 ? scriptOrBinDirs[0] : ''; - }) - .catch((err) => { - traceError('Python Extension (getProspectiveDirectoriesForLookup):', err); - // Ignore exceptions. - return ''; - })); - } - - private async getVirtualEnvDetails(interpreter: string, resource?: Uri): Promise { - return Promise.all([ - this.helper.getInterpreterInformation(interpreter), - this.virtualEnvMgr.getEnvironmentName(interpreter, resource), - this.virtualEnvMgr.getEnvironmentType(interpreter, resource), - ]).then(([details, virtualEnvName, type]):Promise => { - if (!details) { - return Promise.resolve(undefined); - } - this._hasInterpreters.resolve(true); - return Promise.resolve({ - ...(details as PythonEnvironment), - envName: virtualEnvName, - type: type! as EnvironmentType, - }); - }); - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/cacheableLocatorService.ts b/src/client/pythonEnvironments/discovery/locators/services/cacheableLocatorService.ts deleted file mode 100644 index 0292f5521947..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/cacheableLocatorService.ts +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// tslint:disable:no-any - -import { injectable, unmanaged } from 'inversify'; -import * as md5 from 'md5'; -import { - Disposable, Event, EventEmitter, Uri, -} from 'vscode'; -import { IWorkspaceService } from '../../../../common/application/types'; -import '../../../../common/extensions'; -import { traceDecorators, traceVerbose } from '../../../../common/logger'; -import { IDisposableRegistry, IPersistentStateFactory } from '../../../../common/types'; -import { createDeferred, Deferred } from '../../../../common/utils/async'; -import { StopWatch } from '../../../../common/utils/stopWatch'; -import { IInterpreterLocatorService, IInterpreterWatcher } from '../../../../interpreter/contracts'; -import { IServiceContainer } from '../../../../ioc/types'; -import { sendTelemetryEvent } from '../../../../telemetry'; -import { EventName } from '../../../../telemetry/constants'; -import { PythonEnvironment } from '../../../info'; -import { GetInterpreterLocatorOptions } from '../types'; - -/** - * This class exists so that the interpreter fetching can be cached in between tests. Normally - * this cache resides in memory for the duration of the CacheableLocatorService's lifetime, but in the case - * of our functional tests, we want the cached data to exist outside of each test (where each test will destroy the CacheableLocatorService) - * This gives each test a 20 second speedup. - */ -export class CacheableLocatorPromiseCache { - private static useStatic = false; - - private static staticMap = new Map>(); - - private normalMap = new Map>(); - - public static forceUseStatic() { - CacheableLocatorPromiseCache.useStatic = true; - } - - public static forceUseNormal() { - CacheableLocatorPromiseCache.useStatic = false; - } - - public get(key: string): Deferred | undefined { - if (CacheableLocatorPromiseCache.useStatic) { - return CacheableLocatorPromiseCache.staticMap.get(key); - } - return this.normalMap.get(key); - } - - public set(key: string, value: Deferred) { - if (CacheableLocatorPromiseCache.useStatic) { - CacheableLocatorPromiseCache.staticMap.set(key, value); - } else { - this.normalMap.set(key, value); - } - } - - public delete(key: string) { - if (CacheableLocatorPromiseCache.useStatic) { - CacheableLocatorPromiseCache.staticMap.delete(key); - } else { - this.normalMap.delete(key); - } - } -} - -@injectable() -export abstract class CacheableLocatorService implements IInterpreterLocatorService { - protected readonly _hasInterpreters: Deferred; - - private readonly promisesPerResource = new CacheableLocatorPromiseCache(); - - private readonly handlersAddedToResource = new Set(); - - private readonly cacheKeyPrefix: string; - - private readonly locating = new EventEmitter>(); - - private _didTriggerInterpreterSuggestions: boolean; - - constructor( - @unmanaged() private readonly name: string, - @unmanaged() protected readonly serviceContainer: IServiceContainer, - @unmanaged() private cachePerWorkspace: boolean = false, - ) { - this._hasInterpreters = createDeferred(); - this.cacheKeyPrefix = `INTERPRETERS_CACHE_v3_${name}`; - this._didTriggerInterpreterSuggestions = false; - } - - public get didTriggerInterpreterSuggestions(): boolean { - return this._didTriggerInterpreterSuggestions; - } - - public set didTriggerInterpreterSuggestions(value: boolean) { - this._didTriggerInterpreterSuggestions = value; - } - - public get onLocating(): Event> { - return this.locating.event; - } - - public get hasInterpreters(): Promise { - return this._hasInterpreters.completed ? this._hasInterpreters.promise : Promise.resolve(false); - } - - public abstract dispose(): void; - - @traceDecorators.verbose('Get Interpreters in CacheableLocatorService') - public async getInterpreters(resource?: Uri, options?: GetInterpreterLocatorOptions): Promise { - const cacheKey = this.getCacheKey(resource); - let deferred = this.promisesPerResource.get(cacheKey); - if (!deferred || options?.ignoreCache) { - deferred = createDeferred(); - this.promisesPerResource.set(cacheKey, deferred); - - this.addHandlersForInterpreterWatchers(cacheKey, resource).ignoreErrors(); - - const stopWatch = new StopWatch(); - this.getInterpretersImplementation(resource) - .then(async (items) => { - await this.cacheInterpreters(items, resource); - traceVerbose( - `Interpreters returned by ${this.name} are of count ${Array.isArray(items) ? items.length : 0}`, - ); - traceVerbose(`Interpreters returned by ${this.name} are ${JSON.stringify(items)}`); - sendTelemetryEvent(EventName.PYTHON_INTERPRETER_DISCOVERY, stopWatch.elapsedTime, { - locator: this.name, - interpreters: Array.isArray(items) ? items.length : 0, - }); - deferred!.resolve(items); - }) - .catch((ex) => { - sendTelemetryEvent( - EventName.PYTHON_INTERPRETER_DISCOVERY, - stopWatch.elapsedTime, - { locator: this.name }, - ex, - ); - deferred!.reject(ex); - }); - - this.locating.fire(deferred.promise); - } - deferred.promise - .then((items) => this._hasInterpreters.resolve(items.length > 0)) - .catch((_) => this._hasInterpreters.resolve(false)); - - if (deferred.completed) { - return deferred.promise; - } - - const cachedInterpreters = options?.ignoreCache ? undefined : this.getCachedInterpreters(resource); - return Array.isArray(cachedInterpreters) ? cachedInterpreters : deferred.promise; - } - - protected async addHandlersForInterpreterWatchers(cacheKey: string, resource: Uri | undefined): Promise { - if (this.handlersAddedToResource.has(cacheKey)) { - return; - } - this.handlersAddedToResource.add(cacheKey); - const watchers = await this.getInterpreterWatchers(resource); - const disposableRegisry = this.serviceContainer.get(IDisposableRegistry); - watchers.forEach((watcher) => { - watcher.onDidCreate( - () => { - traceVerbose(`Interpreter Watcher change handler for ${this.cacheKeyPrefix}`); - this.promisesPerResource.delete(cacheKey); - this.getInterpreters(resource).ignoreErrors(); - }, - this, - disposableRegisry, - ); - }); - } - - protected async getInterpreterWatchers(_resource: Uri | undefined): Promise { - return []; - } - - protected abstract getInterpretersImplementation(resource?: Uri): Promise; - - protected createPersistenceStore(resource?: Uri) { - const cacheKey = this.getCacheKey(resource); - const persistentFactory = this.serviceContainer.get(IPersistentStateFactory); - if (this.cachePerWorkspace) { - return persistentFactory.createWorkspacePersistentState(cacheKey, undefined as any); - } - return persistentFactory.createGlobalPersistentState(cacheKey, undefined as any); - } - - protected getCachedInterpreters(resource?: Uri): PythonEnvironment[] | undefined { - const persistence = this.createPersistenceStore(resource); - if (!Array.isArray(persistence.value)) { - return; - } - return persistence.value.map((item) => ({ - ...item, - cachedEntry: true, - })); - } - - protected async cacheInterpreters(interpreters: PythonEnvironment[], resource?: Uri) { - const persistence = this.createPersistenceStore(resource); - await persistence.updateValue(interpreters); - } - - protected getCacheKey(resource?: Uri) { - if (!resource || !this.cachePerWorkspace) { - return this.cacheKeyPrefix; - } - // Ensure we have separate caches per workspace where necessary.Î - const workspaceService = this.serviceContainer.get(IWorkspaceService); - if (!Array.isArray(workspaceService.workspaceFolders)) { - return this.cacheKeyPrefix; - } - - const workspace = workspaceService.getWorkspaceFolder(resource); - return workspace ? `${this.cacheKeyPrefix}:${md5(workspace.uri.fsPath)}` : this.cacheKeyPrefix; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/conda.ts b/src/client/pythonEnvironments/discovery/locators/services/conda.ts deleted file mode 100644 index 5a50c56288c4..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/conda.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { EnvironmentType, PythonEnvironment } from '../../../info'; - -// tslint:disable-next-line:variable-name -export const AnacondaCompanyNames = ['Anaconda, Inc.', 'Continuum Analytics, Inc.']; -// tslint:disable-next-line:variable-name -export const AnacondaCompanyName = 'Anaconda, Inc.'; -// tslint:disable-next-line:variable-name -export const AnacondaDisplayName = 'Anaconda'; -// tslint:disable-next-line:variable-name -export const AnacondaIdentifiers = ['Anaconda', 'Conda', 'Continuum']; - -export type CondaEnvironmentInfo = { - name: string; - path: string; -}; - -export type CondaInfo = { - envs?: string[]; - 'sys.version'?: string; - 'sys.prefix'?: string; - python_version?: string; - default_prefix?: string; - conda_version?: string; -}; - -/** - * Return the list of conda env interpreters. - */ -export async function parseCondaInfo( - info: CondaInfo, - getPythonPath: (condaEnv: string) => string, - fileExists: (filename: string) => Promise, - getPythonInfo: (python: string) => Promise | undefined>, -) { - // The root of the conda environment is itself a Python interpreter - // envs reported as e.g.: /Users/bob/miniconda3/envs/someEnv. - const envs = Array.isArray(info.envs) ? info.envs : []; - if (info.default_prefix && info.default_prefix.length > 0) { - envs.push(info.default_prefix); - } - - const promises = envs.map(async (envPath) => { - const pythonPath = getPythonPath(envPath); - - if (!(await fileExists(pythonPath))) { - return; - } - const details = await getPythonInfo(pythonPath); - if (!details) { - return; - } - - return { - ...(details as PythonEnvironment), - path: pythonPath, - companyDisplayName: AnacondaCompanyName, - envType: EnvironmentType.Conda, - envPath, - }; - }); - - return ( - Promise.all(promises) - .then((interpreters) => interpreters.filter( - (interpreter) => interpreter !== null && interpreter !== undefined, - )) - // tslint:disable-next-line:no-non-null-assertion - .then((interpreters) => interpreters.map((interpreter) => interpreter!)) - ); -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/condaEnvFileService.ts b/src/client/pythonEnvironments/discovery/locators/services/condaEnvFileService.ts deleted file mode 100644 index 36b6818bad2f..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/condaEnvFileService.ts +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -/** - * This file is essential to have for us to discover conda interpreters, `CondaEnvService` alone is not sufficient. - * CondaEnvService runs ` env list` command, which requires that we know the path to conda. - * In cases where we're not able to figure that out, we can still use this file to discover paths to conda environments. - * More details: https://github.com/microsoft/vscode-python/issues/8886 - */ - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { Uri } from 'vscode'; -import { traceError } from '../../../../common/logger'; -import { IFileSystem } from '../../../../common/platform/types'; -import { ICondaService, IInterpreterHelper } from '../../../../interpreter/contracts'; -import { IServiceContainer } from '../../../../ioc/types'; -import { EnvironmentType, PythonEnvironment } from '../../../info'; -import { CacheableLocatorService } from './cacheableLocatorService'; -import { AnacondaCompanyName } from './conda'; - -/** - * Locate conda env interpreters based on the "conda environments file". - */ -@injectable() -export class CondaEnvFileService extends CacheableLocatorService { - constructor( - @inject(IInterpreterHelper) private helperService: IInterpreterHelper, - @inject(ICondaService) private condaService: ICondaService, - @inject(IFileSystem) private fileSystem: IFileSystem, - @inject(IServiceContainer) serviceContainer: IServiceContainer, - ) { - super('CondaEnvFileService', serviceContainer); - } - - /** - * Release any held resources. - * - * Called by VS Code to indicate it is done with the resource. - */ - // tslint:disable-next-line:no-empty - public dispose() {} - - /** - * Return the located interpreters. - * - * This is used by CacheableLocatorService.getInterpreters(). - */ - protected getInterpretersImplementation(_resource?: Uri): Promise { - return this.getSuggestionsFromConda(); - } - - /** - * Return the list of interpreters identified by the "conda environments file". - */ - private async getSuggestionsFromConda(): Promise { - if (!this.condaService.condaEnvironmentsFile) { - return []; - } - return this.fileSystem - .fileExists(this.condaService.condaEnvironmentsFile!) - .then((exists) => ( - exists ? this.getEnvironmentsFromFile(this.condaService.condaEnvironmentsFile!) : Promise.resolve([]) - )); - } - - /** - * Return the list of environments identified in the given file. - */ - private async getEnvironmentsFromFile(envFile: string) { - try { - const fileContents = await this.fileSystem.readFile(envFile); - const environmentPaths = fileContents - .split(/\r?\n/g) - .map((environmentPath) => environmentPath.trim()) - .filter((environmentPath) => environmentPath.length > 0); - - const interpreters = ( - await Promise.all( - environmentPaths.map((environmentPath) => this.getInterpreterDetails(environmentPath)), - ) - ) - .filter((item) => !!item) - .map((item) => item!); - - const environments = await this.condaService.getCondaEnvironments(true); - if (Array.isArray(environments) && environments.length > 0) { - interpreters.forEach((interpreter) => { - const environment = environments.find( - (item) => this.fileSystem.arePathsSame(item.path, interpreter!.envPath!), - ); - if (environment) { - interpreter.envName = environment!.name; - } - }); - } - return interpreters; - } catch (err) { - traceError('Python Extension (getEnvironmentsFromFile.readFile):', err); - // Ignore errors in reading the file. - return [] as PythonEnvironment[]; - } - } - - /** - * Return the interpreter info for the given anaconda environment. - */ - private async getInterpreterDetails(environmentPath: string): Promise { - const interpreter = this.condaService.getInterpreterPath(environmentPath); - if (!interpreter || !(await this.fileSystem.fileExists(interpreter))) { - return; - } - - const details = await this.helperService.getInterpreterInformation(interpreter); - if (!details) { - return; - } - const envName = details.envName ? details.envName : path.basename(environmentPath); - this._hasInterpreters.resolve(true); - return { - ...(details as PythonEnvironment), - path: interpreter, - companyDisplayName: AnacondaCompanyName, - envType: EnvironmentType.Conda, - envPath: environmentPath, - envName, - }; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/condaEnvService.ts b/src/client/pythonEnvironments/discovery/locators/services/condaEnvService.ts deleted file mode 100644 index 41ca380a88c6..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/condaEnvService.ts +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -import { Uri } from 'vscode'; -import { traceError } from '../../../../common/logger'; -import { IFileSystem } from '../../../../common/platform/types'; -import { ICondaService, IInterpreterHelper } from '../../../../interpreter/contracts'; -import { IServiceContainer } from '../../../../ioc/types'; -import { PythonEnvironment } from '../../../info'; -import { CacheableLocatorService } from './cacheableLocatorService'; -import { parseCondaInfo } from './conda'; - -/** - * Locates conda env interpreters based on the conda service's info. - */ -@injectable() -export class CondaEnvService extends CacheableLocatorService { - constructor( - @inject(ICondaService) private condaService: ICondaService, - @inject(IInterpreterHelper) private helper: IInterpreterHelper, - @inject(IServiceContainer) serviceContainer: IServiceContainer, - @inject(IFileSystem) private fileSystem: IFileSystem, - ) { - super('CondaEnvService', serviceContainer); - } - - /** - * Release any held resources. - * - * Called by VS Code to indicate it is done with the resource. - */ - // tslint:disable-next-line:no-empty - public dispose() {} - - /** - * Return the located interpreters. - * - * This is used by CacheableLocatorService.getInterpreters(). - */ - protected getInterpretersImplementation(_resource?: Uri): Promise { - return this.getSuggestionsFromConda(); - } - - /** - * Return the list of interpreters for all the conda envs. - */ - private async getSuggestionsFromConda(): Promise { - try { - const info = await this.condaService.getCondaInfo(); - if (!info) { - return []; - } - const interpreters = await parseCondaInfo( - info, - (env) => this.condaService.getInterpreterPath(env), - (f) => this.fileSystem.fileExists(f), - (p) => this.helper.getInterpreterInformation(p), - ); - this._hasInterpreters.resolve(interpreters.length > 0); - const environments = await this.condaService.getCondaEnvironments(true); - if (Array.isArray(environments) && environments.length > 0) { - interpreters.forEach((interpreter) => { - const environment = environments.find( - (item) => this.fileSystem.arePathsSame(item.path, interpreter!.envPath!), - ); - if (environment) { - interpreter.envName = environment!.name; - } - }); - } - - return interpreters; - } catch (ex) { - // Failed because either: - // 1. conda is not installed. - // 2. `conda info --json` has changed signature. - // 3. output of `conda info --json` has changed in structure. - // In all cases, we can't offer conda pythonPath suggestions. - traceError('Failed to get Suggestions from conda', ex); - return []; - } - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/condaHelper.ts b/src/client/pythonEnvironments/discovery/locators/services/condaHelper.ts deleted file mode 100644 index 66930f5ac09d..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/condaHelper.ts +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import '../../../../common/extensions'; -import { AnacondaDisplayName, AnacondaIdentifiers, CondaInfo } from './conda'; - -export type EnvironmentPath = string; -export type EnvironmentName = string; - -/** - * Helpers for conda. - */ - -/** - * Return the string to display for the conda interpreter. - */ -export function getDisplayName(condaInfo: CondaInfo = {}): string { - // Samples. - // "3.6.1 |Anaconda 4.4.0 (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]". - // "3.6.2 |Anaconda, Inc.| (default, Sep 21 2017, 18:29:43) \n[GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)]". - const sysVersion = condaInfo['sys.version']; - if (!sysVersion) { - return AnacondaDisplayName; - } - - // Take the second part of the sys.version. - const sysVersionParts = sysVersion.split('|', 2); - if (sysVersionParts.length === 2) { - const displayName = sysVersionParts[1].trim(); - if (isIdentifiableAsAnaconda(displayName)) { - return displayName; - } - return `${displayName} : ${AnacondaDisplayName}`; - } - return AnacondaDisplayName; -} - -/** - * Parses output returned by the command `conda env list`. - * Sample output is as follows: - * # conda environments: - * # - * base * /Users/donjayamanne/anaconda3 - * one /Users/donjayamanne/anaconda3/envs/one - * py27 /Users/donjayamanne/anaconda3/envs/py27 - * py36 /Users/donjayamanne/anaconda3/envs/py36 - * three /Users/donjayamanne/anaconda3/envs/three - * /Users/donjayamanne/anaconda3/envs/four - * /Users/donjayamanne/anaconda3/envs/five 5 - * aaaa_bbbb_cccc_dddd_eeee_ffff_gggg /Users/donjayamanne/anaconda3/envs/aaaa_bbbb_cccc_dddd_eeee_ffff_gggg - * with*star /Users/donjayamanne/anaconda3/envs/with*star - * "/Users/donjayamanne/anaconda3/envs/seven " - */ -export function parseCondaEnvFileContents( - condaEnvFileContents: string, -): { name: string; path: string; isActive: boolean }[] | undefined { - // Don't trim the lines. `path` portion of the line can end with a space. - const lines = condaEnvFileContents.splitLines({ trim: false }); - const envs: { name: string; path: string; isActive: boolean }[] = []; - - lines.forEach((line) => { - const item = parseCondaEnvFileLine(line); - if (item) { - envs.push(item); - } - }); - - return envs.length > 0 ? envs : undefined; -} - -function parseCondaEnvFileLine(line: string): { name: string; path: string; isActive: boolean } | undefined { - // Empty lines or lines starting with `#` are comments and can be ignored. - if (line.length === 0 || line.startsWith('#')) { - return undefined; - } - - // This extraction is based on the following code for `conda env list`: - // https://github.com/conda/conda/blob/f207a2114c388fd17644ee3a5f980aa7cf86b04b/conda/cli/common.py#L188 - // It uses "%-20s %s %s" as the format string. Where the middle %s is '*' - // if the environment is active, and ' ' if it is not active. - - // If conda environment was created using `-p` then it may NOT have a name. - // Use empty string as default name for envs created using path only. - let name = ''; - let remainder = line; - - // The `name` and `path` parts are separated by at least 5 spaces. We cannot - // use a single space here since it can be part of the name (see below for - // name spec). Another assumption here is that `name` does not start with - // 5*spaces or somewhere in the center. However, ` name` or `a b` is - // a valid name when using --clone. Highly unlikely that users will have this - // form as the environment name. lastIndexOf() can also be used but that assumes - // that `path` does NOT end with 5*spaces. - let spaceIndex = line.indexOf(' '); - if (spaceIndex === -1) { - // This means the environment name is longer than 17 characters and it is - // active. Try ' * ' for separator between name and path. - spaceIndex = line.indexOf(' * '); - } - - if (spaceIndex > 0) { - // Parsing `name` - // > `conda create -n ` - // conda environment `name` should NOT have following characters - // ('/', ' ', ':', '#'). So we can use the index of 5*space - // characters to extract the name. - // - // > `conda create --clone one -p "~/envs/one two"` - // this can generate a cloned env with name `one two`. This is - // only allowed for cloned environments. In both cases, the best - // separator is 5*spaces. It is highly unlikely that users will have - // 5*spaces in their environment name. - // - // Notes: When using clone if the path has a trailing space, it will - // not be preserved for the name. Trailing spaces in environment names - // are NOT allowed. But leading spaces are allowed. Currently there no - // special separator character between name and path, other than spaces. - // We will need a well known separator if this ever becomes a issue. - name = line.substring(0, spaceIndex).trimRight(); - remainder = line.substring(spaceIndex); - } - - // Detecting Active Environment: - // Only active environment will have `*` between `name` and `path`. `name` - // or `path` can have `*` in them as well. So we have to look for `*` in - // between `name` and `path`. We already extracted the name, the next non- - // whitespace character should either be `*` or environment path. - remainder = remainder.trimLeft(); - const isActive = remainder.startsWith('*'); - - // Parsing `path` - // If `*` is the first then we can skip that character. Trim left again, - // don't do trim() or trimRight(), since paths can end with a space. - remainder = (isActive ? remainder.substring(1) : remainder).trimLeft(); - - return { name, path: remainder, isActive }; -} -/** - * Does the given string match a known Anaconda identifier. - */ -function isIdentifiableAsAnaconda(value: string) { - const valueToSearch = value.toLowerCase(); - return AnacondaIdentifiers.some((item) => valueToSearch.indexOf(item.toLowerCase()) !== -1); -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/condaLocator.ts b/src/client/pythonEnvironments/discovery/locators/services/condaLocator.ts deleted file mode 100644 index d92e79f25c08..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/condaLocator.ts +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import * as path from 'path'; -import { pathExists } from '../../../common/externalDependencies'; - -/** - * Checks if the given interpreter path belongs to a conda environment. Using - * known folder layout, and presence of 'conda-meta' directory. - * @param {string} interpreterPath: Absolute path to any python interpreter. - * - * Remarks: This is what we will use to begin with. Another approach we can take - * here is to parse ~/.conda/environments.txt. This file will have list of conda - * environments. We can compare the interpreter path against the paths in that file. - * We don't want to rely on this file because it is an implementation detail of - * conda. If it turns out that the layout based identification is not sufficient - * that is the next alternative that is cheap. - * - * sample content of the ~/.conda/environments.txt: - * C:\envs\myenv - * C:\ProgramData\Miniconda3 - * - * Yet another approach is to use `conda env list --json` and compare the returned env - * list to see if the given interpreter path belongs to any of the returned environments. - * This approach is heavy, and involves running a binary. For now we decided not to - * take this approach, since it does not look like we need it. - * - * sample output from `conda env list --json`: - * conda env list --json - * { - * "envs": [ - * "C:\\envs\\myenv", - * "C:\\ProgramData\\Miniconda3" - * ] - * } - */ -export async function isCondaEnvironment(interpreterPath: string): Promise { - const condaMetaDir = 'conda-meta'; - - // Check if the conda-meta directory is in the same directory as the interpreter. - // This layout is common in Windows. - // env - // |__ conda-meta <--- check if this directory exists - // |__ python.exe <--- interpreterPath - const condaEnvDir1 = path.join(path.dirname(interpreterPath), condaMetaDir); - - // Check if the conda-meta directory is in the parent directory relative to the interpreter. - // This layout is common on linux/Mac. - // env - // |__ conda-meta <--- check if this directory exists - // |__ bin - // |__ python <--- interpreterPath - const condaEnvDir2 = path.join(path.dirname(path.dirname(interpreterPath)), condaMetaDir); - - return [await pathExists(condaEnvDir1), await pathExists(condaEnvDir2)].includes(true); -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/condaService.ts b/src/client/pythonEnvironments/discovery/locators/services/condaService.ts deleted file mode 100644 index 735c84c6f09b..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/condaService.ts +++ /dev/null @@ -1,428 +0,0 @@ -import { - inject, injectable, named, optional, -} from 'inversify'; -import * as path from 'path'; -import { compare, parse, SemVer } from 'semver'; -import { ConfigurationChangeEvent, Uri } from 'vscode'; - -import { IWorkspaceService } from '../../../../common/application/types'; -import { - traceDecorators, traceError, traceVerbose, traceWarning, -} from '../../../../common/logger'; -import { IFileSystem, IPlatformService } from '../../../../common/platform/types'; -import { IProcessServiceFactory } from '../../../../common/process/types'; -import { IConfigurationService, IDisposableRegistry, IPersistentStateFactory } from '../../../../common/types'; -import { cache } from '../../../../common/utils/decorators'; -import { IComponentAdapter, ICondaService, IInterpreterLocatorService, WINDOWS_REGISTRY_SERVICE } from '../../../../interpreter/contracts'; -import { EnvironmentType, PythonEnvironment } from '../../../info'; -import { CondaEnvironmentInfo, CondaInfo } from './conda'; -import { parseCondaEnvFileContents } from './condaHelper'; - -// tslint:disable-next-line:no-require-imports no-var-requires -const untildify: (value: string) => string = require('untildify'); - -// This glob pattern will match all of the following: -// ~/anaconda/bin/conda, ~/anaconda3/bin/conda, ~/miniconda/bin/conda, ~/miniconda3/bin/conda -// /usr/share/anaconda/bin/conda, /usr/share/anaconda3/bin/conda, /usr/share/miniconda/bin/conda, /usr/share/miniconda3/bin/conda - -const condaGlobPathsForLinuxMac = [ - untildify('~/opt/*conda*/bin/conda'), - '/opt/*conda*/bin/conda', - '/usr/share/*conda*/bin/conda', - untildify('~/*conda*/bin/conda'), -]; - -export const CondaLocationsGlob = `{${condaGlobPathsForLinuxMac.join(',')}}`; - -// ...and for windows, the known default install locations: -const condaGlobPathsForWindows = [ - '/ProgramData/[Mm]iniconda*/Scripts/conda.exe', - '/ProgramData/[Aa]naconda*/Scripts/conda.exe', - untildify('~/[Mm]iniconda*/Scripts/conda.exe'), - untildify('~/[Aa]naconda*/Scripts/conda.exe'), - untildify('~/AppData/Local/Continuum/[Mm]iniconda*/Scripts/conda.exe'), - untildify('~/AppData/Local/Continuum/[Aa]naconda*/Scripts/conda.exe'), -]; - -// format for glob processing: -export const CondaLocationsGlobWin = `{${condaGlobPathsForWindows.join(',')}}`; - -export const CondaGetEnvironmentPrefix = 'Outputting Environment Now...'; - -// The parts of IComponentAdapter used here. -interface IComponent { - isCondaEnvironment(interpreterPath: string): Promise; - getCondaEnvironment(interpreterPath: string): Promise; -} - -/** - * A wrapper around a conda installation. - */ -@injectable() -export class CondaService implements ICondaService { - private condaFile?: Promise; - - private isAvailable: boolean | undefined; - - constructor( - @inject(IProcessServiceFactory) private processServiceFactory: IProcessServiceFactory, - @inject(IPlatformService) private platform: IPlatformService, - @inject(IFileSystem) private fileSystem: IFileSystem, - @inject(IPersistentStateFactory) private persistentStateFactory: IPersistentStateFactory, - @inject(IConfigurationService) private configService: IConfigurationService, - @inject(IDisposableRegistry) private disposableRegistry: IDisposableRegistry, - @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, - @inject(IComponentAdapter) private readonly pyenvs: IComponent, - @inject(IInterpreterLocatorService) - @named(WINDOWS_REGISTRY_SERVICE) - @optional() - private registryLookupForConda?: IInterpreterLocatorService, - ) { - this.addCondaPathChangedHandler(); - } - - public get condaEnvironmentsFile(): string | undefined { - const homeDir = this.platform.isWindows ? process.env.USERPROFILE : process.env.HOME || process.env.HOMEPATH; - return homeDir ? path.join(homeDir, '.conda', 'environments.txt') : undefined; - } - - /** - * Release any held resources. - * - * Called by VS Code to indicate it is done with the resource. - */ - // tslint:disable-next-line:no-empty - public dispose() {} - - /** - * Return the path to the "conda file". - */ - public async getCondaFile(): Promise { - if (!this.condaFile) { - this.condaFile = this.getCondaFileImpl(); - } - // tslint:disable-next-line:no-unnecessary-local-variable - const condaFile = await this.condaFile!; - return condaFile!; - } - - /** - * Is there a conda install to use? - */ - public async isCondaAvailable(): Promise { - if (typeof this.isAvailable === 'boolean') { - return this.isAvailable; - } - return this.getCondaVersion() - .then((version) => (this.isAvailable = version !== undefined)) - .catch(() => (this.isAvailable = false)); - } - - /** - * Return the conda version. - * The version info is cached for some time. - * Remember, its possible the user can update the path to `conda` executable in settings.json, - * or environment variables. - * Doing that could change this value. - */ - @cache(120_000) - public async getCondaVersion(): Promise { - const processService = await this.processServiceFactory.create(); - const info = await this.getCondaInfo().catch(() => undefined); - let versionString: string | undefined; - if (info && info.conda_version) { - versionString = info.conda_version; - } else { - const stdOut = await this.getCondaFile() - .then((condaFile) => processService.exec(condaFile, ['--version'], {})) - .then((result) => result.stdout.trim()) - .catch(() => undefined); - - versionString = stdOut && stdOut.startsWith('conda ') ? stdOut.substring('conda '.length).trim() : stdOut; - } - if (!versionString) { - return; - } - const version = parse(versionString, true); - if (version) { - return version; - } - // Use a bogus version, at least to indicate the fact that a version was returned. - traceWarning(`Unable to parse Version of Conda, ${versionString}`); - return new SemVer('0.0.1'); - } - - /** - * Can the shell find conda (to run it)? - */ - public async isCondaInCurrentPath() { - const processService = await this.processServiceFactory.create(); - return processService - .exec('conda', ['--version']) - .then((output) => output.stdout.length > 0) - .catch(() => false); - } - - /** - * Return the info reported by the conda install. - * The result is cached for 30s. - */ - @cache(60_000) - public async getCondaInfo(): Promise { - try { - const condaFile = await this.getCondaFile(); - const processService = await this.processServiceFactory.create(); - const condaInfo = await processService.exec(condaFile, ['info', '--json']).then((output) => output.stdout); - - return JSON.parse(condaInfo) as CondaInfo; - } catch (ex) { - // Failed because either: - // 1. conda is not installed. - // 2. `conda info --json` has changed signature. - } - } - - /** - * Determines whether a python interpreter is a conda environment or not. - * The check is done by simply looking for the 'conda-meta' directory. - * @param {string} interpreterPath - * @returns {Promise} - * @memberof CondaService - */ - public async isCondaEnvironment(interpreterPath: string): Promise { - const result = await this.pyenvs.isCondaEnvironment(interpreterPath); - if (result !== undefined) { - return result; - } - const dir = path.dirname(interpreterPath); - const { isWindows } = this.platform; - const condaMetaDirectory = isWindows ? path.join(dir, 'conda-meta') : path.join(dir, '..', 'conda-meta'); - return this.fileSystem.directoryExists(condaMetaDirectory); - } - - /** - * Return (env name, interpreter filename) for the interpreter. - */ - public async getCondaEnvironment(interpreterPath: string): Promise<{ name: string; path: string } | undefined> { - const found = await this.pyenvs.getCondaEnvironment(interpreterPath); - if (found !== undefined) { - return found; - } - const isCondaEnv = await this.isCondaEnvironment(interpreterPath); - if (!isCondaEnv) { - return; - } - let environments = await this.getCondaEnvironments(false); - const dir = path.dirname(interpreterPath); - - // If interpreter is in bin or Scripts, then go up one level - const subDirName = path.basename(dir); - const goUpOnLevel = ['BIN', 'SCRIPTS'].indexOf(subDirName.toUpperCase()) !== -1; - const interpreterPathToMatch = goUpOnLevel ? path.join(dir, '..') : dir; - - // From the list of conda environments find this dir. - let matchingEnvs = Array.isArray(environments) - ? environments.filter((item) => this.fileSystem.arePathsSame(item.path, interpreterPathToMatch)) - : []; - if (matchingEnvs.length === 0) { - environments = await this.getCondaEnvironments(true); - matchingEnvs = Array.isArray(environments) - ? environments.filter((item) => this.fileSystem.arePathsSame(item.path, interpreterPathToMatch)) - : []; - } - - if (matchingEnvs.length > 0) { - return { name: matchingEnvs[0].name, path: interpreterPathToMatch }; - } - - // If still not available, then the user created the env after starting vs code. - // The only solution is to get the user to re-start vscode. - } - - /** - * Return the list of conda envs (by name, interpreter filename). - */ - @traceDecorators.verbose('Get Conda environments') - public async getCondaEnvironments(ignoreCache: boolean): Promise { - // Global cache. - const globalPersistence = this.persistentStateFactory.createGlobalPersistentState<{ - data: CondaEnvironmentInfo[] | undefined; - // tslint:disable-next-line:no-any - }>('CONDA_ENVIRONMENTS', undefined as any); - if (!ignoreCache && globalPersistence.value) { - return globalPersistence.value.data; - } - - try { - const condaFile = await this.getCondaFile(); - const processService = await this.processServiceFactory.create(); - let envInfo = await processService.exec(condaFile, ['env', 'list']).then((output) => output.stdout); - traceVerbose(`Conda Env List ${envInfo}}`); - if (!envInfo) { - traceVerbose('Conda env list failure, attempting path additions.'); - // Try adding different folders to the path. Miniconda fails to run - // without them. - const baseFolder = path.dirname(path.dirname(condaFile)); - const binFolder = path.join(baseFolder, 'bin'); - const condaBinFolder = path.join(baseFolder, 'condabin'); - const libaryBinFolder = path.join(baseFolder, 'library', 'bin'); - const newEnv = process.env; - newEnv.PATH = `${binFolder};${condaBinFolder};${libaryBinFolder};${newEnv.PATH}`; - traceVerbose(`Attempting new path for conda env list: ${newEnv.PATH}`); - envInfo = await processService - .exec(condaFile, ['env', 'list'], { env: newEnv }) - .then((output) => output.stdout); - } - const environments = parseCondaEnvFileContents(envInfo); - await globalPersistence.updateValue({ data: environments }); - return environments; - } catch (ex) { - await globalPersistence.updateValue({ data: undefined }); - // Failed because either: - // 1. conda is not installed. - // 2. `conda env list has changed signature. - traceError('Failed to get conda environment list from conda', ex); - } - } - - /** - * Return the interpreter's filename for the given environment. - */ - public getInterpreterPath(condaEnvironmentPath: string): string { - // where to find the Python binary within a conda env. - const relativePath = this.platform.isWindows ? 'python.exe' : path.join('bin', 'python'); - return path.join(condaEnvironmentPath, relativePath); - } - - /** - * Get the conda exe from the path to an interpreter's python. This might be different than the globally registered conda.exe - * The value is cached for a while. - * The only way this can change is if user installs conda into this same environment. - * Generally we expect that to happen the other way, the user creates a conda environment with conda in it. - */ - @traceDecorators.verbose('Get Conda File from interpreter') - @cache(120_000) - public async getCondaFileFromInterpreter(interpreterPath?: string, envName?: string): Promise { - const condaExe = this.platform.isWindows ? 'conda.exe' : 'conda'; - const scriptsDir = this.platform.isWindows ? 'Scripts' : 'bin'; - const interpreterDir = interpreterPath ? path.dirname(interpreterPath) : ''; - - // Might be in a situation where this is not the default python env, but rather one running - // from a virtualenv - const envsPos = envName ? interpreterDir.indexOf(path.join('envs', envName)) : -1; - if (envsPos > 0) { - // This should be where the original python was run from when the environment was created. - const originalPath = interpreterDir.slice(0, envsPos); - let condaPath1 = path.join(originalPath, condaExe); - - if (await this.fileSystem.fileExists(condaPath1)) { - return condaPath1; - } - - // Also look in the scripts directory here too. - condaPath1 = path.join(originalPath, scriptsDir, condaExe); - if (await this.fileSystem.fileExists(condaPath1)) { - return condaPath1; - } - } - - let condaPath2 = path.join(interpreterDir, condaExe); - if (await this.fileSystem.fileExists(condaPath2)) { - return condaPath2; - } - // Conda path has changed locations, check the new location in the scripts directory after checking - // the old location - condaPath2 = path.join(interpreterDir, scriptsDir, condaExe); - if (await this.fileSystem.fileExists(condaPath2)) { - return condaPath2; - } - } - - /** - * Is the given interpreter from conda? - */ - private detectCondaEnvironment(env: PythonEnvironment) { - return ( - env.envType === EnvironmentType.Conda - || (env.displayName ? env.displayName : '').toUpperCase().indexOf('ANACONDA') >= 0 - || (env.companyDisplayName ? env.companyDisplayName : '').toUpperCase().indexOf('ANACONDA') >= 0 - || (env.companyDisplayName ? env.companyDisplayName : '').toUpperCase().indexOf('CONTINUUM') >= 0 - ); - } - - /** - * Return the highest Python version from the given list. - */ - private getLatestVersion(interpreters: PythonEnvironment[]) { - const sortedInterpreters = interpreters.slice(); - // tslint:disable-next-line:no-non-null-assertion - sortedInterpreters.sort((a, b) => (a.version && b.version ? compare(a.version.raw, b.version.raw) : 0)); - if (sortedInterpreters.length > 0) { - return sortedInterpreters[sortedInterpreters.length - 1]; - } - } - - private addCondaPathChangedHandler() { - const disposable = this.workspaceService.onDidChangeConfiguration(this.onDidChangeConfiguration.bind(this)); - this.disposableRegistry.push(disposable); - } - - private async onDidChangeConfiguration(event: ConfigurationChangeEvent) { - const workspacesUris: (Uri | undefined)[] = this.workspaceService.hasWorkspaceFolders - ? this.workspaceService.workspaceFolders!.map((workspace) => workspace.uri) - : [undefined]; - if (workspacesUris.findIndex((uri) => event.affectsConfiguration('python.condaPath', uri)) === -1) { - return; - } - this.condaFile = undefined; - } - - /** - * Return the path to the "conda file", if there is one (in known locations). - */ - private async getCondaFileImpl() { - const settings = this.configService.getSettings(); - - const setting = settings.condaPath; - if (setting && setting !== '') { - return setting; - } - - const isAvailable = await this.isCondaInCurrentPath(); - if (isAvailable) { - return 'conda'; - } - if (this.platform.isWindows && this.registryLookupForConda) { - const interpreters = await this.registryLookupForConda.getInterpreters(); - const condaInterpreters = interpreters.filter(this.detectCondaEnvironment); - const condaInterpreter = this.getLatestVersion(condaInterpreters); - if (condaInterpreter) { - const interpreterPath = await this.getCondaFileFromInterpreter( - condaInterpreter.path, - condaInterpreter.envName, - ); - if (interpreterPath) { - return interpreterPath; - } - } - } - return this.getCondaFileFromKnownLocations(); - } - - /** - * Return the path to the "conda file", if there is one (in known locations). - * Note: For now we simply return the first one found. - */ - private async getCondaFileFromKnownLocations(): Promise { - const globPattern = this.platform.isWindows ? CondaLocationsGlobWin : CondaLocationsGlob; - const condaFiles = await this.fileSystem.search(globPattern).catch((failReason) => { - traceWarning( - 'Default conda location search failed.', - `Searching for default install locations for conda results in error: ${failReason}`, - ); - return []; - }); - const validCondaFiles = condaFiles.filter((condaPath) => condaPath.length > 0); - return validCondaFiles.length === 0 ? 'conda' : validCondaFiles[0]; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/currentPathService.ts b/src/client/pythonEnvironments/discovery/locators/services/currentPathService.ts deleted file mode 100644 index 0a2467aa9885..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/currentPathService.ts +++ /dev/null @@ -1,143 +0,0 @@ -// tslint:disable:no-require-imports no-var-requires underscore-consistent-invocation no-unnecessary-callback-wrapper -import { inject, injectable } from 'inversify'; -import { Uri } from 'vscode'; -import { traceError, traceInfo } from '../../../../common/logger'; -import { IFileSystem, IPlatformService } from '../../../../common/platform/types'; -import * as internalPython from '../../../../common/process/internal/python'; -import { IProcessServiceFactory } from '../../../../common/process/types'; -import { IConfigurationService } from '../../../../common/types'; -import { OSType } from '../../../../common/utils/platform'; -import { IInterpreterHelper } from '../../../../interpreter/contracts'; -import { IPythonInPathCommandProvider } from '../../../../interpreter/locators/types'; -import { IServiceContainer } from '../../../../ioc/types'; -import { EnvironmentType, PythonEnvironment } from '../../../info'; -import { CacheableLocatorService } from './cacheableLocatorService'; - -/** - * Locates the currently configured Python interpreter. - * - * If no interpreter is configured then it falls back to the system - * Python (3 then 2). - */ -@injectable() -export class CurrentPathService extends CacheableLocatorService { - private readonly fs: IFileSystem; - - public constructor( - @inject(IInterpreterHelper) private helper: IInterpreterHelper, - @inject(IProcessServiceFactory) private readonly processServiceFactory: IProcessServiceFactory, - @inject(IPythonInPathCommandProvider) private readonly pythonCommandProvider: IPythonInPathCommandProvider, - @inject(IServiceContainer) serviceContainer: IServiceContainer, - ) { - super('CurrentPathService', serviceContainer); - this.fs = serviceContainer.get(IFileSystem); - } - - /** - * Release any held resources. - * - * Called by VS Code to indicate it is done with the resource. - */ - // tslint:disable-next-line:no-empty - public dispose() {} - - /** - * Return the located interpreters. - * - * This is used by CacheableLocatorService.getInterpreters(). - */ - protected getInterpretersImplementation(resource?: Uri): Promise { - return this.suggestionsFromKnownPaths(resource); - } - - /** - * Return the located interpreters. - */ - private async suggestionsFromKnownPaths(resource?: Uri) { - const configSettings = this.serviceContainer - .get(IConfigurationService) - .getSettings(resource); - const pathsToCheck = [...this.pythonCommandProvider.getCommands(), { command: configSettings.pythonPath }]; - - const pythonPaths = Promise.all(pathsToCheck.map((item) => this.getInterpreter(item))); - return ( - pythonPaths - .then((interpreters) => interpreters.filter((item) => item.length > 0)) - // tslint:disable-next-line:promise-function-async - .then((interpreters) => Promise.all( - interpreters.map((interpreter) => this.getInterpreterDetails(interpreter)), - )) - .then((interpreters) => interpreters.filter((item) => !!item).map((item) => item!)) - ); - } - - /** - * Return the information about the identified interpreter binary. - */ - private async getInterpreterDetails(pythonPath: string): Promise { - return this.helper.getInterpreterInformation(pythonPath).then((details) => { - if (!details) { - return; - } - this._hasInterpreters.resolve(true); - return { - ...(details as PythonEnvironment), - path: pythonPath, - envType: details.envType ? details.envType : EnvironmentType.Unknown, - }; - }); - } - - /** - * Return the path to the interpreter (or the default if not found). - */ - private async getInterpreter(options: { command: string; args?: string[] }) { - try { - const processService = await this.processServiceFactory.create(); - const pyArgs = Array.isArray(options.args) ? options.args : []; - const [args, parse] = internalPython.getExecutable(); - return processService - .exec(options.command, pyArgs.concat(args), {}) - .then((output) => parse(output.stdout)) - .then(async (value) => { - if (value.length > 0 && (await this.fs.fileExists(value))) { - return value; - } - traceError( - `Detection of Python Interpreter for Command ${options.command} and args ${pyArgs.join( - ' ', - )} failed as file ${value} does not exist`, - ); - return ''; - }) - .catch((_ex) => { - traceInfo( - `Detection of Python Interpreter for Command ${options.command} and args ${pyArgs.join( - ' ', - )} failed`, - ); - return ''; - }); // Ignore exceptions in getting the executable. - } catch (ex) { - traceError(`Detection of Python Interpreter for Command ${options.command} failed`, ex); - return ''; // Ignore exceptions in getting the executable. - } - } -} - -@injectable() -export class PythonInPathCommandProvider implements IPythonInPathCommandProvider { - constructor(@inject(IPlatformService) private readonly platform: IPlatformService) {} - - public getCommands(): { command: string; args?: string[] }[] { - const paths = ['python3.7', 'python3.6', 'python3', 'python2', 'python'].map((item) => ({ command: item })); - if (this.platform.osType !== OSType.Windows) { - return paths; - } - - const versions = ['3.7', '3.6', '3', '2']; - return paths.concat( - versions.map((version) => ({ command: 'py', args: [`-${version}`] })), - ); - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/globalVirtualEnvService.ts b/src/client/pythonEnvironments/discovery/locators/services/globalVirtualEnvService.ts deleted file mode 100644 index 77d6a20618e8..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/globalVirtualEnvService.ts +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable, named } from 'inversify'; -import * as os from 'os'; -import * as path from 'path'; -import { Uri } from 'vscode'; -import { IConfigurationService, ICurrentProcess } from '../../../../common/types'; -import { IVirtualEnvironmentsSearchPathProvider } from '../../../../interpreter/contracts'; -import { IVirtualEnvironmentManager } from '../../../../interpreter/virtualEnvs/types'; -import { IServiceContainer } from '../../../../ioc/types'; -import { BaseVirtualEnvService } from './baseVirtualEnvService'; - -// tslint:disable-next-line:no-require-imports no-var-requires -const untildify: (value: string) => string = require('untildify'); - -@injectable() -export class GlobalVirtualEnvService extends BaseVirtualEnvService { - public constructor( - @inject(IVirtualEnvironmentsSearchPathProvider) - @named('global') - globalVirtualEnvPathProvider: IVirtualEnvironmentsSearchPathProvider, - @inject(IServiceContainer) serviceContainer: IServiceContainer, - ) { - super(globalVirtualEnvPathProvider, serviceContainer, 'VirtualEnvService'); - } -} - -@injectable() -export class GlobalVirtualEnvironmentsSearchPathProvider implements IVirtualEnvironmentsSearchPathProvider { - private readonly config: IConfigurationService; - - private readonly currentProcess: ICurrentProcess; - - private readonly virtualEnvMgr: IVirtualEnvironmentManager; - - constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - this.config = serviceContainer.get(IConfigurationService); - this.virtualEnvMgr = serviceContainer.get(IVirtualEnvironmentManager); - this.currentProcess = serviceContainer.get(ICurrentProcess); - } - - public async getSearchPaths(resource?: Uri): Promise { - const homedir = os.homedir(); - const venvFolders = [ - 'envs', - '.pyenv', - '.direnv', - '.virtualenvs', - ...this.config.getSettings(resource).venvFolders, - ]; - const folders = [...new Set(venvFolders.map((item) => path.join(homedir, item)))]; - - // Add support for the WORKON_HOME environment variable used by pipenv and virtualenvwrapper. - const workonHomePath = this.currentProcess.env.WORKON_HOME; - if (workonHomePath) { - folders.push(untildify(workonHomePath)); - } - - // tslint:disable-next-line:no-string-literal - const pyenvRoot = await this.virtualEnvMgr.getPyEnvRoot(resource); - if (pyenvRoot) { - folders.push(pyenvRoot); - folders.push(path.join(pyenvRoot, 'versions')); - } - return folders; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/hashProvider.ts b/src/client/pythonEnvironments/discovery/locators/services/hashProvider.ts deleted file mode 100644 index de9aba8a1e00..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/hashProvider.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IFileSystem } from '../../../../common/platform/types'; -import { IInterpreterHashProvider } from '../../../../interpreter/locators/types'; - -@injectable() -export class InterpreterHashProvider implements IInterpreterHashProvider { - constructor(@inject(IFileSystem) private readonly fs: IFileSystem) {} - - public async getInterpreterHash(pythonPath: string): Promise { - return this.fs.getFileHash(pythonPath); - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/hashProviderFactory.ts b/src/client/pythonEnvironments/discovery/locators/services/hashProviderFactory.ts deleted file mode 100644 index c67d13d9cd2a..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/hashProviderFactory.ts +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { Uri } from 'vscode'; -import { IConfigurationService } from '../../../../common/types'; -import { - IInterpreterHashProvider, - IInterpreterHashProviderFactory, - IWindowsStoreInterpreter, -} from '../../../../interpreter/locators/types'; -import { InterpreterHashProvider } from './hashProvider'; -import { WindowsStoreInterpreter } from './windowsStoreInterpreter'; - -@injectable() -export class InterpeterHashProviderFactory implements IInterpreterHashProviderFactory { - constructor( - @inject(IConfigurationService) private readonly configService: IConfigurationService, - @inject(WindowsStoreInterpreter) private readonly windowsStoreInterpreter: IWindowsStoreInterpreter, - @inject(WindowsStoreInterpreter) private readonly windowsStoreHashProvider: IInterpreterHashProvider, - @inject(InterpreterHashProvider) private readonly hashProvider: IInterpreterHashProvider, - ) {} - - public async create(options: { pythonPath: string } | { resource: Uri }): Promise { - const pythonPath = 'pythonPath' in options - ? options.pythonPath - : this.configService.getSettings(options.resource).pythonPath; - - return (await this.windowsStoreInterpreter.isWindowsStoreInterpreter(pythonPath)) - ? this.windowsStoreHashProvider - : this.hashProvider; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/interpreterFilter.ts b/src/client/pythonEnvironments/discovery/locators/services/interpreterFilter.ts deleted file mode 100644 index 84097aff3c44..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/interpreterFilter.ts +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { PythonEnvironment } from '../../../info'; -import { isRestrictedWindowsStoreInterpreterPath } from './windowsStoreInterpreter'; - -export function isHiddenInterpreter(interpreter: PythonEnvironment): boolean { - // Any set of rules to hide interpreters should go here - return isRestrictedWindowsStoreInterpreterPath(interpreter.path); -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/interpreterWatcherBuilder.ts b/src/client/pythonEnvironments/discovery/locators/services/interpreterWatcherBuilder.ts deleted file mode 100644 index aa9756f26ee0..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/interpreterWatcherBuilder.ts +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { Uri } from 'vscode'; -import { IWorkspaceService } from '../../../../common/application/types'; -import { traceDecorators } from '../../../../common/logger'; -import { createDeferred } from '../../../../common/utils/async'; -import { - IInterpreterWatcher, - IInterpreterWatcherBuilder, - WORKSPACE_VIRTUAL_ENV_SERVICE, -} from '../../../../interpreter/contracts'; -import { IServiceContainer } from '../../../../ioc/types'; -import { WorkspaceVirtualEnvWatcherService } from './workspaceVirtualEnvWatcherService'; - -@injectable() -export class InterpreterWatcherBuilder implements IInterpreterWatcherBuilder { - private readonly watchersByResource = new Map>(); - - /** - * Creates an instance of InterpreterWatcherBuilder. - * Inject the DI container, as we need to get a new instance of IInterpreterWatcher to build it. - * @param {IWorkspaceService} workspaceService - * @param {IServiceContainer} serviceContainer - * @memberof InterpreterWatcherBuilder - */ - constructor( - @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, - @inject(IServiceContainer) private readonly serviceContainer: IServiceContainer, - ) {} - - @traceDecorators.verbose('Build the workspace interpreter watcher') - public async getWorkspaceVirtualEnvInterpreterWatcher(resource: Uri | undefined): Promise { - const key = this.getResourceKey(resource); - if (!this.watchersByResource.has(key)) { - const deferred = createDeferred(); - this.watchersByResource.set(key, deferred.promise); - const watcher = this.serviceContainer.get( - IInterpreterWatcher, - WORKSPACE_VIRTUAL_ENV_SERVICE, - ); - await watcher.register(resource); - deferred.resolve(watcher); - } - return this.watchersByResource.get(key)!; - } - - protected getResourceKey(resource: Uri | undefined): string { - const workspaceFolder = resource ? this.workspaceService.getWorkspaceFolder(resource) : undefined; - return workspaceFolder ? workspaceFolder.uri.fsPath : ''; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/pipEnvHelper.ts b/src/client/pythonEnvironments/discovery/locators/services/pipEnvHelper.ts deleted file mode 100644 index cf64be974a96..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/pipEnvHelper.ts +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import * as path from 'path'; -import { traceError } from '../../../../common/logger'; -import { getEnvironmentVariable } from '../../../../common/utils/platform'; -import { arePathsSame, pathExists, readFile } from '../../../common/externalDependencies'; - -function getSearchHeight() { - // PIPENV_MAX_DEPTH tells pipenv the maximum number of directories to recursively search for - // a Pipfile, defaults to 3: https://pipenv.pypa.io/en/latest/advanced/#pipenv.environments.PIPENV_MAX_DEPTH - const maxDepthStr = getEnvironmentVariable('PIPENV_MAX_DEPTH'); - if (maxDepthStr === undefined) { - return 3; - } - const maxDepth = parseInt(maxDepthStr, 10); - // eslint-disable-next-line no-restricted-globals - if (isNaN(maxDepth)) { - traceError(`PIPENV_MAX_DEPTH is incorrectly set. Converting value '${maxDepthStr}' to number results in NaN`); - return 1; - } - return maxDepth; -} - -/** - * Returns the path to Pipfile associated with the provided directory. - * @param searchDir the directory to look into - * @param lookIntoParentDirectories set to true if we should also search for Pipfile in parent directory - */ -export async function _getAssociatedPipfile( - searchDir: string, - options: {lookIntoParentDirectories: boolean}, -): Promise { - const pipFileName = getEnvironmentVariable('PIPENV_PIPFILE') || 'Pipfile'; - let heightToSearch = options.lookIntoParentDirectories ? getSearchHeight() : 1; - while (heightToSearch > 0 && !arePathsSame(searchDir, path.dirname(searchDir))) { - const pipFile = path.join(searchDir, pipFileName); - if (await pathExists(pipFile)) { - return pipFile; - } - searchDir = path.dirname(searchDir); - heightToSearch -= 1; - } - return undefined; -} - -/** - * If interpreter path belongs to a pipenv environment which is located inside a project, return associated Pipfile, - * otherwise return `undefined`. - * @param interpreterPath Absolute path to any python interpreter. - */ -async function getPipfileIfLocal(interpreterPath: string): Promise { - // Local pipenv environments are created by setting PIPENV_VENV_IN_PROJECT to 1, which always names the environment - // folder '.venv': https://pipenv.pypa.io/en/latest/advanced/#pipenv.environments.PIPENV_VENV_IN_PROJECT - // This is the layout we wish to verify. - // project - // |__ Pipfile <--- check if Pipfile exists here - // |__ .venv <--- check if name of the folder is '.venv' - // |__ Scripts/bin - // |__ python <--- interpreterPath - const venvFolder = path.dirname(path.dirname(interpreterPath)); - if (path.basename(venvFolder) !== '.venv') { - return undefined; - } - const directoryWhereVenvResides = path.dirname(venvFolder); - return _getAssociatedPipfile(directoryWhereVenvResides, { lookIntoParentDirectories: false }); -} - -/** - * Returns the project directory for pipenv environments given the environment folder - * @param envFolder Path to the environment folder - */ -async function getProjectDir(envFolder: string): Promise { - // Global pipenv environments have a .project file with the absolute path to the project - // See https://github.com/pypa/pipenv/blob/v2018.6.25/CHANGELOG.rst#features--improvements - // This is the layout we expect - // - // |__ .project <--- check if .project exists here - // |__ Scripts/bin - // |__ python <--- interpreterPath - // We get the project by reading the .project file - const dotProjectFile = path.join(envFolder, '.project'); - if (!(await pathExists(dotProjectFile))) { - return undefined; - } - const projectDir = await readFile(dotProjectFile); - if (!(await pathExists(projectDir))) { - traceError(`The .project file inside environment folder: ${envFolder} doesn't contain a valid path to the project`); - return undefined; - } - return projectDir; -} - -/** - * If interpreter path belongs to a global pipenv environment, return associated Pipfile, otherwise return `undefined`. - * @param interpreterPath Absolute path to any python interpreter. - */ -async function getPipfileIfGlobal(interpreterPath: string): Promise { - const envFolder = path.dirname(path.dirname(interpreterPath)); - const projectDir = await getProjectDir(envFolder); - if (projectDir === undefined) { - return undefined; - } - - // This is the layout we expect to see. - // project - // |__ Pipfile <--- check if Pipfile exists here and return it - // The name of the project (directory where Pipfile resides) is used as a prefix in the environment folder - const envFolderName = path.basename(envFolder); - if (!envFolderName.startsWith(`${path.basename(projectDir)}-`)) { - return undefined; - } - - return _getAssociatedPipfile(projectDir, { lookIntoParentDirectories: false }); -} - -/** - * Checks if the given interpreter path belongs to a pipenv environment, by locating the Pipfile which was used to - * create the environment. - * @param interpreterPath: Absolute path to any python interpreter. - */ -export async function isPipenvEnvironment(interpreterPath: string): Promise { - if (await getPipfileIfLocal(interpreterPath)) { - return true; - } - if (await getPipfileIfGlobal(interpreterPath)) { - return true; - } - return false; -} - -/** - * Returns true if interpreter path belongs to a global pipenv environment which is associated with a particular folder, - * false otherwise. - * @param interpreterPath Absolute path to any python interpreter. - */ -export async function isPipenvEnvironmentRelatedToFolder(interpreterPath: string, folder: string): Promise { - const pipFileAssociatedWithEnvironment = await getPipfileIfGlobal(interpreterPath); - if (!pipFileAssociatedWithEnvironment) { - return false; - } - - // PIPENV_NO_INHERIT is used to tell pipenv not to look for Pipfile in parent directories - // https://pipenv.pypa.io/en/latest/advanced/#pipenv.environments.PIPENV_NO_INHERIT - const lookIntoParentDirectories = (getEnvironmentVariable('PIPENV_NO_INHERIT') === undefined); - const pipFileAssociatedWithFolder = await _getAssociatedPipfile(folder, { lookIntoParentDirectories }); - if (!pipFileAssociatedWithFolder) { - return false; - } - return arePathsSame(pipFileAssociatedWithEnvironment, pipFileAssociatedWithFolder); -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/pipEnvService.ts b/src/client/pythonEnvironments/discovery/locators/services/pipEnvService.ts deleted file mode 100644 index 8a5c23c43109..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/pipEnvService.ts +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { Uri } from 'vscode'; -import { IApplicationShell, IWorkspaceService } from '../../../../common/application/types'; -import { traceError, traceWarning } from '../../../../common/logger'; -import { IFileSystem, IPlatformService } from '../../../../common/platform/types'; -import { IProcessServiceFactory } from '../../../../common/process/types'; -import { IConfigurationService, ICurrentProcess } from '../../../../common/types'; -import { StopWatch } from '../../../../common/utils/stopWatch'; -import { IInterpreterHelper, IPipEnvService } from '../../../../interpreter/contracts'; -import { IPipEnvServiceHelper } from '../../../../interpreter/locators/types'; -import { IServiceContainer } from '../../../../ioc/types'; -import { sendTelemetryEvent } from '../../../../telemetry'; -import { EventName } from '../../../../telemetry/constants'; -import { EnvironmentType, PythonEnvironment } from '../../../info'; -import { GetInterpreterLocatorOptions } from '../types'; -import { CacheableLocatorService } from './cacheableLocatorService'; - -const pipEnvFileNameVariable = 'PIPENV_PIPFILE'; - -@injectable() -export class PipEnvService extends CacheableLocatorService implements IPipEnvService { - private readonly helper: IInterpreterHelper; - - private readonly processServiceFactory: IProcessServiceFactory; - - private readonly workspace: IWorkspaceService; - - private readonly fs: IFileSystem; - - private readonly configService: IConfigurationService; - - private readonly pipEnvServiceHelper: IPipEnvServiceHelper; - - constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - super('PipEnvService', serviceContainer, true); - this.helper = this.serviceContainer.get(IInterpreterHelper); - this.processServiceFactory = this.serviceContainer.get(IProcessServiceFactory); - this.workspace = this.serviceContainer.get(IWorkspaceService); - this.fs = this.serviceContainer.get(IFileSystem); - this.configService = this.serviceContainer.get(IConfigurationService); - this.pipEnvServiceHelper = this.serviceContainer.get(IPipEnvServiceHelper); - } - - // tslint:disable-next-line:no-empty - public dispose() {} - - public async isRelatedPipEnvironment(dir: string, pythonPath: string): Promise { - if (!this.didTriggerInterpreterSuggestions) { - return false; - } - - // In PipEnv, the name of the cwd is used as a prefix in the virtual env. - if (pythonPath.indexOf(`${path.sep}${path.basename(dir)}-`) === -1) { - return false; - } - const envName = await this.getInterpreterPathFromPipenv(dir, true); - return !!envName; - } - - public get executable(): string { - return this.didTriggerInterpreterSuggestions ? this.configService.getSettings().pipenvPath : ''; - } - - public async getInterpreters(resource?: Uri, options?: GetInterpreterLocatorOptions): Promise { - if (!this.didTriggerInterpreterSuggestions) { - return []; - } - - const stopwatch = new StopWatch(); - const startDiscoveryTime = stopwatch.elapsedTime; - - const interpreters = await super.getInterpreters(resource, options); - - const discoveryDuration = stopwatch.elapsedTime - startDiscoveryTime; - sendTelemetryEvent(EventName.PIPENV_INTERPRETER_DISCOVERY, discoveryDuration); - - return interpreters; - } - - protected getInterpretersImplementation(resource?: Uri): Promise { - if (!this.didTriggerInterpreterSuggestions) { - return Promise.resolve([]); - } - - const pipenvCwd = this.getPipenvWorkingDirectory(resource); - if (!pipenvCwd) { - return Promise.resolve([]); - } - - return this.getInterpreterFromPipenv(pipenvCwd) - .then((item) => (item ? [item] : [])) - .catch(() => []); - } - - private async getInterpreterFromPipenv(pipenvCwd: string): Promise { - const interpreterPath = await this.getInterpreterPathFromPipenv(pipenvCwd); - if (!interpreterPath) { - return; - } - - const details = await this.helper.getInterpreterInformation(interpreterPath); - if (!details) { - return; - } - this._hasInterpreters.resolve(true); - await this.pipEnvServiceHelper.trackWorkspaceFolder(interpreterPath, Uri.file(pipenvCwd)); - return { - ...(details as PythonEnvironment), - path: interpreterPath, - envType: EnvironmentType.Pipenv, - pipEnvWorkspaceFolder: pipenvCwd, - }; - } - - private getPipenvWorkingDirectory(resource?: Uri): string | undefined { - // The file is not in a workspace. However, workspace may be opened - // and file is just a random file opened from elsewhere. In this case - // we still want to provide interpreter associated with the workspace. - // Otherwise if user tries and formats the file, we may end up using - // plain pip module installer to bring in the formatter and it is wrong. - const wsFolder = resource ? this.workspace.getWorkspaceFolder(resource) : undefined; - return wsFolder ? wsFolder.uri.fsPath : this.workspace.rootPath; - } - - private async getInterpreterPathFromPipenv(cwd: string, ignoreErrors = false): Promise { - // Quick check before actually running pipenv - if (!(await this.checkIfPipFileExists(cwd))) { - return; - } - try { - // call pipenv --version just to see if pipenv is in the PATH - const version = await this.invokePipenv('--version', cwd); - if (version === undefined) { - const appShell = this.serviceContainer.get(IApplicationShell); - appShell.showWarningMessage( - `Workspace contains Pipfile but '${this.executable}' was not found. Make sure '${this.executable}' is on the PATH.`, - ); - return; - } - // The --py command will fail if the virtual environment has not been setup yet. - // so call pipenv --venv to check for the virtual environment first. - const venv = await this.invokePipenv('--venv', cwd); - if (venv === undefined) { - const appShell = this.serviceContainer.get(IApplicationShell); - appShell.showWarningMessage( - 'Workspace contains Pipfile but the associated virtual environment has not been setup. Setup the virtual environment manually if needed.', - ); - return; - } - const pythonPath = await this.invokePipenv('--py', cwd); - return pythonPath && (await this.fs.fileExists(pythonPath)) ? pythonPath : undefined; - // tslint:disable-next-line:no-empty - } catch (error) { - traceError('PipEnv identification failed', error); - if (ignoreErrors) { - return undefined; - } - } - } - - private async checkIfPipFileExists(cwd: string): Promise { - const currentProcess = this.serviceContainer.get(ICurrentProcess); - const pipFileName = currentProcess.env[pipEnvFileNameVariable]; - if (typeof pipFileName === 'string' && (await this.fs.fileExists(path.join(cwd, pipFileName)))) { - return true; - } - if (await this.fs.fileExists(path.join(cwd, 'Pipfile'))) { - return true; - } - return false; - } - - private async invokePipenv(arg: string, rootPath: string): Promise { - try { - const processService = await this.processServiceFactory.create(Uri.file(rootPath)); - const execName = this.executable; - const result = await processService.exec(execName, [arg], { cwd: rootPath }); - if (result) { - const stdout = result.stdout ? result.stdout.trim() : ''; - const stderr = result.stderr ? result.stderr.trim() : ''; - if (stderr.length > 0 && stdout.length === 0) { - throw new Error(stderr); - } - return stdout; - } - // tslint:disable-next-line:no-empty - } catch (error) { - const platformService = this.serviceContainer.get(IPlatformService); - const currentProc = this.serviceContainer.get(ICurrentProcess); - const enviromentVariableValues: Record = { - LC_ALL: currentProc.env.LC_ALL, - LANG: currentProc.env.LANG, - }; - enviromentVariableValues[platformService.pathVariableName] = currentProc.env[platformService.pathVariableName]; - - traceWarning('Error in invoking PipEnv', error); - traceWarning(`Relevant Environment Variables ${JSON.stringify(enviromentVariableValues, undefined, 4)}`); - } - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/pipEnvServiceHelper.ts b/src/client/pythonEnvironments/discovery/locators/services/pipEnvServiceHelper.ts deleted file mode 100644 index 56acffb936be..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/pipEnvServiceHelper.ts +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { Uri } from 'vscode'; -import { IFileSystem } from '../../../../common/platform/types'; -import { IPersistentState, IPersistentStateFactory } from '../../../../common/types'; -import { IPipEnvServiceHelper } from '../../../../interpreter/locators/types'; - -type PipEnvInformation = { pythonPath: string; workspaceFolder: string; envName: string }; -@injectable() -export class PipEnvServiceHelper implements IPipEnvServiceHelper { - private initialized = false; - - private readonly state: IPersistentState>; - - constructor( - @inject(IPersistentStateFactory) private readonly statefactory: IPersistentStateFactory, - @inject(IFileSystem) private readonly fs: IFileSystem, - ) { - this.state = this.statefactory.createGlobalPersistentState>( - 'PipEnvInformation', - [], - ); - } - - public async getPipEnvInfo(pythonPath: string): Promise<{ workspaceFolder: Uri; envName: string } | undefined> { - await this.initializeStateStore(); - const info = this.state.value.find((item) => this.fs.arePathsSame(item.pythonPath, pythonPath)); - return info ? { workspaceFolder: Uri.file(info.workspaceFolder), envName: info.envName } : undefined; - } - - public async trackWorkspaceFolder(pythonPath: string, workspaceFolder: Uri): Promise { - await this.initializeStateStore(); - const values = [...this.state.value].filter((item) => !this.fs.arePathsSame(item.pythonPath, pythonPath)); - const envName = path.basename(workspaceFolder.fsPath); - values.push({ pythonPath, workspaceFolder: workspaceFolder.fsPath, envName }); - await this.state.updateValue(values); - } - - protected async initializeStateStore() { - if (this.initialized) { - return; - } - const list = await Promise.all( - this.state.value.map(async (item) => ((await this.fs.fileExists(item.pythonPath)) ? item : undefined)), - ); - const filteredList = list.filter((item) => !!item) as PipEnvInformation[]; - await this.state.updateValue(filteredList); - this.initialized = true; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/posixKnownPathsLocator.ts b/src/client/pythonEnvironments/discovery/locators/services/posixKnownPathsLocator.ts deleted file mode 100644 index 5049c4e06156..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/posixKnownPathsLocator.ts +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import * as fsapi from 'fs-extra'; -import * as path from 'path'; -import { traceError, traceInfo } from '../../../../common/logger'; - -import { Architecture } from '../../../../common/utils/platform'; -import { - PythonEnvInfo, PythonEnvKind, PythonReleaseLevel, PythonVersion, -} from '../../../base/info'; -import { parseVersion } from '../../../base/info/pythonVersion'; -import { ILocator, IPythonEnvsIterator } from '../../../base/locator'; -import { PythonEnvsWatcher } from '../../../base/watcher'; -import { getFileInfo, resolveSymbolicLink } from '../../../common/externalDependencies'; -import { commonPosixBinPaths, isPosixPythonBin } from '../../../common/posixUtils'; - -async function getPythonBinFromKnownPaths(): Promise { - const knownPaths = await commonPosixBinPaths(); - const pythonBins:Set = new Set(); - for (const knownPath of knownPaths) { - const files = (await fsapi.readdir(knownPath)) - .map((filename:string) => path.join(knownPath, filename)) - .filter(isPosixPythonBin); - - for (const file of files) { - // Ensure that we have a collection of unique global binaries by - // resolving all symlinks to the target binaries. - try { - const resolvedBin = await resolveSymbolicLink(file); - pythonBins.add(resolvedBin); - traceInfo(`Found: ${file} --> ${resolvedBin}`); - } catch (ex) { - traceError('Failed to resolve symbolic link: ', ex); - } - } - } - - return Array.from(pythonBins); -} - -export class PosixKnownPathsLocator extends PythonEnvsWatcher implements ILocator { - private kind: PythonEnvKind = PythonEnvKind.OtherGlobal; - - public iterEnvs(): IPythonEnvsIterator { - const buildEnvInfo = (bin:string) => this.buildEnvInfo(bin); - const iterator = async function* () { - const exes = await getPythonBinFromKnownPaths(); - yield* exes.map(buildEnvInfo); - }; - return iterator(); - } - - public resolveEnv(env: string | PythonEnvInfo): Promise { - const executablePath = typeof env === 'string' ? env : env.executable.filename; - return this.buildEnvInfo(executablePath); - } - - private async buildEnvInfo(bin:string): Promise { - let version:PythonVersion; - try { - version = parseVersion(path.basename(bin)); - } catch (ex) { - traceError(`Failed to parse version from path: ${bin}`, ex); - version = { - major: -1, - minor: -1, - micro: -1, - release: { level: PythonReleaseLevel.Final, serial: -1 }, - sysVersion: undefined, - }; - } - return { - name: '', - location: '', - kind: this.kind, - executable: { - filename: bin, - sysPrefix: '', - ...(await getFileInfo(bin)), - }, - version, - arch: Architecture.Unknown, - distro: { org: '' }, - }; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/pyenvLocator.ts b/src/client/pythonEnvironments/discovery/locators/services/pyenvLocator.ts deleted file mode 100644 index 0e79a29f4bfe..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/pyenvLocator.ts +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import * as path from 'path'; -import { - getEnvironmentVariable, getOSType, getUserHomeDir, OSType, -} from '../../../../common/utils/platform'; -import { pathExists } from '../../../common/externalDependencies'; - -/** - * Checks if the given interpreter belongs to a pyenv based environment. - * @param {string} interpreterPath: Absolute path to the python interpreter. - * @returns {boolean}: Returns true if the interpreter belongs to a pyenv environment. - */ -export async function isPyenvEnvironment(interpreterPath:string): Promise { - // Check if the pyenv environment variables exist: PYENV on Windows, PYENV_ROOT on Unix. - // They contain the path to pyenv's installation folder. - // If they don't exist, use the default path: ~/.pyenv/pyenv-win on Windows, ~/.pyenv on Unix. - // If the interpreter path starts with the path to the pyenv folder, then it is a pyenv environment. - // See https://github.com/pyenv/pyenv#locating-the-python-installation for general usage, - // And https://github.com/pyenv-win/pyenv-win for Windows specifics. - const isWindows = getOSType() === OSType.Windows; - const envVariable = isWindows ? 'PYENV' : 'PYENV_ROOT'; - - let pyenvDir = getEnvironmentVariable(envVariable); - let pathToCheck = interpreterPath; - - if (!pyenvDir) { - const homeDir = getUserHomeDir() || ''; - pyenvDir = isWindows ? path.join(homeDir, '.pyenv', 'pyenv-win') : path.join(homeDir, '.pyenv'); - } - - if (!await pathExists(pyenvDir)) { - return false; - } - - if (!pyenvDir.endsWith(path.sep)) { - pyenvDir += path.sep; - } - - if (getOSType() === OSType.Windows) { - pyenvDir = pyenvDir.toUpperCase(); - pathToCheck = pathToCheck.toUpperCase(); - } - - return pathToCheck.startsWith(pyenvDir); -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/venvLocator.ts b/src/client/pythonEnvironments/discovery/locators/services/venvLocator.ts deleted file mode 100644 index ea3e6a859ba0..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/venvLocator.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import * as path from 'path'; -import { pathExists } from '../../../common/externalDependencies'; - - -/** - * Checks if the given interpreter belongs to a venv based environment. - * @param {string} interpreterPath: Absolute path to the python interpreter. - * @returns {boolean} : Returns true if the interpreter belongs to a venv environment. - */ -export async function isVenvEnvironment(interpreterPath:string): Promise{ - const pyvenvConfigFile = 'pyvenv.cfg'; - - // Check if the pyvenv.cfg file is in the directory as the interpreter. - // env - // |__ pyvenv.cfg <--- check if this file exists - // |__ python <--- interpreterPath - const venvPath1 = path.join(path.dirname(interpreterPath), pyvenvConfigFile); - - // Check if the pyvenv.cfg file is in the parent directory relative to the interpreter. - // env - // |__ pyvenv.cfg <--- check if this file exists - // |__ bin or Scripts - // |__ python <--- interpreterPath - const venvPath2 = path.join(path.dirname(path.dirname(interpreterPath)), pyvenvConfigFile); - - return [await pathExists(venvPath1), await pathExists(venvPath2)].includes(true); -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/virtualenvLocator.ts b/src/client/pythonEnvironments/discovery/locators/services/virtualenvLocator.ts deleted file mode 100644 index 056fd3a97dab..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/virtualenvLocator.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import * as fsapi from 'fs-extra'; -import * as path from 'path'; - -/** - * Checks if the given interpreter belongs to a virtualenv based environment. - * @param {string} interpreterPath: Absolute path to the python interpreter. - * @returns {boolean} : Returns true if the interpreter belongs to a virtualenv environment. - */ -export async function isVirtualenvEnvironment(interpreterPath:string): Promise { - // Check if there are any activate.* files in the same directory as the interpreter. - // - // env - // |__ activate, activate.* <--- check if any of these files exist - // |__ python <--- interpreterPath - const directory = path.dirname(interpreterPath); - const files = await fsapi.readdir(directory); - const regex = /^activate(\.([A-z]|\d)+)?$/; - - return files.find((file) => regex.test(file)) !== undefined; -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/virtualenvwrapperLocator.ts b/src/client/pythonEnvironments/discovery/locators/services/virtualenvwrapperLocator.ts deleted file mode 100644 index 8e89c7ad7e46..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/virtualenvwrapperLocator.ts +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import * as path from 'path'; -import { - getEnvironmentVariable, getOSType, OSType, -} from '../../../../common/utils/platform'; -import { pathExists } from '../../../common/externalDependencies'; -import { getDefaultVirtualenvwrapperDir } from '../../../common/virtualenvwrapperUtils'; - -/** - * Checks if the given interpreter belongs to a virtualenvWrapper based environment. - * @param {string} interpreterPath: Absolute path to the python interpreter. - * @returns {boolean}: Returns true if the interpreter belongs to a virtualenvWrapper environment. - */ -export async function isVirtualenvwrapperEnvironment(interpreterPath:string): Promise { - // The WORKON_HOME variable contains the path to the root directory of all virtualenvwrapper environments. - // If the interpreter path belongs to one of them then it is a virtualenvwrapper type of environment. - const workonHomeDir = getEnvironmentVariable('WORKON_HOME') || getDefaultVirtualenvwrapperDir(); - const environmentName = path.basename(path.dirname(path.dirname(interpreterPath))); - - let environmentDir = path.join(workonHomeDir, environmentName); - let pathToCheck = interpreterPath; - - if (getOSType() === OSType.Windows) { - environmentDir = environmentDir.toUpperCase(); - pathToCheck = interpreterPath.toUpperCase(); - } - - return await pathExists(environmentDir) && pathToCheck.startsWith(`${environmentDir}${path.sep}`); -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/windowsRegistryLocator.ts b/src/client/pythonEnvironments/discovery/locators/services/windowsRegistryLocator.ts deleted file mode 100644 index 9167c33d5019..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/windowsRegistryLocator.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { uniqBy } from 'lodash'; -import { HKCU, HKLM } from 'winreg'; -import { getInterpreterDataFromRegistry, IRegistryInterpreterData, readRegistryKeys } from '../../../common/windowsUtils'; - -export async function getRegistryInterpreters() : Promise { - let registryData:IRegistryInterpreterData[] = []; - - for (const arch of ['x64', 'x86']) { - for (const hive of [HKLM, HKCU]) { - const keys = (await readRegistryKeys({ arch, hive, key: '\\SOFTWARE\\Python' })).map((k) => k.key); - for (const key of keys) { - registryData = registryData.concat(await getInterpreterDataFromRegistry(arch, hive, key)); - } - } - } - - return uniqBy(registryData, (r:IRegistryInterpreterData) => r.interpreterPath); -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/windowsRegistryService.ts b/src/client/pythonEnvironments/discovery/locators/services/windowsRegistryService.ts deleted file mode 100644 index d72ad667ebcb..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/windowsRegistryService.ts +++ /dev/null @@ -1,217 +0,0 @@ -// tslint:disable:no-require-imports no-var-requires underscore-consistent-invocation - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { Uri } from 'vscode'; -import { traceError } from '../../../../common/logger'; -import { - IFileSystem, IPlatformService, IRegistry, RegistryHive, -} from '../../../../common/platform/types'; -import { IPathUtils } from '../../../../common/types'; -import { Architecture } from '../../../../common/utils/platform'; -import { IInterpreterHelper } from '../../../../interpreter/contracts'; -import { IWindowsStoreInterpreter } from '../../../../interpreter/locators/types'; -import { IServiceContainer } from '../../../../ioc/types'; -import { EnvironmentType, PythonEnvironment } from '../../../info'; -import { parsePythonVersion } from '../../../info/pythonVersion'; -import { CacheableLocatorService } from './cacheableLocatorService'; -import { AnacondaCompanyName, AnacondaCompanyNames } from './conda'; -import { WindowsStoreInterpreter } from './windowsStoreInterpreter'; - -const flatten = require('lodash/flatten') as typeof import('lodash/flatten'); - -// tslint:disable-next-line:variable-name -const DefaultPythonExecutable = 'python.exe'; -// tslint:disable-next-line:variable-name -const CompaniesToIgnore = ['PYLAUNCHER']; -// tslint:disable-next-line:variable-name -const PythonCoreCompanyDisplayName = 'Python Software Foundation'; -// tslint:disable-next-line:variable-name -const PythonCoreComany = 'PYTHONCORE'; - -type CompanyInterpreter = { - companyKey: string; - hive: RegistryHive; - arch?: Architecture; -}; - -@injectable() -export class WindowsRegistryService extends CacheableLocatorService { - private readonly pathUtils: IPathUtils; - - private readonly fs: IFileSystem; - - constructor( - @inject(IRegistry) private registry: IRegistry, - @inject(IPlatformService) private readonly platform: IPlatformService, - @inject(IServiceContainer) serviceContainer: IServiceContainer, - @inject(WindowsStoreInterpreter) private readonly windowsStoreInterpreter: IWindowsStoreInterpreter, - ) { - super('WindowsRegistryService', serviceContainer); - this.pathUtils = serviceContainer.get(IPathUtils); - this.fs = serviceContainer.get(IFileSystem); - } - - // tslint:disable-next-line:no-empty - public dispose() {} - - protected async getInterpretersImplementation(_resource?: Uri): Promise { - return this.platform.isWindows - ? this.getInterpretersFromRegistry().catch((ex) => { - traceError('Fetching interpreters from registry failed with error', ex); - return []; - }) - : []; - } - - private async getInterpretersFromRegistry() { - // https://github.com/python/peps/blob/master/pep-0514.txt#L357 - const hkcuArch = this.platform.is64bit ? undefined : Architecture.x86; - const promises: Promise[] = [ - this.getCompanies(RegistryHive.HKCU, hkcuArch), - this.getCompanies(RegistryHive.HKLM, Architecture.x86), - ]; - // https://github.com/Microsoft/PTVS/blob/ebfc4ca8bab234d453f15ee426af3b208f3c143c/Python/Product/Cookiecutter/Shared/Interpreters/PythonRegistrySearch.cs#L44 - if (this.platform.is64bit) { - promises.push(this.getCompanies(RegistryHive.HKLM, Architecture.x64)); - } - - const companies = await Promise.all(promises); - const companyInterpreters = await Promise.all( - flatten(companies) - .filter((item) => item !== undefined && item !== null) - .map((company) => this.getInterpretersForCompany(company.companyKey, company.hive, company.arch)), - ); - - return ( - flatten(companyInterpreters) - .filter((item) => item !== undefined && item !== null) - // tslint:disable-next-line:no-non-null-assertion - .map((item) => item!) - .reduce((prev, current) => { - if (prev.findIndex((item) => item.path.toUpperCase() === current.path.toUpperCase()) === -1) { - prev.push(current); - } - return prev; - }, []) - ); - } - - private async getCompanies(hive: RegistryHive, arch?: Architecture): Promise { - return this.registry.getKeys('\\Software\\Python', hive, arch).then((companyKeys) => companyKeys - .filter( - (companyKey) => CompaniesToIgnore.indexOf(this.pathUtils.basename(companyKey).toUpperCase()) === -1, - ) - .map((companyKey) => ({ companyKey, hive, arch }))); - } - - private async getInterpretersForCompany(companyKey: string, hive: RegistryHive, arch?: Architecture) { - const tagKeys = await this.registry.getKeys(companyKey, hive, arch); - return Promise.all( - tagKeys.map((tagKey) => this.getInreterpreterDetailsForCompany(tagKey, companyKey, hive, arch)), - ); - } - - private getInreterpreterDetailsForCompany( - tagKey: string, - companyKey: string, - hive: RegistryHive, - arch?: Architecture, - ): Promise { - const key = `${tagKey}\\InstallPath`; - type InterpreterInformation = - | null - | undefined - | { - installPath: string; - executablePath?: string; - displayName?: string; - version?: string; - companyDisplayName?: string; - }; - return this.registry - .getValue(key, hive, arch) - .then((installPath) => { - // Install path is mandatory. - if (!installPath) { - return Promise.resolve(null); - } - // Check if 'ExecutablePath' exists. - // Remember Python 2.7 doesn't have 'ExecutablePath' (there could be others). - // Treat all other values as optional. - return Promise.all([ - Promise.resolve(installPath), - this.registry.getValue(key, hive, arch, 'ExecutablePath'), - this.registry.getValue(tagKey, hive, arch, 'SysVersion'), - this.getCompanyDisplayName(companyKey, hive, arch), - ]).then(([installedPath, executablePath, version, companyDisplayName]) => { - companyDisplayName = AnacondaCompanyNames.indexOf(companyDisplayName!) === -1 - ? companyDisplayName - : AnacondaCompanyName; - // tslint:disable-next-line:prefer-type-cast no-object-literal-type-assertion - return { - installPath: installedPath, - executablePath, - version, - companyDisplayName, - } as InterpreterInformation; - }); - }) - .then(async (interpreterInfo?: InterpreterInformation) => { - if (!interpreterInfo) { - return; - } - - const executablePath = interpreterInfo.executablePath && interpreterInfo.executablePath.length > 0 - ? interpreterInfo.executablePath - : path.join(interpreterInfo.installPath, DefaultPythonExecutable); - - if (this.windowsStoreInterpreter.isHiddenInterpreter(executablePath)) { - return; - } - const helper = this.serviceContainer.get(IInterpreterHelper); - const details = await helper.getInterpreterInformation(executablePath); - if (!details) { - return; - } - const version = interpreterInfo.version - ? this.pathUtils.basename(interpreterInfo.version) - : this.pathUtils.basename(tagKey); - this._hasInterpreters.resolve(true); - // tslint:disable-next-line:prefer-type-cast no-object-literal-type-assertion - return { - ...(details as PythonEnvironment), - path: executablePath, - // Do not use version info from registry, this doesn't contain the release level. - // Give preference to what we have retrieved from getInterpreterInformation. - version: details.version || parsePythonVersion(version), - companyDisplayName: interpreterInfo.companyDisplayName, - envType: (await this.windowsStoreInterpreter.isWindowsStoreInterpreter(executablePath)) - ? EnvironmentType.WindowsStore - : EnvironmentType.Unknown, - } as PythonEnvironment; - }) - .then((interpreter) => (interpreter - ? this.fs - .fileExists(interpreter.path) - .catch(() => false) - .then((exists) => (exists ? interpreter : null)) - : null)) - .catch((error) => { - traceError( - `Failed to retrieve interpreter details for company ${companyKey},tag: ${tagKey}, hive: ${hive}, arch: ${arch}`, - error, - ); - return null; - }); - } - - private async getCompanyDisplayName(companyKey: string, hive: RegistryHive, arch?: Architecture) { - const displayName = await this.registry.getValue(companyKey, hive, arch, 'DisplayName'); - if (displayName && displayName.length > 0) { - return displayName; - } - const company = this.pathUtils.basename(companyKey); - return company.toUpperCase() === PythonCoreComany ? PythonCoreCompanyDisplayName : company; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/windowsStoreInterpreter.ts b/src/client/pythonEnvironments/discovery/locators/services/windowsStoreInterpreter.ts deleted file mode 100644 index 4e04f0828c31..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/windowsStoreInterpreter.ts +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { traceDecorators } from '../../../../common/logger'; -import { IFileSystem } from '../../../../common/platform/types'; -import { IPythonExecutionFactory } from '../../../../common/process/types'; -import { IPersistentStateFactory } from '../../../../common/types'; -import { IComponentAdapter } from '../../../../interpreter/contracts'; -import { IInterpreterHashProvider, IWindowsStoreInterpreter } from '../../../../interpreter/locators/types'; -import { IServiceContainer } from '../../../../ioc/types'; - -/** - * When using Windows Store interpreter the path that should be used is under - * %USERPROFILE%\AppData\Local\Microsoft\WindowsApps\python*.exe. The python.exe path - * under ProgramFiles\WindowsApps should not be used at all. Execute permissions on - * that instance of the store interpreter are restricted to system. Paths under - * %USERPROFILE%\AppData\Local\Microsoft\WindowsApps\PythonSoftwareFoundation* are also ok - * to use. But currently this results in duplicate entries. - * - * @param {string} pythonPath - * @returns {boolean} - */ -export function isRestrictedWindowsStoreInterpreterPath(pythonPath: string): boolean { - const pythonPathToCompare = pythonPath.toUpperCase().replace(/\//g, '\\'); - return ( - pythonPathToCompare.includes('\\Program Files\\WindowsApps\\'.toUpperCase()) - || pythonPathToCompare.includes('\\Microsoft\\WindowsApps\\PythonSoftwareFoundation'.toUpperCase()) - ); -} - -// The parts of IComponentAdapter used here. -interface IComponent { - isWindowsStoreInterpreter(pythonPath: string): Promise; -} - -/** - * The default location of Windows apps are `%ProgramFiles%\WindowsApps`. - * (https://www.samlogic.net/articles/windows-8-windowsapps-folder.htm) - * When users access Python interpreter it is installed in `\Microsoft\WindowsApps` - * Based on our testing this is where Python interpreters installed from Windows Store is always installed. - * (unfortunately couldn't find any documentation on this). - * What we've identified is the fact that: - * - The Python interpreter in Microsoft\WIndowsApps\python.exe is a symbolic link to files located in: - * - Program Files\WindowsApps\ & Microsoft\WindowsApps\PythonSoftwareFoundation - * - I.e. they all point to the same place. - * However when the user launches the executable, its located in `Microsoft\WindowsApps\python.exe` - * Hence for all intensive purposes that's the main executable, that's what the user uses. - * As a result: - * - We'll only display what the user has access to, that being `Microsoft\WindowsApps\python.exe` - * - Others are hidden. - * - * Details can be found here (original issue https://github.com/microsoft/vscode-python/issues/5926). - * - * @export - * @class WindowsStoreInterpreter - * @implements {IWindowsStoreInterpreter} - * @implements {IInterpreterHashProvider} - */ -@injectable() -export class WindowsStoreInterpreter implements IWindowsStoreInterpreter, IInterpreterHashProvider { - constructor( - @inject(IServiceContainer) private readonly serviceContainer: IServiceContainer, - @inject(IPersistentStateFactory) private readonly persistentFactory: IPersistentStateFactory, - @inject(IFileSystem) private readonly fs: IFileSystem, - @inject(IComponentAdapter) private readonly pyenvs: IComponent - ) {} - - /** - * Whether this is a Windows Store/App Interpreter. - * - * @param {string} pythonPath - * @returns {boolean} - * @memberof WindowsStoreInterpreter - */ - public async isWindowsStoreInterpreter(pythonPath: string): Promise { - const result = await this.pyenvs.isWindowsStoreInterpreter(pythonPath); - if (result !== undefined) { - return result; - } - const pythonPathToCompare = pythonPath.toUpperCase().replace(/\//g, '\\'); - return ( - pythonPathToCompare.includes('\\Microsoft\\WindowsApps\\'.toUpperCase()) - || pythonPathToCompare.includes('\\Program Files\\WindowsApps\\'.toUpperCase()) - || pythonPathToCompare.includes('\\Microsoft\\WindowsApps\\PythonSoftwareFoundation'.toUpperCase()) - ); - } - - /** - * Whether this is a python executable in a windows app store folder that is internal and can be hidden from users. - * Interpreters that fall into this category will not be displayed to the users. - * - * @param {string} pythonPath - * @returns {Promise} - * @memberof IInterpreterHelper - */ - public isHiddenInterpreter(pythonPath: string): boolean { - return isRestrictedWindowsStoreInterpreterPath(pythonPath); - } - - /** - * Gets the hash of the Python interpreter (installed from the windows store). - * We need to use a special way to get the hash for these, by first resolving the - * path to the actual executable and then calculating the hash on that file. - * - * Using fs.lstat or similar nodejs functions do not work, as these are some weird form of symbolic linked files. - * - * Note: Store the hash in a temporary state store (as we're spawning processes here). - * Spawning processes to get a hash of a terminal is expensive. - * Hence to minimize resource usage (just to get a file hash), we will cache the generated hash for 1hr. - * (why 1hr, simple, why 2hrs, or 3hrs.) - * If user installs/updates/uninstalls Windows Store Python apps, 1hr is enough time to get things rolling again. - * - * @param {string} pythonPath - * @returns {Promise} - * @memberof InterpreterHelper - */ - @traceDecorators.error('Get Windows Store Interpreter Hash') - public async getInterpreterHash(pythonPath: string): Promise { - const key = `WINDOWS_STORE_INTERPRETER_HASH_${pythonPath}`; - const stateStore = this.persistentFactory.createGlobalPersistentState( - key, - undefined, - 60 * 60 * 1000, - ); - - if (stateStore.value) { - return stateStore.value; - } - const executionFactory = this.serviceContainer.get(IPythonExecutionFactory); - const pythonService = await executionFactory.create({ pythonPath }); - const executablePath = await pythonService.getExecutablePath(); - // If we are unable to get file hash of executable, then get hash of parent directory. - // Its likely it will fail for the executable (fails during development, but try nevertheless - in case things start working). - const hash = await this.fs - .getFileHash(executablePath) - .catch(() => this.fs.getFileHash(path.dirname(executablePath))); - await stateStore.updateValue(hash); - - return hash; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/windowsStoreLocator.ts b/src/client/pythonEnvironments/discovery/locators/services/windowsStoreLocator.ts deleted file mode 100644 index c5128daeb444..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/windowsStoreLocator.ts +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import * as fsapi from 'fs-extra'; -import * as path from 'path'; -import { traceError, traceWarning } from '../../../../common/logger'; -import { Architecture, getEnvironmentVariable } from '../../../../common/utils/platform'; -import { - PythonEnvInfo, PythonEnvKind, PythonReleaseLevel, PythonVersion, -} from '../../../base/info'; -import { parseVersion } from '../../../base/info/pythonVersion'; -import { ILocator, IPythonEnvsIterator } from '../../../base/locator'; -import { PythonEnvsWatcher } from '../../../base/watcher'; -import { getFileInfo } from '../../../common/externalDependencies'; -import { isWindowsPythonExe } from '../../../common/windowsUtils'; - -/** - * Gets path to the Windows Apps directory. - * @returns {string} : Returns path to the Windows Apps directory under - * `%LOCALAPPDATA%/Microsoft/WindowsApps`. - */ -export function getWindowsStoreAppsRoot(): string { - const localAppData = getEnvironmentVariable('LOCALAPPDATA') || ''; - return path.join(localAppData, 'Microsoft', 'WindowsApps'); -} - -/** - * Checks if a given path is under the forbidden windows store directory. - * @param {string} interpreterPath : Absolute path to the python interpreter. - * @returns {boolean} : Returns true if `interpreterPath` is under - * `%ProgramFiles%/WindowsApps`. - */ -export function isForbiddenStorePath(interpreterPath:string):boolean { - const programFilesStorePath = path - .join(getEnvironmentVariable('ProgramFiles') || 'Program Files', 'WindowsApps') - .normalize() - .toUpperCase(); - return path.normalize(interpreterPath).toUpperCase().includes(programFilesStorePath); -} - -/** - * Checks if the given interpreter belongs to Windows Store Python environment. - * @param interpreterPath: Absolute path to any python interpreter. - * - * Remarks: - * 1. Checking if the path includes `Microsoft\WindowsApps`, `Program Files\WindowsApps`, is - * NOT enough. In WSL, `/mnt/c/users/user/AppData/Local/Microsoft/WindowsApps` is available as a search - * path. It is possible to get a false positive for that path. So the comparison should check if the - * absolute path to 'WindowsApps' directory is present in the given interpreter path. The WSL path to - * 'WindowsApps' is not a valid path to access, Windows Store Python. - * - * 2. 'startsWith' comparison may not be right, user can provide '\\?\C:\users\' style long paths in windows. - * - * 3. A limitation of the checks here is that they don't handle 8.3 style windows paths. - * For example, - * `C:\Users\USER\AppData\Local\MICROS~1\WINDOW~1\PYTHON~2.EXE` - * is the shortened form of - * `C:\Users\USER\AppData\Local\Microsoft\WindowsApps\python3.7.exe` - * - * The correct way to compare these would be to always convert given paths to long path (or to short path). - * For either approach to work correctly you need actual file to exist, and accessible from the user's - * account. - * - * To convert to short path without using N-API in node would be to use this command. This is very expensive: - * `> cmd /c for %A in ("C:\Users\USER\AppData\Local\Microsoft\WindowsApps\python3.7.exe") do @echo %~sA` - * The above command will print out this: - * `C:\Users\USER\AppData\Local\MICROS~1\WINDOW~1\PYTHON~2.EXE` - * - * If we go down the N-API route, use node-ffi and either call GetShortPathNameW or GetLongPathNameW from, - * Kernel32 to convert between the two path variants. - * - */ -export async function isWindowsStoreEnvironment(interpreterPath: string): Promise { - const pythonPathToCompare = path.normalize(interpreterPath).toUpperCase(); - const localAppDataStorePath = path - .normalize(getWindowsStoreAppsRoot()) - .toUpperCase(); - if (pythonPathToCompare.includes(localAppDataStorePath)) { - return true; - } - - // Program Files store path is a forbidden path. Only admins and system has access this path. - // We should never have to look at this path or even execute python from this path. - if (isForbiddenStorePath(pythonPathToCompare)) { - traceWarning('isWindowsStoreEnvironment called with Program Files store path.'); - return true; - } - return false; -} - -/** - * Gets paths to the Python executable under Windows Store apps. - * @returns: Returns python*.exe for the windows store app root directory. - * - * Remarks: We don't need to find the path to the interpreter under the specific application - * directory. Such as: - * `%LOCALAPPDATA%/Microsoft/WindowsApps/PythonSoftwareFoundation.Python.3.7_qbz5n2kfra8p0` - * The same python executable is also available at: - * `%LOCALAPPDATA%/Microsoft/WindowsApps` - * It would be a duplicate. - * - * All python executable under `%LOCALAPPDATA%/Microsoft/WindowsApps` or the sub-directories - * are 'reparse points' that point to the real executable at `%PROGRAMFILES%/WindowsApps`. - * However, that directory is off limits to users. So no need to populate interpreters from - * that location. - */ -export async function getWindowsStorePythonExes(): Promise { - const windowsAppsRoot = getWindowsStoreAppsRoot(); - - // Collect python*.exe directly under %LOCALAPPDATA%/Microsoft/WindowsApps - const files = await fsapi.readdir(windowsAppsRoot); - return files - .map((filename:string) => path.join(windowsAppsRoot, filename)) - .filter(isWindowsPythonExe); -} - -export class WindowsStoreLocator extends PythonEnvsWatcher implements ILocator { - private readonly kind:PythonEnvKind = PythonEnvKind.WindowsStore; - - public iterEnvs(): IPythonEnvsIterator { - const buildEnvInfo = (exe:string) => this.buildEnvInfo(exe); - const iterator = async function* () { - const exes = await getWindowsStorePythonExes(); - yield* exes.map(buildEnvInfo); - }; - return iterator(); - } - - public async resolveEnv(env: string | PythonEnvInfo): Promise { - const executablePath = typeof env === 'string' ? env : env.executable.filename; - if (await isWindowsStoreEnvironment(executablePath)) { - return this.buildEnvInfo(executablePath); - } - return undefined; - } - - private async buildEnvInfo(exe:string): Promise { - let version:PythonVersion; - try { - version = parseVersion(path.basename(exe)); - } catch (ex) { - traceError(`Failed to parse version from path: ${exe}`, ex); - version = { - major: 3, - minor: -1, - micro: -1, - release: { level: PythonReleaseLevel.Final, serial: -1 }, - sysVersion: undefined, - }; - } - return { - name: '', - location: '', - kind: this.kind, - executable: { - filename: exe, - sysPrefix: '', - ...(await getFileInfo(exe)), - }, - version, - arch: Architecture.x64, - distro: { org: 'Microsoft' }, - }; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/workspaceVirtualEnvService.ts b/src/client/pythonEnvironments/discovery/locators/services/workspaceVirtualEnvService.ts deleted file mode 100644 index a3e3b99f3176..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/workspaceVirtualEnvService.ts +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -// tslint:disable:no-require-imports - -import { inject, injectable, named } from 'inversify'; -import * as path from 'path'; -import { Uri } from 'vscode'; -import { IWorkspaceService } from '../../../../common/application/types'; -import { IConfigurationService } from '../../../../common/types'; -import { - IInterpreterWatcher, - IInterpreterWatcherBuilder, - IVirtualEnvironmentsSearchPathProvider, -} from '../../../../interpreter/contracts'; -import { IServiceContainer } from '../../../../ioc/types'; -import { BaseVirtualEnvService } from './baseVirtualEnvService'; - -// tslint:disable-next-line: no-var-requires -const untildify = require('untildify'); - -@injectable() -export class WorkspaceVirtualEnvService extends BaseVirtualEnvService { - public constructor( - @inject(IVirtualEnvironmentsSearchPathProvider) - @named('workspace') - workspaceVirtualEnvPathProvider: IVirtualEnvironmentsSearchPathProvider, - @inject(IServiceContainer) serviceContainer: IServiceContainer, - @inject(IInterpreterWatcherBuilder) private readonly builder: IInterpreterWatcherBuilder, - ) { - super(workspaceVirtualEnvPathProvider, serviceContainer, 'WorkspaceVirtualEnvService', true); - } - - protected async getInterpreterWatchers(resource: Uri | undefined): Promise { - return [await this.builder.getWorkspaceVirtualEnvInterpreterWatcher(resource)]; - } -} - -@injectable() -export class WorkspaceVirtualEnvironmentsSearchPathProvider implements IVirtualEnvironmentsSearchPathProvider { - public constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {} - - public async getSearchPaths(resource?: Uri): Promise { - const configService = this.serviceContainer.get(IConfigurationService); - const paths: string[] = []; - const { venvPath } = configService.getSettings(resource); - if (venvPath) { - paths.push(untildify(venvPath)); - } - const workspaceService = this.serviceContainer.get(IWorkspaceService); - if (Array.isArray(workspaceService.workspaceFolders) && workspaceService.workspaceFolders.length > 0) { - let wsPath: string | undefined; - if (resource && workspaceService.workspaceFolders.length > 1) { - const wkspaceFolder = workspaceService.getWorkspaceFolder(resource); - if (wkspaceFolder) { - wsPath = wkspaceFolder.uri.fsPath; - } - } else { - wsPath = workspaceService.workspaceFolders[0].uri.fsPath; - } - if (wsPath) { - paths.push(wsPath); - paths.push(path.join(wsPath, '.direnv')); - } - } - return paths; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/services/workspaceVirtualEnvWatcherService.ts b/src/client/pythonEnvironments/discovery/locators/services/workspaceVirtualEnvWatcherService.ts deleted file mode 100644 index 592c65e4315d..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/services/workspaceVirtualEnvWatcherService.ts +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { - Disposable, Event, EventEmitter, FileSystemWatcher, RelativePattern, Uri, -} from 'vscode'; -import { IWorkspaceService } from '../../../../common/application/types'; -import '../../../../common/extensions'; -import { traceDecorators, traceVerbose } from '../../../../common/logger'; -import { IPlatformService } from '../../../../common/platform/types'; -import { IPythonExecutionFactory } from '../../../../common/process/types'; -import { IDisposableRegistry, Resource } from '../../../../common/types'; -import { IInterpreterWatcher } from '../../../../interpreter/contracts'; - -const maxTimeToWaitForEnvCreation = 60_000; -const timeToPollForEnvCreation = 2_000; - -@injectable() -export class WorkspaceVirtualEnvWatcherService implements IInterpreterWatcher, Disposable { - private readonly didCreate: EventEmitter; - - private timers = new Map(); - - private fsWatchers: FileSystemWatcher[] = []; - - private resource: Resource; - - constructor( - @inject(IDisposableRegistry) private readonly disposableRegistry: Disposable[], - @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, - @inject(IPlatformService) private readonly platformService: IPlatformService, - @inject(IPythonExecutionFactory) private readonly pythonExecFactory: IPythonExecutionFactory, - ) { - this.didCreate = new EventEmitter(); - disposableRegistry.push(this); - } - - public get onDidCreate(): Event { - return this.didCreate.event; - } - - public dispose() { - this.clearTimers(); - } - - @traceDecorators.verbose('Register Interpreter Watcher') - public async register(resource: Resource): Promise { - if (this.fsWatchers.length > 0) { - return; - } - this.resource = resource; - const workspaceFolder = resource ? this.workspaceService.getWorkspaceFolder(resource) : undefined; - const executable = this.platformService.isWindows ? 'python.exe' : 'python'; - const patterns = [path.join('*', executable), path.join('*', '*', executable)]; - - for (const pattern of patterns) { - const globPatern = workspaceFolder ? new RelativePattern(workspaceFolder.uri.fsPath, pattern) : pattern; - traceVerbose(`Create file systemwatcher with pattern ${pattern}`); - - const fsWatcher = this.workspaceService.createFileSystemWatcher(globPatern); - fsWatcher.onDidCreate((e) => this.createHandler(e), this, this.disposableRegistry); - - this.disposableRegistry.push(fsWatcher); - this.fsWatchers.push(fsWatcher); - } - } - - @traceDecorators.verbose('Interpreter Watcher change handler') - public async createHandler(e: Uri) { - this.didCreate.fire(this.resource); - // On Windows, creation of environments are very slow, hence lets notify again after - // the python executable is accessible (i.e. when we can launch the process). - this.notifyCreationWhenReady(e.fsPath).ignoreErrors(); - } - - protected async notifyCreationWhenReady(pythonPath: string) { - const counter = this.timers.has(pythonPath) ? this.timers.get(pythonPath)!.counter + 1 : 0; - const isValid = await this.isValidExecutable(pythonPath); - if (isValid) { - if (counter > 0) { - this.didCreate.fire(this.resource); - } - return this.timers.delete(pythonPath); - } - if (counter > maxTimeToWaitForEnvCreation / timeToPollForEnvCreation) { - // Send notification before we give up trying. - this.didCreate.fire(this.resource); - this.timers.delete(pythonPath); - return; - } - - const timer = setTimeout( - () => this.notifyCreationWhenReady(pythonPath).ignoreErrors(), - timeToPollForEnvCreation, - ); - this.timers.set(pythonPath, { timer, counter }); - } - - private clearTimers() { - // tslint:disable-next-line: no-any - this.timers.forEach((item) => clearTimeout(item.timer as any)); - this.timers.clear(); - } - - private async isValidExecutable(pythonPath: string): Promise { - const execService = await this.pythonExecFactory.create({ pythonPath }); - const info = await execService.getInterpreterInformation().catch(() => undefined); - return info !== undefined; - } -} diff --git a/src/client/pythonEnvironments/discovery/locators/types.ts b/src/client/pythonEnvironments/discovery/locators/types.ts deleted file mode 100644 index 687a2e224b75..000000000000 --- a/src/client/pythonEnvironments/discovery/locators/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import type { GetInterpreterOptions } from '../../../interpreter/interpreterService'; - -export type GetInterpreterLocatorOptions = GetInterpreterOptions & { ignoreCache?: boolean }; diff --git a/src/client/pythonEnvironments/discovery/subenv.ts b/src/client/pythonEnvironments/discovery/subenv.ts deleted file mode 100644 index 10633706f24a..000000000000 --- a/src/client/pythonEnvironments/discovery/subenv.ts +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { EnvironmentType } from '../info'; -import { getPyenvTypeFinder } from './globalenv'; - -type ExecFunc = (cmd: string, args: string[]) => Promise<{ stdout: string }>; - -type NameFinderFunc = (python: string) => Promise; -type TypeFinderFunc = (python: string) => Promise; -type ExecutableFinderFunc = (python: string) => Promise; - -/** - * Determine the environment name for the given Python executable. - * - * @param python - the executable to inspect - * @param finders - the functions specific to different Python environment types - */ -export async function getName(python: string, finders: NameFinderFunc[]): Promise { - for (const find of finders) { - const found = await find(python); - if (found && found !== '') { - return found; - } - } - return undefined; -} - -/** - * Determine the environment type for the given Python executable. - * - * @param python - the executable to inspect - * @param finders - the functions specific to different Python environment types - */ -export async function getType(python: string, finders: TypeFinderFunc[]): Promise { - for (const find of finders) { - const found = await find(python); - if (found && found !== EnvironmentType.Unknown) { - return found; - } - } - return undefined; -} - -// ======= default sets ======== - -/** - * Build the list of default "name finder" functions to pass to `getName()`. - * - * @param dirname - the "root" of a directory tree to search - * @param pathDirname - typically `path.dirname` - * @param pathBasename - typically `path.basename` - * @param isPipenvRoot - a function the determines if it's a pipenv dir - */ -export function getNameFinders( - dirname: string | undefined, - // - pathDirname: (filename: string) => string, - pathBasename: (filename: string) => string, - // - isPipenvRoot: (dir: string, python: string) => Promise, -): NameFinderFunc[] { - return [ - // Note that currently there is only one finder function in - // the list. That is only a temporary situation as we - // consolidate code under the py-envs component. - async (python) => { - if (dirname && (await isPipenvRoot(dirname, python))) { - // In pipenv, return the folder name of the root dir. - return pathBasename(dirname); - } - return pathBasename(pathDirname(pathDirname(python))); - }, - ]; -} - -/** - * Build the list of default "type finder" functions to pass to `getType()`. - * - * @param homedir - the user's home directory (e.g. `$HOME`) - * @param scripts - the names of possible activation scripts (e.g. `activate.sh`) - * @param pathSep - the path separator to use (typically `path.sep`) - * @param pathJoin - typically `path.join` - * @param pathDirname - typically `path.dirname` - * @param getCurDir - a function that returns `$CWD` - * @param isPipenvRoot - a function the determines if it's a pipenv dir - * @param getEnvVar - a function to look up a process environment variable (i,e. `process.env[name]`) - * @param fileExists - typically `fs.exists` - * @param exec - the function to use to run a command in a subprocess - */ -export function getTypeFinders( - homedir: string, - scripts: string[], - // - pathSep: string, - pathJoin: (...parts: string[]) => string, - pathDirname: (filename: string) => string, - // - getCurDir: () => Promise, - isPipenvRoot: (dir: string, python: string) => Promise, - getEnvVar: (name: string) => string | undefined, - fileExists: (n: string) => Promise, - exec: ExecFunc, -): TypeFinderFunc[] { - return [ - getVenvTypeFinder(pathDirname, pathJoin, fileExists), - // For now we treat pyenv as a "virtual" environment (to keep compatibility)... - getPyenvTypeFinder(homedir, pathSep, pathJoin, getEnvVar, exec), - getPipenvTypeFinder(getCurDir, isPipenvRoot), - getVirtualenvTypeFinder(scripts, pathDirname, pathJoin, fileExists), - // Lets not try to determine whether this is a conda environment or not. - ]; -} - -// ======= venv ======== - -/** - * Build a "type finder" function that identifies venv environments. - * - * @param pathDirname - typically `path.dirname` - * @param pathJoin - typically `path.join` - * @param fileExists - typically `fs.exists` - */ -export function getVenvTypeFinder( - // - pathDirname: (filename: string) => string, - pathJoin: (...parts: string[]) => string, - // - fileExists: (n: string) => Promise, -): TypeFinderFunc { - return async (python: string) => { - const dir = pathDirname(python); - const VENVFILES = ['pyvenv.cfg', pathJoin('..', 'pyvenv.cfg')]; - const cfgFiles = VENVFILES.map((file) => pathJoin(dir, file)); - for (const file of cfgFiles) { - if (await fileExists(file)) { - return EnvironmentType.Venv; - } - } - return undefined; - }; -} - -/** - * Build an "executable finder" function that identifies venv environments. - * - * @param basename - the venv name or names to look for - * @param pathDirname - typically `path.dirname` - * @param pathJoin - typically `path.join` - * @param fileExists - typically `fs.exists` - */ -export function getVenvExecutableFinder( - basename: string | string[], - // - pathDirname: (filename: string) => string, - pathJoin: (...parts: string[]) => string, - // - fileExists: (n: string) => Promise, -): ExecutableFinderFunc { - const basenames = typeof basename === 'string' ? [basename] : basename; - return async (python: string) => { - // Generated scripts are found in the same directory as the interpreter. - const binDir = pathDirname(python); - for (const name of basenames) { - const filename = pathJoin(binDir, name); - if (await fileExists(filename)) { - return filename; - } - } - // No matches so return undefined. - return undefined; - }; -} - -// ======= virtualenv ======== - -/** - * Build a "type finder" function that identifies virtualenv environments. - * - * @param scripts - the names of possible activation scripts (e.g. `activate.sh`) - * @param pathDirname - typically `path.dirname` - * @param pathJoin - typically `path.join` - * @param fileExists - typically `fs.exists` - */ -export function getVirtualenvTypeFinder( - scripts: string[], - // - pathDirname: (filename: string) => string, - pathJoin: (...parts: string[]) => string, - // - fileExists: (n: string) => Promise, -): TypeFinderFunc { - const find = getVenvExecutableFinder(scripts, pathDirname, pathJoin, fileExists); - return async (python: string) => { - const found = await find(python); - return found !== undefined ? EnvironmentType.VirtualEnv : undefined; - }; -} - -// ======= pipenv ======== - -/** - * Build a "type finder" function that identifies pipenv environments. - * - * @param getCurDir - a function that returns `$CWD` - * @param isPipenvRoot - a function the determines if it's a pipenv dir - */ -export function getPipenvTypeFinder( - getCurDir: () => Promise, - isPipenvRoot: (dir: string, python: string) => Promise, -): TypeFinderFunc { - return async (python: string) => { - const curDir = await getCurDir(); - if (curDir && (await isPipenvRoot(curDir, python))) { - return EnvironmentType.Pipenv; - } - return undefined; - }; -} diff --git a/src/client/pythonEnvironments/exec.ts b/src/client/pythonEnvironments/exec.ts index f758fa1dbb3b..bd07a2a6192c 100644 --- a/src/client/pythonEnvironments/exec.ts +++ b/src/client/pythonEnvironments/exec.ts @@ -23,7 +23,11 @@ export type PythonExecInfo = { * @param python - the path (or command + arguments) to use to invoke Python * @param pythonArgs - any extra arguments to use when running Python */ -export function buildPythonExecInfo(python: string | string[], pythonArgs?: string[]): PythonExecInfo { +export function buildPythonExecInfo( + python: string | string[], + pythonArgs?: string[], + pythonExecutable?: string, +): PythonExecInfo { if (Array.isArray(python)) { const args = python.slice(1); if (pythonArgs) { @@ -33,9 +37,7 @@ export function buildPythonExecInfo(python: string | string[], pythonArgs?: stri args, command: python[0], python: [...python], - // It isn't necessarily the last item but our supported - // cases it currently is. - pythonExecutable: python[python.length - 1], + pythonExecutable: pythonExecutable ?? python[python.length - 1], }; } return { @@ -63,9 +65,7 @@ export function copyPythonExecInfo(orig: PythonExecInfo, extraPythonArgs?: strin info.args.push(...extraPythonArgs); } if (info.pythonExecutable === undefined) { - // It isn't necessarily the last item but our supported - // cases it currently is. - info.pythonExecutable = info.python[info.python.length - 1]; + info.pythonExecutable = info.python[info.python.length - 1]; // Default case } return info; } diff --git a/src/client/pythonEnvironments/index.ts b/src/client/pythonEnvironments/index.ts index 34089990d5fc..299dfab59132 100644 --- a/src/client/pythonEnvironments/index.ts +++ b/src/client/pythonEnvironments/index.ts @@ -2,104 +2,272 @@ // Licensed under the MIT License. import * as vscode from 'vscode'; -import { IServiceContainer, IServiceManager } from '../ioc/types'; -import { PythonEnvInfoCache } from './base/envsCache'; +import { Uri } from 'vscode'; +import { cloneDeep } from 'lodash'; +import { getGlobalStorage, IPersistentStorage } from '../common/persistentState'; +import { getOSType, OSType } from '../common/utils/platform'; +import { ActivationResult, ExtensionState } from '../components'; import { PythonEnvInfo } from './base/info'; -import { ILocator, IPythonEnvsIterator, PythonLocatorQuery } from './base/locator'; -import { PythonEnvsChangedEvent } from './base/watcher'; -import { ExtensionLocators, WorkspaceLocators } from './discovery/locators'; -import { registerForIOC } from './legacyIOC'; +import { BasicEnvInfo, IDiscoveryAPI, ILocator } from './base/locator'; +import { PythonEnvsReducer } from './base/locators/composite/envsReducer'; +import { PythonEnvsResolver } from './base/locators/composite/envsResolver'; +import { WindowsPathEnvVarLocator } from './base/locators/lowLevel/windowsKnownPathsLocator'; +import { WorkspaceVirtualEnvironmentLocator } from './base/locators/lowLevel/workspaceVirtualEnvLocator'; +import { + initializeExternalDependencies as initializeLegacyExternalDependencies, + normCasePath, +} from './common/externalDependencies'; +import { ExtensionLocators, WatchRootsArgs, WorkspaceLocators } from './base/locators/wrappers'; +import { CustomVirtualEnvironmentLocator } from './base/locators/lowLevel/customVirtualEnvLocator'; +import { CondaEnvironmentLocator } from './base/locators/lowLevel/condaLocator'; +import { GlobalVirtualEnvironmentLocator } from './base/locators/lowLevel/globalVirtualEnvronmentLocator'; +import { PosixKnownPathsLocator } from './base/locators/lowLevel/posixKnownPathsLocator'; +import { PyenvLocator } from './base/locators/lowLevel/pyenvLocator'; +import { WindowsRegistryLocator } from './base/locators/lowLevel/windowsRegistryLocator'; +import { MicrosoftStoreLocator } from './base/locators/lowLevel/microsoftStoreLocator'; +import { getEnvironmentInfoService } from './base/info/environmentInfoService'; +import { registerNewDiscoveryForIOC } from './legacyIOC'; +import { PoetryLocator } from './base/locators/lowLevel/poetryLocator'; +import { HatchLocator } from './base/locators/lowLevel/hatchLocator'; +import { createPythonEnvironments } from './api'; +import { + createCollectionCache as createCache, + IEnvsCollectionCache, +} from './base/locators/composite/envsCollectionCache'; +import { EnvsCollectionService } from './base/locators/composite/envsCollectionService'; +import { IDisposable } from '../common/types'; +import { traceError } from '../logging'; +import { ActiveStateLocator } from './base/locators/lowLevel/activeStateLocator'; +import { CustomWorkspaceLocator } from './base/locators/lowLevel/customWorkspaceLocator'; +import { PixiLocator } from './base/locators/lowLevel/pixiLocator'; +import { getConfiguration } from '../common/vscodeApis/workspaceApis'; +import { getNativePythonFinder } from './base/locators/common/nativePythonFinder'; +import { createNativeEnvironmentsApi } from './nativeAPI'; +import { useEnvExtension } from '../envExt/api.internal'; +import { createEnvExtApi } from '../envExt/envExtApi'; -/** - * Activate the Python environments component (during extension activation).' - */ -export function activate(serviceManager: IServiceManager, serviceContainer: IServiceContainer) { - const [api, activateAPI] = createAPI(); - registerForIOC(serviceManager, serviceContainer, api); - activateAPI(); +const PYTHON_ENV_INFO_CACHE_KEY = 'PYTHON_ENV_INFO_CACHEv2'; + +export function shouldUseNativeLocator(): boolean { + const config = getConfiguration('python'); + return config.get('locator', 'js') === 'native'; } /** - * The public API for the Python environments component. - * - * Note that this is composed of sub-components. + * Set up the Python environments component (during extension activation).' */ -export class PythonEnvironments implements ILocator { - constructor( - // These are the sub-components the full component is composed of: - private readonly locators: ILocator - ) {} - - public get onChanged(): vscode.Event { - return this.locators.onChanged; +export async function initialize(ext: ExtensionState): Promise { + // Set up the legacy IOC container before api is created. + initializeLegacyExternalDependencies(ext.legacyIOC.serviceContainer); + + if (useEnvExtension()) { + const api = await createEnvExtApi(ext.disposables); + registerNewDiscoveryForIOC( + // These are what get wrapped in the legacy adapter. + ext.legacyIOC.serviceManager, + api, + ); + return api; } - public iterEnvs(query?: PythonLocatorQuery): IPythonEnvsIterator { - return this.locators.iterEnvs(query); + if (shouldUseNativeLocator()) { + const finder = getNativePythonFinder(ext.context); + const api = createNativeEnvironmentsApi(finder); + ext.disposables.push(api); + registerNewDiscoveryForIOC( + // These are what get wrapped in the legacy adapter. + ext.legacyIOC.serviceManager, + api, + ); + return api; } - public async resolveEnv(env: string | PythonEnvInfo): Promise { - return this.locators.resolveEnv(env); + const api = await createPythonEnvironments(() => createLocator(ext)); + registerNewDiscoveryForIOC( + // These are what get wrapped in the legacy adapter. + ext.legacyIOC.serviceManager, + api, + ); + return api; +} + +/** + * Make use of the component (e.g. register with VS Code). + */ +export async function activate(api: IDiscoveryAPI, ext: ExtensionState): Promise { + /** + * Force an initial background refresh of the environments. + * + * Note API is ready to be queried only after a refresh has been triggered, and extension activation is + * blocked on API being ready. So if discovery was never triggered for a scope, we need to block + * extension activation on the "refresh trigger". + */ + const folders = vscode.workspace.workspaceFolders; + // Trigger discovery if environment cache is empty. + const wasTriggered = getGlobalStorage(ext.context, PYTHON_ENV_INFO_CACHE_KEY, []).get().length > 0; + if (!wasTriggered) { + api.triggerRefresh().ignoreErrors(); + folders?.forEach(async (folder) => { + const wasTriggeredForFolder = getGlobalStorage( + ext.context, + `PYTHON_WAS_DISCOVERY_TRIGGERED_${normCasePath(folder.uri.fsPath)}`, + false, + ); + await wasTriggeredForFolder.set(true); + }); + } else { + // Figure out which workspace folders need to be activated if any. + folders?.forEach(async (folder) => { + const wasTriggeredForFolder = getGlobalStorage( + ext.context, + `PYTHON_WAS_DISCOVERY_TRIGGERED_${normCasePath(folder.uri.fsPath)}`, + false, + ); + if (!wasTriggeredForFolder.get()) { + api.triggerRefresh({ + searchLocations: { roots: [folder.uri], doNotIncludeNonRooted: true }, + }).ignoreErrors(); + await wasTriggeredForFolder.set(true); + } + }); } + + return { + fullyReady: Promise.resolve(), + }; } /** - * Initialize everything needed for the API and provide the API object. - * - * An activation function is also returned, which should be called soon. + * Get the locator to use in the component. */ -export function createAPI(): [PythonEnvironments, () => void] { - const [locators, activateLocators] = initLocators(); - - // Update this to pass in an actual function that checks for env info completeness. - const envsCache = new PythonEnvInfoCache(() => true); - - return [ - new PythonEnvironments(locators), - () => { - activateLocators(); - // Any other activation needed for the API will go here later. - envsCache.initialize(); - }, - ]; +async function createLocator( + ext: ExtensionState, + // This is shared. +): Promise { + // Create the low-level locators. + const locators: ILocator = new ExtensionLocators( + // Here we pull the locators together. + createNonWorkspaceLocators(ext), + createWorkspaceLocator(ext), + ); + + // Create the env info service used by ResolvingLocator and CachingLocator. + const envInfoService = getEnvironmentInfoService(ext.disposables); + + // Build the stack of composite locators. + const reducer = new PythonEnvsReducer(locators); + const resolvingLocator = new PythonEnvsResolver( + reducer, + // These are shared. + envInfoService, + ); + const caching = new EnvsCollectionService( + await createCollectionCache(ext), + // This is shared. + resolvingLocator, + shouldUseNativeLocator(), + ); + return caching; } -function initLocators(): [ExtensionLocators, () => void] { - // We will add locators in similar order - // to PythonInterpreterLocatorService.getLocators(). - const nonWorkspaceLocators: ILocator[] = [ - // Add an ILocator object here for each non-workspace locator. - ]; +function createNonWorkspaceLocators(ext: ExtensionState): ILocator[] { + const locators: (ILocator & Partial)[] = []; + locators.push( + // OS-independent locators go here. + new PyenvLocator(), + new CondaEnvironmentLocator(), + new ActiveStateLocator(), + new GlobalVirtualEnvironmentLocator(), + new CustomVirtualEnvironmentLocator(), + ); - const workspaceLocators = new WorkspaceLocators([ - // Add an ILocator factory func here for each kind of workspace-rooted locator. - ]); + if (getOSType() === OSType.Windows) { + locators.push( + // Windows specific locators go here. + new WindowsRegistryLocator(), + new MicrosoftStoreLocator(), + new WindowsPathEnvVarLocator(), + ); + } else { + locators.push( + // Linux/Mac locators go here. + new PosixKnownPathsLocator(), + ); + } - return [ - new ExtensionLocators(nonWorkspaceLocators, workspaceLocators), - // combined activation func: - () => { - // Any non-workspace locator activation goes here. - workspaceLocators.activate(getWorkspaceFolders()); - } - ]; + const disposables = locators.filter((d) => d.dispose !== undefined) as IDisposable[]; + ext.disposables.push(...disposables); + return locators; } -function getWorkspaceFolders() { - const rootAdded = new vscode.EventEmitter(); - const rootRemoved = new vscode.EventEmitter(); - vscode.workspace.onDidChangeWorkspaceFolders((event) => { +function watchRoots(args: WatchRootsArgs): IDisposable { + const { initRoot, addRoot, removeRoot } = args; + + const folders = vscode.workspace.workspaceFolders; + if (folders) { + folders.map((f) => f.uri).forEach(initRoot); + } + + return vscode.workspace.onDidChangeWorkspaceFolders((event) => { for (const root of event.removed) { - rootRemoved.fire(root.uri); + removeRoot(root.uri); } for (const root of event.added) { - rootAdded.fire(root.uri); + addRoot(root.uri); } }); - const folders = vscode.workspace.workspaceFolders; - return { - roots: folders ? folders.map((f) => f.uri) : [], - onAdded: rootAdded.event, - onRemoved: rootRemoved.event - }; +} + +function createWorkspaceLocator(ext: ExtensionState): WorkspaceLocators { + const locators = new WorkspaceLocators(watchRoots, [ + (root: vscode.Uri) => [ + new WorkspaceVirtualEnvironmentLocator(root.fsPath), + new PoetryLocator(root.fsPath), + new HatchLocator(root.fsPath), + new PixiLocator(root.fsPath), + new CustomWorkspaceLocator(root.fsPath), + ], + // Add an ILocator factory func here for each kind of workspace-rooted locator. + ]); + ext.disposables.push(locators); + return locators; +} + +function getFromStorage(storage: IPersistentStorage): PythonEnvInfo[] { + return storage.get().map((e) => { + if (e.searchLocation) { + if (typeof e.searchLocation === 'string') { + e.searchLocation = Uri.parse(e.searchLocation); + } else if ('scheme' in e.searchLocation && 'path' in e.searchLocation) { + e.searchLocation = Uri.parse(`${e.searchLocation.scheme}://${e.searchLocation.path}`); + } else { + traceError('Unexpected search location', JSON.stringify(e.searchLocation)); + } + } + return e; + }); +} + +function putIntoStorage(storage: IPersistentStorage, envs: PythonEnvInfo[]): Promise { + storage.set( + // We have to `cloneDeep()` here so that we don't overwrite the original `PythonEnvInfo` objects. + cloneDeep(envs).map((e) => { + if (e.searchLocation) { + // Make TS believe it is string. This is temporary. We need to serialize this in + // a custom way. + e.searchLocation = (e.searchLocation.toString() as unknown) as Uri; + } + return e; + }), + ); + return Promise.resolve(); +} + +async function createCollectionCache(ext: ExtensionState): Promise { + const storage = getGlobalStorage(ext.context, PYTHON_ENV_INFO_CACHE_KEY, []); + const cache = await createCache({ + get: () => getFromStorage(storage), + store: async (e) => putIntoStorage(storage, e), + }); + return cache; } diff --git a/src/client/pythonEnvironments/info/environmentInfoService.ts b/src/client/pythonEnvironments/info/environmentInfoService.ts deleted file mode 100644 index 6ab761c86278..000000000000 --- a/src/client/pythonEnvironments/info/environmentInfoService.ts +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { injectable } from 'inversify'; -import { createDeferred, Deferred } from '../../common/utils/async'; -import { createWorkerPool, IWorkerPool, QueuePosition } from '../../common/utils/workerPool'; -import { getInterpreterInfo, InterpreterInformation } from '../base/info/interpreter'; -import { shellExecute } from '../common/externalDependencies'; -import { buildPythonExecInfo } from '../exec'; - -export enum EnvironmentInfoServiceQueuePriority { - Default, - High -} - -export const IEnvironmentInfoService = Symbol('IEnvironmentInfoService'); -export interface IEnvironmentInfoService { - getEnvironmentInfo( - interpreterPath: string, - priority?: EnvironmentInfoServiceQueuePriority - ): Promise; -} - -async function buildEnvironmentInfo(interpreterPath: string): Promise { - const interpreterInfo = await getInterpreterInfo(buildPythonExecInfo(interpreterPath), shellExecute).catch( - () => undefined, - ); - if (interpreterInfo === undefined || interpreterInfo.version === undefined) { - return undefined; - } - return interpreterInfo; -} - -@injectable() -export class EnvironmentInfoService implements IEnvironmentInfoService { - // Caching environment here in-memory. This is so that we don't have to run this on the same - // path again and again in a given session. This information will likely not change in a given - // session. There are definitely cases where this will change. But a simple reload should address - // those. - private readonly cache: Map> = new Map< - string, - Deferred - >(); - - private readonly workerPool: IWorkerPool; - - public constructor() { - this.workerPool = createWorkerPool(buildEnvironmentInfo); - } - - public async getEnvironmentInfo( - interpreterPath: string, - priority?: EnvironmentInfoServiceQueuePriority, - ): Promise { - const result = this.cache.get(interpreterPath); - if (result !== undefined) { - // Another call for this environment has already been made, return its result - return result.promise; - } - const deferred = createDeferred(); - this.cache.set(interpreterPath, deferred); - return (priority === EnvironmentInfoServiceQueuePriority.High - ? this.workerPool.addToQueue(interpreterPath, QueuePosition.Front) - : this.workerPool.addToQueue(interpreterPath, QueuePosition.Back) - ).then((r) => { - deferred.resolve(r); - if (r === undefined) { - this.cache.delete(interpreterPath); - } - return r; - }); - } -} diff --git a/src/client/pythonEnvironments/info/executable.ts b/src/client/pythonEnvironments/info/executable.ts index f561a4cc077e..70c74329c49b 100644 --- a/src/client/pythonEnvironments/info/executable.ts +++ b/src/client/pythonEnvironments/info/executable.ts @@ -1,25 +1,37 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { getExecutable as getPythonExecutableCommand } from '../../common/process/internal/python'; +import { getExecutable } from '../../common/process/internal/python'; +import { ShellExecFunc } from '../../common/process/types'; +import { traceError } from '../../logging'; import { copyPythonExecInfo, PythonExecInfo } from '../exec'; -type ExecResult = { - stdout: string; -}; -type ExecFunc = (command: string, args: string[]) => Promise; - /** * Find the filename for the corresponding Python executable. * * Effectively, we look up `sys.executable`. * * @param python - the information to use when running Python - * @param exec - the function to use to run Python + * @param shellExec - the function to use to run Python */ -export async function getExecutablePath(python: PythonExecInfo, exec: ExecFunc): Promise { - const [args, parse] = getPythonExecutableCommand(); - const info = copyPythonExecInfo(python, args); - const result = await exec(info.command, info.args); - return parse(result.stdout); +export async function getExecutablePath(python: PythonExecInfo, shellExec: ShellExecFunc): Promise { + try { + const [args, parse] = getExecutable(); + const info = copyPythonExecInfo(python, args); + const argv = [info.command, ...info.args]; + // Concat these together to make a set of quoted strings + const quoted = argv.reduce( + (p, c) => (p ? `${p} ${c.toCommandArgumentForPythonExt()}` : `${c.toCommandArgumentForPythonExt()}`), + '', + ); + const result = await shellExec(quoted, { timeout: 15000 }); + const executable = parse(result.stdout.trim()); + if (executable === '') { + throw new Error(`${quoted} resulted in empty stdout`); + } + return executable; + } catch (ex) { + traceError(ex); + return undefined; + } } diff --git a/src/client/pythonEnvironments/info/index.ts b/src/client/pythonEnvironments/info/index.ts index 51fc84285638..08310767914a 100644 --- a/src/client/pythonEnvironments/info/index.ts +++ b/src/client/pythonEnvironments/info/index.ts @@ -3,11 +3,9 @@ 'use strict'; -import * as path from 'path'; -import * as semver from 'semver'; -import { IFileSystem } from '../../common/platform/types'; import { Architecture } from '../../common/utils/platform'; -import { areSameVersion, PythonVersion } from './pythonVersion'; +import { PythonEnvType } from '../base/info'; +import { PythonVersion } from './pythonVersion'; /** * The supported Python environment types. @@ -19,21 +17,40 @@ export enum EnvironmentType { Pipenv = 'PipEnv', Pyenv = 'Pyenv', Venv = 'Venv', - WindowsStore = 'WindowsStore', + MicrosoftStore = 'MicrosoftStore', Poetry = 'Poetry', + Hatch = 'Hatch', + Pixi = 'Pixi', VirtualEnvWrapper = 'VirtualEnvWrapper', + ActiveState = 'ActiveState', Global = 'Global', - System = 'System' + System = 'System', } +/** + * These envs are only created for a specific workspace, which we're able to detect. + */ +export const workspaceVirtualEnvTypes = [EnvironmentType.Poetry, EnvironmentType.Pipenv, EnvironmentType.Pixi]; -type ReleaseLevel = 'alpha' | 'beta' | 'candidate' | 'final' | 'unknown'; +export const virtualEnvTypes = [ + ...workspaceVirtualEnvTypes, + EnvironmentType.Hatch, // This is also a workspace virtual env, but we're not treating it as such as of today. + EnvironmentType.Venv, + EnvironmentType.VirtualEnvWrapper, + EnvironmentType.Conda, + EnvironmentType.VirtualEnv, +]; /** - * The components of a Python version. - * - * These match the elements of `sys.version_info`. + * The IModuleInstaller implementations. */ -export type PythonVersionInfo = [number, number, number, ReleaseLevel]; +export enum ModuleInstallerType { + Unknown = 'Unknown', + Conda = 'Conda', + Pip = 'Pip', + Poetry = 'Poetry', + Pipenv = 'Pipenv', + Pixi = 'Pixi', +} /** * Details about a Python runtime. @@ -59,36 +76,26 @@ export type InterpreterInformation = { * * @prop companyDisplayName - the user-facing name of the distro publisher * @prop displayName - the user-facing name for the environment - * @prop type - the kind of Python environment + * @prop envType - the kind of Python environment * @prop envName - the environment's name, if applicable (else `envPath` is set) * @prop envPath - the environment's root dir, if applicable (else `envName`) * @prop cachedEntry - whether or not the info came from a cache + * @prop type - the type of Python environment, if applicable */ // Note that "cachedEntry" is specific to the caching machinery // and doesn't really belong here. export type PythonEnvironment = InterpreterInformation & { + id?: string; companyDisplayName?: string; displayName?: string; + detailedDisplayName?: string; envType: EnvironmentType; envName?: string; envPath?: string; cachedEntry?: boolean; + type?: PythonEnvType; }; -/** - * Python environment containing only partial info. But it will contain the environment path. - */ -export type PartialPythonEnvironment = Partial> & { path: string }; - -/** - * Standardize the given env info. - * - * @param environment = the env info to normalize - */ -export function normalizeEnvironment(environment: PartialPythonEnvironment): void { - environment.path = path.normalize(environment.path); -} - /** * Convert the Python environment type to a user-facing name. */ @@ -98,7 +105,7 @@ export function getEnvironmentTypeName(environmentType: EnvironmentType): string return 'conda'; } case EnvironmentType.Pipenv: { - return 'pipenv'; + return 'Pipenv'; } case EnvironmentType.Pyenv: { return 'pyenv'; @@ -109,126 +116,26 @@ export function getEnvironmentTypeName(environmentType: EnvironmentType): string case EnvironmentType.VirtualEnv: { return 'virtualenv'; } - case EnvironmentType.WindowsStore: { - return 'windows store'; + case EnvironmentType.MicrosoftStore: { + return 'Microsoft Store'; } case EnvironmentType.Poetry: { - return 'poetry'; + return 'Poetry'; } - default: { - return ''; + case EnvironmentType.Hatch: { + return 'Hatch'; } - } -} - -/** - * 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 areSamePartialEnvironment( - environment1: PartialPythonEnvironment | undefined, - environment2: PartialPythonEnvironment | undefined, - fs: IFileSystem, -): boolean { - if (!environment1 || !environment2) { - return false; - } - if (fs.arePathsSame(environment1.path, environment2.path)) { - return true; - } - if (!areSameVersion(environment1.version, environment2.version)) { - return false; - } - // Could be Python 3.6 with path = python.exe, and Python 3.6 - // and path = python3.exe, so we check the parent directory. - if (!inSameDirectory(environment1.path, environment2.path, fs)) { - return false; - } - return true; -} - -/** - * Update one env info with another. - * - * @param environment - the info to update - * @param other - the info to copy in - */ -export function updateEnvironment(environment: PartialPythonEnvironment, other: PartialPythonEnvironment): void { - // Preserve type information. - // Possible we identified environment as unknown, but a later provider has identified env type. - if (environment.envType === EnvironmentType.Unknown && other.envType && other.envType !== EnvironmentType.Unknown) { - environment.envType = other.envType; - } - const props: (keyof PythonEnvironment)[] = [ - 'envName', - 'envPath', - 'path', - 'sysPrefix', - 'architecture', - 'sysVersion', - 'version', - 'pipEnvWorkspaceFolder', - ]; - props.forEach((prop) => { - if (!environment[prop] && other[prop]) { - // tslint:disable-next-line: no-any - (environment as any)[prop] = other[prop]; + case EnvironmentType.Pixi: { + return 'pixi'; } - }); -} - -/** - * Combine env info for matching environments. - * - * Environments are matched by path and version. - * - * @param environments - the env infos to merge - */ -export function mergeEnvironments( - environments: PartialPythonEnvironment[], - fs: IFileSystem, -): PartialPythonEnvironment[] { - return environments.reduce((accumulator, current) => { - const existingItem = accumulator.find((item) => areSamePartialEnvironment(current, item, fs)); - if (!existingItem) { - const copied: PartialPythonEnvironment = { ...current }; - normalizeEnvironment(copied); - accumulator.push(copied); - } else { - updateEnvironment(existingItem, current); + case EnvironmentType.VirtualEnvWrapper: { + return 'virtualenvwrapper'; + } + case EnvironmentType.ActiveState: { + return 'ActiveState'; + } + default: { + return ''; } - return accumulator; - }, []); -} - -/** - * Determine if the given paths are in the same directory. - * - * @param path1 - one of the two paths to compare - * @param path2 - one of the two paths to compare - */ -export function inSameDirectory(path1: string | undefined, path2: string | undefined, fs: IFileSystem): boolean { - if (!path1 || !path2) { - return false; - } - const dir1 = path.dirname(path1); - const dir2 = path.dirname(path2); - return fs.arePathsSame(dir1, dir2); -} - -/** - * Build a version-sorted list from the given one, with lowest first. - */ -export function sortInterpreters(interpreters: PythonEnvironment[]): PythonEnvironment[] { - if (interpreters.length === 0) { - return []; - } - if (interpreters.length === 1) { - return [interpreters[0]]; } - const sorted = interpreters.slice(); - sorted.sort((a, b) => (a.version && b.version ? semver.compare(a.version.raw, b.version.raw) : 0)); - return sorted; } diff --git a/src/client/pythonEnvironments/info/interpreter.ts b/src/client/pythonEnvironments/info/interpreter.ts index fb0b11f0506a..8fe9bc7d49a8 100644 --- a/src/client/pythonEnvironments/info/interpreter.ts +++ b/src/client/pythonEnvironments/info/interpreter.ts @@ -1,11 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { SemVer } from 'semver'; import { InterpreterInformation } from '.'; -import { interpreterInfo as getInterpreterInfoCommand, InterpreterInfoJson } from '../../common/process/internal/scripts'; +import { + interpreterInfo as getInterpreterInfoCommand, + InterpreterInfoJson, +} from '../../common/process/internal/scripts'; +import { ShellExecFunc } from '../../common/process/types'; +import { replaceAll } from '../../common/stringUtils'; import { Architecture } from '../../common/utils/platform'; import { copyPythonExecInfo, PythonExecInfo } from '../exec'; -import { parsePythonVersion } from './pythonVersion'; /** * Compose full interpreter information based on the given data. @@ -15,25 +20,35 @@ import { parsePythonVersion } from './pythonVersion'; * @param python - the path to the Python executable * @param raw - the information returned by the `interpreterInfo.py` script */ -export function extractInterpreterInfo(python: string, raw: InterpreterInfoJson): InterpreterInformation { - const rawVersion = `${raw.versionInfo.slice(0, 3).join('.')}-${raw.versionInfo[3]}`; +function extractInterpreterInfo(python: string, raw: InterpreterInfoJson): InterpreterInformation { + let rawVersion = `${raw.versionInfo.slice(0, 3).join('.')}`; + // We only need additional version details if the version is 'alpha', 'beta' or 'candidate'. + // This restriction is needed to avoid sending any PII if this data is used with telemetry. + // With custom builds of python it is possible that release level and values after that can + // contain PII. + if (raw.versionInfo[3] !== undefined && ['alpha', 'beta', 'candidate'].includes(raw.versionInfo[3])) { + rawVersion = `${rawVersion}-${raw.versionInfo[3]}`; + if (raw.versionInfo[4] !== undefined) { + let serial = -1; + try { + serial = parseInt(`${raw.versionInfo[4]}`, 10); + } catch (ex) { + serial = -1; + } + rawVersion = serial >= 0 ? `${rawVersion}${serial}` : rawVersion; + } + } return { architecture: raw.is64Bit ? Architecture.x64 : Architecture.x86, path: python, - version: parsePythonVersion(rawVersion), + version: new SemVer(rawVersion), sysVersion: raw.sysVersion, sysPrefix: raw.sysPrefix, }; } -type ShellExecResult = { - stdout: string; - stderr?: string; -}; -type ShellExecFunc = (command: string, timeout: number) => Promise; - type Logger = { - info(msg: string): void; + verbose(msg: string): void; error(msg: string): void; }; @@ -54,7 +69,7 @@ export async function getInterpreterInfo( const argv = [info.command, ...info.args]; // Concat these together to make a set of quoted strings - const quoted = argv.reduce((p, c) => (p ? `${p} "${c}"` : `"${c.replace('\\', '\\\\')}"`), ''); + const quoted = argv.reduce((p, c) => (p ? `${p} "${c}"` : `"${replaceAll(c, '\\', '\\\\')}"`), ''); // Try shell execing the command, followed by the arguments. This will make node kill the process if it // takes too long. @@ -62,16 +77,18 @@ export async function getInterpreterInfo( // See these two bugs: // https://github.com/microsoft/vscode-python/issues/7569 // https://github.com/microsoft/vscode-python/issues/7760 - const result = await shellExec(quoted, 15000); + const result = await shellExec(quoted, { timeout: 15000 }); if (result.stderr) { if (logger) { logger.error(`Failed to parse interpreter information for ${argv} stderr: ${result.stderr}`); } - return; } const json = parse(result.stdout); if (logger) { - logger.info(`Found interpreter for ${argv}`); + logger.verbose(`Found interpreter for ${argv}`); + } + if (!json) { + return undefined; } return extractInterpreterInfo(python.pythonExecutable, json); } diff --git a/src/client/pythonEnvironments/info/pythonVersion.ts b/src/client/pythonEnvironments/info/pythonVersion.ts index ccd9524d0388..d61fcf14db4d 100644 --- a/src/client/pythonEnvironments/info/pythonVersion.ts +++ b/src/client/pythonEnvironments/info/pythonVersion.ts @@ -1,10 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { SemVer } from 'semver'; -import '../../common/extensions'; // For string.splitLines() -import { getVersion as getPythonVersionCommand } from '../../common/process/internal/python'; - /** * A representation of a Python runtime's version. * @@ -30,84 +26,10 @@ export type PythonVersion = { prerelease: string[]; }; -/** - * Convert a Python version string. - * - * The supported formats are: - * - * * MAJOR.MINOR.MICRO-RELEASE_LEVEL - * - * (where RELEASE_LEVEL is one of {alpha,beta,candidate,final}) - * - * Everything else, including an empty string, results in `undefined`. - */ -// Eventually we will want to also support the release serial -// (e.g. beta1, candidate3) and maybe even release abbreviations -// (e.g. 3.9.2b1, 3.8.10rc3). -export function parsePythonVersion(raw: string): PythonVersion | undefined { - if (!raw || raw.trim().length === 0) { - return; - } - const versionParts = (raw || '') - .split('.') - .map((item) => item.trim()) - .filter((item) => item.length > 0) - .filter((_, index) => index < 4); - - if (versionParts.length > 0 && versionParts[versionParts.length - 1].indexOf('-') > 0) { - const lastPart = versionParts[versionParts.length - 1]; - versionParts[versionParts.length - 1] = lastPart.split('-')[0].trim(); - versionParts.push(lastPart.split('-')[1].trim()); - } - while (versionParts.length < 4) { - versionParts.push(''); - } - // Exclude PII from `version_info` to ensure we don't send this up via telemetry. - for (let index = 0; index < 3; index += 1) { - versionParts[index] = /^\d+$/.test(versionParts[index]) ? versionParts[index] : '0'; - } - if (['alpha', 'beta', 'candidate', 'final'].indexOf(versionParts[3]) === -1) { - versionParts.pop(); - } - const numberParts = `${versionParts[0]}.${versionParts[1]}.${versionParts[2]}`; - const rawVersion = versionParts.length === 4 ? `${numberParts}-${versionParts[3]}` : numberParts; - return new SemVer(rawVersion); -} - -/** - * Determine if the given versions are the same. - * - * @param version1 - one of the two versions to compare - * @param version2 - one of the two versions to compare - */ -export function areSameVersion(version1?: PythonVersion, version2?: PythonVersion): boolean { - if (!version1 || !version2) { - return false; - } - return version1.raw === version2.raw; -} - -type ExecResult = { - stdout: string; -}; -type ExecFunc = (command: string, args: string[]) => Promise; - -/** - * Get the version string of the given Python executable by running it. - * - * Effectively, we look up `sys.version`. - * - * @param pythonPath - the Python executable to exec - * @param defaultValue - the value to return if anything goes wrong - * @param exec - the function to call to run the Python executable - */ -export async function getPythonVersion(pythonPath: string, defaultValue: string, exec: ExecFunc): Promise { - const [args, parse] = getPythonVersionCommand(); - // It may make sense eventually to use buildPythonExecInfo() here - // instead of using pythonPath and args directly. That would allow - // buildPythonExecInfo() to assume any burden of flexibility. - return exec(pythonPath, args) - .then((result) => parse(result.stdout).splitLines()[0]) - .then((version) => (version.length === 0 ? defaultValue : version)) - .catch(() => defaultValue); +export function isStableVersion(version: PythonVersion): boolean { + // A stable version is one that has no prerelease tags. + return ( + version.prerelease.length === 0 && + (version.build.length === 0 || (version.build.length === 1 && version.build[0] === 'final')) + ); } diff --git a/src/client/pythonEnvironments/legacyIOC.ts b/src/client/pythonEnvironments/legacyIOC.ts index d6c199d9e301..49df2ee03f21 100644 --- a/src/client/pythonEnvironments/legacyIOC.ts +++ b/src/client/pythonEnvironments/legacyIOC.ts @@ -2,88 +2,59 @@ // Licensed under the MIT License. import { injectable } from 'inversify'; +import { intersection } from 'lodash'; import * as vscode from 'vscode'; -import { getVersionString, parseVersion } from '../common/utils/version'; +import { FileChangeType } from '../common/platform/fileSystemWatcher'; +import { Resource } from '../common/types'; +import { IComponentAdapter, ICondaService, PythonEnvironmentsChangedEvent } from '../interpreter/contracts'; +import { IServiceManager } from '../ioc/types'; +import { PythonEnvInfo, PythonEnvKind, PythonEnvSource } from './base/info'; import { - CONDA_ENV_FILE_SERVICE, - CONDA_ENV_SERVICE, - CURRENT_PATH_SERVICE, - GLOBAL_VIRTUAL_ENV_SERVICE, - IComponentAdapter, - ICondaService, - IInterpreterLocatorHelper, - IInterpreterLocatorProgressService, - IInterpreterLocatorService, - IInterpreterWatcher, - IInterpreterWatcherBuilder, - IKnownSearchPathsForInterpreters, - INTERPRETER_LOCATOR_SERVICE, - IVirtualEnvironmentsSearchPathProvider, - KNOWN_PATH_SERVICE, - PIPENV_SERVICE, - WINDOWS_REGISTRY_SERVICE, - WORKSPACE_VIRTUAL_ENV_SERVICE, -} from '../interpreter/contracts'; -import { IPipEnvServiceHelper, IPythonInPathCommandProvider } from '../interpreter/locators/types'; -import { IServiceContainer, IServiceManager } from '../ioc/types'; -import { PythonEnvInfo, PythonEnvKind, PythonReleaseLevel } from './base/info'; -import { buildEnvInfo } from './base/info/env'; -import { ILocator, PythonLocatorQuery } from './base/locator'; -import { getEnvs } from './base/locatorUtils'; -import { initializeExternalDependencies } from './common/externalDependencies'; -import { PythonInterpreterLocatorService } from './discovery/locators'; -import { InterpreterLocatorHelper } from './discovery/locators/helpers'; -import { InterpreterLocatorProgressService } from './discovery/locators/progressService'; -import { CondaEnvironmentInfo } from './discovery/locators/services/conda'; -import { CondaEnvFileService } from './discovery/locators/services/condaEnvFileService'; -import { CondaEnvService } from './discovery/locators/services/condaEnvService'; -import { CondaService } from './discovery/locators/services/condaService'; -import { CurrentPathService, PythonInPathCommandProvider } from './discovery/locators/services/currentPathService'; -import { - GlobalVirtualEnvironmentsSearchPathProvider, - GlobalVirtualEnvService, -} from './discovery/locators/services/globalVirtualEnvService'; -import { InterpreterHashProvider } from './discovery/locators/services/hashProvider'; -import { InterpeterHashProviderFactory } from './discovery/locators/services/hashProviderFactory'; -import { InterpreterWatcherBuilder } from './discovery/locators/services/interpreterWatcherBuilder'; -import { KnownPathsService, KnownSearchPathsForInterpreters } from './discovery/locators/services/KnownPathsService'; -import { PipEnvService } from './discovery/locators/services/pipEnvService'; -import { PipEnvServiceHelper } from './discovery/locators/services/pipEnvServiceHelper'; -import { WindowsRegistryService } from './discovery/locators/services/windowsRegistryService'; -import { WindowsStoreInterpreter } from './discovery/locators/services/windowsStoreInterpreter'; -import { - WorkspaceVirtualEnvironmentsSearchPathProvider, - WorkspaceVirtualEnvService, -} from './discovery/locators/services/workspaceVirtualEnvService'; -import { WorkspaceVirtualEnvWatcherService } from './discovery/locators/services/workspaceVirtualEnvWatcherService'; + GetRefreshEnvironmentsOptions, + IDiscoveryAPI, + PythonLocatorQuery, + TriggerRefreshOptions, +} from './base/locator'; +import { isMacDefaultPythonPath } from './common/environmentManagers/macDefault'; +import { isParentPath } from './common/externalDependencies'; import { EnvironmentType, PythonEnvironment } from './info'; -import { EnvironmentInfoService, IEnvironmentInfoService } from './info/environmentInfoService'; - -const convertedKinds = new Map(Object.entries({ - [PythonEnvKind.System]: EnvironmentType.System, - [PythonEnvKind.MacDefault]: EnvironmentType.System, - [PythonEnvKind.WindowsStore]: EnvironmentType.WindowsStore, - [PythonEnvKind.Pyenv]: EnvironmentType.Pyenv, - [PythonEnvKind.Conda]: EnvironmentType.Conda, - [PythonEnvKind.CondaBase]: EnvironmentType.Conda, - [PythonEnvKind.VirtualEnv]: EnvironmentType.VirtualEnv, - [PythonEnvKind.Pipenv]: EnvironmentType.Pipenv, - [PythonEnvKind.Venv]: EnvironmentType.Venv, -})); +import { toSemverLikeVersion } from './base/info/pythonVersion'; +import { PythonVersion } from './info/pythonVersion'; +import { createDeferred } from '../common/utils/async'; +import { PythonEnvCollectionChangedEvent } from './base/watcher'; +import { asyncFilter } from '../common/utils/arrayUtils'; +import { CondaEnvironmentInfo, isCondaEnvironment } from './common/environmentManagers/conda'; +import { isMicrosoftStoreEnvironment } from './common/environmentManagers/microsoftStoreEnv'; +import { CondaService } from './common/environmentManagers/condaService'; +import { traceError, traceVerbose } from '../logging'; + +const convertedKinds = new Map( + Object.entries({ + [PythonEnvKind.OtherGlobal]: EnvironmentType.Global, + [PythonEnvKind.System]: EnvironmentType.System, + [PythonEnvKind.MicrosoftStore]: EnvironmentType.MicrosoftStore, + [PythonEnvKind.Pyenv]: EnvironmentType.Pyenv, + [PythonEnvKind.Conda]: EnvironmentType.Conda, + [PythonEnvKind.VirtualEnv]: EnvironmentType.VirtualEnv, + [PythonEnvKind.Pipenv]: EnvironmentType.Pipenv, + [PythonEnvKind.Poetry]: EnvironmentType.Poetry, + [PythonEnvKind.Hatch]: EnvironmentType.Hatch, + [PythonEnvKind.Pixi]: EnvironmentType.Pixi, + [PythonEnvKind.Venv]: EnvironmentType.Venv, + [PythonEnvKind.VirtualEnvWrapper]: EnvironmentType.VirtualEnvWrapper, + [PythonEnvKind.ActiveState]: EnvironmentType.ActiveState, + }), +); + +export function convertEnvInfoToPythonEnvironment(info: PythonEnvInfo): PythonEnvironment { + return convertEnvInfo(info); +} function convertEnvInfo(info: PythonEnvInfo): PythonEnvironment { - const { - name, - location, - executable, - arch, - kind, - searchLocation, - version, - distro, - } = info; + const { name, location, executable, arch, kind, version, distro, id } = info; const { filename, sysPrefix } = executable; const env: PythonEnvironment = { + id, sysPrefix, envType: EnvironmentType.Unknown, envName: name, @@ -98,274 +69,228 @@ function convertEnvInfo(info: PythonEnvInfo): PythonEnvironment { } // Otherwise it stays Unknown. - if (searchLocation !== undefined) { - if (kind === PythonEnvKind.Pipenv) { - env.pipEnvWorkspaceFolder = searchLocation.fsPath; - } - } - if (version !== undefined) { const { release, sysVersion } = version; if (release === undefined) { - const versionStr = `${getVersionString(version)}-final`; - env.version = parseVersion(versionStr); env.sysVersion = ''; } else { - const { level, serial } = release; - const releaseStr = level === PythonReleaseLevel.Final - ? 'final' - : `${level}${serial}`; - const versionStr = `${getVersionString(version)}-${releaseStr}`; - env.version = parseVersion(versionStr); env.sysVersion = sysVersion; } + + const semverLikeVersion: PythonVersion = toSemverLikeVersion(version); + env.version = semverLikeVersion; } if (distro !== undefined && distro.org !== '') { env.companyDisplayName = distro.org; } - // We do not worry about using distro.defaultDisplayName - // or info.defaultDisplayName. + env.displayName = info.display; + env.detailedDisplayName = info.detailedDisplayName; + env.type = info.type; + // We do not worry about using distro.defaultDisplayName. return env; } - -interface IPythonEnvironments extends ILocator {} - @injectable() class ComponentAdapter implements IComponentAdapter { + private readonly changed = new vscode.EventEmitter(); + constructor( // The adapter only wraps one thing: the component API. - private readonly api: IPythonEnvironments, - // For now we effectively disable the component. - private readonly enabled = false, - ) {} + private readonly api: IDiscoveryAPI, + ) { + this.api.onChanged((event) => { + this.changed.fire({ + type: event.type, + new: event.new ? convertEnvInfo(event.new) : undefined, + old: event.old ? convertEnvInfo(event.old) : undefined, + resource: event.searchLocation, + }); + }); + } - // IInterpreterHelper + public triggerRefresh(query?: PythonLocatorQuery, options?: TriggerRefreshOptions): Promise { + return this.api.triggerRefresh(query, options); + } - // A result of `undefined` means "Fall back to the old code!" - public async getInterpreterInformation(pythonPath: string): Promise> { - if (!this.enabled) { - return undefined; - } - const env = await this.api.resolveEnv(pythonPath); - if (env === undefined) { - return undefined; - } - return convertEnvInfo(env); + public getRefreshPromise(options?: GetRefreshEnvironmentsOptions) { + return this.api.getRefreshPromise(options); } - // A result of `undefined` means "Fall back to the old code!" - public async isMacDefaultPythonPath(pythonPath: string): Promise { - if (!this.enabled) { - return undefined; - } + public get onProgress() { + return this.api.onProgress; + } + + public get onChanged() { + return this.changed.event; + } + + // For use in VirtualEnvironmentPrompt.activate() + + // Call callback if an environment gets created within the resource provided. + public onDidCreate(resource: Resource, callback: () => void): vscode.Disposable { + const workspaceFolder = resource ? vscode.workspace.getWorkspaceFolder(resource) : undefined; + return this.api.onChanged((e) => { + if (!workspaceFolder || !e.searchLocation) { + return; + } + traceVerbose(`Received event ${JSON.stringify(e)} file change event`); + if ( + e.type === FileChangeType.Created && + isParentPath(e.searchLocation.fsPath, workspaceFolder.uri.fsPath) + ) { + callback(); + } + }); + } + + // Implements IInterpreterHelper + public async getInterpreterInformation(pythonPath: string): Promise | undefined> { const env = await this.api.resolveEnv(pythonPath); - if (env === undefined) { - return undefined; - } - return env.kind === PythonEnvKind.MacDefault; + return env ? convertEnvInfo(env) : undefined; } - // IInterpreterService + // eslint-disable-next-line class-methods-use-this + public async isMacDefaultPythonPath(pythonPath: string): Promise { + // While `ComponentAdapter` represents how the component would be used in the rest of the + // extension, we cheat here for the sake of performance. This is not a problem because when + // we start using the component's public API directly we will be dealing with `PythonEnvInfo` + // instead of just `pythonPath`. + return isMacDefaultPythonPath(pythonPath); + } - // We use the same getInterpreters() here as for IInterpreterLocatorService. + // Implements IInterpreterService - // A result of `undefined` means "Fall back to the old code!" - public async getInterpreterDetails( - pythonPath: string, - resource?: vscode.Uri, - ): Promise { - if (!this.enabled) { - return undefined; - } - const info = buildEnvInfo({ executable: pythonPath }); - if (resource !== undefined) { - const wsFolder = vscode.workspace.getWorkspaceFolder(resource); - if (wsFolder !== undefined) { - info.searchLocation = wsFolder.uri; + // We use the same getInterpreters() here as for IInterpreterLocatorService. + public async getInterpreterDetails(pythonPath: string): Promise { + try { + const env = await this.api.resolveEnv(pythonPath); + if (!env) { + return undefined; } - } - const env = await this.api.resolveEnv(info); - if (env === undefined) { + return convertEnvInfo(env); + } catch (ex) { + traceError(`Failed to resolve interpreter: ${pythonPath}`, ex); return undefined; } - return convertEnvInfo(env); } - // ICondaService + // Implements ICondaService - // A result of `undefined` means "Fall back to the old code!" - public async isCondaEnvironment(interpreterPath: string): Promise { - if (!this.enabled) { - return undefined; - } - const env = await this.api.resolveEnv(interpreterPath); - if (env === undefined) { - return undefined; - } - return env.kind === PythonEnvKind.Conda; + // eslint-disable-next-line class-methods-use-this + public async isCondaEnvironment(interpreterPath: string): Promise { + // While `ComponentAdapter` represents how the component would be used in the rest of the + // extension, we cheat here for the sake of performance. This is not a problem because when + // we start using the component's public API directly we will be dealing with `PythonEnvInfo` + // instead of just `pythonPath`. + return isCondaEnvironment(interpreterPath); } - // A result of `undefined` means "Fall back to the old code!" public async getCondaEnvironment(interpreterPath: string): Promise { - if (!this.enabled) { + if (!(await isCondaEnvironment(interpreterPath))) { + // Undefined is expected here when the env is not Conda env. return undefined; } - const env = await this.api.resolveEnv(interpreterPath); - if (env === undefined) { - return undefined; - } - if (env.kind !== PythonEnvKind.Conda) { - return undefined; - } - if (env.name !== '') { - return { name: env.name, path: '' }; - } - // else - return { name: '', path: env.location }; - } - // IWindowsStoreInterpreter + // The API getCondaEnvironment() is not called automatically, unless user attempts to install or activate environments + // So calling resolveEnv() which although runs python unnecessarily, is not that expensive here. + const env = await this.api.resolveEnv(interpreterPath); - // A result of `undefined` means "Fall back to the old code!" - public async isWindowsStoreInterpreter(pythonPath: string): Promise { - if (!this.enabled) { + if (!env) { return undefined; } - const env = await this.api.resolveEnv(pythonPath); - if (env === undefined) { - return undefined; - } - return env.kind === PythonEnvKind.WindowsStore; + + return { name: env.name, path: env.location }; } - // IInterpreterLocatorService + // eslint-disable-next-line class-methods-use-this + public async isMicrosoftStoreInterpreter(pythonPath: string): Promise { + // Eventually we won't be calling 'isMicrosoftStoreInterpreter' in the component adapter, so we won't + // need to use 'isMicrosoftStoreEnvironment' directly here. This is just a temporary implementation. + return isMicrosoftStoreEnvironment(pythonPath); + } - // A result of `undefined` means "Fall back to the old code!" - public get hasInterpreters(): Promise { - if (!this.enabled) { - return Promise.resolve(undefined); + // Implements IInterpreterLocatorService + public async hasInterpreters( + filter: (e: PythonEnvironment) => Promise = async () => true, + ): Promise { + const onAddedToCollection = createDeferred(); + // Watch for collection changed events. + this.api.onChanged(async (e: PythonEnvCollectionChangedEvent) => { + if (e.new) { + if (await filter(convertEnvInfo(e.new))) { + onAddedToCollection.resolve(); + } + } + }); + const initialEnvs = await asyncFilter(this.api.getEnvs(), (e) => filter(convertEnvInfo(e))); + if (initialEnvs.length > 0) { + return true; } - const iterator = this.api.iterEnvs(); - return iterator.next().then((res) => !res.done); + // Wait for an env to be added to the collection until the refresh has finished. Note although it's not + // guaranteed we have initiated discovery in this session, we do trigger refresh in the very first session, + // when Python is not installed, etc. Assuming list is more or less upto date. + await Promise.race([onAddedToCollection.promise, this.api.getRefreshPromise()]); + const envs = await asyncFilter(this.api.getEnvs(), (e) => filter(convertEnvInfo(e))); + return envs.length > 0; } - // A result of `undefined` means "Fall back to the old code!" - public async getInterpreters( - resource?: vscode.Uri, - // Currently we have no plans to support GetInterpreterLocatorOptions: - // { - // ignoreCache?: boolean - // onSuggestion?: boolean; - // } - ): Promise { - if (!this.enabled) { - return undefined; - } + public getInterpreters(resource?: vscode.Uri, source?: PythonEnvSource[]): PythonEnvironment[] { const query: PythonLocatorQuery = {}; + let roots: vscode.Uri[] = []; + let wsFolder: vscode.WorkspaceFolder | undefined; if (resource !== undefined) { - const wsFolder = vscode.workspace.getWorkspaceFolder(resource); - if (wsFolder !== undefined) { - query.searchLocations = { roots: [wsFolder.uri] }; + wsFolder = vscode.workspace.getWorkspaceFolder(resource); + if (wsFolder) { + roots = [wsFolder.uri]; } } + // Untitled files should still use the workspace as the query location + if ( + !wsFolder && + vscode.workspace.workspaceFolders && + vscode.workspace.workspaceFolders.length > 0 && + (!resource || resource.scheme === 'untitled') + ) { + roots = vscode.workspace.workspaceFolders.map((w) => w.uri); + } - const iterator = this.api.iterEnvs(query); - const envs = await getEnvs(iterator); + query.searchLocations = { + roots, + }; + + let envs = this.api.getEnvs(query); + if (source) { + envs = envs.filter((env) => intersection(source, env.source).length > 0); + } + + return envs.map(convertEnvInfo); + } + + public async getWorkspaceVirtualEnvInterpreters( + resource: vscode.Uri, + options?: { ignoreCache?: boolean }, + ): Promise { + const workspaceFolder = vscode.workspace.getWorkspaceFolder(resource); + if (!workspaceFolder) { + return []; + } + const query: PythonLocatorQuery = { + searchLocations: { + roots: [workspaceFolder.uri], + doNotIncludeNonRooted: true, + }, + }; + if (options?.ignoreCache) { + await this.api.triggerRefresh(query); + } + await this.api.getRefreshPromise(); + const envs = this.api.getEnvs(query); return envs.map(convertEnvInfo); } } -export function registerForIOC( - serviceManager: IServiceManager, - serviceContainer: IServiceContainer, - api: IPythonEnvironments, -): void { - const adapter = new ComponentAdapter(api); - serviceManager.addSingletonInstance(IComponentAdapter, adapter); - - serviceManager.addSingleton(IInterpreterLocatorHelper, InterpreterLocatorHelper); - serviceManager.addSingleton( - IInterpreterLocatorService, - PythonInterpreterLocatorService, - INTERPRETER_LOCATOR_SERVICE, - ); - serviceManager.addSingleton( - IInterpreterLocatorProgressService, - InterpreterLocatorProgressService, - ); - serviceManager.addSingleton( - IInterpreterLocatorService, - CondaEnvFileService, - CONDA_ENV_FILE_SERVICE, - ); - serviceManager.addSingleton( - IInterpreterLocatorService, - CondaEnvService, - CONDA_ENV_SERVICE, - ); - serviceManager.addSingleton( - IInterpreterLocatorService, - CurrentPathService, - CURRENT_PATH_SERVICE, - ); - serviceManager.addSingleton( - IInterpreterLocatorService, - GlobalVirtualEnvService, - GLOBAL_VIRTUAL_ENV_SERVICE, - ); - serviceManager.addSingleton( - IInterpreterLocatorService, - WorkspaceVirtualEnvService, - WORKSPACE_VIRTUAL_ENV_SERVICE, - ); - serviceManager.addSingleton(IInterpreterLocatorService, PipEnvService, PIPENV_SERVICE); - - serviceManager.addSingleton( - IInterpreterLocatorService, - WindowsRegistryService, - WINDOWS_REGISTRY_SERVICE, - ); - serviceManager.addSingleton( - IInterpreterLocatorService, - KnownPathsService, - KNOWN_PATH_SERVICE, - ); +export function registerNewDiscoveryForIOC(serviceManager: IServiceManager, api: IDiscoveryAPI): void { serviceManager.addSingleton(ICondaService, CondaService); - serviceManager.addSingleton(IPipEnvServiceHelper, PipEnvServiceHelper); - serviceManager.addSingleton( - IPythonInPathCommandProvider, - PythonInPathCommandProvider, - ); - - serviceManager.add( - IInterpreterWatcher, - WorkspaceVirtualEnvWatcherService, - WORKSPACE_VIRTUAL_ENV_SERVICE, - ); - serviceManager.addSingleton(WindowsStoreInterpreter, WindowsStoreInterpreter); - serviceManager.addSingleton(InterpreterHashProvider, InterpreterHashProvider); - serviceManager.addSingleton( - InterpeterHashProviderFactory, - InterpeterHashProviderFactory, - ); - serviceManager.addSingleton( - IVirtualEnvironmentsSearchPathProvider, - GlobalVirtualEnvironmentsSearchPathProvider, - 'global', - ); - serviceManager.addSingleton( - IVirtualEnvironmentsSearchPathProvider, - WorkspaceVirtualEnvironmentsSearchPathProvider, - 'workspace', - ); - serviceManager.addSingleton( - IKnownSearchPathsForInterpreters, - KnownSearchPathsForInterpreters, - ); - serviceManager.addSingleton(IInterpreterWatcherBuilder, InterpreterWatcherBuilder); - - serviceManager.addSingletonInstance(IEnvironmentInfoService, new EnvironmentInfoService()); - initializeExternalDependencies(serviceContainer); + serviceManager.addSingletonInstance(IComponentAdapter, new ComponentAdapter(api)); } diff --git a/src/client/pythonEnvironments/nativeAPI.ts b/src/client/pythonEnvironments/nativeAPI.ts new file mode 100644 index 000000000000..62695c8dd543 --- /dev/null +++ b/src/client/pythonEnvironments/nativeAPI.ts @@ -0,0 +1,548 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { Disposable, Event, EventEmitter, Uri, WorkspaceFoldersChangeEvent } from 'vscode'; +import { PythonEnvInfo, PythonEnvKind, PythonEnvType, PythonVersion } from './base/info'; +import { + GetRefreshEnvironmentsOptions, + IDiscoveryAPI, + ProgressNotificationEvent, + ProgressReportStage, + PythonLocatorQuery, + TriggerRefreshOptions, +} from './base/locator'; +import { PythonEnvCollectionChangedEvent } from './base/watcher'; +import { + isNativeEnvInfo, + NativeEnvInfo, + NativeEnvManagerInfo, + NativePythonFinder, +} from './base/locators/common/nativePythonFinder'; +import { createDeferred, Deferred } from '../common/utils/async'; +import { Architecture, getPathEnvVariable, getUserHomeDir } from '../common/utils/platform'; +import { parseVersion } from './base/info/pythonVersion'; +import { cache } from '../common/utils/decorators'; +import { traceError, traceInfo, traceLog, traceWarn } from '../logging'; +import { StopWatch } from '../common/utils/stopWatch'; +import { FileChangeType } from '../common/platform/fileSystemWatcher'; +import { categoryToKind, NativePythonEnvironmentKind } from './base/locators/common/nativePythonUtils'; +import { getCondaEnvDirs, getCondaPathSetting, setCondaBinary } from './common/environmentManagers/conda'; +import { setPyEnvBinary } from './common/environmentManagers/pyenv'; +import { + createPythonWatcher, + PythonGlobalEnvEvent, + PythonWorkspaceEnvEvent, +} from './base/locators/common/pythonWatcher'; +import { getWorkspaceFolders, onDidChangeWorkspaceFolders } from '../common/vscodeApis/workspaceApis'; + +function makeExecutablePath(prefix?: string): string { + if (!prefix) { + return process.platform === 'win32' ? 'python.exe' : 'python'; + } + return process.platform === 'win32' ? path.join(prefix, 'python.exe') : path.join(prefix, 'python'); +} + +function toArch(a: string | undefined): Architecture { + switch (a) { + case 'x86': + return Architecture.x86; + case 'x64': + return Architecture.x64; + default: + return Architecture.Unknown; + } +} + +function getLocation(nativeEnv: NativeEnvInfo, executable: string): string { + if (nativeEnv.kind === NativePythonEnvironmentKind.Conda) { + return nativeEnv.prefix ?? path.dirname(executable); + } + + if (nativeEnv.executable) { + return nativeEnv.executable; + } + + if (nativeEnv.prefix) { + return nativeEnv.prefix; + } + + // This is a path to a generated executable. Needed for backwards compatibility. + return executable; +} + +function kindToShortString(kind: PythonEnvKind): string | undefined { + switch (kind) { + case PythonEnvKind.Poetry: + return 'poetry'; + case PythonEnvKind.Pyenv: + return 'pyenv'; + case PythonEnvKind.VirtualEnv: + case PythonEnvKind.Venv: + case PythonEnvKind.VirtualEnvWrapper: + case PythonEnvKind.OtherVirtual: + return 'venv'; + case PythonEnvKind.Pipenv: + return 'pipenv'; + case PythonEnvKind.Conda: + return 'conda'; + case PythonEnvKind.ActiveState: + return 'active-state'; + case PythonEnvKind.MicrosoftStore: + return 'Microsoft Store'; + case PythonEnvKind.Hatch: + return 'hatch'; + case PythonEnvKind.Pixi: + return 'pixi'; + case PythonEnvKind.System: + case PythonEnvKind.Unknown: + case PythonEnvKind.OtherGlobal: + case PythonEnvKind.Custom: + default: + return undefined; + } +} + +function toShortVersionString(version: PythonVersion): string { + return `${version.major}.${version.minor}.${version.micro}`.trim(); +} + +function getDisplayName(version: PythonVersion, kind: PythonEnvKind, arch: Architecture, name?: string): string { + const versionStr = toShortVersionString(version); + const kindStr = kindToShortString(kind); + if (arch === Architecture.x86) { + if (kindStr) { + return name ? `Python ${versionStr} 32-bit (${name})` : `Python ${versionStr} 32-bit (${kindStr})`; + } + return name ? `Python ${versionStr} 32-bit (${name})` : `Python ${versionStr} 32-bit`; + } + if (kindStr) { + return name ? `Python ${versionStr} (${name})` : `Python ${versionStr} (${kindStr})`; + } + return name ? `Python ${versionStr} (${name})` : `Python ${versionStr}`; +} + +function validEnv(nativeEnv: NativeEnvInfo): boolean { + if (nativeEnv.prefix === undefined && nativeEnv.executable === undefined) { + traceError(`Invalid environment [native]: ${JSON.stringify(nativeEnv)}`); + return false; + } + return true; +} + +function getEnvType(kind: PythonEnvKind): PythonEnvType | undefined { + switch (kind) { + case PythonEnvKind.Poetry: + case PythonEnvKind.Pyenv: + case PythonEnvKind.VirtualEnv: + case PythonEnvKind.Venv: + case PythonEnvKind.VirtualEnvWrapper: + case PythonEnvKind.OtherVirtual: + case PythonEnvKind.Pipenv: + case PythonEnvKind.ActiveState: + case PythonEnvKind.Hatch: + case PythonEnvKind.Pixi: + return PythonEnvType.Virtual; + + case PythonEnvKind.Conda: + return PythonEnvType.Conda; + + case PythonEnvKind.System: + case PythonEnvKind.Unknown: + case PythonEnvKind.OtherGlobal: + case PythonEnvKind.Custom: + case PythonEnvKind.MicrosoftStore: + default: + return undefined; + } +} + +function isSubDir(pathToCheck: string | undefined, parents: string[]): boolean { + return parents.some((prefix) => { + if (pathToCheck) { + return path.normalize(pathToCheck).startsWith(path.normalize(prefix)); + } + return false; + }); +} + +function foundOnPath(fsPath: string): boolean { + const paths = getPathEnvVariable().map((p) => path.normalize(p).toLowerCase()); + const normalized = path.normalize(fsPath).toLowerCase(); + return paths.some((p) => normalized.includes(p)); +} + +function getName(nativeEnv: NativeEnvInfo, kind: PythonEnvKind, condaEnvDirs: string[]): string { + if (nativeEnv.name) { + return nativeEnv.name; + } + + const envType = getEnvType(kind); + if (nativeEnv.prefix && envType === PythonEnvType.Virtual) { + return path.basename(nativeEnv.prefix); + } + + if (nativeEnv.prefix && envType === PythonEnvType.Conda) { + if (nativeEnv.name === 'base') { + return 'base'; + } + + const workspaces = (getWorkspaceFolders() ?? []).map((wf) => wf.uri.fsPath); + if (isSubDir(nativeEnv.prefix, workspaces)) { + traceInfo(`Conda env is --prefix environment: ${nativeEnv.prefix}`); + return ''; + } + + if (condaEnvDirs.length > 0 && isSubDir(nativeEnv.prefix, condaEnvDirs)) { + traceInfo(`Conda env is --named environment: ${nativeEnv.prefix}`); + return path.basename(nativeEnv.prefix); + } + } + + return ''; +} + +function toPythonEnvInfo(nativeEnv: NativeEnvInfo, condaEnvDirs: string[]): PythonEnvInfo | undefined { + if (!validEnv(nativeEnv)) { + return undefined; + } + const kind = categoryToKind(nativeEnv.kind); + const arch = toArch(nativeEnv.arch); + const version: PythonVersion = parseVersion(nativeEnv.version ?? ''); + const name = getName(nativeEnv, kind, condaEnvDirs); + const displayName = nativeEnv.version + ? getDisplayName(version, kind, arch, name) + : nativeEnv.displayName ?? 'Python'; + + const executable = nativeEnv.executable ?? makeExecutablePath(nativeEnv.prefix); + return { + name, + location: getLocation(nativeEnv, executable), + kind, + id: executable, + executable: { + filename: executable, + sysPrefix: nativeEnv.prefix ?? '', + ctime: -1, + mtime: -1, + }, + version: { + sysVersion: nativeEnv.version, + major: version.major, + minor: version.minor, + micro: version.micro, + }, + arch, + distro: { + org: '', + }, + source: [], + detailedDisplayName: displayName, + display: displayName, + type: getEnvType(kind), + }; +} + +function hasChanged(old: PythonEnvInfo, newEnv: PythonEnvInfo): boolean { + if (old.name !== newEnv.name) { + return true; + } + if (old.executable.filename !== newEnv.executable.filename) { + return true; + } + if (old.version.major !== newEnv.version.major) { + return true; + } + if (old.version.minor !== newEnv.version.minor) { + return true; + } + if (old.version.micro !== newEnv.version.micro) { + return true; + } + if (old.location !== newEnv.location) { + return true; + } + if (old.kind !== newEnv.kind) { + return true; + } + if (old.arch !== newEnv.arch) { + return true; + } + + return false; +} + +class NativePythonEnvironments implements IDiscoveryAPI, Disposable { + private _onProgress: EventEmitter; + + private _onChanged: EventEmitter; + + private _refreshPromise?: Deferred; + + private _envs: PythonEnvInfo[] = []; + + private _disposables: Disposable[] = []; + + private _condaEnvDirs: string[] = []; + + constructor(private readonly finder: NativePythonFinder) { + this._onProgress = new EventEmitter(); + this._onChanged = new EventEmitter(); + + this.onProgress = this._onProgress.event; + this.onChanged = this._onChanged.event; + + this.refreshState = ProgressReportStage.idle; + this._disposables.push(this._onProgress, this._onChanged); + + this.initializeWatcher(); + } + + dispose(): void { + this._disposables.forEach((d) => d.dispose()); + } + + refreshState: ProgressReportStage; + + onProgress: Event; + + onChanged: Event; + + getRefreshPromise(_options?: GetRefreshEnvironmentsOptions): Promise | undefined { + return this._refreshPromise?.promise; + } + + triggerRefresh(_query?: PythonLocatorQuery, _options?: TriggerRefreshOptions): Promise { + const stopwatch = new StopWatch(); + traceLog('Native locator: Refresh started'); + if (this.refreshState === ProgressReportStage.discoveryStarted && this._refreshPromise?.promise) { + return this._refreshPromise?.promise; + } + + this.refreshState = ProgressReportStage.discoveryStarted; + this._onProgress.fire({ stage: this.refreshState }); + this._refreshPromise = createDeferred(); + + setImmediate(async () => { + try { + const before = this._envs.map((env) => env.executable.filename); + const after: string[] = []; + for await (const native of this.finder.refresh()) { + const exe = this.processNative(native); + if (exe) { + after.push(exe); + } + } + const envsToRemove = before.filter((item) => !after.includes(item)); + envsToRemove.forEach((item) => this.removeEnv(item)); + this._refreshPromise?.resolve(); + } catch (error) { + this._refreshPromise?.reject(error); + } finally { + traceLog(`Native locator: Refresh finished in ${stopwatch.elapsedTime} ms`); + this.refreshState = ProgressReportStage.discoveryFinished; + this._refreshPromise = undefined; + this._onProgress.fire({ stage: this.refreshState }); + } + }); + + return this._refreshPromise?.promise; + } + + private processNative(native: NativeEnvInfo | NativeEnvManagerInfo): string | undefined { + if (isNativeEnvInfo(native)) { + return this.processEnv(native); + } + this.processEnvManager(native); + + return undefined; + } + + private processEnv(native: NativeEnvInfo): string | undefined { + if (!validEnv(native)) { + return undefined; + } + + try { + const version = native.version ? parseVersion(native.version) : undefined; + + if (categoryToKind(native.kind) === PythonEnvKind.Conda && !native.executable) { + // This is a conda env without python, no point trying to resolve this. + // There is nothing to resolve + return this.addEnv(native)?.executable.filename; + } + if (native.executable && (!version || version.major < 0 || version.minor < 0 || version.micro < 0)) { + // We have a path, but no version info, try to resolve the environment. + this.finder + .resolve(native.executable) + .then((env) => { + if (env) { + this.addEnv(env); + } + }) + .ignoreErrors(); + return native.executable; + } + if (native.executable && version && version.major >= 0 && version.minor >= 0 && version.micro >= 0) { + return this.addEnv(native)?.executable.filename; + } + traceError(`Failed to process environment: ${JSON.stringify(native)}`); + } catch (err) { + traceError(`Failed to process environment: ${err}`); + } + return undefined; + } + + private condaPathAlreadySet: string | undefined; + + // eslint-disable-next-line class-methods-use-this + private processEnvManager(native: NativeEnvManagerInfo) { + const tool = native.tool.toLowerCase(); + switch (tool) { + case 'conda': + { + traceLog(`Conda environment manager found at: ${native.executable}`); + const settingPath = getCondaPathSetting(); + if (!this.condaPathAlreadySet) { + if (settingPath === '' || settingPath === undefined) { + if (foundOnPath(native.executable)) { + setCondaBinary(native.executable); + this.condaPathAlreadySet = native.executable; + traceInfo(`Using conda: ${native.executable}`); + } else { + traceInfo(`Conda not found on PATH, skipping: ${native.executable}`); + traceInfo( + 'You can set the path to conda using the setting: `python.condaPath` if you want to use a different conda binary', + ); + } + } else { + traceInfo(`Using conda from setting: ${settingPath}`); + this.condaPathAlreadySet = settingPath; + } + } else { + traceInfo(`Conda set to: ${this.condaPathAlreadySet}`); + } + } + break; + case 'pyenv': + traceLog(`Pyenv environment manager found at: ${native.executable}`); + setPyEnvBinary(native.executable); + break; + case 'poetry': + traceLog(`Poetry environment manager found at: ${native.executable}`); + break; + default: + traceWarn(`Unknown environment manager: ${native.tool}`); + break; + } + } + + getEnvs(_query?: PythonLocatorQuery): PythonEnvInfo[] { + return this._envs; + } + + private addEnv(native: NativeEnvInfo, searchLocation?: Uri): PythonEnvInfo | undefined { + const info = toPythonEnvInfo(native, this._condaEnvDirs); + if (info) { + const old = this._envs.find((item) => item.executable.filename === info.executable.filename); + if (old) { + this._envs = this._envs.filter((item) => item.executable.filename !== info.executable.filename); + this._envs.push(info); + if (hasChanged(old, info)) { + this._onChanged.fire({ type: FileChangeType.Changed, old, new: info, searchLocation }); + } + } else { + this._envs.push(info); + this._onChanged.fire({ type: FileChangeType.Created, new: info, searchLocation }); + } + } + + return info; + } + + private removeEnv(env: PythonEnvInfo | string): void { + if (typeof env === 'string') { + const old = this._envs.find((item) => item.executable.filename === env); + this._envs = this._envs.filter((item) => item.executable.filename !== env); + this._onChanged.fire({ type: FileChangeType.Deleted, old }); + return; + } + this._envs = this._envs.filter((item) => item.executable.filename !== env.executable.filename); + this._onChanged.fire({ type: FileChangeType.Deleted, old: env }); + } + + @cache(30_000, true) + async resolveEnv(envPath?: string): Promise { + if (envPath === undefined) { + return undefined; + } + try { + const native = await this.finder.resolve(envPath); + if (native) { + if (native.kind === NativePythonEnvironmentKind.Conda && this._condaEnvDirs.length === 0) { + this._condaEnvDirs = (await getCondaEnvDirs()) ?? []; + } + return this.addEnv(native); + } + return undefined; + } catch { + return undefined; + } + } + + private initializeWatcher(): void { + const watcher = createPythonWatcher(); + this._disposables.push( + watcher.onDidGlobalEnvChanged((e) => this.pathEventHandler(e)), + watcher.onDidWorkspaceEnvChanged(async (e) => { + await this.workspaceEventHandler(e); + }), + onDidChangeWorkspaceFolders((e: WorkspaceFoldersChangeEvent) => { + e.removed.forEach((wf) => watcher.unwatchWorkspace(wf)); + e.added.forEach((wf) => watcher.watchWorkspace(wf)); + }), + watcher, + ); + + getWorkspaceFolders()?.forEach((wf) => watcher.watchWorkspace(wf)); + const home = getUserHomeDir(); + if (home) { + watcher.watchPath(Uri.file(path.join(home, '.conda', 'environments.txt'))); + } + } + + private async pathEventHandler(e: PythonGlobalEnvEvent): Promise { + if (e.type === FileChangeType.Created || e.type === FileChangeType.Changed) { + if (e.uri.fsPath.endsWith('environment.txt')) { + const before = this._envs + .filter((env) => env.kind === PythonEnvKind.Conda) + .map((env) => env.executable.filename); + for await (const native of this.finder.refresh(NativePythonEnvironmentKind.Conda)) { + this.processNative(native); + } + const after = this._envs + .filter((env) => env.kind === PythonEnvKind.Conda) + .map((env) => env.executable.filename); + const envsToRemove = before.filter((item) => !after.includes(item)); + envsToRemove.forEach((item) => this.removeEnv(item)); + } + } + } + + private async workspaceEventHandler(e: PythonWorkspaceEnvEvent): Promise { + if (e.type === FileChangeType.Created || e.type === FileChangeType.Changed) { + const native = await this.finder.resolve(e.executable); + if (native) { + this.addEnv(native, e.workspaceFolder.uri); + } + } else { + this.removeEnv(e.executable); + } + } +} + +export function createNativeEnvironmentsApi(finder: NativePythonFinder): IDiscoveryAPI & Disposable { + const native = new NativePythonEnvironments(finder); + native.triggerRefresh().ignoreErrors(); + return native; +} diff --git a/src/client/refactor/proxy.ts b/src/client/refactor/proxy.ts deleted file mode 100644 index 94bc5cba1b7a..000000000000 --- a/src/client/refactor/proxy.ts +++ /dev/null @@ -1,209 +0,0 @@ -// tslint:disable:no-any no-empty member-ordering prefer-const prefer-template no-var-self - -import { ChildProcess } from 'child_process'; -import { Disposable, Position, Range, TextDocument, TextEditorOptions, window } from 'vscode'; -import '../common/extensions'; -import { traceError } from '../common/logger'; -import { IS_WINDOWS } from '../common/platform/constants'; -import * as internalScripts from '../common/process/internal/scripts'; -import { IPythonExecutionService } from '../common/process/types'; -import { createDeferred, Deferred } from '../common/utils/async'; -import { getWindowsLineEndingCount } from '../common/utils/text'; - -export class RefactorProxy extends Disposable { - private _process?: ChildProcess; - private _previousOutData: string = ''; - private _previousStdErrData: string = ''; - private _startedSuccessfully: boolean = false; - private _commandResolve?: (value?: any | PromiseLike) => void; - private _commandReject!: (reason?: any) => void; - private initialized!: Deferred; - constructor( - private workspaceRoot: string, - private getPythonExecutionService: () => Promise - ) { - super(() => {}); - } - - public dispose() { - try { - this._process!.kill(); - } catch (ex) {} - this._process = undefined; - } - private getOffsetAt(document: TextDocument, position: Position): number { - if (!IS_WINDOWS) { - return document.offsetAt(position); - } - - // get line count - // Rope always uses LF, instead of CRLF on windows, funny isn't it - // So for each line, reduce one characer (for CR) - // But Not all Windows users use CRLF - const offset = document.offsetAt(position); - const winEols = getWindowsLineEndingCount(document, offset); - - return offset - winEols; - } - public rename( - document: TextDocument, - name: string, - filePath: string, - range: Range, - options?: TextEditorOptions - ): Promise { - if (!options) { - options = window.activeTextEditor!.options; - } - const command = { - lookup: 'rename', - file: filePath, - start: this.getOffsetAt(document, range.start).toString(), - id: '1', - name: name, - indent_size: options.tabSize - }; - - return this.sendCommand(JSON.stringify(command)); - } - public extractVariable( - document: TextDocument, - name: string, - filePath: string, - range: Range, - options?: TextEditorOptions - ): Promise { - if (!options) { - options = window.activeTextEditor!.options; - } - const command = { - lookup: 'extract_variable', - file: filePath, - start: this.getOffsetAt(document, range.start).toString(), - end: this.getOffsetAt(document, range.end).toString(), - id: '1', - name: name, - indent_size: options.tabSize - }; - return this.sendCommand(JSON.stringify(command)); - } - public extractMethod( - document: TextDocument, - name: string, - filePath: string, - range: Range, - options?: TextEditorOptions - ): Promise { - if (!options) { - options = window.activeTextEditor!.options; - } - // Ensure last line is an empty line - if ( - !document.lineAt(document.lineCount - 1).isEmptyOrWhitespace && - range.start.line === document.lineCount - 1 - ) { - return Promise.reject('Missing blank line at the end of document (PEP8).'); - } - const command = { - lookup: 'extract_method', - file: filePath, - start: this.getOffsetAt(document, range.start).toString(), - end: this.getOffsetAt(document, range.end).toString(), - id: '1', - name: name, - indent_size: options.tabSize - }; - return this.sendCommand(JSON.stringify(command)); - } - private sendCommand(command: string): Promise { - return this.initialize().then(() => { - // tslint:disable-next-line:promise-must-complete - return new Promise((resolve, reject) => { - this._commandResolve = resolve; - this._commandReject = reject; - this._process!.stdin.write(command + '\n'); - }); - }); - } - private async initialize(): Promise { - const pythonProc = await this.getPythonExecutionService(); - this.initialized = createDeferred(); - const [args, parse] = internalScripts.refactor(this.workspaceRoot); - const result = pythonProc.execObservable(args, {}); - this._process = result.proc; - result.out.subscribe( - (output) => { - if (output.source === 'stdout') { - if (!this._startedSuccessfully && output.out.startsWith('STARTED')) { - this._startedSuccessfully = true; - return this.initialized.resolve(); - } - this.onData(output.out, parse); - } else { - this.handleStdError(output.out); - } - }, - (error) => this.handleError(error) - ); - - return this.initialized.promise; - } - private handleStdError(data: string) { - // Possible there was an exception in parsing the data returned - // So append the data then parse it - let dataStr = (this._previousStdErrData = this._previousStdErrData + data + ''); - let errorResponse: { message: string; traceback: string; type: string }[]; - try { - errorResponse = dataStr - .split(/\r?\n/g) - .filter((line) => line.length > 0) - .map((resp) => JSON.parse(resp)); - this._previousStdErrData = ''; - } catch (ex) { - traceError(ex); - // Possible we've only received part of the data, hence don't clear previousData - return; - } - if (typeof errorResponse[0].message !== 'string' || errorResponse[0].message.length === 0) { - errorResponse[0].message = errorResponse[0].traceback.splitLines().pop()!; - } - let errorMessage = errorResponse[0].message + '\n' + errorResponse[0].traceback; - - if (this._startedSuccessfully) { - this._commandReject(`Refactor failed. ${errorMessage}`); - } else { - if (typeof errorResponse[0].type === 'string' && errorResponse[0].type === 'ModuleNotFoundError') { - this.initialized.reject('Not installed'); - return; - } - - this.initialized.reject(`Refactor failed. ${errorMessage}`); - } - } - private handleError(error: Error) { - if (this._startedSuccessfully) { - return this._commandReject(error); - } - this.initialized.reject(error); - } - private onData(data: string, parse: (out: string) => object[]) { - if (!this._commandResolve) { - return; - } - - // Possible there was an exception in parsing the data returned - // So append the data then parse it - let dataStr = (this._previousOutData = this._previousOutData + data + ''); - let response: any; - try { - response = parse(dataStr); - this._previousOutData = ''; - } catch (ex) { - // Possible we've only received part of the data, hence don't clear previousData - return; - } - this.dispose(); - this._commandResolve!(response[0]); - this._commandResolve = undefined; - } -} diff --git a/src/client/repl/nativeRepl.ts b/src/client/repl/nativeRepl.ts new file mode 100644 index 000000000000..3f8a085da467 --- /dev/null +++ b/src/client/repl/nativeRepl.ts @@ -0,0 +1,267 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// Native Repl class that holds instance of pythonServer and replController + +import { NotebookController, NotebookDocument, QuickPickItem, TextEditor, Uri, WorkspaceFolder } from 'vscode'; +import * as path from 'path'; +import { Disposable } from 'vscode-jsonrpc'; +import { PVSC_EXTENSION_ID } from '../common/constants'; +import { showNotebookDocument, showQuickPick } from '../common/vscodeApis/windowApis'; +import { getWorkspaceFolders, onDidCloseNotebookDocument } from '../common/vscodeApis/workspaceApis'; +import { PythonEnvironment } from '../pythonEnvironments/info'; +import { createPythonServer, PythonServer } from './pythonServer'; +import { executeNotebookCell, openInteractiveREPL, selectNotebookKernel } from './replCommandHandler'; +import { createReplController } from './replController'; +import { EventName } from '../telemetry/constants'; +import { sendTelemetryEvent } from '../telemetry'; +import { VariablesProvider } from './variables/variablesProvider'; +import { VariableRequester } from './variables/variableRequester'; +import { getTabNameForUri } from './replUtils'; +import { getWorkspaceStateValue, updateWorkspaceStateValue } from '../common/persistentState'; +import { onDidChangeEnvironmentEnvExt, useEnvExtension } from '../envExt/api.internal'; +import { getActiveInterpreterLegacy } from '../envExt/api.legacy'; + +export const NATIVE_REPL_URI_MEMENTO = 'nativeReplUri'; +let nativeRepl: NativeRepl | undefined; +export class NativeRepl implements Disposable { + // Adding ! since it will get initialized in create method, not the constructor. + private pythonServer!: PythonServer; + + private cwd: string | undefined; + + private interpreter!: PythonEnvironment; + + private disposables: Disposable[] = []; + + private replController!: NotebookController; + + private notebookDocument: NotebookDocument | undefined; + + public newReplSession: boolean | undefined = true; + + private envChangeListenerRegistered = false; + + private pendingInterpreterChange?: { resource?: Uri }; + + // TODO: In the future, could also have attribute of URI for file specific REPL. + private constructor() { + this.watchNotebookClosed(); + } + + // Static async factory method to handle asynchronous initialization + public static async create(interpreter: PythonEnvironment): Promise { + const nativeRepl = new NativeRepl(); + nativeRepl.interpreter = interpreter; + await nativeRepl.setReplDirectory(); + nativeRepl.pythonServer = createPythonServer([interpreter.path as string], nativeRepl.cwd); + nativeRepl.disposables.push(nativeRepl.pythonServer); + nativeRepl.setReplController(); + nativeRepl.registerInterpreterChangeHandler(); + + return nativeRepl; + } + + dispose(): void { + this.disposables.forEach((d) => d.dispose()); + } + + /** + * Function that watches for Notebook Closed event. + * This is for the purposes of correctly updating the notebookEditor and notebookDocument on close. + */ + private watchNotebookClosed(): void { + this.disposables.push( + onDidCloseNotebookDocument(async (nb) => { + if (this.notebookDocument && nb.uri.toString() === this.notebookDocument.uri.toString()) { + this.notebookDocument = undefined; + this.newReplSession = true; + await updateWorkspaceStateValue(NATIVE_REPL_URI_MEMENTO, undefined); + this.pythonServer.dispose(); + this.pythonServer = createPythonServer([this.interpreter.path as string], this.cwd); + this.disposables.push(this.pythonServer); + if (this.replController) { + this.replController.dispose(); + } + nativeRepl = undefined; + } + }), + ); + } + + /** + * Function that set up desired directory for REPL. + * If there is multiple workspaces, prompt the user to choose + * which directory we should set in context of native REPL. + */ + private async setReplDirectory(): Promise { + // Figure out uri via workspaceFolder as uri parameter always + // seem to be undefined from parameter when trying to access from replCommands.ts + const workspaces: readonly WorkspaceFolder[] | undefined = getWorkspaceFolders(); + + if (workspaces) { + // eslint-disable-next-line no-shadow + const workspacesQuickPickItems: QuickPickItem[] = workspaces.map((workspace) => ({ + label: workspace.name, + description: workspace.uri.fsPath, + })); + + if (workspacesQuickPickItems.length === 0) { + this.cwd = process.cwd(); // Yields '/' on no workspace scenario. + } else if (workspacesQuickPickItems.length === 1) { + this.cwd = workspacesQuickPickItems[0].description; + } else { + // Show choices of workspaces for user to choose from. + const selection = (await showQuickPick(workspacesQuickPickItems, { + placeHolder: 'Select current working directory for new REPL', + matchOnDescription: true, + ignoreFocusOut: true, + })) as QuickPickItem; + this.cwd = selection?.description; + } + } + } + + /** + * Function that check if NotebookController for REPL exists, and returns it in Singleton manner. + */ + public setReplController(force: boolean = false): NotebookController { + if (!this.replController || force) { + this.replController = createReplController(this.interpreter!.path, this.disposables, this.cwd); + this.replController.variableProvider = new VariablesProvider( + new VariableRequester(this.pythonServer), + () => this.notebookDocument, + this.pythonServer.onCodeExecuted, + ); + } + return this.replController; + } + + private registerInterpreterChangeHandler(): void { + if (!useEnvExtension() || this.envChangeListenerRegistered) { + return; + } + this.envChangeListenerRegistered = true; + this.disposables.push( + onDidChangeEnvironmentEnvExt((event) => { + this.updateInterpreterForChange(event.uri).catch(() => undefined); + }), + ); + this.disposables.push( + this.pythonServer.onCodeExecuted(() => { + if (this.pendingInterpreterChange) { + const { resource } = this.pendingInterpreterChange; + this.pendingInterpreterChange = undefined; + this.updateInterpreterForChange(resource).catch(() => undefined); + } + }), + ); + } + + private async updateInterpreterForChange(resource?: Uri): Promise { + if (this.pythonServer?.isExecuting) { + this.pendingInterpreterChange = { resource }; + return; + } + if (!this.shouldApplyInterpreterChange(resource)) { + return; + } + const scope = resource ?? (this.cwd ? Uri.file(this.cwd) : undefined); + const interpreter = await getActiveInterpreterLegacy(scope); + if (!interpreter || interpreter.path === this.interpreter?.path) { + return; + } + + this.interpreter = interpreter; + this.pythonServer.dispose(); + this.pythonServer = createPythonServer([interpreter.path as string], this.cwd); + this.disposables.push(this.pythonServer); + if (this.replController) { + this.replController.dispose(); + } + this.setReplController(true); + + if (this.notebookDocument) { + const notebookEditor = await showNotebookDocument(this.notebookDocument, { preserveFocus: true }); + await selectNotebookKernel(notebookEditor, this.replController.id, PVSC_EXTENSION_ID); + } + } + + private shouldApplyInterpreterChange(resource?: Uri): boolean { + if (!resource || !this.cwd) { + return true; + } + const relative = path.relative(this.cwd, resource.fsPath); + return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative)); + } + + /** + * Function that checks if native REPL's text input box contains complete code. + * @returns Promise - True if complete/Valid code is present, False otherwise. + */ + public async checkUserInputCompleteCode(activeEditor: TextEditor | undefined): Promise { + let completeCode = false; + let userTextInput; + if (activeEditor) { + const { document } = activeEditor; + userTextInput = document.getText(); + } + + // Check if userTextInput is a complete Python command + if (userTextInput) { + completeCode = await this.pythonServer.checkValidCommand(userTextInput); + } + + return completeCode; + } + + /** + * Function that opens interactive repl, selects kernel, and send/execute code to the native repl. + */ + public async sendToNativeRepl(code?: string | undefined, preserveFocus: boolean = true): Promise { + let wsMementoUri: Uri | undefined; + + if (!this.notebookDocument) { + const wsMemento = getWorkspaceStateValue(NATIVE_REPL_URI_MEMENTO); + wsMementoUri = wsMemento ? Uri.parse(wsMemento) : undefined; + + if (!wsMementoUri || getTabNameForUri(wsMementoUri) !== 'Python REPL') { + await updateWorkspaceStateValue(NATIVE_REPL_URI_MEMENTO, undefined); + wsMementoUri = undefined; + } + } + + const result = await openInteractiveREPL(this.notebookDocument ?? wsMementoUri, preserveFocus); + if (result) { + this.notebookDocument = result.notebookEditor.notebook; + await updateWorkspaceStateValue( + NATIVE_REPL_URI_MEMENTO, + this.notebookDocument.uri.toString(), + ); + + if (result.documentCreated) { + await selectNotebookKernel(result.notebookEditor, this.replController.id, PVSC_EXTENSION_ID); + } + if (code) { + await executeNotebookCell(result.notebookEditor, code); + } + } + } +} + +/** + * Get Singleton Native REPL Instance + * @param interpreter + * @returns Native REPL instance + */ +export async function getNativeRepl(interpreter: PythonEnvironment, disposables: Disposable[]): Promise { + if (!nativeRepl) { + nativeRepl = await NativeRepl.create(interpreter); + disposables.push(nativeRepl); + } + if (nativeRepl && nativeRepl.newReplSession) { + sendTelemetryEvent(EventName.REPL, undefined, { replType: 'Native' }); + nativeRepl.newReplSession = false; + } + return nativeRepl; +} diff --git a/src/client/repl/pythonServer.ts b/src/client/repl/pythonServer.ts new file mode 100644 index 000000000000..c4b1722b5079 --- /dev/null +++ b/src/client/repl/pythonServer.ts @@ -0,0 +1,168 @@ +import * as path from 'path'; +import * as ch from 'child_process'; +import * as rpc from 'vscode-jsonrpc/node'; +import { Disposable, Event, EventEmitter, window } from 'vscode'; +import { EXTENSION_ROOT_DIR } from '../constants'; +import { traceError, traceLog } from '../logging'; +import { captureTelemetry } from '../telemetry'; +import { EventName } from '../telemetry/constants'; + +const SERVER_PATH = path.join(EXTENSION_ROOT_DIR, 'python_files', 'python_server.py'); +let serverInstance: PythonServer | undefined; +export interface ExecutionResult { + status: boolean; + output: string; +} + +export interface PythonServer extends Disposable { + onCodeExecuted: Event; + readonly isExecuting: boolean; + readonly isDisposed: boolean; + execute(code: string): Promise; + executeSilently(code: string): Promise; + interrupt(): void; + input(): void; + checkValidCommand(code: string): Promise; +} + +class PythonServerImpl implements PythonServer, Disposable { + private readonly disposables: Disposable[] = []; + + private readonly _onCodeExecuted = new EventEmitter(); + + onCodeExecuted = this._onCodeExecuted.event; + + private inFlightRequests = 0; + + private disposed = false; + + public get isExecuting(): boolean { + return this.inFlightRequests > 0; + } + + public get isDisposed(): boolean { + return this.disposed; + } + + constructor(private connection: rpc.MessageConnection, private pythonServer: ch.ChildProcess) { + this.initialize(); + this.input(); + } + + private initialize(): void { + this.disposables.push( + this.connection.onNotification('log', (message: string) => { + traceLog('Log:', message); + }), + ); + this.pythonServer.on('exit', (code) => { + traceError(`Python server exited with code ${code}`); + this.markDisposed(); + }); + this.pythonServer.on('error', (err) => { + traceError(err); + this.markDisposed(); + }); + this.connection.listen(); + } + + public input(): void { + // Register input request handler + this.connection.onRequest('input', async (request) => { + // Ask for user input via popup quick input, send it back to Python + let userPrompt = 'Enter your input here: '; + if (request && request.prompt) { + userPrompt = request.prompt; + } + const input = await window.showInputBox({ + title: 'Input Request', + prompt: userPrompt, + ignoreFocusOut: true, + }); + return { userInput: input }; + }); + } + + @captureTelemetry(EventName.EXECUTION_CODE, { scope: 'selection' }, false) + public async execute(code: string): Promise { + const result = await this.executeCode(code); + if (result?.status) { + this._onCodeExecuted.fire(); + } + return result; + } + + public executeSilently(code: string): Promise { + return this.executeCode(code); + } + + private async executeCode(code: string): Promise { + this.inFlightRequests += 1; + try { + const result = await this.connection.sendRequest('execute', code); + return result as ExecutionResult; + } catch (err) { + const error = err as Error; + traceError(`Error getting response from REPL server:`, error); + } finally { + this.inFlightRequests -= 1; + } + return undefined; + } + + public interrupt(): void { + // Passing SIGINT to interrupt only would work for Mac and Linux + if (this.pythonServer.kill('SIGINT')) { + traceLog('Python REPL server interrupted'); + } + } + + public async checkValidCommand(code: string): Promise { + this.inFlightRequests += 1; + try { + const completeCode: ExecutionResult = await this.connection.sendRequest('check_valid_command', code); + return completeCode.output === 'True'; + } finally { + this.inFlightRequests -= 1; + } + } + + public dispose(): void { + if (this.disposed) { + return; + } + this.disposed = true; + this.connection.sendNotification('exit'); + this.disposables.forEach((d) => d.dispose()); + this.connection.dispose(); + serverInstance = undefined; + } + + private markDisposed(): void { + if (this.disposed) { + return; + } + this.disposed = true; + this.connection.dispose(); + serverInstance = undefined; + } +} + +export function createPythonServer(interpreter: string[], cwd?: string): PythonServer { + if (serverInstance && !serverInstance.isDisposed) { + return serverInstance; + } + + const pythonServer = ch.spawn(interpreter[0], [...interpreter.slice(1), SERVER_PATH], { + cwd, // Launch with correct workspace directory + }); + pythonServer.stderr.on('data', (data) => { + traceError(data.toString()); + }); + const connection = rpc.createMessageConnection( + new rpc.StreamMessageReader(pythonServer.stdout), + new rpc.StreamMessageWriter(pythonServer.stdin), + ); + serverInstance = new PythonServerImpl(connection, pythonServer); + return serverInstance; +} diff --git a/src/client/repl/replCommandHandler.ts b/src/client/repl/replCommandHandler.ts new file mode 100644 index 000000000000..630eddfdd565 --- /dev/null +++ b/src/client/repl/replCommandHandler.ts @@ -0,0 +1,98 @@ +import { + NotebookEditor, + ViewColumn, + NotebookDocument, + NotebookCellData, + NotebookCellKind, + NotebookEdit, + WorkspaceEdit, + Uri, +} from 'vscode'; +import { getExistingReplViewColumn, getTabNameForUri } from './replUtils'; +import { showNotebookDocument } from '../common/vscodeApis/windowApis'; +import { openNotebookDocument, applyEdit } from '../common/vscodeApis/workspaceApis'; +import { executeCommand } from '../common/vscodeApis/commandApis'; + +/** + * Function that opens/show REPL using IW UI. + */ +export async function openInteractiveREPL( + notebookDocument: NotebookDocument | Uri | undefined, + preserveFocus: boolean = true, +): Promise<{ notebookEditor: NotebookEditor; documentCreated: boolean } | undefined> { + let viewColumn = ViewColumn.Beside; + let alreadyExists = false; + if (notebookDocument instanceof Uri) { + // Case where NotebookDocument is undefined, but workspace mementoURI exists. + notebookDocument = await openNotebookDocument(notebookDocument); + } else if (notebookDocument) { + // Case where NotebookDocument (REPL document already exists in the tab) + const existingReplViewColumn = getExistingReplViewColumn(notebookDocument); + viewColumn = existingReplViewColumn ?? viewColumn; + alreadyExists = true; + } else if (!notebookDocument) { + // Case where NotebookDocument doesnt exist, or + // became outdated (untitled.ipynb created without Python extension knowing, effectively taking over original Python REPL's URI) + notebookDocument = await openNotebookDocument('jupyter-notebook'); + } + + const notebookEditor = await showNotebookDocument(notebookDocument!, { + viewColumn, + asRepl: 'Python REPL', + preserveFocus, + }); + + // Sanity check that we opened a Native REPL from showNotebookDocument. + if ( + !notebookEditor || + !notebookEditor.notebook || + !notebookEditor.notebook.uri || + getTabNameForUri(notebookEditor.notebook.uri) !== 'Python REPL' + ) { + return undefined; + } + + return { notebookEditor, documentCreated: !alreadyExists }; +} + +/** + * Function that selects notebook Kernel. + */ +export async function selectNotebookKernel( + notebookEditor: NotebookEditor, + notebookControllerId: string, + extensionId: string, +): Promise { + await executeCommand('notebook.selectKernel', { + notebookEditor, + id: notebookControllerId, + extension: extensionId, + }); +} + +/** + * Function that executes notebook cell given code. + */ +export async function executeNotebookCell(notebookEditor: NotebookEditor, code: string): Promise { + const { notebook, replOptions } = notebookEditor; + const cellIndex = replOptions?.appendIndex ?? notebook.cellCount; + await addCellToNotebook(notebook, cellIndex, code); + // Execute the cell + executeCommand('notebook.cell.execute', { + ranges: [{ start: cellIndex, end: cellIndex + 1 }], + document: notebook.uri, + }); +} + +/** + * Function that adds cell to notebook. + * This function will only get called when notebook document is defined. + */ +async function addCellToNotebook(notebookDocument: NotebookDocument, index: number, code: string): Promise { + const notebookCellData = new NotebookCellData(NotebookCellKind.Code, code as string, 'python'); + // Add new cell to interactive window document + const notebookEdit = NotebookEdit.insertCells(index, [notebookCellData]); + const workspaceEdit = new WorkspaceEdit(); + workspaceEdit.set(notebookDocument!.uri, [notebookEdit]); + await applyEdit(workspaceEdit); +} diff --git a/src/client/repl/replCommands.ts b/src/client/repl/replCommands.ts new file mode 100644 index 000000000000..1171e9466ee8 --- /dev/null +++ b/src/client/repl/replCommands.ts @@ -0,0 +1,131 @@ +import { commands, Uri, window } from 'vscode'; +import { Disposable } from 'vscode-jsonrpc'; +import { ICommandManager } from '../common/application/types'; +import { Commands } from '../common/constants'; +import { IInterpreterService } from '../interpreter/contracts'; +import { ICodeExecutionHelper } from '../terminals/types'; +import { getNativeRepl } from './nativeRepl'; +import { + executeInTerminal, + getActiveInterpreter, + getSelectedTextToExecute, + getSendToNativeREPLSetting, + insertNewLineToREPLInput, + isMultiLineText, +} from './replUtils'; +import { registerCommand } from '../common/vscodeApis/commandApis'; +import { sendTelemetryEvent } from '../telemetry'; +import { EventName } from '../telemetry/constants'; +import { ReplType } from './types'; + +/** + * Register Start Native REPL command in the command palette + */ +export async function registerStartNativeReplCommand( + disposables: Disposable[], + interpreterService: IInterpreterService, +): Promise { + disposables.push( + registerCommand(Commands.Start_Native_REPL, async (uri: Uri) => { + sendTelemetryEvent(EventName.REPL, undefined, { replType: 'Native' }); + const interpreter = await getActiveInterpreter(uri, interpreterService); + if (interpreter) { + const nativeRepl = await getNativeRepl(interpreter, disposables); + await nativeRepl.sendToNativeRepl(undefined, false); + } + }), + ); +} + +/** + * Registers REPL command for shift+enter if sendToNativeREPL setting is enabled. + */ +export async function registerReplCommands( + disposables: Disposable[], + interpreterService: IInterpreterService, + executionHelper: ICodeExecutionHelper, + commandManager: ICommandManager, +): Promise { + disposables.push( + commandManager.registerCommand(Commands.Exec_In_REPL, async (uri: Uri) => { + const nativeREPLSetting = getSendToNativeREPLSetting(); + + if (!nativeREPLSetting) { + await executeInTerminal(); + return; + } + const interpreter = await getActiveInterpreter(uri, interpreterService); + + if (interpreter) { + const nativeRepl = await getNativeRepl(interpreter, disposables); + const activeEditor = window.activeTextEditor; + if (activeEditor) { + const code = await getSelectedTextToExecute(activeEditor); + if (code) { + // Smart Send + let wholeFileContent = ''; + if (activeEditor && activeEditor.document) { + wholeFileContent = activeEditor.document.getText(); + } + const normalizedCode = await executionHelper.normalizeLines( + code!, + ReplType.native, + wholeFileContent, + ); + await nativeRepl.sendToNativeRepl(normalizedCode); + } + } + } + }), + ); +} + +/** + * Command triggered for 'Enter': Conditionally call interactive.execute OR insert \n in text input box. + */ +export async function registerReplExecuteOnEnter( + disposables: Disposable[], + interpreterService: IInterpreterService, + commandManager: ICommandManager, +): Promise { + disposables.push( + commandManager.registerCommand(Commands.Exec_In_REPL_Enter, async (uri: Uri) => { + await onInputEnter(uri, 'repl.execute', interpreterService, disposables); + }), + ); + disposables.push( + commandManager.registerCommand(Commands.Exec_In_IW_Enter, async (uri: Uri) => { + await onInputEnter(uri, 'interactive.execute', interpreterService, disposables); + }), + ); +} + +async function onInputEnter( + uri: Uri | undefined, + commandName: string, + interpreterService: IInterpreterService, + disposables: Disposable[], +): Promise { + const interpreter = await getActiveInterpreter(uri, interpreterService); + if (!interpreter) { + return; + } + + const nativeRepl = await getNativeRepl(interpreter, disposables); + const completeCode = await nativeRepl?.checkUserInputCompleteCode(window.activeTextEditor); + const editor = window.activeTextEditor; + + if (editor) { + // Execute right away when complete code and Not multi-line + if (completeCode && !isMultiLineText(editor)) { + await commands.executeCommand(commandName); + } else { + insertNewLineToREPLInput(editor); + + // Handle case when user enters on blank line, just trigger interactive.execute + if (editor && editor.document.lineAt(editor.selection.active.line).text === '') { + await commands.executeCommand(commandName); + } + } + } +} diff --git a/src/client/repl/replController.ts b/src/client/repl/replController.ts new file mode 100644 index 000000000000..f30b8d9cbf6f --- /dev/null +++ b/src/client/repl/replController.ts @@ -0,0 +1,40 @@ +import * as vscode from 'vscode'; +import { createPythonServer } from './pythonServer'; + +export function createReplController( + interpreterPath: string, + disposables: vscode.Disposable[], + cwd?: string, +): vscode.NotebookController { + const server = createPythonServer([interpreterPath], cwd); + disposables.push(server); + + const controller = vscode.notebooks.createNotebookController('pythonREPL', 'jupyter-notebook', 'Python REPL'); + controller.supportedLanguages = ['python']; + + controller.description = 'Python REPL'; + + controller.interruptHandler = async () => { + server.interrupt(); + }; + + controller.executeHandler = async (cells) => { + for (const cell of cells) { + const exec = controller.createNotebookCellExecution(cell); + exec.start(Date.now()); + + const result = await server.execute(cell.document.getText()); + + if (result?.output) { + exec.replaceOutput([ + new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.text(result.output, 'text/plain')]), + ]); + // TODO: Properly update via NotebookCellOutputItem.error later. + } + + exec.end(result?.status); + } + }; + disposables.push(controller); + return controller; +} diff --git a/src/client/repl/replUtils.ts b/src/client/repl/replUtils.ts new file mode 100644 index 000000000000..93ae6f2a4573 --- /dev/null +++ b/src/client/repl/replUtils.ts @@ -0,0 +1,135 @@ +import { NotebookDocument, TextEditor, Selection, Uri, commands, window, TabInputNotebook, ViewColumn } from 'vscode'; +import { Commands } from '../common/constants'; +import { noop } from '../common/utils/misc'; +import { getActiveResource } from '../common/vscodeApis/windowApis'; +import { getConfiguration } from '../common/vscodeApis/workspaceApis'; +import { IInterpreterService } from '../interpreter/contracts'; +import { PythonEnvironment } from '../pythonEnvironments/info'; +import { getMultiLineSelectionText, getSingleLineSelectionText } from '../terminals/codeExecution/helper'; + +/** + * Function that executes selected code in the terminal. + */ +export async function executeInTerminal(): Promise { + await commands.executeCommand(Commands.Exec_Selection_In_Terminal); +} + +/** + * Function that returns selected text to execute in the REPL. + * @param textEditor + * @returns code - Code to execute in the REPL. + */ +export async function getSelectedTextToExecute(textEditor: TextEditor): Promise { + const { selection } = textEditor; + let code: string; + + if (selection.isEmpty) { + code = textEditor.document.lineAt(selection.start.line).text; + } else if (selection.isSingleLine) { + code = getSingleLineSelectionText(textEditor); + } else { + code = getMultiLineSelectionText(textEditor); + } + + return code; +} + +/** + * Function that returns user's Native REPL setting. + * @returns boolean - True if sendToNativeREPL setting is enabled, False otherwise. + */ +export function getSendToNativeREPLSetting(): boolean { + const uri = getActiveResource(); + const configuration = getConfiguration('python', uri); + return configuration.get('REPL.sendToNativeREPL', false); +} + +// Function that inserts new line in the given (input) text editor +export function insertNewLineToREPLInput(activeEditor: TextEditor | undefined): void { + if (activeEditor) { + const position = activeEditor.selection.active; + const newPosition = position.with(position.line, activeEditor.document.lineAt(position.line).text.length); + activeEditor.selection = new Selection(newPosition, newPosition); + + activeEditor.edit((editBuilder) => { + editBuilder.insert(newPosition, '\n'); + }); + } +} + +export function isMultiLineText(textEditor: TextEditor): boolean { + return (textEditor?.document?.lineCount ?? 0) > 1; +} + +/** + * Function that trigger interpreter warning if invalid interpreter. + * Function will also return undefined or active interpreter + */ +export async function getActiveInterpreter( + uri: Uri | undefined, + interpreterService: IInterpreterService, +): Promise { + const resource = uri ?? getActiveResource(); + const interpreter = await interpreterService.getActiveInterpreter(resource); + if (!interpreter) { + commands.executeCommand(Commands.TriggerEnvironmentSelection, resource).then(noop, noop); + return undefined; + } + return interpreter; +} + +/** + * Function that will return ViewColumn for existing Native REPL that belongs to given NotebookDocument. + */ +export function getExistingReplViewColumn(notebookDocument: NotebookDocument): ViewColumn | undefined { + const ourNotebookUri = notebookDocument.uri.toString(); + // Use Tab groups, to locate previously opened Python REPL tab and fetch view column. + const ourTb = window.tabGroups; + for (const tabGroup of ourTb.all) { + for (const tab of tabGroup.tabs) { + if (tab.label === 'Python REPL') { + const tabInput = (tab.input as unknown) as TabInputNotebook; + const tabUri = tabInput.uri.toString(); + if (tab.input && tabUri === ourNotebookUri) { + // This is the tab we are looking for. + const existingReplViewColumn = tab.group.viewColumn; + return existingReplViewColumn; + } + } + } + } + return undefined; +} + +/** + * Function that will return tab name for before reloading VS Code + * This is so we can make sure tab name is still 'Python REPL' after reloading VS Code, + * and make sure Python REPL does not get 'merged' into unaware untitled.ipynb tab. + */ +export function getTabNameForUri(uri: Uri): string | undefined { + const tabGroups = window.tabGroups.all; + + for (const tabGroup of tabGroups) { + for (const tab of tabGroup.tabs) { + if (tab.input instanceof TabInputNotebook && tab.input.uri.toString() === uri.toString()) { + return tab.label; + } + } + } + + return undefined; +} + +/** + * Function that will return the minor version of current active Python interpreter. + */ +export async function getPythonMinorVersion( + uri: Uri | undefined, + interpreterService: IInterpreterService, +): Promise { + if (uri) { + const pythonVersion = await getActiveInterpreter(uri, interpreterService); + return pythonVersion?.version?.minor; + } + return undefined; +} diff --git a/src/client/repl/types.ts b/src/client/repl/types.ts new file mode 100644 index 000000000000..38de9bfe2137 --- /dev/null +++ b/src/client/repl/types.ts @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +export enum ReplType { + terminal = 'terminal', + native = 'native', +} diff --git a/src/client/repl/variables/types.ts b/src/client/repl/variables/types.ts new file mode 100644 index 000000000000..1e3c80d32077 --- /dev/null +++ b/src/client/repl/variables/types.ts @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { CancellationToken, Variable } from 'vscode'; + +export interface IVariableDescription extends Variable { + /** The name of the variable at the root scope */ + root: string; + /** How to look up the specific property of the root variable */ + propertyChain: (string | number)[]; + /** The number of children for collection types */ + count?: number; + /** Names of children */ + hasNamedChildren?: boolean; + /** A method to get the children of this variable */ + getChildren?: (start: number, token: CancellationToken) => Promise; +} diff --git a/src/client/repl/variables/variableRequester.ts b/src/client/repl/variables/variableRequester.ts new file mode 100644 index 000000000000..e66afdcd6616 --- /dev/null +++ b/src/client/repl/variables/variableRequester.ts @@ -0,0 +1,59 @@ +import { CancellationToken } from 'vscode'; +import path from 'path'; +import * as fsapi from '../../common/platform/fs-paths'; +import { IVariableDescription } from './types'; +import { PythonServer } from '../pythonServer'; +import { EXTENSION_ROOT_DIR } from '../../constants'; + +const VARIABLE_SCRIPT_LOCATION = path.join(EXTENSION_ROOT_DIR, 'python_files', 'get_variable_info.py'); + +export class VariableRequester { + public static scriptContents: string | undefined; + + constructor(private pythonServer: PythonServer) {} + + async getAllVariableDescriptions( + parent: IVariableDescription | undefined, + start: number, + token: CancellationToken, + ): Promise { + const scriptLines = (await getContentsOfVariablesScript()).split(/(?:\r\n|\n)/); + if (parent) { + const printCall = `import json;return json.dumps(getAllChildrenDescriptions(\'${ + parent.root + }\', ${JSON.stringify(parent.propertyChain)}, ${start}))`; + scriptLines.push(printCall); + } else { + scriptLines.push('import json;return json.dumps(getVariableDescriptions())'); + } + + if (token.isCancellationRequested) { + return []; + } + + const script = wrapScriptInFunction(scriptLines); + const result = await this.pythonServer.executeSilently(script); + + if (result?.output && !token.isCancellationRequested) { + return JSON.parse(result.output) as IVariableDescription[]; + } + + return []; + } +} + +function wrapScriptInFunction(scriptLines: string[]): string { + const indented = scriptLines.map((line) => ` ${line}`).join('\n'); + // put everything into a function scope and then delete that scope + // TODO: run in a background thread + return `def __VSCODE_run_script():\n${indented}\nprint(__VSCODE_run_script())\ndel __VSCODE_run_script`; +} + +async function getContentsOfVariablesScript(): Promise { + if (VariableRequester.scriptContents) { + return VariableRequester.scriptContents; + } + const contents = await fsapi.readFile(VARIABLE_SCRIPT_LOCATION, 'utf-8'); + VariableRequester.scriptContents = contents; + return VariableRequester.scriptContents; +} diff --git a/src/client/repl/variables/variableResultCache.ts b/src/client/repl/variables/variableResultCache.ts new file mode 100644 index 000000000000..1e19415becb7 --- /dev/null +++ b/src/client/repl/variables/variableResultCache.ts @@ -0,0 +1,28 @@ +import { VariablesResult } from 'vscode'; + +export class VariableResultCache { + private cache = new Map(); + + private executionCount = 0; + + getResults(executionCount: number, cacheKey: string): VariablesResult[] | undefined { + if (this.executionCount !== executionCount) { + this.cache.clear(); + this.executionCount = executionCount; + } + + return this.cache.get(cacheKey); + } + + setResults(executionCount: number, cacheKey: string, results: VariablesResult[]): void { + if (this.executionCount < executionCount) { + this.cache.clear(); + this.executionCount = executionCount; + } else if (this.executionCount > executionCount) { + // old results, don't cache + return; + } + + this.cache.set(cacheKey, results); + } +} diff --git a/src/client/repl/variables/variablesProvider.ts b/src/client/repl/variables/variablesProvider.ts new file mode 100644 index 000000000000..f033451dc80e --- /dev/null +++ b/src/client/repl/variables/variablesProvider.ts @@ -0,0 +1,159 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { + CancellationToken, + NotebookDocument, + Variable, + NotebookVariablesRequestKind, + VariablesResult, + EventEmitter, + Event, + NotebookVariableProvider, + Uri, +} from 'vscode'; +import { VariableResultCache } from './variableResultCache'; +import { IVariableDescription } from './types'; +import { VariableRequester } from './variableRequester'; +import { getConfiguration } from '../../common/vscodeApis/workspaceApis'; + +export class VariablesProvider implements NotebookVariableProvider { + private readonly variableResultCache = new VariableResultCache(); + + private _onDidChangeVariables = new EventEmitter(); + + onDidChangeVariables = this._onDidChangeVariables.event; + + private executionCount = 0; + + constructor( + private readonly variableRequester: VariableRequester, + private readonly getNotebookDocument: () => NotebookDocument | undefined, + codeExecutedEvent: Event, + ) { + codeExecutedEvent(() => this.onDidExecuteCode()); + } + + onDidExecuteCode(): void { + const notebook = this.getNotebookDocument(); + if (notebook) { + this.executionCount += 1; + if (isEnabled(notebook.uri)) { + this._onDidChangeVariables.fire(notebook); + } + } + } + + async *provideVariables( + notebook: NotebookDocument, + parent: Variable | undefined, + kind: NotebookVariablesRequestKind, + start: number, + token: CancellationToken, + ): AsyncIterable { + const notebookDocument = this.getNotebookDocument(); + if ( + !isEnabled(notebook.uri) || + token.isCancellationRequested || + !notebookDocument || + notebookDocument !== notebook + ) { + return; + } + + const { executionCount } = this; + const cacheKey = getVariableResultCacheKey(notebook.uri.toString(), parent, start); + let results = this.variableResultCache.getResults(executionCount, cacheKey); + + if (parent) { + const parentDescription = parent as IVariableDescription; + if (!results && parentDescription.getChildren) { + const variables = await parentDescription.getChildren(start, token); + if (token.isCancellationRequested) { + return; + } + results = variables.map((variable) => this.createVariableResult(variable)); + this.variableResultCache.setResults(executionCount, cacheKey, results); + } else if (!results) { + // no cached results and no way to get children, so return empty + return; + } + + for (const result of results) { + yield result; + } + + // check if we have more indexed children to return + if ( + kind === 2 && + parentDescription.count && + results.length > 0 && + parentDescription.count > start + results.length + ) { + for await (const result of this.provideVariables( + notebook, + parent, + kind, + start + results.length, + token, + )) { + yield result; + } + } + } else { + if (!results) { + const variables = await this.variableRequester.getAllVariableDescriptions(undefined, start, token); + if (token.isCancellationRequested) { + return; + } + results = variables.map((variable) => this.createVariableResult(variable)); + this.variableResultCache.setResults(executionCount, cacheKey, results); + } + + for (const result of results) { + yield result; + } + } + } + + private createVariableResult(result: IVariableDescription): VariablesResult { + const indexedChildrenCount = result.count ?? 0; + const hasNamedChildren = !!result.hasNamedChildren; + const variable = { + getChildren: (start: number, token: CancellationToken) => this.getChildren(variable, start, token), + expression: createExpression(result.root, result.propertyChain), + ...result, + } as Variable; + return { variable, hasNamedChildren, indexedChildrenCount }; + } + + async getChildren(variable: Variable, start: number, token: CancellationToken): Promise { + const parent = variable as IVariableDescription; + return this.variableRequester.getAllVariableDescriptions(parent, start, token); + } +} + +function createExpression(root: string, propertyChain: (string | number)[]): string { + let expression = root; + for (const property of propertyChain) { + if (typeof property === 'string') { + expression += `.${property}`; + } else { + expression += `[${property}]`; + } + } + return expression; +} + +function getVariableResultCacheKey(uri: string, parent: Variable | undefined, start: number) { + let parentKey = ''; + const parentDescription = parent as IVariableDescription; + if (parentDescription) { + parentKey = `${parentDescription.name}.${parentDescription.propertyChain.join('.')}[[${start}`; + } + return `${uri}:${parentKey}`; +} + +function isEnabled(resource?: Uri) { + return getConfiguration('python', resource).get('REPL.provideVariables'); +} diff --git a/src/client/sourceMapSupport.ts b/src/client/sourceMapSupport.ts deleted file mode 100644 index 0303e1e43a93..000000000000 --- a/src/client/sourceMapSupport.ts +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as path from 'path'; -import { WorkspaceConfiguration } from 'vscode'; -import './common/extensions'; -import { traceError } from './common/logger'; -import { FileSystem } from './common/platform/fileSystem'; -import { EXTENSION_ROOT_DIR } from './constants'; - -type VSCode = typeof import('vscode'); - -// tslint:disable:no-require-imports -const setting = 'sourceMapsEnabled'; - -export class SourceMapSupport { - private readonly config: WorkspaceConfiguration; - constructor(private readonly vscode: VSCode) { - this.config = this.vscode.workspace.getConfiguration('python.diagnostics', null); - } - public async initialize(): Promise { - if (!this.enabled) { - return; - } - await this.enableSourceMaps(true); - require('source-map-support').install(); - const localize = require('./common/utils/localize') as typeof import('./common/utils/localize'); - const disable = localize.Diagnostics.disableSourceMaps(); - this.vscode.window.showWarningMessage(localize.Diagnostics.warnSourceMaps(), disable).then((selection) => { - if (selection === disable) { - this.disable().ignoreErrors(); - } - }); - } - public get enabled(): boolean { - return this.config.get(setting, false); - } - public async disable(): Promise { - if (this.enabled) { - await this.config.update(setting, false, this.vscode.ConfigurationTarget.Global); - } - await this.enableSourceMaps(false); - } - protected async enableSourceMaps(enable: boolean) { - const extensionSourceFile = path.join(EXTENSION_ROOT_DIR, 'out', 'client', 'extension.js'); - const debuggerSourceFile = path.join( - EXTENSION_ROOT_DIR, - 'out', - 'client', - 'debugger', - 'debugAdapter', - 'main.js' - ); - await Promise.all([ - this.enableSourceMap(enable, extensionSourceFile), - this.enableSourceMap(enable, debuggerSourceFile) - ]); - } - protected async enableSourceMap(enable: boolean, sourceFile: string) { - const sourceMapFile = `${sourceFile}.map`; - const disabledSourceMapFile = `${sourceFile}.map.disabled`; - if (enable) { - await this.rename(disabledSourceMapFile, sourceMapFile); - } else { - await this.rename(sourceMapFile, disabledSourceMapFile); - } - } - protected async rename(sourceFile: string, targetFile: string) { - const fs = new FileSystem(); - if (await fs.fileExists(targetFile)) { - return; - } - await fs.move(sourceFile, targetFile); - } -} -export function initialize(vscode: VSCode = require('vscode')) { - if (!vscode.workspace.getConfiguration('python.diagnostics', null).get('sourceMapsEnabled', false)) { - new SourceMapSupport(vscode).disable().ignoreErrors(); - return; - } - new SourceMapSupport(vscode).initialize().catch((_ex) => { - traceError('Failed to initialize source map support in extension'); - }); -} diff --git a/src/client/startupTelemetry.ts b/src/client/startupTelemetry.ts index 13f8bcfbd5da..f7a2a6aea517 100644 --- a/src/client/startupTelemetry.ts +++ b/src/client/startupTelemetry.ts @@ -1,40 +1,29 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import * as vscode from 'vscode'; import { IWorkspaceService } from './common/application/types'; import { isTestExecution } from './common/constants'; -import { DeprecatePythonPath } from './common/experiments/groups'; -import { traceError } from './common/logger'; import { ITerminalHelper } from './common/terminal/types'; -import { - IConfigurationService, - IExperimentsManager, - IInterpreterPathService, - InspectInterpreterSettingType, - Resource -} from './common/types'; -import { - AutoSelectionRule, - IInterpreterAutoSelectionRule, - IInterpreterAutoSelectionService -} from './interpreter/autoSelection/types'; +import { IInterpreterPathService, Resource } from './common/types'; +import { IStopWatch } from './common/utils/stopWatch'; +import { IInterpreterAutoSelectionService } from './interpreter/autoSelection/types'; import { ICondaService, IInterpreterService } from './interpreter/contracts'; import { IServiceContainer } from './ioc/types'; -import { PythonEnvironment } from './pythonEnvironments/info'; +import { traceError } from './logging'; +import { EnvironmentType, PythonEnvironment } from './pythonEnvironments/info'; import { sendTelemetryEvent } from './telemetry'; import { EventName } from './telemetry/constants'; import { EditorLoadTelemetry } from './telemetry/types'; - -interface IStopWatch { - elapsedTime: number; -} +import { IStartupDurations } from './types'; +import { useEnvExtension } from './envExt/api.internal'; export async function sendStartupTelemetry( - // tslint:disable-next-line:no-any activatedPromise: Promise, - durations: Record, + durations: IStartupDurations, stopWatch: IStopWatch, - serviceContainer: IServiceContainer + serviceContainer: IServiceContainer, + isFirstSession: boolean, ) { if (isTestExecution()) { return; @@ -42,8 +31,8 @@ export async function sendStartupTelemetry( try { await activatedPromise; - durations.totalActivateTime = stopWatch.elapsedTime; - const props = await getActivationTelemetryProps(serviceContainer); + durations.totalNonBlockingActivateTime = stopWatch.elapsedTime - durations.startActivateTime; + const props = await getActivationTelemetryProps(serviceContainer, isFirstSession); sendTelemetryEvent(EventName.EDITOR_LOAD, durations, props); } catch (ex) { traceError('sendStartupTelemetry() failed.', ex); @@ -52,11 +41,10 @@ export async function sendStartupTelemetry( export async function sendErrorTelemetry( ex: Error, - durations: Record, - serviceContainer?: IServiceContainer + durations: IStartupDurations, + serviceContainer?: IServiceContainer, ) { try { - // tslint:disable-next-line:no-any let props: any = {}; if (serviceContainer) { try { @@ -81,16 +69,8 @@ function isUsingGlobalInterpreterInWorkspace(currentPythonPath: string, serviceC } export function hasUserDefinedPythonPath(resource: Resource, serviceContainer: IServiceContainer) { - const abExperiments = serviceContainer.get(IExperimentsManager); - const workspaceService = serviceContainer.get(IWorkspaceService); const interpreterPathService = serviceContainer.get(IInterpreterPathService); - let settings: InspectInterpreterSettingType; - if (abExperiments.inExperiment(DeprecatePythonPath.experiment)) { - settings = interpreterPathService.inspect(resource); - } else { - settings = workspaceService.getConfiguration('python', resource)!.inspect('pythonPath')!; - } - abExperiments.sendTelemetryIfInExperiment(DeprecatePythonPath.control); + let settings = interpreterPathService.inspect(resource); return (settings.workspaceFolderValue && settings.workspaceFolderValue !== 'python') || (settings.workspaceValue && settings.workspaceValue !== 'python') || (settings.globalValue && settings.globalValue !== 'python') @@ -98,51 +78,65 @@ export function hasUserDefinedPythonPath(resource: Resource, serviceContainer: I : false; } -function getPreferredWorkspaceInterpreter(resource: Resource, serviceContainer: IServiceContainer) { - const workspaceInterpreterSelector = serviceContainer.get( - IInterpreterAutoSelectionRule, - AutoSelectionRule.workspaceVirtualEnvs - ); - const interpreter = workspaceInterpreterSelector.getPreviouslyAutoSelectedInterpreter(resource); - return interpreter ? interpreter.path : undefined; -} - -async function getActivationTelemetryProps(serviceContainer: IServiceContainer): Promise { - // tslint:disable-next-line:no-suspicious-comment +async function getActivationTelemetryProps( + serviceContainer: IServiceContainer, + isFirstSession?: boolean, +): Promise { // TODO: Not all of this data is showing up in the database... - // tslint:disable-next-line:no-suspicious-comment + // TODO: If any one of these parts fails we send no info. We should // be able to partially populate as much as possible instead // (through granular try-catch statements). + const appName = vscode.env.appName; + const workspaceService = serviceContainer.get(IWorkspaceService); + const workspaceFolderCount = workspaceService.workspaceFolders?.length || 0; const terminalHelper = serviceContainer.get(ITerminalHelper); const terminalShellType = terminalHelper.identifyTerminalShell(); - const condaLocator = serviceContainer.get(ICondaService); + if (!workspaceService.isTrusted) { + return { workspaceFolderCount, terminal: terminalShellType, isFirstSession }; + } const interpreterService = serviceContainer.get(IInterpreterService); - const workspaceService = serviceContainer.get(IWorkspaceService); - const configurationService = serviceContainer.get(IConfigurationService); - const mainWorkspaceUri = workspaceService.hasWorkspaceFolders - ? workspaceService.workspaceFolders![0].uri + const mainWorkspaceUri = workspaceService.workspaceFolders?.length + ? workspaceService.workspaceFolders[0].uri : undefined; - const settings = configurationService.getSettings(mainWorkspaceUri); - const [condaVersion, interpreter, interpreters] = await Promise.all([ - condaLocator - .getCondaVersion() - .then((ver) => (ver ? ver.raw : '')) - .catch(() => ''), - interpreterService.getActiveInterpreter().catch(() => undefined), - interpreterService.getInterpreters(mainWorkspaceUri).catch(() => []) - ]); - const workspaceFolderCount = workspaceService.hasWorkspaceFolders ? workspaceService.workspaceFolders!.length : 0; + const hasPythonThree = await interpreterService.hasInterpreters(async (item) => item.version?.major === 3); + // If an unknown type environment can be found from windows registry or path env var, + // consider them as global type instead of unknown. Such types can only be known after + // windows registry is queried. So wait for the refresh of windows registry locator to + // finish. API getActiveInterpreter() does not block on windows registry by default as + // it is slow. + await interpreterService.refreshPromise; + let interpreter: PythonEnvironment | undefined; + + // include main workspace uri if using env extension + if (useEnvExtension()) { + interpreter = await interpreterService + .getActiveInterpreter(mainWorkspaceUri) + .catch(() => undefined); + } else { + interpreter = await interpreterService + .getActiveInterpreter() + .catch(() => undefined); + } + const pythonVersion = interpreter && interpreter.version ? interpreter.version.raw : undefined; const interpreterType = interpreter ? interpreter.envType : undefined; + if (interpreterType === EnvironmentType.Unknown) { + traceError('Active interpreter type is detected as Unknown', JSON.stringify(interpreter)); + } + let condaVersion = undefined; + if (interpreterType === EnvironmentType.Conda) { + const condaLocator = serviceContainer.get(ICondaService); + condaVersion = await condaLocator + .getCondaVersion() + .then((ver) => (ver ? ver.raw : '')) + .catch(() => ''); + } const usingUserDefinedInterpreter = hasUserDefinedPythonPath(mainWorkspaceUri, serviceContainer); - const preferredWorkspaceInterpreter = getPreferredWorkspaceInterpreter(mainWorkspaceUri, serviceContainer); - const usingGlobalInterpreter = isUsingGlobalInterpreterInWorkspace(settings.pythonPath, serviceContainer); - const usingAutoSelectedWorkspaceInterpreter = preferredWorkspaceInterpreter - ? settings.pythonPath === getPreferredWorkspaceInterpreter(mainWorkspaceUri, serviceContainer) + const usingGlobalInterpreter = interpreter + ? isUsingGlobalInterpreterInWorkspace(interpreter.path, serviceContainer) : false; - const hasPython3 = - interpreters!.filter((item) => (item && item.version ? item.version.major === 3 : false)).length > 0; + const usingEnvironmentsExtension = useEnvExtension(); return { condaVersion, @@ -150,9 +144,11 @@ async function getActivationTelemetryProps(serviceContainer: IServiceContainer): pythonVersion, interpreterType, workspaceFolderCount, - hasPython3, + hasPythonThree, usingUserDefinedInterpreter, - usingAutoSelectedWorkspaceInterpreter, - usingGlobalInterpreter + usingGlobalInterpreter, + appName, + isFirstSession, + usingEnvironmentsExtension, }; } diff --git a/src/client/telemetry/constants.ts b/src/client/telemetry/constants.ts index b04516bd48b5..eff32a6e3299 100644 --- a/src/client/telemetry/constants.ts +++ b/src/client/telemetry/constants.ts @@ -4,118 +4,104 @@ 'use strict'; export enum EventName { - COMPLETION = 'COMPLETION', - COMPLETION_ADD_BRACKETS = 'COMPLETION.ADD_BRACKETS', - DEFINITION = 'DEFINITION', - HOVER_DEFINITION = 'HOVER_DEFINITION', - REFERENCE = 'REFERENCE', - SIGNATURE = 'SIGNATURE', - SYMBOL = 'SYMBOL', - FORMAT_SORT_IMPORTS = 'FORMAT.SORT_IMPORTS', - FORMAT = 'FORMAT.FORMAT', FORMAT_ON_TYPE = 'FORMAT.FORMAT_ON_TYPE', EDITOR_LOAD = 'EDITOR.LOAD', - LINTING = 'LINTING', - GO_TO_OBJECT_DEFINITION = 'GO_TO_OBJECT_DEFINITION', - REFACTOR_RENAME = 'REFACTOR_RENAME', - REFACTOR_EXTRACT_VAR = 'REFACTOR_EXTRACT_VAR', - REFACTOR_EXTRACT_FUNCTION = 'REFACTOR_EXTRACT_FUNCTION', REPL = 'REPL', + INVOKE_TOOL = 'INVOKE_TOOL', + CREATE_NEW_FILE_COMMAND = 'CREATE_NEW_FILE_COMMAND', SELECT_INTERPRETER = 'SELECT_INTERPRETER', SELECT_INTERPRETER_ENTER_BUTTON = 'SELECT_INTERPRETER_ENTER_BUTTON', SELECT_INTERPRETER_ENTER_CHOICE = 'SELECT_INTERPRETER_ENTER_CHOICE', + SELECT_INTERPRETER_SELECTED = 'SELECT_INTERPRETER_SELECTED', + SELECT_INTERPRETER_ENTER_OR_FIND = 'SELECT_INTERPRETER_ENTER_OR_FIND', + SELECT_INTERPRETER_ENTERED_EXISTS = 'SELECT_INTERPRETER_ENTERED_EXISTS', PYTHON_INTERPRETER = 'PYTHON_INTERPRETER', PYTHON_INSTALL_PACKAGE = 'PYTHON_INSTALL_PACKAGE', + ENVIRONMENT_WITHOUT_PYTHON_SELECTED = 'ENVIRONMENT_WITHOUT_PYTHON_SELECTED', + PYTHON_ENVIRONMENTS_API = 'PYTHON_ENVIRONMENTS_API', PYTHON_INTERPRETER_DISCOVERY = 'PYTHON_INTERPRETER_DISCOVERY', + NATIVE_FINDER_MISSING_CONDA_ENVS = 'NATIVE_FINDER_MISSING_CONDA_ENVS', + NATIVE_FINDER_MISSING_POETRY_ENVS = 'NATIVE_FINDER_MISSING_POETRY_ENVS', + NATIVE_FINDER_PERF = 'NATIVE_FINDER_PERF', + PYTHON_INTERPRETER_DISCOVERY_INVALID_NATIVE = 'PYTHON_INTERPRETER_DISCOVERY_INVALID_NATIVE', PYTHON_INTERPRETER_AUTO_SELECTION = 'PYTHON_INTERPRETER_AUTO_SELECTION', - PYTHON_INTERPRETER_ACTIVATION_ENVIRONMENT_VARIABLES = 'PYTHON_INTERPRETER_ACTIVATION_ENVIRONMENT_VARIABLES', + PYTHON_INTERPRETER_ACTIVATION_ENVIRONMENT_VARIABLES = 'PYTHON_INTERPRETER.ACTIVATION_ENVIRONMENT_VARIABLES', PYTHON_INTERPRETER_ACTIVATION_FOR_RUNNING_CODE = 'PYTHON_INTERPRETER_ACTIVATION_FOR_RUNNING_CODE', PYTHON_INTERPRETER_ACTIVATION_FOR_TERMINAL = 'PYTHON_INTERPRETER_ACTIVATION_FOR_TERMINAL', - PIPENV_INTERPRETER_DISCOVERY = 'PIPENV_INTERPRETER_DISCOVERY', TERMINAL_SHELL_IDENTIFICATION = 'TERMINAL_SHELL_IDENTIFICATION', PYTHON_INTERPRETER_ACTIVATE_ENVIRONMENT_PROMPT = 'PYTHON_INTERPRETER_ACTIVATE_ENVIRONMENT_PROMPT', PYTHON_NOT_INSTALLED_PROMPT = 'PYTHON_NOT_INSTALLED_PROMPT', CONDA_INHERIT_ENV_PROMPT = 'CONDA_INHERIT_ENV_PROMPT', - UNSAFE_INTERPRETER_PROMPT = 'UNSAFE_INTERPRETER_PROMPT', - INSIDERS_RELOAD_PROMPT = 'INSIDERS_RELOAD_PROMPT', - INSIDERS_PROMPT = 'INSIDERS_PROMPT', + REQUIRE_JUPYTER_PROMPT = 'REQUIRE_JUPYTER_PROMPT', + ACTIVATED_CONDA_ENV_LAUNCH = 'ACTIVATED_CONDA_ENV_LAUNCH', ENVFILE_VARIABLE_SUBSTITUTION = 'ENVFILE_VARIABLE_SUBSTITUTION', ENVFILE_WORKSPACE = 'ENVFILE_WORKSPACE', - WORKSPACE_SYMBOLS_BUILD = 'WORKSPACE_SYMBOLS.BUILD', - WORKSPACE_SYMBOLS_GO_TO = 'WORKSPACE_SYMBOLS.GO_TO', EXECUTION_CODE = 'EXECUTION_CODE', EXECUTION_DJANGO = 'EXECUTION_DJANGO', - DEBUG_ADAPTER_USING_WHEELS_PATH = 'DEBUG_ADAPTER.USING_WHEELS_PATH', - DEBUG_SESSION_ERROR = 'DEBUG_SESSION.ERROR', - DEBUG_SESSION_START = 'DEBUG_SESSION.START', - DEBUG_SESSION_STOP = 'DEBUG_SESSION.STOP', - DEBUG_SESSION_USER_CODE_RUNNING = 'DEBUG_SESSION.USER_CODE_RUNNING', - DEBUGGER = 'DEBUGGER', - DEBUGGER_ATTACH_TO_CHILD_PROCESS = 'DEBUGGER.ATTACH_TO_CHILD_PROCESS', - DEBUGGER_ATTACH_TO_LOCAL_PROCESS = 'DEBUGGER.ATTACH_TO_LOCAL_PROCESS', - DEBUGGER_CONFIGURATION_PROMPTS = 'DEBUGGER.CONFIGURATION.PROMPTS', - DEBUGGER_CONFIGURATION_PROMPTS_IN_LAUNCH_JSON = 'DEBUGGER.CONFIGURATION.PROMPTS.IN.LAUNCH.JSON', - UNITTEST_STOP = 'UNITTEST.STOP', - UNITTEST_DISABLE = 'UNITTEST.DISABLE', - UNITTEST_RUN = 'UNITTEST.RUN', - UNITTEST_DISCOVER = 'UNITTEST.DISCOVER', - UNITTEST_DISCOVER_WITH_PYCODE = 'UNITTEST.DISCOVER.WITH.PYTHONCODE', - UNITTEST_CONFIGURE = 'UNITTEST.CONFIGURE', - UNITTEST_CONFIGURING = 'UNITTEST.CONFIGURING', - UNITTEST_VIEW_OUTPUT = 'UNITTEST.VIEW_OUTPUT', - UNITTEST_NAVIGATE = 'UNITTEST.NAVIGATE', - UNITTEST_ENABLED = 'UNITTEST.ENABLED', - UNITTEST_EXPLORER_WORK_SPACE_COUNT = 'UNITTEST.TEST_EXPLORER.WORK_SPACE_COUNT', - PYTHON_EXPERIMENTS = 'PYTHON_EXPERIMENTS', - PYTHON_EXPERIMENTS_DISABLED = 'PYTHON_EXPERIMENTS_DISABLED', - PYTHON_EXPERIMENTS_OPT_IN_OUT = 'PYTHON_EXPERIMENTS_OPT_IN_OUT', - PYTHON_EXPERIMENTS_DOWNLOAD_SUCCESS_RATE = 'PYTHON_EXPERIMENTS_DOWNLOAD_SUCCESS_RATE', - PLAY_BUTTON_ICON_DISABLED = 'PLAY_BUTTON_ICON.DISABLED', - PYTHON_WEB_APP_RELOAD = 'PYTHON_WEB_APP.RELOAD', - EXTENSION_SURVEY_PROMPT = 'EXTENSION_SURVEY_PROMPT', - ACTIVATION_TIP_PROMPT = 'ACTIVATION_TIP_PROMPT', - ACTIVATION_SURVEY_PROMPT = 'ACTIVATION_SURVEY_PROMPT', - JOIN_MAILING_LIST_PROMPT = 'JOIN_MAILING_LIST_PROMPT', + // Python testing specific telemetry + UNITTEST_CONFIGURING = 'UNITTEST.CONFIGURING', + UNITTEST_CONFIGURE = 'UNITTEST.CONFIGURE', + UNITTEST_DISCOVERY_TRIGGER = 'UNITTEST.DISCOVERY.TRIGGER', + UNITTEST_DISCOVERING = 'UNITTEST.DISCOVERING', + UNITTEST_DISCOVERING_STOP = 'UNITTEST.DISCOVERY.STOP', + UNITTEST_DISCOVERY_DONE = 'UNITTEST.DISCOVERY.DONE', + UNITTEST_RUN_STOP = 'UNITTEST.RUN.STOP', + UNITTEST_RUN = 'UNITTEST.RUN', + UNITTEST_RUN_ALL_FAILED = 'UNITTEST.RUN_ALL_FAILED', + UNITTEST_DISABLED = 'UNITTEST.DISABLED', - PYTHON_LANGUAGE_SERVER_CURRENT_SELECTION = 'PYTHON_LANGUAGE_SERVER_CURRENT_SELECTION', - PYTHON_LANGUAGE_SERVER_LIST_BLOB_STORE_PACKAGES = 'PYTHON_LANGUAGE_SERVER.LIST_BLOB_PACKAGES', - PYTHON_LANGUAGE_SERVER_EXTRACTED = 'PYTHON_LANGUAGE_SERVER.EXTRACTED', - PYTHON_LANGUAGE_SERVER_DOWNLOADED = 'PYTHON_LANGUAGE_SERVER.DOWNLOADED', - PYTHON_LANGUAGE_SERVER_ERROR = 'PYTHON_LANGUAGE_SERVER.ERROR', + PYTHON_EXPERIMENTS_INIT_PERFORMANCE = 'PYTHON_EXPERIMENTS_INIT_PERFORMANCE', + PYTHON_EXPERIMENTS_LSP_NOTEBOOKS = 'PYTHON_EXPERIMENTS_LSP_NOTEBOOKS', + PYTHON_EXPERIMENTS_OPT_IN_OPT_OUT_SETTINGS = 'PYTHON_EXPERIMENTS_OPT_IN_OPT_OUT_SETTINGS', - PYTHON_LANGUAGE_SERVER_PLATFORM_SUPPORTED = 'PYTHON_LANGUAGE_SERVER.PLATFORM_SUPPORTED', - PYTHON_LANGUAGE_SERVER_ENABLED = 'PYTHON_LANGUAGE_SERVER.ENABLED', - PYTHON_LANGUAGE_SERVER_STARTUP = 'PYTHON_LANGUAGE_SERVER.STARTUP', - PYTHON_LANGUAGE_SERVER_READY = 'PYTHON_LANGUAGE_SERVER.READY', - PYTHON_LANGUAGE_SERVER_TELEMETRY = 'PYTHON_LANGUAGE_SERVER.EVENT', - PYTHON_LANGUAGE_SERVER_REQUEST = 'PYTHON_LANGUAGE_SERVER.REQUEST', + EXTENSION_SURVEY_PROMPT = 'EXTENSION_SURVEY_PROMPT', LANGUAGE_SERVER_ENABLED = 'LANGUAGE_SERVER.ENABLED', + LANGUAGE_SERVER_TRIGGER_TIME = 'LANGUAGE_SERVER_TRIGGER_TIME', LANGUAGE_SERVER_STARTUP = 'LANGUAGE_SERVER.STARTUP', LANGUAGE_SERVER_READY = 'LANGUAGE_SERVER.READY', LANGUAGE_SERVER_TELEMETRY = 'LANGUAGE_SERVER.EVENT', LANGUAGE_SERVER_REQUEST = 'LANGUAGE_SERVER.REQUEST', - LANGUAGE_SERVER_TRY_PYLANCE = 'LANGUAGE_SERVER.TRY_PYLANCE', + LANGUAGE_SERVER_RESTART = 'LANGUAGE_SERVER.RESTART', TERMINAL_CREATE = 'TERMINAL.CREATE', ACTIVATE_ENV_IN_CURRENT_TERMINAL = 'ACTIVATE_ENV_IN_CURRENT_TERMINAL', ACTIVATE_ENV_TO_GET_ENV_VARS_FAILED = 'ACTIVATE_ENV_TO_GET_ENV_VARS_FAILED', DIAGNOSTICS_ACTION = 'DIAGNOSTICS.ACTION', DIAGNOSTICS_MESSAGE = 'DIAGNOSTICS.MESSAGE', - PLATFORM_INFO = 'PLATFORM.INFO', - SELECT_LINTER = 'LINTING.SELECT', + USE_REPORT_ISSUE_COMMAND = 'USE_REPORT_ISSUE_COMMAND', - LINTER_NOT_INSTALLED_PROMPT = 'LINTER_NOT_INSTALLED_PROMPT', - CONFIGURE_AVAILABLE_LINTER_PROMPT = 'CONFIGURE_AVAILABLE_LINTER_PROMPT', HASHED_PACKAGE_NAME = 'HASHED_PACKAGE_NAME', - HASHED_PACKAGE_PERF = 'HASHED_PACKAGE_PERF', - JEDI_MEMORY = 'JEDI_MEMORY' + JEDI_LANGUAGE_SERVER_ENABLED = 'JEDI_LANGUAGE_SERVER.ENABLED', + JEDI_LANGUAGE_SERVER_STARTUP = 'JEDI_LANGUAGE_SERVER.STARTUP', + JEDI_LANGUAGE_SERVER_READY = 'JEDI_LANGUAGE_SERVER.READY', + JEDI_LANGUAGE_SERVER_REQUEST = 'JEDI_LANGUAGE_SERVER.REQUEST', + + TENSORBOARD_INSTALL_PROMPT_SHOWN = 'TENSORBOARD.INSTALL_PROMPT_SHOWN', + TENSORBOARD_INSTALL_PROMPT_SELECTION = 'TENSORBOARD.INSTALL_PROMPT_SELECTION', + TENSORBOARD_DETECTED_IN_INTEGRATED_TERMINAL = 'TENSORBOARD_DETECTED_IN_INTEGRATED_TERMINAL', + TENSORBOARD_PACKAGE_INSTALL_RESULT = 'TENSORBOARD.PACKAGE_INSTALL_RESULT', + TENSORBOARD_TORCH_PROFILER_IMPORT = 'TENSORBOARD.TORCH_PROFILER_IMPORT', + + ENVIRONMENT_CREATING = 'ENVIRONMENT.CREATING', + ENVIRONMENT_CREATED = 'ENVIRONMENT.CREATED', + ENVIRONMENT_FAILED = 'ENVIRONMENT.FAILED', + ENVIRONMENT_INSTALLING_PACKAGES = 'ENVIRONMENT.INSTALLING_PACKAGES', + ENVIRONMENT_INSTALLED_PACKAGES = 'ENVIRONMENT.INSTALLED_PACKAGES', + ENVIRONMENT_INSTALLING_PACKAGES_FAILED = 'ENVIRONMENT.INSTALLING_PACKAGES_FAILED', + ENVIRONMENT_BUTTON = 'ENVIRONMENT.BUTTON', + ENVIRONMENT_DELETE = 'ENVIRONMENT.DELETE', + ENVIRONMENT_REUSE = 'ENVIRONMENT.REUSE', + + ENVIRONMENT_CHECK_TRIGGER = 'ENVIRONMENT.CHECK.TRIGGER', + ENVIRONMENT_CHECK_RESULT = 'ENVIRONMENT.CHECK.RESULT', + ENVIRONMENT_TERMINAL_GLOBAL_PIP = 'ENVIRONMENT.TERMINAL.GLOBAL_PIP', } export enum PlatformErrors { FailedToParseVersion = 'FailedToParseVersion', - FailedToDetermineOS = 'FailedToDetermineOS' + FailedToDetermineOS = 'FailedToDetermineOS', } diff --git a/src/client/telemetry/envFileTelemetry.ts b/src/client/telemetry/envFileTelemetry.ts index dfb30d851297..bf76a08733f6 100644 --- a/src/client/telemetry/envFileTelemetry.ts +++ b/src/client/telemetry/envFileTelemetry.ts @@ -14,13 +14,13 @@ import { EventName } from './constants'; let _defaultEnvFileSetting: string | undefined; let envFileTelemetrySent = false; -export function sendSettingTelemetry(workspaceService: IWorkspaceService, envFileSetting?: string) { +export function sendSettingTelemetry(workspaceService: IWorkspaceService, envFileSetting?: string): void { if (shouldSendTelemetry() && envFileSetting !== defaultEnvFileSetting(workspaceService)) { sendTelemetry(true); } } -export function sendFileCreationTelemetry() { +export function sendFileCreationTelemetry(): void { if (shouldSendTelemetry()) { sendTelemetry(); } @@ -29,8 +29,8 @@ export function sendFileCreationTelemetry() { export async function sendActivationTelemetry( fileSystem: IFileSystem, workspaceService: IWorkspaceService, - resource: Resource -) { + resource: Resource, +): Promise { if (shouldSendTelemetry()) { const systemVariables = new SystemVariables(resource, undefined, workspaceService); const envFilePath = systemVariables.resolveAny(defaultEnvFileSetting(workspaceService))!; @@ -42,7 +42,7 @@ export async function sendActivationTelemetry( } } -function sendTelemetry(hasCustomEnvPath: boolean = false) { +function sendTelemetry(hasCustomEnvPath = false) { sendTelemetryEvent(EventName.ENVFILE_WORKSPACE, undefined, { hasCustomEnvPath }); envFileTelemetrySent = true; @@ -62,18 +62,17 @@ function defaultEnvFileSetting(workspaceService: IWorkspaceService) { } // Set state for tests. -export namespace EnvFileTelemetryTests { - export function setState({ telemetrySent, defaultSetting }: { telemetrySent?: boolean; defaultSetting?: string }) { +export const EnvFileTelemetryTests = { + setState: ({ telemetrySent, defaultSetting }: { telemetrySent?: boolean; defaultSetting?: string }): void => { if (telemetrySent !== undefined) { envFileTelemetrySent = telemetrySent; } if (defaultEnvFileSetting !== undefined) { _defaultEnvFileSetting = defaultSetting; } - } - - export function resetState() { + }, + resetState: (): void => { _defaultEnvFileSetting = undefined; envFileTelemetrySent = false; - } -} + }, +}; diff --git a/src/client/telemetry/extensionInstallTelemetry.ts b/src/client/telemetry/extensionInstallTelemetry.ts index 87e6ec50e3ea..ea012b694971 100644 --- a/src/client/telemetry/extensionInstallTelemetry.ts +++ b/src/client/telemetry/extensionInstallTelemetry.ts @@ -13,7 +13,7 @@ import { EXTENSION_ROOT_DIR } from '../constants'; * case as 'MarketPlace'. * */ -export async function setExtensionInstallTelemetryProperties(fs: IFileSystem) { +export async function setExtensionInstallTelemetryProperties(fs: IFileSystem): Promise { // Look for PythonCodingPack file under `%USERPROFILE%/.vscode/extensions` // folder. If that file exists treat this extension as installed from coding // pack. diff --git a/src/client/telemetry/importTracker.ts b/src/client/telemetry/importTracker.ts index 44fa02794b60..cf8e1ed48837 100644 --- a/src/client/telemetry/importTracker.ts +++ b/src/client/telemetry/importTracker.ts @@ -1,18 +1,22 @@ +/* eslint-disable class-methods-use-this */ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + 'use strict'; import { inject, injectable } from 'inversify'; import * as path from 'path'; +import { clearTimeout, setTimeout } from 'timers'; import { TextDocument } from 'vscode'; -import { captureTelemetry, sendTelemetryEvent } from '.'; -import { splitMultilineString } from '../../datascience-ui/common'; +import { createHash } from 'crypto'; +import { sendTelemetryEvent } from '.'; import { IExtensionSingleActivationService } from '../activation/types'; import { IDocumentManager } from '../common/application/types'; import { isTestExecution } from '../common/constants'; import '../common/extensions'; +import { IDisposableRegistry } from '../common/types'; import { noop } from '../common/utils/misc'; -import { ICell, INotebookEditor, INotebookEditorProvider, INotebookExecutionLogger } from '../datascience/types'; +import { TorchProfilerImportRegEx } from '../tensorBoard/helpers'; import { EventName } from './constants'; /* @@ -44,91 +48,41 @@ const MAX_DOCUMENT_LINES = 1000; const testExecution = isTestExecution(); @injectable() -export class ImportTracker implements IExtensionSingleActivationService, INotebookExecutionLogger { - private pendingChecks = new Map(); - private sentMatches: Set = new Set(); - // tslint:disable-next-line:no-require-imports - private hashFn = require('hash.js').sha256; +export class ImportTracker implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; + + private pendingChecks = new Map(); + + private static sentMatches: Set = new Set(); constructor( @inject(IDocumentManager) private documentManager: IDocumentManager, - @inject(INotebookEditorProvider) private notebookEditorProvider: INotebookEditorProvider + @inject(IDisposableRegistry) private disposables: IDisposableRegistry, ) { - this.documentManager.onDidOpenTextDocument((t) => this.onOpenedOrSavedDocument(t)); - this.documentManager.onDidSaveTextDocument((t) => this.onOpenedOrSavedDocument(t)); - this.notebookEditorProvider.onDidOpenNotebookEditor((t) => this.onOpenedOrClosedNotebook(t)); - this.notebookEditorProvider.onDidCloseNotebookEditor((t) => this.onOpenedOrClosedNotebook(t)); + this.documentManager.onDidOpenTextDocument((t) => this.onOpenedOrSavedDocument(t), this, this.disposables); + this.documentManager.onDidSaveTextDocument((t) => this.onOpenedOrSavedDocument(t), this, this.disposables); } - public dispose() { + public dispose(): void { this.pendingChecks.clear(); } - public onKernelRestarted() { - // Do nothing on restarted - } - public async preExecute(_cell: ICell, _silent: boolean): Promise { - // Do nothing on pre execute - } - public async postExecute(cell: ICell, silent: boolean): Promise { - // Check for imports in the cell itself. - if (!silent && cell.data.cell_type === 'code') { - this.scheduleCheck(this.createCellKey(cell), this.checkCell.bind(this, cell)); - } - } - public async activate(): Promise { // Act like all of our open documents just opened; our timeout will make sure this is delayed. this.documentManager.textDocuments.forEach((d) => this.onOpenedOrSavedDocument(d)); - this.notebookEditorProvider.editors.forEach((e) => this.onOpenedOrClosedNotebook(e)); - } - - private getDocumentLines(document: TextDocument): (string | undefined)[] { - const array = Array(Math.min(document.lineCount, MAX_DOCUMENT_LINES)).fill(''); - return array - .map((_a: string, i: number) => { - const line = document.lineAt(i); - if (line && !line.isEmptyOrWhitespace) { - return line.text; - } - return undefined; - }) - .filter((f: string | undefined) => f); - } - - private getNotebookLines(e: INotebookEditor): (string | undefined)[] { - let result: (string | undefined)[] = []; - if (e.model) { - e.model.cells - .filter((c) => c.data.cell_type === 'code') - .forEach((c) => { - const cellArray = this.getCellLines(c); - if (result.length < MAX_DOCUMENT_LINES) { - result = [...result, ...cellArray]; - } - }); - } - return result; } - private getCellLines(cell: ICell): (string | undefined)[] { - // Split into multiple lines removing line feeds on the end. - return splitMultilineString(cell.data.source).map((s) => s.replace(/\n/g, '')); + public static hasModuleImport(moduleName: string): boolean { + return this.sentMatches.has(moduleName); } private onOpenedOrSavedDocument(document: TextDocument) { // Make sure this is a Python file. - if (path.extname(document.fileName) === '.py') { + if (path.extname(document.fileName).toLowerCase() === '.py') { this.scheduleDocument(document); } } - private onOpenedOrClosedNotebook(e: INotebookEditor) { - if (e.file) { - this.scheduleCheck(e.file.fsPath, this.checkNotebook.bind(this, e)); - } - } - private scheduleDocument(document: TextDocument) { this.scheduleCheck(document.fileName, this.checkDocument.bind(this, document)); } @@ -137,8 +91,7 @@ export class ImportTracker implements IExtensionSingleActivationService, INotebo // If already scheduled, cancel. const currentTimeout = this.pendingChecks.get(file); if (currentTimeout) { - // tslint:disable-next-line: no-any - clearTimeout(currentTimeout as any); + clearTimeout(currentTimeout); this.pendingChecks.delete(file); } @@ -152,40 +105,21 @@ export class ImportTracker implements IExtensionSingleActivationService, INotebo } } - private createCellKey(cell: ICell): string { - return `${cell.file}${cell.id}`; - } - - @captureTelemetry(EventName.HASHED_PACKAGE_PERF) - private checkCell(cell: ICell) { - this.pendingChecks.delete(this.createCellKey(cell)); - const lines = this.getCellLines(cell); - this.lookForImports(lines); - } - - @captureTelemetry(EventName.HASHED_PACKAGE_PERF) - private checkNotebook(e: INotebookEditor) { - this.pendingChecks.delete(e.file.fsPath); - const lines = this.getNotebookLines(e); - this.lookForImports(lines); - } - - @captureTelemetry(EventName.HASHED_PACKAGE_PERF) private checkDocument(document: TextDocument) { this.pendingChecks.delete(document.fileName); - const lines = this.getDocumentLines(document); + const lines = getDocumentLines(document); this.lookForImports(lines); } private sendTelemetry(packageName: string) { // No need to send duplicate telemetry or waste CPU cycles on an unneeded hash. - if (this.sentMatches.has(packageName)) { + if (ImportTracker.sentMatches.has(packageName)) { return; } - this.sentMatches.add(packageName); + ImportTracker.sentMatches.add(packageName); // Hash the package name so that we will never accidentally see a // user's private package name. - const hash = this.hashFn().update(packageName).digest('hex'); + const hash = createHash('sha256').update(packageName).digest('hex'); sendTelemetryEvent(EventName.HASHED_PACKAGE_NAME, undefined, { hashedName: hash }); } @@ -206,6 +140,9 @@ export class ImportTracker implements IExtensionSingleActivationService, INotebo packageNames.forEach((p) => this.sendTelemetry(p)); } } + if (s && TorchProfilerImportRegEx.test(s)) { + sendTelemetryEvent(EventName.TENSORBOARD_TORCH_PROFILER_IMPORT); + } } } catch { // Don't care about failures since this is just telemetry. @@ -213,3 +150,16 @@ export class ImportTracker implements IExtensionSingleActivationService, INotebo } } } + +export function getDocumentLines(document: TextDocument): (string | undefined)[] { + const array = Array(Math.min(document.lineCount, MAX_DOCUMENT_LINES)).fill(''); + return array + .map((_a: string, i: number) => { + const line = document.lineAt(i); + if (line && !line.isEmptyOrWhitespace) { + return line.text; + } + return undefined; + }) + .filter((f: string | undefined) => f); +} diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index 9c71a5ec6fbc..763f7405aa0d 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -1,65 +1,51 @@ +/* eslint-disable global-require */ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import type { JSONObject } from '@phosphor/coreutils'; -// tslint:disable-next-line: import-name -import TelemetryReporter from 'vscode-extension-telemetry/lib/telemetryReporter'; - -import { LanguageServerType } from '../activation/types'; +import TelemetryReporter from '@vscode/extension-telemetry'; +import type * as vscodeTypes from 'vscode'; import { DiagnosticCodes } from '../application/diagnostics/constants'; -import { IWorkspaceService } from '../common/application/types'; import { AppinsightsKey, isTestExecution, isUnitTestExecution, PVSC_EXTENSION_ID } from '../common/constants'; -import { traceError, traceInfo } from '../common/logger'; -import { TerminalShellType } from '../common/terminal/types'; -import { Architecture } from '../common/utils/platform'; +import type { TerminalShellType } from '../common/terminal/types'; +import { isPromise } from '../common/utils/async'; import { StopWatch } from '../common/utils/stopWatch'; -import { - JupyterCommands, - NativeKeyboardCommandTelemetry, - NativeMouseCommandTelemetry, - Telemetry, - VSCodeNativeTelemetry -} from '../datascience/constants'; -import { ExportFormat } from '../datascience/export/types'; -import { DebugConfigurationType } from '../debugger/extension/types'; -import { ConsoleType, TriggerType } from '../debugger/types'; -import { AutoSelectionRule } from '../interpreter/autoSelection/types'; -import { LinterId } from '../linters/types'; -import { EnvironmentType } from '../pythonEnvironments/info'; -import { TestProvider } from '../testing/common/types'; -import { EventName, PlatformErrors } from './constants'; -import { LinterTrigger, TestTool } from './types'; - -// tslint:disable: no-any +import { EnvironmentType, PythonEnvironment } from '../pythonEnvironments/info'; +import { TensorBoardPromptSelection } from '../tensorBoard/constants'; +import { EventName } from './constants'; +import type { TestTool } from './types'; /** * Checks whether telemetry is supported. * Its possible this function gets called within Debug Adapter, vscode isn't available in there. * Within DA, there's a completely different way to send telemetry. - * @returns {boolean} */ function isTelemetrySupported(): boolean { try { - // tslint:disable-next-line:no-require-imports const vsc = require('vscode'); - // tslint:disable-next-line:no-require-imports - const reporter = require('vscode-extension-telemetry'); + const reporter = require('@vscode/extension-telemetry'); + return vsc !== undefined && reporter !== undefined; } catch { return false; } } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +let packageJSON: any; + /** - * Checks if the telemetry is disabled in user settings - * @returns {boolean} + * Checks if the telemetry is disabled */ -export function isTelemetryDisabled(workspaceService: IWorkspaceService): boolean { - const settings = workspaceService.getConfiguration('telemetry').inspect('enableTelemetry')!; - return settings.globalValue === false ? true : false; +export function isTelemetryDisabled(): boolean { + if (!packageJSON) { + const vscode = require('vscode') as typeof vscodeTypes; + const pythonExtension = vscode.extensions.getExtension(PVSC_EXTENSION_ID)!; + packageJSON = pythonExtension.packageJSON; + } + return !packageJSON.enableTelemetry; } -const sharedProperties: Record = {}; +const sharedProperties: Record = {}; /** * Set shared properties for all telemetry events. */ @@ -86,102 +72,106 @@ export function _resetSharedProperties(): void { } let telemetryReporter: TelemetryReporter | undefined; -function getTelemetryReporter() { +export function getTelemetryReporter(): TelemetryReporter { if (!isTestExecution() && telemetryReporter) { return telemetryReporter; } - const extensionId = PVSC_EXTENSION_ID; - // tslint:disable-next-line:no-require-imports - const extensions = (require('vscode') as typeof import('vscode')).extensions; - const extension = extensions.getExtension(extensionId)!; - const extensionVersion = extension.packageJSON.version; - // tslint:disable-next-line:no-require-imports - const reporter = require('vscode-extension-telemetry').default as typeof TelemetryReporter; - return (telemetryReporter = new reporter(extensionId, extensionVersion, AppinsightsKey, true)); + const Reporter = require('@vscode/extension-telemetry').default as typeof TelemetryReporter; + telemetryReporter = new Reporter(AppinsightsKey, [ + { + lookup: /(errorName|errorMessage|errorStack)/g, + }, + ]); + + return telemetryReporter; } -export function clearTelemetryReporter() { +export function clearTelemetryReporter(): void { telemetryReporter = undefined; } export function sendTelemetryEvent

( eventName: E, - durationMs?: Record | number, + measuresOrDurationMs?: Record | number, properties?: P[E], - ex?: Error -) { - if (isTestExecution() || !isTelemetrySupported()) { + ex?: Error, +): void { + if (isTestExecution() || !isTelemetrySupported() || isTelemetryDisabled()) { return; } const reporter = getTelemetryReporter(); - const measures = typeof durationMs === 'number' ? { duration: durationMs } : durationMs ? durationMs : undefined; - let customProperties: Record = {}; + const measures = + typeof measuresOrDurationMs === 'number' + ? { duration: measuresOrDurationMs } + : measuresOrDurationMs || undefined; + const customProperties: Record = {}; const eventNameSent = eventName as string; - if (ex) { - // When sending telemetry events for exceptions no need to send custom properties. - // Else we have to review all properties every time as part of GDPR. - // Assume we have 10 events all with their own properties. - // As we have errors for each event, those properties are treated as new data items. - // Hence they need to be classified as part of the GDPR process, and thats unnecessary and onerous. - customProperties = { originalEventName: eventName as string }; - reporter.sendTelemetryException(ex, customProperties, measures); - } else { - if (properties) { - const data = properties as any; - Object.getOwnPropertyNames(data).forEach((prop) => { - if (data[prop] === undefined || data[prop] === null) { - return; - } - try { - // If there are any errors in serializing one property, ignore that and move on. - // Else nothing will be sent. - customProperties[prop] = - typeof data[prop] === 'string' - ? data[prop] - : typeof data[prop] === 'object' - ? 'object' - : data[prop].toString(); - } catch (ex) { - traceError(`Failed to serialize ${prop} for ${eventName}`, ex); + if (properties) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const data = properties as any; + Object.getOwnPropertyNames(data).forEach((prop) => { + if (data[prop] === undefined || data[prop] === null) { + return; + } + try { + // If there are any errors in serializing one property, ignore that and move on. + // Else nothing will be sent. + switch (typeof data[prop]) { + case 'string': + customProperties[prop] = data[prop]; + break; + case 'object': + customProperties[prop] = 'object'; + break; + default: + customProperties[prop] = data[prop].toString(); + break; } - }); - } - - // Add shared properties to telemetry props (we may overwrite existing ones). - Object.assign(customProperties, sharedProperties); - - // Remove shared DS properties from core extension telemetry. - Object.keys(sharedProperties).forEach((shareProperty) => { - if ( - customProperties[shareProperty] && - shareProperty.startsWith('ds_') && - !(eventNameSent.startsWith('DS_') || eventNameSent.startsWith('DATASCIENCE')) - ) { - delete customProperties[shareProperty]; + } catch (exception) { + console.error(`Failed to serialize ${prop} for ${String(eventName)}`, exception); // use console due to circular dependencies with trace calls } }); + } + // Add shared properties to telemetry props (we may overwrite existing ones). + Object.assign(customProperties, sharedProperties); + + if (ex) { + const errorProps = { + errorName: ex.name, + errorStack: ex.stack ?? '', + }; + Object.assign(customProperties, errorProps); + reporter.sendTelemetryErrorEvent(eventNameSent, customProperties, measures); + } else { reporter.sendTelemetryEvent(eventNameSent, customProperties, measures); } if (process.env && process.env.VSC_PYTHON_LOG_TELEMETRY) { - traceInfo( + console.info( `Telemetry Event : ${eventNameSent} Measures: ${JSON.stringify(measures)} Props: ${JSON.stringify( - customProperties - )} ` - ); + customProperties, + )} `, + ); // use console due to circular dependencies with trace calls } } // Type-parameterized form of MethodDecorator in lib.es5.d.ts. type TypedMethodDescriptor = ( - target: Object, + target: unknown, propertyKey: string | symbol, - descriptor: TypedPropertyDescriptor + descriptor: TypedPropertyDescriptor, ) => TypedPropertyDescriptor | void; +// The following code uses "any" in many places, as TS does not have rich support +// for typing decorators. Specifically, while it is possible to write types which +// encode the signature of the wrapped function, TS fails to actually infer the +// type of "this" and the signature at call sites, instead choosing to infer +// based on other hints (like the closure parameters), which ends up making it +// no safer than "any" (and sometimes misleading enough to be more unsafe). + /** * Decorates a method, sending a telemetry event with the given properties. * @param eventName The event name to send. @@ -190,67 +180,72 @@ type TypedMethodDescriptor = ( * @param failureEventName If the decorated method returns a Promise and fails, send this event instead of eventName. * @param lazyProperties A static function on the decorated class which returns extra properties to add to the event. * This can be used to provide properties which are only known at runtime (after the decorator has executed). + * @param lazyMeasures A static function on the decorated class which returns extra measures to add to the event. + * This can be used to provide measures which are only known at runtime (after the decorator has executed). */ -// tslint:disable-next-line:no-any function-name export function captureTelemetry( eventName: E, properties?: P[E], - captureDuration: boolean = true, + captureDuration = true, failureEventName?: E, - lazyProperties?: (obj: This) => P[E] + // eslint-disable-next-line @typescript-eslint/no-explicit-any + lazyProperties?: (obj: This, result?: any) => P[E], + // eslint-disable-next-line @typescript-eslint/no-explicit-any + lazyMeasures?: (obj: This, result?: any) => Record, + // eslint-disable-next-line @typescript-eslint/no-explicit-any ): TypedMethodDescriptor<(this: This, ...args: any[]) => any> { - // tslint:disable-next-line:no-function-expression no-any return function ( - _target: Object, + _target: unknown, _propertyKey: string | symbol, - descriptor: TypedPropertyDescriptor<(this: This, ...args: any[]) => any> + // eslint-disable-next-line @typescript-eslint/no-explicit-any + descriptor: TypedPropertyDescriptor<(this: This, ...args: any[]) => any>, ) { const originalMethod = descriptor.value!; - // tslint:disable-next-line:no-function-expression no-any + + // eslint-disable-next-line @typescript-eslint/no-explicit-any descriptor.value = function (this: This, ...args: any[]) { // Legacy case; fast path that sends event before method executes. // Does not set "failed" if the result is a Promise and throws an exception. - if (!captureDuration && !lazyProperties) { + if (!captureDuration && !lazyProperties && !lazyMeasures) { sendTelemetryEvent(eventName, undefined, properties); - // tslint:disable-next-line:no-invalid-this + return originalMethod.apply(this, args); } - const props = () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const getProps = (result?: any) => { if (lazyProperties) { - return { ...properties, ...lazyProperties(this) }; + return { ...properties, ...lazyProperties(this, result) }; } return properties; }; const stopWatch = captureDuration ? new StopWatch() : undefined; - // tslint:disable-next-line:no-invalid-this no-use-before-declare no-unsafe-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const getMeasures = (result?: any) => { + const measures = stopWatch ? { duration: stopWatch.elapsedTime } : undefined; + if (lazyMeasures) { + return { ...measures, ...lazyMeasures(this, result) }; + } + return measures; + }; + const result = originalMethod.apply(this, args); // If method being wrapped returns a promise then wait for it. - // tslint:disable-next-line:no-unsafe-any - if (result && typeof result.then === 'function' && typeof result.catch === 'function') { - // tslint:disable-next-line:prefer-type-cast - (result as Promise) + if (result && isPromise(result)) { + result .then((data) => { - sendTelemetryEvent(eventName, stopWatch?.elapsedTime, props()); + sendTelemetryEvent(eventName, getMeasures(data), getProps(data)); return data; }) - // tslint:disable-next-line:promise-function-async .catch((ex) => { - // tslint:disable-next-line:no-any - const failedProps: P[E] = props() || ({} as any); - (failedProps as any).failed = true; - sendTelemetryEvent( - failureEventName ? failureEventName : eventName, - stopWatch?.elapsedTime, - failedProps, - ex - ); + const failedProps: P[E] = { ...getProps(), failed: true } as P[E] & FailedEventType; + sendTelemetryEvent(failureEventName || eventName, getMeasures(), failedProps, ex); }); } else { - sendTelemetryEvent(eventName, stopWatch?.elapsedTime, props()); + sendTelemetryEvent(eventName, getMeasures(result), getProps(result)); } return result; @@ -263,25 +258,21 @@ export function captureTelemetry(eventName: K, properties?: T[K]); export function sendTelemetryWhenDone

( eventName: E, - promise: Promise | Thenable, + promise: Promise | Thenable, stopWatch?: StopWatch, - properties?: P[E] -) { - stopWatch = stopWatch ? stopWatch : new StopWatch(); + properties?: P[E], +): void { + stopWatch = stopWatch || new StopWatch(); if (typeof promise.then === 'function') { - // tslint:disable-next-line:prefer-type-cast no-any - (promise as Promise).then( + (promise as Promise).then( (data) => { - // tslint:disable-next-line:no-non-null-assertion sendTelemetryEvent(eventName, stopWatch!.elapsedTime, properties); return data; - // tslint:disable-next-line:promise-function-async }, (ex) => { - // tslint:disable-next-line:no-non-null-assertion sendTelemetryEvent(eventName, stopWatch!.elapsedTime, properties, ex); return Promise.reject(ex); - } + }, ); } else { throw new Error('Method is neither a Promise nor a Theneable'); @@ -304,301 +295,10 @@ export interface ISharedPropertyMapping { ['installSource']: undefined | 'marketPlace' | 'pythonCodingPack'; } +type FailedEventType = { failed: true }; + // Map all events to their properties export interface IEventNamePropertyMapping { - /** - * Telemetry event sent when providing completion items for the given position and document. - */ - [EventName.COMPLETION]: never | undefined; - /** - * Telemetry event sent with details 'python.autoComplete.addBrackets' setting - */ - [EventName.COMPLETION_ADD_BRACKETS]: { - /** - * Carries boolean `true` if 'python.autoComplete.addBrackets' is set to true, `false` otherwise - */ - enabled: boolean; - }; - /** - * Telemetry event captured when debug adapter executable is created - */ - [EventName.DEBUG_ADAPTER_USING_WHEELS_PATH]: { - /** - * Carries boolean - * - `true` if path used for the adapter is the debugger with wheels. - * - `false` if path used for the adapter is the source only version of the debugger. - */ - usingWheels: boolean; - }; - /** - * Telemetry captured before starting debug session. - */ - [EventName.DEBUG_SESSION_START]: { - /** - * Trigger for starting the debugger. - * - `launch`: Launch/start new code and debug it. - * - `attach`: Attach to an exiting python process (remote debugging). - * - `test`: Debugging python tests. - * - * @type {TriggerType} - */ - trigger: TriggerType; - /** - * Type of console used. - * -`internalConsole`: Use VS Code debug console (no shells/terminals). - * - `integratedTerminal`: Use VS Code terminal. - * - `externalTerminal`: Use an External terminal. - * - * @type {ConsoleType} - */ - console?: ConsoleType; - }; - /** - * Telemetry captured when debug session runs into an error. - */ - [EventName.DEBUG_SESSION_ERROR]: { - /** - * Trigger for starting the debugger. - * - `launch`: Launch/start new code and debug it. - * - `attach`: Attach to an exiting python process (remote debugging). - * - `test`: Debugging python tests. - * - * @type {TriggerType} - */ - trigger: TriggerType; - /** - * Type of console used. - * -`internalConsole`: Use VS Code debug console (no shells/terminals). - * - `integratedTerminal`: Use VS Code terminal. - * - `externalTerminal`: Use an External terminal. - * - * @type {ConsoleType} - */ - console?: ConsoleType; - }; - /** - * Telemetry captured after stopping debug session. - */ - [EventName.DEBUG_SESSION_STOP]: { - /** - * Trigger for starting the debugger. - * - `launch`: Launch/start new code and debug it. - * - `attach`: Attach to an exiting python process (remote debugging). - * - `test`: Debugging python tests. - * - * @type {TriggerType} - */ - trigger: TriggerType; - /** - * Type of console used. - * -`internalConsole`: Use VS Code debug console (no shells/terminals). - * - `integratedTerminal`: Use VS Code terminal. - * - `externalTerminal`: Use an External terminal. - * - * @type {ConsoleType} - */ - console?: ConsoleType; - }; - /** - * Telemetry captured when user code starts running after loading the debugger. - */ - [EventName.DEBUG_SESSION_USER_CODE_RUNNING]: { - /** - * Trigger for starting the debugger. - * - `launch`: Launch/start new code and debug it. - * - `attach`: Attach to an exiting python process (remote debugging). - * - `test`: Debugging python tests. - * - * @type {TriggerType} - */ - trigger: TriggerType; - /** - * Type of console used. - * -`internalConsole`: Use VS Code debug console (no shells/terminals). - * - `integratedTerminal`: Use VS Code terminal. - * - `externalTerminal`: Use an External terminal. - * - * @type {ConsoleType} - */ - console?: ConsoleType; - }; - /** - * Telemetry captured when starting the debugger. - */ - [EventName.DEBUGGER]: { - /** - * Trigger for starting the debugger. - * - `launch`: Launch/start new code and debug it. - * - `attach`: Attach to an exiting python process (remote debugging). - * - `test`: Debugging python tests. - * - * @type {TriggerType} - */ - trigger: TriggerType; - /** - * Type of console used. - * -`internalConsole`: Use VS Code debug console (no shells/terminals). - * - `integratedTerminal`: Use VS Code terminal. - * - `externalTerminal`: Use an External terminal. - * - * @type {ConsoleType} - */ - console?: ConsoleType; - /** - * Whether user has defined environment variables. - * Could have been defined in launch.json or the env file (defined in `settings.json`). - * Default `env file` is `.env` in the workspace folder. - * - * @type {boolean} - */ - hasEnvVars: boolean; - /** - * Whether there are any CLI arguments that need to be passed into the program being debugged. - * - * @type {boolean} - */ - hasArgs: boolean; - /** - * Whether the user is debugging `django`. - * - * @type {boolean} - */ - django: boolean; - /** - * Whether the user is debugging `flask`. - * - * @type {boolean} - */ - flask: boolean; - /** - * Whether the user is debugging `jinja` templates. - * - * @type {boolean} - */ - jinja: boolean; - /** - * Whether user is attaching to a local python program (attach scenario). - * - * @type {boolean} - */ - isLocalhost: boolean; - /** - * Whether debugging a module. - * - * @type {boolean} - */ - isModule: boolean; - /** - * Whether debugging with `sudo`. - * - * @type {boolean} - */ - isSudo: boolean; - /** - * Whether required to stop upon entry. - * - * @type {boolean} - */ - stopOnEntry: boolean; - /** - * Whether required to display return types in debugger. - * - * @type {boolean} - */ - showReturnValue: boolean; - /** - * Whether debugging `pyramid`. - * - * @type {boolean} - */ - pyramid: boolean; - /** - * Whether debugging a subprocess. - * - * @type {boolean} - */ - subProcess: boolean; - /** - * Whether debugging `watson`. - * - * @type {boolean} - */ - watson: boolean; - /** - * Whether degbugging `pyspark`. - * - * @type {boolean} - */ - pyspark: boolean; - /** - * Whether using `gevent` when debugging. - * - * @type {boolean} - */ - gevent: boolean; - /** - * Whether debugging `scrapy`. - * - * @type {boolean} - */ - scrapy: boolean; - }; - /** - * Telemetry event sent when attaching to child process - */ - [EventName.DEBUGGER_ATTACH_TO_CHILD_PROCESS]: never | undefined; - /** - * Telemetry event sent when attaching to a local process. - */ - [EventName.DEBUGGER_ATTACH_TO_LOCAL_PROCESS]: never | undefined; - /** - * Telemetry sent after building configuration for debugger - */ - [EventName.DEBUGGER_CONFIGURATION_PROMPTS]: { - /** - * The type of debug configuration to build configuration for - * - * @type {DebugConfigurationType} - */ - configurationType: DebugConfigurationType; - /** - * Carries `true` if we are able to auto-detect manage.py path for Django, `false` otherwise - * - * @type {boolean} - */ - autoDetectedDjangoManagePyPath?: boolean; - /** - * Carries `true` if we are able to auto-detect .ini file path for Pyramid, `false` otherwise - * - * @type {boolean} - */ - autoDetectedPyramidIniPath?: boolean; - /** - * Carries `true` if we are able to auto-detect app.py path for Flask, `false` otherwise - * - * @type {boolean} - */ - autoDetectedFlaskAppPyPath?: boolean; - /** - * Carries `true` if user manually entered the required path for the app - * (path to `manage.py` for Django, path to `.ini` for Pyramid, path to `app.py` for Flask), `false` otherwise - * - * @type {boolean} - */ - manuallyEnteredAValue?: boolean; - }; - /** - * Telemetry event sent when providing completion provider in launch.json. It is sent just *after* inserting the completion. - */ - [EventName.DEBUGGER_CONFIGURATION_PROMPTS_IN_LAUNCH_JSON]: never | undefined; - /** - * Telemetry is sent when providing definitions for python code, particularly when [go to definition](https://code.visualstudio.com/docs/editor/editingevolved#_go-to-definition) - * and peek definition features are used. - */ - [EventName.DEFINITION]: never | undefined; - /** - * Telemetry event sent with details of actions when invoking a diagnostic command - */ [EventName.DIAGNOSTICS_ACTION]: { /** * Diagnostics command executed. @@ -624,6 +324,11 @@ export interface IEventNamePropertyMapping { /** * Telemetry event sent when we are checking if we can handle the diagnostic code */ + /* __GDPR__ + "diagnostics.message" : { + "code" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } + */ [EventName.DIAGNOSTICS_MESSAGE]: { /** * Code of diagnostics message detected and displayed. @@ -634,19 +339,44 @@ export interface IEventNamePropertyMapping { /** * Telemetry event sent with details just after editor loads */ + /* __GDPR__ + "editor.load" : { + "appName" : {"classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "luabud"}, + "codeloadingtime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "luabud" }, + "condaversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "luabud" }, + "errorname" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth", "owner": "luabud" }, + "errorstack" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth", "owner": "luabud" }, + "pythonversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "luabud" }, + "installsource" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "luabud" }, + "interpretertype" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "luabud" }, + "terminal" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "luabud" }, + "workspacefoldercount" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "luabud" }, + "haspythonthree" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "luabud" }, + "startactivatetime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "luabud" }, + "totalactivatetime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "luabud" }, + "totalnonblockingactivatetime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "luabud" }, + "usinguserdefinedinterpreter" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "luabud" }, + "usingglobalinterpreter" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "luabud" }, + "isfirstsession" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "luabud" } + } + */ [EventName.EDITOR_LOAD]: { + /** + * The name of the application where the Python extension is running + */ + appName?: string | undefined; /** * The conda version if selected */ - condaVersion: string | undefined; + condaVersion?: string | undefined; /** * The python interpreter version if selected */ - pythonVersion: string | undefined; + pythonVersion?: string | undefined; /** * The type of interpreter (conda, virtualenv, pipenv etc.) */ - interpreterType: EnvironmentType | undefined; + interpreterType?: EnvironmentType | undefined; /** * The type of terminal shell created: powershell, cmd, zsh, bash etc. * @@ -660,27 +390,41 @@ export interface IEventNamePropertyMapping { /** * If interpreters found for the main workspace contains a python3 interpreter */ - hasPython3: boolean; + hasPythonThree?: boolean; /** * If user has defined an interpreter in settings.json */ - usingUserDefinedInterpreter: boolean; + usingUserDefinedInterpreter?: boolean; /** - * If interpreter is auto selected for the workspace + * If global interpreter is being used */ - usingAutoSelectedWorkspaceInterpreter: boolean; + usingGlobalInterpreter?: boolean; /** - * If global interpreter is being used + * Carries `true` if it is the very first session of the user. We check whether persistent cache is empty + * to approximately guess if it's the first session. + */ + isFirstSession?: boolean; + /** + * If user has enabled the Python Environments extension integration */ - usingGlobalInterpreter: boolean; + usingEnvironmentsExtension?: boolean; }; /** * Telemetry event sent when substituting Environment variables to calculate value of variables */ + /* __GDPR__ + "envfile_variable_substitution" : { "owner": "karthiknadig" } + */ [EventName.ENVFILE_VARIABLE_SUBSTITUTION]: never | undefined; /** * Telemetry event sent when an environment file is detected in the workspace. */ + /* __GDPR__ + "envfile_workspace" : { + "hascustomenvpath" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "luabud" } + } + */ + [EventName.ENVFILE_WORKSPACE]: { /** * If there's a custom path specified in the python.envFile workspace settings. @@ -691,19 +435,31 @@ export interface IEventNamePropertyMapping { * Telemetry Event sent when user sends code to be executed in the terminal. * */ + /* __GDPR__ + "execution_code" : { + "scope" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }, + "trigger" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } + */ [EventName.EXECUTION_CODE]: { /** - * Whether the user executed a file in the terminal or just the selected text. + * Whether the user executed a file in the terminal or just the selected text or line by shift+enter. * * @type {('file' | 'selection')} */ - scope: 'file' | 'selection'; + scope: 'file' | 'selection' | 'line'; /** * How was the code executed (through the command or by clicking the `Run File` icon). * * @type {('command' | 'icon')} */ trigger?: 'command' | 'icon'; + /** + * Whether user chose to execute this Python file in a separate terminal or not. + * + * @type {boolean} + */ + newTerminalPerFile?: boolean; }; /** * Telemetry Event sent when user executes code against Django Shell. @@ -711,6 +467,11 @@ export interface IEventNamePropertyMapping { * scope * */ + /* __GDPR__ + "execution_django" : { + "scope" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } + */ [EventName.EXECUTION_DJANGO]: { /** * If `file`, then the file was executed in the django shell. @@ -720,26 +481,15 @@ export interface IEventNamePropertyMapping { */ scope: 'file' | 'selection'; }; - /** - * Telemetry event sent with details when formatting a document - */ - [EventName.FORMAT]: { - /** - * Tool being used to format - */ - tool: 'autopep8' | 'black' | 'yapf'; - /** - * If arguments for formatter is provided in resource settings - */ - hasCustomArgs: boolean; - /** - * Carries `true` when formatting a selection of text, `false` otherwise - */ - formatSelection: boolean; - }; + /** * Telemetry event sent with the value of setting 'Format on type' */ + /* __GDPR__ + "format.format_on_type" : { + "enabled" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } + */ [EventName.FORMAT_ON_TYPE]: { /** * Carries `true` if format on type is enabled, `false` otherwise @@ -748,21 +498,15 @@ export interface IEventNamePropertyMapping { */ enabled: boolean; }; - /** - * Telemetry event sent when sorting imports using formatter - */ - [EventName.FORMAT_SORT_IMPORTS]: never | undefined; - /** - * Telemetry event sent when Go to Python object command is executed - */ - [EventName.GO_TO_OBJECT_DEFINITION]: never | undefined; - /** - * Telemetry event sent when providing a hover for the given position and document for interactive window using Jedi. - */ - [EventName.HOVER_DEFINITION]: never | undefined; + /** * Telemetry event sent with details when tracking imports */ + /* __GDPR__ + "hashed_package_name" : { + "hashedname" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "luabud" } + } + */ [EventName.HASHED_PACKAGE_NAME]: { /** * Hash of the package name @@ -771,127 +515,80 @@ export interface IEventNamePropertyMapping { */ hashedName: string; }; - [Telemetry.HashedCellOutputMimeTypePerf]: never | undefined; - [Telemetry.HashedNotebookCellOutputMimeTypePerf]: never | undefined; - [Telemetry.HashedCellOutputMimeType]: { - /** - * Hash of the cell output mimetype - * - * @type {string} - */ - hashedName: string; - hasText: boolean; - hasLatex: boolean; - hasHtml: boolean; - hasSvg: boolean; - hasXml: boolean; - hasJson: boolean; - hasImage: boolean; - hasGeo: boolean; - hasPlotly: boolean; - hasVega: boolean; - hasWidget: boolean; - hasJupyter: boolean; - hasVnd: boolean; - }; - [EventName.HASHED_PACKAGE_PERF]: never | undefined; - /** - * Telemetry event sent with details of selection in prompt - * `Prompt message` :- 'Linter ${productName} is not installed' - */ - [EventName.LINTER_NOT_INSTALLED_PROMPT]: { - /** - * Name of the linter - * - * @type {LinterId} - */ - tool?: LinterId; - /** - * `select` When 'Select linter' option is selected - * `disablePrompt` When 'Do not show again' option is selected - * `install` When 'Install' option is selected - * - * @type {('select' | 'disablePrompt' | 'install')} - */ - action: 'select' | 'disablePrompt' | 'install'; - }; + /** * Telemetry event sent when installing modules */ + /* __GDPR__ + "python_install_package" : { + "installer" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }, + "requiredinstaller" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }, + "productname" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }, + "isinstalled" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }, + "envtype" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }, + "version" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } + */ [EventName.PYTHON_INSTALL_PACKAGE]: { /** * The name of the module. (pipenv, Conda etc.) - * - * @type {string} + * One of the possible values includes `unavailable`, meaning user doesn't have pip, conda, or other tools available that can be used to install a python package. */ installer: string; - }; - /** - * Telemetry sent with details immediately after linting a document completes - */ - [EventName.LINTING]: { /** - * Name of the linter being used - * - * @type {LinterId} + * The name of the installer required (expected to be available) for installation of packages. (pipenv, Conda etc.) */ - tool: LinterId; + requiredInstaller?: string; /** - * If custom arguments for linter is provided in settings.json - * - * @type {boolean} + * Name of the corresponding product (package) to be installed. */ - hasCustomArgs: boolean; + productName?: string; /** - * Carries the source which triggered configuration of tests - * - * @type {LinterTrigger} + * Whether the product (package) has been installed or not. */ - trigger: LinterTrigger; + isInstalled?: boolean; /** - * Carries `true` if linter executable is specified, `false` otherwise - * - * @type {boolean} + * Type of the Python environment into which the Python package is being installed. + */ + envType?: PythonEnvironment['envType']; + /** + * Version of the Python environment into which the Python package is being installed. */ - executableSpecified: boolean; + version?: string; }; /** - * Telemetry event sent after fetching the OS version + * Telemetry event sent when an environment without contain a python binary is selected. */ - [EventName.PLATFORM_INFO]: { - /** - * If fetching OS version fails, list the failure type - * - * @type {PlatformErrors} - */ - failureType?: PlatformErrors; - /** - * The OS version of the platform - * - * @type {string} - */ - osVersion?: string; - }; - /** - * Telemetry is sent with details about the play run file icon + /* __GDPR__ + "environment_without_python_selected" : { + "duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "karthiknadig" } + } */ - [EventName.PLAY_BUTTON_ICON_DISABLED]: { - /** - * Carries `true` if play button icon is not shown (because code runner is installed), `false` otherwise - */ - disabled: boolean; - }; + [EventName.ENVIRONMENT_WITHOUT_PYTHON_SELECTED]: never | undefined; /** * Telemetry event sent when 'Select Interpreter' command is invoked. */ + /* __GDPR__ + "select_interpreter" : { + "duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + } + */ [EventName.SELECT_INTERPRETER]: never | undefined; /** * Telemetry event sent when 'Enter interpreter path' button is clicked. */ + /* __GDPR__ + "select_interpreter_enter_button" : { "owner": "karthiknadig" } + */ [EventName.SELECT_INTERPRETER_ENTER_BUTTON]: never | undefined; /** * Telemetry event sent with details about what choice user made to input the interpreter path. */ + /* __GDPR__ + "select_interpreter_enter_choice" : { + "choice" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } + */ [EventName.SELECT_INTERPRETER_ENTER_CHOICE]: { /** * Carries 'enter' if user chose to enter the path to executable. @@ -899,9 +596,76 @@ export interface IEventNamePropertyMapping { */ choice: 'enter' | 'browse'; }; + /** + * Telemetry event sent after an action has been taken while the interpreter quickpick was displayed, + * and if the action was not 'Enter interpreter path'. + */ + /* __GDPR__ + "select_interpreter_selected" : { + "action" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } + */ + [EventName.SELECT_INTERPRETER_SELECTED]: { + /** + * 'escape' if the quickpick was dismissed. + * 'selected' if an interpreter was selected. + */ + action: 'escape' | 'selected'; + }; + /** + * Telemetry event sent when the user select to either enter or find the interpreter from the quickpick. + */ + /* __GDPR__ + "select_interpreter_enter_or_find" : { "owner": "karthiknadig" } + */ + + [EventName.SELECT_INTERPRETER_ENTER_OR_FIND]: never | undefined; + /** + * Telemetry event sent after the user entered an interpreter path, or found it by browsing the filesystem. + */ + /* __GDPR__ + "select_interpreter_entered_exists" : { + "discovered" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "karthiknadig" } + } + */ + [EventName.SELECT_INTERPRETER_ENTERED_EXISTS]: { + /** + * Carries `true` if the interpreter that was selected had already been discovered earlier (exists in the cache). + */ + discovered: boolean; + }; + + /** + * Telemetry event sent when another extension calls into python extension's environment API. Contains details + * of the other extension. + */ + /* __GDPR__ + "python_environments_api" : { + "extensionId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": false , "owner": "karthiknadig"}, + "apiName" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": false, "owner": "karthiknadig" } + } + */ + [EventName.PYTHON_ENVIRONMENTS_API]: { + /** + * The ID of the extension calling the API. + */ + extensionId: string; + /** + * The name of the API called. + */ + apiName: string; + }; /** * Telemetry event sent with details after updating the python interpreter */ + /* __GDPR__ + "python_interpreter" : { + "duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }, + "trigger" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "karthiknadig" }, + "pythonversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } + */ [EventName.PYTHON_INTERPRETER]: { /** * Carries the source which triggered the update @@ -921,19 +685,13 @@ export interface IEventNamePropertyMapping { * @type {string} */ pythonVersion?: string; - /** - * The version of pip module installed in the python interpreter - * - * @type {string} - */ - pipVersion?: string; - /** - * The bit-ness of the python interpreter represented using architecture. - * - * @type {Architecture} - */ - architecture?: Architecture; }; + /* __GDPR__ + "python_interpreter.activation_environment_variables" : { + "hasenvvars" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "karthiknadig" } + } + */ [EventName.PYTHON_INTERPRETER_ACTIVATION_ENVIRONMENT_VARIABLES]: { /** * Carries `true` if environment variables are present, `false` otherwise @@ -947,23 +705,19 @@ export interface IEventNamePropertyMapping { * @type {boolean} */ failed?: boolean; - /** - * Whether the environment was activated within a terminal or not. - * - * @type {boolean} - */ - activatedInTerminal?: boolean; - /** - * Whether the environment was activated by the wrapper class. - * If `true`, this telemetry is sent by the class that wraps the two activation providers . - * - * @type {boolean} - */ - activatedByWrapper?: boolean; }; /** * Telemetry event sent when getting activation commands for active interpreter */ + /* __GDPR__ + "python_interpreter_activation_for_running_code" : { + "hascommands" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "karthiknadig" }, + "terminal" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }, + "pythonversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }, + "interpretertype" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } + */ [EventName.PYTHON_INTERPRETER_ACTIVATION_FOR_RUNNING_CODE]: { /** * Carries `true` if activation commands exists for interpreter, `false` otherwise @@ -999,6 +753,15 @@ export interface IEventNamePropertyMapping { /** * Telemetry event sent when getting activation commands for terminal when interpreter is not specified */ + /* __GDPR__ + "python_interpreter_activation_for_terminal" : { + "hascommands" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "karthiknadig" }, + "terminal" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }, + "pythonversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }, + "interpretertype" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } + */ [EventName.PYTHON_INTERPRETER_ACTIVATION_FOR_TERMINAL]: { /** * Carries `true` if activation commands exists for terminal, `false` otherwise @@ -1031,428 +794,1264 @@ export interface IEventNamePropertyMapping { */ interpreterType: EnvironmentType; }; + /** + * Telemetry event sent when auto-selection is called. + */ + /* __GDPR__ + "python_interpreter_auto_selection" : { + "usecachedinterpreter" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } + */ + [EventName.PYTHON_INTERPRETER_AUTO_SELECTION]: { /** - * The rule used to auto-select the interpreter + * If auto-selection has been run earlier in this session, and this call returned a cached value. * - * @type {AutoSelectionRule} + * @type {boolean} */ - rule?: AutoSelectionRule; + useCachedInterpreter?: boolean; + }; + /** + * Telemetry event sent when discovery of all python environments (virtualenv, conda, pipenv etc.) finishes. + */ + /* __GDPR__ + "python_interpreter_discovery" : { + "telVer" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "workspaceFolderCount" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "nativeDuration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "condaInfoEnvsInvalid" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "condaInfoEnvsDuplicate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "condaInfoEnvsInvalidPrefix" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "interpreters" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true , "owner": "donjayamanne"}, + "envsWithDuplicatePrefixes" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true , "owner": "donjayamanne"}, + "envsNotFound" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true , "owner": "donjayamanne"}, + "condaInfoEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true , "owner": "donjayamanne"}, + "condaInfoEnvsDirs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true , "owner": "donjayamanne"}, + "nativeCondaInfoEnvsDirs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true , "owner": "donjayamanne"}, + "condaRcs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true , "owner": "donjayamanne"}, + "nativeCondaRcs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true , "owner": "donjayamanne"}, + "condaEnvsInEnvDir" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true , "owner": "donjayamanne"}, + "condaEnvsInTxt" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true , "owner": "donjayamanne"}, + "nativeCondaEnvsInEnvDir" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true , "owner": "donjayamanne"}, + "invalidCondaEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true , "owner": "donjayamanne"}, + "prefixNotExistsCondaEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true , "owner": "donjayamanne"}, + "condaEnvsWithoutPrefix" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true , "owner": "donjayamanne"}, + "environmentsWithoutPython" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "usingNativeLocator" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "canSpawnConda" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "nativeCanSpawnConda" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne"}, + "userProvidedEnvFound" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaRootPrefixFoundInInfoNotInNative" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaDefaultPrefixFoundAsAnotherKind" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaRootPrefixFoundAsPrefixOfAnother" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaDefaultPrefixFoundAsPrefixOfAnother" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaRootPrefixFoundInTxt" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaDefaultPrefixFoundInTxt" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaRootPrefixFoundInInfoAfterFind" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaRootPrefixFoundInInfoAfterFindKind" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaRootPrefixFoundAsAnotherKind" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaRootPrefixInCondaExePath" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaDefaultPrefixFoundInInfoNotInNative" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaDefaultPrefixFoundInInfoAfterFind" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaDefaultPrefixFoundInInfoAfterFindKind" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaDefaultPrefixInCondaExePath" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "userProvidedCondaExe" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaRootPrefixEnvsAfterFind" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "condaDefaultPrefixEnvsAfterFind" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "activeStateEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "condaEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "customEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "hatchEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "microsoftStoreEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "otherGlobalEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "otherVirtualEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "pipEnvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "poetryEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "pyenvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "systemEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "unknownEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "venvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "virtualEnvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "virtualEnvWrapperEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "global" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "nativeEnvironmentsWithoutPython" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "nativeCondaEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "nativeCustomEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "nativeMicrosoftStoreEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "nativeOtherGlobalEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "nativeOtherVirtualEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "nativePipEnvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "nativePoetryEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "nativePyenvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "nativeSystemEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "nativeUnknownEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "nativeVenvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "nativeVirtualEnvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "nativeVirtualEnvWrapperEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "nativeGlobal" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "missingNativeCondaEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "missingNativeCustomEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "missingNativeMicrosoftStoreEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "missingNativeGlobalEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "missingNativeOtherVirtualEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "missingNativePipEnvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "missingNativePoetryEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "missingNativePyenvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "missingNativeSystemEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "missingNativeUnknownEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "missingNativeVenvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "missingNativeVirtualEnvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "missingNativeVirtualEnvWrapperEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "missingNativeOtherGlobalEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "nativeCondaRcsNotFound" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "nativeCondaEnvDirsNotFound" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "nativeCondaEnvDirsNotFoundHasEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "nativeCondaEnvDirsNotFoundHasEnvsInTxt" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "nativeCondaEnvTxtSame" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "nativeCondaEnvsFromTxt" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "nativeCondaEnvTxtExists" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" } + } + */ + [EventName.PYTHON_INTERPRETER_DISCOVERY]: { /** - * If cached interpreter no longer exists or is invalid - * - * @type {boolean} + * Version of this telemetry. */ - interpreterMissing?: boolean; + telVer?: number; /** - * Carries `true` if next rule is identified for autoselecting interpreter - * - * @type {boolean} + * Number of invalid envs returned by `conda info` */ - identified?: boolean; + condaInfoEnvsInvalid?: number; /** - * Carries `true` if cached interpreter is updated to use the current interpreter, `false` otherwise - * - * @type {boolean} + * Number of conda envs found in the environments.txt file. */ - updated?: boolean; - }; - /** - * Sends information regarding discovered python environments (virtualenv, conda, pipenv etc.) - */ - [EventName.PYTHON_INTERPRETER_DISCOVERY]: { + condaEnvsInTxt?: number; /** - * Name of the locator + * Number of duplicate envs returned by `conda info` */ - locator: string; + condaInfoEnvsDuplicate?: number; /** - * The number of the interpreters returned by locator + * Number of envs with invalid prefix returned by `conda info` + */ + condaInfoEnvsInvalidPrefix?: number; + /** + * Number of workspaces. + */ + workspaceFolderCount?: number; + /** + * Time taken to discover using native locator. + */ + nativeDuration?: number; + /** + * The number of the interpreters discovered */ interpreters?: number; + /** + * The number of the interpreters with duplicate prefixes + */ + envsWithDuplicatePrefixes?: number; + /** + * The number of the interpreters returned by `conda info` + */ + condaInfoEnvs?: number; + /** + * The number of the envs_dirs returned by `conda info` + */ + condaInfoEnvsDirs?: number; + /** + * The number of the envs_dirs returned by native locator. + */ + nativeCondaInfoEnvsDirs?: number; + /** + * The number of the conda rc files found using conda info + */ + condaRcs?: number; + /** + * The number of the conda rc files found using native locator. + */ + nativeCondaRcs?: number; + /** + * The number of the conda rc files returned by `conda info` that weren't found by native locator. + */ + nativeCondaRcsNotFound?: number; + /** + * The number of the conda env_dirs returned by `conda info` that weren't found by native locator. + */ + nativeCondaEnvDirsNotFound?: number; + /** + * The number of envs in the env_dirs contained in the count for `nativeCondaEnvDirsNotFound` + */ + nativeCondaEnvDirsNotFoundHasEnvs?: number; + /** + * The number of envs from environments.txt that are in the env_dirs contained in the count for `nativeCondaEnvDirsNotFound` + */ + nativeCondaEnvDirsNotFoundHasEnvsInTxt?: number; + /** + * The number of conda interpreters that are in the one of the global conda env locations. + * Global conda envs locations are returned by `conda info` in the `envs_dirs` setting. + */ + condaEnvsInEnvDir?: number; + /** + * The number of native conda interpreters that are in the one of the global conda env locations. + * Global conda envs locations are returned by `conda info` in the `envs_dirs` setting. + */ + nativeCondaEnvsInEnvDir?: number; + condaRootPrefixEnvsAfterFind?: number; + condaDefaultPrefixEnvsAfterFind?: number; + /** + * A conda env found that matches the root_prefix returned by `conda info` + * However a corresponding conda env not found by native locator. + */ + condaDefaultPrefixFoundInInfoAfterFind?: boolean; + condaRootPrefixFoundInTxt?: boolean; + condaDefaultPrefixFoundInTxt?: boolean; + condaDefaultPrefixFoundInInfoAfterFindKind?: string; + condaRootPrefixFoundAsAnotherKind?: string; + condaRootPrefixFoundAsPrefixOfAnother?: string; + condaDefaultPrefixFoundAsAnotherKind?: string; + condaDefaultPrefixFoundAsPrefixOfAnother?: string; + /** + * Whether we were able to identify the conda root prefix in the conda exe path as a conda env using `find` in native finder API. + */ + condaRootPrefixFoundInInfoAfterFind?: boolean; + /** + * Type of python env detected for the conda root prefix. + */ + condaRootPrefixFoundInInfoAfterFindKind?: string; + /** + * The conda root prefix is found in the conda exe path. + */ + condaRootPrefixInCondaExePath?: boolean; + /** + * A conda env found that matches the root_prefix returned by `conda info` + * However a corresponding conda env not found by native locator. + */ + condaDefaultPrefixFoundInInfoNotInNative?: boolean; + /** + * The conda root prefix is found in the conda exe path. + */ + condaDefaultPrefixInCondaExePath?: boolean; + /** + * User provided a path to the conda exe + */ + userProvidedCondaExe?: boolean; + /** + * The number of conda interpreters without the `conda-meta` directory. + */ + invalidCondaEnvs?: number; + /** + * The number of conda interpreters that have prefix that doesn't exist on disc. + */ + prefixNotExistsCondaEnvs?: number; + /** + * The number of conda interpreters without the prefix. + */ + condaEnvsWithoutPrefix?: number; + /** + * Conda exe can be spawned. + */ + canSpawnConda?: boolean; + /** + * Conda exe can be spawned by native locator. + */ + nativeCanSpawnConda?: boolean; + /** + * Conda env belonging to the conda exe provided by the user is found by native locator. + * I.e. even if the user didn't provide the path to the conda exe, the conda env is found by native locator. + */ + userProvidedEnvFound?: boolean; + /** + * The number of the interpreters not found in disc. + */ + envsNotFount?: number; + /** + * Whether or not we're using the native locator. + */ + usingNativeLocator?: boolean; + /** + * The number of environments discovered not containing an interpreter + */ + environmentsWithoutPython?: number; + /** + * Number of environments of a specific type + */ + activeStateEnvs?: number; + /** + * Number of environments of a specific type + */ + condaEnvs?: number; + /** + * Number of environments of a specific type + */ + customEnvs?: number; + /** + * Number of environments of a specific type + */ + hatchEnvs?: number; + /** + * Number of environments of a specific type + */ + microsoftStoreEnvs?: number; + /** + * Number of environments of a specific type + */ + otherGlobalEnvs?: number; + /** + * Number of environments of a specific type + */ + otherVirtualEnvs?: number; + /** + * Number of environments of a specific type + */ + pipEnvEnvs?: number; + /** + * Number of environments of a specific type + */ + poetryEnvs?: number; + /** + * Number of environments of a specific type + */ + pyenvEnvs?: number; + /** + * Number of environments of a specific type + */ + systemEnvs?: number; + /** + * Number of environments of a specific type + */ + unknownEnvs?: number; + /** + * Number of environments of a specific type + */ + venvEnvs?: number; + /** + * Number of environments of a specific type + */ + virtualEnvEnvs?: number; + /** + * Number of environments of a specific type + */ + virtualEnvWrapperEnvs?: number; + /** + * Number of all known Globals (System, Custom, GlobalCustom, etc) + */ + global?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativeEnvironmentsWithoutPython?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativeCondaEnvs?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativeCustomEnvs?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativeMicrosoftStoreEnvs?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativeOtherGlobalEnvs?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativeOtherVirtualEnvs?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativePipEnvEnvs?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativePoetryEnvs?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativePyenvEnvs?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativeSystemEnvs?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativeUnknownEnvs?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativeVenvEnvs?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativeVirtualEnvEnvs?: number; + /** + * Number of environments of a specific type found by native finder + */ + nativeVirtualEnvWrapperEnvs?: number; + /** + * Number of all known Globals (System, Custom, GlobalCustom, etc) + */ + nativeGlobal?: number; + /** + * Number of environments of a specific type missing in Native Locator (compared to the Stable Locator). + */ + missingNativeCondaEnvs?: number; + /** + * Whether the env txt found by native locator is the same as that found by pythonn ext. + */ + nativeCondaEnvTxtSame?: boolean; + /** + * Number of environments found from env txt by native locator. + */ + nativeCondaEnvsFromTxt?: number; + /** + * Whether the env txt found by native locator exists. + */ + nativeCondaEnvTxtExists?: boolean; + /** + * Number of environments of a specific type missing in Native Locator (compared to the Stable Locator). + */ + missingNativeCustomEnvs?: number; + /** + * Number of environments of a specific type missing in Native Locator (compared to the Stable Locator). + */ + missingNativeMicrosoftStoreEnvs?: number; + /** + * Number of environments of a specific type missing in Native Locator (compared to the Stable Locator). + */ + missingNativeGlobalEnvs?: number; + /** + * Number of environments of a specific type missing in Native Locator (compared to the Stable Locator). + */ + missingNativeOtherVirtualEnvs?: number; + /** + * Number of environments of a specific type missing in Native Locator (compared to the Stable Locator). + */ + missingNativePipEnvEnvs?: number; + /** + * Number of environments of a specific type missing in Native Locator (compared to the Stable Locator). + */ + missingNativePoetryEnvs?: number; + /** + * Number of environments of a specific type missing in Native Locator (compared to the Stable Locator). + */ + missingNativePyenvEnvs?: number; + /** + * Number of environments of a specific type missing in Native Locator (compared to the Stable Locator). + */ + missingNativeSystemEnvs?: number; + /** + * Number of environments of a specific type missing in Native Locator (compared to the Stable Locator). + */ + missingNativeUnknownEnvs?: number; + /** + * Number of environments of a specific type missing in Native Locator (compared to the Stable Locator). + */ + missingNativeVenvEnvs?: number; + /** + * Number of environments of a specific type missing in Native Locator (compared to the Stable Locator). + */ + missingNativeVirtualEnvEnvs?: number; + /** + * Number of environments of a specific type missing in Native Locator (compared to the Stable Locator). + */ + missingNativeVirtualEnvWrapperEnvs?: number; + /** + * Number of environments of a specific type missing in Native Locator (compared to the Stable Locator). + */ + missingNativeOtherGlobalEnvs?: number; }; /** - * Telemetry event sent when pipenv interpreter discovery is executed. + * Telemetry event sent when Native finder fails to find some conda envs. */ - [EventName.PIPENV_INTERPRETER_DISCOVERY]: never | undefined; - /** - * Telemetry event sent with details when user clicks the prompt with the following message - * `Prompt message` :- 'We noticed you're using a conda environment. If you are experiencing issues with this environment in the integrated terminal, we suggest the "terminal.integrated.inheritEnv" setting to be changed to false. Would you like to update this setting?' + /* __GDPR__ + "native_finder_missing_conda_envs" : { + "missing" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "envDirsNotFound" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "userProvidedCondaExe" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "rootPrefixNotFound" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaPrefixNotFound" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "condaManagerNotFound" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "missingEnvDirsFromSysRc" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "missingEnvDirsFromUserRc" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "missingEnvDirsFromOtherRc" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "missingFromSysRcEnvDirs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "missingFromUserRcEnvDirs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "missingFromOtherRcEnvDirs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" } + } */ - [EventName.CONDA_INHERIT_ENV_PROMPT]: { + [EventName.NATIVE_FINDER_MISSING_CONDA_ENVS]: { /** - * `Yes` When 'Yes' option is selected - * `No` When 'No' option is selected - * `More info` When 'More Info' option is selected + * Number of missing conda environments. */ - selection: 'Yes' | 'No' | 'More Info' | undefined; - }; - /** - * Telemetry event sent with details when user clicks the prompt with the following message - * `Prompt message` :- 'We found a Python environment in this workspace. Do you want to select it to start up the features in the Python extension? Only accept if you trust this environment.' - */ - [EventName.UNSAFE_INTERPRETER_PROMPT]: { + missing: number; /** - * `Yes` When 'Yes' option is selected - * `No` When 'No' option is selected - * `Learn more` When 'More Info' option is selected - * `Do not show again` When 'Do not show again' option is selected + * Total number of env_dirs not found even after parsing the conda_rc files. + * This will tell us that we are either unable to parse some of the conda_rc files or there are other + * env_dirs that we are not able to find. */ - selection: 'Yes' | 'No' | 'Learn more' | 'Do not show again' | undefined; - }; - /** - * Telemetry event sent with details when user clicks a button in the virtual environment prompt. - * `Prompt message` :- 'We noticed a new virtual environment has been created. Do you want to select it for the workspace folder?' - */ - [EventName.PYTHON_INTERPRETER_ACTIVATE_ENVIRONMENT_PROMPT]: { + envDirsNotFound?: number; /** - * `Yes` When 'Yes' option is selected - * `No` When 'No' option is selected - * `Ignore` When 'Do not show again' option is clicked - * - * @type {('Yes' | 'No' | 'Ignore' | undefined)} + * Whether a conda exe was provided by the user. */ - selection: 'Yes' | 'No' | 'Ignore' | undefined; - }; - /** - * Telemetry event sent with details when the user clicks a button in the "Python is not installed" prompt. - * * `Prompt message` :- 'Python is not installed. Please download and install Python before using the extension.' - */ - [EventName.PYTHON_NOT_INSTALLED_PROMPT]: { + userProvidedCondaExe?: boolean; /** - * `Download` When the 'Download' option is clicked - * `Ignore` When the prompt is dismissed - * - * @type {('Download' | 'Ignore' | undefined)} + * Whether the user provided a conda executable. */ - selection: 'Download' | 'Ignore' | undefined; - }; - - /** - * Telemetry event sent with details when user clicks a button in the following prompt - * `Prompt message` :- 'We noticed you are using Visual Studio Code Insiders. Would you like to use the Insiders build of the Python extension?' - */ - [EventName.INSIDERS_PROMPT]: { + rootPrefixNotFound?: boolean; /** - * `Yes, weekly` When user selects to use "weekly" as extension channel in insiders prompt - * `Yes, daily` When user selects to use "daily" as extension channel in insiders prompt - * `No, thanks` When user decides to keep using the same extension channel as before + * Whether the conda prefix returned by conda was not found by us. */ - selection: 'Yes, weekly' | 'Yes, daily' | 'No, thanks' | undefined; - }; - /** - * Telemetry event sent with details when user clicks a button in the 'Reload to install insiders prompt'. - * `Prompt message` :- 'Please reload Visual Studio Code to use the insiders build of the extension' - */ - [EventName.INSIDERS_RELOAD_PROMPT]: { + condaPrefixNotFound?: boolean; /** - * `Reload` When 'Reload' option is clicked - * `undefined` When prompt is closed - * - * @type {('Reload' | undefined)} + * Whether we found a conda manager or not. */ - selection: 'Reload' | undefined; - }; - /** - * Telemetry sent with details about the current selection of language server - */ - [EventName.PYTHON_LANGUAGE_SERVER_CURRENT_SELECTION]: { + condaManagerNotFound?: boolean; /** - * The startup value of the language server setting + * Whether we failed to find the system rc path. */ - lsStartup?: LanguageServerType; + sysRcNotFound?: boolean; /** - * Used to track switch between language servers. Carries the final state after the switch. + * Whether we failed to find the user rc path. */ - switchTo?: LanguageServerType; - }; - /** - * Telemetry event sent with details after attempting to download LS - */ - [EventName.PYTHON_LANGUAGE_SERVER_DOWNLOADED]: { + userRcNotFound?: boolean; /** - * Whether LS downloading succeeds + * Number of config files (excluding sys and user rc) that were not found. */ - success: boolean; + otherRcNotFound?: boolean; /** - * Version of LS downloaded + * Number of conda envs that were not found by us, and the envs belong to env_dirs in the sys config rc. */ - lsVersion?: string; + missingEnvDirsFromSysRc?: number; /** - * Whether download uri starts with `https:` or not + * Number of conda envs that were not found by us, and the envs belong to env_dirs in the user config rc. */ - usedSSL?: boolean; - + missingEnvDirsFromUserRc?: number; + /** + * Number of conda envs that were not found by us, and the envs belong to env_dirs in the other config rc. + */ + missingEnvDirsFromOtherRc?: number; + /** + * Number of conda envs that were not found by us, and the envs belong to env_dirs in the sys config rc. + */ + missingFromSysRcEnvDirs?: number; + /** + * Number of conda envs that were not found by us, and the envs belong to env_dirs in the user config rc. + */ + missingFromUserRcEnvDirs?: number; /** - * Name of LS downloaded + * Number of conda envs that were not found by us, and the envs belong to env_dirs in the other config rc. */ - lsName?: string; + missingFromOtherRcEnvDirs?: number; }; /** - * Telemetry event sent when LS is started for workspace (workspace folder in case of multi-root) + * Telemetry event sent when Native finder fails to find some conda envs. */ - [EventName.PYTHON_LANGUAGE_SERVER_ENABLED]: { - lsVersion?: string; - }; - /** - * Telemetry event sent with details when downloading or extracting LS fails + /* __GDPR__ + "native_finder_missing_poetry_envs" : { + "missing" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "missingInPath" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "userProvidedPoetryExe" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "poetryExeNotFound" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "globalConfigNotFound" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "cacheDirNotFound" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "cacheDirIsDifferent" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "virtualenvsPathNotFound" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "virtualenvsPathIsDifferent" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "inProjectIsDifferent" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" } + } */ - [EventName.PYTHON_LANGUAGE_SERVER_ERROR]: { + [EventName.NATIVE_FINDER_MISSING_POETRY_ENVS]: { + /** + * Number of missing poetry environments. + */ + missing: number; /** - * The error associated with initializing language server + * Total number of missing envs, where the envs are created in the virtualenvs_path directory. */ - error: string; + missingInPath: number; + /** + * Whether a poetry exe was provided by the user. + */ + userProvidedPoetryExe?: boolean; + /** + * Whether poetry exe was not found. + */ + poetryExeNotFound?: boolean; + /** + * Whether poetry config was not found. + */ + globalConfigNotFound?: boolean; + /** + * Whether cache_dir was not found. + */ + cacheDirNotFound?: boolean; + /** + * Whether cache_dir found was different from that returned by poetry exe. + */ + cacheDirIsDifferent?: boolean; + /** + * Whether virtualenvs.path was not found. + */ + virtualenvsPathNotFound?: boolean; + /** + * Whether virtualenvs.path found was different from that returned by poetry exe. + */ + virtualenvsPathIsDifferent?: boolean; + /** + * Whether virtualenvs.in-project found was different from that returned by poetry exe. + */ + inProjectIsDifferent?: boolean; }; /** - * Telemetry event sent with details after attempting to extract LS + * Telemetry containing performance metrics for Native Finder. + */ + /* __GDPR__ + "native_finder_perf" : { + "duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "totalDuration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "breakdownLocators" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "breakdownPath" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "breakdownGlobalVirtualEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "breakdownWorkspaces" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "locatorConda" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "locatorHomebrew" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "locatorLinuxGlobalPython" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "locatorMacCmdLineTools" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "locatorMacPythonOrg" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "locatorMacXCode" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "locatorPipEnv" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "locatorPoetry" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "locatorPixi" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "locatorPyEnv" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "locatorVenv" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "locatorVirtualEnv" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "locatorVirtualEnvWrapper" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "locatorWindowsRegistry" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "locatorWindowsStore" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "timeToSpawn" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "timeToConfigure" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "timeToRefresh" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" } + } */ - [EventName.PYTHON_LANGUAGE_SERVER_EXTRACTED]: { + [EventName.NATIVE_FINDER_PERF]: { /** - * Whether LS extracting succeeds + * Total duration to find envs using native locator. + * This is the time from the perspective of the Native Locator. + * I.e. starting from the time the request to refresh was received until the end of the refresh. */ - success: boolean; + totalDuration: number; /** - * Version of LS extracted + * Time taken by all locators to find the environments. + * I.e. time for Conda + Poetry + Pyenv, etc (note: all of them run in parallel). */ - lsVersion?: string; + breakdownLocators?: number; + /** + * Time taken to find Python environments in the paths found in the PATH env variable. + */ + breakdownPath?: number; + /** + * Time taken to find Python environments in the global virtual env locations. + */ + breakdownGlobalVirtualEnvs?: number; /** - * Whether download uri starts with `https:` or not + * Time taken to find Python environments in the workspaces. */ - usedSSL?: boolean; + breakdownWorkspaces?: number; /** - * Package name of LS extracted + * Time taken to find all global Conda environments. */ - lsName?: string; + locatorConda?: number; + /** + * Time taken to find all Homebrew environments. + */ + locatorHomebrew?: number; + /** + * Time taken to find all global Python environments on Linux. + */ + locatorLinuxGlobalPython?: number; + /** + * Time taken to find all Python environments belonging to Mac Command Line Tools . + */ + locatorMacCmdLineTools?: number; + /** + * Time taken to find all Python environments belonging to Mac Python Org. + */ + locatorMacPythonOrg?: number; + /** + * Time taken to find all Python environments belonging to Mac XCode. + */ + locatorMacXCode?: number; + /** + * Time taken to find all Pipenv environments. + */ + locatorPipEnv?: number; + /** + * Time taken to find all Pixi environments. + */ + locatorPixi?: number; + /** + * Time taken to find all Poetry environments. + */ + locatorPoetry?: number; + /** + * Time taken to find all Pyenv environments. + */ + locatorPyEnv?: number; + /** + * Time taken to find all Venv environments. + */ + locatorVenv?: number; + /** + * Time taken to find all VirtualEnv environments. + */ + locatorVirtualEnv?: number; + /** + * Time taken to find all VirtualEnvWrapper environments. + */ + locatorVirtualEnvWrapper?: number; + /** + * Time taken to find all Windows Registry environments. + */ + locatorWindowsRegistry?: number; + /** + * Time taken to find all Windows Store environments. + */ + locatorWindowsStore?: number; + /** + * Total time taken to spawn the Native Python finder process. + */ + timeToSpawn?: number; + /** + * Total time taken to configure the Native Python finder process. + */ + timeToConfigure?: number; + /** + * Total time taken to refresh the Environments (from perspective of Python extension). + * Time = total time taken to process the `refresh` request. + */ + timeToRefresh?: number; }; /** - * Telemetry event sent if azure blob packages are being listed + * Telemetry event sent when discovery of all python environments using the native locator(virtualenv, conda, pipenv etc.) finishes. */ - [EventName.PYTHON_LANGUAGE_SERVER_LIST_BLOB_STORE_PACKAGES]: never | undefined; - /** - * Tracks if LS is supported on platform or not + /* __GDPR__ + "python_interpreter_discovery_invalid_native" : { + "duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidVersionsCondaEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidVersionsCustomEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidVersionsMicrosoftStoreEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidVersionsGlobalEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidVersionsOtherVirtualEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidVersionsPipEnvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidVersionsPoetryEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidVersionsPyenvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidVersionsSystemEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidVersionsUnknownEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidVersionsVenvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidVersionsVirtualEnvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidVersionsVirtualEnvWrapperEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidVersionsOtherGlobalEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidSysPrefixCondaEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidSysPrefixCustomEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidSysPrefixMicrosoftStoreEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidSysPrefixGlobalEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidSysPrefixOtherVirtualEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidSysPrefixPipEnvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidSysPrefixPoetryEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidSysPrefixPyenvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidSysPrefixSystemEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidSysPrefixUnknownEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidSysPrefixVenvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidSysPrefixVirtualEnvEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidSysPrefixVirtualEnvWrapperEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "invalidSysPrefixOtherGlobalEnvs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" } + } */ - [EventName.PYTHON_LANGUAGE_SERVER_PLATFORM_SUPPORTED]: { + [EventName.PYTHON_INTERPRETER_DISCOVERY_INVALID_NATIVE]: { /** - * Carries `true` if LS is supported, `false` otherwise - * - * @type {boolean} + * Number of Python envs of a particular type that have invalid version from Native Locator. */ - supported: boolean; + invalidVersionsCondaEnvs?: number; /** - * If checking support for LS failed - * - * @type {'UnknownError'} + * Number of Python envs of a particular type that have invalid version from Native Locator. + */ + invalidVersionsCustomEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid version from Native Locator. + */ + invalidVersionsMicrosoftStoreEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid version from Native Locator. + */ + invalidVersionsGlobalEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid version from Native Locator. + */ + invalidVersionsOtherVirtualEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid version from Native Locator. + */ + invalidVersionsPipEnvEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid version from Native Locator. + */ + invalidVersionsPoetryEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid version from Native Locator. + */ + invalidVersionsPyenvEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid version from Native Locator. + */ + invalidVersionsSystemEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid version from Native Locator. + */ + invalidVersionsUnknownEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid version from Native Locator. + */ + invalidVersionsVenvEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid version from Native Locator. + */ + invalidVersionsVirtualEnvEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid version from Native Locator. + */ + invalidVersionsVirtualEnvWrapperEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid version from Native Locator. + */ + invalidVersionsOtherGlobalEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid sys prefix from Native Locator. */ - failureType?: 'UnknownError'; + invalidSysPrefixCondaEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid sys prefix from Native Locator. + */ + invalidSysPrefixCustomEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid sys prefix from Native Locator. + */ + invalidSysPrefixMicrosoftStoreEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid sys prefix from Native Locator. + */ + invalidSysPrefixGlobalEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid sys prefix from Native Locator. + */ + invalidSysPrefixOtherVirtualEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid sys prefix from Native Locator. + */ + invalidSysPrefixPipEnvEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid sys prefix from Native Locator. + */ + invalidSysPrefixPoetryEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid sys prefix from Native Locator. + */ + invalidSysPrefixPyenvEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid sys prefix from Native Locator. + */ + invalidSysPrefixSystemEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid sys prefix from Native Locator. + */ + invalidSysPrefixUnknownEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid sys prefix from Native Locator. + */ + invalidSysPrefixVenvEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid sys prefix from Native Locator. + */ + invalidSysPrefixVirtualEnvEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid sys prefix from Native Locator. + */ + invalidSysPrefixVirtualEnvWrapperEnvs?: number; + /** + * Number of Python envs of a particular type that have invalid sys prefix from Native Locator. + */ + invalidSysPrefixOtherGlobalEnvs?: number; }; /** - * Telemetry event sent when LS is ready to start + * Telemetry event sent with details when user clicks the prompt with the following message: + * + * 'We noticed you're using a conda environment. If you are experiencing issues with this environment in the integrated terminal, we suggest the "terminal.integrated.inheritEnv" setting to be changed to false. Would you like to update this setting?' */ - [EventName.PYTHON_LANGUAGE_SERVER_READY]: { - lsVersion?: string; - }; - /** - * Telemetry event sent when starting LS + /* __GDPR__ + "conda_inherit_env_prompt" : { + "selection" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } */ - [EventName.PYTHON_LANGUAGE_SERVER_STARTUP]: { - lsVersion?: string; + [EventName.CONDA_INHERIT_ENV_PROMPT]: { + /** + * `Yes` When 'Allow' option is selected + * `Close` When 'Close' option is selected + */ + selection: 'Allow' | 'Close' | undefined; }; + /** - * Telemetry sent from language server (details of telemetry sent can be provided by LS team) + * Telemetry event sent with details when user attempts to run in interactive window when Jupyter is not installed. */ - [EventName.PYTHON_LANGUAGE_SERVER_TELEMETRY]: any; - /** - * Telemetry sent when the client makes a request to the language server + /* __GDPR__ + "require_jupyter_prompt" : { + "selection" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } */ - [EventName.PYTHON_LANGUAGE_SERVER_REQUEST]: any; + [EventName.REQUIRE_JUPYTER_PROMPT]: { + /** + * `Yes` When 'Yes' option is selected + * `No` When 'No' option is selected + * `undefined` When 'x' is selected + */ + selection: 'Yes' | 'No' | undefined; + }; /** - * Telemetry event sent with details when inExperiment() API is called + * Telemetry event sent with details when user clicks the prompt with the following message: + * + * 'We noticed VS Code was launched from an activated conda environment, would you like to select it?' + */ + /* __GDPR__ + "activated_conda_env_launch" : { + "selection" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } */ - [EventName.PYTHON_EXPERIMENTS]: { + [EventName.ACTIVATED_CONDA_ENV_LAUNCH]: { /** - * Name of the experiment group the user is in - * @type {string} + * `Yes` When 'Yes' option is selected + * `No` When 'No' option is selected */ - expName?: string; + selection: 'Yes' | 'No' | undefined; }; /** - * Telemetry event sent when Experiments have been disabled. + * Telemetry event sent with details when user clicks a button in the virtual environment prompt. + * `Prompt message` :- 'We noticed a new virtual environment has been created. Do you want to select it for the workspace folder?' */ - [EventName.PYTHON_EXPERIMENTS_DISABLED]: never | undefined; - /** - * Telemetry event sent with details when a user has requested to opt it or out of an experiment group + /* __GDPR__ + "python_interpreter_activate_environment_prompt" : { + "selection" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } */ - [EventName.PYTHON_EXPERIMENTS_OPT_IN_OUT]: { + [EventName.PYTHON_INTERPRETER_ACTIVATE_ENVIRONMENT_PROMPT]: { /** - * Carries the name of the experiment user has been opted into manually + * `Yes` When 'Yes' option is selected + * `No` When 'No' option is selected + * `Ignore` When "Don't show again" option is clicked + * + * @type {('Yes' | 'No' | 'Ignore' | undefined)} */ - expNameOptedInto?: string; + selection: 'Yes' | 'No' | 'Ignore' | undefined; + }; + /** + * Telemetry event sent with details when the user clicks a button in the "Python is not installed" prompt. + * * `Prompt message` :- 'Python is not installed. Please download and install Python before using the extension.' + */ + /* __GDPR__ + "python_not_installed_prompt" : { + "selection" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } + */ + [EventName.PYTHON_NOT_INSTALLED_PROMPT]: { /** - * Carries the name of the experiment user has been opted out of manually + * `Download` When the 'Download' option is clicked + * `Ignore` When the prompt is dismissed + * + * @type {('Download' | 'Ignore' | undefined)} */ - expNameOptedOutOf?: string; + selection: 'Download' | 'Ignore' | undefined; }; /** - * Telemetry event sent with details when doing best effort to download the experiments within timeout and using it in the current session only + * Telemetry event sent when the experiments service is initialized for the first time. + */ + /* __GDPR__ + "python_experiments_init_performance" : { + "duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "luabud" } + } + */ + [EventName.PYTHON_EXPERIMENTS_INIT_PERFORMANCE]: unknown; + /** + * Telemetry event sent when the user use the report issue command. */ - [EventName.PYTHON_EXPERIMENTS_DOWNLOAD_SUCCESS_RATE]: { + /* __GDPR__ + "use_report_issue_command" : { "owner": "paulacamargo25" } + */ + [EventName.USE_REPORT_ISSUE_COMMAND]: unknown; + /** + * Telemetry event sent when the New Python File command is executed. + */ + /* __GDPR__ + "create_new_file_command" : { "owner": "luabud" } + */ + [EventName.CREATE_NEW_FILE_COMMAND]: unknown; + /** + * Telemetry event sent when the installed versions of Python, Jupyter, and Pylance are all capable + * of supporting the LSP notebooks experiment. This does not indicate that the experiment is enabled. + */ + + /* __GDPR__ + "python_experiments_lsp_notebooks" : { "owner": "luabud" } + */ + [EventName.PYTHON_EXPERIMENTS_LSP_NOTEBOOKS]: unknown; + /** + * Telemetry event sent once on session start with details on which experiments are opted into and opted out from. + */ + /* __GDPR__ + "python_experiments_opt_in_opt_out_settings" : { + "optedinto" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "luabud" }, + "optedoutfrom" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "luabud" } + } + */ + [EventName.PYTHON_EXPERIMENTS_OPT_IN_OPT_OUT_SETTINGS]: { /** - * Carries `true` if downloading experiments successfully finishes within timeout, `false` otherwise - * @type {boolean} + * List of valid experiments in the python.experiments.optInto setting + * @type {string} */ - success?: boolean; + optedInto: string; /** - * Carries an error string if downloading experiments fails with error + * List of valid experiments in the python.experiments.optOutFrom setting * @type {string} */ - error?: string; + optedOutFrom: string; }; /** * Telemetry event sent when LS is started for workspace (workspace folder in case of multi-root) */ + /* __GDPR__ + "language_server_enabled" : { + "lsversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } + */ [EventName.LANGUAGE_SERVER_ENABLED]: { lsVersion?: string; }; /** * Telemetry event sent when Node.js server is ready to start */ + /* __GDPR__ + "language_server_ready" : { + "lsversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } + */ [EventName.LANGUAGE_SERVER_READY]: { lsVersion?: string; }; + /** + * Track how long it takes to trigger language server activation code, after Python extension starts activating. + */ + /* __GDPR__ + "language_server_trigger_time" : { + "duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "karthiknadig" }, + "triggerTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "karthiknadig" } + } + */ + [EventName.LANGUAGE_SERVER_TRIGGER_TIME]: { + /** + * Time it took to trigger language server startup. + */ + triggerTime: number; + }; /** * Telemetry event sent when starting Node.js server */ + /* __GDPR__ + "language_server_startup" : { + "lsversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } + */ [EventName.LANGUAGE_SERVER_STARTUP]: { lsVersion?: string; }; /** * Telemetry sent from Node.js server (details of telemetry sent can be provided by LS team) */ - [EventName.LANGUAGE_SERVER_TELEMETRY]: any; + /* __GDPR__ + "language_server_telemetry" : { + "lsversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } + */ + [EventName.LANGUAGE_SERVER_TELEMETRY]: unknown; /** * Telemetry sent when the client makes a request to the Node.js server + * + * This event also has a measure, "resultLength", which records the number of completions provided. */ - [EventName.LANGUAGE_SERVER_REQUEST]: any; - /** - * Telemetry sent on user response to 'Try Pylance' prompt. + /* __GDPR__ + "language_server_request" : { + "lsversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } */ - [EventName.LANGUAGE_SERVER_TRY_PYLANCE]: { - /** - * User response to the prompt. - * @type {string} - */ - userAction: string; - }; + [EventName.LANGUAGE_SERVER_REQUEST]: unknown; /** - * Telemetry captured for enabling reload. + * Telemetry send when Language Server is restarted. */ - [EventName.PYTHON_WEB_APP_RELOAD]: { - /** - * Carries value indicating if the experiment modified `subProcess` field in debug config: - * - `true` if reload experiment modified the `subProcess` field. - * - `false` if user provided debug configuration was not changed (already setup for reload) - */ - subProcessModified?: boolean; - /** - * Carries value indicating if the experiment modified `args` field in debug config: - * - `true` if reload experiment modified the `args` field. - * - `false` if user provided debug configuration was not changed (already setup for reload) - */ - argsModified?: boolean; + /* __GDPR__ + "language_server_restart" : { + "reason" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } + */ + [EventName.LANGUAGE_SERVER_RESTART]: { + reason: 'command' | 'settings' | 'notebooksExperiment'; }; /** - * When user clicks a button in the python extension survey prompt, this telemetry event is sent with details + * Telemetry event sent when Jedi Language Server is started for workspace (workspace folder in case of multi-root) */ - [EventName.EXTENSION_SURVEY_PROMPT]: { - /** - * Carries the selection of user when they are asked to take the extension survey - */ - selection: 'Yes' | 'Maybe later' | 'Do not show again' | undefined; + /* __GDPR__ + "jedi_language_server.enabled" : { + "lsversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } + */ + [EventName.JEDI_LANGUAGE_SERVER_ENABLED]: { + lsVersion?: string; }; /** - * Telemetry event sent when the Python interpreter tip is shown on activation for new users. + * Telemetry event sent when Jedi Language Server server is ready to receive messages */ - [EventName.ACTIVATION_TIP_PROMPT]: never | undefined; - /** - * Telemetry event sent when the feedback survey prompt is shown on activation for new users, and they click on the survey link. + /* __GDPR__ + "jedi_language_server.ready" : { + "lsversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } */ - [EventName.ACTIVATION_SURVEY_PROMPT]: never | undefined; + [EventName.JEDI_LANGUAGE_SERVER_READY]: { + lsVersion?: string; + }; /** - * Telemetry sent back when join mailing list prompt is shown. + * Telemetry event sent when starting Node.js server */ - [EventName.JOIN_MAILING_LIST_PROMPT]: { - /** - * Carries the selection of user when they are asked to join the mailing list. - */ - selection: 'Yes' | 'No' | undefined; + /* __GDPR__ + "jedi_language_server.startup" : { + "lsversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } + */ + [EventName.JEDI_LANGUAGE_SERVER_STARTUP]: { + lsVersion?: string; }; /** - * Telemetry event sent when 'Extract Method' command is invoked + * Telemetry sent when the client makes a request to the Node.js server + * + * This event also has a measure, "resultLength", which records the number of completions provided. */ - [EventName.REFACTOR_EXTRACT_FUNCTION]: never | undefined; - /** - * Telemetry event sent when 'Extract Variable' command is invoked + /* __GDPR__ + "jedi_language_server.request" : { + "method": {"classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig"} + } */ - [EventName.REFACTOR_EXTRACT_VAR]: never | undefined; + [EventName.JEDI_LANGUAGE_SERVER_REQUEST]: unknown; /** - * Telemetry event sent when providing an edit that describes changes to rename a symbol to a different name + * When user clicks a button in the python extension survey prompt, this telemetry event is sent with details */ - [EventName.REFACTOR_RENAME]: never | undefined; - /** - * Telemetry event sent when providing a set of project-wide references for the given position and document + /* __GDPR__ + "extension_survey_prompt" : { + "selection" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } */ - [EventName.REFERENCE]: never | undefined; + [EventName.EXTENSION_SURVEY_PROMPT]: { + /** + * Carries the selection of user when they are asked to take the extension survey + */ + selection: 'Yes' | 'Maybe later' | "Don't show again" | undefined; + }; /** * Telemetry event sent when starting REPL */ - [EventName.REPL]: never | undefined; - /** - * Telemetry event sent with details of linter selected in quickpick of linter list. + /* __GDPR__ + "repl" : { + "duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "anthonykim1" }, + "repltype" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "anthonykim1" } + } */ - [EventName.SELECT_LINTER]: { - /** - * The name of the linter - */ - tool?: LinterId; + [EventName.REPL]: { /** - * Carries `true` if linter is enabled, `false` otherwise + * Whether the user launched the Terminal REPL or Native REPL + * + * Terminal - Terminal REPL user ran `Python: Start Terminal REPL` command. + * Native - Native REPL user ran `Python: Start Native Python REPL` command. + * manualTerminal - User started REPL in terminal using `python`, `python3` or `py` etc without arguments in terminal. + * runningScript - User ran a script in terminal like `python myscript.py`. */ - enabled: boolean; + replType: 'Terminal' | 'Native' | 'manualTerminal' | `runningScript`; }; /** - * Telemetry event sent with details when clicking the prompt with the following message, - * `Prompt message` :- 'You have a pylintrc file in your workspace. Do you want to enable pylint?' + * Telemetry event sent when invoking a Tool + */ + /* __GDPR__ + "INVOKE_TOOL" : { + "duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "owner": "donjayamanne" }, + "toolName" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "failed": {"classification":"SystemMetaData","purpose":"FeatureInsight","comment":"Whether there was a failure. Common to most of the events.", "owner": "donjayamanne" }, + "failureCategory": {"classification":"SystemMetaData","purpose":"FeatureInsight","comment":"A reason that we generate (e.g. kerneldied, noipykernel, etc), more like a category of the error. Common to most of the events.", "owner": "donjayamanne" }, + "resolveOutcome": {"classification":"SystemMetaData","purpose":"FeatureInsight","comment":"Which code path resolved the environment in configure_python_environment.", "owner": "donjayamanne" }, + "envType": {"classification":"SystemMetaData","purpose":"FeatureInsight","comment":"The type of Python environment (e.g. venv, conda, system).", "owner": "donjayamanne" }, + "packageCount": {"classification":"SystemMetaData","purpose":"FeatureInsight","comment":"Number of packages requested for installation (install_python_packages only).", "owner": "donjayamanne" }, + "installerType": {"classification":"SystemMetaData","purpose":"FeatureInsight","comment":"Which installer was used: pip or conda (install_python_packages only).", "owner": "donjayamanne" }, + "responsePackageCount": {"classification":"SystemMetaData","purpose":"FeatureInsight","comment":"Number of packages in the environment response (get_python_environment_details only).", "owner": "donjayamanne" } + } */ - [EventName.CONFIGURE_AVAILABLE_LINTER_PROMPT]: { + [EventName.INVOKE_TOOL]: { /** - * Name of the linter tool - * - * @type {LinterId} + * Tool name. */ - tool: LinterId; + toolName: string; /** - * `enable` When 'Enable [linter name]' option is clicked - * `ignore` When 'Not now' option is clicked - * `disablePrompt` When 'Do not show again` option is clicked - * - * @type {('enable' | 'ignore' | 'disablePrompt' | undefined)} + * Whether there was a failure. + * Common to most of the events. + */ + failed: boolean; + /** + * A reason the error was thrown. + */ + failureCategory?: string; + /** + * Which code path resolved the environment (configure_python_environment only). + */ + resolveOutcome?: string; + /** + * The type of Python environment (e.g. venv, conda, system). + */ + envType?: string; + /** + * Number of packages requested for installation (install_python_packages only). + */ + packageCount?: string; + /** + * Which installer was used: pip or conda (install_python_packages only). + */ + installerType?: string; + /** + * Number of packages in the environment response (get_python_environment_details only). */ - action: 'enable' | 'ignore' | 'disablePrompt' | undefined; + responsePackageCount?: string; }; - /** - * Telemetry event sent when providing help for the signature at the given position and document. - */ - [EventName.SIGNATURE]: never | undefined; - /** - * Telemetry event sent when providing document symbol information for Jedi autocomplete intellisense - */ - [EventName.SYMBOL]: never | undefined; /** * Telemetry event sent if and when user configure tests command. This command can be trigerred from multiple places in the extension. (Command palette, prompt etc.) */ + /* __GDPR__ + "unittest.configure" : { "owner": "eleanorjboyd" } + */ [EventName.UNITTEST_CONFIGURE]: never | undefined; /** * Telemetry event sent when user chooses a test framework in the Quickpick displayed for enabling and configuring test framework */ + /* __GDPR__ + "unittest.configuring" : { + "tool" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }, + "trigger" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "eleanorjboyd" } + } + */ [EventName.UNITTEST_CONFIGURING]: { /** * Name of the test framework to configure @@ -1475,6 +2074,11 @@ export interface IEventNamePropertyMapping { * Telemetry event sent when the extension is activated, if an active terminal is present and * the `python.terminal.activateEnvInCurrentTerminal` setting is set to `true`. */ + /* __GDPR__ + "activate_env_in_current_terminal" : { + "isterminalvisible" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } + */ [EventName.ACTIVATE_ENV_IN_CURRENT_TERMINAL]: { /** * Carries boolean `true` if an active terminal is present (terminal is visible), `false` otherwise @@ -1484,6 +2088,14 @@ export interface IEventNamePropertyMapping { /** * Telemetry event sent with details when a terminal is created */ + /* __GDPR__ + "terminal.create" : { + "terminal" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }, + "triggeredby" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }, + "pythonversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }, + "interpretertype" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } + */ [EventName.TERMINAL_CREATE]: { /** * The type of terminal shell created: powershell, cmd, zsh, bash etc. @@ -1511,352 +2123,112 @@ export interface IEventNamePropertyMapping { interpreterType?: EnvironmentType; }; /** - * Telemetry event sent with details about discovering tests + * Telemetry event sent indicating the trigger source for discovery. */ - [EventName.UNITTEST_DISCOVER]: { - /** - * The test framework used to discover tests - * - * @type {TestTool} - */ - tool: TestTool; + /* __GDPR__ + "unittest.discovery.trigger" : { + "trigger" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" } + } + */ + [EventName.UNITTEST_DISCOVERY_TRIGGER]: { /** * Carries the source which triggered discovering of tests * - * @type {('ui' | 'commandpalette')} - */ - trigger: 'ui' | 'commandpalette'; - /** - * Carries `true` if discovering tests failed, `false` otherwise - * - * @type {boolean} + * @type {('auto' | 'ui' | 'commandpalette' | 'watching' | 'interpreter')} + * auto : Triggered by VS Code editor. + * ui : Triggered by clicking a button. + * commandpalette : Triggered by running the command from the command palette. + * watching : Triggered by filesystem or content changes. + * interpreter : Triggered by interpreter change. */ - failed: boolean; + trigger: 'auto' | 'ui' | 'commandpalette' | 'watching' | 'interpreter'; }; /** - * Telemetry event is sent if we are doing test discovery using python code + * Telemetry event sent with details about discovering tests */ - [EventName.UNITTEST_DISCOVER_WITH_PYCODE]: never | undefined; - /** - * Telemetry event sent when user clicks a file, function, or suite in test explorer. + /* __GDPR__ + "unittest.discovering" : { + "tool" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" } + } */ - [EventName.UNITTEST_NAVIGATE]: { - /** - * Carries `true` if user clicks a file, `false` otherwise - * - * @type {boolean} - */ - byFile?: boolean; + [EventName.UNITTEST_DISCOVERING]: { /** - * Carries `true` if user clicks a function, `false` otherwise + * The test framework used to discover tests * - * @type {boolean} + * @type {TestTool} */ - byFunction?: boolean; + tool: TestTool; + }; + /** + * Telemetry event sent with details about discovering tests + */ + /* __GDPR__ + "unittest.discovery.done" : { + "tool" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }, + "failed" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" } + } + */ + [EventName.UNITTEST_DISCOVERY_DONE]: { /** - * Carries `true` if user clicks a suite, `false` otherwise + * The test framework used to discover tests * - * @type {boolean} + * @type {TestTool} */ - bySuite?: boolean; + tool: TestTool; /** - * Carries `true` if we are changing focus to the suite/file/function, `false` otherwise + * Carries `true` if discovering tests failed, `false` otherwise * * @type {boolean} */ - focus_code?: boolean; + failed: boolean; }; /** - * Tracks number of workspace folders shown in test explorer + * Telemetry event sent when cancelling discovering tests */ - [EventName.UNITTEST_EXPLORER_WORK_SPACE_COUNT]: { count: number }; + /* __GDPR__ + "unittest.discovery.stop" : { "owner": "eleanorjboyd" } + */ + [EventName.UNITTEST_DISCOVERING_STOP]: never | undefined; /** * Telemetry event sent with details about running the tests, what is being run, what framework is being used etc. */ + /* __GDPR__ + "unittest.run" : { + "tool" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" }, + "debugging" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "eleanorjboyd" } + } + */ [EventName.UNITTEST_RUN]: { /** * Framework being used to run tests */ tool: TestTool; - /** - * Carries info what is being run - */ - scope: 'currentFile' | 'all' | 'file' | 'class' | 'function' | 'failed'; /** * Carries `true` if debugging, `false` otherwise */ debugging: boolean; - /** - * Carries what triggered the execution of the tests - */ - triggerSource: 'ui' | 'codelens' | 'commandpalette' | 'auto' | 'testExplorer'; - /** - * Carries `true` if running tests failed, `false` otherwise - */ - failed: boolean; }; /** - * Telemetry event sent when cancelling running or discovering tests + * Telemetry event sent when cancelling running tests */ - [EventName.UNITTEST_STOP]: never | undefined; - /** - * Telemetry event sent when disabling all test frameworks + /* __GDPR__ + "unittest.run.stop" : { "owner": "eleanorjboyd" } */ - [EventName.UNITTEST_DISABLE]: never | undefined; + [EventName.UNITTEST_RUN_STOP]: never | undefined; /** - * Telemetry event sent when viewing Python test log output + * Telemetry event sent when run all failed test command is triggered */ - [EventName.UNITTEST_VIEW_OUTPUT]: never | undefined; - /** - * Tracks which testing framework has been enabled by the user. - * Telemetry is sent when settings have been modified by the user. - * Values sent include: - * unittest - If this value is `true`, then unittest has been enabled by the user. - * pytest - If this value is `true`, then pytest has been enabled by the user. - * nosetest - If this value is `true`, then nose has been enabled by the user. - * @type {(never | undefined)} - * @memberof IEventNamePropertyMapping + /* __GDPR__ + "unittest.run.all_failed" : { "owner": "eleanorjboyd" } */ - [EventName.UNITTEST_ENABLED]: Partial>; - /** - * Telemetry sent when building workspace symbols - */ - [EventName.WORKSPACE_SYMBOLS_BUILD]: never | undefined; - /** - * Telemetry sent when providing workspace symbols doing Project-wide search for a symbol matching the given query string - */ - [EventName.WORKSPACE_SYMBOLS_GO_TO]: never | undefined; - // Data Science - [Telemetry.AddCellBelow]: never | undefined; - [Telemetry.CodeLensAverageAcquisitionTime]: never | undefined; - [Telemetry.CollapseAll]: never | undefined; - [Telemetry.ConnectFailedJupyter]: never | undefined; - [Telemetry.ConnectLocalJupyter]: never | undefined; - [Telemetry.ConnectRemoteJupyter]: never | undefined; - /** - * Connecting to an existing Jupyter server, but connecting to localhost. - */ - [Telemetry.ConnectRemoteJupyterViaLocalHost]: never | undefined; - [Telemetry.ConnectRemoteFailedJupyter]: never | undefined; - [Telemetry.ConnectRemoteSelfCertFailedJupyter]: never | undefined; - [Telemetry.RegisterAndUseInterpreterAsKernel]: never | undefined; - [Telemetry.UseInterpreterAsKernel]: never | undefined; - [Telemetry.UseExistingKernel]: never | undefined; - [Telemetry.SwitchToExistingKernel]: { language: string }; - [Telemetry.SwitchToInterpreterAsKernel]: never | undefined; - [Telemetry.ConvertToPythonFile]: never | undefined; - [Telemetry.CopySourceCode]: never | undefined; - [Telemetry.CreateNewNotebook]: never | undefined; - [Telemetry.DataScienceSettings]: JSONObject; - [Telemetry.DebugContinue]: never | undefined; - [Telemetry.DebugCurrentCell]: never | undefined; - [Telemetry.DebugStepOver]: never | undefined; - [Telemetry.DebugStop]: never | undefined; - [Telemetry.DebugFileInteractive]: never | undefined; - [Telemetry.DeleteAllCells]: never | undefined; - [Telemetry.DeleteCell]: never | undefined; - [Telemetry.FindJupyterCommand]: { command: string }; - [Telemetry.FindJupyterKernelSpec]: never | undefined; - [Telemetry.DisableInteractiveShiftEnter]: never | undefined; - [Telemetry.EnableInteractiveShiftEnter]: never | undefined; - [Telemetry.ExecuteCell]: never | undefined; - /** - * Telemetry sent to capture first time execution of a cell. - * If `notebook = true`, this its telemetry for native editor/notebooks. - */ - [Telemetry.ExecuteCellPerceivedCold]: undefined | { notebook: boolean }; - /** - * Telemetry sent to capture subsequent execution of a cell. - * If `notebook = true`, this its telemetry for native editor/notebooks. - */ - [Telemetry.ExecuteCellPerceivedWarm]: undefined | { notebook: boolean }; - /** - * Time take for jupyter server to start and be ready to run first user cell. - */ - [Telemetry.PerceivedJupyterStartupNotebook]: never | undefined; - /** - * Time take for jupyter server to be busy from the time user first hit `run` cell until jupyter reports it is busy running a cell. - */ - [Telemetry.StartExecuteNotebookCellPerceivedCold]: never | undefined; - [Telemetry.ExecuteNativeCell]: never | undefined; - [Telemetry.ExpandAll]: never | undefined; - [Telemetry.ExportNotebookInteractive]: never | undefined; - [Telemetry.ExportPythonFileInteractive]: never | undefined; - [Telemetry.ExportPythonFileAndOutputInteractive]: never | undefined; - [Telemetry.ClickedExportNotebookAsQuickPick]: { format: ExportFormat }; - [Telemetry.ExportNotebookAs]: { format: ExportFormat; cancelled?: boolean; successful?: boolean; opened?: boolean }; - [Telemetry.ExportNotebookAsCommand]: { format: ExportFormat }; - [Telemetry.ExportNotebookAsFailed]: { format: ExportFormat }; - [Telemetry.GetPasswordAttempt]: never | undefined; - [Telemetry.GetPasswordFailure]: never | undefined; - [Telemetry.GetPasswordSuccess]: never | undefined; - [Telemetry.GotoSourceCode]: never | undefined; - [Telemetry.HiddenCellTime]: never | undefined; - [Telemetry.ImportNotebook]: { scope: 'command' | 'file' }; - [Telemetry.Interrupt]: never | undefined; - [Telemetry.InterruptJupyterTime]: never | undefined; - [Telemetry.NotebookRunCount]: { count: number }; - [Telemetry.NotebookWorkspaceCount]: { count: number }; - [Telemetry.NotebookOpenCount]: { count: number }; - [Telemetry.NotebookOpenTime]: number; - [Telemetry.PandasNotInstalled]: never | undefined; - [Telemetry.PandasTooOld]: never | undefined; - [Telemetry.DebugpyInstallCancelled]: never | undefined; - [Telemetry.DebugpyInstallFailed]: never | undefined; - [Telemetry.DebugpyPromptToInstall]: never | undefined; - [Telemetry.DebugpySuccessfullyInstalled]: never | undefined; - [Telemetry.OpenNotebook]: { scope: 'command' | 'file' }; - [Telemetry.OpenNotebookAll]: never | undefined; - [Telemetry.OpenedInteractiveWindow]: never | undefined; - [Telemetry.OpenPlotViewer]: never | undefined; - [Telemetry.Redo]: never | undefined; - [Telemetry.RemoteAddCode]: never | undefined; - [Telemetry.RemoteReexecuteCode]: never | undefined; - [Telemetry.RestartJupyterTime]: never | undefined; - [Telemetry.RestartKernel]: never | undefined; - [Telemetry.RestartKernelCommand]: never | undefined; - /** - * Run Cell Commands in Interactive Python - */ - [Telemetry.RunAllCells]: never | undefined; - [Telemetry.RunSelectionOrLine]: never | undefined; - [Telemetry.RunCell]: never | undefined; - [Telemetry.RunCurrentCell]: never | undefined; - [Telemetry.RunAllCellsAbove]: never | undefined; - [Telemetry.RunCellAndAllBelow]: never | undefined; - [Telemetry.RunCurrentCellAndAdvance]: never | undefined; - [Telemetry.RunToLine]: never | undefined; - [Telemetry.RunFileInteractive]: never | undefined; - [Telemetry.RunFromLine]: never | undefined; - [Telemetry.ScrolledToCell]: never | undefined; - /** - * Cell Edit Commands in Interactive Python - */ - [Telemetry.InsertCellBelowPosition]: never | undefined; - [Telemetry.InsertCellBelow]: never | undefined; - [Telemetry.InsertCellAbove]: never | undefined; - [Telemetry.DeleteCells]: never | undefined; - [Telemetry.SelectCell]: never | undefined; - [Telemetry.SelectCellContents]: never | undefined; - [Telemetry.ExtendSelectionByCellAbove]: never | undefined; - [Telemetry.ExtendSelectionByCellBelow]: never | undefined; - [Telemetry.MoveCellsUp]: never | undefined; - [Telemetry.MoveCellsDown]: never | undefined; - [Telemetry.ChangeCellToMarkdown]: never | undefined; - [Telemetry.ChangeCellToCode]: never | undefined; - [Telemetry.GotoNextCellInFile]: never | undefined; - [Telemetry.GotoPrevCellInFile]: never | undefined; - /** - * Misc - */ - [Telemetry.AddEmptyCellToBottom]: never | undefined; - [Telemetry.RunCurrentCellAndAddBelow]: never | undefined; - [Telemetry.CellCount]: { count: number }; - [Telemetry.Save]: never | undefined; - [Telemetry.SelfCertsMessageClose]: never | undefined; - [Telemetry.SelfCertsMessageEnabled]: never | undefined; - [Telemetry.SelectJupyterURI]: never | undefined; - [Telemetry.SelectLocalJupyterKernel]: never | undefined; - [Telemetry.SelectRemoteJupyterKernel]: never | undefined; - [Telemetry.SessionIdleTimeout]: never | undefined; - [Telemetry.JupyterNotInstalledErrorShown]: never | undefined; - [Telemetry.JupyterCommandSearch]: { - where: 'activeInterpreter' | 'otherInterpreter' | 'path' | 'nowhere'; - command: JupyterCommands; - }; - [Telemetry.UserInstalledJupyter]: never | undefined; - [Telemetry.UserInstalledPandas]: never | undefined; - [Telemetry.UserDidNotInstallJupyter]: never | undefined; - [Telemetry.UserDidNotInstallPandas]: never | undefined; - [Telemetry.SetJupyterURIToLocal]: never | undefined; - [Telemetry.SetJupyterURIToUserSpecified]: never | undefined; - [Telemetry.ShiftEnterBannerShown]: never | undefined; - [Telemetry.ShowDataViewer]: { rows: number | undefined; columns: number | undefined }; - [Telemetry.CreateNewInteractive]: never | undefined; - [Telemetry.StartJupyter]: never | undefined; - [Telemetry.StartJupyterProcess]: never | undefined; - /** - * Telemetry event sent when jupyter has been found in interpreter but we cannot find kernelspec. - * - * @type {(never | undefined)} - * @memberof IEventNamePropertyMapping + [EventName.UNITTEST_RUN_ALL_FAILED]: never | undefined; + /** + * Telemetry event sent when testing is disabled for a workspace. */ - [Telemetry.JupyterInstalledButNotKernelSpecModule]: never | undefined; - [Telemetry.JupyterStartTimeout]: { - /** - * Total time spent in attempting to start and connect to jupyter before giving up. - * - * @type {number} - */ - timeout: number; - }; - [Telemetry.SubmitCellThroughInput]: never | undefined; - [Telemetry.Undo]: never | undefined; - [Telemetry.VariableExplorerFetchTime]: never | undefined; - [Telemetry.VariableExplorerToggled]: { open: boolean; runByLine: boolean }; - [Telemetry.VariableExplorerVariableCount]: { variableCount: number }; - [Telemetry.WaitForIdleJupyter]: never | undefined; - [Telemetry.WebviewMonacoStyleUpdate]: never | undefined; - [Telemetry.WebviewStartup]: { type: string }; - [Telemetry.WebviewStyleUpdate]: never | undefined; - [Telemetry.RegisterInterpreterAsKernel]: never | undefined; - /** - * Telemetry sent when user selects an interpreter to start jupyter server. - * - * @type {(never | undefined)} - * @memberof IEventNamePropertyMapping + /* __GDPR__ + "unittest.disabled" : { "owner": "eleanorjboyd" } */ - [Telemetry.SelectJupyterInterpreterCommand]: never | undefined; - [Telemetry.SelectJupyterInterpreter]: { - /** - * The result of the selection. - * notSelected - No interpreter was selected. - * selected - An interpreter was selected (and configured to have jupyter and notebook). - * installationCancelled - Installation of jupyter and/or notebook was cancelled for an interpreter. - * - * @type {('notSelected' | 'selected' | 'installationCancelled')} - */ - result?: 'notSelected' | 'selected' | 'installationCancelled'; - }; - [NativeKeyboardCommandTelemetry.ArrowDown]: never | undefined; - [NativeKeyboardCommandTelemetry.ArrowUp]: never | undefined; - [NativeKeyboardCommandTelemetry.ChangeToCode]: never | undefined; - [NativeKeyboardCommandTelemetry.ChangeToMarkdown]: never | undefined; - [NativeKeyboardCommandTelemetry.DeleteCell]: never | undefined; - [NativeKeyboardCommandTelemetry.InsertAbove]: never | undefined; - [NativeKeyboardCommandTelemetry.InsertBelow]: never | undefined; - [NativeKeyboardCommandTelemetry.Redo]: never | undefined; - [NativeKeyboardCommandTelemetry.Run]: never | undefined; - [NativeKeyboardCommandTelemetry.RunAndAdd]: never | undefined; - [NativeKeyboardCommandTelemetry.RunAndMove]: never | undefined; - [NativeKeyboardCommandTelemetry.Save]: never | undefined; - [NativeKeyboardCommandTelemetry.ToggleLineNumbers]: never | undefined; - [NativeKeyboardCommandTelemetry.ToggleOutput]: never | undefined; - [NativeKeyboardCommandTelemetry.Undo]: never | undefined; - [NativeKeyboardCommandTelemetry.Unfocus]: never | undefined; - [NativeMouseCommandTelemetry.AddToEnd]: never | undefined; - [NativeMouseCommandTelemetry.ChangeToCode]: never | undefined; - [NativeMouseCommandTelemetry.ChangeToMarkdown]: never | undefined; - [NativeMouseCommandTelemetry.DeleteCell]: never | undefined; - [NativeMouseCommandTelemetry.InsertBelow]: never | undefined; - [NativeMouseCommandTelemetry.MoveCellDown]: never | undefined; - [NativeMouseCommandTelemetry.MoveCellUp]: never | undefined; - [NativeMouseCommandTelemetry.Run]: never | undefined; - [NativeMouseCommandTelemetry.RunAbove]: never | undefined; - [NativeMouseCommandTelemetry.RunAll]: never | undefined; - [NativeMouseCommandTelemetry.RunBelow]: never | undefined; - [NativeMouseCommandTelemetry.Save]: never | undefined; - [NativeMouseCommandTelemetry.SelectKernel]: never | undefined; - [NativeMouseCommandTelemetry.SelectServer]: never | undefined; - [NativeMouseCommandTelemetry.ToggleVariableExplorer]: never | undefined; - /* - Telemetry event sent with details of Jedi Memory usage. - mem_use - Memory usage of Process in kb. - limit - Upper bound for memory usage of Jedi process. - isUserDefinedLimit - Whether the user has configfured the upper bound limit. - restart - Whether to restart the Jedi Process (i.e. memory > limit). - */ - [EventName.JEDI_MEMORY]: { mem_use: number; limit: number; isUserDefinedLimit: boolean; restart: boolean }; + [EventName.UNITTEST_DISABLED]: never | undefined; /* Telemetry event sent to provide information on whether we have successfully identify the type of shell used. This information is useful in determining how well we identify shells on users machines. @@ -1878,6 +2250,15 @@ export interface IEventNamePropertyMapping { If true, user has a shell in their environment. If false, user does not have a shell in their environment. */ + /* __GDPR__ + "terminal_shell_identification" : { + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "karthiknadig" }, + "terminalprovided" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }, + "shellidentificationsource" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }, + "hascustomshell" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" }, + "hasshellinenv" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karthiknadig" } + } + */ [EventName.TERMINAL_SHELL_IDENTIFICATION]: { failed: boolean; terminalProvided: boolean; @@ -1891,6 +2272,12 @@ export interface IEventNamePropertyMapping { * @type {(undefined | never)} * @memberof IEventNamePropertyMapping */ + /* __GDPR__ + "activate_env_to_get_env_vars_failed" : { + "ispossiblycondaenv" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "karthiknadig" }, + "terminal" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "karthiknadig" } + } + */ [EventName.ACTIVATE_ENV_TO_GET_ENV_VARS_FAILED]: { /** * Whether the activation commands contain the name `conda`. @@ -1905,341 +2292,225 @@ export interface IEventNamePropertyMapping { */ terminal: TerminalShellType; }; + + // TensorBoard integration events /** - * Telemetry event sent once done searching for kernel spec and interpreter for a local connection. - * - * @type {{ - * kernelSpecFound: boolean; - * interpreterFound: boolean; - * }} - * @memberof IEventNamePropertyMapping - */ - [Telemetry.FindKernelForLocalConnection]: { - /** - * Whether a kernel spec was found. - * - * @type {boolean} - */ - kernelSpecFound: boolean; - /** - * Whether an interpreter was found. - * - * @type {boolean} - */ - interpreterFound: boolean; - /** - * Whether user was prompted to select a kernel spec. - * - * @type {boolean} - */ - promptedToSelect?: boolean; - }; - /** - * Telemetry event sent when starting a session for a local connection failed. - * - * @type {(undefined | never)} - * @memberof IEventNamePropertyMapping - */ - [Telemetry.StartSessionFailedJupyter]: undefined | never; - /** - * Telemetry event fired if a failure occurs loading a notebook + * Telemetry event sent when the user is prompted to install Python packages that are + * dependencies for launching an integrated TensorBoard session. */ - [Telemetry.OpenNotebookFailure]: undefined | never; - /** - * Telemetry event sent to capture total time taken for completions list to be provided by LS. - * This is used to compare against time taken by Jupyter. - * - * @type {(undefined | never)} - * @memberof IEventNamePropertyMapping + /* __GDPR__ + "tensorboard.session_duration" : { "owner": "donjayamanne" } */ - [Telemetry.CompletionTimeFromLS]: undefined | never; + [EventName.TENSORBOARD_INSTALL_PROMPT_SHOWN]: never | undefined; /** - * Telemetry event sent to capture total time taken for completions list to be provided by Jupyter. - * This is used to compare against time taken by LS. - * - * @type {(undefined | never)} - * @memberof IEventNamePropertyMapping + * Telemetry event sent after the user has clicked on an option in the prompt we display + * asking them if they want to install Python packages for launching an integrated TensorBoard session. + * `selection` is one of 'yes' or 'no'. */ - [Telemetry.CompletionTimeFromJupyter]: undefined | never; - /** - * Telemetry event sent to indicate the language used in a notebook - * - * @type { language: string } - * @memberof IEventNamePropertyMapping + /* __GDPR__ + "tensorboard.install_prompt_selection" : { + "selection" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" }, + "operationtype" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "donjayamanne" } + } */ - [Telemetry.NotebookLanguage]: { - /** - * Language found in the notebook if a known language. Otherwise 'unknown' - */ - language: string; + [EventName.TENSORBOARD_INSTALL_PROMPT_SELECTION]: { + selection: TensorBoardPromptSelection; + operationType: 'install' | 'upgrade'; }; /** - * Telemetry event sent to indicate 'jupyter kernelspec' is not possible. - * - * @type {(undefined | never)} - * @memberof IEventNamePropertyMapping + * Telemetry event sent when we find an active integrated terminal running tensorboard. */ - [Telemetry.KernelSpecNotFound]: undefined | never; - /** - * Telemetry event sent to indicate registering a kernel with jupyter failed. - * - * @type {(undefined | never)} - * @memberof IEventNamePropertyMapping + /* __GDPR__ + "tensorboard_detected_in_integrated_terminal" : { "owner": "donjayamanne" } */ - [Telemetry.KernelRegisterFailed]: undefined | never; + [EventName.TENSORBOARD_DETECTED_IN_INTEGRATED_TERMINAL]: never | undefined; /** - * Telemetry event sent to every time a kernel enumeration is done - * - * @type {...} - * @memberof IEventNamePropertyMapping + * Telemetry event sent after attempting to install TensorBoard session dependencies. + * Note, this is only sent if install was attempted. It is not sent if the user opted + * not to install, or if all dependencies were already installed. */ - [Telemetry.KernelEnumeration]: { - /** - * Count of the number of kernels found - */ - count: number; - /** - * Boolean indicating if any are python or not - */ - isPython: boolean; - /** - * Indicates how the enumeration was acquired. - */ - source: 'cli' | 'connection'; + /* __GDPR__ + "tensorboard.package_install_result" : { + "wasprofilerpluginattempted" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "donjayamanne" }, + "wastensorboardattempted" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "donjayamanne" }, + "wasprofilerplugininstalled" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "donjayamanne" }, + "wastensorboardinstalled" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "donjayamanne" } + } + */ + + [EventName.TENSORBOARD_PACKAGE_INSTALL_RESULT]: { + wasProfilerPluginAttempted: boolean; + wasTensorBoardAttempted: boolean; + wasProfilerPluginInstalled: boolean; + wasTensorBoardInstalled: boolean; }; /** - * Total time taken to Launch a raw kernel. + * Telemetry event sent when the user's files contain a PyTorch profiler module + * import. Files are checked for matching imports when they are opened or saved. + * Matches cover import statements of the form `import torch.profiler` and + * `from torch import profiler`. */ - [Telemetry.KernelLauncherPerf]: undefined | never; - /** - * Total time taken to find a kernel on disc. + /* __GDPR__ + "tensorboard.torch_profiler_import" : { "owner": "donjayamanne" } */ - [Telemetry.KernelFinderPerf]: undefined | never; + [EventName.TENSORBOARD_TORCH_PROFILER_IMPORT]: never | undefined; + [EventName.TENSORBOARD_DETECTED_IN_INTEGRATED_TERMINAL]: never | undefined; /** - * Telemetry event sent if there's an error installing a jupyter required dependency - * - * @type { product: string } - * @memberof IEventNamePropertyMapping + * Telemetry event sent before creating an environment. */ - [Telemetry.JupyterInstallFailed]: { - /** - * Product being installed (jupyter or ipykernel or other) - */ - product: string; + /* __GDPR__ + "environment.creating" : { + "environmentType" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "karthiknadig" }, + "pythonVersion" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "karthiknadig" } + } + */ + [EventName.ENVIRONMENT_CREATING]: { + environmentType: 'venv' | 'conda' | 'microvenv' | undefined; + pythonVersion: string | undefined; }; /** - * Telemetry event sent when installing a jupyter dependency - * - * @type {product: string} - * @memberof IEventNamePropertyMapping + * Telemetry event sent after creating an environment, but before attempting package installation. */ - [Telemetry.UserInstalledModule]: { product: string }; - /** - * Telemetry event sent to when user customizes the jupyter command line - * @type {(undefined | never)} - * @memberof IEventNamePropertyMapping + /* __GDPR__ + "environment.created" : { + "environmentType" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "karthiknadig" }, + "reason" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "karthiknadig" } + } */ - [Telemetry.JupyterCommandLineNonDefault]: undefined | never; + [EventName.ENVIRONMENT_CREATED]: { + environmentType: 'venv' | 'conda' | 'microvenv'; + reason: 'created' | 'existing'; + }; /** - * Telemetry event sent when a user runs the interactive window with a new file - * @type {(undefined | never)} - * @memberof IEventNamePropertyMapping + * Telemetry event sent if creating an environment failed. */ - [Telemetry.NewFileForInteractiveWindow]: undefined | never; - /** - * Telemetry event sent when a kernel picked crashes on startup - * @type {(undefined | never)} - * @memberof IEventNamePropertyMapping + /* __GDPR__ + "environment.failed" : { + "environmentType" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "karthiknadig" }, + "reason" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "karthiknadig" } + } */ - [Telemetry.KernelInvalid]: undefined | never; - [Telemetry.GatherIsInstalled]: undefined | never; - [Telemetry.GatherCompleted]: { - /** - * result indicates whether the gather was completed to a script, notebook or suffered an internal error. - */ - result: 'err' | 'script' | 'notebook' | 'unavailable'; - }; - [Telemetry.GatherStats]: { - linesSubmitted: number; - cellsSubmitted: number; - linesGathered: number; - cellsGathered: number; + [EventName.ENVIRONMENT_FAILED]: { + environmentType: 'venv' | 'conda' | 'microvenv'; + reason: 'noVenv' | 'noPip' | 'noDistUtils' | 'other'; }; - [Telemetry.GatherException]: { - exceptionType: 'activate' | 'gather' | 'log' | 'reset'; - }; - /** - * Telemetry event sent when a gathered notebook has been saved by the user. - */ - [Telemetry.GatheredNotebookSaved]: undefined | never; /** - * Telemetry event sent when the user reports whether Gathered notebook was good or not + * Telemetry event sent before installing packages. */ - [Telemetry.GatherQualityReport]: { result: 'yes' | 'no' }; - /** - * Telemetry event sent when the ZMQ native binaries do not work. + /* __GDPR__ + "environment.installing_packages" : { + "environmentType" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "karthiknadig" }, + "using" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "karthiknadig" } + } */ - [Telemetry.ZMQNotSupported]: undefined | never; + [EventName.ENVIRONMENT_INSTALLING_PACKAGES]: { + environmentType: 'venv' | 'conda' | 'microvenv'; + using: 'requirements.txt' | 'pyproject.toml' | 'environment.yml' | 'pipUpgrade' | 'pipInstall' | 'pipDownload'; + }; /** - * Telemetry event sent when the ZMQ native binaries do work. + * Telemetry event sent after installing packages. */ - [Telemetry.ZMQSupported]: undefined | never; - /** - * Telemetry event sent with name of a Widget that is used. + /* __GDPR__ + "environment.installed_packages" : { + "environmentType" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "karthiknadig" }, + "using" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "karthiknadig" } + } */ - [Telemetry.HashedIPyWidgetNameUsed]: { - /** - * Hash of the widget - */ - hashedName: string; - /** - * Where did we find the hashed name (CDN or user environment or remote jupyter). - */ - source?: 'cdn' | 'local' | 'remote'; - /** - * Whether we searched CDN or not. - */ - cdnSearched: boolean; + [EventName.ENVIRONMENT_INSTALLED_PACKAGES]: { + environmentType: 'venv' | 'conda'; + using: 'requirements.txt' | 'pyproject.toml' | 'environment.yml' | 'pipUpgrade'; }; /** - * Telemetry event sent with name of a Widget found. + * Telemetry event sent if installing packages failed. */ - [Telemetry.HashedIPyWidgetNameDiscovered]: { - /** - * Hash of the widget - */ - hashedName: string; - /** - * Where did we find the hashed name (CDN or user environment or remote jupyter). - */ - source?: 'cdn' | 'local' | 'remote'; + /* __GDPR__ + "environment.installing_packages_failed" : { + "environmentType" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "karthiknadig" }, + "using" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "karthiknadig" } + } + */ + [EventName.ENVIRONMENT_INSTALLING_PACKAGES_FAILED]: { + environmentType: 'venv' | 'conda' | 'microvenv'; + using: 'pipUpgrade' | 'requirements.txt' | 'pyproject.toml' | 'environment.yml' | 'pipDownload' | 'pipInstall'; }; /** - * Total time taken to discover all IPyWidgets on disc. - * This is how long it takes to discover a single widget on disc (from python environment). + * Telemetry event sent if create environment button was used to trigger the command. */ - [Telemetry.DiscoverIPyWidgetNamesLocalPerf]: never | undefined; - /** - * Something went wrong in looking for a widget. + /* __GDPR__ + "environment.button" : {"owner": "karthiknadig" } */ - [Telemetry.HashedIPyWidgetScriptDiscoveryError]: never | undefined; + [EventName.ENVIRONMENT_BUTTON]: never | undefined; /** - * Telemetry event sent when an ipywidget module loads. Module name is hashed. + * Telemetry event if user selected to delete the existing environment. */ - [Telemetry.IPyWidgetLoadSuccess]: { moduleHash: string; moduleVersion: string }; - /** - * Telemetry event sent when an ipywidget module fails to load. Module name is hashed. + /* __GDPR__ + "environment.delete" : { + "environmentType" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "karthiknadig" }, + "status" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "karthiknadig" } + } */ - [Telemetry.IPyWidgetLoadFailure]: { - isOnline: boolean; - moduleHash: string; - moduleVersion: string; - // Whether we timedout getting the source of the script (fetching script source in extension code). - timedout: boolean; + [EventName.ENVIRONMENT_DELETE]: { + environmentType: 'venv' | 'conda'; + status: 'triggered' | 'deleted' | 'failed'; }; /** - * Telemetry event sent when an ipywidget version that is not supported is used & we have trapped this and warned the user abou it. - */ - [Telemetry.IPyWidgetWidgetVersionNotSupportedLoadFailure]: { moduleHash: string; moduleVersion: string }; - /** - * Telemetry event sent when an loading of 3rd party ipywidget JS scripts from 3rd party source has been disabled. + * Telemetry event if user selected to re-use the existing environment. */ - [Telemetry.IPyWidgetLoadDisabled]: { moduleHash: string; moduleVersion: string }; - /** - * Total time taken to discover a widget script on CDN. + /* __GDPR__ + "environment.reuse" : { + "environmentType" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "karthiknadig" } + } */ - [Telemetry.DiscoverIPyWidgetNamesCDNPerf]: { - // The CDN we were testing. - cdn: string; - // Whether we managed to find the widget on the CDN or not. - exists: boolean; + [EventName.ENVIRONMENT_REUSE]: { + environmentType: 'venv' | 'conda'; }; /** - * Telemetry sent when we prompt user to use a CDN for IPyWidget scripts. - * This is always sent when we display a prompt. + * Telemetry event sent when a check for environment creation conditions is triggered. */ - [Telemetry.IPyWidgetPromptToUseCDN]: never | undefined; - /** - * Telemetry sent when user does somethign with the prompt displsyed to user about using CDN for IPyWidget scripts. + /* __GDPR__ + "environment.check.trigger" : { + "trigger" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "karthiknadig" } + } */ - [Telemetry.IPyWidgetPromptToUseCDNSelection]: { - selection: 'ok' | 'cancel' | 'dismissed' | 'doNotShowAgain'; + [EventName.ENVIRONMENT_CHECK_TRIGGER]: { + trigger: + | 'run-in-terminal' + | 'debug-in-terminal' + | 'run-selection' + | 'on-workspace-load' + | 'as-command' + | 'debug'; }; /** - * Telemetry event sent to indicate the overhead of syncing the kernel with the UI. + * Telemetry event sent when a check for environment creation condition is computed. + */ + /* __GDPR__ + "environment.check.result" : { + "result" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "owner": "karthiknadig" } + } */ - [Telemetry.IPyWidgetOverhead]: { - totalOverheadInMs: number; - numberOfMessagesWaitedOn: number; - averageWaitTime: number; - numberOfRegisteredHooks: number; + [EventName.ENVIRONMENT_CHECK_RESULT]: { + result: 'criteria-met' | 'criteria-not-met' | 'already-ran' | 'turned-off' | 'no-uri'; }; /** - * Telemetry event sent when the widget render function fails (note, this may not be sufficient to capture all failures). + * Telemetry event sent when `pip install` was called from a global env in a shell where shell inegration is supported. */ - [Telemetry.IPyWidgetRenderFailure]: never | undefined; - /** - * Telemetry event sent when the widget tries to send a kernel message but nothing was listening + /* __GDPR__ + "environment.terminal.global_pip" : { "owner": "karthiknadig" } */ - [Telemetry.IPyWidgetUnhandledMessage]: { - msg_type: string; - }; - - // Telemetry send when we create a notebook for a raw kernel or jupyter - [Telemetry.RawKernelCreatingNotebook]: never | undefined; - [Telemetry.JupyterCreatingNotebook]: never | undefined; - - // Raw kernel timing events - [Telemetry.RawKernelSessionConnect]: never | undefined; - [Telemetry.RawKernelStartRawSession]: never | undefined; - [Telemetry.RawKernelProcessLaunch]: never | undefined; - - // Raw kernel single events - [Telemetry.RawKernelSessionStartSuccess]: never | undefined; - [Telemetry.RawKernelSessionStartException]: never | undefined; - [Telemetry.RawKernelSessionStartTimeout]: never | undefined; - [Telemetry.RawKernelSessionStartUserCancel]: never | undefined; - - // Start Page Events - [Telemetry.StartPageViewed]: never | undefined; - [Telemetry.StartPageOpenedFromCommandPalette]: never | undefined; - [Telemetry.StartPageOpenedFromNewInstall]: never | undefined; - [Telemetry.StartPageOpenedFromNewUpdate]: never | undefined; - [Telemetry.StartPageWebViewError]: never | undefined; - [Telemetry.StartPageTime]: never | undefined; - [Telemetry.StartPageClickedDontShowAgain]: never | undefined; - [Telemetry.StartPageClosedWithoutAction]: never | undefined; - [Telemetry.StartPageUsedAnActionOnFirstTime]: never | undefined; - [Telemetry.StartPageOpenBlankNotebook]: never | undefined; - [Telemetry.StartPageOpenBlankPythonFile]: never | undefined; - [Telemetry.StartPageOpenInteractiveWindow]: never | undefined; - [Telemetry.StartPageOpenCommandPalette]: never | undefined; - [Telemetry.StartPageOpenCommandPaletteWithOpenNBSelected]: never | undefined; - [Telemetry.StartPageOpenSampleNotebook]: never | undefined; - [Telemetry.StartPageOpenFileBrowser]: never | undefined; - [Telemetry.StartPageOpenFolder]: never | undefined; - [Telemetry.StartPageOpenWorkspace]: never | undefined; - - // Run by line events - [Telemetry.RunByLineStart]: never | undefined; - [Telemetry.RunByLineStep]: never | undefined; - [Telemetry.RunByLineStop]: never | undefined; - [Telemetry.RunByLineVariableHover]: never | undefined; - - // Trusted notebooks events - [Telemetry.NotebookTrustPromptShown]: never | undefined; - [Telemetry.TrustNotebook]: never | undefined; - [Telemetry.TrustAllNotebooks]: never | undefined; - [Telemetry.DoNotTrustNotebook]: never | undefined; - - // Native notebooks events - [VSCodeNativeTelemetry.AddCell]: never | undefined; - [VSCodeNativeTelemetry.DeleteCell]: never | undefined; - [VSCodeNativeTelemetry.MoveCell]: never | undefined; - [VSCodeNativeTelemetry.ChangeToCode]: never | undefined; - [VSCodeNativeTelemetry.ChangeToMarkdown]: never | undefined; - [VSCodeNativeTelemetry.RunAllCells]: never | undefined; - [Telemetry.VSCNotebookCellTranslationFailed]: { - isErrorOutput: boolean; // Whether we're trying to translate an error output when we shuldn't be. - }; + [EventName.ENVIRONMENT_TERMINAL_GLOBAL_PIP]: never | undefined; + /* __GDPR__ + "query-expfeature" : { + "owner": "luabud", + "comment": "Logs queries to the experiment service by feature for metric calculations", + "ABExp.queriedFeature": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The experimental feature being queried" } + } + */ + /* __GDPR__ + "call-tas-error" : { + "owner": "luabud", + "comment": "Logs when calls to the experiment service fails", + "errortype": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Type of error when calling TAS (ServerError, NoResponse, etc.)"} + } + */ } diff --git a/src/client/telemetry/pylance.ts b/src/client/telemetry/pylance.ts new file mode 100644 index 000000000000..63bd113893e2 --- /dev/null +++ b/src/client/telemetry/pylance.ts @@ -0,0 +1,484 @@ +/* __GDPR__ + "language_server.enabled" : { + "duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "errorname" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, + "errorstack" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server.jinja_usage" : { + "lsversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } , + "openfileextensions" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server.ready" : { + "duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "errorname" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, + "errorstack" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server.request" : { + "duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "errorname" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, + "errorstack" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "method" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "modulehash" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "moduleversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "resultlength" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server.startup" : { + "duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "errorname" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, + "errorstack" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server/analysis_complete" : { + "configparseerroroccurred" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "elapsedms" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "externalmb" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "fatalerroroccurred" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "heaptotalmb" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "heapusedmb" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "installsource" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "isdone" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "isfirstrun" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "numfilesanalyzed" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "numfilesinprogram" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "peakrssmb" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "resolverid" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "rssmb" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "diagnosticsseen" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "editablepthcount": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "computedpthcount": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + + } +*/ +/* __GDPR__ + "language_server/analysis_exception" : { + "errorname" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, + "errorstack" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server/completion_accepted" : { + "autoimport" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "dictionarykey" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "memberaccess" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "keyword" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server/completion_coverage" : { + "failures" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "overallfailures" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "overallsuccesses" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "overalltotal" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "successes" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "total" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server/completion_metrics" : { + "installsource" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "lastknownmembernamehash" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "lastknownmodulehash" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "packagehash" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "unknownmembernamehash" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server/completion_slow" : { + "bindcallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "bindtime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "correlationid" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "custom_autoimportadditiontimeinms" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "custom_autoimportedittimeinms" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "custom_autoimportimportaliascount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "custom_autoimportimportaliastimeinms" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "custom_autoimportindexcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "custom_autoimportindextimeinms" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "custom_autoimportindexused" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "custom_autoimportitemcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "custom_autoimportmoduleresolvetimeinms" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "custom_autoimportmoduletimeinms" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "custom_autoimportsymbolcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "custom_autoimporttotaltimeinms" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "custom_autoimportuserindexcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "custom_completionitems" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "custom_completionitemtelemetrybuildtimeinms" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "custom_extensiontotaltimeinms" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "custom_selecteditemtelemetrybuildtimeinms" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "custom_completiontype" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "custom_filetype" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "parsecallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "parsetime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "readfilecallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "readfiletime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "resolvecallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "resolvetime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "tokenizecallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "tokenizetime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "totaltime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "typeevalcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "typeevaltime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server/completion_context_items" : { + "lsversion" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "context" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } +*/ +/* __GDPR__ + "language_server/documentcolor_slow" : { + "bindcallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "bindtime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "parsecallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "parsetime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "readfilecallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "readfiletime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "resolvecallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "resolvetime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "tokenizecallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "tokenizetime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "totaltime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "typeevalcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "typeevaltime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server/exception_intellicode" : { + "lsversion" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server/execute_command" : { + "lsversion" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "name" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server/goto_def_inside_string" : { + "resultlength" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server/import_heuristic" : { + "avgcost" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "avglevel" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "conflicts" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "nativemodules" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "nativepackages" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "reason_because_it_is_not_a_valid_directory" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "reason_could_not_parse_output" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "reason_did_not_find_file" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "reason_no_python_interpreter_search_path" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "reason_typeshed_path_not_found" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "resolverid" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "success" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "total" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server/import_metrics" : { + "absolutestubs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "absolutetotal" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "absoluteunresolved" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "absoluteuserunresolved" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "builtinimportstubs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "builtinimporttotal" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "installsource" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "localimportstubs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "localimporttotal" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "nativemodules" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "nativepackages" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "relativestubs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "relativetotal" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "relativeunresolved" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "resolverid" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "stubs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "thirdpartyimportstubs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "thirdpartyimporttotal" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "total" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "unresolvedmodules" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "unresolvedpackages" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "unresolvedpackageslowercase" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "unresolvedtotal" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server/index_slow" : { + "bindcallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "bindtime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "custom_count" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "parsecallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "parsetime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "readfilecallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "readfiletime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "resolvecallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "resolvetime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "tokenizecallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "tokenizetime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "totaltime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "typeevalcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "typeevaltime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server/installed_packages" : { + "packagesbitarray" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "packageslowercase" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "resolverid" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "editablepthcount": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } +*/ +/* __GDPR__ + "language_server/intellicode_completion_item_selected" : { + "class" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "count" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "elapsedtime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "failurereason" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "id" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "index" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "installsource" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "isintellicodecommit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "language" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "memoryincreasekb" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "method" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "methods" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "modeltype" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "modelversion" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "selecteditemtelemetrybuildtimeinms" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server/intellicode_enabled" : { + "enabled" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "installsource" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "startup" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server/intellicode_model_load_failed" : { + "errorname" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, + "errorstack" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, + "installsource" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "reason" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server/intellicode_onnx_load_failed" : { + "errorname" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, + "errorstack" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" }, + "installsource" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "reason" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server/rename_files" : { + "affectedfilescount" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "bindcallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "bindtime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "filerenamed" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "parsecallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "parsetime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "readfilecallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "readfiletime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "resolvecallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "resolvetime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "tokenizecallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "tokenizetime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "totaltime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "type" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "typeevalcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "typeevaltime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server/semantictokens_slow" : { + "bindcallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "bindtime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "custom_count" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "parsecallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "parsetime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "readfilecallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "readfiletime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "resolvecallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "resolvetime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "tokenizecallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "tokenizetime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "totaltime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "typeevalcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "typeevaltime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server/server_side_request" : { + "duration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "method" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "modulehash" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "resultlength" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server/settings" : { + "addimportexactmatchonly" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "aicodeactionsimplementabstractclasses" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "aiCodeActionsGenerateDocstring" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "aiCodeActionsGenerateSymbols" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "aiCodeActionsConvertFormatString" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "autoimportcompletions" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "autosearchpaths" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "callArgumentNameInlayHints" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "completefunctionparens" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "disableTaggedHints" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "disableworkspacesymbol" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "enableextractcodeaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "enablePytestSupport" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "extracommitchars" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "formatontype" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "functionReturnInlayTypeHints" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "hasconfigfile" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "hasextrapaths" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "importformat" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "intelliCodeEnabled" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "includeusersymbolsinautoimport" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "indexing" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "languageservermode" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "lspinteractivewindows" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "lspnotebooks" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "movesymbol" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "nodeExecutable" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "openfilesonly" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "pytestparameterinlaytypehints" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "typecheckingmode" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "unusablecompilerflags": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "useimportheuristic" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "uselibrarycodefortypes" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "variableinlaytypehints" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "watchforlibrarychanges" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "workspacecount" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } +*/ +/* __GDPR__ + "language_server/startup_metrics" : { + "analysisms" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "peakrssmb" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "presetfileopenms" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "tokendeltams" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "tokenfullms" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "tokenrangems" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "totalms" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "userindexms" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } +*/ +/* __GDPR__ + "language_server/workspaceindex_slow" : { + "bindcallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "bindtime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "custom_count" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "parsecallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "parsetime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "readfilecallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "readfiletime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "resolvecallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "resolvetime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "tokenizecallcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "tokenizetime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "totaltime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "typeevalcount" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "typeevaltime" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server/workspaceindex_threshold_reached" : { + "index_count" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server/mcp_tool" : { + "kind" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "cancelled" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "cancellation_reason" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/* __GDPR__ + "language_server/copilot_hover" : { + "symbolName" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } +*/ +/** + * Telemetry event sent when LSP server crashes + */ +/* __GDPR__ +"language_server.crash" : { + "oom" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "rchiodo" }, + "lsversion" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "rchiodo" }, + "failed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } +} +*/ diff --git a/src/client/telemetry/types.ts b/src/client/telemetry/types.ts index cc17128c858b..42e51b261129 100644 --- a/src/client/telemetry/types.ts +++ b/src/client/telemetry/types.ts @@ -1,25 +1,17 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + 'use strict'; -import { IEventNamePropertyMapping } from '../telemetry/index'; +import type { IEventNamePropertyMapping } from './index'; import { EventName } from './constants'; export type EditorLoadTelemetry = IEventNamePropertyMapping[EventName.EDITOR_LOAD]; -export type LinterTrigger = 'auto' | 'save'; - -export type LintingTelemetry = IEventNamePropertyMapping[EventName.LINTING]; - export type PythonInterpreterTelemetry = IEventNamePropertyMapping[EventName.PYTHON_INTERPRETER]; -export type CodeExecutionTelemetry = IEventNamePropertyMapping[EventName.EXECUTION_CODE]; -export type DebuggerTelemetry = IEventNamePropertyMapping[EventName.DEBUGGER]; -export type TestTool = 'nosetest' | 'pytest' | 'unittest'; +export type TestTool = 'pytest' | 'unittest'; export type TestRunTelemetry = IEventNamePropertyMapping[EventName.UNITTEST_RUN]; -export type TestDiscoverytTelemetry = IEventNamePropertyMapping[EventName.UNITTEST_DISCOVER]; +export type TestDiscoveryTelemetry = IEventNamePropertyMapping[EventName.UNITTEST_DISCOVERY_DONE]; export type TestConfiguringTelemetry = IEventNamePropertyMapping[EventName.UNITTEST_CONFIGURING]; -export type ImportNotebook = { - scope: 'command'; -}; export const IImportTracker = Symbol('IImportTracker'); export interface IImportTracker {} diff --git a/src/client/tensorBoard/constants.ts b/src/client/tensorBoard/constants.ts new file mode 100644 index 000000000000..aec38eecd95f --- /dev/null +++ b/src/client/tensorBoard/constants.ts @@ -0,0 +1,25 @@ +export enum TensorBoardPromptSelection { + Yes = 'yes', + No = 'no', + DoNotAskAgain = 'doNotAskAgain', + None = 'none', +} + +export enum TensorBoardEntrypointTrigger { + tfeventfiles = 'tfeventfiles', + fileimport = 'fileimport', + nbextension = 'nbextension', + palette = 'palette', +} + +export enum TensorBoardSessionStartResult { + cancel = 'canceled', + success = 'success', + error = 'error', +} + +export enum TensorBoardEntrypoint { + prompt = 'prompt', + codelens = 'codelens', + palette = 'palette', +} diff --git a/src/client/tensorBoard/helpers.ts b/src/client/tensorBoard/helpers.ts new file mode 100644 index 000000000000..8da3ef6a38f2 --- /dev/null +++ b/src/client/tensorBoard/helpers.ts @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// While it is uncommon for users to `import tensorboard`, TensorBoard is frequently +// included as a submodule of other packages, e.g. torch.utils.tensorboard. +// This is a modified version of the regex from src/client/telemetry/importTracker.ts +// in order to match on imported submodules as well, since the original regex only +// matches the 'main' module. + +// RegEx to match `import torch.profiler` or `from torch import profiler` +export const TorchProfilerImportRegEx = /^\s*(?:import (?:(\w+, )*torch\.profiler(, \w+)*))|(?:from torch import (?:(\w+, )*profiler(, \w+)*))/; diff --git a/src/client/tensorBoard/serviceRegistry.ts b/src/client/tensorBoard/serviceRegistry.ts new file mode 100644 index 000000000000..9f53af72053e --- /dev/null +++ b/src/client/tensorBoard/serviceRegistry.ts @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { IServiceManager } from '../ioc/types'; +import { TensorBoardPrompt } from './tensorBoardPrompt'; +import { TensorboardDependencyChecker } from './tensorboardDependencyChecker'; + +export function registerTypes(serviceManager: IServiceManager): void { + serviceManager.addSingleton(TensorBoardPrompt, TensorBoardPrompt); + serviceManager.addSingleton(TensorboardDependencyChecker, TensorboardDependencyChecker); +} diff --git a/src/client/tensorBoard/tensorBoardPrompt.ts b/src/client/tensorBoard/tensorBoardPrompt.ts new file mode 100644 index 000000000000..563419bd4ea6 --- /dev/null +++ b/src/client/tensorBoard/tensorBoardPrompt.ts @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable } from 'inversify'; +import { IPersistentState, IPersistentStateFactory } from '../common/types'; + +enum TensorBoardPromptStateKeys { + ShowNativeTensorBoardPrompt = 'showNativeTensorBoardPrompt', +} + +@injectable() +export class TensorBoardPrompt { + private state: IPersistentState; + + constructor(@inject(IPersistentStateFactory) private persistentStateFactory: IPersistentStateFactory) { + this.state = this.persistentStateFactory.createWorkspacePersistentState( + TensorBoardPromptStateKeys.ShowNativeTensorBoardPrompt, + true, + ); + } + + public isPromptEnabled(): boolean { + return this.state.value; + } +} diff --git a/src/client/tensorBoard/tensorBoardSession.ts b/src/client/tensorBoard/tensorBoardSession.ts new file mode 100644 index 000000000000..b18202810e45 --- /dev/null +++ b/src/client/tensorBoard/tensorBoardSession.ts @@ -0,0 +1,183 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +import { CancellationTokenSource, Uri } from 'vscode'; +import { IApplicationShell, ICommandManager } from '../common/application/types'; +import { createPromiseFromCancellation } from '../common/cancellation'; +import { IInstaller, InstallerResponse, ProductInstallStatus, Product } from '../common/types'; +import { Common, TensorBoard } from '../common/utils/localize'; +import { IInterpreterService } from '../interpreter/contracts'; +import { sendTelemetryEvent } from '../telemetry'; +import { EventName } from '../telemetry/constants'; +import { ImportTracker } from '../telemetry/importTracker'; +import { TensorBoardPromptSelection } from './constants'; +import { ModuleInstallFlags } from '../common/installer/types'; +import { traceError, traceVerbose } from '../logging'; + +const TensorBoardSemVerRequirement = '>= 2.4.1'; +const TorchProfilerSemVerRequirement = '>= 0.2.0'; + +/** + * Manages the lifecycle of a TensorBoard session. + * Specifically, it: + * - ensures the TensorBoard Python package is installed, + * - asks the user for a log directory to start TensorBoard with + * - spawns TensorBoard in a background process which must stay running + * to serve the TensorBoard website + * - frames the TensorBoard website in a VSCode webview + * - shuts down the TensorBoard process when the webview is closed + */ +export class TensorBoardSession { + constructor( + private readonly installer: IInstaller, + private readonly interpreterService: IInterpreterService, + private readonly commandManager: ICommandManager, + private readonly applicationShell: IApplicationShell, + ) {} + + private async promptToInstall( + tensorBoardInstallStatus: ProductInstallStatus, + profilerPluginInstallStatus: ProductInstallStatus, + ) { + sendTelemetryEvent(EventName.TENSORBOARD_INSTALL_PROMPT_SHOWN); + const yes = Common.bannerLabelYes; + const no = Common.bannerLabelNo; + const isUpgrade = tensorBoardInstallStatus === ProductInstallStatus.NeedsUpgrade; + let message; + + if ( + tensorBoardInstallStatus === ProductInstallStatus.Installed && + profilerPluginInstallStatus !== ProductInstallStatus.Installed + ) { + // PyTorch user already has TensorBoard, just ask if they want the profiler plugin + message = TensorBoard.installProfilerPluginPrompt; + } else if (profilerPluginInstallStatus !== ProductInstallStatus.Installed) { + // PyTorch user doesn't have compatible TensorBoard or the profiler plugin + message = TensorBoard.installTensorBoardAndProfilerPluginPrompt; + } else if (isUpgrade) { + // Not a PyTorch user and needs upgrade, don't need to mention profiler plugin + message = TensorBoard.upgradePrompt; + } else { + // Not a PyTorch user and needs install, again don't need to mention profiler plugin + message = TensorBoard.installPrompt; + } + const selection = await this.applicationShell.showErrorMessage(message, ...[yes, no]); + let telemetrySelection = TensorBoardPromptSelection.None; + if (selection === yes) { + telemetrySelection = TensorBoardPromptSelection.Yes; + } else if (selection === no) { + telemetrySelection = TensorBoardPromptSelection.No; + } + sendTelemetryEvent(EventName.TENSORBOARD_INSTALL_PROMPT_SELECTION, undefined, { + selection: telemetrySelection, + operationType: isUpgrade ? 'upgrade' : 'install', + }); + return selection; + } + + // Ensure that the TensorBoard package is installed before we attempt + // to start a TensorBoard session. If the user has a torch import in + // any of their open documents, also try to install the torch-tb-plugin + // package, but don't block if installing that fails. + public async ensurePrerequisitesAreInstalled(resource?: Uri): Promise { + traceVerbose('Ensuring TensorBoard package is installed into active interpreter'); + const interpreter = + (await this.interpreterService.getActiveInterpreter(resource)) || + (await this.commandManager.executeCommand('python.setInterpreter')); + if (!interpreter) { + return false; + } + + // First see what dependencies we're missing + let [tensorboardInstallStatus, profilerPluginInstallStatus] = await Promise.all([ + this.installer.isProductVersionCompatible(Product.tensorboard, TensorBoardSemVerRequirement, interpreter), + this.installer.isProductVersionCompatible( + Product.torchProfilerImportName, + TorchProfilerSemVerRequirement, + interpreter, + ), + ]); + const isTorchUser = ImportTracker.hasModuleImport('torch'); + const needsTensorBoardInstall = tensorboardInstallStatus !== ProductInstallStatus.Installed; + const needsProfilerPluginInstall = profilerPluginInstallStatus !== ProductInstallStatus.Installed; + if ( + // PyTorch user, in profiler install experiment, TensorBoard and profiler plugin already installed + (isTorchUser && !needsTensorBoardInstall && !needsProfilerPluginInstall) || + // Not PyTorch user or not in profiler install experiment, so no need for profiler plugin, + // and TensorBoard is already installed + (!isTorchUser && tensorboardInstallStatus === ProductInstallStatus.Installed) + ) { + return true; + } + + // Ask the user if they want to install packages to start a TensorBoard session + const selection = await this.promptToInstall( + tensorboardInstallStatus, + isTorchUser ? profilerPluginInstallStatus : ProductInstallStatus.Installed, + ); + if (selection !== Common.bannerLabelYes && !needsTensorBoardInstall) { + return true; + } + if (selection !== Common.bannerLabelYes) { + return false; + } + + // User opted to install packages. Figure out which ones we need and install them + const tokenSource = new CancellationTokenSource(); + const installerToken = tokenSource.token; + const cancellationPromise = createPromiseFromCancellation({ + cancelAction: 'resolve', + defaultValue: InstallerResponse.Ignore, + token: installerToken, + }); + const installPromises = []; + // If need to install torch.profiler and it's not already installed, add it to our list of promises + if (needsTensorBoardInstall) { + installPromises.push( + this.installer.install( + Product.tensorboard, + interpreter, + installerToken, + tensorboardInstallStatus === ProductInstallStatus.NeedsUpgrade + ? ModuleInstallFlags.upgrade + : undefined, + ), + ); + } + if (isTorchUser && needsProfilerPluginInstall) { + installPromises.push( + this.installer.install( + Product.torchProfilerInstallName, + interpreter, + installerToken, + profilerPluginInstallStatus === ProductInstallStatus.NeedsUpgrade + ? ModuleInstallFlags.upgrade + : undefined, + ), + ); + } + await Promise.race([...installPromises, cancellationPromise]); + + // Check install status again after installing + [tensorboardInstallStatus, profilerPluginInstallStatus] = await Promise.all([ + this.installer.isProductVersionCompatible(Product.tensorboard, TensorBoardSemVerRequirement, interpreter), + this.installer.isProductVersionCompatible( + Product.torchProfilerImportName, + TorchProfilerSemVerRequirement, + interpreter, + ), + ]); + // Send telemetry regarding results of install + sendTelemetryEvent(EventName.TENSORBOARD_PACKAGE_INSTALL_RESULT, undefined, { + wasTensorBoardAttempted: needsTensorBoardInstall, + wasProfilerPluginAttempted: needsProfilerPluginInstall, + wasTensorBoardInstalled: tensorboardInstallStatus === ProductInstallStatus.Installed, + wasProfilerPluginInstalled: profilerPluginInstallStatus === ProductInstallStatus.Installed, + }); + // Profiler plugin is not required to start TensorBoard. If it failed, note that it failed + // in the log, but report success only based on TensorBoard package install status. + if (isTorchUser && profilerPluginInstallStatus !== ProductInstallStatus.Installed) { + traceError(`Failed to install torch-tb-plugin. Profiler plugin will not appear in TensorBoard session.`); + } + return tensorboardInstallStatus === ProductInstallStatus.Installed; + } +} diff --git a/src/client/tensorBoard/tensorboardDependencyChecker.ts b/src/client/tensorBoard/tensorboardDependencyChecker.ts new file mode 100644 index 000000000000..995344284eec --- /dev/null +++ b/src/client/tensorBoard/tensorboardDependencyChecker.ts @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable } from 'inversify'; +import { Uri } from 'vscode'; +import { IApplicationShell, ICommandManager } from '../common/application/types'; +import { IInstaller } from '../common/types'; +import { IInterpreterService } from '../interpreter/contracts'; +import { TensorBoardSession } from './tensorBoardSession'; + +@injectable() +export class TensorboardDependencyChecker { + constructor( + @inject(IInstaller) private readonly installer: IInstaller, + @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, + @inject(IApplicationShell) private readonly applicationShell: IApplicationShell, + @inject(ICommandManager) private readonly commandManager: ICommandManager, + ) {} + + public async ensureDependenciesAreInstalled(resource?: Uri): Promise { + const newSession = new TensorBoardSession( + this.installer, + this.interpreterService, + this.commandManager, + this.applicationShell, + ); + const result = await newSession.ensurePrerequisitesAreInstalled(resource); + return result; + } +} diff --git a/src/client/tensorBoard/tensorboardIntegration.ts b/src/client/tensorBoard/tensorboardIntegration.ts new file mode 100644 index 000000000000..f3cbad59977b --- /dev/null +++ b/src/client/tensorBoard/tensorboardIntegration.ts @@ -0,0 +1,88 @@ +/* eslint-disable comma-dangle */ + +/* eslint-disable implicit-arrow-linebreak */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable } from 'inversify'; +import { Extension, Uri } from 'vscode'; +import { IWorkspaceService } from '../common/application/types'; +import { TENSORBOARD_EXTENSION_ID } from '../common/constants'; +import { IExtensions, Resource } from '../common/types'; +import { IEnvironmentActivationService } from '../interpreter/activation/types'; +import { TensorBoardPrompt } from './tensorBoardPrompt'; +import { TensorboardDependencyChecker } from './tensorboardDependencyChecker'; + +type PythonApiForTensorboardExtension = { + /** + * Gets activated env vars for the active Python Environment for the given resource. + */ + getActivatedEnvironmentVariables(resource: Resource): Promise; + /** + * Ensures that the dependencies required for TensorBoard are installed in Active Environment for the given resource. + */ + ensureDependenciesAreInstalled(resource?: Uri): Promise; + /** + * Whether to allow displaying tensorboard prompt. + */ + isPromptEnabled(): boolean; +}; + +type TensorboardExtensionApi = { + /** + * Registers python extension specific parts with the tensorboard extension + */ + registerPythonApi(interpreterService: PythonApiForTensorboardExtension): void; +}; + +@injectable() +export class TensorboardExtensionIntegration { + private tensorboardExtension: Extension | undefined; + + constructor( + @inject(IExtensions) private readonly extensions: IExtensions, + @inject(IEnvironmentActivationService) private readonly envActivation: IEnvironmentActivationService, + @inject(IWorkspaceService) private workspaceService: IWorkspaceService, + @inject(TensorboardDependencyChecker) private readonly dependencyChcker: TensorboardDependencyChecker, + @inject(TensorBoardPrompt) private readonly tensorBoardPrompt: TensorBoardPrompt, + ) {} + + public registerApi(tensorboardExtensionApi: TensorboardExtensionApi): TensorboardExtensionApi | undefined { + if (!this.workspaceService.isTrusted) { + this.workspaceService.onDidGrantWorkspaceTrust(() => this.registerApi(tensorboardExtensionApi)); + return undefined; + } + tensorboardExtensionApi.registerPythonApi({ + getActivatedEnvironmentVariables: async (resource: Resource) => + this.envActivation.getActivatedEnvironmentVariables(resource, undefined, true), + ensureDependenciesAreInstalled: async (resource?: Uri): Promise => + this.dependencyChcker.ensureDependenciesAreInstalled(resource), + isPromptEnabled: () => this.tensorBoardPrompt.isPromptEnabled(), + }); + return undefined; + } + + public async integrateWithTensorboardExtension(): Promise { + const api = await this.getExtensionApi(); + if (api) { + this.registerApi(api); + } + } + + private async getExtensionApi(): Promise { + if (!this.tensorboardExtension) { + const extension = this.extensions.getExtension(TENSORBOARD_EXTENSION_ID); + if (!extension) { + return undefined; + } + await extension.activate(); + if (extension.isActive) { + this.tensorboardExtension = extension; + return this.tensorboardExtension.exports; + } + } else { + return this.tensorboardExtension.exports; + } + return undefined; + } +} diff --git a/src/client/terminals/activation.ts b/src/client/terminals/activation.ts index b42d4b3bd4b0..ed26916e3eaa 100644 --- a/src/client/terminals/activation.ts +++ b/src/client/terminals/activation.ts @@ -4,71 +4,67 @@ 'use strict'; import { inject, injectable } from 'inversify'; -import { Terminal } from 'vscode'; -import { IExtensionSingleActivationService } from '../activation/types'; -import { IActiveResourceService, ICommandManager, ITerminalManager } from '../common/application/types'; -import { CODE_RUNNER_EXTENSION_ID } from '../common/constants'; +import { Terminal, Uri } from 'vscode'; +import { IActiveResourceService, ITerminalManager } from '../common/application/types'; import { ITerminalActivator } from '../common/terminal/types'; -import { IDisposable, IDisposableRegistry, IExtensions } from '../common/types'; -import { noop } from '../common/utils/misc'; -import { sendTelemetryEvent } from '../telemetry'; -import { EventName } from '../telemetry/constants'; +import { IDisposable, IDisposableRegistry } from '../common/types'; import { ITerminalAutoActivation } from './types'; - -@injectable() -export class ExtensionActivationForTerminalActivation implements IExtensionSingleActivationService { - constructor( - @inject(ICommandManager) private commands: ICommandManager, - @inject(IExtensions) private extensions: IExtensions, - @inject(IDisposableRegistry) disposables: IDisposable[] - ) { - disposables.push(this.extensions.onDidChange(this.activate.bind(this))); - } - - public async activate(): Promise { - const isInstalled = this.isCodeRunnerInstalled(); - // Hide the play icon if code runner is installed, otherwise display the play icon. - this.commands.executeCommand('setContext', 'python.showPlayIcon', !isInstalled).then(noop, noop); - sendTelemetryEvent(EventName.PLAY_BUTTON_ICON_DISABLED, undefined, { disabled: isInstalled }); - } - - private isCodeRunnerInstalled(): boolean { - const extension = this.extensions.getExtension(CODE_RUNNER_EXTENSION_ID)!; - return extension === undefined ? false : true; - } -} +import { shouldEnvExtHandleActivation } from '../envExt/api.internal'; @injectable() export class TerminalAutoActivation implements ITerminalAutoActivation { private handler?: IDisposable; + + private readonly terminalsNotToAutoActivate = new WeakSet(); + constructor( - @inject(ITerminalManager) private readonly terminalManager: ITerminalManager, + @inject(ITerminalManager) + private readonly terminalManager: ITerminalManager, @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, @inject(ITerminalActivator) private readonly activator: ITerminalActivator, - @inject(IActiveResourceService) private readonly activeResourceService: IActiveResourceService + @inject(IActiveResourceService) + private readonly activeResourceService: IActiveResourceService, ) { disposableRegistry.push(this); } - public dispose() { + + public dispose(): void { if (this.handler) { this.handler.dispose(); this.handler = undefined; } } - public register() { + + public register(): void { if (this.handler) { return; } this.handler = this.terminalManager.onDidOpenTerminal(this.activateTerminal, this); } + + public disableAutoActivation(terminal: Terminal): void { + this.terminalsNotToAutoActivate.add(terminal); + } + private async activateTerminal(terminal: Terminal): Promise { + if (this.terminalsNotToAutoActivate.has(terminal)) { + return; + } + if (shouldEnvExtHandleActivation()) { + return; + } if ('hideFromUser' in terminal.creationOptions && terminal.creationOptions.hideFromUser) { return; } - // If we have just one workspace, then pass that as the resource. - // Until upstream VSC issue is resolved https://github.com/Microsoft/vscode/issues/63052. + + const cwd = + 'cwd' in terminal.creationOptions + ? terminal.creationOptions.cwd + : this.activeResourceService.getActiveResource(); + const resource = typeof cwd === 'string' ? Uri.file(cwd) : cwd; + await this.activator.activateEnvironmentInTerminal(terminal, { - resource: this.activeResourceService.getActiveResource() + resource, }); } } diff --git a/src/client/terminals/codeExecution/codeExecutionManager.ts b/src/client/terminals/codeExecution/codeExecutionManager.ts index 312eef37b180..48165adcd169 100644 --- a/src/client/terminals/codeExecution/codeExecutionManager.ts +++ b/src/client/terminals/codeExecution/codeExecutionManager.ts @@ -3,25 +3,26 @@ 'use strict'; -import { inject, injectable, named } from 'inversify'; -import { Disposable, Event, EventEmitter, Uri } from 'vscode'; - +import { inject, injectable } from 'inversify'; +import { Disposable, EventEmitter, Terminal, Uri } from 'vscode'; +import * as path from 'path'; import { ICommandManager, IDocumentManager } from '../../common/application/types'; import { Commands } from '../../common/constants'; import '../../common/extensions'; -import { traceError } from '../../common/logger'; -import { IFileSystem } from '../../common/platform/types'; -import { - BANNER_NAME_INTERACTIVE_SHIFTENTER, - IDisposableRegistry, - IPythonExtensionBanner, - Resource -} from '../../common/types'; +import { IDisposableRegistry, IConfigurationService, Resource } from '../../common/types'; import { noop } from '../../common/utils/misc'; +import { IInterpreterService } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; +import { traceError, traceVerbose } from '../../logging'; import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; import { ICodeExecutionHelper, ICodeExecutionManager, ICodeExecutionService } from '../../terminals/types'; +import { + CreateEnvironmentCheckKind, + triggerCreateEnvironmentCheckNonBlocking, +} from '../../pythonEnvironments/creation/createEnvironmentTrigger'; +import { ReplType } from '../../repl/types'; +import { runInDedicatedTerminal, runInTerminal, useEnvExtension } from '../../envExt/api.internal'; @injectable() export class CodeExecutionManager implements ICodeExecutionManager { @@ -30,66 +31,155 @@ export class CodeExecutionManager implements ICodeExecutionManager { @inject(ICommandManager) private commandManager: ICommandManager, @inject(IDocumentManager) private documentManager: IDocumentManager, @inject(IDisposableRegistry) private disposableRegistry: Disposable[], - @inject(IFileSystem) private fileSystem: IFileSystem, - @inject(IPythonExtensionBanner) - @named(BANNER_NAME_INTERACTIVE_SHIFTENTER) - private readonly shiftEnterBanner: IPythonExtensionBanner, - @inject(IServiceContainer) private serviceContainer: IServiceContainer + @inject(IConfigurationService) private readonly configSettings: IConfigurationService, + @inject(IServiceContainer) private serviceContainer: IServiceContainer, ) {} - public get onExecutedCode(): Event { - return this.eventEmitter.event; - } - public registerCommands() { - [Commands.Exec_In_Terminal, Commands.Exec_In_Terminal_Icon].forEach((cmd) => { - this.disposableRegistry.push( - this.commandManager.registerCommand( - // tslint:disable-next-line:no-any - cmd as any, - async (file: Resource) => { + [Commands.Exec_In_Terminal, Commands.Exec_In_Terminal_Icon, Commands.Exec_In_Separate_Terminal].forEach( + (cmd) => { + this.disposableRegistry.push( + this.commandManager.registerCommand(cmd as any, async (file: Resource) => { + traceVerbose(`Attempting to run Python file`, file?.fsPath); const trigger = cmd === Commands.Exec_In_Terminal ? 'command' : 'icon'; - await this.executeFileInTerminal(file, trigger).catch((ex) => - traceError('Failed to execute file in terminal', ex) - ); - } - ) - ); - }); + const newTerminalPerFile = cmd === Commands.Exec_In_Separate_Terminal; + + if (useEnvExtension()) { + try { + await this.executeUsingExtension(file, cmd === Commands.Exec_In_Separate_Terminal); + } catch (ex) { + traceError('Failed to execute file in terminal', ex); + } + sendTelemetryEvent(EventName.ENVIRONMENT_CHECK_TRIGGER, undefined, { + trigger: 'run-in-terminal', + }); + sendTelemetryEvent(EventName.EXECUTION_CODE, undefined, { + scope: 'file', + trigger, + newTerminalPerFile, + }); + return; + } + + const interpreterService = this.serviceContainer.get(IInterpreterService); + const interpreter = await interpreterService.getActiveInterpreter(file); + if (!interpreter) { + this.commandManager + .executeCommand(Commands.TriggerEnvironmentSelection, file) + .then(noop, noop); + return; + } + sendTelemetryEvent(EventName.ENVIRONMENT_CHECK_TRIGGER, undefined, { + trigger: 'run-in-terminal', + }); + triggerCreateEnvironmentCheckNonBlocking(CreateEnvironmentCheckKind.File, file); + + await this.executeFileInTerminal(file, trigger, { + newTerminalPerFile, + }) + .then(() => { + if (this.shouldTerminalFocusOnStart(file)) + this.commandManager.executeCommand('workbench.action.terminal.focus'); + }) + .catch((ex) => traceError('Failed to execute file in terminal', ex)); + }), + ); + }, + ); this.disposableRegistry.push( - this.commandManager.registerCommand( - Commands.Exec_Selection_In_Terminal, - this.executeSelectionInTerminal.bind(this) - ) + this.commandManager.registerCommand(Commands.Exec_Selection_In_Terminal as any, async (file: Resource) => { + const interpreterService = this.serviceContainer.get(IInterpreterService); + const interpreter = await interpreterService.getActiveInterpreter(file); + if (!interpreter) { + this.commandManager.executeCommand(Commands.TriggerEnvironmentSelection, file).then(noop, noop); + return; + } + sendTelemetryEvent(EventName.ENVIRONMENT_CHECK_TRIGGER, undefined, { trigger: 'run-selection' }); + triggerCreateEnvironmentCheckNonBlocking(CreateEnvironmentCheckKind.File, file); + await this.executeSelectionInTerminal().then(() => { + if (this.shouldTerminalFocusOnStart(file)) + this.commandManager.executeCommand('workbench.action.terminal.focus'); + }); + }), ); this.disposableRegistry.push( this.commandManager.registerCommand( - Commands.Exec_Selection_In_Django_Shell, - this.executeSelectionInDjangoShell.bind(this) - ) + Commands.Exec_Selection_In_Django_Shell as any, + async (file: Resource) => { + const interpreterService = this.serviceContainer.get(IInterpreterService); + const interpreter = await interpreterService.getActiveInterpreter(file); + if (!interpreter) { + this.commandManager.executeCommand(Commands.TriggerEnvironmentSelection, file).then(noop, noop); + return; + } + sendTelemetryEvent(EventName.ENVIRONMENT_CHECK_TRIGGER, undefined, { trigger: 'run-selection' }); + triggerCreateEnvironmentCheckNonBlocking(CreateEnvironmentCheckKind.File, file); + await this.executeSelectionInDjangoShell().then(() => { + if (this.shouldTerminalFocusOnStart(file)) + this.commandManager.executeCommand('workbench.action.terminal.focus'); + }); + }, + ), ); } - private async executeFileInTerminal(file: Resource, trigger: 'command' | 'icon') { - sendTelemetryEvent(EventName.EXECUTION_CODE, undefined, { scope: 'file', trigger }); + + private async executeUsingExtension(file: Resource, dedicated: boolean): Promise { const codeExecutionHelper = this.serviceContainer.get(ICodeExecutionHelper); file = file instanceof Uri ? file : undefined; - const fileToExecute = file ? file : await codeExecutionHelper.getFileToExecute(); + let fileToExecute = file ? file : await codeExecutionHelper.getFileToExecute(); if (!fileToExecute) { return; } - await codeExecutionHelper.saveFileIfDirty(fileToExecute); - try { - const contents = await this.fileSystem.readFile(fileToExecute.fsPath); - this.eventEmitter.fire(contents); - } catch { - // Ignore any errors that occur for firing this event. It's only used - // for telemetry - noop(); + const fileAfterSave = await codeExecutionHelper.saveFileIfDirty(fileToExecute); + if (fileAfterSave) { + fileToExecute = fileAfterSave; + } + + // Check on setting terminal.executeInFileDir + const pythonSettings = this.configSettings.getSettings(file); + let cwd = pythonSettings.terminal.executeInFileDir ? path.dirname(fileToExecute.fsPath) : undefined; + + // Check on setting terminal.launchArgs + const launchArgs = pythonSettings.terminal.launchArgs; + const totalArgs = [...launchArgs, fileToExecute.fsPath.fileToCommandArgumentForPythonExt()]; + + const show = this.shouldTerminalFocusOnStart(fileToExecute); + let terminal: Terminal | undefined; + if (dedicated) { + terminal = await runInDedicatedTerminal(fileToExecute, totalArgs, cwd, show); + } else { + terminal = await runInTerminal(fileToExecute, totalArgs, cwd, show); + } + + if (terminal) { + terminal.show(); + } + } + + private async executeFileInTerminal( + file: Resource, + trigger: 'command' | 'icon', + options?: { newTerminalPerFile: boolean }, + ): Promise { + sendTelemetryEvent(EventName.EXECUTION_CODE, undefined, { + scope: 'file', + trigger, + newTerminalPerFile: options?.newTerminalPerFile, + }); + const codeExecutionHelper = this.serviceContainer.get(ICodeExecutionHelper); + file = file instanceof Uri ? file : undefined; + let fileToExecute = file ? file : await codeExecutionHelper.getFileToExecute(); + if (!fileToExecute) { + return; + } + const fileAfterSave = await codeExecutionHelper.saveFileIfDirty(fileToExecute); + if (fileAfterSave) { + fileToExecute = fileAfterSave; } const executionService = this.serviceContainer.get(ICodeExecutionService, 'standard'); - await executionService.executeFile(fileToExecute); + await executionService.executeFile(fileToExecute, options); } @captureTelemetry(EventName.EXECUTION_CODE, { scope: 'selection' }, false) @@ -97,8 +187,6 @@ export class CodeExecutionManager implements ICodeExecutionManager { const executionService = this.serviceContainer.get(ICodeExecutionService, 'standard'); await this.executeSelection(executionService); - // Prompt one time to ask if they want to send shift-enter to the Interactive Window - this.shiftEnterBanner.showBanner().ignoreErrors(); } @captureTelemetry(EventName.EXECUTION_DJANGO, { scope: 'selection' }, false) @@ -113,8 +201,16 @@ export class CodeExecutionManager implements ICodeExecutionManager { return; } const codeExecutionHelper = this.serviceContainer.get(ICodeExecutionHelper); - const codeToExecute = await codeExecutionHelper.getSelectedTextToExecute(activeEditor!); - const normalizedCode = await codeExecutionHelper.normalizeLines(codeToExecute!); + const codeToExecute = await codeExecutionHelper.getSelectedTextToExecute(activeEditor); + let wholeFileContent = ''; + if (activeEditor && activeEditor.document) { + wholeFileContent = activeEditor.document.getText(); + } + const normalizedCode = await codeExecutionHelper.normalizeLines( + codeToExecute!, + ReplType.terminal, + wholeFileContent, + ); if (!normalizedCode || normalizedCode.trim().length === 0) { return; } @@ -127,6 +223,10 @@ export class CodeExecutionManager implements ICodeExecutionManager { noop(); } - await executionService.execute(normalizedCode, activeEditor!.document.uri); + await executionService.execute(normalizedCode, activeEditor.document.uri); + } + + private shouldTerminalFocusOnStart(uri: Uri | undefined): boolean { + return this.configSettings.getSettings(uri)?.terminal.focusAfterLaunch; } } diff --git a/src/client/terminals/codeExecution/djangoContext.ts b/src/client/terminals/codeExecution/djangoContext.ts index 2c3de7b79a07..74643084db28 100644 --- a/src/client/terminals/codeExecution/djangoContext.ts +++ b/src/client/terminals/codeExecution/djangoContext.ts @@ -6,8 +6,8 @@ import * as path from 'path'; import { Disposable } from 'vscode'; import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../common/application/types'; import { ContextKey } from '../../common/contextKey'; -import { traceError } from '../../common/logger'; import { IFileSystem } from '../../common/platform/types'; +import { traceError } from '../../logging'; @injectable() export class DjangoContextInitializer implements Disposable { @@ -21,12 +21,12 @@ export class DjangoContextInitializer implements Disposable { private documentManager: IDocumentManager, private workpaceService: IWorkspaceService, private fileSystem: IFileSystem, - commandManager: ICommandManager + commandManager: ICommandManager, ) { this.isDjangoProject = new ContextKey('python.isDjangoProject', commandManager); this.ensureContextStateIsSet().catch((ex) => traceError('Python Extension: ensureState', ex)); this.disposables.push( - this.workpaceService.onDidChangeWorkspaceFolders(() => this.updateContextKeyBasedOnActiveWorkspace()) + this.workpaceService.onDidChangeWorkspaceFolders(() => this.updateContextKeyBasedOnActiveWorkspace()), ); } diff --git a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts index 0e96c13e1690..05a1470b5727 100644 --- a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts +++ b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts @@ -6,11 +6,17 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; import { Disposable, Uri } from 'vscode'; -import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../common/application/types'; +import { + IApplicationShell, + ICommandManager, + IDocumentManager, + IWorkspaceService, +} from '../../common/application/types'; import '../../common/extensions'; import { IFileSystem, IPlatformService } from '../../common/platform/types'; import { ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; +import { IInterpreterService } from '../../interpreter/contracts'; import { copyPythonExecInfo, PythonExecInfo } from '../../pythonEnvironments/exec'; import { DjangoContextInitializer } from './djangoContext'; import { TerminalCodeExecutionProvider } from './terminalCodeExecution'; @@ -25,9 +31,20 @@ export class DjangoShellCodeExecutionProvider extends TerminalCodeExecutionProvi @inject(IPlatformService) platformService: IPlatformService, @inject(ICommandManager) commandManager: ICommandManager, @inject(IFileSystem) fileSystem: IFileSystem, - @inject(IDisposableRegistry) disposableRegistry: Disposable[] + @inject(IDisposableRegistry) disposableRegistry: Disposable[], + @inject(IInterpreterService) interpreterService: IInterpreterService, + @inject(IApplicationShell) applicationShell: IApplicationShell, ) { - super(terminalServiceFactory, configurationService, workspace, disposableRegistry, platformService); + super( + terminalServiceFactory, + configurationService, + workspace, + disposableRegistry, + platformService, + interpreterService, + commandManager, + applicationShell, + ); this.terminalTitle = 'Django Shell'; disposableRegistry.push(new DjangoContextInitializer(documentManager, workspace, fileSystem, commandManager)); } @@ -43,7 +60,7 @@ export class DjangoShellCodeExecutionProvider extends TerminalCodeExecutionProvi const workspaceRoot = workspaceUri ? workspaceUri.uri.fsPath : defaultWorkspace; const managePyPath = workspaceRoot.length === 0 ? 'manage.py' : path.join(workspaceRoot, 'manage.py'); - return copyPythonExecInfo(info, [managePyPath.fileToCommandArgument(), 'shell']); + return copyPythonExecInfo(info, [managePyPath.fileToCommandArgumentForPythonExt(), 'shell']); } public async getExecuteFileArgs(resource?: Uri, executeArgs: string[] = []): Promise { diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 87af541f7547..4efad5ee174e 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -3,30 +3,62 @@ import '../../common/extensions'; import { inject, injectable } from 'inversify'; -import { Range, TextEditor, Uri } from 'vscode'; +import { l10n, Position, Range, TextEditor, Uri } from 'vscode'; -import { IApplicationShell, IDocumentManager } from '../../common/application/types'; +import { + IActiveResourceService, + IApplicationShell, + ICommandManager, + IDocumentManager, + IWorkspaceService, +} from '../../common/application/types'; import { PYTHON_LANGUAGE } from '../../common/constants'; -import { traceError } from '../../common/logger'; import * as internalScripts from '../../common/process/internal/scripts'; import { IProcessServiceFactory } from '../../common/process/types'; +import { createDeferred } from '../../common/utils/async'; import { IInterpreterService } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; import { ICodeExecutionHelper } from '../types'; +import { traceError } from '../../logging'; +import { IConfigurationService, Resource } from '../../common/types'; +import { sendTelemetryEvent } from '../../telemetry'; +import { EventName } from '../../telemetry/constants'; +import { ReplType } from '../../repl/types'; @injectable() export class CodeExecutionHelper implements ICodeExecutionHelper { private readonly documentManager: IDocumentManager; + private readonly applicationShell: IApplicationShell; + private readonly processServiceFactory: IProcessServiceFactory; + private readonly interpreterService: IInterpreterService; - constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { + + private readonly commandManager: ICommandManager; + + private activeResourceService: IActiveResourceService; + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error TS6133: 'configSettings' is declared but its value is never read. + private readonly configSettings: IConfigurationService; + + constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) { this.documentManager = serviceContainer.get(IDocumentManager); this.applicationShell = serviceContainer.get(IApplicationShell); this.processServiceFactory = serviceContainer.get(IProcessServiceFactory); this.interpreterService = serviceContainer.get(IInterpreterService); + this.configSettings = serviceContainer.get(IConfigurationService); + this.commandManager = serviceContainer.get(ICommandManager); + this.activeResourceService = this.serviceContainer.get(IActiveResourceService); } - public async normalizeLines(code: string, resource?: Uri): Promise { + + public async normalizeLines( + code: string, + _replType: ReplType, + wholeFileContent?: string, + resource?: Uri, + ): Promise { try { if (code.trim().length === 0) { return ''; @@ -34,57 +66,260 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { // On windows cr is not handled well by python when passing in/out via stdin/stdout. // So just remove cr from the input. code = code.replace(new RegExp('\\r', 'g'), ''); + + const activeEditor = this.documentManager.activeTextEditor; const interpreter = await this.interpreterService.getActiveInterpreter(resource); const processService = await this.processServiceFactory.create(resource); - const [args, parse] = internalScripts.normalizeForInterpreter(code); - const proc = await processService.exec(interpreter?.path || 'python', args, { throwOnStdErr: true }); - return parse(proc.stdout); + const [args, parse] = internalScripts.normalizeSelection(); + const observable = processService.execObservable(interpreter?.path || 'python', args, { + throwOnStdErr: true, + }); + const normalizeOutput = createDeferred(); + + // Read result from the normalization script from stdout, and resolve the promise when done. + let normalized = ''; + observable.out.subscribe({ + next: (output) => { + if (output.source === 'stdout') { + normalized += output.out; + } + }, + complete: () => { + normalizeOutput.resolve(normalized); + }, + }); + // If there is no explicit selection, we are exeucting 'line' or 'block'. + if (activeEditor?.selection?.isEmpty) { + sendTelemetryEvent(EventName.EXECUTION_CODE, undefined, { scope: 'line' }); + } + // The normalization script expects a serialized JSON object, with the selection under the "code" key. + // We're using a JSON object so that we don't have to worry about encoding, or escaping non-ASCII characters. + const startLineVal = activeEditor?.selection?.start.line ?? 0; + const endLineVal = activeEditor?.selection?.end.line ?? 0; + const emptyHighlightVal = activeEditor?.selection?.isEmpty ?? true; + let smartSendSettingsEnabledVal = true; + let shellIntegrationEnabled = false; + const configuration = this.serviceContainer.get(IConfigurationService); + if (configuration) { + const pythonSettings = configuration.getSettings(this.activeResourceService.getActiveResource()); + smartSendSettingsEnabledVal = pythonSettings.REPL.enableREPLSmartSend; + shellIntegrationEnabled = pythonSettings.terminal.shellIntegration.enabled; + } + + const input = JSON.stringify({ + code, + wholeFileContent, + startLine: startLineVal, + endLine: endLineVal, + emptyHighlight: emptyHighlightVal, + smartSendSettingsEnabled: smartSendSettingsEnabledVal, + }); + observable.proc?.stdin?.write(input); + observable.proc?.stdin?.end(); + + // We expect a serialized JSON object back, with the normalized code under the "normalized" key. + const result = await normalizeOutput.promise; + const object = JSON.parse(result); + + if (activeEditor?.selection && smartSendSettingsEnabledVal && object.normalized !== 'deprecated') { + const lineOffset = object.nextBlockLineno - activeEditor!.selection.start.line - 1; + await this.moveToNextBlock(lineOffset, activeEditor); + } + + // For new _pyrepl for Python3.13+ && !shellIntegration, we need to send code via bracketed paste mode. + if (object.attach_bracket_paste && !shellIntegrationEnabled && _replType === ReplType.terminal) { + let trimmedNormalized = object.normalized.replace(/\n$/, ''); + if (trimmedNormalized.endsWith(':\n')) { + // In case where statement is unfinished via :, truncate so auto-indentation lands nicely. + trimmedNormalized = trimmedNormalized.replace(/\n$/, ''); + } + return `\u001b[200~${trimmedNormalized}\u001b[201~`; + } + + return parse(object.normalized); } catch (ex) { traceError(ex, 'Python: Failed to normalize code for execution in terminal'); return code; } } + /** + * Depending on whether or not user is in experiment for smart send, + * dynamically move the cursor to the next block of code. + * The cursor movement is not moved by one everytime, + * since with the smart selection, the next executable code block + * can be multiple lines away. + * Intended to provide smooth shift+enter user experience + * bringing user's cursor to the next executable block of code when used with smart selection. + */ + // eslint-disable-next-line class-methods-use-this + private async moveToNextBlock(lineOffset: number, activeEditor?: TextEditor): Promise { + if (activeEditor?.selection?.isEmpty) { + await this.commandManager.executeCommand('cursorMove', { + to: 'down', + by: 'line', + value: Number(lineOffset), + }); + await this.commandManager.executeCommand('cursorEnd'); + } + + return Promise.resolve(); + } + public async getFileToExecute(): Promise { - const activeEditor = this.documentManager.activeTextEditor!; + const activeEditor = this.documentManager.activeTextEditor; if (!activeEditor) { - this.applicationShell.showErrorMessage('No open file to run in terminal'); - return; + this.applicationShell.showErrorMessage(l10n.t('No open file to run in terminal')); + return undefined; } if (activeEditor.document.isUntitled) { - this.applicationShell.showErrorMessage('The active file needs to be saved before it can be run'); - return; + this.applicationShell.showErrorMessage(l10n.t('The active file needs to be saved before it can be run')); + return undefined; } if (activeEditor.document.languageId !== PYTHON_LANGUAGE) { - this.applicationShell.showErrorMessage('The active file is not a Python source file'); - return; + this.applicationShell.showErrorMessage(l10n.t('The active file is not a Python source file')); + return undefined; } if (activeEditor.document.isDirty) { await activeEditor.document.save(); } + return activeEditor.document.uri; } + // eslint-disable-next-line class-methods-use-this public async getSelectedTextToExecute(textEditor: TextEditor): Promise { if (!textEditor) { - return; + return undefined; } - const selection = textEditor.selection; + const { selection } = textEditor; let code: string; + if (selection.isEmpty) { code = textEditor.document.lineAt(selection.start.line).text; + } else if (selection.isSingleLine) { + code = getSingleLineSelectionText(textEditor); } else { - const textRange = new Range(selection.start, selection.end); - code = textEditor.document.getText(textRange); + code = getMultiLineSelectionText(textEditor); } + return code; } - public async saveFileIfDirty(file: Uri): Promise { + + public async saveFileIfDirty(file: Uri): Promise { const docs = this.documentManager.textDocuments.filter((d) => d.uri.path === file.path); - if (docs.length === 1 && docs[0].isDirty) { - await docs[0].save(); + if (docs.length === 1 && (docs[0].isDirty || docs[0].isUntitled)) { + const workspaceService = this.serviceContainer.get(IWorkspaceService); + return workspaceService.save(docs[0].uri); } + return undefined; } } + +export function getSingleLineSelectionText(textEditor: TextEditor): string { + const { selection } = textEditor; + const selectionRange = new Range(selection.start, selection.end); + const selectionText = textEditor.document.getText(selectionRange); + const fullLineText = textEditor.document.lineAt(selection.start.line).text; + + if (selectionText.trim() === fullLineText.trim()) { + // This handles the following case: + // if (x): + // print(x) + // ↑------↑ <--- selection range + // + // We should return: + // print(x) + // ↑----------↑ <--- text including the initial white space + return fullLineText; + } + + // This is where part of the line is selected: + // if(isPrime(x) || isFibonacci(x)): + // ↑--------↑ <--- selection range + // + // We should return just the selection: + // isPrime(x) + return selectionText; +} + +export function getMultiLineSelectionText(textEditor: TextEditor): string { + const { selection } = textEditor; + const selectionRange = new Range(selection.start, selection.end); + const selectionText = textEditor.document.getText(selectionRange); + + const fullTextRange = new Range( + new Position(selection.start.line, 0), + new Position(selection.end.line, textEditor.document.lineAt(selection.end.line).text.length), + ); + const fullText = textEditor.document.getText(fullTextRange); + + // This handles case where: + // def calc(m, n): + // ↓<------------------------------- selection start + // print(m) + // print(n) + // ↑<------------------------ selection end + // if (m == 0): + // return n + 1 + // if (m > 0 and n == 0): + // return calc(m - 1 , 1) + // return calc(m - 1, calc(m, n - 1)) + // + // We should return: + // ↓<---------------------------------- From here + // print(m) + // print(n) + // ↑<----------------------- To here + if (selectionText.trim() === fullText.trim()) { + return fullText; + } + + const fullStartLineText = textEditor.document.lineAt(selection.start.line).text; + const selectionFirstLineRange = new Range( + selection.start, + new Position(selection.start.line, fullStartLineText.length), + ); + const selectionFirstLineText = textEditor.document.getText(selectionFirstLineRange); + + // This handles case where: + // def calc(m, n): + // ↓<------------------------------ selection start + // if (m == 0): + // return n + 1 + // ↑<------------------- selection end (notice " + 1" is not selected) + // if (m > 0 and n == 0): + // return calc(m - 1 , 1) + // return calc(m - 1, calc(m, n - 1)) + // + // We should return: + // ↓<---------------------------------- From here + // if (m == 0): + // return n + 1 + // ↑<------------------- To here (notice " + 1" is not selected) + if (selectionFirstLineText.trimLeft() === fullStartLineText.trimLeft()) { + return fullStartLineText + selectionText.substr(selectionFirstLineText.length); + } + + // If you are here then user has selected partial start and partial end lines: + // def calc(m, n): + + // if (m == 0): + // return n + 1 + + // ↓<------------------------------- selection start + // if (m > 0 + // and n == 0): + // ↑<-------------------- selection end + // return calc(m - 1 , 1) + // return calc(m - 1, calc(m, n - 1)) + // + // We should return: + // ↓<---------------------------------- From here + // (m > 0 + // and n == 0) + // ↑<---------------- To here + return selectionText; +} diff --git a/src/client/terminals/codeExecution/repl.ts b/src/client/terminals/codeExecution/repl.ts index f3c620e83b75..bc9a30af1fac 100644 --- a/src/client/terminals/codeExecution/repl.ts +++ b/src/client/terminals/codeExecution/repl.ts @@ -5,10 +5,11 @@ import { inject, injectable } from 'inversify'; import { Disposable } from 'vscode'; -import { IWorkspaceService } from '../../common/application/types'; +import { IApplicationShell, ICommandManager, IWorkspaceService } from '../../common/application/types'; import { IPlatformService } from '../../common/platform/types'; import { ITerminalServiceFactory } from '../../common/terminal/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; +import { IInterpreterService } from '../../interpreter/contracts'; import { TerminalCodeExecutionProvider } from './terminalCodeExecution'; @injectable() @@ -18,9 +19,21 @@ export class ReplProvider extends TerminalCodeExecutionProvider { @inject(IConfigurationService) configurationService: IConfigurationService, @inject(IWorkspaceService) workspace: IWorkspaceService, @inject(IDisposableRegistry) disposableRegistry: Disposable[], - @inject(IPlatformService) platformService: IPlatformService + @inject(IPlatformService) platformService: IPlatformService, + @inject(IInterpreterService) interpreterService: IInterpreterService, + @inject(ICommandManager) commandManager: ICommandManager, + @inject(IApplicationShell) applicationShell: IApplicationShell, ) { - super(terminalServiceFactory, configurationService, workspace, disposableRegistry, platformService); + super( + terminalServiceFactory, + configurationService, + workspace, + disposableRegistry, + platformService, + interpreterService, + commandManager, + applicationShell, + ); this.terminalTitle = 'REPL'; } } diff --git a/src/client/terminals/codeExecution/terminalCodeExecution.ts b/src/client/terminals/codeExecution/terminalCodeExecution.ts index 4c5b8034129d..ea444af4d89e 100644 --- a/src/client/terminals/codeExecution/terminalCodeExecution.ts +++ b/src/client/terminals/codeExecution/terminalCodeExecution.ts @@ -6,63 +6,116 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; import { Disposable, Uri } from 'vscode'; -import { IWorkspaceService } from '../../common/application/types'; +import { IApplicationShell, ICommandManager, IWorkspaceService } from '../../common/application/types'; import '../../common/extensions'; import { IPlatformService } from '../../common/platform/types'; import { ITerminalService, ITerminalServiceFactory } from '../../common/terminal/types'; -import { IConfigurationService, IDisposableRegistry } from '../../common/types'; +import { IConfigurationService, IDisposable, IDisposableRegistry, Resource } from '../../common/types'; +import { Diagnostics, Repl } from '../../common/utils/localize'; +import { showWarningMessage } from '../../common/vscodeApis/windowApis'; +import { IInterpreterService } from '../../interpreter/contracts'; +import { traceInfo } from '../../logging'; import { buildPythonExecInfo, PythonExecInfo } from '../../pythonEnvironments/exec'; import { ICodeExecutionService } from '../../terminals/types'; +import { EventName } from '../../telemetry/constants'; +import { sendTelemetryEvent } from '../../telemetry'; @injectable() export class TerminalCodeExecutionProvider implements ICodeExecutionService { + private hasRanOutsideCurrentDrive = false; protected terminalTitle!: string; - private _terminalService!: ITerminalService; private replActive?: Promise; + constructor( @inject(ITerminalServiceFactory) protected readonly terminalServiceFactory: ITerminalServiceFactory, @inject(IConfigurationService) protected readonly configurationService: IConfigurationService, @inject(IWorkspaceService) protected readonly workspace: IWorkspaceService, @inject(IDisposableRegistry) protected readonly disposables: Disposable[], - @inject(IPlatformService) protected readonly platformService: IPlatformService + @inject(IPlatformService) protected readonly platformService: IPlatformService, + @inject(IInterpreterService) protected readonly interpreterService: IInterpreterService, + @inject(ICommandManager) protected readonly commandManager: ICommandManager, + @inject(IApplicationShell) protected readonly applicationShell: IApplicationShell, ) {} - public async executeFile(file: Uri) { - await this.setCwdForFileExecution(file); - const { command, args } = await this.getExecuteFileArgs(file, [file.fsPath.fileToCommandArgument()]); + public async executeFile(file: Uri, options?: { newTerminalPerFile: boolean }) { + await this.setCwdForFileExecution(file, options); + const { command, args } = await this.getExecuteFileArgs(file, [ + file.fsPath.fileToCommandArgumentForPythonExt(), + ]); - await this.getTerminalService(file).sendCommand(command, args); + await this.getTerminalService(file, options).sendCommand(command, args); } public async execute(code: string, resource?: Uri): Promise { if (!code || code.trim().length === 0) { return; } - - await this.initializeRepl(); - await this.getTerminalService(resource).sendText(code); + await this.initializeRepl(resource); + if (code == 'deprecated') { + // If user is trying to smart send deprecated code show warning + const selection = await showWarningMessage(Diagnostics.invalidSmartSendMessage, Repl.disableSmartSend); + traceInfo(`Selected file contains invalid Python or Deprecated Python 2 code`); + if (selection === Repl.disableSmartSend) { + this.configurationService.updateSetting('REPL.enableREPLSmartSend', false, resource); + } + } else { + await this.getTerminalService(resource).executeCommand(code, true); + } } - public async initializeRepl(resource?: Uri) { - if (this.replActive && (await this.replActive!)) { - await this._terminalService!.show(); + + public async initializeRepl(resource: Resource) { + const terminalService = this.getTerminalService(resource); + if (this.replActive && (await this.replActive)) { + await terminalService.show(); return; } + sendTelemetryEvent(EventName.REPL, undefined, { replType: 'Terminal' }); this.replActive = new Promise(async (resolve) => { const replCommandArgs = await this.getExecutableInfo(resource); - await this.getTerminalService(resource).sendCommand(replCommandArgs.command, replCommandArgs.args); + let listener: IDisposable; + Promise.race([ + new Promise((resolve) => setTimeout(() => resolve(true), 3000)), + new Promise((resolve) => { + let count = 0; + const terminalDataTimeout = setTimeout(() => { + resolve(true); // Fall back for test case scenarios. + }, 3000); + // Watch TerminalData to see if REPL launched. + listener = this.applicationShell.onDidWriteTerminalData((e) => { + for (let i = 0; i < e.data.length; i++) { + if (e.data[i] === '>') { + count++; + if (count === 3) { + clearTimeout(terminalDataTimeout); + resolve(true); + } + } + } + }); + }), + ]).then(() => { + if (listener) { + listener.dispose(); + } + resolve(true); + }); - // Give python repl time to start before we start sending text. - setTimeout(() => resolve(true), 1000); + await terminalService.sendCommand(replCommandArgs.command, replCommandArgs.args); }); + this.disposables.push( + terminalService.onDidCloseTerminal(() => { + this.replActive = undefined; + }), + ); await this.replActive; } public async getExecutableInfo(resource?: Uri, args: string[] = []): Promise { const pythonSettings = this.configurationService.getSettings(resource); - const command = this.platformService.isWindows - ? pythonSettings.pythonPath.replace(/\\/g, '/') - : pythonSettings.pythonPath; + const interpreter = await this.interpreterService.getActiveInterpreter(resource); + const interpreterPath = interpreter?.path ?? pythonSettings.pythonPath; + const command = this.platformService.isWindows ? interpreterPath.replace(/\\/g, '/') : interpreterPath; const launchArgs = pythonSettings.terminal.launchArgs; return buildPythonExecInfo(command, [...launchArgs, ...args]); } @@ -71,26 +124,34 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { public async getExecuteFileArgs(resource?: Uri, executeArgs: string[] = []): Promise { return this.getExecutableInfo(resource, executeArgs); } - private getTerminalService(resource?: Uri): ITerminalService { - if (!this._terminalService) { - this._terminalService = this.terminalServiceFactory.getTerminalService(resource, this.terminalTitle); - this.disposables.push( - this._terminalService.onDidCloseTerminal(() => { - this.replActive = undefined; - }) - ); - } - return this._terminalService; + private getTerminalService(resource: Resource, options?: { newTerminalPerFile: boolean }): ITerminalService { + return this.terminalServiceFactory.getTerminalService({ + resource, + title: this.terminalTitle, + newTerminalPerFile: options?.newTerminalPerFile, + }); } - private async setCwdForFileExecution(file: Uri) { + private async setCwdForFileExecution(file: Uri, options?: { newTerminalPerFile: boolean }) { const pythonSettings = this.configurationService.getSettings(file); if (!pythonSettings.terminal.executeInFileDir) { return; } const fileDirPath = path.dirname(file.fsPath); - const wkspace = this.workspace.getWorkspaceFolder(file); - if ((!wkspace || fileDirPath !== wkspace.uri.fsPath) && fileDirPath.length > 0) { - await this.getTerminalService(file).sendText(`cd ${fileDirPath.fileToCommandArgument()}`); + if (fileDirPath.length > 0) { + if (this.platformService.isWindows && /[a-z]\:/i.test(fileDirPath)) { + const currentDrive = + typeof this.workspace.rootPath === 'string' + ? this.workspace.rootPath.replace(/\:.*/g, '') + : undefined; + const fileDrive = fileDirPath.replace(/\:.*/g, ''); + if (fileDrive !== currentDrive || this.hasRanOutsideCurrentDrive) { + this.hasRanOutsideCurrentDrive = true; + await this.getTerminalService(file).sendText(`${fileDrive}:`); + } + } + await this.getTerminalService(file, options).sendText( + `cd ${fileDirPath.fileToCommandArgumentForPythonExt()}`, + ); } } } diff --git a/src/client/terminals/codeExecution/terminalReplWatcher.ts b/src/client/terminals/codeExecution/terminalReplWatcher.ts new file mode 100644 index 000000000000..951961ab6901 --- /dev/null +++ b/src/client/terminals/codeExecution/terminalReplWatcher.ts @@ -0,0 +1,27 @@ +import { Disposable, TerminalShellExecutionStartEvent } from 'vscode'; +import { onDidStartTerminalShellExecution } from '../../common/vscodeApis/windowApis'; +import { sendTelemetryEvent } from '../../telemetry'; +import { EventName } from '../../telemetry/constants'; + +function checkREPLCommand(command: string): undefined | 'manualTerminal' | `runningScript` { + const lower = command.toLowerCase().trimStart(); + if (lower.startsWith('python') || lower.startsWith('py ')) { + const parts = lower.split(' '); + if (parts.length === 1) { + return 'manualTerminal'; + } + return 'runningScript'; + } + return undefined; +} + +export function registerTriggerForTerminalREPL(disposables: Disposable[]): void { + disposables.push( + onDidStartTerminalShellExecution(async (e: TerminalShellExecutionStartEvent) => { + const replType = checkREPLCommand(e.execution.commandLine.value); + if (e.execution.commandLine.isTrusted && replType) { + sendTelemetryEvent(EventName.REPL, undefined, { replType }); + } + }), + ); +} diff --git a/src/client/terminals/envCollectionActivation/deactivateService.ts b/src/client/terminals/envCollectionActivation/deactivateService.ts new file mode 100644 index 000000000000..0758f3e22311 --- /dev/null +++ b/src/client/terminals/envCollectionActivation/deactivateService.ts @@ -0,0 +1,102 @@ +/* eslint-disable class-methods-use-this */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable } from 'inversify'; +import * as path from 'path'; +import { ITerminalManager } from '../../common/application/types'; +import { pathExists } from '../../common/platform/fs-paths'; +import { _SCRIPTS_DIR } from '../../common/process/internal/scripts/constants'; +import { identifyShellFromShellPath } from '../../common/terminal/shellDetectors/baseShellDetector'; +import { ITerminalHelper, TerminalShellType } from '../../common/terminal/types'; +import { Resource } from '../../common/types'; +import { waitForCondition } from '../../common/utils/async'; +import { cache } from '../../common/utils/decorators'; +import { StopWatch } from '../../common/utils/stopWatch'; +import { IInterpreterService } from '../../interpreter/contracts'; +import { traceVerbose } from '../../logging'; +import { PythonEnvType } from '../../pythonEnvironments/base/info'; +import { ITerminalDeactivateService } from '../types'; + +/** + * This is a list of shells which support shell integration: + * https://code.visualstudio.com/docs/terminal/shell-integration + */ +const ShellIntegrationShells = [ + TerminalShellType.powershell, + TerminalShellType.powershellCore, + TerminalShellType.bash, + TerminalShellType.zsh, + TerminalShellType.fish, +]; + +@injectable() +export class TerminalDeactivateService implements ITerminalDeactivateService { + private readonly envVarScript = path.join(_SCRIPTS_DIR, 'printEnvVariablesToFile.py'); + + constructor( + @inject(ITerminalManager) private readonly terminalManager: ITerminalManager, + @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, + @inject(ITerminalHelper) private readonly terminalHelper: ITerminalHelper, + ) {} + + @cache(-1, true) + public async initializeScriptParams(shell: string): Promise { + const location = this.getLocation(shell); + if (!location) { + return; + } + const shellType = identifyShellFromShellPath(shell); + const terminal = this.terminalManager.createTerminal({ + name: `Python ${shellType} Deactivate`, + shellPath: shell, + hideFromUser: true, + cwd: location, + }); + const globalInterpreters = this.interpreterService.getInterpreters().filter((i) => !i.type); + const outputFile = path.join(location, `envVars.txt`); + const interpreterPath = + globalInterpreters.length > 0 && globalInterpreters[0] ? globalInterpreters[0].path : 'python'; + const checkIfFileHasBeenCreated = () => pathExists(outputFile); + const stopWatch = new StopWatch(); + const command = this.terminalHelper.buildCommandForTerminal(shellType, interpreterPath, [ + this.envVarScript, + outputFile, + ]); + terminal.sendText(command); + await waitForCondition(checkIfFileHasBeenCreated, 30_000, `"${outputFile}" file not created`); + traceVerbose(`Time taken to get env vars using terminal is ${stopWatch.elapsedTime}ms`); + } + + public async getScriptLocation(shell: string, resource: Resource): Promise { + const interpreter = await this.interpreterService.getActiveInterpreter(resource); + if (interpreter?.type !== PythonEnvType.Virtual) { + return undefined; + } + return this.getLocation(shell); + } + + private getLocation(shell: string) { + const shellType = identifyShellFromShellPath(shell); + if (!ShellIntegrationShells.includes(shellType)) { + return undefined; + } + return path.join(_SCRIPTS_DIR, 'deactivate', this.getShellFolderName(shellType)); + } + + private getShellFolderName(shellType: TerminalShellType): string { + switch (shellType) { + case TerminalShellType.powershell: + case TerminalShellType.powershellCore: + return 'powershell'; + case TerminalShellType.fish: + return 'fish'; + case TerminalShellType.zsh: + return 'zsh'; + case TerminalShellType.bash: + return 'bash'; + default: + throw new Error(`Unsupported shell type ${shellType}`); + } + } +} diff --git a/src/client/terminals/envCollectionActivation/indicatorPrompt.ts b/src/client/terminals/envCollectionActivation/indicatorPrompt.ts new file mode 100644 index 000000000000..5701bf78603e --- /dev/null +++ b/src/client/terminals/envCollectionActivation/indicatorPrompt.ts @@ -0,0 +1,116 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable } from 'inversify'; +import { Uri } from 'vscode'; +import * as path from 'path'; +import { IActiveResourceService, IApplicationShell, ITerminalManager } from '../../common/application/types'; +import { + IConfigurationService, + IDisposableRegistry, + IExperimentService, + IPersistentStateFactory, + Resource, +} from '../../common/types'; +import { Common, Interpreters } from '../../common/utils/localize'; +import { IExtensionSingleActivationService } from '../../activation/types'; +import { inTerminalEnvVarExperiment } from '../../common/experiments/helpers'; +import { IInterpreterService } from '../../interpreter/contracts'; +import { PythonEnvironment } from '../../pythonEnvironments/info'; +import { ITerminalEnvVarCollectionService } from '../types'; +import { sleep } from '../../common/utils/async'; +import { isTestExecution } from '../../common/constants'; +import { PythonEnvType } from '../../pythonEnvironments/base/info'; +import { useEnvExtension } from '../../envExt/api.internal'; + +export const terminalEnvCollectionPromptKey = 'TERMINAL_ENV_COLLECTION_PROMPT_KEY'; + +@injectable() +export class TerminalIndicatorPrompt implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; + + constructor( + @inject(IApplicationShell) private readonly appShell: IApplicationShell, + @inject(IPersistentStateFactory) private readonly persistentStateFactory: IPersistentStateFactory, + @inject(ITerminalManager) private readonly terminalManager: ITerminalManager, + @inject(IDisposableRegistry) private readonly disposableRegistry: IDisposableRegistry, + @inject(IActiveResourceService) private readonly activeResourceService: IActiveResourceService, + @inject(ITerminalEnvVarCollectionService) + private readonly terminalEnvVarCollectionService: ITerminalEnvVarCollectionService, + @inject(IConfigurationService) private readonly configurationService: IConfigurationService, + @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, + @inject(IExperimentService) private readonly experimentService: IExperimentService, + ) {} + + public async activate(): Promise { + if (!inTerminalEnvVarExperiment(this.experimentService) || useEnvExtension()) { + return; + } + if (!isTestExecution()) { + // Avoid showing prompt until startup completes. + await sleep(6000); + } + this.disposableRegistry.push( + this.terminalManager.onDidOpenTerminal(async (terminal) => { + const hideFromUser = + 'hideFromUser' in terminal.creationOptions && terminal.creationOptions.hideFromUser; + const strictEnv = 'strictEnv' in terminal.creationOptions && terminal.creationOptions.strictEnv; + if (hideFromUser || strictEnv || terminal.creationOptions.name) { + // Only show this notification for basic terminals created using the '+' button. + return; + } + const cwd = + 'cwd' in terminal.creationOptions && terminal.creationOptions.cwd + ? terminal.creationOptions.cwd + : this.activeResourceService.getActiveResource(); + const resource = typeof cwd === 'string' ? Uri.file(cwd) : cwd; + const settings = this.configurationService.getSettings(resource); + if (!settings.terminal.activateEnvironment) { + return; + } + if (this.terminalEnvVarCollectionService.isTerminalPromptSetCorrectly(resource)) { + // No need to show notification if terminal prompt already indicates when env is activated. + return; + } + await this.notifyUsers(resource); + }), + ); + } + + private async notifyUsers(resource: Resource): Promise { + const notificationPromptEnabled = this.persistentStateFactory.createGlobalPersistentState( + terminalEnvCollectionPromptKey, + true, + ); + if (!notificationPromptEnabled.value) { + return; + } + const prompts = [Common.doNotShowAgain]; + const interpreter = await this.interpreterService.getActiveInterpreter(resource); + if (!interpreter || !interpreter.type) { + return; + } + const terminalPromptName = getPromptName(interpreter); + const environmentType = interpreter.type === PythonEnvType.Conda ? 'Selected conda' : 'Python virtual'; + const selection = await this.appShell.showInformationMessage( + Interpreters.terminalEnvVarCollectionPrompt.format(environmentType, terminalPromptName), + ...prompts, + ); + if (!selection) { + return; + } + if (selection === prompts[0]) { + await notificationPromptEnabled.updateValue(false); + } + } +} + +function getPromptName(interpreter: PythonEnvironment) { + if (interpreter.envName) { + return `"(${interpreter.envName})"`; + } + if (interpreter.envPath) { + return `"(${path.basename(interpreter.envPath)})"`; + } + return 'environment indicator'; +} diff --git a/src/client/terminals/envCollectionActivation/service.ts b/src/client/terminals/envCollectionActivation/service.ts new file mode 100644 index 000000000000..2ce8d5d5d86a --- /dev/null +++ b/src/client/terminals/envCollectionActivation/service.ts @@ -0,0 +1,515 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { inject, injectable } from 'inversify'; +import { + MarkdownString, + WorkspaceFolder, + GlobalEnvironmentVariableCollection, + EnvironmentVariableScope, + EnvironmentVariableMutatorOptions, + ProgressLocation, +} from 'vscode'; +import { pathExists, normCase } from '../../common/platform/fs-paths'; +import { IExtensionActivationService } from '../../activation/types'; +import { IApplicationShell, IApplicationEnvironment, IWorkspaceService } from '../../common/application/types'; +import { inTerminalEnvVarExperiment } from '../../common/experiments/helpers'; +import { IPlatformService } from '../../common/platform/types'; +import { identifyShellFromShellPath } from '../../common/terminal/shellDetectors/baseShellDetector'; +import { + IExtensionContext, + IExperimentService, + Resource, + IDisposableRegistry, + IConfigurationService, + IPathUtils, +} from '../../common/types'; +import { Interpreters } from '../../common/utils/localize'; +import { traceError, traceInfo, traceLog, traceVerbose, traceWarn } from '../../logging'; +import { IInterpreterService } from '../../interpreter/contracts'; +import { defaultShells } from '../../interpreter/activation/service'; +import { IEnvironmentActivationService } from '../../interpreter/activation/types'; +import { EnvironmentType, PythonEnvironment } from '../../pythonEnvironments/info'; +import { getSearchPathEnvVarNames } from '../../common/utils/exec'; +import { EnvironmentVariables, IEnvironmentVariablesProvider } from '../../common/variables/types'; +import { TerminalShellType } from '../../common/terminal/types'; +import { OSType } from '../../common/utils/platform'; + +import { PythonEnvType } from '../../pythonEnvironments/base/info'; +import { + IShellIntegrationDetectionService, + ITerminalDeactivateService, + ITerminalEnvVarCollectionService, +} from '../types'; +import { ProgressService } from '../../common/application/progressService'; +import { useEnvExtension } from '../../envExt/api.internal'; +import { registerPythonStartup } from '../pythonStartup'; + +@injectable() +export class TerminalEnvVarCollectionService implements IExtensionActivationService, ITerminalEnvVarCollectionService { + public readonly supportedWorkspaceTypes = { + untrustedWorkspace: false, + virtualWorkspace: false, + }; + + /** + * Prompts for these shells cannot be set reliably using variables + */ + private noPromptVariableShells = [ + TerminalShellType.powershell, + TerminalShellType.powershellCore, + TerminalShellType.fish, + ]; + + private registeredOnce = false; + + /** + * Carries default environment variables for the currently selected shell. + */ + private processEnvVars: EnvironmentVariables | undefined; + + private readonly progressService: ProgressService; + + private separator: string; + + constructor( + @inject(IPlatformService) private readonly platform: IPlatformService, + @inject(IInterpreterService) private interpreterService: IInterpreterService, + @inject(IExtensionContext) private context: IExtensionContext, + @inject(IApplicationShell) private shell: IApplicationShell, + @inject(IExperimentService) private experimentService: IExperimentService, + @inject(IApplicationEnvironment) private applicationEnvironment: IApplicationEnvironment, + @inject(IDisposableRegistry) private disposables: IDisposableRegistry, + @inject(IEnvironmentActivationService) private environmentActivationService: IEnvironmentActivationService, + @inject(IWorkspaceService) private workspaceService: IWorkspaceService, + @inject(IConfigurationService) private readonly configurationService: IConfigurationService, + @inject(ITerminalDeactivateService) private readonly terminalDeactivateService: ITerminalDeactivateService, + @inject(IPathUtils) private readonly pathUtils: IPathUtils, + @inject(IShellIntegrationDetectionService) + private readonly shellIntegrationDetectionService: IShellIntegrationDetectionService, + @inject(IEnvironmentVariablesProvider) + private readonly environmentVariablesProvider: IEnvironmentVariablesProvider, + ) { + this.separator = platform.osType === OSType.Windows ? ';' : ':'; + this.progressService = new ProgressService(this.shell); + } + + public async activate(resource: Resource): Promise { + try { + if (useEnvExtension()) { + traceVerbose('Ignoring environment variable experiment since env extension is being used'); + this.context.environmentVariableCollection.clear(); + // Needed for shell integration + await registerPythonStartup(this.context); + return; + } + + if (!inTerminalEnvVarExperiment(this.experimentService)) { + this.context.environmentVariableCollection.clear(); + await this.handleMicroVenv(resource); + if (!this.registeredOnce) { + this.interpreterService.onDidChangeInterpreter( + async (r) => { + await this.handleMicroVenv(r); + }, + this, + this.disposables, + ); + this.registeredOnce = true; + } + await registerPythonStartup(this.context); + return; + } + if (!this.registeredOnce) { + this.interpreterService.onDidChangeInterpreter( + async (r) => { + await this._applyCollection(r).ignoreErrors(); + }, + this, + this.disposables, + ); + this.shellIntegrationDetectionService.onDidChangeStatus( + async () => { + traceInfo("Shell integration status changed, can confirm it's working."); + await this._applyCollection(undefined).ignoreErrors(); + }, + this, + this.disposables, + ); + this.environmentVariablesProvider.onDidEnvironmentVariablesChange( + async (r: Resource) => { + await this._applyCollection(r).ignoreErrors(); + }, + this, + this.disposables, + ); + this.applicationEnvironment.onDidChangeShell( + async (shell: string) => { + this.processEnvVars = undefined; + // Pass in the shell where known instead of relying on the application environment, because of bug + // on VSCode: https://github.com/microsoft/vscode/issues/160694 + await this._applyCollection(undefined, shell).ignoreErrors(); + }, + this, + this.disposables, + ); + const { shell } = this.applicationEnvironment; + const isActive = await this.shellIntegrationDetectionService.isWorking(); + const shellType = identifyShellFromShellPath(shell); + if (!isActive && shellType !== TerminalShellType.commandPrompt) { + traceWarn( + `Shell integration may not be active, environment activated may be overridden by the shell.`, + ); + } + this.registeredOnce = true; + } + this._applyCollection(resource).ignoreErrors(); + } catch (ex) { + traceError(`Activating terminal env collection failed`, ex); + } + } + + public async _applyCollection(resource: Resource, shell?: string): Promise { + this.progressService.showProgress({ + location: ProgressLocation.Window, + title: Interpreters.activatingTerminals, + }); + await this._applyCollectionImpl(resource, shell).catch((ex) => { + traceError(`Failed to apply terminal env vars`, shell, ex); + return Promise.reject(ex); // Ensures progress indicator does not disappear in case of errors, so we can catch issues faster. + }); + this.progressService.hideProgress(); + } + + private async _applyCollectionImpl(resource: Resource, shell = this.applicationEnvironment.shell): Promise { + const workspaceFolder = this.getWorkspaceFolder(resource); + const settings = this.configurationService.getSettings(resource); + const envVarCollection = this.getEnvironmentVariableCollection({ workspaceFolder }); + if (useEnvExtension()) { + envVarCollection.clear(); + traceVerbose('Do not activate terminal env vars as env extension is being used'); + return; + } + + if (!settings.terminal.activateEnvironment) { + envVarCollection.clear(); + traceVerbose('Activating environments in terminal is disabled for', resource?.fsPath); + return; + } + const activatedEnv = await this.environmentActivationService.getActivatedEnvironmentVariables( + resource, + undefined, + undefined, + shell, + ); + const env = activatedEnv ? normCaseKeys(activatedEnv) : undefined; + traceVerbose(`Activated environment variables for ${resource?.fsPath}`, env); + if (!env) { + const shellType = identifyShellFromShellPath(shell); + const defaultShell = defaultShells[this.platform.osType]; + if (defaultShell?.shellType !== shellType) { + // Commands to fetch env vars may fail in custom shells due to unknown reasons, in that case + // fallback to default shells as they are known to work better. + await this._applyCollectionImpl(resource, defaultShell?.shell); + return; + } + await this.trackTerminalPrompt(shell, resource, env); + envVarCollection.clear(); + this.processEnvVars = undefined; + return; + } + if (!this.processEnvVars) { + this.processEnvVars = await this.environmentActivationService.getProcessEnvironmentVariables( + resource, + shell, + ); + } + const processEnv = normCaseKeys(this.processEnvVars); + + // PS1 in some cases is a shell variable (not an env variable) so "env" might not contain it, calculate it in that case. + env.PS1 = await this.getPS1(shell, resource, env); + const defaultPrependOptions = await this.getPrependOptions(); + + // Clear any previously set env vars from collection + envVarCollection.clear(); + const deactivate = await this.terminalDeactivateService.getScriptLocation(shell, resource); + Object.keys(env).forEach((key) => { + if (shouldSkip(key)) { + return; + } + let value = env[key]; + const prevValue = processEnv[key]; + if (prevValue !== value) { + if (value !== undefined) { + if (key === 'PS1') { + // We cannot have the full PS1 without executing in terminal, which we do not. Hence prepend it. + traceLog( + `Prepending environment variable ${key} in collection with ${value} ${JSON.stringify( + defaultPrependOptions, + )}`, + ); + envVarCollection.prepend(key, value, defaultPrependOptions); + return; + } + if (key === 'PATH') { + const options = { + applyAtShellIntegration: true, + applyAtProcessCreation: true, + }; + if (processEnv.PATH && env.PATH?.endsWith(processEnv.PATH)) { + // Prefer prepending to PATH instead of replacing it, as we do not want to replace any + // changes to PATH users might have made it in their init scripts (~/.bashrc etc.) + value = env.PATH.slice(0, -processEnv.PATH.length); + if (deactivate) { + value = `${deactivate}${this.separator}${value}`; + } + traceLog( + `Prepending environment variable ${key} in collection with ${value} ${JSON.stringify( + options, + )}`, + ); + envVarCollection.prepend(key, value, options); + } else { + if (!value.endsWith(this.separator)) { + value = value.concat(this.separator); + } + if (deactivate) { + value = `${deactivate}${this.separator}${value}`; + } + traceLog( + `Prepending environment variable ${key} in collection to ${value} ${JSON.stringify( + options, + )}`, + ); + envVarCollection.prepend(key, value, options); + } + return; + } + const options = { + applyAtShellIntegration: true, + applyAtProcessCreation: true, + }; + traceLog( + `Setting environment variable ${key} in collection to ${value} ${JSON.stringify(options)}`, + ); + envVarCollection.replace(key, value, options); + } + } + }); + + const displayPath = this.pathUtils.getDisplayName(settings.pythonPath, workspaceFolder?.uri.fsPath); + const description = new MarkdownString(`${Interpreters.activateTerminalDescription} \`${displayPath}\``); + envVarCollection.description = description; + + await this.trackTerminalPrompt(shell, resource, env); + await this.terminalDeactivateService.initializeScriptParams(shell).catch((ex) => { + traceError(`Failed to initialize deactivate script`, shell, ex); + }); + } + + private isPromptSet = new Map(); + + // eslint-disable-next-line class-methods-use-this + public isTerminalPromptSetCorrectly(resource?: Resource): boolean { + const workspaceFolder = this.getWorkspaceFolder(resource); + return !!this.isPromptSet.get(workspaceFolder?.index); + } + + /** + * Call this once we know terminal prompt is set correctly for terminal owned by this resource. + */ + private terminalPromptIsCorrect(resource: Resource) { + const key = this.getWorkspaceFolder(resource)?.index; + this.isPromptSet.set(key, true); + } + + private terminalPromptIsUnknown(resource: Resource) { + const key = this.getWorkspaceFolder(resource)?.index; + this.isPromptSet.delete(key); + } + + /** + * Tracks whether prompt for terminal was correctly set. + */ + private async trackTerminalPrompt(shell: string, resource: Resource, env: EnvironmentVariables | undefined) { + this.terminalPromptIsUnknown(resource); + if (!env) { + this.terminalPromptIsCorrect(resource); + return; + } + const customShellType = identifyShellFromShellPath(shell); + if (this.noPromptVariableShells.includes(customShellType)) { + return; + } + if (this.platform.osType !== OSType.Windows) { + // These shells are expected to set PS1 variable for terminal prompt for virtual/conda environments. + const interpreter = await this.interpreterService.getActiveInterpreter(resource); + const shouldSetPS1 = shouldPS1BeSet(interpreter?.type, env); + if (shouldSetPS1 && !env.PS1) { + // PS1 should be set but no PS1 was set. + return; + } + const config = await this.shellIntegrationDetectionService.isWorking(); + if (!config) { + traceVerbose('PS1 is not set when shell integration is disabled.'); + return; + } + } + this.terminalPromptIsCorrect(resource); + } + + private async getPS1(shell: string, resource: Resource, env: EnvironmentVariables) { + // PS1 returned by shell is not predictable: #22078 + // Hence calculate it ourselves where possible. Should no longer be needed once #22128 is available. + const customShellType = identifyShellFromShellPath(shell); + if (this.noPromptVariableShells.includes(customShellType)) { + return env.PS1; + } + if (this.platform.osType !== OSType.Windows) { + // These shells are expected to set PS1 variable for terminal prompt for virtual/conda environments. + const interpreter = await this.interpreterService.getActiveInterpreter(resource); + const shouldSetPS1 = shouldPS1BeSet(interpreter?.type, env); + if (shouldSetPS1) { + const prompt = getPromptForEnv(interpreter, env); + if (prompt) { + return prompt; + } + } + } + if (env.PS1) { + // Prefer PS1 set by env vars, as env.PS1 may or may not contain the full PS1: #22056. + return env.PS1; + } + return undefined; + } + + private async handleMicroVenv(resource: Resource) { + try { + const settings = this.configurationService.getSettings(resource); + const workspaceFolder = this.getWorkspaceFolder(resource); + if (useEnvExtension()) { + this.getEnvironmentVariableCollection({ workspaceFolder }).clear(); + traceVerbose('Do not activate microvenv as env extension is being used'); + return; + } + if (!settings.terminal.activateEnvironment) { + this.getEnvironmentVariableCollection({ workspaceFolder }).clear(); + traceVerbose( + 'Do not activate microvenv as activating environments in terminal is disabled for', + resource?.fsPath, + ); + return; + } + const interpreter = await this.interpreterService.getActiveInterpreter(resource); + if (interpreter?.envType === EnvironmentType.Venv) { + const activatePath = path.join(path.dirname(interpreter.path), 'activate'); + if (!(await pathExists(activatePath))) { + const envVarCollection = this.getEnvironmentVariableCollection({ workspaceFolder }); + const pathVarName = getSearchPathEnvVarNames()[0]; + envVarCollection.replace( + 'PATH', + `${path.dirname(interpreter.path)}${path.delimiter}${process.env[pathVarName]}`, + { applyAtShellIntegration: true, applyAtProcessCreation: true }, + ); + return; + } + this.getEnvironmentVariableCollection({ workspaceFolder }).clear(); + } + } catch (ex) { + traceWarn(`Microvenv failed as it is using proposed API which is constantly changing`, ex); + } + } + + private async getPrependOptions(): Promise { + const isActive = await this.shellIntegrationDetectionService.isWorking(); + // Ideally we would want to prepend exactly once, either at shell integration or process creation. + // TODO: Stop prepending altogether once https://github.com/microsoft/vscode/issues/145234 is available. + return isActive + ? { + applyAtShellIntegration: true, + applyAtProcessCreation: false, + } + : { + applyAtShellIntegration: true, // Takes care of false negatives in case manual integration is being used. + applyAtProcessCreation: true, + }; + } + + private getEnvironmentVariableCollection(scope: EnvironmentVariableScope = {}) { + const envVarCollection = this.context.environmentVariableCollection as GlobalEnvironmentVariableCollection; + return envVarCollection.getScoped(scope); + } + + private getWorkspaceFolder(resource: Resource): WorkspaceFolder | undefined { + let workspaceFolder = this.workspaceService.getWorkspaceFolder(resource); + if ( + !workspaceFolder && + Array.isArray(this.workspaceService.workspaceFolders) && + this.workspaceService.workspaceFolders.length > 0 + ) { + [workspaceFolder] = this.workspaceService.workspaceFolders; + } + return workspaceFolder; + } +} + +function shouldPS1BeSet(type: PythonEnvType | undefined, env: EnvironmentVariables): boolean { + if (env.PS1) { + // Activated variables contain PS1, meaning it was supposed to be set. + return true; + } + if (type === PythonEnvType.Virtual) { + const promptDisabledVar = env.VIRTUAL_ENV_DISABLE_PROMPT; + const isPromptDisabled = promptDisabledVar && promptDisabledVar !== undefined; + return !isPromptDisabled; + } + if (type === PythonEnvType.Conda) { + // Instead of checking config value using `conda config --get changeps1`, simply check + // `CONDA_PROMPT_MODIFER` to avoid the cost of launching the conda binary. + const promptEnabledVar = env.CONDA_PROMPT_MODIFIER; + const isPromptEnabled = promptEnabledVar && promptEnabledVar !== ''; + return !!isPromptEnabled; + } + return false; +} + +function shouldSkip(env: string) { + return [ + '_', + 'SHLVL', + // Even though this maybe returned, setting it can result in output encoding errors in terminal. + 'PYTHONUTF8', + // We have deactivate service which takes care of setting it. + '_OLD_VIRTUAL_PATH', + 'PWD', + ].includes(env); +} + +function getPromptForEnv(interpreter: PythonEnvironment | undefined, env: EnvironmentVariables) { + if (!interpreter) { + return undefined; + } + if (interpreter.envName) { + if (interpreter.envName === 'base') { + // If conda base environment is selected, it can lead to "(base)" appearing twice if we return the env name. + return undefined; + } + if (interpreter.type === PythonEnvType.Virtual && env.VIRTUAL_ENV_PROMPT) { + return `${env.VIRTUAL_ENV_PROMPT}`; + } + return `(${interpreter.envName}) `; + } + if (interpreter.envPath) { + return `(${path.basename(interpreter.envPath)}) `; + } + return undefined; +} + +function normCaseKeys(env: EnvironmentVariables): EnvironmentVariables { + const result: EnvironmentVariables = {}; + Object.keys(env).forEach((key) => { + result[normCase(key)] = env[key]; + }); + return result; +} diff --git a/src/client/terminals/envCollectionActivation/shellIntegrationService.ts b/src/client/terminals/envCollectionActivation/shellIntegrationService.ts new file mode 100644 index 000000000000..92bb98029892 --- /dev/null +++ b/src/client/terminals/envCollectionActivation/shellIntegrationService.ts @@ -0,0 +1,166 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { injectable, inject } from 'inversify'; +import { EventEmitter } from 'vscode'; +import { + IApplicationEnvironment, + IApplicationShell, + ITerminalManager, + IWorkspaceService, +} from '../../common/application/types'; +import { identifyShellFromShellPath } from '../../common/terminal/shellDetectors/baseShellDetector'; +import { TerminalShellType } from '../../common/terminal/types'; +import { IDisposableRegistry, IPersistentStateFactory } from '../../common/types'; +import { sleep } from '../../common/utils/async'; +import { traceError, traceVerbose } from '../../logging'; +import { IShellIntegrationDetectionService } from '../types'; +import { isTrusted } from '../../common/vscodeApis/workspaceApis'; + +/** + * This is a list of shells which support shell integration: + * https://code.visualstudio.com/docs/terminal/shell-integration + */ +const ShellIntegrationShells = [ + TerminalShellType.powershell, + TerminalShellType.powershellCore, + TerminalShellType.bash, + TerminalShellType.zsh, + TerminalShellType.fish, +]; + +export enum isShellIntegrationWorking { + key = 'SHELL_INTEGRATION_WORKING_KEY', +} + +@injectable() +export class ShellIntegrationDetectionService implements IShellIntegrationDetectionService { + private isWorkingForShell = new Set(); + + private readonly didChange = new EventEmitter(); + + private isDataWriteEventWorking = true; + + constructor( + @inject(ITerminalManager) private readonly terminalManager: ITerminalManager, + @inject(IApplicationShell) private readonly appShell: IApplicationShell, + @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, + @inject(IPersistentStateFactory) private readonly persistentStateFactory: IPersistentStateFactory, + @inject(IApplicationEnvironment) private readonly appEnvironment: IApplicationEnvironment, + @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry, + ) { + try { + const activeShellType = identifyShellFromShellPath(this.appEnvironment.shell); + const key = getKeyForShell(activeShellType); + const persistedResult = this.persistentStateFactory.createGlobalPersistentState(key); + if (persistedResult.value) { + this.isWorkingForShell.add(activeShellType); + } + this.appShell.onDidWriteTerminalData( + (e) => { + if (e.data.includes('\x1b]633;A\x07') || e.data.includes('\x1b]133;A\x07')) { + let { shell } = this.appEnvironment; + if ('shellPath' in e.terminal.creationOptions && e.terminal.creationOptions.shellPath) { + shell = e.terminal.creationOptions.shellPath; + } + const shellType = identifyShellFromShellPath(shell); + traceVerbose('Received shell integration sequence for', shellType); + const wasWorking = this.isWorkingForShell.has(shellType); + this.isWorkingForShell.add(shellType); + if (!wasWorking) { + // If it wasn't working previously, status has changed. + this.didChange.fire(); + } + } + }, + this, + this.disposables, + ); + this.appEnvironment.onDidChangeShell( + async (shell: string) => { + this.createDummyHiddenTerminal(shell); + }, + this, + this.disposables, + ); + this.createDummyHiddenTerminal(this.appEnvironment.shell); + } catch (ex) { + this.isDataWriteEventWorking = false; + traceError('Unable to check if shell integration is active', ex); + } + const isEnabled = !!this.workspaceService + .getConfiguration('terminal') + .get('integrated.shellIntegration.enabled'); + if (!isEnabled) { + traceVerbose('Shell integration is disabled in user settings.'); + } + } + + public readonly onDidChangeStatus = this.didChange.event; + + public async isWorking(): Promise { + const { shell } = this.appEnvironment; + return this._isWorking(shell).catch((ex) => { + traceError(`Failed to determine if shell supports shell integration`, shell, ex); + return false; + }); + } + + public async _isWorking(shell: string): Promise { + const shellType = identifyShellFromShellPath(shell); + const isSupposedToWork = ShellIntegrationShells.includes(shellType); + if (!isSupposedToWork) { + return false; + } + const key = getKeyForShell(shellType); + const persistedResult = this.persistentStateFactory.createGlobalPersistentState(key); + if (persistedResult.value !== undefined) { + return persistedResult.value; + } + const result = await this.useDataWriteApproach(shellType); + if (result) { + // Once we know that shell integration is working for a shell, persist it so we need not do this check every session. + await persistedResult.updateValue(result); + } + return result; + } + + private async useDataWriteApproach(shellType: TerminalShellType) { + // For now, based on problems with using the command approach, use terminal data write event. + if (!this.isDataWriteEventWorking) { + // Assume shell integration is working, if data write event isn't working. + return true; + } + if (shellType === TerminalShellType.powershell || shellType === TerminalShellType.powershellCore) { + // Due to upstream bug: https://github.com/microsoft/vscode/issues/204616, assume shell integration is working for now. + return true; + } + if (!this.isWorkingForShell.has(shellType)) { + // Maybe data write event has not been processed yet, wait a bit. + await sleep(1000); + } + traceVerbose( + 'Did we determine shell integration to be working for', + shellType, + '?', + this.isWorkingForShell.has(shellType), + ); + return this.isWorkingForShell.has(shellType); + } + + /** + * Creates a dummy terminal so that we are guaranteed a data write event for this shell type. + */ + private createDummyHiddenTerminal(shell: string) { + if (isTrusted()) { + this.terminalManager.createTerminal({ + shellPath: shell, + hideFromUser: true, + }); + } + } +} + +function getKeyForShell(shellType: TerminalShellType) { + return `${isShellIntegrationWorking.key}_${shellType}`; +} diff --git a/src/client/terminals/pythonStartup.ts b/src/client/terminals/pythonStartup.ts new file mode 100644 index 000000000000..b6f68c860b46 --- /dev/null +++ b/src/client/terminals/pythonStartup.ts @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { ExtensionContext, MarkdownString, Uri } from 'vscode'; +import * as path from 'path'; +import { copy, createDirectory, getConfiguration, onDidChangeConfiguration } from '../common/vscodeApis/workspaceApis'; +import { EXTENSION_ROOT_DIR } from '../constants'; +import { Interpreters } from '../common/utils/localize'; + +async function applyPythonStartupSetting(context: ExtensionContext): Promise { + const config = getConfiguration('python'); + const pythonrcSetting = config.get('terminal.shellIntegration.enabled'); + + if (pythonrcSetting) { + const storageUri = context.storageUri || context.globalStorageUri; + try { + await createDirectory(storageUri); + } catch { + // already exists, most likely + } + const destPath = Uri.joinPath(storageUri, 'pythonrc.py'); + const sourcePath = path.join(EXTENSION_ROOT_DIR, 'python_files', 'pythonrc.py'); + await copy(Uri.file(sourcePath), destPath, { overwrite: true }); + context.environmentVariableCollection.replace('PYTHONSTARTUP', destPath.fsPath); + // When shell integration is enabled, we disable PyREPL from cpython. + context.environmentVariableCollection.replace('PYTHON_BASIC_REPL', '1'); + context.environmentVariableCollection.description = new MarkdownString( + Interpreters.shellIntegrationEnvVarCollectionDescription, + ); + } else { + context.environmentVariableCollection.delete('PYTHONSTARTUP'); + context.environmentVariableCollection.delete('PYTHON_BASIC_REPL'); + context.environmentVariableCollection.description = new MarkdownString( + Interpreters.shellIntegrationDisabledEnvVarCollectionDescription, + ); + } +} + +export async function registerPythonStartup(context: ExtensionContext): Promise { + await applyPythonStartupSetting(context); + context.subscriptions.push( + onDidChangeConfiguration(async (e) => { + if (e.affectsConfiguration('python.terminal.shellIntegration.enabled')) { + await applyPythonStartupSetting(context); + } + }), + ); +} diff --git a/src/client/terminals/pythonStartupLinkProvider.ts b/src/client/terminals/pythonStartupLinkProvider.ts new file mode 100644 index 000000000000..aba1270f1412 --- /dev/null +++ b/src/client/terminals/pythonStartupLinkProvider.ts @@ -0,0 +1,50 @@ +/* eslint-disable class-methods-use-this */ +import { + CancellationToken, + Disposable, + ProviderResult, + TerminalLink, + TerminalLinkContext, + TerminalLinkProvider, +} from 'vscode'; +import { executeCommand } from '../common/vscodeApis/commandApis'; +import { registerTerminalLinkProvider } from '../common/vscodeApis/windowApis'; +import { Repl } from '../common/utils/localize'; + +interface CustomTerminalLink extends TerminalLink { + command: string; +} + +export class CustomTerminalLinkProvider implements TerminalLinkProvider { + provideTerminalLinks( + context: TerminalLinkContext, + _token: CancellationToken, + ): ProviderResult { + const links: CustomTerminalLink[] = []; + let expectedNativeLink; + + if (process.platform === 'darwin') { + expectedNativeLink = 'Cmd click to launch VS Code Native REPL'; + } else { + expectedNativeLink = 'Ctrl click to launch VS Code Native REPL'; + } + + if (context.line.includes(expectedNativeLink)) { + links.push({ + startIndex: context.line.indexOf(expectedNativeLink), + length: expectedNativeLink.length, + tooltip: Repl.launchNativeRepl, + command: 'python.startNativeREPL', + }); + } + return links; + } + + async handleTerminalLink(link: CustomTerminalLink): Promise { + await executeCommand(link.command); + } +} + +export function registerCustomTerminalLinkProvider(disposables: Disposable[]): void { + disposables.push(registerTerminalLinkProvider(new CustomTerminalLinkProvider())); +} diff --git a/src/client/terminals/serviceRegistry.ts b/src/client/terminals/serviceRegistry.ts index 619c69eab7c0..e62701dcec0e 100644 --- a/src/client/terminals/serviceRegistry.ts +++ b/src/client/terminals/serviceRegistry.ts @@ -1,26 +1,29 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { interfaces } from 'inversify'; -import { IExtensionSingleActivationService } from '../activation/types'; -import { ClassType } from '../ioc/types'; -import { ExtensionActivationForTerminalActivation, TerminalAutoActivation } from './activation'; +import { IServiceManager } from '../ioc/types'; +import { TerminalAutoActivation } from './activation'; import { CodeExecutionManager } from './codeExecution/codeExecutionManager'; import { DjangoShellCodeExecutionProvider } from './codeExecution/djangoShellCodeExecution'; import { CodeExecutionHelper } from './codeExecution/helper'; import { ReplProvider } from './codeExecution/repl'; import { TerminalCodeExecutionProvider } from './codeExecution/terminalCodeExecution'; -import { ICodeExecutionHelper, ICodeExecutionManager, ICodeExecutionService, ITerminalAutoActivation } from './types'; +import { + ICodeExecutionHelper, + ICodeExecutionManager, + ICodeExecutionService, + IShellIntegrationDetectionService, + ITerminalAutoActivation, + ITerminalDeactivateService, + ITerminalEnvVarCollectionService, +} from './types'; +import { TerminalEnvVarCollectionService } from './envCollectionActivation/service'; +import { IExtensionActivationService, IExtensionSingleActivationService } from '../activation/types'; +import { TerminalIndicatorPrompt } from './envCollectionActivation/indicatorPrompt'; +import { TerminalDeactivateService } from './envCollectionActivation/deactivateService'; +import { ShellIntegrationDetectionService } from './envCollectionActivation/shellIntegrationService'; -interface IServiceRegistry { - addSingleton( - serviceIdentifier: interfaces.ServiceIdentifier, - constructor: ClassType, - name?: string | number | symbol - ): void; -} - -export function registerTypes(serviceManager: IServiceRegistry) { +export function registerTypes(serviceManager: IServiceManager): void { serviceManager.addSingleton(ICodeExecutionHelper, CodeExecutionHelper); serviceManager.addSingleton(ICodeExecutionManager, CodeExecutionManager); @@ -28,19 +31,29 @@ export function registerTypes(serviceManager: IServiceRegistry) { serviceManager.addSingleton( ICodeExecutionService, DjangoShellCodeExecutionProvider, - 'djangoShell' + 'djangoShell', ); serviceManager.addSingleton( ICodeExecutionService, TerminalCodeExecutionProvider, - 'standard' + 'standard', ); serviceManager.addSingleton(ICodeExecutionService, ReplProvider, 'repl'); serviceManager.addSingleton(ITerminalAutoActivation, TerminalAutoActivation); - + serviceManager.addSingleton( + ITerminalEnvVarCollectionService, + TerminalEnvVarCollectionService, + ); + serviceManager.addSingleton(ITerminalDeactivateService, TerminalDeactivateService); serviceManager.addSingleton( IExtensionSingleActivationService, - ExtensionActivationForTerminalActivation + TerminalIndicatorPrompt, + ); + serviceManager.addSingleton( + IShellIntegrationDetectionService, + ShellIntegrationDetectionService, ); + + serviceManager.addBinding(ITerminalEnvVarCollectionService, IExtensionActivationService); } diff --git a/src/client/terminals/types.ts b/src/client/terminals/types.ts index b096153a3db8..1384057c3b7c 100644 --- a/src/client/terminals/types.ts +++ b/src/client/terminals/types.ts @@ -1,34 +1,60 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { Event, TextEditor, Uri } from 'vscode'; -import { IDisposable } from '../common/types'; +import { Event, Terminal, TextEditor, Uri } from 'vscode'; +import { IDisposable, Resource } from '../common/types'; +import { ReplType } from '../repl/types'; export const ICodeExecutionService = Symbol('ICodeExecutionService'); export interface ICodeExecutionService { execute(code: string, resource?: Uri): Promise; - executeFile(file: Uri): Promise; + executeFile(file: Uri, options?: { newTerminalPerFile: boolean }): Promise; initializeRepl(resource?: Uri): Promise; } export const ICodeExecutionHelper = Symbol('ICodeExecutionHelper'); export interface ICodeExecutionHelper { - normalizeLines(code: string): Promise; + normalizeLines(code: string, replType: ReplType, wholeFileContent?: string, resource?: Uri): Promise; getFileToExecute(): Promise; - saveFileIfDirty(file: Uri): Promise; + saveFileIfDirty(file: Uri): Promise; getSelectedTextToExecute(textEditor: TextEditor): Promise; } export const ICodeExecutionManager = Symbol('ICodeExecutionManager'); export interface ICodeExecutionManager { - onExecutedCode: Event; registerCommands(): void; } export const ITerminalAutoActivation = Symbol('ITerminalAutoActivation'); export interface ITerminalAutoActivation extends IDisposable { register(): void; + disableAutoActivation(terminal: Terminal): void; +} + +export const ITerminalEnvVarCollectionService = Symbol('ITerminalEnvVarCollectionService'); +export interface ITerminalEnvVarCollectionService { + /** + * Returns true if we know with high certainity the terminal prompt is set correctly for a particular resource. + */ + isTerminalPromptSetCorrectly(resource?: Resource): boolean; +} + +export const IShellIntegrationDetectionService = Symbol('IShellIntegrationDetectionService'); +export interface IShellIntegrationDetectionService { + onDidChangeStatus: Event; + isWorking(): Promise; +} + +export const ITerminalDeactivateService = Symbol('ITerminalDeactivateService'); +export interface ITerminalDeactivateService { + initializeScriptParams(shell: string): Promise; + getScriptLocation(shell: string, resource: Resource): Promise; +} + +export const IPythonStartupEnvVarService = Symbol('IPythonStartupEnvVarService'); +export interface IPythonStartupEnvVarService { + register(): void; } diff --git a/src/client/testing/codeLenses/main.ts b/src/client/testing/codeLenses/main.ts deleted file mode 100644 index 2179c10d2ec8..000000000000 --- a/src/client/testing/codeLenses/main.ts +++ /dev/null @@ -1,27 +0,0 @@ -import * as vscode from 'vscode'; -import { IServiceContainer } from '../../../client/ioc/types'; -import { PYTHON } from '../../common/constants'; -import { ITestCollectionStorageService } from '../common/types'; -import { TestFileCodeLensProvider } from './testFiles'; - -export function activateCodeLenses( - onDidChange: vscode.EventEmitter, - symbolProvider: vscode.DocumentSymbolProvider, - testCollectionStorage: ITestCollectionStorageService, - serviceContainer: IServiceContainer -): vscode.Disposable { - const disposables: vscode.Disposable[] = []; - const codeLensProvider = new TestFileCodeLensProvider( - onDidChange, - symbolProvider, - testCollectionStorage, - serviceContainer - ); - disposables.push(vscode.languages.registerCodeLensProvider(PYTHON, codeLensProvider)); - - return { - dispose: () => { - disposables.forEach((d) => d.dispose()); - } - }; -} diff --git a/src/client/testing/codeLenses/testFiles.ts b/src/client/testing/codeLenses/testFiles.ts deleted file mode 100644 index 4186e216dc45..000000000000 --- a/src/client/testing/codeLenses/testFiles.ts +++ /dev/null @@ -1,315 +0,0 @@ -'use strict'; - -// tslint:disable:no-object-literal-type-assertion - -import { - CancellationToken, - CancellationTokenSource, - CodeLens, - CodeLensProvider, - DocumentSymbolProvider, - Event, - EventEmitter, - Position, - Range, - SymbolInformation, - SymbolKind, - TextDocument, - Uri -} from 'vscode'; -import { IWorkspaceService } from '../../../client/common/application/types'; -import { IFileSystem } from '../../../client/common/platform/types'; -import { IServiceContainer } from '../../../client/ioc/types'; -import * as constants from '../../common/constants'; -import { CommandSource } from '../common/constants'; -import { - ITestCollectionStorageService, - TestFile, - TestFunction, - TestStatus, - TestsToRun, - TestSuite -} from '../common/types'; - -type FunctionsAndSuites = { - functions: TestFunction[]; - suites: TestSuite[]; -}; - -export class TestFileCodeLensProvider implements CodeLensProvider { - private workspaceService: IWorkspaceService; - private fileSystem: IFileSystem; - // tslint:disable-next-line:variable-name - constructor( - private _onDidChange: EventEmitter, - private symbolProvider: DocumentSymbolProvider, - private testCollectionStorage: ITestCollectionStorageService, - serviceContainer: IServiceContainer - ) { - this.workspaceService = serviceContainer.get(IWorkspaceService); - this.fileSystem = serviceContainer.get(IFileSystem); - } - - get onDidChangeCodeLenses(): Event { - return this._onDidChange.event; - } - - public async provideCodeLenses(document: TextDocument, token: CancellationToken) { - const wkspace = this.workspaceService.getWorkspaceFolder(document.uri); - if (!wkspace) { - return []; - } - const testItems = this.testCollectionStorage.getTests(wkspace.uri); - if (!testItems || testItems.testFiles.length === 0 || testItems.testFunctions.length === 0) { - return []; - } - - const cancelTokenSrc = new CancellationTokenSource(); - token.onCancellationRequested(() => { - cancelTokenSrc.cancel(); - }); - - // Strop trying to build the code lenses if unable to get a list of - // symbols in this file afrer x time. - setTimeout(() => { - if (!cancelTokenSrc.token.isCancellationRequested) { - cancelTokenSrc.cancel(); - } - }, constants.Delays.MaxUnitTestCodeLensDelay); - - return this.getCodeLenses(document, cancelTokenSrc.token, this.symbolProvider); - } - - public resolveCodeLens(codeLens: CodeLens, _token: CancellationToken): CodeLens | Thenable { - codeLens.command = { command: 'python.runtests', title: 'Test' }; - return Promise.resolve(codeLens); - } - - public getTestFileWhichNeedsCodeLens(document: TextDocument): TestFile | undefined { - const wkspace = this.workspaceService.getWorkspaceFolder(document.uri); - if (!wkspace) { - return; - } - const tests = this.testCollectionStorage.getTests(wkspace.uri); - if (!tests) { - return; - } - return tests.testFiles.find((item) => this.fileSystem.arePathsSame(item.fullPath, document.uri.fsPath)); - } - - private async getCodeLenses( - document: TextDocument, - token: CancellationToken, - symbolProvider: DocumentSymbolProvider - ) { - const file = this.getTestFileWhichNeedsCodeLens(document); - if (!file) { - return []; - } - const allFuncsAndSuites = getAllTestSuitesAndFunctionsPerFile(file); - - try { - const symbols = (await symbolProvider.provideDocumentSymbols(document, token)) as SymbolInformation[]; - if (!symbols) { - return []; - } - return symbols - .filter( - (symbol) => - symbol.kind === SymbolKind.Function || - symbol.kind === SymbolKind.Method || - symbol.kind === SymbolKind.Class - ) - .map((symbol) => { - // This is crucial, if the start and end columns are the same then vscode bugs out - // whenever you edit a line (start scrolling magically). - const range = new Range( - symbol.location.range.start, - new Position(symbol.location.range.end.line, symbol.location.range.end.character + 1) - ); - - return this.getCodeLens( - document.uri, - allFuncsAndSuites, - range, - symbol.name, - symbol.kind, - symbol.containerName - ); - }) - .reduce((previous, current) => previous.concat(current), []) - .filter((codeLens) => codeLens !== null); - } catch (reason) { - if (token.isCancellationRequested) { - return []; - } - return Promise.reject(reason); - } - } - - private getCodeLens( - file: Uri, - allFuncsAndSuites: FunctionsAndSuites, - range: Range, - symbolName: string, - symbolKind: SymbolKind, - symbolContainer: string - ): CodeLens[] { - switch (symbolKind) { - case SymbolKind.Function: - case SymbolKind.Method: { - return getFunctionCodeLens(file, allFuncsAndSuites, symbolName, range, symbolContainer); - } - case SymbolKind.Class: { - const cls = allFuncsAndSuites.suites.find((item) => item.name === symbolName); - if (!cls) { - return []; - } - return [ - new CodeLens(range, { - title: getTestStatusIcon(cls.status) + constants.Text.CodeLensRunUnitTest, - command: constants.Commands.Tests_Run, - arguments: [undefined, CommandSource.codelens, file, { testSuite: [cls] }] - }), - new CodeLens(range, { - title: getTestStatusIcon(cls.status) + constants.Text.CodeLensDebugUnitTest, - command: constants.Commands.Tests_Debug, - arguments: [undefined, CommandSource.codelens, file, { testSuite: [cls] }] - }) - ]; - } - default: { - return []; - } - } - } -} - -function getTestStatusIcon(status?: TestStatus): string { - switch (status) { - case TestStatus.Pass: { - return `${constants.Octicons.Test_Pass} `; - } - case TestStatus.Error: { - return `${constants.Octicons.Test_Error} `; - } - case TestStatus.Fail: { - return `${constants.Octicons.Test_Fail} `; - } - case TestStatus.Skipped: { - return `${constants.Octicons.Test_Skip} `; - } - default: { - return ''; - } - } -} - -function getTestStatusIcons(fns: TestFunction[]): string { - const statuses: string[] = []; - let count = fns.filter((fn) => fn.status === TestStatus.Pass).length; - if (count > 0) { - statuses.push(`${constants.Octicons.Test_Pass} ${count}`); - } - count = fns.filter((fn) => fn.status === TestStatus.Skipped).length; - if (count > 0) { - statuses.push(`${constants.Octicons.Test_Skip} ${count}`); - } - count = fns.filter((fn) => fn.status === TestStatus.Fail).length; - if (count > 0) { - statuses.push(`${constants.Octicons.Test_Fail} ${count}`); - } - count = fns.filter((fn) => fn.status === TestStatus.Error).length; - if (count > 0) { - statuses.push(`${constants.Octicons.Test_Error} ${count}`); - } - - return statuses.join(' '); -} -function getFunctionCodeLens( - file: Uri, - functionsAndSuites: FunctionsAndSuites, - symbolName: string, - range: Range, - symbolContainer: string -): CodeLens[] { - let fn: TestFunction | undefined; - if (symbolContainer.length === 0) { - fn = functionsAndSuites.functions.find((func) => func.name === symbolName); - } else { - // Assume single levels for now. - functionsAndSuites.suites - .filter((s) => s.name === symbolContainer) - .forEach((s) => { - const f = s.functions.find((item) => item.name === symbolName); - if (f) { - fn = f; - } - }); - } - - if (fn) { - return [ - new CodeLens(range, { - title: getTestStatusIcon(fn.status) + constants.Text.CodeLensRunUnitTest, - command: constants.Commands.Tests_Run, - arguments: [undefined, CommandSource.codelens, file, { testFunction: [fn] }] - }), - new CodeLens(range, { - title: getTestStatusIcon(fn.status) + constants.Text.CodeLensDebugUnitTest, - command: constants.Commands.Tests_Debug, - arguments: [undefined, CommandSource.codelens, file, { testFunction: [fn] }] - }) - ]; - } - - // Ok, possible we're dealing with parameterized unit tests. - // If we have [ in the name, then this is a parameterized function. - const functions = functionsAndSuites.functions.filter( - (func) => func.name.startsWith(`${symbolName}[`) && func.name.endsWith(']') - ); - if (functions.length === 0) { - return []; - } - - // Find all flattened functions. - return [ - new CodeLens(range, { - title: `${getTestStatusIcons(functions)} ${constants.Text.CodeLensRunUnitTest} (Multiple)`, - command: constants.Commands.Tests_Picker_UI, - arguments: [undefined, CommandSource.codelens, file, functions] - }), - new CodeLens(range, { - title: `${getTestStatusIcons(functions)} ${constants.Text.CodeLensDebugUnitTest} (Multiple)`, - command: constants.Commands.Tests_Picker_UI_Debug, - arguments: [undefined, CommandSource.codelens, file, functions] - }) - ]; -} - -function getAllTestSuitesAndFunctionsPerFile(testFile: TestFile): FunctionsAndSuites { - // tslint:disable-next-line:prefer-type-cast - const all = { functions: [...testFile.functions], suites: [] as TestSuite[] }; - testFile.suites.forEach((suite) => { - all.suites.push(suite); - - const allChildItems = getAllTestSuitesAndFunctions(suite); - all.functions.push(...allChildItems.functions); - all.suites.push(...allChildItems.suites); - }); - return all; -} -function getAllTestSuitesAndFunctions(testSuite: TestSuite): FunctionsAndSuites { - const all: { functions: TestFunction[]; suites: TestSuite[] } = { functions: [], suites: [] }; - testSuite.functions.forEach((fn) => { - all.functions.push(fn); - }); - testSuite.suites.forEach((suite) => { - all.suites.push(suite); - - const allChildItems = getAllTestSuitesAndFunctions(suite); - all.functions.push(...allChildItems.functions); - all.suites.push(...allChildItems.suites); - }); - return all; -} diff --git a/src/client/testing/common/argumentsHelper.ts b/src/client/testing/common/argumentsHelper.ts deleted file mode 100644 index 2633f99ab50c..000000000000 --- a/src/client/testing/common/argumentsHelper.ts +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { injectable } from 'inversify'; -import { traceWarning } from '../../common/logger'; -import { IArgumentsHelper } from '../types'; - -@injectable() -export class ArgumentsHelper implements IArgumentsHelper { - public getOptionValues(args: string[], option: string): string | string[] | undefined { - const values: string[] = []; - let returnNextValue = false; - for (const arg of args) { - if (returnNextValue) { - values.push(arg); - returnNextValue = false; - continue; - } - if (arg.startsWith(`${option}=`)) { - values.push(arg.substring(`${option}=`.length)); - continue; - } - if (arg === option) { - returnNextValue = true; - } - } - switch (values.length) { - case 0: { - return; - } - case 1: { - return values[0]; - } - default: { - return values; - } - } - } - public getPositionalArguments( - args: string[], - optionsWithArguments: string[] = [], - optionsWithoutArguments: string[] = [] - ): string[] { - const nonPositionalIndexes: number[] = []; - args.forEach((arg, index) => { - if (optionsWithoutArguments.indexOf(arg) !== -1) { - nonPositionalIndexes.push(index); - return; - } else if (optionsWithArguments.indexOf(arg) !== -1) { - nonPositionalIndexes.push(index); - // Cuz the next item is the value. - nonPositionalIndexes.push(index + 1); - } else if (optionsWithArguments.findIndex((item) => arg.startsWith(`${item}=`)) !== -1) { - nonPositionalIndexes.push(index); - return; - } else if (arg.startsWith('-')) { - // Ok this is an unknown option, lets treat this as one without values. - traceWarning( - `Unknown command line option passed into args parser for tests '${arg}'. Please report on https://github.com/Microsoft/vscode-python/issues/new` - ); - nonPositionalIndexes.push(index); - return; - } else if (arg.indexOf('=') > 0) { - // Ok this is an unknown option with a value - traceWarning( - `Unknown command line option passed into args parser for tests '${arg}'. Please report on https://github.com/Microsoft/vscode-python/issues/new` - ); - nonPositionalIndexes.push(index); - } - }); - return args.filter((_, index) => nonPositionalIndexes.indexOf(index) === -1); - } - public filterArguments( - args: string[], - optionsWithArguments: string[] = [], - optionsWithoutArguments: string[] = [] - ): string[] { - let ignoreIndex = -1; - return args.filter((arg, index) => { - if (ignoreIndex === index) { - return false; - } - // Options can use willd cards (with trailing '*') - if ( - optionsWithoutArguments.indexOf(arg) >= 0 || - optionsWithoutArguments.filter((option) => option.endsWith('*') && arg.startsWith(option.slice(0, -1))) - .length > 0 - ) { - return false; - } - // Ignore args that match exactly. - if (optionsWithArguments.indexOf(arg) >= 0) { - ignoreIndex = index + 1; - return false; - } - // Ignore args that match exactly with wild cards & do not have inline values. - if (optionsWithArguments.filter((option) => arg.startsWith(`${option}=`)).length > 0) { - return false; - } - // Ignore args that match a wild card (ending with *) and no ineline values. - // Eg. arg='--log-cli-level' and optionsArguments=['--log-*'] - if ( - arg.indexOf('=') === -1 && - optionsWithoutArguments.filter((option) => option.endsWith('*') && arg.startsWith(option.slice(0, -1))) - .length > 0 - ) { - ignoreIndex = index + 1; - return false; - } - // Ignore args that match a wild card (ending with *) and have ineline values. - // Eg. arg='--log-cli-level=XYZ' and optionsArguments=['--log-*'] - if ( - arg.indexOf('=') >= 0 && - optionsWithoutArguments.filter((option) => option.endsWith('*') && arg.startsWith(option.slice(0, -1))) - .length > 0 - ) { - return false; - } - return true; - }); - } -} diff --git a/src/client/testing/common/bufferedTestConfigSettingService.ts b/src/client/testing/common/bufferedTestConfigSettingService.ts new file mode 100644 index 000000000000..35266de38c72 --- /dev/null +++ b/src/client/testing/common/bufferedTestConfigSettingService.ts @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { Uri } from 'vscode'; +import { ITestConfigSettingsService, UnitTestProduct } from './types'; + +export class BufferedTestConfigSettingsService implements ITestConfigSettingsService { + private ops: [string, string | Uri, UnitTestProduct, string[]][]; + + constructor() { + this.ops = []; + } + + public async updateTestArgs(testDirectory: string | Uri, product: UnitTestProduct, args: string[]): Promise { + this.ops.push(['updateTestArgs', testDirectory, product, args]); + return Promise.resolve(); + } + + public async enable(testDirectory: string | Uri, product: UnitTestProduct): Promise { + this.ops.push(['enable', testDirectory, product, []]); + return Promise.resolve(); + } + + public async disable(testDirectory: string | Uri, product: UnitTestProduct): Promise { + this.ops.push(['disable', testDirectory, product, []]); + return Promise.resolve(); + } + + public async apply(cfg: ITestConfigSettingsService): Promise { + const { ops } = this; + this.ops = []; + // Note that earlier ops do not get rolled back if a later + // one fails. + for (const [op, testDir, prod, args] of ops) { + switch (op) { + case 'updateTestArgs': + await cfg.updateTestArgs(testDir, prod, args); + break; + case 'enable': + await cfg.enable(testDir, prod); + break; + case 'disable': + await cfg.disable(testDir, prod); + break; + default: + break; + } + } + return Promise.resolve(); + } + + // eslint-disable-next-line class-methods-use-this + public getTestEnablingSetting(_: UnitTestProduct): string { + throw new Error('Method not implemented.'); + } +} diff --git a/src/client/testing/common/configSettingService.ts b/src/client/testing/common/configSettingService.ts new file mode 100644 index 000000000000..f6cfeee773e5 --- /dev/null +++ b/src/client/testing/common/configSettingService.ts @@ -0,0 +1,77 @@ +import { inject, injectable } from 'inversify'; +import { Uri, WorkspaceConfiguration } from 'vscode'; +import { IWorkspaceService } from '../../common/application/types'; +import { Product } from '../../common/types'; +import { IServiceContainer } from '../../ioc/types'; +import { ITestConfigSettingsService, UnitTestProduct } from './types'; + +@injectable() +export class TestConfigSettingsService implements ITestConfigSettingsService { + private readonly workspaceService: IWorkspaceService; + + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { + this.workspaceService = serviceContainer.get(IWorkspaceService); + } + + public async updateTestArgs(testDirectory: string | Uri, product: UnitTestProduct, args: string[]): Promise { + const setting = this.getTestArgSetting(product); + return this.updateSetting(testDirectory, setting, args); + } + + public async enable(testDirectory: string | Uri, product: UnitTestProduct): Promise { + const setting = this.getTestEnablingSetting(product); + return this.updateSetting(testDirectory, setting, true); + } + + public async disable(testDirectory: string | Uri, product: UnitTestProduct): Promise { + const setting = this.getTestEnablingSetting(product); + return this.updateSetting(testDirectory, setting, false); + } + + // eslint-disable-next-line class-methods-use-this + public getTestEnablingSetting(product: UnitTestProduct): string { + switch (product) { + case Product.unittest: + return 'testing.unittestEnabled'; + case Product.pytest: + return 'testing.pytestEnabled'; + default: + throw new Error('Invalid Test Product'); + } + } + + // eslint-disable-next-line class-methods-use-this + private getTestArgSetting(product: UnitTestProduct): string { + switch (product) { + case Product.unittest: + return 'testing.unittestArgs'; + case Product.pytest: + return 'testing.pytestArgs'; + default: + throw new Error('Invalid Test Product'); + } + } + + private async updateSetting(testDirectory: string | Uri, setting: string, value: unknown) { + let pythonConfig: WorkspaceConfiguration; + const resource = typeof testDirectory === 'string' ? Uri.file(testDirectory) : testDirectory; + const hasWorkspaceFolders = (this.workspaceService.workspaceFolders?.length || 0) > 0; + if (!hasWorkspaceFolders) { + pythonConfig = this.workspaceService.getConfiguration('python'); + } else if (this.workspaceService.workspaceFolders!.length === 1) { + pythonConfig = this.workspaceService.getConfiguration( + 'python', + this.workspaceService.workspaceFolders![0].uri, + ); + } else { + const workspaceFolder = this.workspaceService.getWorkspaceFolder(resource); + if (!workspaceFolder) { + throw new Error(`Test directory does not belong to any workspace (${testDirectory})`); + } + + pythonConfig = this.workspaceService.getConfiguration('python', workspaceFolder.uri); + } + + return pythonConfig.update(setting, value); + } +} diff --git a/src/client/testing/common/constants.ts b/src/client/testing/common/constants.ts index 002b250d58c5..4f41e60c8806 100644 --- a/src/client/testing/common/constants.ts +++ b/src/client/testing/common/constants.ts @@ -1,24 +1,7 @@ import { Product } from '../../common/types'; -import { TestProvider, UnitTestProduct } from './types'; +import { TestProvider } from '../types'; +import { UnitTestProduct } from './types'; -export const CANCELLATION_REASON = 'cancelled_user_request'; -export enum CommandSource { - auto = 'auto', - ui = 'ui', - codelens = 'codelens', - commandPalette = 'commandpalette', - testExplorer = 'testExplorer' -} -export const TEST_OUTPUT_CHANNEL = 'TEST_OUTPUT_CHANNEL'; - -export const UNIT_TEST_PRODUCTS: UnitTestProduct[] = [Product.pytest, Product.unittest, Product.nosetest]; -export const NOSETEST_PROVIDER: TestProvider = 'nosetest'; +export const UNIT_TEST_PRODUCTS: UnitTestProduct[] = [Product.pytest, Product.unittest]; export const PYTEST_PROVIDER: TestProvider = 'pytest'; export const UNITTEST_PROVIDER: TestProvider = 'unittest'; - -export enum Icons { - discovering = 'discovering-tests.svg', - passed = 'status-ok.svg', - failed = 'status-error.svg', - unknown = 'status-unknown.svg' -} diff --git a/src/client/testing/common/debugLauncher.ts b/src/client/testing/common/debugLauncher.ts index 969c02e19eb7..037bfb265088 100644 --- a/src/client/testing/common/debugLauncher.ts +++ b/src/client/testing/common/debugLauncher.ts @@ -1,81 +1,158 @@ import { inject, injectable, named } from 'inversify'; -import { parse } from 'jsonc-parser'; import * as path from 'path'; -import { DebugConfiguration, Uri, WorkspaceFolder } from 'vscode'; -import { IApplicationShell, IDebugService, IWorkspaceService } from '../../common/application/types'; +import { DebugConfiguration, l10n, Uri, WorkspaceFolder, DebugSession, DebugSessionOptions, Disposable } from 'vscode'; +import { IApplicationShell, IDebugService } from '../../common/application/types'; import { EXTENSION_ROOT_DIR } from '../../common/constants'; -import { traceError } from '../../common/logger'; -import { IFileSystem } from '../../common/platform/types'; import * as internalScripts from '../../common/process/internal/scripts'; import { IConfigurationService, IPythonSettings } from '../../common/types'; -import { noop } from '../../common/utils/misc'; -import { DebuggerTypeName } from '../../debugger/constants'; +import { DebuggerTypeName, PythonDebuggerTypeName } from '../../debugger/constants'; import { IDebugConfigurationResolver } from '../../debugger/extension/configuration/types'; -import { LaunchRequestArguments } from '../../debugger/types'; +import { DebugPurpose, LaunchRequestArguments } from '../../debugger/types'; import { IServiceContainer } from '../../ioc/types'; -import { ITestDebugConfig, ITestDebugLauncher, LaunchOptions, TestProvider } from './types'; +import { traceError, traceVerbose } from '../../logging'; +import { TestProvider } from '../types'; +import { ITestDebugLauncher, LaunchOptions } from './types'; +import { getConfigurationsForWorkspace } from '../../debugger/extension/configuration/launch.json/launchJsonReader'; +import { getWorkspaceFolder, getWorkspaceFolders } from '../../common/vscodeApis/workspaceApis'; +import { showErrorMessage } from '../../common/vscodeApis/windowApis'; +import { createDeferred } from '../../common/utils/async'; +import { addPathToPythonpath } from './helpers'; +import * as envExtApi from '../../envExt/api.internal'; + +/** + * Key used to mark debug configurations with a unique session identifier. + * This allows us to track which debug session belongs to which launchDebugger() call + * when multiple debug sessions are launched in parallel. + */ +const TEST_SESSION_MARKER_KEY = '__vscodeTestSessionMarker'; @injectable() export class DebugLauncher implements ITestDebugLauncher { private readonly configService: IConfigurationService; - private readonly workspaceService: IWorkspaceService; - private readonly fs: IFileSystem; + constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer, @inject(IDebugConfigurationResolver) @named('launch') - private readonly launchResolver: IDebugConfigurationResolver + private readonly launchResolver: IDebugConfigurationResolver, ) { this.configService = this.serviceContainer.get(IConfigurationService); - this.workspaceService = this.serviceContainer.get(IWorkspaceService); - this.fs = this.serviceContainer.get(IFileSystem); } - public async launchDebugger(options: LaunchOptions) { - if (options.token && options.token!.isCancellationRequested) { - return; + /** + * Launches a debug session for test execution. + * Handles cancellation, multi-session support via unique markers, and cleanup. + */ + public async launchDebugger( + options: LaunchOptions, + callback?: () => void, + sessionOptions?: DebugSessionOptions, + ): Promise { + const deferred = createDeferred(); + let hasCallbackBeenCalled = false; + + // Collect disposables for cleanup when debugging completes + const disposables: Disposable[] = []; + + // Ensure callback is only invoked once, even if multiple termination paths fire + const callCallbackOnce = () => { + if (!hasCallbackBeenCalled) { + hasCallbackBeenCalled = true; + callback?.(); + } + }; + + // Early exit if already cancelled before we start + if (options.token && options.token.isCancellationRequested) { + callCallbackOnce(); + deferred.resolve(); + return deferred.promise; + } + + // Listen for cancellation from the test run (e.g., user clicks stop in Test Explorer) + // This allows the caller to clean up resources even if the debug session is still running + if (options.token) { + disposables.push( + options.token.onCancellationRequested(() => { + deferred.resolve(); + callCallbackOnce(); + }), + ); } - const workspaceFolder = this.resolveWorkspaceFolder(options.cwd); + const workspaceFolder = DebugLauncher.resolveWorkspaceFolder(options.cwd); const launchArgs = await this.getLaunchArgs( options, workspaceFolder, - this.configService.getSettings(workspaceFolder.uri) + this.configService.getSettings(workspaceFolder.uri), ); const debugManager = this.serviceContainer.get(IDebugService); - return debugManager - .startDebugging(workspaceFolder, launchArgs) - .then(noop, (ex) => traceError('Failed to start debugging tests', ex)); - } - public async readAllDebugConfigs(workspaceFolder: WorkspaceFolder): Promise { - const filename = path.join(workspaceFolder.uri.fsPath, '.vscode', 'launch.json'); - if (!(await this.fs.fileExists(filename))) { - return []; - } + + // Unique marker to identify this session among concurrent debug sessions + const sessionMarker = `test-${Date.now()}-${Math.random().toString(36).slice(2)}`; + launchArgs[TEST_SESSION_MARKER_KEY] = sessionMarker; + + let ourSession: DebugSession | undefined; + + // Capture our specific debug session when it starts by matching the marker. + // This fires for ALL debug sessions, so we filter to only our marker. + disposables.push( + debugManager.onDidStartDebugSession((session) => { + if (session.configuration[TEST_SESSION_MARKER_KEY] === sessionMarker) { + ourSession = session; + traceVerbose(`[test-debug] Debug session started: ${session.name} (${session.id})`); + } + }), + ); + + // Handle debug session termination (user stops debugging, or tests complete). + // Only react to OUR session terminating - other parallel sessions should + // continue running independently. + disposables.push( + debugManager.onDidTerminateDebugSession((session) => { + if (ourSession && session.id === ourSession.id) { + traceVerbose(`[test-debug] Debug session terminated: ${session.name} (${session.id})`); + deferred.resolve(); + callCallbackOnce(); + } + }), + ); + + // Clean up event subscriptions when debugging completes (success, failure, or cancellation) + deferred.promise.finally(() => { + disposables.forEach((d) => d.dispose()); + }); + + // Start the debug session + let started = false; try { - const text = await this.fs.readFile(filename); - const parsed = parse(text, [], { allowTrailingComma: true, disallowComments: false }); - if (!parsed.version || !parsed.configurations || !Array.isArray(parsed.configurations)) { - throw Error('malformed launch.json'); - } - // We do not bother ensuring each item is a DebugConfiguration... - return parsed.configurations; - } catch (exc) { - traceError('could not get debug config', exc); - const appShell = this.serviceContainer.get(IApplicationShell); - await appShell.showErrorMessage('Could not load unit test config from launch.json'); - return []; + started = await debugManager.startDebugging(workspaceFolder, launchArgs, sessionOptions); + } catch (error) { + traceError('Error starting debug session', error); + deferred.reject(error); + callCallbackOnce(); + return deferred.promise; } + if (!started) { + traceError('Failed to start debug session'); + deferred.resolve(); + callCallbackOnce(); + } + + return deferred.promise; } - private resolveWorkspaceFolder(cwd: string): WorkspaceFolder { - if (!this.workspaceService.hasWorkspaceFolders) { + + private static resolveWorkspaceFolder(cwd: string): WorkspaceFolder { + const hasWorkspaceFolders = (getWorkspaceFolders()?.length || 0) > 0; + if (!hasWorkspaceFolders) { throw new Error('Please open a workspace'); } const cwdUri = cwd ? Uri.file(cwd) : undefined; - let workspaceFolder = this.workspaceService.getWorkspaceFolder(cwdUri); + let workspaceFolder = getWorkspaceFolder(cwdUri); if (!workspaceFolder) { - workspaceFolder = this.workspaceService.workspaceFolders![0]; + const [first] = getWorkspaceFolders()!; + workspaceFolder = first; } return workspaceFolder; } @@ -83,50 +160,89 @@ export class DebugLauncher implements ITestDebugLauncher { private async getLaunchArgs( options: LaunchOptions, workspaceFolder: WorkspaceFolder, - configSettings: IPythonSettings + configSettings: IPythonSettings, ): Promise { - let debugConfig = await this.readDebugConfig(workspaceFolder); + let debugConfig = await DebugLauncher.readDebugConfig(workspaceFolder); if (!debugConfig) { debugConfig = { name: 'Debug Unit Test', - type: 'python', + type: 'debugpy', request: 'test', - subProcess: true + subProcess: true, }; } + + // Use project name in debug session name if provided + if (options.project) { + debugConfig.name = `Debug Tests: ${options.project.name}`; + } + if (!debugConfig.rules) { debugConfig.rules = []; } debugConfig.rules.push({ - path: path.join(EXTENSION_ROOT_DIR, 'pythonFiles'), - include: false + path: path.join(EXTENSION_ROOT_DIR, 'python_files'), + include: false, }); - this.applyDefaults(debugConfig!, workspaceFolder, configSettings); + + DebugLauncher.applyDefaults(debugConfig!, workspaceFolder, configSettings, options.cwd); return this.convertConfigToArgs(debugConfig!, workspaceFolder, options); } - private async readDebugConfig(workspaceFolder: WorkspaceFolder): Promise { - const configs = await this.readAllDebugConfigs(workspaceFolder); - for (const cfg of configs) { - if (!cfg.name || cfg.type !== DebuggerTypeName || cfg.request !== 'test') { - continue; + public async readAllDebugConfigs(workspace: WorkspaceFolder): Promise { + try { + const configs = await getConfigurationsForWorkspace(workspace); + return configs; + } catch (exc) { + traceError('could not get debug config', exc); + const appShell = this.serviceContainer.get(IApplicationShell); + await appShell.showErrorMessage( + l10n.t('Could not load unit test config from launch.json as it is missing a field'), + ); + return []; + } + } + + private static async readDebugConfig( + workspaceFolder: WorkspaceFolder, + ): Promise { + try { + const configs = await getConfigurationsForWorkspace(workspaceFolder); + for (const cfg of configs) { + if ( + cfg.name && + (cfg.type === DebuggerTypeName || cfg.type === PythonDebuggerTypeName) && + (cfg.request === 'test' || + (cfg as LaunchRequestArguments).purpose?.includes(DebugPurpose.DebugTest)) + ) { + // Return the first one. + return cfg as LaunchRequestArguments; + } } - // Return the first one. - return cfg as ITestDebugConfig; + return undefined; + } catch (exc) { + traceError('could not get debug config', exc); + await showErrorMessage(l10n.t('Could not load unit test config from launch.json as it is missing a field')); + return undefined; } - return undefined; } - private applyDefaults(cfg: ITestDebugConfig, workspaceFolder: WorkspaceFolder, configSettings: IPythonSettings) { + + private static applyDefaults( + cfg: LaunchRequestArguments, + workspaceFolder: WorkspaceFolder, + configSettings: IPythonSettings, + optionsCwd?: string, + ) { // cfg.pythonPath is handled by LaunchConfigurationResolver. - // Default value of justMyCode is not provided intentionally, for now we derive its value required for launchArgs using debugStdLib - // Have to provide it if and when we remove complete support for debugStdLib if (!cfg.console) { cfg.console = 'internalConsole'; } if (!cfg.cwd) { - cfg.cwd = workspaceFolder.uri.fsPath; + // For project-based testing, use the project's cwd (optionsCwd) if provided. + // Otherwise fall back to settings.testing.cwd or the workspace folder. + cfg.cwd = optionsCwd || configSettings.testing.cwd || workspaceFolder.uri.fsPath; } if (!cfg.env) { cfg.env = {}; @@ -134,7 +250,6 @@ export class DebugLauncher implements ITestDebugLauncher { if (!cfg.envFile) { cfg.envFile = configSettings.envFile; } - if (cfg.stopOnEntry === undefined) { cfg.stopOnEntry = false; } @@ -151,48 +266,96 @@ export class DebugLauncher implements ITestDebugLauncher { } private async convertConfigToArgs( - debugConfig: ITestDebugConfig, + debugConfig: LaunchRequestArguments, workspaceFolder: WorkspaceFolder, - options: LaunchOptions + options: LaunchOptions, ): Promise { const configArgs = debugConfig as LaunchRequestArguments; - - const testArgs = this.fixArgs(options.args, options.testProvider); - const script = this.getTestLauncherScript(options.testProvider); + const testArgs = + options.testProvider === 'unittest' ? options.args.filter((item) => item !== '--debug') : options.args; + const script = DebugLauncher.getTestLauncherScript(options.testProvider); const args = script(testArgs); - configArgs.program = args[0]; + const [program] = args; + configArgs.program = program; + configArgs.args = args.slice(1); // We leave configArgs.request as "test" so it will be sent in telemetry. - const launchArgs = await this.launchResolver.resolveDebugConfiguration( + let launchArgs = await this.launchResolver.resolveDebugConfiguration( workspaceFolder, configArgs, - options.token + options.token, + ); + if (!launchArgs) { + throw Error(`Invalid debug config "${debugConfig.name}"`); + } + launchArgs = await this.launchResolver.resolveDebugConfigurationWithSubstitutedVariables( + workspaceFolder, + launchArgs, + options.token, ); if (!launchArgs) { throw Error(`Invalid debug config "${debugConfig.name}"`); } launchArgs.request = 'launch'; - return launchArgs!; - } - - private fixArgs(args: string[], testProvider: TestProvider): string[] { - if (testProvider === 'unittest') { - return args.filter((item) => item !== '--debug'); + if (options.pytestPort && options.runTestIdsPort) { + launchArgs.env = { + ...launchArgs.env, + TEST_RUN_PIPE: options.pytestPort, + RUN_TEST_IDS_PIPE: options.runTestIdsPort, + }; } else { - return args; + throw Error( + `Missing value for debug setup, both port and uuid need to be defined. port: "${options.pytestPort}" uuid: "${options.pytestUUID}"`, + ); } + + const pluginPath = path.join(EXTENSION_ROOT_DIR, 'python_files'); + // check if PYTHONPATH is already set in the environment variables + if (launchArgs.env) { + const additionalPythonPath = [pluginPath]; + if (launchArgs.cwd) { + additionalPythonPath.push(launchArgs.cwd); + } else if (options.cwd) { + additionalPythonPath.push(options.cwd); + } + // add the plugin path or cwd to PYTHONPATH if it is not already there using the following function + // this function will handle if PYTHONPATH is undefined + addPathToPythonpath(additionalPythonPath, launchArgs.env.PYTHONPATH); + } + + // Clear out purpose so we can detect if the configuration was used to + // run via F5 style debugging. + launchArgs.purpose = []; + + // For project-based execution, get the Python path from the project's environment. + // Fallback: if env API unavailable or fails, LaunchConfigurationResolver already set + // launchArgs.python from the active interpreter, so debugging still works. + if (options.project && envExtApi.useEnvExtension()) { + try { + const pythonEnv = await envExtApi.getEnvironment(options.project.uri); + if (pythonEnv?.execInfo?.run?.executable) { + launchArgs.python = pythonEnv.execInfo.run.executable; + traceVerbose( + `[test-by-project] Debug session using Python path from project: ${launchArgs.python}`, + ); + } + } catch (error) { + traceVerbose(`[test-by-project] Could not get environment for project, using default: ${error}`); + } + } + + return launchArgs; } - private getTestLauncherScript(testProvider: TestProvider) { + private static getTestLauncherScript(testProvider: TestProvider) { switch (testProvider) { case 'unittest': { - return internalScripts.visualstudio_py_testlauncher; + return internalScripts.execution_py_testlauncher; // this is the new way to run unittest execution, debugger } - case 'pytest': - case 'nosetest': { - return internalScripts.testlauncher; + case 'pytest': { + return internalScripts.pytestlauncher; // this is the new way to run pytest execution, debugger } default: { throw new Error(`Unknown test provider '${testProvider}'`); diff --git a/src/client/testing/common/enablementTracker.ts b/src/client/testing/common/enablementTracker.ts deleted file mode 100644 index fe6b319873ec..000000000000 --- a/src/client/testing/common/enablementTracker.ts +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { ConfigurationChangeEvent } from 'vscode'; -import { IExtensionSingleActivationService } from '../../activation/types'; -import { IWorkspaceService } from '../../common/application/types'; -import { IDisposableRegistry, Resource } from '../../common/types'; -import { sendTelemetryEvent } from '../../telemetry'; -import { EventName } from '../../telemetry/constants'; -import { ITestConfigSettingsService } from '../types'; -import { ITestsHelper, TestProvider } from './types'; - -@injectable() -export class EnablementTracker implements IExtensionSingleActivationService { - constructor( - @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, - @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry, - @inject(ITestConfigSettingsService) private readonly testConfig: ITestConfigSettingsService, - @inject(ITestsHelper) private readonly testsHelper: ITestsHelper - ) {} - public async activate(): Promise { - this.disposables.push(this.workspaceService.onDidChangeConfiguration(this.onDidChangeConfiguration, this)); - } - public onDidChangeConfiguration(args: ConfigurationChangeEvent) { - const resourcesToCheck: Resource[] = [undefined]; - if (Array.isArray(this.workspaceService.workspaceFolders)) { - this.workspaceService.workspaceFolders.forEach((item) => resourcesToCheck.push(item.uri)); - } - - const testProviders: TestProvider[] = ['nosetest', 'pytest', 'unittest']; - resourcesToCheck.forEach((resource) => { - const telemetry: Partial> = {}; - testProviders.forEach((item) => { - const product = this.testsHelper.parseProduct(item); - const testingSetting = this.testConfig.getTestEnablingSetting(product); - const settingToCheck = `python.${testingSetting}`; - // If the setting was modified and if its value is true, then track this. - if ( - args.affectsConfiguration(settingToCheck) && - this.workspaceService.getConfiguration('python', resource).get(testingSetting, false) - ) { - telemetry[item] = true; - } - }); - // If anyone of the items have been enabled, then send telemetry. - if (telemetry.nosetest || telemetry.pytest || telemetry.unittest) { - this.sendTelemetry(telemetry); - } - }); - } - public sendTelemetry(telemetry: Partial>) { - sendTelemetryEvent(EventName.UNITTEST_ENABLED, undefined, telemetry); - } -} diff --git a/src/client/testing/common/helpers.ts b/src/client/testing/common/helpers.ts new file mode 100644 index 000000000000..021849277b33 --- /dev/null +++ b/src/client/testing/common/helpers.ts @@ -0,0 +1,37 @@ +import * as path from 'path'; + +/** + * This function normalizes the provided paths and the existing paths in PYTHONPATH, + * adds the provided paths to PYTHONPATH if they're not already present, + * and then returns the updated PYTHONPATH. + * + * @param newPaths - An array of paths to be added to PYTHONPATH + * @param launchPythonPath - The initial PYTHONPATH + * @returns The updated PYTHONPATH + */ +export function addPathToPythonpath(newPaths: string[], launchPythonPath: string | undefined): string { + // Split PYTHONPATH into array of paths if it exists + let paths: string[]; + if (!launchPythonPath) { + paths = []; + } else { + paths = launchPythonPath.split(path.delimiter); + } + + // Normalize each path in the existing PYTHONPATH + paths = paths.map((p) => path.normalize(p)); + + // Normalize each new path and add it to PYTHONPATH if it's not already present + newPaths.forEach((newPath) => { + const normalizedNewPath: string = path.normalize(newPath); + + if (!paths.includes(normalizedNewPath)) { + paths.push(normalizedNewPath); + } + }); + + // Join the paths with ':' to create the updated PYTHONPATH + const updatedPythonPath: string = paths.join(path.delimiter); + + return updatedPythonPath; +} diff --git a/src/client/testing/common/managers/baseTestManager.ts b/src/client/testing/common/managers/baseTestManager.ts deleted file mode 100644 index ded6581f62cf..000000000000 --- a/src/client/testing/common/managers/baseTestManager.ts +++ /dev/null @@ -1,516 +0,0 @@ -import { - CancellationToken, - CancellationTokenSource, - Diagnostic, - DiagnosticCollection, - DiagnosticRelatedInformation, - Disposable, - Event, - EventEmitter, - languages, - OutputChannel, - Uri -} from 'vscode'; -import { ICommandManager, IWorkspaceService } from '../../../common/application/types'; -import '../../../common/extensions'; -import { isNotInstalledError } from '../../../common/helpers'; -import { traceError } from '../../../common/logger'; -import { IFileSystem } from '../../../common/platform/types'; -import { - IConfigurationService, - IDisposableRegistry, - IInstaller, - IOutputChannel, - IPythonSettings, - Product -} from '../../../common/types'; -import { getNamesAndValues } from '../../../common/utils/enum'; -import { noop } from '../../../common/utils/misc'; -import { IServiceContainer } from '../../../ioc/types'; -import { EventName } from '../../../telemetry/constants'; -import { sendTelemetryEvent } from '../../../telemetry/index'; -import { TestDiscoverytTelemetry, TestRunTelemetry } from '../../../telemetry/types'; -import { IPythonTestMessage, ITestDiagnosticService, WorkspaceTestStatus } from '../../types'; -import { copyDesiredTestResults } from '../testUtils'; -import { CANCELLATION_REASON, CommandSource, TEST_OUTPUT_CHANNEL } from './../constants'; -import { - ITestCollectionStorageService, - ITestDiscoveryService, - ITestManager, - ITestResultsService, - ITestsHelper, - ITestsStatusUpdaterService, - TestDiscoveryOptions, - TestProvider, - Tests, - TestStatus, - TestsToRun -} from './../types'; - -enum CancellationTokenType { - testDiscovery, - testRunner -} - -// tslint:disable: member-ordering max-func-body-length - -export abstract class BaseTestManager implements ITestManager { - public diagnosticCollection: DiagnosticCollection; - protected readonly settings: IPythonSettings; - private readonly unitTestDiagnosticService: ITestDiagnosticService; - public abstract get enabled(): boolean; - protected get outputChannel() { - return this._outputChannel; - } - protected get testResultsService() { - return this._testResultsService; - } - private readonly testCollectionStorage: ITestCollectionStorageService; - private readonly _testResultsService: ITestResultsService; - private readonly commandManager: ICommandManager; - private readonly workspaceService: IWorkspaceService; - private readonly _outputChannel: OutputChannel; - protected tests?: Tests; - private _status: TestStatus = TestStatus.Unknown; - private testDiscoveryCancellationTokenSource?: CancellationTokenSource; - private testRunnerCancellationTokenSource?: CancellationTokenSource; - private _installer!: IInstaller; - private readonly testsStatusUpdaterService: ITestsStatusUpdaterService; - private discoverTestsPromise?: Promise; - private readonly _onDidStatusChange = new EventEmitter(); - private get installer(): IInstaller { - if (!this._installer) { - this._installer = this.serviceContainer.get(IInstaller); - } - return this._installer; - } - constructor( - public readonly testProvider: TestProvider, - private readonly product: Product, - public readonly workspaceFolder: Uri, - protected rootDirectory: string, - protected serviceContainer: IServiceContainer - ) { - this.updateStatus(TestStatus.Unknown); - const configService = serviceContainer.get(IConfigurationService); - this.settings = configService.getSettings(this.rootDirectory ? Uri.file(this.rootDirectory) : undefined); - const disposables = serviceContainer.get(IDisposableRegistry); - this._outputChannel = this.serviceContainer.get(IOutputChannel, TEST_OUTPUT_CHANNEL); - this.testCollectionStorage = this.serviceContainer.get( - ITestCollectionStorageService - ); - this._testResultsService = this.serviceContainer.get(ITestResultsService); - this.workspaceService = this.serviceContainer.get(IWorkspaceService); - this.diagnosticCollection = languages.createDiagnosticCollection(this.testProvider); - this.unitTestDiagnosticService = serviceContainer.get(ITestDiagnosticService); - this.testsStatusUpdaterService = serviceContainer.get(ITestsStatusUpdaterService); - this.commandManager = serviceContainer.get(ICommandManager); - disposables.push(this); - } - protected get testDiscoveryCancellationToken(): CancellationToken | undefined { - return this.testDiscoveryCancellationTokenSource ? this.testDiscoveryCancellationTokenSource.token : undefined; - } - protected get testRunnerCancellationToken(): CancellationToken | undefined { - return this.testRunnerCancellationTokenSource ? this.testRunnerCancellationTokenSource.token : undefined; - } - public dispose() { - this.stop(); - } - public get status(): TestStatus { - return this._status; - } - public get onDidStatusChange(): Event { - return this._onDidStatusChange.event; - } - public get workingDirectory(): string { - return this.settings.testing.cwd && this.settings.testing.cwd.length > 0 - ? this.settings.testing.cwd - : this.rootDirectory; - } - public stop() { - if (this.testDiscoveryCancellationTokenSource) { - this.testDiscoveryCancellationTokenSource.cancel(); - } - if (this.testRunnerCancellationTokenSource) { - this.testRunnerCancellationTokenSource.cancel(); - } - } - public reset() { - this.tests = undefined; - this.updateStatus(TestStatus.Unknown); - } - public resetTestResults() { - if (!this.tests) { - return; - } - - this.testResultsService.resetResults(this.tests!); - } - public async discoverTests( - cmdSource: CommandSource, - ignoreCache: boolean = false, - quietMode: boolean = false, - userInitiated: boolean = false, - clearTestStatus: boolean = false - ): Promise { - if (this.discoverTestsPromise) { - return this.discoverTestsPromise; - } - this.discoverTestsPromise = this._discoverTests( - cmdSource, - ignoreCache, - quietMode, - userInitiated, - clearTestStatus - ); - this.discoverTestsPromise - .catch(noop) - .then(() => (this.discoverTestsPromise = undefined)) - .ignoreErrors(); - return this.discoverTestsPromise; - } - private async _discoverTests( - cmdSource: CommandSource, - ignoreCache: boolean = false, - quietMode: boolean = false, - userInitiated: boolean = false, - clearTestStatus: boolean = false - ): Promise { - if (!ignoreCache && this.tests! && this.tests!.testFunctions.length > 0) { - this.updateStatus(TestStatus.Idle); - return Promise.resolve(this.tests!); - } - if (userInitiated) { - this.testsStatusUpdaterService.updateStatusAsDiscovering(this.workspaceFolder, this.tests); - } - this.updateStatus(TestStatus.Discovering); - // If ignoreCache is true, its an indication of the fact that its a user invoked operation. - // Hence we can stop the debugger. - if (userInitiated) { - this.stop(); - } - const telementryProperties: TestDiscoverytTelemetry = { - tool: this.testProvider, - // tslint:disable-next-line:no-any prefer-type-cast - trigger: cmdSource as any, - failed: false - }; - this.commandManager.executeCommand('setContext', 'testsDiscovered', true).then(noop, noop); - this.createCancellationToken(CancellationTokenType.testDiscovery); - const discoveryOptions = this.getDiscoveryOptions(ignoreCache); - const discoveryService = this.serviceContainer.get( - ITestDiscoveryService, - this.testProvider - ); - return discoveryService - .discoverTests(discoveryOptions) - .then((tests) => { - const wkspace = this.workspaceService.getWorkspaceFolder(Uri.file(this.rootDirectory))!.uri; - const existingTests = this.testCollectionStorage.getTests(wkspace)!; - if (clearTestStatus) { - this.resetTestResults(); - } else if (existingTests) { - copyDesiredTestResults(existingTests, tests); - this._testResultsService.updateResults(tests); - } - this.testCollectionStorage.storeTests(wkspace, tests); - this.tests = tests; - this.updateStatus(TestStatus.Idle); - this.discoverTestsPromise = undefined; - - // have errors in Discovering - let haveErrorsInDiscovering = false; - tests.testFiles.forEach((file) => { - if (file.errorsWhenDiscovering && file.errorsWhenDiscovering.length > 0) { - haveErrorsInDiscovering = true; - this.outputChannel.append('_'.repeat(10)); - this.outputChannel.append(`There was an error in identifying unit tests in ${file.nameToRun}`); - this.outputChannel.appendLine('_'.repeat(10)); - this.outputChannel.appendLine(file.errorsWhenDiscovering); - } - }); - if (haveErrorsInDiscovering && !quietMode) { - const testsHelper = this.serviceContainer.get(ITestsHelper); - testsHelper.displayTestErrorMessage('There were some errors in discovering unit tests'); - } - this.disposeCancellationToken(CancellationTokenType.testDiscovery); - sendTelemetryEvent(EventName.UNITTEST_DISCOVER, undefined, telementryProperties); - return tests; - }) - .catch(async (reason: {}) => { - if (userInitiated) { - this.testsStatusUpdaterService.updateStatusAsUnknown(this.workspaceFolder, this.tests); - } - if ( - isNotInstalledError(reason as Error) && - !quietMode && - !(await this.installer.isInstalled(this.product, this.workspaceFolder)) - ) { - this.installer - .promptToInstall(this.product, this.workspaceFolder) - .catch((ex) => traceError('isNotInstalledError', ex)); - } - - this.tests = undefined; - this.discoverTestsPromise = undefined; - if ( - this.testDiscoveryCancellationToken && - this.testDiscoveryCancellationToken.isCancellationRequested - ) { - reason = CANCELLATION_REASON; - this.updateStatus(TestStatus.Idle); - } else { - telementryProperties.failed = true; - sendTelemetryEvent(EventName.UNITTEST_DISCOVER, undefined, telementryProperties); - this.updateStatus(TestStatus.Error); - this.outputChannel.appendLine('Test Discovery failed: '); - this.outputChannel.appendLine(reason.toString()); - } - const wkspace = this.workspaceService.getWorkspaceFolder(Uri.file(this.rootDirectory))!.uri; - this.testCollectionStorage.storeTests(wkspace, undefined); - this.disposeCancellationToken(CancellationTokenType.testDiscovery); - return Promise.reject(reason); - }); - } - public async runTest( - cmdSource: CommandSource, - testsToRun?: TestsToRun, - runFailedTests?: boolean, - debug?: boolean - ): Promise { - const moreInfo = { - Test_Provider: this.testProvider, - Run_Failed_Tests: 'false', - Run_Specific_File: 'false', - Run_Specific_Class: 'false', - Run_Specific_Function: 'false' - }; - //Ensure valid values are sent. - const validCmdSourceValues = getNamesAndValues(CommandSource).map((item) => item.value); - const telementryProperties: TestRunTelemetry = { - tool: this.testProvider, - scope: 'all', - debugging: debug === true, - triggerSource: validCmdSourceValues.indexOf(cmdSource) === -1 ? 'commandpalette' : cmdSource, - failed: false - }; - - if (!runFailedTests && !testsToRun) { - this.testsStatusUpdaterService.updateStatusAsRunning(this.workspaceFolder, this.tests); - } - - this.updateStatus(TestStatus.Running); - if (this.testRunnerCancellationTokenSource) { - this.testRunnerCancellationTokenSource.cancel(); - } - - if (runFailedTests === true) { - moreInfo.Run_Failed_Tests = runFailedTests.toString(); - telementryProperties.scope = 'failed'; - this.testsStatusUpdaterService.updateStatusAsRunningFailedTests(this.workspaceFolder, this.tests); - } - if (testsToRun && typeof testsToRun === 'object') { - if (Array.isArray(testsToRun.testFile) && testsToRun.testFile.length > 0) { - telementryProperties.scope = 'file'; - moreInfo.Run_Specific_File = 'true'; - } - if (Array.isArray(testsToRun.testSuite) && testsToRun.testSuite.length > 0) { - telementryProperties.scope = 'class'; - moreInfo.Run_Specific_Class = 'true'; - } - if (Array.isArray(testsToRun.testFunction) && testsToRun.testFunction.length > 0) { - telementryProperties.scope = 'function'; - moreInfo.Run_Specific_Function = 'true'; - } - this.testsStatusUpdaterService.updateStatusAsRunningSpecificTests( - this.workspaceFolder, - testsToRun, - this.tests - ); - } - - this.testsStatusUpdaterService.triggerUpdatesToTests(this.workspaceFolder, this.tests); - // If running failed tests, then don't clear the previously build UnitTests - // If we do so, then we end up re-discovering the unit tests and clearing previously cached list of failed tests - // Similarly, if running a specific test or test file, don't clear the cache (possible tests have some state information retained) - const clearDiscoveredTestCache = - runFailedTests || - moreInfo.Run_Specific_File || - moreInfo.Run_Specific_Class || - moreInfo.Run_Specific_Function - ? false - : true; - return this.discoverTests(cmdSource, clearDiscoveredTestCache, true, true) - .catch((reason) => { - if ( - this.testDiscoveryCancellationToken && - this.testDiscoveryCancellationToken.isCancellationRequested - ) { - return Promise.reject(reason); - } - const testsHelper = this.serviceContainer.get(ITestsHelper); - testsHelper.displayTestErrorMessage('Errors in discovering tests, continuing with tests'); - return { - rootTestFolders: [], - testFiles: [], - testFolders: [], - testFunctions: [], - testSuites: [], - summary: { errors: 0, failures: 0, passed: 0, skipped: 0 } - }; - }) - .then((tests) => { - this.updateStatus(TestStatus.Running); - this.createCancellationToken(CancellationTokenType.testRunner); - return this.runTestImpl(tests, testsToRun, runFailedTests, debug); - }) - .then(() => { - this.updateStatus(TestStatus.Idle); - this.disposeCancellationToken(CancellationTokenType.testRunner); - sendTelemetryEvent(EventName.UNITTEST_RUN, undefined, telementryProperties); - this.testsStatusUpdaterService.updateStatusOfRunningTestsAsIdle(this.workspaceFolder, this.tests); - this.testsStatusUpdaterService.triggerUpdatesToTests(this.workspaceFolder, this.tests); - return this.tests!; - }) - .catch((reason) => { - this.testsStatusUpdaterService.updateStatusOfRunningTestsAsIdle(this.workspaceFolder, this.tests); - this.testsStatusUpdaterService.triggerUpdatesToTests(this.workspaceFolder, this.tests); - if (this.testRunnerCancellationToken && this.testRunnerCancellationToken.isCancellationRequested) { - reason = CANCELLATION_REASON; - this.updateStatus(TestStatus.Idle); - } else { - this.updateStatus(TestStatus.Error); - telementryProperties.failed = true; - sendTelemetryEvent(EventName.UNITTEST_RUN, undefined, telementryProperties); - } - this.disposeCancellationToken(CancellationTokenType.testRunner); - return Promise.reject(reason); - }); - } - public async updateDiagnostics(tests: Tests, messages: IPythonTestMessage[]): Promise { - await this.stripStaleDiagnostics(tests, messages); - - // Update relevant file diagnostics for tests that have problems. - const uniqueMsgFiles = messages.reduce((filtered, msg) => { - if (filtered.indexOf(msg.testFilePath) === -1 && msg.testFilePath !== undefined) { - filtered.push(msg.testFilePath); - } - return filtered; - }, []); - const fs = this.serviceContainer.get(IFileSystem); - for (const msgFile of uniqueMsgFiles) { - // Check all messages against each test file. - const fileUri = Uri.file(msgFile); - if (!this.diagnosticCollection.has(fileUri)) { - // Create empty diagnostic for file URI so the rest of the logic can assume one already exists. - const diagnostics: Diagnostic[] = []; - this.diagnosticCollection.set(fileUri, diagnostics); - } - // Get the diagnostics for this file's URI before updating it so old tests that weren't run can still show problems. - const oldDiagnostics = this.diagnosticCollection.get(fileUri)!; - const newDiagnostics: Diagnostic[] = []; - for (const diagnostic of oldDiagnostics) { - newDiagnostics.push(diagnostic); - } - for (const msg of messages) { - if ( - fs.arePathsSame(fileUri.fsPath, Uri.file(msg.testFilePath).fsPath) && - msg.status !== TestStatus.Pass - ) { - const diagnostic = this.createDiagnostics(msg); - newDiagnostics.push(diagnostic); - } - } - - // Set the diagnostics for the file. - this.diagnosticCollection.set(fileUri, newDiagnostics); - } - } - protected abstract runTestImpl( - tests: Tests, - testsToRun?: TestsToRun, - runFailedTests?: boolean, - debug?: boolean - ): Promise; - protected abstract getDiscoveryOptions(ignoreCache: boolean): TestDiscoveryOptions; - private updateStatus(status: TestStatus): void { - this._status = status; - // Fire after 1ms, let existing code run to completion, - // We need to allow for code to get into a consistent state. - setTimeout(() => this._onDidStatusChange.fire({ workspace: this.workspaceFolder, status }), 1); - } - private createCancellationToken(tokenType: CancellationTokenType) { - this.disposeCancellationToken(tokenType); - if (tokenType === CancellationTokenType.testDiscovery) { - this.testDiscoveryCancellationTokenSource = new CancellationTokenSource(); - } else { - this.testRunnerCancellationTokenSource = new CancellationTokenSource(); - } - } - private disposeCancellationToken(tokenType: CancellationTokenType) { - if (tokenType === CancellationTokenType.testDiscovery) { - if (this.testDiscoveryCancellationTokenSource) { - this.testDiscoveryCancellationTokenSource.dispose(); - } - this.testDiscoveryCancellationTokenSource = undefined; - } else { - if (this.testRunnerCancellationTokenSource) { - this.testRunnerCancellationTokenSource.dispose(); - } - this.testRunnerCancellationTokenSource = undefined; - } - } - /** - * Whenever a test is run, any previous problems it had should be removed. This runs through - * every already existing set of diagnostics for any that match the tests that were just run - * so they can be stripped out (as they are now no longer relevant). If the tests pass, then - * there is no need to have a diagnostic for it. If they fail, the stale diagnostic will be - * replaced by an up-to-date diagnostic showing the most recent problem with that test. - * - * In order to identify diagnostics associated with the tests that were run, the `nameToRun` - * property of each messages is compared to the `code` property of each diagnostic. - * - * @param messages Details about the tests that were just run. - */ - private async stripStaleDiagnostics(tests: Tests, messages: IPythonTestMessage[]): Promise { - this.diagnosticCollection.forEach((diagnosticUri, oldDiagnostics, collection) => { - const newDiagnostics: Diagnostic[] = []; - for (const diagnostic of oldDiagnostics) { - const matchingMsg = messages.find((msg) => msg.code === diagnostic.code); - if (matchingMsg === undefined) { - // No matching message was found, so this test was not included in the test run. - const matchingTest = tests.testFunctions.find( - (tf) => tf.testFunction.nameToRun === diagnostic.code - ); - if (matchingTest !== undefined) { - // Matching test was found, so the diagnostic is still relevant. - newDiagnostics.push(diagnostic); - } - } - } - // Set the diagnostics for the file. - collection.set(diagnosticUri, newDiagnostics); - }); - } - - private createDiagnostics(message: IPythonTestMessage): Diagnostic { - const stackStart = message.locationStack![0]; - const diagPrefix = this.unitTestDiagnosticService.getMessagePrefix(message.status!); - const severity = this.unitTestDiagnosticService.getSeverity(message.severity)!; - const diagMsg = message.message ? message.message.split('\n')[0] : ''; - const diagnostic = new Diagnostic( - stackStart.location.range, - `${diagPrefix ? `${diagPrefix}: ` : ''}${diagMsg}`, - severity - ); - diagnostic.code = message.code; - diagnostic.source = message.provider; - const relatedInfoArr: DiagnosticRelatedInformation[] = []; - for (const frameDetails of message.locationStack!) { - const relatedInfo = new DiagnosticRelatedInformation(frameDetails.location, frameDetails.lineText); - relatedInfoArr.push(relatedInfo); - } - diagnostic.relatedInformation = relatedInfoArr; - return diagnostic; - } -} diff --git a/src/client/testing/common/managers/testConfigurationManager.ts b/src/client/testing/common/managers/testConfigurationManager.ts deleted file mode 100644 index 22c19e0b821d..000000000000 --- a/src/client/testing/common/managers/testConfigurationManager.ts +++ /dev/null @@ -1,128 +0,0 @@ -import * as path from 'path'; -import { OutputChannel, QuickPickItem, Uri } from 'vscode'; -import { IApplicationShell } from '../../../common/application/types'; -import { traceInfo } from '../../../common/logger'; -import { IFileSystem } from '../../../common/platform/types'; -import { IInstaller, IOutputChannel } from '../../../common/types'; -import { createDeferred } from '../../../common/utils/async'; -import { IServiceContainer } from '../../../ioc/types'; -import { ITestConfigSettingsService, ITestConfigurationManager } from '../../types'; -import { TEST_OUTPUT_CHANNEL, UNIT_TEST_PRODUCTS } from '../constants'; -import { UnitTestProduct } from '../types'; - -export abstract class TestConfigurationManager implements ITestConfigurationManager { - protected readonly outputChannel: OutputChannel; - protected readonly installer: IInstaller; - protected readonly testConfigSettingsService: ITestConfigSettingsService; - constructor( - protected workspace: Uri, - protected product: UnitTestProduct, - protected readonly serviceContainer: IServiceContainer, - cfg?: ITestConfigSettingsService - ) { - this.outputChannel = serviceContainer.get(IOutputChannel, TEST_OUTPUT_CHANNEL); - this.installer = serviceContainer.get(IInstaller); - this.testConfigSettingsService = cfg - ? cfg - : serviceContainer.get(ITestConfigSettingsService); - } - public abstract configure(wkspace: Uri): Promise; - public abstract requiresUserToConfigure(wkspace: Uri): Promise; - public async enable() { - // Disable other test frameworks. - await Promise.all( - UNIT_TEST_PRODUCTS.filter((prod) => prod !== this.product).map((prod) => - this.testConfigSettingsService.disable(this.workspace, prod) - ) - ); - await this.testConfigSettingsService.enable(this.workspace, this.product); - } - // tslint:disable-next-line:no-any - public async disable() { - return this.testConfigSettingsService.enable(this.workspace, this.product); - } - protected selectTestDir(rootDir: string, subDirs: string[], customOptions: QuickPickItem[] = []): Promise { - const options = { - ignoreFocusOut: true, - matchOnDescription: true, - matchOnDetail: true, - placeHolder: 'Select the directory containing the tests' - }; - let items: QuickPickItem[] = subDirs - .map((dir) => { - const dirName = path.relative(rootDir, dir); - if (dirName.indexOf('.') === 0) { - return; - } - return { - label: dirName, - description: '' - }; - }) - .filter((item) => item !== undefined) - .map((item) => item!); - - items = [{ label: '.', description: 'Root directory' }, ...items]; - items = customOptions.concat(items); - const def = createDeferred(); - const appShell = this.serviceContainer.get(IApplicationShell); - appShell.showQuickPick(items, options).then((item) => { - if (!item) { - this.handleCancelled(); // This will throw an exception. - return; - } - - def.resolve(item.label); - }); - - return def.promise; - } - - protected selectTestFilePattern(): Promise { - const options = { - ignoreFocusOut: true, - matchOnDescription: true, - matchOnDetail: true, - placeHolder: 'Select the pattern to identify test files' - }; - const items: QuickPickItem[] = [ - { label: '*test.py', description: "Python Files ending with 'test'" }, - { label: '*_test.py', description: "Python Files ending with '_test'" }, - { label: 'test*.py', description: "Python Files beginning with 'test'" }, - { label: 'test_*.py', description: "Python Files beginning with 'test_'" }, - { label: '*test*.py', description: "Python Files containing the word 'test'" } - ]; - - const def = createDeferred(); - const appShell = this.serviceContainer.get(IApplicationShell); - appShell.showQuickPick(items, options).then((item) => { - if (!item) { - this.handleCancelled(); // This will throw an exception. - return; - } - - def.resolve(item.label); - }); - - return def.promise; - } - protected getTestDirs(rootDir: string): Promise { - const fs = this.serviceContainer.get(IFileSystem); - return fs.getSubDirectories(rootDir).then((subDirs) => { - subDirs.sort(); - - // Find out if there are any dirs with the name test and place them on the top. - const possibleTestDirs = subDirs.filter((dir) => dir.match(/test/i)); - const nonTestDirs = subDirs.filter((dir) => possibleTestDirs.indexOf(dir) === -1); - possibleTestDirs.push(...nonTestDirs); - - // The test dirs are now on top. - return possibleTestDirs; - }); - } - - private handleCancelled() { - traceInfo('testing configuration (in UI) cancelled'); - throw Error('cancelled'); - } -} diff --git a/src/client/testing/common/runner.ts b/src/client/testing/common/runner.ts deleted file mode 100644 index fd2f3145b7d8..000000000000 --- a/src/client/testing/common/runner.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { ErrorUtils } from '../../common/errors/errorUtils'; -import { ModuleNotInstalledError } from '../../common/errors/moduleNotInstalledError'; -import { - IPythonExecutionFactory, - IPythonExecutionService, - IPythonToolExecutionService, - ObservableExecutionResult, - SpawnOptions -} from '../../common/process/types'; -import { ExecutionInfo, IConfigurationService, IPythonSettings } from '../../common/types'; -import { IServiceContainer } from '../../ioc/types'; -import { NOSETEST_PROVIDER, PYTEST_PROVIDER, UNITTEST_PROVIDER } from './constants'; -import { ITestRunner, ITestsHelper, Options, TestProvider } from './types'; -export { Options } from './types'; - -@injectable() -export class TestRunner implements ITestRunner { - constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {} - public run(testProvider: TestProvider, options: Options): Promise { - return run(this.serviceContainer, testProvider, options); - } -} - -export async function run( - serviceContainer: IServiceContainer, - testProvider: TestProvider, - options: Options -): Promise { - const testExecutablePath = getExecutablePath( - testProvider, - serviceContainer.get(IConfigurationService).getSettings(options.workspaceFolder) - ); - const moduleName = getTestModuleName(testProvider); - const spawnOptions = options as SpawnOptions; - let pythonExecutionServicePromise: Promise; - spawnOptions.mergeStdOutErr = typeof spawnOptions.mergeStdOutErr === 'boolean' ? spawnOptions.mergeStdOutErr : true; - - let promise: Promise>; - - // Since conda 4.4.0 we have found that running python code needs the environment activated. - // So if running an executable, there's no way we can activate, if its a module, then activate and run the module. - const testHelper = serviceContainer.get(ITestsHelper); - const executionInfo: ExecutionInfo = { - execPath: testExecutablePath, - args: options.args, - moduleName: testExecutablePath && testExecutablePath.length > 0 ? undefined : moduleName, - product: testHelper.parseProduct(testProvider) - }; - - if (testProvider === UNITTEST_PROVIDER) { - promise = serviceContainer - .get(IPythonExecutionFactory) - .createActivatedEnvironment({ resource: options.workspaceFolder }) - .then((executionService) => executionService.execObservable(options.args, { ...spawnOptions })); - } else if (typeof executionInfo.moduleName === 'string' && executionInfo.moduleName.length > 0) { - pythonExecutionServicePromise = serviceContainer - .get(IPythonExecutionFactory) - .createActivatedEnvironment({ resource: options.workspaceFolder }); - promise = pythonExecutionServicePromise.then((executionService) => - executionService.execModuleObservable(executionInfo.moduleName!, executionInfo.args, options) - ); - } else { - const pythonToolsExecutionService = serviceContainer.get( - IPythonToolExecutionService - ); - promise = pythonToolsExecutionService.execObservable(executionInfo, spawnOptions, options.workspaceFolder); - } - - return promise.then((result) => { - return new Promise((resolve, reject) => { - let stdOut = ''; - let stdErr = ''; - result.out.subscribe( - (output) => { - stdOut += output.out; - // If the test runner python module is not installed we'll have something in stderr. - // Hence track that separately and check at the end. - if (output.source === 'stderr') { - stdErr += output.out; - } - if (options.outChannel) { - options.outChannel.append(output.out); - } - }, - reject, - async () => { - // If the test runner python module is not installed we'll have something in stderr. - if ( - moduleName && - pythonExecutionServicePromise && - ErrorUtils.outputHasModuleNotInstalledError(moduleName, stdErr) - ) { - const pythonExecutionService = await pythonExecutionServicePromise; - const isInstalled = await pythonExecutionService.isModuleInstalled(moduleName); - if (!isInstalled) { - return reject(new ModuleNotInstalledError(moduleName)); - } - } - resolve(stdOut); - } - ); - }); - }); -} - -function getExecutablePath(testProvider: TestProvider, settings: IPythonSettings): string | undefined { - let testRunnerExecutablePath: string | undefined; - switch (testProvider) { - case NOSETEST_PROVIDER: { - testRunnerExecutablePath = settings.testing.nosetestPath; - break; - } - case PYTEST_PROVIDER: { - testRunnerExecutablePath = settings.testing.pytestPath; - break; - } - default: { - return undefined; - } - } - return path.basename(testRunnerExecutablePath) === testRunnerExecutablePath ? undefined : testRunnerExecutablePath; -} -function getTestModuleName(testProvider: TestProvider) { - switch (testProvider) { - case NOSETEST_PROVIDER: { - return 'nose'; - } - case PYTEST_PROVIDER: { - return 'pytest'; - } - case UNITTEST_PROVIDER: { - return 'unittest'; - } - default: { - throw new Error(`Test provider '${testProvider}' not supported`); - } - } -} diff --git a/src/client/testing/common/services/configSettingService.ts b/src/client/testing/common/services/configSettingService.ts deleted file mode 100644 index 3fd888620750..000000000000 --- a/src/client/testing/common/services/configSettingService.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { inject, injectable } from 'inversify'; -import { Uri, WorkspaceConfiguration } from 'vscode'; -import { IWorkspaceService } from '../../../common/application/types'; -import { Product } from '../../../common/types'; -import { IServiceContainer } from '../../../ioc/types'; -import { ITestConfigSettingsService } from '../../types'; -import { UnitTestProduct } from './../types'; - -@injectable() -export class TestConfigSettingsService implements ITestConfigSettingsService { - private readonly workspaceService: IWorkspaceService; - constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - this.workspaceService = serviceContainer.get(IWorkspaceService); - } - public async updateTestArgs(testDirectory: string | Uri, product: UnitTestProduct, args: string[]) { - const setting = this.getTestArgSetting(product); - return this.updateSetting(testDirectory, setting, args); - } - - public async enable(testDirectory: string | Uri, product: UnitTestProduct): Promise { - const setting = this.getTestEnablingSetting(product); - return this.updateSetting(testDirectory, setting, true); - } - - public async disable(testDirectory: string | Uri, product: UnitTestProduct): Promise { - const setting = this.getTestEnablingSetting(product); - return this.updateSetting(testDirectory, setting, false); - } - public getTestEnablingSetting(product: UnitTestProduct) { - switch (product) { - case Product.unittest: - return 'testing.unittestEnabled'; - case Product.pytest: - return 'testing.pytestEnabled'; - case Product.nosetest: - return 'testing.nosetestsEnabled'; - default: - throw new Error('Invalid Test Product'); - } - } - private getTestArgSetting(product: UnitTestProduct) { - switch (product) { - case Product.unittest: - return 'testing.unittestArgs'; - case Product.pytest: - return 'testing.pytestArgs'; - case Product.nosetest: - return 'testing.nosetestArgs'; - default: - throw new Error('Invalid Test Product'); - } - } - // tslint:disable-next-line:no-any - private async updateSetting(testDirectory: string | Uri, setting: string, value: any) { - let pythonConfig: WorkspaceConfiguration; - const resource = typeof testDirectory === 'string' ? Uri.file(testDirectory) : testDirectory; - if (!this.workspaceService.hasWorkspaceFolders) { - pythonConfig = this.workspaceService.getConfiguration('python'); - } else if (this.workspaceService.workspaceFolders!.length === 1) { - pythonConfig = this.workspaceService.getConfiguration( - 'python', - this.workspaceService.workspaceFolders![0].uri - ); - } else { - const workspaceFolder = this.workspaceService.getWorkspaceFolder(resource); - if (!workspaceFolder) { - throw new Error(`Test directory does not belong to any workspace (${testDirectory})`); - } - // tslint:disable-next-line:no-non-null-assertion - pythonConfig = this.workspaceService.getConfiguration('python', workspaceFolder!.uri); - } - - return pythonConfig.update(setting, value); - } -} - -export class BufferedTestConfigSettingsService implements ITestConfigSettingsService { - private ops: [string, string | Uri, UnitTestProduct, string[]][]; - constructor() { - this.ops = []; - } - - public async updateTestArgs(testDirectory: string | Uri, product: UnitTestProduct, args: string[]) { - this.ops.push(['updateTestArgs', testDirectory, product, args]); - } - - public async enable(testDirectory: string | Uri, product: UnitTestProduct): Promise { - this.ops.push(['enable', testDirectory, product, []]); - } - - public async disable(testDirectory: string | Uri, product: UnitTestProduct): Promise { - this.ops.push(['disable', testDirectory, product, []]); - } - - public async apply(cfg: ITestConfigSettingsService) { - const ops = this.ops; - this.ops = []; - // Note that earlier ops do not get rolled back if a later - // one fails. - for (const [op, testDir, prod, args] of ops) { - switch (op) { - case 'updateTestArgs': - await cfg.updateTestArgs(testDir, prod, args); - break; - case 'enable': - await cfg.enable(testDir, prod); - break; - case 'disable': - await cfg.disable(testDir, prod); - break; - default: - break; - } - } - } - public getTestEnablingSetting(_: UnitTestProduct): string { - throw new Error('Method not implemented.'); - } -} diff --git a/src/client/testing/common/services/contextService.ts b/src/client/testing/common/services/contextService.ts deleted file mode 100644 index 40b70561b649..000000000000 --- a/src/client/testing/common/services/contextService.ts +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { ICommandManager } from '../../../common/application/types'; -import { ContextKey } from '../../../common/contextKey'; -import { IDisposable } from '../../../common/types'; -import { swallowExceptions } from '../../../common/utils/decorators'; -import { ITestManagementService, WorkspaceTestStatus } from '../../types'; -import { ITestCollectionStorageService, ITestContextService, TestStatus } from '../types'; - -@injectable() -export class TestContextService implements ITestContextService { - private readonly hasFailedTests: ContextKey; - private readonly runningTests: ContextKey; - private readonly discoveringTests: ContextKey; - private readonly busyTests: ContextKey; - private readonly disposables: IDisposable[] = []; - constructor( - @inject(ITestCollectionStorageService) private readonly storage: ITestCollectionStorageService, - @inject(ITestManagementService) private readonly testManager: ITestManagementService, - @inject(ICommandManager) cmdManager: ICommandManager - ) { - this.hasFailedTests = new ContextKey('hasFailedTests', cmdManager); - this.runningTests = new ContextKey('runningTests', cmdManager); - this.discoveringTests = new ContextKey('discoveringTests', cmdManager); - this.busyTests = new ContextKey('busyTests', cmdManager); - } - public dispose(): void { - this.disposables.forEach((d) => d.dispose()); - } - public register(): void { - this.testManager.onDidStatusChange(this.onStatusChange, this, this.disposables); - } - @swallowExceptions('Handle status change of tests') - protected async onStatusChange(status: WorkspaceTestStatus): Promise { - const tests = this.storage.getTests(status.workspace); - const promises: Promise[] = []; - if (tests && tests.summary) { - promises.push(this.hasFailedTests.set(tests.summary.failures > 0)); - } - promises.push( - ...[ - this.runningTests.set(status.status === TestStatus.Running), - this.discoveringTests.set(status.status === TestStatus.Discovering), - this.busyTests.set(status.status === TestStatus.Running || status.status === TestStatus.Discovering) - ] - ); - - await Promise.all(promises); - } -} diff --git a/src/client/testing/common/services/discoveredTestParser.ts b/src/client/testing/common/services/discoveredTestParser.ts deleted file mode 100644 index 200c50cd88bd..000000000000 --- a/src/client/testing/common/services/discoveredTestParser.ts +++ /dev/null @@ -1,384 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { Uri } from 'vscode'; -import { IWorkspaceService } from '../../../common/application/types'; -import { traceError } from '../../../common/logger'; -import { TestDataItem, TestDataItemType } from '../../types'; -import { getParentFile, getParentSuite, getTestDataItemType } from '../testUtils'; -import * as testing from '../types'; -import * as discovery from './types'; - -@injectable() -export class TestDiscoveredTestParser implements discovery.ITestDiscoveredTestParser { - constructor(@inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService) {} - - public parse(resource: Uri, discoveredTests: discovery.DiscoveredTests[]): testing.Tests { - const tests: testing.Tests = { - rootTestFolders: [], - summary: { errors: 0, failures: 0, passed: 0, skipped: 0 }, - testFiles: [], - testFolders: [], - testFunctions: [], - testSuites: [] - }; - - const workspace = this.workspaceService.getWorkspaceFolder(resource); - if (!workspace) { - traceError('Resource does not belong to any workspace folder'); - return tests; - } - - for (const data of discoveredTests) { - const rootFolder = { - name: data.root, - folders: [], - time: 0, - testFiles: [], - resource: resource, - nameToRun: data.rootid - }; - tests.rootTestFolders.push(rootFolder); - tests.testFolders.push(rootFolder); - this.buildChildren(rootFolder, rootFolder, data, tests); - } - - return tests; - } - - /** - * Not the best solution to use `case statements`, but it keeps the code simple and easy to read in one place. - * Could go with separate classes for each type and use stratergies, but that just ends up a class for - * 10 lines of code. Hopefully this is more readable and maintainable than having multiple classes for - * the simple processing of the children. - * - * @protected - * @param {TestFolder} rootFolder - * @param {TestDataItem} parent - * @param {DiscoveredTests} discoveredTests - * @param {Tests} tests - * @memberof TestsDiscovery - */ - public buildChildren( - rootFolder: testing.TestFolder, - parent: TestDataItem, - discoveredTests: discovery.DiscoveredTests, - tests: testing.Tests - ) { - const parentType = getTestDataItemType(parent); - switch (parentType) { - case TestDataItemType.folder: { - this.processFolder(rootFolder, parent as testing.TestFolder, discoveredTests, tests); - break; - } - case TestDataItemType.file: { - this.processFile(rootFolder, parent as testing.TestFile, discoveredTests, tests); - break; - } - case TestDataItemType.suite: { - this.processSuite(rootFolder, parent as testing.TestSuite, discoveredTests, tests); - break; - } - default: - break; - } - } - - /** - * Process the children of a folder. - * A folder can only contain other folders and files. - * Hence limit processing to those items. - * - * @protected - * @param {TestFolder} rootFolder - * @param {TestFolder} parentFolder - * @param {DiscoveredTests} discoveredTests - * @param {Tests} tests - * @memberof TestDiscoveredTestParser - */ - protected processFolder( - rootFolder: testing.TestFolder, - parentFolder: testing.TestFolder, - discoveredTests: discovery.DiscoveredTests, - tests: testing.Tests - ) { - const folders = discoveredTests.parents - .filter((child) => child.kind === 'folder' && child.parentid === parentFolder.nameToRun) - .map((folder) => createTestFolder(rootFolder, folder as discovery.TestFolder)); - folders.forEach((folder) => { - parentFolder.folders.push(folder); - tests.testFolders.push(folder); - this.buildChildren(rootFolder, folder, discoveredTests, tests); - }); - - const files = discoveredTests.parents - .filter((child) => child.kind === 'file' && child.parentid === parentFolder.nameToRun) - .map((file) => createTestFile(rootFolder, file as discovery.TestFile)); - files.forEach((file) => { - parentFolder.testFiles.push(file); - tests.testFiles.push(file); - this.buildChildren(rootFolder, file, discoveredTests, tests); - }); - } - - /** - * Process the children of a file. - * A file can only contain suites, functions and paramerterized functions. - * Hence limit processing just to those items. - * - * @protected - * @param {TestFolder} rootFolder - * @param {TestFile} parentFile - * @param {DiscoveredTests} discoveredTests - * @param {Tests} tests - * @memberof TestDiscoveredTestParser - */ - protected processFile( - rootFolder: testing.TestFolder, - parentFile: testing.TestFile, - discoveredTests: discovery.DiscoveredTests, - tests: testing.Tests - ) { - const suites = discoveredTests.parents - .filter((child) => child.kind === 'suite' && child.parentid === parentFile.nameToRun) - .map((suite) => createTestSuite(parentFile, rootFolder.resource, suite as discovery.TestSuite)); - suites.forEach((suite) => { - parentFile.suites.push(suite); - tests.testSuites.push(createFlattenedSuite(tests, suite)); - this.buildChildren(rootFolder, suite, discoveredTests, tests); - }); - - const functions = discoveredTests.tests - .filter((test) => test.parentid === parentFile.nameToRun) - .map((test) => createTestFunction(rootFolder, test)); - functions.forEach((func) => { - parentFile.functions.push(func); - tests.testFunctions.push(createFlattenedFunction(tests, func)); - }); - - const parameterizedFunctions = discoveredTests.parents - .filter((child) => child.kind === 'function' && child.parentid === parentFile.nameToRun) - .map((func) => createParameterizedTestFunction(rootFolder, func as discovery.TestFunction)); - parameterizedFunctions.forEach((func) => - this.processParameterizedFunction(rootFolder, parentFile, func, discoveredTests, tests) - ); - } - - /** - * Process the children of a suite. - * A suite can only contain suites, functions and paramerterized functions. - * Hence limit processing just to those items. - * - * @protected - * @param {TestFolder} rootFolder - * @param {TestSuite} parentSuite - * @param {DiscoveredTests} discoveredTests - * @param {Tests} tests - * @memberof TestDiscoveredTestParser - */ - protected processSuite( - rootFolder: testing.TestFolder, - parentSuite: testing.TestSuite, - discoveredTests: discovery.DiscoveredTests, - tests: testing.Tests - ) { - const suites = discoveredTests.parents - .filter((child) => child.kind === 'suite' && child.parentid === parentSuite.nameToRun) - .map((suite) => createTestSuite(parentSuite, rootFolder.resource, suite as discovery.TestSuite)); - suites.forEach((suite) => { - parentSuite.suites.push(suite); - tests.testSuites.push(createFlattenedSuite(tests, suite)); - this.buildChildren(rootFolder, suite, discoveredTests, tests); - }); - - const functions = discoveredTests.tests - .filter((test) => test.parentid === parentSuite.nameToRun) - .map((test) => createTestFunction(rootFolder, test)); - functions.forEach((func) => { - parentSuite.functions.push(func); - tests.testFunctions.push(createFlattenedFunction(tests, func)); - }); - - const parameterizedFunctions = discoveredTests.parents - .filter((child) => child.kind === 'function' && child.parentid === parentSuite.nameToRun) - .map((func) => createParameterizedTestFunction(rootFolder, func as discovery.TestFunction)); - parameterizedFunctions.forEach((func) => - this.processParameterizedFunction(rootFolder, parentSuite, func, discoveredTests, tests) - ); - } - - /** - * Process the children of a parameterized function. - * A parameterized function can only contain functions (in tests). - * Hence limit processing just to those items. - * - * @protected - * @param {TestFolder} rootFolder - * @param {TestFile | TestSuite} parent - * @param {TestFunction} parentFunction - * @param {DiscoveredTests} discoveredTests - * @param {Tests} tests - * @returns - * @memberof TestDiscoveredTestParser - */ - protected processParameterizedFunction( - rootFolder: testing.TestFolder, - parent: testing.TestFile | testing.TestSuite, - parentFunction: testing.SubtestParent, - discoveredTests: discovery.DiscoveredTests, - tests: testing.Tests - ) { - if (!parentFunction.asSuite) { - return; - } - const functions = discoveredTests.tests - .filter((test) => test.parentid === parentFunction.nameToRun) - .map((test) => createTestFunction(rootFolder, test)); - functions.forEach((func) => { - func.subtestParent = parentFunction; - parentFunction.asSuite.functions.push(func); - parent.functions.push(func); - tests.testFunctions.push(createFlattenedParameterizedFunction(tests, func, parent)); - }); - } -} - -function createTestFolder(root: testing.TestFolder, item: discovery.TestFolder): testing.TestFolder { - return { - name: item.name, - nameToRun: item.id, - resource: root.resource, - time: 0, - folders: [], - testFiles: [] - }; -} - -function createTestFile(root: testing.TestFolder, item: discovery.TestFile): testing.TestFile { - const fullpath = path.isAbsolute(item.relpath) ? item.relpath : path.resolve(root.name, item.relpath); - return { - fullPath: fullpath, - functions: [], - name: item.name, - nameToRun: item.id, - resource: root.resource, - suites: [], - time: 0, - xmlName: createXmlName(item.id) - }; -} - -function createTestSuite( - parentSuiteFile: testing.TestFile | testing.TestSuite, - resource: Uri, - item: discovery.TestSuite -): testing.TestSuite { - const suite = { - functions: [], - name: item.name, - nameToRun: item.id, - resource: resource, - suites: [], - time: 0, - xmlName: '', - isInstance: false, - isUnitTest: false - }; - suite.xmlName = `${parentSuiteFile.xmlName}.${item.name}`; - return suite; -} - -function createFlattenedSuite(tests: testing.Tests, suite: testing.TestSuite): testing.FlattenedTestSuite { - const parentFile = getParentFile(tests, suite); - return { - parentTestFile: parentFile, - testSuite: suite, - xmlClassName: parentFile.xmlName - }; -} - -function createFlattenedParameterizedFunction( - tests: testing.Tests, - func: testing.TestFunction, - parent: testing.TestFile | testing.TestSuite -): testing.FlattenedTestFunction { - const type = getTestDataItemType(parent); - const parentFile = - type && type === TestDataItemType.suite ? getParentFile(tests, func) : (parent as testing.TestFile); - const parentSuite = type && type === TestDataItemType.suite ? (parent as testing.TestSuite) : undefined; - return { - parentTestFile: parentFile, - parentTestSuite: parentSuite, - xmlClassName: parentSuite ? parentSuite.xmlName : parentFile.xmlName, - testFunction: func - }; -} - -function createFlattenedFunction(tests: testing.Tests, func: testing.TestFunction): testing.FlattenedTestFunction { - const parent = getParentFile(tests, func); - const type = parent ? getTestDataItemType(parent) : undefined; - const parentFile = - type && type === TestDataItemType.suite ? getParentFile(tests, func) : (parent as testing.TestFile); - const parentSuite = getParentSuite(tests, func); - return { - parentTestFile: parentFile, - parentTestSuite: parentSuite, - xmlClassName: parentSuite ? parentSuite.xmlName : parentFile.xmlName, - testFunction: func - }; -} - -function createParameterizedTestFunction( - root: testing.TestFolder, - item: discovery.TestFunction -): testing.SubtestParent { - const suite: testing.TestSuite = { - functions: [], - isInstance: false, - isUnitTest: false, - name: item.name, - nameToRun: item.id, - resource: root.resource, - time: 0, - suites: [], - xmlName: '' - }; - return { - asSuite: suite, - name: item.name, - nameToRun: item.id, - time: 0 - }; -} - -function createTestFunction(root: testing.TestFolder, item: discovery.Test): testing.TestFunction { - return { - name: item.name, - nameToRun: item.id, - resource: root.resource, - time: 0, - file: item.source.substr(0, item.source.lastIndexOf(':')) - }; -} - -/** - * Creates something known as an Xml Name, used to identify items - * from an xunit test result. - * Once we have the test runner done in Python, this can be discarded. - * @param {string} fileId - * @returns - */ -function createXmlName(fileId: string) { - let name = path.join(path.dirname(fileId), path.basename(fileId, path.extname(fileId))); - // Replace all path separators with ".". - name = name.replace(/\\/g, '.').replace(/\//g, '.'); - // Remove leading "." and path separators. - while (name.startsWith('.') || name.startsWith('/') || name.startsWith('\\')) { - name = name.substring(1); - } - return name; -} diff --git a/src/client/testing/common/services/discovery.ts b/src/client/testing/common/services/discovery.ts deleted file mode 100644 index 0042f5e003f0..000000000000 --- a/src/client/testing/common/services/discovery.ts +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable, named } from 'inversify'; -import { OutputChannel } from 'vscode'; -import { traceError } from '../../../common/logger'; -import * as internalScripts from '../../../common/process/internal/scripts'; -import { - ExecutionFactoryCreateWithEnvironmentOptions, - IPythonExecutionFactory, - SpawnOptions -} from '../../../common/process/types'; -import { IOutputChannel } from '../../../common/types'; -import { captureTelemetry } from '../../../telemetry'; -import { EventName } from '../../../telemetry/constants'; -import { TEST_OUTPUT_CHANNEL } from '../constants'; -import { ITestDiscoveryService, TestDiscoveryOptions, Tests } from '../types'; -import { DiscoveredTests, ITestDiscoveredTestParser } from './types'; - -@injectable() -export class TestsDiscoveryService implements ITestDiscoveryService { - constructor( - @inject(IPythonExecutionFactory) private readonly execFactory: IPythonExecutionFactory, - @inject(ITestDiscoveredTestParser) private readonly parser: ITestDiscoveredTestParser, - @inject(IOutputChannel) @named(TEST_OUTPUT_CHANNEL) private readonly outChannel: OutputChannel - ) {} - @captureTelemetry(EventName.UNITTEST_DISCOVER_WITH_PYCODE, undefined, true) - public async discoverTests(options: TestDiscoveryOptions): Promise { - try { - const discoveredTests = await this.exec(options); - return this.parser.parse(options.workspaceFolder, discoveredTests); - } catch (ex) { - if (ex.stdout) { - traceError('Failed to parse discovered Test', new Error(ex.stdout)); - } - traceError('Failed to parse discovered Test', ex); - throw ex; - } - } - public async exec(options: TestDiscoveryOptions): Promise { - const [args, parse] = internalScripts.testing_tools.run_adapter(options.args); - const creationOptions: ExecutionFactoryCreateWithEnvironmentOptions = { - allowEnvironmentFetchExceptions: false, - resource: options.workspaceFolder - }; - const execService = await this.execFactory.createActivatedEnvironment(creationOptions); - const spawnOptions: SpawnOptions = { - token: options.token, - cwd: options.cwd, - throwOnStdErr: true - }; - this.outChannel.appendLine(`python ${args.join(' ')}`); - const proc = await execService.exec(args, spawnOptions); - try { - return parse(proc.stdout); - } catch (ex) { - ex.stdout = proc.stdout; - throw ex; // re-throw - } - } -} diff --git a/src/client/testing/common/services/storageService.ts b/src/client/testing/common/services/storageService.ts deleted file mode 100644 index 46aafe1c74de..000000000000 --- a/src/client/testing/common/services/storageService.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { inject, injectable } from 'inversify'; -import { Disposable, Event, EventEmitter, Uri } from 'vscode'; -import { IWorkspaceService } from '../../../common/application/types'; -import { IDisposableRegistry } from '../../../common/types'; -import { TestDataItem } from '../../types'; -import { - FlattenedTestFunction, - FlattenedTestSuite, - ITestCollectionStorageService, - TestFunction, - Tests, - TestSuite -} from './../types'; - -@injectable() -export class TestCollectionStorageService implements ITestCollectionStorageService { - private readonly _onDidChange = new EventEmitter<{ uri: Uri; data?: TestDataItem }>(); - private readonly testsIndexedByWorkspaceUri = new Map(); - - constructor( - @inject(IDisposableRegistry) disposables: Disposable[], - @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService - ) { - disposables.push(this); - } - public get onDidChange(): Event<{ uri: Uri; data?: TestDataItem }> { - return this._onDidChange.event; - } - public getTests(resource: Uri): Tests | undefined { - const workspaceFolder = this.workspaceService.getWorkspaceFolderIdentifier(resource); - return this.testsIndexedByWorkspaceUri.has(workspaceFolder) - ? this.testsIndexedByWorkspaceUri.get(workspaceFolder) - : undefined; - } - public storeTests(resource: Uri, tests: Tests | undefined): void { - const workspaceFolder = this.workspaceService.getWorkspaceFolderIdentifier(resource); - this.testsIndexedByWorkspaceUri.set(workspaceFolder, tests); - this._onDidChange.fire({ uri: resource }); - } - public findFlattendTestFunction(resource: Uri, func: TestFunction): FlattenedTestFunction | undefined { - const tests = this.getTests(resource); - if (!tests) { - return; - } - return tests.testFunctions.find((f) => f.testFunction === func); - } - public findFlattendTestSuite(resource: Uri, suite: TestSuite): FlattenedTestSuite | undefined { - const tests = this.getTests(resource); - if (!tests) { - return; - } - return tests.testSuites.find((f) => f.testSuite === suite); - } - public dispose() { - this.testsIndexedByWorkspaceUri.clear(); - } - public update(resource: Uri, item: TestDataItem): void { - this._onDidChange.fire({ uri: resource, data: item }); - } -} diff --git a/src/client/testing/common/services/testManagerService.ts b/src/client/testing/common/services/testManagerService.ts deleted file mode 100644 index 42a96877ef0c..000000000000 --- a/src/client/testing/common/services/testManagerService.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Disposable, Uri } from 'vscode'; -import { IConfigurationService, IDisposableRegistry, Product } from '../../../common/types'; -import { IServiceContainer } from '../../../ioc/types'; -import { ITestManager, ITestManagerFactory, ITestManagerService, ITestsHelper, UnitTestProduct } from './../types'; - -export class TestManagerService implements ITestManagerService { - private cachedTestManagers = new Map(); - private readonly configurationService: IConfigurationService; - constructor(private wkspace: Uri, private testsHelper: ITestsHelper, private serviceContainer: IServiceContainer) { - const disposables = serviceContainer.get(IDisposableRegistry); - this.configurationService = serviceContainer.get(IConfigurationService); - disposables.push(this); - } - public dispose() { - this.cachedTestManagers.forEach((info) => { - info.dispose(); - }); - } - public getTestManager(): ITestManager | undefined { - const preferredTestManager = this.getPreferredTestManager(); - if (typeof preferredTestManager !== 'number') { - return; - } - - // tslint:disable-next-line:no-non-null-assertion - if (!this.cachedTestManagers.has(preferredTestManager)) { - const testDirectory = this.getTestWorkingDirectory(); - const testProvider = this.testsHelper.parseProviderName(preferredTestManager); - const factory = this.serviceContainer.get(ITestManagerFactory); - this.cachedTestManagers.set(preferredTestManager, factory(testProvider, this.wkspace, testDirectory)); - } - const testManager = this.cachedTestManagers.get(preferredTestManager)!; - return testManager.enabled ? testManager : undefined; - } - public getTestWorkingDirectory() { - const settings = this.configurationService.getSettings(this.wkspace); - return settings.testing.cwd && settings.testing.cwd.length > 0 ? settings.testing.cwd : this.wkspace.fsPath; - } - public getPreferredTestManager(): UnitTestProduct | undefined { - const settings = this.configurationService.getSettings(this.wkspace); - if (settings.testing.nosetestsEnabled) { - return Product.nosetest; - } else if (settings.testing.pytestEnabled) { - return Product.pytest; - } else if (settings.testing.unittestEnabled) { - return Product.unittest; - } - return undefined; - } -} diff --git a/src/client/testing/common/services/testResultsService.ts b/src/client/testing/common/services/testResultsService.ts deleted file mode 100644 index 0112b3599521..000000000000 --- a/src/client/testing/common/services/testResultsService.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { inject, injectable, named } from 'inversify'; -import { TestDataItem, TestDataItemType } from '../../types'; -import { getChildren, getTestDataItemType } from '../testUtils'; -import { ITestResultsService, ITestVisitor, Tests, TestStatus } from '../types'; - -@injectable() -export class TestResultsService implements ITestResultsService { - constructor(@inject(ITestVisitor) @named('TestResultResetVisitor') private resultResetVisitor: ITestVisitor) {} - public resetResults(tests: Tests): void { - tests.testFolders.forEach((f) => this.resultResetVisitor.visitTestFolder(f)); - tests.testFunctions.forEach((fn) => this.resultResetVisitor.visitTestFunction(fn.testFunction)); - tests.testSuites.forEach((suite) => this.resultResetVisitor.visitTestSuite(suite.testSuite)); - tests.testFiles.forEach((testFile) => this.resultResetVisitor.visitTestFile(testFile)); - } - public updateResults(tests: Tests): void { - // Update Test tree bottom to top - const testQueue: TestDataItem[] = []; - const testStack: TestDataItem[] = []; - tests.rootTestFolders.forEach((folder) => testQueue.push(folder)); - - while (testQueue.length > 0) { - const item = testQueue.shift(); - if (!item) { - continue; - } - testStack.push(item); - const children = getChildren(item); - children.forEach((child) => testQueue.push(child)); - } - while (testStack.length > 0) { - const item = testStack.pop(); - this.updateTestItem(item!); - } - } - private updateTestItem(test: TestDataItem): void { - if (getTestDataItemType(test) === TestDataItemType.function) { - return; - } - let allChildrenPassed = true; - let noChildrenRan = true; - test.functionsPassed = test.functionsFailed = test.functionsDidNotRun = 0; - - const children = getChildren(test); - children.forEach((child) => { - if (getTestDataItemType(child) === TestDataItemType.function) { - if (typeof child.passed === 'boolean') { - noChildrenRan = false; - if (child.passed) { - test.functionsPassed! += 1; - } else { - test.functionsFailed! += 1; - allChildrenPassed = false; - } - } else { - test.functionsDidNotRun! += 1; - } - } else { - if (typeof child.passed === 'boolean') { - noChildrenRan = false; - if (!child.passed) { - allChildrenPassed = false; - } - } - test.functionsFailed! += child.functionsFailed!; - test.functionsPassed! += child.functionsPassed!; - test.functionsDidNotRun! += child.functionsDidNotRun!; - } - }); - if (noChildrenRan) { - test.passed = undefined; - test.status = TestStatus.Unknown; - } else { - test.passed = allChildrenPassed; - test.status = test.passed ? TestStatus.Pass : TestStatus.Fail; - } - } -} diff --git a/src/client/testing/common/services/testsStatusService.ts b/src/client/testing/common/services/testsStatusService.ts deleted file mode 100644 index b2094a3e26b1..000000000000 --- a/src/client/testing/common/services/testsStatusService.ts +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { Uri } from 'vscode'; -import { TestDataItem } from '../../types'; -import { visitRecursive } from '../testVisitors/visitor'; -import { ITestCollectionStorageService, ITestsStatusUpdaterService, Tests, TestStatus, TestsToRun } from '../types'; - -@injectable() -export class TestsStatusUpdaterService implements ITestsStatusUpdaterService { - constructor(@inject(ITestCollectionStorageService) private readonly storage: ITestCollectionStorageService) {} - public updateStatusAsDiscovering(resource: Uri, tests?: Tests): void { - if (!tests) { - return; - } - const visitor = (item: TestDataItem) => { - item.status = TestStatus.Discovering; - this.storage.update(resource, item); - }; - tests.rootTestFolders.forEach((item) => visitRecursive(tests, item, visitor)); - } - public updateStatusAsUnknown(resource: Uri, tests?: Tests): void { - if (!tests) { - return; - } - const visitor = (item: TestDataItem) => { - item.status = TestStatus.Unknown; - this.storage.update(resource, item); - }; - tests.rootTestFolders.forEach((item) => visitRecursive(tests, item, visitor)); - } - public updateStatusAsRunning(resource: Uri, tests?: Tests): void { - if (!tests) { - return; - } - const visitor = (item: TestDataItem) => { - item.status = TestStatus.Running; - this.storage.update(resource, item); - }; - tests.rootTestFolders.forEach((item) => visitRecursive(tests, item, visitor)); - } - public updateStatusAsRunningFailedTests(resource: Uri, tests?: Tests): void { - if (!tests) { - return; - } - const predicate = (item: TestDataItem) => item.status === TestStatus.Fail || item.status === TestStatus.Error; - const visitor = (item: TestDataItem) => { - if (item.status && predicate(item)) { - item.status = TestStatus.Running; - this.storage.update(resource, item); - } - }; - const failedItems = [ - ...tests.testFunctions.map((f) => f.testFunction).filter(predicate), - ...tests.testSuites.map((f) => f.testSuite).filter(predicate) - ]; - failedItems.forEach((failedItem) => visitRecursive(tests, failedItem, visitor)); - } - public updateStatusAsRunningSpecificTests(resource: Uri, testsToRun: TestsToRun, tests?: Tests): void { - if (!tests) { - return; - } - const itemsRunning = [ - ...(testsToRun.testFile || []), - ...(testsToRun.testSuite || []), - ...(testsToRun.testFunction || []) - ]; - const visitor = (item: TestDataItem) => { - item.status = TestStatus.Running; - this.storage.update(resource, item); - }; - itemsRunning.forEach((item) => visitRecursive(tests, item, visitor)); - } - public updateStatusOfRunningTestsAsIdle(resource: Uri, tests?: Tests): void { - if (!tests) { - return; - } - const visitor = (item: TestDataItem) => { - if (item.status === TestStatus.Running) { - item.status = TestStatus.Idle; - this.storage.update(resource, item); - } - }; - tests.rootTestFolders.forEach((item) => visitRecursive(tests, item, visitor)); - } - public triggerUpdatesToTests(resource: Uri, tests?: Tests): void { - if (!tests) { - return; - } - const visitor = (item: TestDataItem) => this.storage.update(resource, item); - tests.rootTestFolders.forEach((item) => visitRecursive(tests, item, visitor)); - } -} diff --git a/src/client/testing/common/services/types.ts b/src/client/testing/common/services/types.ts deleted file mode 100644 index e2b929379bbb..000000000000 --- a/src/client/testing/common/services/types.ts +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { Uri } from 'vscode'; -import * as internalScripts from '../../../common/process/internal/scripts'; -import { Tests } from '../types'; - -// We expose these here as a convenience and to cut down on churn -// elsewhere in the code. -export type DiscoveredTests = internalScripts.testing_tools.DiscoveredTests; -export type Test = internalScripts.testing_tools.Test; -export type TestFolder = internalScripts.testing_tools.TestFolder; -export type TestFile = internalScripts.testing_tools.TestFile; -export type TestSuite = internalScripts.testing_tools.TestSuite; -export type TestFunction = internalScripts.testing_tools.TestFunction; - -export const ITestDiscoveredTestParser = Symbol('ITestDiscoveredTestParser'); -export interface ITestDiscoveredTestParser { - parse(resource: Uri, discoveredTests: DiscoveredTests[]): Tests; -} diff --git a/src/client/testing/common/services/unitTestDiagnosticService.ts b/src/client/testing/common/services/unitTestDiagnosticService.ts deleted file mode 100644 index 5e88a6df615b..000000000000 --- a/src/client/testing/common/services/unitTestDiagnosticService.ts +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { injectable } from 'inversify'; -import { DiagnosticSeverity } from 'vscode'; -import * as localize from '../../../common/utils/localize'; -import { DiagnosticMessageType, ITestDiagnosticService, PythonTestMessageSeverity } from '../../types'; -import { TestStatus } from '../types'; - -@injectable() -export class UnitTestDiagnosticService implements ITestDiagnosticService { - private MessageTypes = new Map(); - private MessageSeverities = new Map(); - private MessagePrefixes = new Map(); - - constructor() { - this.MessageTypes.set(TestStatus.Error, DiagnosticMessageType.Error); - this.MessageTypes.set(TestStatus.Fail, DiagnosticMessageType.Fail); - this.MessageTypes.set(TestStatus.Skipped, DiagnosticMessageType.Skipped); - this.MessageTypes.set(TestStatus.Pass, DiagnosticMessageType.Pass); - this.MessageSeverities.set(PythonTestMessageSeverity.Error, DiagnosticSeverity.Error); - this.MessageSeverities.set(PythonTestMessageSeverity.Failure, DiagnosticSeverity.Error); - this.MessageSeverities.set(PythonTestMessageSeverity.Skip, DiagnosticSeverity.Information); - this.MessageSeverities.set(PythonTestMessageSeverity.Pass, undefined); - this.MessagePrefixes.set(DiagnosticMessageType.Error, localize.Testing.testErrorDiagnosticMessage()); - this.MessagePrefixes.set(DiagnosticMessageType.Fail, localize.Testing.testFailDiagnosticMessage()); - this.MessagePrefixes.set(DiagnosticMessageType.Skipped, localize.Testing.testSkippedDiagnosticMessage()); - this.MessagePrefixes.set(DiagnosticMessageType.Pass, ''); - } - public getMessagePrefix(status: TestStatus): string | undefined { - const msgType = this.MessageTypes.get(status); - return msgType !== undefined ? this.MessagePrefixes.get(msgType!) : undefined; - } - public getSeverity(unitTestSeverity: PythonTestMessageSeverity): DiagnosticSeverity | undefined { - return this.MessageSeverities.get(unitTestSeverity); - } -} diff --git a/src/client/testing/common/services/workspaceTestManagerService.ts b/src/client/testing/common/services/workspaceTestManagerService.ts deleted file mode 100644 index e0061997e8dd..000000000000 --- a/src/client/testing/common/services/workspaceTestManagerService.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { inject, injectable, named } from 'inversify'; -import { Disposable, OutputChannel, Uri, workspace } from 'vscode'; -import { IDisposableRegistry, IOutputChannel } from '../../../common/types'; -import { TEST_OUTPUT_CHANNEL } from './../constants'; -import { - ITestManager, - ITestManagerService, - ITestManagerServiceFactory, - IWorkspaceTestManagerService, - UnitTestProduct -} from './../types'; - -@injectable() -export class WorkspaceTestManagerService implements IWorkspaceTestManagerService, Disposable { - private workspaceTestManagers = new Map(); - constructor( - @inject(IOutputChannel) @named(TEST_OUTPUT_CHANNEL) private outChannel: OutputChannel, - @inject(ITestManagerServiceFactory) private testManagerServiceFactory: ITestManagerServiceFactory, - @inject(IDisposableRegistry) disposables: Disposable[] - ) { - disposables.push(this); - } - public dispose() { - this.workspaceTestManagers.forEach((info) => info.dispose()); - } - public getTestManager(resource: Uri): ITestManager | undefined { - const wkspace = this.getWorkspace(resource); - this.ensureTestManagerService(wkspace); - return this.workspaceTestManagers.get(wkspace.fsPath)!.getTestManager(); - } - public getTestWorkingDirectory(resource: Uri) { - const wkspace = this.getWorkspace(resource); - this.ensureTestManagerService(wkspace); - return this.workspaceTestManagers.get(wkspace.fsPath)!.getTestWorkingDirectory(); - } - public getPreferredTestManager(resource: Uri): UnitTestProduct | undefined { - const wkspace = this.getWorkspace(resource); - this.ensureTestManagerService(wkspace); - return this.workspaceTestManagers.get(wkspace.fsPath)!.getPreferredTestManager(); - } - private getWorkspace(resource: Uri): Uri { - if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) { - const noWkspaceMessage = 'Please open a workspace'; - this.outChannel.appendLine(noWkspaceMessage); - throw new Error(noWkspaceMessage); - } - if (!resource || workspace.workspaceFolders.length === 1) { - return workspace.workspaceFolders[0].uri; - } - const workspaceFolder = workspace.getWorkspaceFolder(resource); - if (workspaceFolder) { - return workspaceFolder.uri; - } - const message = `Resource '${resource.fsPath}' does not belong to any workspace`; - this.outChannel.appendLine(message); - throw new Error(message); - } - private ensureTestManagerService(wkspace: Uri) { - if (!this.workspaceTestManagers.has(wkspace.fsPath)) { - this.workspaceTestManagers.set(wkspace.fsPath, this.testManagerServiceFactory(wkspace)); - } - } -} diff --git a/src/client/testing/common/testConfigurationManager.ts b/src/client/testing/common/testConfigurationManager.ts new file mode 100644 index 000000000000..be3f0109da02 --- /dev/null +++ b/src/client/testing/common/testConfigurationManager.ts @@ -0,0 +1,125 @@ +import * as path from 'path'; +import { QuickPickItem, QuickPickOptions, Uri } from 'vscode'; +import { IApplicationShell } from '../../common/application/types'; +import { IFileSystem } from '../../common/platform/types'; +import { IInstaller } from '../../common/types'; +import { createDeferred } from '../../common/utils/async'; +import { IServiceContainer } from '../../ioc/types'; +import { traceVerbose } from '../../logging'; +import { UNIT_TEST_PRODUCTS } from './constants'; +import { ITestConfigSettingsService, ITestConfigurationManager, UnitTestProduct } from './types'; + +function handleCancelled(): void { + traceVerbose('testing configuration (in UI) cancelled'); + throw Error('cancelled'); +} + +export abstract class TestConfigurationManager implements ITestConfigurationManager { + protected readonly installer: IInstaller; + + protected readonly testConfigSettingsService: ITestConfigSettingsService; + + private readonly handleCancelled = handleCancelled; + + constructor( + protected workspace: Uri, + protected product: UnitTestProduct, + protected readonly serviceContainer: IServiceContainer, + cfg?: ITestConfigSettingsService, + ) { + this.installer = serviceContainer.get(IInstaller); + this.testConfigSettingsService = + cfg || serviceContainer.get(ITestConfigSettingsService); + } + + public abstract configure(wkspace: Uri): Promise; + + public abstract requiresUserToConfigure(wkspace: Uri): Promise; + + public async enable(): Promise { + // Disable other test frameworks. + await Promise.all( + UNIT_TEST_PRODUCTS.filter((prod) => prod !== this.product).map((prod) => + this.testConfigSettingsService.disable(this.workspace, prod), + ), + ); + await this.testConfigSettingsService.enable(this.workspace, this.product); + } + + public async disable(): Promise { + return this.testConfigSettingsService.enable(this.workspace, this.product); + } + + protected selectTestDir(rootDir: string, subDirs: string[], customOptions: QuickPickItem[] = []): Promise { + const options = { + ignoreFocusOut: true, + matchOnDescription: true, + matchOnDetail: true, + placeHolder: 'Select the directory containing the tests', + }; + let items: QuickPickItem[] = subDirs + .map((dir) => { + const dirName = path.relative(rootDir, dir); + if (dirName.indexOf('.') === 0) { + return undefined; + } + return { + label: dirName, + description: '', + }; + }) + .filter((item) => item !== undefined) + .map((item) => item!); + + items = [{ label: '.', description: 'Root directory' }, ...items]; + items = customOptions.concat(items); + return this.showQuickPick(items, options); + } + + protected selectTestFilePattern(): Promise { + const options = { + ignoreFocusOut: true, + matchOnDescription: true, + matchOnDetail: true, + placeHolder: 'Select the pattern to identify test files', + }; + const items: QuickPickItem[] = [ + { label: '*test.py', description: "Python files ending with 'test'" }, + { label: '*_test.py', description: "Python files ending with '_test'" }, + { label: 'test*.py', description: "Python files beginning with 'test'" }, + { label: 'test_*.py', description: "Python files beginning with 'test_'" }, + { label: '*test*.py', description: "Python files containing the word 'test'" }, + ]; + + return this.showQuickPick(items, options); + } + + protected getTestDirs(rootDir: string): Promise { + const fs = this.serviceContainer.get(IFileSystem); + return fs.getSubDirectories(rootDir).then((subDirs) => { + subDirs.sort(); + + // Find out if there are any dirs with the name test and place them on the top. + const possibleTestDirs = subDirs.filter((dir) => dir.match(/test/i)); + const nonTestDirs = subDirs.filter((dir) => possibleTestDirs.indexOf(dir) === -1); + possibleTestDirs.push(...nonTestDirs); + + // The test dirs are now on top. + return possibleTestDirs; + }); + } + + private showQuickPick(items: QuickPickItem[], options: QuickPickOptions): Promise { + const def = createDeferred(); + const appShell = this.serviceContainer.get(IApplicationShell); + appShell.showQuickPick(items, options).then((item) => { + if (!item) { + this.handleCancelled(); // This will throw an exception. + return; + } + + def.resolve(item.label); + }); + return def.promise; + } +} diff --git a/src/client/testing/common/testUtils.ts b/src/client/testing/common/testUtils.ts index 2aa97f6950d8..04e82e1caa52 100644 --- a/src/client/testing/common/testUtils.ts +++ b/src/client/testing/common/testUtils.ts @@ -1,28 +1,10 @@ -import { inject, injectable, named } from 'inversify'; -import * as path from 'path'; +import { injectable } from 'inversify'; import { Uri, workspace } from 'vscode'; -import { IApplicationShell, ICommandManager } from '../../common/application/types'; -import * as constants from '../../common/constants'; -import { ITestingSettings, Product } from '../../common/types'; -import { IServiceContainer } from '../../ioc/types'; -import { TestDataItem, TestDataItemType, TestWorkspaceFolder } from '../types'; -import { CommandSource } from './constants'; -import { TestFlatteningVisitor } from './testVisitors/flatteningVisitor'; -import { - FlattenedTestFunction, - FlattenedTestSuite, - ITestsHelper, - ITestVisitor, - TestFile, - TestFolder, - TestFunction, - TestProvider, - Tests, - TestSettingsPropertyNames, - TestsToRun, - TestSuite, - UnitTestProduct -} from './types'; +import { IApplicationShell } from '../../common/application/types'; +import { Product } from '../../common/types'; +import { ITestingSettings, TestSettingsPropertyNames } from '../configuration/types'; +import { TestProvider } from '../types'; +import { ITestsHelper, UnitTestProduct } from './types'; export async function selectTestWorkspace(appShell: IApplicationShell): Promise { if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) { @@ -35,31 +17,10 @@ export async function selectTestWorkspace(appShell: IApplicationShell): Promise< } } -export function extractBetweenDelimiters(content: string, startDelimiter: string, endDelimiter: string): string { - content = content.substring(content.indexOf(startDelimiter) + startDelimiter.length); - return content.substring(0, content.lastIndexOf(endDelimiter)); -} - -export function convertFileToPackage(filePath: string): string { - const lastIndex = filePath.lastIndexOf('.'); - return filePath.substring(0, lastIndex).replace(/\//g, '.').replace(/\\/g, '.'); -} - @injectable() export class TestsHelper implements ITestsHelper { - private readonly appShell: IApplicationShell; - private readonly commandManager: ICommandManager; - constructor( - @inject(ITestVisitor) @named('TestFlatteningVisitor') private readonly flatteningVisitor: TestFlatteningVisitor, - @inject(IServiceContainer) serviceContainer: IServiceContainer - ) { - this.appShell = serviceContainer.get(IApplicationShell); - this.commandManager = serviceContainer.get(ICommandManager); - } public parseProviderName(product: UnitTestProduct): TestProvider { switch (product) { - case Product.nosetest: - return 'nosetest'; case Product.pytest: return 'pytest'; case Product.unittest: @@ -71,8 +32,6 @@ export class TestsHelper implements ITestsHelper { } public parseProduct(provider: TestProvider): UnitTestProduct { switch (provider) { - case 'nosetest': - return Product.nosetest; case 'pytest': return Product.pytest; case 'unittest': @@ -89,20 +48,13 @@ export class TestsHelper implements ITestsHelper { return { argsName: 'pytestArgs' as keyof ITestingSettings, pathName: 'pytestPath' as keyof ITestingSettings, - enabledName: 'pytestEnabled' as keyof ITestingSettings - }; - } - case 'nosetest': { - return { - argsName: 'nosetestArgs' as keyof ITestingSettings, - pathName: 'nosetestPath' as keyof ITestingSettings, - enabledName: 'nosetestsEnabled' as keyof ITestingSettings + enabledName: 'pytestEnabled' as keyof ITestingSettings, }; } case 'unittest': { return { argsName: 'unittestArgs' as keyof ITestingSettings, - enabledName: 'unittestEnabled' as keyof ITestingSettings + enabledName: 'unittestEnabled' as keyof ITestingSettings, }; } default: { @@ -110,498 +62,4 @@ export class TestsHelper implements ITestsHelper { } } } - public flattenTestFiles(testFiles: TestFile[], workspaceFolder: string): Tests { - testFiles.forEach((testFile) => this.flatteningVisitor.visitTestFile(testFile)); - - // tslint:disable-next-line:no-object-literal-type-assertion - const tests = { - testFiles: testFiles, - testFunctions: this.flatteningVisitor.flattenedTestFunctions, - testSuites: this.flatteningVisitor.flattenedTestSuites, - testFolders: [], - rootTestFolders: [], - summary: { passed: 0, failures: 0, errors: 0, skipped: 0 } - }; - - this.placeTestFilesIntoFolders(tests, workspaceFolder); - - return tests; - } - public placeTestFilesIntoFolders(tests: Tests, workspaceFolder: string): void { - // First get all the unique folders - const folders: string[] = []; - tests.testFiles.forEach((file) => { - const relativePath = path.relative(workspaceFolder, file.fullPath); - const dir = path.dirname(relativePath); - if (folders.indexOf(dir) === -1) { - folders.push(dir); - } - }); - - tests.testFolders = []; - const folderMap = new Map(); - folders.sort(); - const resource = Uri.file(workspaceFolder); - folders.forEach((dir) => { - dir.split(path.sep).reduce((parentPath, currentName, _index, _values) => { - let newPath = currentName; - let parentFolder: TestFolder | undefined; - if (parentPath.length > 0) { - parentFolder = folderMap.get(parentPath); - newPath = path.join(parentPath, currentName); - } - if (!folderMap.has(newPath)) { - const testFolder: TestFolder = { - resource, - name: newPath, - testFiles: [], - folders: [], - nameToRun: newPath, - time: 0, - functionsPassed: 0, - functionsFailed: 0, - functionsDidNotRun: 0 - }; - folderMap.set(newPath, testFolder); - if (parentFolder) { - parentFolder!.folders.push(testFolder); - } else { - tests.rootTestFolders.push(testFolder); - } - tests.testFiles - .filter((fl) => path.dirname(path.relative(workspaceFolder, fl.fullPath)) === newPath) - .forEach((testFile) => { - testFolder.testFiles.push(testFile); - }); - tests.testFolders.push(testFolder); - } - return newPath; - }, ''); - }); - } - public parseTestName(name: string, rootDirectory: string, tests: Tests): TestsToRun | undefined { - // tslint:disable-next-line:no-suspicious-comment - // TODO: We need a better way to match (currently we have raw name, name, xmlname, etc = which one do we. - // Use to identify a file given the full file name, similarly for a folder and function. - // Perhaps something like a parser or methods like TestFunction.fromString()... something). - if (!tests) { - return undefined; - } - const absolutePath = path.isAbsolute(name) ? name : path.resolve(rootDirectory, name); - const testFolders = tests.testFolders.filter( - (folder) => folder.nameToRun === name || folder.name === name || folder.name === absolutePath - ); - if (testFolders.length > 0) { - return { testFolder: testFolders }; - } - - const testFiles = tests.testFiles.filter( - (file) => file.nameToRun === name || file.name === name || file.fullPath === absolutePath - ); - if (testFiles.length > 0) { - return { testFile: testFiles }; - } - - const testFns = tests.testFunctions - .filter((fn) => fn.testFunction.nameToRun === name || fn.testFunction.name === name) - .map((fn) => fn.testFunction); - if (testFns.length > 0) { - return { testFunction: testFns }; - } - - // Just return this as a test file. - return { - testFile: [ - { - resource: Uri.file(rootDirectory), - name: name, - nameToRun: name, - functions: [], - suites: [], - xmlName: name, - fullPath: '', - time: 0, - functionsPassed: 0, - functionsFailed: 0, - functionsDidNotRun: 0 - } - ] - }; - } - public displayTestErrorMessage(message: string) { - this.appShell.showErrorMessage(message, constants.Button_Text_Tests_View_Output).then((action) => { - if (action === constants.Button_Text_Tests_View_Output) { - this.commandManager.executeCommand(constants.Commands.Tests_ViewOutput, undefined, CommandSource.ui); - } - }); - } - public mergeTests(items: Tests[]): Tests { - return items.reduce((tests, otherTests, index) => { - if (index === 0) { - return tests; - } - - tests.summary.errors += otherTests.summary.errors; - tests.summary.failures += otherTests.summary.failures; - tests.summary.passed += otherTests.summary.passed; - tests.summary.skipped += otherTests.summary.skipped; - tests.rootTestFolders.push(...otherTests.rootTestFolders); - tests.testFiles.push(...otherTests.testFiles); - tests.testFolders.push(...otherTests.testFolders); - tests.testFunctions.push(...otherTests.testFunctions); - tests.testSuites.push(...otherTests.testSuites); - - return tests; - }, items[0]); - } - - public shouldRunAllTests(testsToRun?: TestsToRun) { - if (!testsToRun) { - return true; - } - if ( - (Array.isArray(testsToRun.testFile) && testsToRun.testFile.length > 0) || - (Array.isArray(testsToRun.testFolder) && testsToRun.testFolder.length > 0) || - (Array.isArray(testsToRun.testFunction) && testsToRun.testFunction.length > 0) || - (Array.isArray(testsToRun.testSuite) && testsToRun.testSuite.length > 0) - ) { - return false; - } - - return true; - } -} - -export function getTestDataItemType(test: TestDataItem): TestDataItemType { - if (test instanceof TestWorkspaceFolder) { - return TestDataItemType.workspaceFolder; - } - if (getTestFile(test)) { - return TestDataItemType.file; - } - if (getTestFolder(test)) { - return TestDataItemType.folder; - } - if (getTestSuite(test)) { - return TestDataItemType.suite; - } - if (getTestFunction(test)) { - return TestDataItemType.function; - } - throw new Error('Unknown test type'); -} -export function getTestFile(test: TestDataItem): TestFile | undefined { - if (!test) { - return; - } - // Only TestFile has a `fullPath` property. - return typeof (test as TestFile).fullPath === 'string' ? (test as TestFile) : undefined; -} -export function getTestSuite(test: TestDataItem): TestSuite | undefined { - if (!test) { - return; - } - // Only TestSuite has a `suites` property. - return Array.isArray((test as TestSuite).suites) && !getTestFile(test) ? (test as TestSuite) : undefined; -} -export function getTestFolder(test: TestDataItem): TestFolder | undefined { - if (!test) { - return; - } - // Only TestFolder has a `folders` property. - return Array.isArray((test as TestFolder).folders) ? (test as TestFolder) : undefined; -} -export function getTestFunction(test: TestDataItem): TestFunction | undefined { - if (!test) { - return; - } - if (test instanceof TestWorkspaceFolder || getTestFile(test) || getTestFolder(test) || getTestSuite(test)) { - return; - } - return test as TestFunction; -} - -/** - * Gets the parent for a given test item. - * For test functions, this will return either a test suite or a test file. - * For test suites, this will return either a test suite or a test file. - * For test files, this will return a test folder. - * For a test folder, this will return either a test folder or `undefined`. - * @export - * @param {Tests} tests - * @param {TestDataItem} data - * @returns {(TestDataItem | undefined)} - */ -export function getParent(tests: Tests, data: TestDataItem): TestDataItem | undefined { - switch (getTestDataItemType(data)) { - case TestDataItemType.file: { - return getParentTestFolderForFile(tests, data as TestFile); - } - case TestDataItemType.folder: { - return getParentTestFolder(tests, data as TestFolder); - } - case TestDataItemType.suite: { - const suite = data as TestSuite; - if (isSubtestsParent(suite)) { - const fn = suite.functions[0]; - const parent = tests.testSuites.find((item) => item.testSuite.functions.indexOf(fn) >= 0); - if (parent) { - return parent.testSuite; - } - return tests.testFiles.find((item) => item.functions.indexOf(fn) >= 0); - } - const parentSuite = tests.testSuites.find((item) => item.testSuite.suites.indexOf(suite) >= 0); - if (parentSuite) { - return parentSuite.testSuite; - } - return tests.testFiles.find((item) => item.suites.indexOf(suite) >= 0); - } - case TestDataItemType.function: { - const fn = data as TestFunction; - if (fn.subtestParent) { - return fn.subtestParent.asSuite; - } - const parentSuite = tests.testSuites.find((item) => item.testSuite.functions.indexOf(fn) >= 0); - if (parentSuite) { - return parentSuite.testSuite; - } - return tests.testFiles.find((item) => item.functions.indexOf(fn) >= 0); - } - default: { - throw new Error('Unknown test type'); - } - } -} - -/** - * Returns the parent test folder give a given test file or folder. - * - * @export - * @param {Tests} tests - * @param {(TestFolder | TestFile)} item - * @returns {(TestFolder | undefined)} - */ -function getParentTestFolder(tests: Tests, item: TestFolder | TestFile): TestFolder | undefined { - if (getTestDataItemType(item) === TestDataItemType.folder) { - return getParentTestFolderForFolder(tests, item as TestFolder); - } - return getParentTestFolderForFile(tests, item as TestFile); -} - -/** - * Gets the parent test file for a test item. - * - * @param {Tests} tests - * @param {(TestSuite | TestFunction)} suite - * @returns {TestFile} - */ -export function getParentFile(tests: Tests, suite: TestSuite | TestFunction): TestFile { - let parent = getParent(tests, suite); - while (parent) { - if (getTestDataItemType(parent) === TestDataItemType.file) { - return parent as TestFile; - } - parent = getParent(tests, parent); - } - throw new Error('No parent file for provided test item'); -} -/** - * Gets the parent test suite for a suite/function. - * - * @param {Tests} tests - * @param {(TestSuite | TestFunction)} suite - * @returns {(TestSuite | undefined)} - */ -export function getParentSuite(tests: Tests, suite: TestSuite | TestFunction): TestSuite | undefined { - let parent = getParent(tests, suite); - while (parent) { - if (getTestDataItemType(parent) === TestDataItemType.suite) { - return parent as TestSuite; - } - parent = getParent(tests, parent); - } - return; -} - -/** - * Returns the parent test folder give a given test file. - * - * @param {Tests} tests - * @param {TestFile} file - * @returns {(TestFolder | undefined)} - */ -function getParentTestFolderForFile(tests: Tests, file: TestFile): TestFolder | undefined { - return tests.testFolders.find((folder) => folder.testFiles.some((item) => item === file)); -} - -/** - * Returns the parent test folder for a given test folder. - * - * @param {Tests} tests - * @param {TestFolder} folder - * @returns {(TestFolder | undefined)} - */ -function getParentTestFolderForFolder(tests: Tests, folder: TestFolder): TestFolder | undefined { - if (tests.rootTestFolders.indexOf(folder) >= 0) { - return; - } - return tests.testFolders.find((item) => item.folders.some((child) => child === folder)); -} - -/** - * Given a test function will return the corresponding flattened test function. - * - * @export - * @param {Tests} tests - * @param {TestFunction} func - * @returns {(FlattenedTestFunction | undefined)} - */ -export function findFlattendTestFunction(tests: Tests, func: TestFunction): FlattenedTestFunction | undefined { - return tests.testFunctions.find((f) => f.testFunction === func); -} - -/** - * Given a test suite, will return the corresponding flattened test suite. - * - * @export - * @param {Tests} tests - * @param {TestSuite} suite - * @returns {(FlattenedTestSuite | undefined)} - */ -export function findFlattendTestSuite(tests: Tests, suite: TestSuite): FlattenedTestSuite | undefined { - return tests.testSuites.find((f) => f.testSuite === suite); -} - -/** - * Returns the children of a given test data item. - * - * @export - * @param {Tests} tests - * @param {TestDataItem} item - * @returns {TestDataItem[]} - */ -export function getChildren(item: TestDataItem): TestDataItem[] { - switch (getTestDataItemType(item)) { - case TestDataItemType.folder: { - return [...(item as TestFolder).folders, ...(item as TestFolder).testFiles]; - } - case TestDataItemType.file: { - const [subSuites, functions] = divideSubtests((item as TestFile).functions); - return [...functions, ...(item as TestFile).suites, ...subSuites]; - } - case TestDataItemType.suite: { - let subSuites: TestSuite[] = []; - let functions = (item as TestSuite).functions; - if (!isSubtestsParent(item as TestSuite)) { - [subSuites, functions] = divideSubtests((item as TestSuite).functions); - } - return [...functions, ...(item as TestSuite).suites, ...subSuites]; - } - case TestDataItemType.function: { - return []; - } - default: { - throw new Error('Unknown Test Type'); - } - } -} - -function divideSubtests(mixed: TestFunction[]): [TestSuite[], TestFunction[]] { - const suites: TestSuite[] = []; - const functions: TestFunction[] = []; - mixed.forEach((func) => { - if (!func.subtestParent) { - functions.push(func); - return; - } - const parent = func.subtestParent.asSuite; - if (suites.indexOf(parent) < 0) { - suites.push(parent); - } - }); - return [suites, functions]; -} - -export function isSubtestsParent(suite: TestSuite): boolean { - const functions = suite.functions; - if (functions.length === 0) { - return false; - } - const subtestParent = functions[0].subtestParent; - if (subtestParent === undefined) { - return false; - } - return subtestParent.asSuite === suite; -} - -export function copyDesiredTestResults(source: Tests, target: Tests): void { - copyResultsForFolders(source.testFolders, target.testFolders); -} - -function copyResultsForFolders(source: TestFolder[], target: TestFolder[]): void { - source.forEach((sourceFolder) => { - const targetFolder = target.find( - (folder) => folder.name === sourceFolder.name && folder.nameToRun === sourceFolder.nameToRun - ); - if (!targetFolder) { - return; - } - copyValueTypes(sourceFolder, targetFolder); - copyResultsForFiles(sourceFolder.testFiles, targetFolder.testFiles); - // These should be reinitialized - targetFolder.functionsPassed = targetFolder.functionsDidNotRun = targetFolder.functionsFailed = 0; - }); -} -function copyResultsForFiles(source: TestFile[], target: TestFile[]): void { - source.forEach((sourceFile) => { - const targetFile = target.find((file) => file.name === sourceFile.name); - if (!targetFile) { - return; - } - copyValueTypes(sourceFile, targetFile); - copyResultsForFunctions(sourceFile.functions, targetFile.functions); - copyResultsForSuites(sourceFile.suites, targetFile.suites); - // These should be reinitialized - targetFile.functionsPassed = targetFile.functionsDidNotRun = targetFile.functionsFailed = 0; - }); -} - -function copyResultsForFunctions(source: TestFunction[], target: TestFunction[]): void { - source.forEach((sourceFn) => { - const targetFn = target.find((fn) => fn.name === sourceFn.name && fn.nameToRun === sourceFn.nameToRun); - if (!targetFn) { - return; - } - copyValueTypes(sourceFn, targetFn); - }); -} - -function copyResultsForSuites(source: TestSuite[], target: TestSuite[]): void { - source.forEach((sourceSuite) => { - const targetSuite = target.find( - (suite) => - suite.name === sourceSuite.name && - suite.nameToRun === sourceSuite.nameToRun && - suite.xmlName === sourceSuite.xmlName - ); - if (!targetSuite) { - return; - } - copyValueTypes(sourceSuite, targetSuite); - copyResultsForFunctions(sourceSuite.functions, targetSuite.functions); - copyResultsForSuites(sourceSuite.suites, targetSuite.suites); - // These should be reinitialized - targetSuite.functionsPassed = targetSuite.functionsDidNotRun = targetSuite.functionsFailed = 0; - }); -} - -function copyValueTypes(source: T, target: T): void { - Object.keys(source).forEach((key) => { - // tslint:disable-next-line:no-any - const value = (source as any)[key]; - if (['boolean', 'number', 'string', 'undefined'].indexOf(typeof value) >= 0) { - // tslint:disable-next-line:no-any - (target as any)[key] = value; - } - }); } diff --git a/src/client/testing/common/testVisitors/flatteningVisitor.ts b/src/client/testing/common/testVisitors/flatteningVisitor.ts deleted file mode 100644 index bca5cc0cc271..000000000000 --- a/src/client/testing/common/testVisitors/flatteningVisitor.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { injectable } from 'inversify'; -import { convertFileToPackage } from '../testUtils'; -import { - FlattenedTestFunction, - FlattenedTestSuite, - ITestVisitor, - TestFile, - TestFolder, - TestFunction, - TestSuite -} from '../types'; - -@injectable() -export class TestFlatteningVisitor implements ITestVisitor { - // tslint:disable-next-line:variable-name - private _flattedTestFunctions = new Map(); - // tslint:disable-next-line:variable-name - private _flattenedTestSuites = new Map(); - public get flattenedTestFunctions(): FlattenedTestFunction[] { - return [...this._flattedTestFunctions.values()]; - } - public get flattenedTestSuites(): FlattenedTestSuite[] { - return [...this._flattenedTestSuites.values()]; - } - // tslint:disable-next-line:no-empty - public visitTestFunction(_testFunction: TestFunction): void {} - // tslint:disable-next-line:no-empty - public visitTestSuite(_testSuite: TestSuite): void {} - public visitTestFile(testFile: TestFile): void { - // sample test_three (file name without extension and all / replaced with ., meaning this is the package) - const packageName = convertFileToPackage(testFile.name); - - testFile.functions.forEach((fn) => this.addTestFunction(fn, testFile, packageName)); - testFile.suites.forEach((suite) => this.visitTestSuiteOfAFile(suite, testFile)); - } - // tslint:disable-next-line:no-empty - public visitTestFolder(_testFile: TestFolder) {} - private visitTestSuiteOfAFile(testSuite: TestSuite, parentTestFile: TestFile): void { - testSuite.functions.forEach((fn) => this.visitTestFunctionOfASuite(fn, testSuite, parentTestFile)); - testSuite.suites.forEach((suite) => this.visitTestSuiteOfAFile(suite, parentTestFile)); - this.addTestSuite(testSuite, parentTestFile); - } - private visitTestFunctionOfASuite( - testFunction: TestFunction, - parentTestSuite: TestSuite, - parentTestFile: TestFile - ) { - const key = `Function:${testFunction.name},Suite:${parentTestSuite.name},SuiteXmlName:${parentTestSuite.xmlName},ParentFile:${parentTestFile.fullPath}`; - if (this._flattenedTestSuites.has(key)) { - return; - } - const flattenedFunction = { - testFunction, - xmlClassName: parentTestSuite.xmlName, - parentTestFile, - parentTestSuite - }; - this._flattedTestFunctions.set(key, flattenedFunction); - } - private addTestSuite(testSuite: TestSuite, parentTestFile: TestFile) { - const key = `Suite:${testSuite.name},SuiteXmlName:${testSuite.xmlName},ParentFile:${parentTestFile.fullPath}`; - if (this._flattenedTestSuites.has(key)) { - return; - } - const flattenedSuite = { parentTestFile, testSuite, xmlClassName: testSuite.xmlName }; - this._flattenedTestSuites.set(key, flattenedSuite); - } - private addTestFunction(testFunction: TestFunction, parentTestFile: TestFile, parentTestPackage: string) { - const key = `Function:${testFunction.name},ParentFile:${parentTestFile.fullPath}`; - if (this._flattedTestFunctions.has(key)) { - return; - } - const flattendFunction = { testFunction, xmlClassName: parentTestPackage, parentTestFile }; - this._flattedTestFunctions.set(key, flattendFunction); - } -} diff --git a/src/client/testing/common/testVisitors/resultResetVisitor.ts b/src/client/testing/common/testVisitors/resultResetVisitor.ts deleted file mode 100644 index 5b4113bd43e5..000000000000 --- a/src/client/testing/common/testVisitors/resultResetVisitor.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { injectable } from 'inversify'; -import { ITestVisitor, TestFile, TestFolder, TestFunction, TestStatus, TestSuite } from '../types'; - -@injectable() -export class TestResultResetVisitor implements ITestVisitor { - public visitTestFunction(testFunction: TestFunction): void { - testFunction.passed = undefined; - testFunction.time = 0; - testFunction.message = ''; - testFunction.traceback = ''; - testFunction.status = TestStatus.Unknown; - testFunction.functionsFailed = 0; - testFunction.functionsPassed = 0; - testFunction.functionsDidNotRun = 0; - } - public visitTestSuite(testSuite: TestSuite): void { - testSuite.passed = undefined; - testSuite.time = 0; - testSuite.status = TestStatus.Unknown; - testSuite.functionsFailed = 0; - testSuite.functionsPassed = 0; - testSuite.functionsDidNotRun = 0; - } - public visitTestFile(testFile: TestFile): void { - testFile.passed = undefined; - testFile.time = 0; - testFile.status = TestStatus.Unknown; - testFile.functionsFailed = 0; - testFile.functionsPassed = 0; - testFile.functionsDidNotRun = 0; - } - public visitTestFolder(testFolder: TestFolder) { - testFolder.functionsDidNotRun = 0; - testFolder.functionsFailed = 0; - testFolder.functionsPassed = 0; - testFolder.passed = undefined; - testFolder.time = 0; - testFolder.status = TestStatus.Unknown; - } -} diff --git a/src/client/testing/common/testVisitors/visitor.ts b/src/client/testing/common/testVisitors/visitor.ts deleted file mode 100644 index 6b511f5200be..000000000000 --- a/src/client/testing/common/testVisitors/visitor.ts +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { TestDataItem } from '../../types'; -import { getChildren, getParent } from '../testUtils'; -import { Tests } from '../types'; - -export type Visitor = (item: TestDataItem) => void; - -/** - * Vists tests recursively. - * - * @export - * @param {Tests} tests - * @param {Visitor} visitor - */ -export function visitRecursive(tests: Tests, visitor: Visitor): void; - -/** - * Vists tests recursively. - * - * @export - * @param {Tests} tests - * @param {TestDataItem} start - * @param {Visitor} visitor - */ -export function visitRecursive(tests: Tests, start: TestDataItem, visitor: Visitor): void; -export function visitRecursive(tests: Tests, arg1: TestDataItem | Visitor, arg2?: Visitor): void { - const startItem = typeof arg1 === 'function' ? undefined : (arg1 as TestDataItem); - const visitor = startItem ? arg2! : (arg1 as Visitor); - let children: TestDataItem[] = []; - if (startItem) { - visitor(startItem); - children = getChildren(startItem); - } else { - children = tests.rootTestFolders; - } - children.forEach((folder) => visitRecursive(tests, folder, visitor)); -} - -/** - * Visits parents recursively. - * - * @export - * @param {Tests} tests - * @param {TestDataItem} startItem - * @param {Visitor} visitor - * @returns {void} - */ -export function visitParentsRecursive(tests: Tests, startItem: TestDataItem, visitor: Visitor): void { - visitor(startItem); - const parent = getParent(tests, startItem); - if (!parent) { - return; - } - visitor(parent); - visitParentsRecursive(tests, parent, visitor); -} diff --git a/src/client/testing/common/types.ts b/src/client/testing/common/types.ts index 009afc50d311..e2fa2d6d2e5a 100644 --- a/src/client/testing/common/types.ts +++ b/src/client/testing/common/types.ts @@ -1,337 +1,83 @@ -import { - CancellationToken, - DebugConfiguration, - DiagnosticCollection, - Disposable, - Event, - OutputChannel, - Uri -} from 'vscode'; -import { ITestingSettings, Product } from '../../common/types'; -import { DebuggerTypeName } from '../../debugger/constants'; -import { ConsoleType } from '../../debugger/types'; -import { IPythonTestMessage, TestDataItem, WorkspaceTestStatus } from '../types'; -import { CommandSource } from './constants'; +import { CancellationToken, DebugSessionOptions, OutputChannel, Uri } from 'vscode'; +import { Product } from '../../common/types'; +import { TestSettingsPropertyNames } from '../configuration/types'; +import { TestProvider } from '../types'; +import { PythonProject } from '../../envExt/types'; -export type TestProvider = 'nosetest' | 'pytest' | 'unittest'; +export type UnitTestProduct = Product.pytest | Product.unittest; -export type UnitTestProduct = Product.nosetest | Product.pytest | Product.unittest; - -export type TestSettingsPropertyNames = { - enabledName: keyof ITestingSettings; - argsName: keyof ITestingSettings; - pathName?: keyof ITestingSettings; -}; +// **************** +// test args/options export type TestDiscoveryOptions = { workspaceFolder: Uri; cwd: string; args: string[]; - token: CancellationToken; + token?: CancellationToken; ignoreCache: boolean; - outChannel: OutputChannel; -}; - -export type TestRunOptions = { - workspaceFolder: Uri; - cwd: string; - tests: Tests; - args: string[]; - testsToRun?: TestsToRun; - token: CancellationToken; outChannel?: OutputChannel; - debug?: boolean; }; -export type UnitTestParserOptions = TestDiscoveryOptions & { startDirectory: string }; - export type LaunchOptions = { cwd: string; args: string[]; testProvider: TestProvider; token?: CancellationToken; outChannel?: OutputChannel; + pytestPort?: string; + pytestUUID?: string; + runTestIdsPort?: string; + /** Optional Python project for project-based execution. */ + project?: PythonProject; }; -export type ParserOptions = TestDiscoveryOptions; - -export type Options = { - workspaceFolder: Uri; - cwd: string; - args: string[]; - outChannel?: OutputChannel; - token: CancellationToken; -}; - -export type TestsToRun = { - testFolder?: TestFolder[]; - testFile?: TestFile[]; - testSuite?: TestSuite[]; - testFunction?: TestFunction[]; -}; - -//***************** -// test results - -export enum TestingType { - folder = 'folder', - file = 'file', - suite = 'suite', - function = 'function' +export enum TestFilter { + removeTests = 'removeTests', + discovery = 'discovery', + runAll = 'runAll', + runSpecific = 'runSpecific', + debugAll = 'debugAll', + debugSpecific = 'debugSpecific', } -export enum TestStatus { - Unknown = 'Unknown', - Discovering = 'Discovering', - Idle = 'Idle', - Running = 'Running', - Fail = 'Fail', - Error = 'Error', - Skipped = 'Skipped', - Pass = 'Pass' -} - -export type TestResult = { - status?: TestStatus; - passed?: boolean; - time: number; - line?: number; - file?: string; - message?: string; - traceback?: string; - functionsPassed?: number; - functionsFailed?: number; - functionsDidNotRun?: number; -}; - -export type TestingNode = TestResult & { - name: string; - nameToRun: string; - resource: Uri; -}; - -export type TestFolder = TestingNode & { - folders: TestFolder[]; - testFiles: TestFile[]; -}; - -export type TestingXMLNode = TestingNode & { - xmlName: string; -}; - -export type TestFile = TestingXMLNode & { - fullPath: string; - functions: TestFunction[]; - suites: TestSuite[]; - errorsWhenDiscovering?: string; -}; - -export type TestSuite = TestingXMLNode & { - functions: TestFunction[]; - suites: TestSuite[]; - isUnitTest: Boolean; - isInstance: Boolean; -}; - -export type TestFunction = TestingNode & { - subtestParent?: SubtestParent; -}; - -export type SubtestParent = TestResult & { - name: string; - nameToRun: string; - asSuite: TestSuite; -}; - -export type FlattenedTestFunction = { - testFunction: TestFunction; - parentTestSuite?: TestSuite; - parentTestFile: TestFile; - xmlClassName: string; -}; - -export type FlattenedTestSuite = { - testSuite: TestSuite; - parentTestFile: TestFile; - xmlClassName: string; -}; - -export type TestSummary = { - passed: number; - failures: number; - errors: number; - skipped: number; -}; - -export type Tests = { - summary: TestSummary; - testFiles: TestFile[]; - testFunctions: FlattenedTestFunction[]; - testSuites: FlattenedTestSuite[]; - testFolders: TestFolder[]; - rootTestFolders: TestFolder[]; -}; - -//***************** +// **************** // interfaces -export interface ITestManagerService extends Disposable { - getTestManager(): ITestManager | undefined; - getTestWorkingDirectory(): string; - getPreferredTestManager(): UnitTestProduct | undefined; -} - -export const IWorkspaceTestManagerService = Symbol('IWorkspaceTestManagerService'); -export interface IWorkspaceTestManagerService extends Disposable { - getTestManager(resource: Uri): ITestManager | undefined; - getTestWorkingDirectory(resource: Uri): string; - getPreferredTestManager(resource: Uri): UnitTestProduct | undefined; -} - export const ITestsHelper = Symbol('ITestsHelper'); export interface ITestsHelper { parseProviderName(product: UnitTestProduct): TestProvider; parseProduct(provider: TestProvider): UnitTestProduct; getSettingsPropertyNames(product: Product): TestSettingsPropertyNames; - flattenTestFiles(testFiles: TestFile[], workspaceFolder: string): Tests; - placeTestFilesIntoFolders(tests: Tests, workspaceFolder: string): void; - displayTestErrorMessage(message: string): void; - shouldRunAllTests(testsToRun?: TestsToRun): boolean; - mergeTests(items: Tests[]): Tests; } -export const ITestVisitor = Symbol('ITestVisitor'); -export interface ITestVisitor { - visitTestFunction(testFunction: TestFunction): void; - visitTestSuite(testSuite: TestSuite): void; - visitTestFile(testFile: TestFile): void; - visitTestFolder(testFile: TestFolder): void; +export const ITestConfigurationService = Symbol('ITestConfigurationService'); +export interface ITestConfigurationService { + hasConfiguredTests(wkspace: Uri): boolean; + selectTestRunner(placeHolderMessage: string): Promise; + enableTest(wkspace: Uri, product: UnitTestProduct): Promise; + promptToEnableAndConfigureTestFramework(wkspace: Uri): Promise; } -export const ITestCollectionStorageService = Symbol('ITestCollectionStorageService'); -export interface ITestCollectionStorageService extends Disposable { - onDidChange: Event<{ uri: Uri; data?: TestDataItem }>; - getTests(wkspace: Uri): Tests | undefined; - storeTests(wkspace: Uri, tests: Tests | null | undefined): void; - findFlattendTestFunction(resource: Uri, func: TestFunction): FlattenedTestFunction | undefined; - findFlattendTestSuite(resource: Uri, suite: TestSuite): FlattenedTestSuite | undefined; - update(resource: Uri, item: TestDataItem): void; +export const ITestConfigSettingsService = Symbol('ITestConfigSettingsService'); +export interface ITestConfigSettingsService { + updateTestArgs(testDirectory: string | Uri, product: UnitTestProduct, args: string[]): Promise; + enable(testDirectory: string | Uri, product: UnitTestProduct): Promise; + disable(testDirectory: string | Uri, product: UnitTestProduct): Promise; + getTestEnablingSetting(product: UnitTestProduct): string; } -export const ITestResultsService = Symbol('ITestResultsService'); -export interface ITestResultsService { - resetResults(tests: Tests): void; - updateResults(tests: Tests): void; +export interface ITestConfigurationManager { + requiresUserToConfigure(wkspace: Uri): Promise; + configure(wkspace: Uri): Promise; + enable(): Promise; + disable(): Promise; } +export const ITestConfigurationManagerFactory = Symbol('ITestConfigurationManagerFactory'); +export interface ITestConfigurationManagerFactory { + create(wkspace: Uri, product: Product, cfg?: ITestConfigSettingsService): ITestConfigurationManager; +} export const ITestDebugLauncher = Symbol('ITestDebugLauncher'); export interface ITestDebugLauncher { - launchDebugger(options: LaunchOptions): Promise; -} - -export const ITestManagerFactory = Symbol('ITestManagerFactory'); -export interface ITestManagerFactory extends Function { - // tslint:disable-next-line:callable-types - (testProvider: TestProvider, workspaceFolder: Uri, rootDirectory: string): ITestManager; -} - -export const ITestManagerServiceFactory = Symbol('TestManagerServiceFactory'); -export interface ITestManagerServiceFactory extends Function { - // tslint:disable-next-line:callable-types - (workspaceFolder: Uri): ITestManagerService; -} - -export const ITestManager = Symbol('ITestManager'); -export interface ITestManager extends Disposable { - readonly status: TestStatus; - readonly enabled: boolean; - readonly workingDirectory: string; - readonly workspaceFolder: Uri; - diagnosticCollection: DiagnosticCollection; - readonly onDidStatusChange: Event; - stop(): void; - resetTestResults(): void; - discoverTests( - cmdSource: CommandSource, - ignoreCache?: boolean, - quietMode?: boolean, - userInitiated?: boolean, - clearTestStatus?: boolean - ): Promise; - runTest( - cmdSource: CommandSource, - testsToRun?: TestsToRun, - runFailedTests?: boolean, - debug?: boolean - ): Promise; -} - -export const ITestDiscoveryService = Symbol('ITestDiscoveryService'); -export interface ITestDiscoveryService { - discoverTests(options: TestDiscoveryOptions): Promise; -} - -export const ITestsParser = Symbol('ITestsParser'); -export interface ITestsParser { - parse(content: string, options: ParserOptions): Tests; -} - -export const IUnitTestSocketServer = Symbol('IUnitTestSocketServer'); -export interface IUnitTestSocketServer extends Disposable { - on(event: string | symbol, listener: Function): this; - removeListener(event: string | symbol, listener: Function): this; - removeAllListeners(event?: string | symbol): this; - start(options?: { port?: number; host?: string }): Promise; - stop(): void; -} - -export const ITestRunner = Symbol('ITestRunner'); -export interface ITestRunner { - run(testProvider: TestProvider, options: Options): Promise; -} - -export const IXUnitParser = Symbol('IXUnitParser'); -export interface IXUnitParser { - // Update "tests" with the results parsed from the given file. - updateResultsFromXmlLogFile(tests: Tests, outputXmlFile: string): Promise; -} - -export const ITestMessageService = Symbol('ITestMessageService'); -export interface ITestMessageService { - getFilteredTestMessages(rootDirectory: string, testResults: Tests): Promise; -} - -export interface ITestDebugConfig extends DebugConfiguration { - type: typeof DebuggerTypeName; - request: 'test'; - - pythonPath?: string; - console?: ConsoleType; - cwd?: string; - env?: Record; - envFile?: string; - - // converted to DebugOptions: - stopOnEntry?: boolean; - showReturnValue?: boolean; - redirectOutput?: boolean; // default: true - debugStdLib?: boolean; - justMyCode?: boolean; - subProcess?: boolean; -} - -export const ITestContextService = Symbol('ITestContextService'); -export interface ITestContextService extends Disposable { - register(): void; -} - -export const ITestsStatusUpdaterService = Symbol('ITestsStatusUpdaterService'); -export interface ITestsStatusUpdaterService { - updateStatusAsDiscovering(resource: Uri, tests?: Tests): void; - updateStatusAsUnknown(resource: Uri, tests?: Tests): void; - updateStatusAsRunning(resource: Uri, tests?: Tests): void; - updateStatusAsRunningFailedTests(resource: Uri, tests?: Tests): void; - updateStatusAsRunningSpecificTests(resource: Uri, testsToRun: TestsToRun, tests?: Tests): void; - updateStatusOfRunningTestsAsIdle(resource: Uri, tests?: Tests): void; - triggerUpdatesToTests(resource: Uri, tests?: Tests): void; + launchDebugger(options: LaunchOptions, callback?: () => void, sessionOptions?: DebugSessionOptions): Promise; } diff --git a/src/client/testing/common/updateTestSettings.ts b/src/client/testing/common/updateTestSettings.ts deleted file mode 100644 index dc549bdd4fae..000000000000 --- a/src/client/testing/common/updateTestSettings.ts +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { applyEdits, findNodeAtLocation, getNodeValue, ModificationOptions, modify, parseTree } from 'jsonc-parser'; -import * as path from 'path'; -import { IExtensionActivationService, LanguageServerType } from '../../activation/types'; -import { IApplicationEnvironment, IWorkspaceService } from '../../common/application/types'; -import '../../common/extensions'; -import { traceDecorators, traceError } from '../../common/logger'; -import { IFileSystem } from '../../common/platform/types'; -import { Resource } from '../../common/types'; -import { swallowExceptions } from '../../common/utils/decorators'; - -// tslint:disable-next-line:no-suspicious-comment -// TODO: rename the class since it is not used just for test settings -@injectable() -export class UpdateTestSettingService implements IExtensionActivationService { - constructor( - @inject(IFileSystem) private readonly fs: IFileSystem, - @inject(IApplicationEnvironment) private readonly application: IApplicationEnvironment, - @inject(IWorkspaceService) private readonly workspace: IWorkspaceService - ) {} - public async activate(resource: Resource): Promise { - this.updateTestSettings(resource).ignoreErrors(); - } - @traceDecorators.error('Failed to update test settings') - public async updateTestSettings(resource: Resource): Promise { - const filesToBeFixed = await this.getFilesToBeFixed(resource); - await Promise.all(filesToBeFixed.map((file) => this.fixSettingInFile(file))); - } - public getSettingsFiles(resource: Resource): string[] { - const settingsFiles: string[] = []; - if (this.application.userSettingsFile) { - settingsFiles.push(this.application.userSettingsFile); - } - const workspaceFolder = this.workspace.getWorkspaceFolder(resource); - if (workspaceFolder) { - settingsFiles.push(path.join(workspaceFolder.uri.fsPath, '.vscode', 'settings.json')); - } - return settingsFiles; - } - public async getFilesToBeFixed(resource: Resource): Promise { - const files = this.getSettingsFiles(resource); - const result = await Promise.all( - files.map(async (file) => { - const needsFixing = await this.doesFileNeedToBeFixed(file); - return { file, needsFixing }; - }) - ); - return result.filter((item) => item.needsFixing).map((item) => item.file); - } - // fixLanguageServerSetting provided for tests so not all tests have to - // deal with potential whitespace changes. - @swallowExceptions('Failed to update settings.json') - public async fixSettingInFile(filePath: string, fixLanguageServerSetting = true): Promise { - let fileContents = await this.fs.readFile(filePath); - - const setting = new RegExp('"python\\.unitTest', 'g'); - fileContents = fileContents.replace(setting, '"python.testing'); - - const setting_pytest_enabled = new RegExp('\\.pyTestEnabled"', 'g'); - const setting_pytest_args = new RegExp('\\.pyTestArgs"', 'g'); - const setting_pytest_path = new RegExp('\\.pyTestPath"', 'g'); - fileContents = fileContents.replace(setting_pytest_enabled, '.pytestEnabled"'); - fileContents = fileContents.replace(setting_pytest_args, '.pytestArgs"'); - fileContents = fileContents.replace(setting_pytest_path, '.pytestPath"'); - - const setting_pep8_args = new RegExp('\\.(? { - try { - const contents = await this.fs.readFile(filePath); - return ( - contents.indexOf('python.jediEnabled') > 0 || - contents.indexOf('python.unitTest.') > 0 || - contents.indexOf('.pyTest') > 0 || - contents.indexOf('.pep8') > 0 - ); - } catch (ex) { - traceError('Failed to check if file needs to be fixed', ex); - return false; - } - } - - private fixLanguageServerSettings(fileContent: string): string { - // `python.jediEnabled` is deprecated: - // - If missing, do nothing. - // - `true`, then set to `languageServer: Jedi`. - // - `false` and `languageServer` is present, do nothing. - // - `false` and `languageServer` is NOT present, set `languageServer` to `Microsoft`. - // `jediEnabled` is NOT removed since JSONC parser may also remove comments. - const jediEnabledPath = ['python.jediEnabled']; - const languageServerPath = ['python.languageServer']; - - try { - const ast = parseTree(fileContent); - - const jediEnabledNode = findNodeAtLocation(ast, jediEnabledPath); - const languageServerNode = findNodeAtLocation(ast, languageServerPath); - - // If missing, do nothing. - if (!jediEnabledNode) { - return fileContent; - } - - const jediEnabled = getNodeValue(jediEnabledNode); - - const modificationOptions: ModificationOptions = { - formattingOptions: { - tabSize: 4, - insertSpaces: true - } - }; - - // `jediEnabled` is true, set it to Jedi. - if (jediEnabled) { - return applyEdits( - fileContent, - modify(fileContent, languageServerPath, LanguageServerType.Jedi, modificationOptions) - ); - } - - // `jediEnabled` is false. if languageServer is missing, set it to Microsoft. - if (!languageServerNode) { - return applyEdits( - fileContent, - modify(fileContent, languageServerPath, LanguageServerType.Microsoft, modificationOptions) - ); - } - - // tslint:disable-next-line:no-empty - } catch {} - return fileContent; - } -} diff --git a/src/client/testing/common/xUnitParser.ts b/src/client/testing/common/xUnitParser.ts deleted file mode 100644 index 76ed830672ba..000000000000 --- a/src/client/testing/common/xUnitParser.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { inject, injectable } from 'inversify'; -import { IFileSystem } from '../../common/platform/types'; -import { FlattenedTestFunction, IXUnitParser, TestFunction, TestResult, Tests, TestStatus, TestSummary } from './types'; - -type TestSuiteResult = { - $: { - errors: string; - failures: string; - name: string; - skips: string; - skip: string; - tests: string; - time: string; - }; - testcase: TestCaseResult[]; -}; -type TestCaseResult = { - $: { - classname: string; - file: string; - line: string; - name: string; - time: string; - }; - failure: { - _: string; - $: { message: string; type: string }; - }[]; - error: { - _: string; - $: { message: string; type: string }; - }[]; - skipped: { - _: string; - $: { message: string; type: string }; - }[]; -}; - -// tslint:disable-next-line:no-any -function getSafeInt(value: string, defaultValue: any = 0): number { - const num = parseInt(value, 10); - if (isNaN(num)) { - return defaultValue; - } - return num; -} - -@injectable() -export class XUnitParser implements IXUnitParser { - constructor(@inject(IFileSystem) private readonly fs: IFileSystem) {} - - // Update "tests" with the results parsed from the given file. - public async updateResultsFromXmlLogFile(tests: Tests, outputXmlFile: string) { - const data = await this.fs.readFile(outputXmlFile); - - const parserResult = await parseXML(data); - const junitResults = getJunitResults(parserResult); - if (junitResults) { - updateTests(tests, junitResults); - } - } -} - -// An async wrapper around xml2js.parseString(). -// tslint:disable-next-line:no-any -async function parseXML(data: string): Promise { - const xml2js = await import('xml2js'); - // tslint:disable-next-line:no-any - return new Promise((resolve, reject) => { - // tslint:disable-next-line:no-any - xml2js.parseString(data, (error: Error, result: any) => { - if (error) { - return reject(error); - } - return resolve(result); - }); - }); -} - -// Return the actual test results from the given data. -// tslint:disable-next-line:no-any -function getJunitResults(parserResult: any): TestSuiteResult | undefined { - // This is the newer JUnit XML format (e.g. pytest 5.1 and later). - const fullResults = parserResult as { testsuites: { testsuite: TestSuiteResult[] } }; - if (!fullResults.testsuites) { - return (parserResult as { testsuite: TestSuiteResult }).testsuite; - } - - const junitSuites = fullResults.testsuites.testsuite; - if (!Array.isArray(junitSuites)) { - throw Error('bad JUnit XML data'); - } - if (junitSuites.length === 0) { - return; - } - if (junitSuites.length > 1) { - throw Error('got multiple XML results'); - } - return junitSuites[0]; -} - -// Update "tests" with the given results. -function updateTests(tests: Tests, testSuiteResult: TestSuiteResult) { - updateSummary(tests.summary, testSuiteResult); - - if (!Array.isArray(testSuiteResult.testcase)) { - return; - } - - // Update the results for each test. - // Previously unknown tests are ignored. - testSuiteResult.testcase.forEach((testcase: TestCaseResult) => { - const testFunc = findTestFunction(tests.testFunctions, testcase.$.classname, testcase.$.name); - if (testFunc) { - updateResultInfo(testFunc, testcase); - updateResultStatus(testFunc, testcase); - } else { - // Possible we're dealing with nosetests, where the file name isn't returned to us - // When dealing with nose tests - // It is possible to have a test file named x in two separate test sub directories and have same functions/classes - // And unforutnately xunit log doesn't ouput the filename - - // result = tests.testFunctions.find(fn => fn.testFunction.name === testcase.$.name && - // fn.parentTestSuite && fn.parentTestSuite.name === testcase.$.classname); - - // Look for failed file test - const fileTest = testcase.$.file && tests.testFiles.find((file) => file.nameToRun === testcase.$.file); - if (fileTest && testcase.error) { - updateResultStatus(fileTest, testcase); - } - } - }); -} - -// Update the summary with the information in the given results. -function updateSummary(summary: TestSummary, testSuiteResult: TestSuiteResult) { - summary.errors = getSafeInt(testSuiteResult.$.errors); - summary.failures = getSafeInt(testSuiteResult.$.failures); - summary.skipped = getSafeInt(testSuiteResult.$.skips ? testSuiteResult.$.skips : testSuiteResult.$.skip); - const testCount = getSafeInt(testSuiteResult.$.tests); - summary.passed = testCount - summary.failures - summary.skipped - summary.errors; -} - -function findTestFunction( - candidates: FlattenedTestFunction[], - className: string, - funcName: string -): TestFunction | undefined { - const xmlClassName = className.replace(/\(\)/g, '').replace(/\.\./g, '.').replace(/\.\./g, '.').replace(/\.+$/, ''); - const flattened = candidates.find((fn) => fn.xmlClassName === xmlClassName && fn.testFunction.name === funcName); - if (!flattened) { - return; - } - return flattened.testFunction; -} - -function updateResultInfo(result: TestResult, testCase: TestCaseResult) { - result.file = testCase.$.file; - result.line = getSafeInt(testCase.$.line, null); - result.time = parseFloat(testCase.$.time); -} - -function updateResultStatus(result: TestResult, testCase: TestCaseResult) { - if (testCase.error) { - result.status = TestStatus.Error; - result.passed = false; - result.message = testCase.error[0].$.message; - result.traceback = testCase.error[0]._; - } else if (testCase.failure) { - result.status = TestStatus.Fail; - result.passed = false; - result.message = testCase.failure[0].$.message; - result.traceback = testCase.failure[0]._; - } else if (testCase.skipped) { - result.status = TestStatus.Skipped; - result.passed = undefined; - result.message = testCase.skipped[0].$.message; - result.traceback = ''; - } else { - result.status = TestStatus.Pass; - result.passed = true; - } -} diff --git a/src/client/testing/configuration.ts b/src/client/testing/configuration.ts deleted file mode 100644 index 6c7adde7ede1..000000000000 --- a/src/client/testing/configuration.ts +++ /dev/null @@ -1,157 +0,0 @@ -'use strict'; - -import { inject, injectable } from 'inversify'; -import { Uri } from 'vscode'; -import { IApplicationShell, IWorkspaceService } from '../common/application/types'; -import { traceError } from '../common/logger'; -import { IConfigurationService, Product } from '../common/types'; -import { IServiceContainer } from '../ioc/types'; -import { sendTelemetryEvent } from '../telemetry'; -import { EventName } from '../telemetry/constants'; -import { TestConfiguringTelemetry, TestTool } from '../telemetry/types'; -import { BufferedTestConfigSettingsService } from './common/services/configSettingService'; -import { ITestsHelper, UnitTestProduct } from './common/types'; -import { - ITestConfigSettingsService, - ITestConfigurationManager, - ITestConfigurationManagerFactory, - ITestConfigurationService -} from './types'; - -@injectable() -export class UnitTestConfigurationService implements ITestConfigurationService { - private readonly configurationService: IConfigurationService; - private readonly appShell: IApplicationShell; - private readonly workspaceService: IWorkspaceService; - constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { - this.configurationService = serviceContainer.get(IConfigurationService); - this.appShell = serviceContainer.get(IApplicationShell); - this.workspaceService = serviceContainer.get(IWorkspaceService); - } - public async displayTestFrameworkError(wkspace: Uri): Promise { - const settings = this.configurationService.getSettings(wkspace); - let enabledCount = settings.testing.pytestEnabled ? 1 : 0; - enabledCount += settings.testing.nosetestsEnabled ? 1 : 0; - enabledCount += settings.testing.unittestEnabled ? 1 : 0; - if (enabledCount > 1) { - return this._promptToEnableAndConfigureTestFramework( - wkspace, - 'Enable only one of the test frameworks (unittest, pytest or nosetest).', - true - ); - } else { - const option = 'Enable and configure a Test Framework'; - const item = await this.appShell.showInformationMessage( - 'No test framework configured (unittest, pytest or nosetest)', - option - ); - if (item === option) { - return this._promptToEnableAndConfigureTestFramework(wkspace); - } - return Promise.reject(null); - } - } - public async selectTestRunner(placeHolderMessage: string): Promise { - const items = [ - { - label: 'unittest', - product: Product.unittest, - description: 'Standard Python test framework', - detail: 'https://docs.python.org/3/library/unittest.html' - }, - { - label: 'pytest', - product: Product.pytest, - description: 'pytest framework', - // tslint:disable-next-line:no-http-string - detail: 'http://docs.pytest.org/' - }, - { - label: 'nose', - product: Product.nosetest, - description: 'nose framework', - detail: 'https://nose.readthedocs.io/' - } - ]; - const options = { - ignoreFocusOut: true, - matchOnDescription: true, - matchOnDetail: true, - placeHolder: placeHolderMessage - }; - const selectedTestRunner = await this.appShell.showQuickPick(items, options); - // tslint:disable-next-line:prefer-type-cast - return selectedTestRunner ? (selectedTestRunner.product as UnitTestProduct) : undefined; - } - public async enableTest(wkspace: Uri, product: UnitTestProduct): Promise { - const factory = this.serviceContainer.get(ITestConfigurationManagerFactory); - const configMgr = factory.create(wkspace, product); - return this._enableTest(wkspace, configMgr); - } - - public async promptToEnableAndConfigureTestFramework(wkspace: Uri) { - await this._promptToEnableAndConfigureTestFramework(wkspace, undefined, false, 'commandpalette'); - } - - private _enableTest(wkspace: Uri, configMgr: ITestConfigurationManager) { - const pythonConfig = this.workspaceService.getConfiguration('python', wkspace); - if (pythonConfig.get('testing.promptToConfigure')) { - return configMgr.enable(); - } - return pythonConfig.update('testing.promptToConfigure', undefined).then( - () => { - return configMgr.enable(); - }, - (reason) => { - return configMgr.enable().then(() => Promise.reject(reason)); - } - ); - } - - private async _promptToEnableAndConfigureTestFramework( - wkspace: Uri, - messageToDisplay: string = 'Select a test framework/tool to enable', - enableOnly: boolean = false, - trigger: 'ui' | 'commandpalette' = 'ui' - ) { - const telemetryProps: TestConfiguringTelemetry = { - trigger: trigger, - failed: false - }; - try { - const selectedTestRunner = await this.selectTestRunner(messageToDisplay); - if (typeof selectedTestRunner !== 'number') { - return Promise.reject(null); - } - const helper = this.serviceContainer.get(ITestsHelper); - telemetryProps.tool = helper.parseProviderName(selectedTestRunner) as TestTool; - const delayed = new BufferedTestConfigSettingsService(); - const factory = this.serviceContainer.get( - ITestConfigurationManagerFactory - ); - const configMgr = factory.create(wkspace, selectedTestRunner, delayed); - if (enableOnly) { - await configMgr.enable(); - } else { - // Configure everything before enabling. - // Cuz we don't want the test engine (in main.ts file - tests get discovered when config changes are detected) - // to start discovering tests when tests haven't been configured properly. - await configMgr - .configure(wkspace) - .then(() => this._enableTest(wkspace, configMgr)) - .catch((reason) => { - return this._enableTest(wkspace, configMgr).then(() => Promise.reject(reason)); - }); - } - const cfg = this.serviceContainer.get(ITestConfigSettingsService); - try { - await delayed.apply(cfg); - } catch (exc) { - traceError('Python Extension: applying unit test config updates', exc); - telemetryProps.failed = true; - } - } finally { - sendTelemetryEvent(EventName.UNITTEST_CONFIGURING, undefined, telemetryProps); - } - } -} diff --git a/src/client/testing/configuration/index.ts b/src/client/testing/configuration/index.ts new file mode 100644 index 000000000000..b78475293594 --- /dev/null +++ b/src/client/testing/configuration/index.ts @@ -0,0 +1,135 @@ +'use strict'; + +import { inject, injectable } from 'inversify'; +import { Uri } from 'vscode'; +import { IApplicationShell, IWorkspaceService } from '../../common/application/types'; +import { IConfigurationService, Product } from '../../common/types'; +import { IServiceContainer } from '../../ioc/types'; +import { traceError } from '../../logging'; +import { sendTelemetryEvent } from '../../telemetry'; +import { EventName } from '../../telemetry/constants'; +import { TestConfiguringTelemetry } from '../../telemetry/types'; +import { BufferedTestConfigSettingsService } from '../common/bufferedTestConfigSettingService'; +import { + ITestConfigSettingsService, + ITestConfigurationManager, + ITestConfigurationManagerFactory, + ITestConfigurationService, + ITestsHelper, + UnitTestProduct, +} from '../common/types'; + +export const NONE_SELECTED = Error('none selected'); + +@injectable() +export class UnitTestConfigurationService implements ITestConfigurationService { + private readonly configurationService: IConfigurationService; + + private readonly appShell: IApplicationShell; + + private readonly workspaceService: IWorkspaceService; + + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { + this.configurationService = serviceContainer.get(IConfigurationService); + this.appShell = serviceContainer.get(IApplicationShell); + this.workspaceService = serviceContainer.get(IWorkspaceService); + } + + public hasConfiguredTests(wkspace: Uri): boolean { + const settings = this.configurationService.getSettings(wkspace); + return settings.testing.pytestEnabled || settings.testing.unittestEnabled || false; + } + + public async selectTestRunner(placeHolderMessage: string): Promise { + const items = [ + { + label: 'unittest', + product: Product.unittest, + description: 'Standard Python test framework', + detail: 'https://docs.python.org/3/library/unittest.html', + }, + { + label: 'pytest', + product: Product.pytest, + description: 'pytest framework', + + detail: 'http://docs.pytest.org/', + }, + ]; + const options = { + ignoreFocusOut: true, + matchOnDescription: true, + matchOnDetail: true, + placeHolder: placeHolderMessage, + }; + const selectedTestRunner = await this.appShell.showQuickPick(items, options); + + return selectedTestRunner ? (selectedTestRunner.product as UnitTestProduct) : undefined; + } + + public async enableTest(wkspace: Uri, product: UnitTestProduct): Promise { + const factory = this.serviceContainer.get(ITestConfigurationManagerFactory); + const configMgr = factory.create(wkspace, product); + return this._enableTest(wkspace, configMgr); + } + + public async promptToEnableAndConfigureTestFramework(wkspace: Uri): Promise { + await this._promptToEnableAndConfigureTestFramework(wkspace, undefined, false, 'commandpalette'); + } + + private _enableTest(wkspace: Uri, configMgr: ITestConfigurationManager) { + const pythonConfig = this.workspaceService.getConfiguration('python', wkspace); + if (pythonConfig.get('testing.promptToConfigure')) { + return configMgr.enable(); + } + return pythonConfig.update('testing.promptToConfigure', undefined).then( + () => configMgr.enable(), + (reason) => configMgr.enable().then(() => Promise.reject(reason)), + ); + } + + private async _promptToEnableAndConfigureTestFramework( + wkspace: Uri, + messageToDisplay = 'Select a test framework/tool to enable', + enableOnly = false, + trigger: 'ui' | 'commandpalette' = 'ui', + ): Promise { + const telemetryProps: TestConfiguringTelemetry = { + trigger, + failed: false, + }; + try { + const selectedTestRunner = await this.selectTestRunner(messageToDisplay); + if (typeof selectedTestRunner !== 'number') { + throw NONE_SELECTED; + } + const helper = this.serviceContainer.get(ITestsHelper); + telemetryProps.tool = helper.parseProviderName(selectedTestRunner); + const delayed = new BufferedTestConfigSettingsService(); + const factory = this.serviceContainer.get( + ITestConfigurationManagerFactory, + ); + const configMgr = factory.create(wkspace, selectedTestRunner, delayed); + if (enableOnly) { + await configMgr.enable(); + } else { + // Configure everything before enabling. + // Cuz we don't want the test engine (in main.ts file - tests get discovered when config changes are detected) + // to start discovering tests when tests haven't been configured properly. + await configMgr + .configure(wkspace) + .then(() => this._enableTest(wkspace, configMgr)) + .catch((reason) => this._enableTest(wkspace, configMgr).then(() => Promise.reject(reason))); + } + const cfg = this.serviceContainer.get(ITestConfigSettingsService); + try { + await delayed.apply(cfg); + } catch (exc) { + traceError('Python Extension: applying unit test config updates', exc); + telemetryProps.failed = true; + } + } finally { + sendTelemetryEvent(EventName.UNITTEST_CONFIGURING, undefined, telemetryProps); + } + } +} diff --git a/src/client/testing/configuration/pytest/testConfigurationManager.ts b/src/client/testing/configuration/pytest/testConfigurationManager.ts new file mode 100644 index 000000000000..08f88f8564c7 --- /dev/null +++ b/src/client/testing/configuration/pytest/testConfigurationManager.ts @@ -0,0 +1,78 @@ +import * as path from 'path'; +import { QuickPickItem, Uri } from 'vscode'; +import { IFileSystem } from '../../../common/platform/types'; +import { Product } from '../../../common/types'; +import { IServiceContainer } from '../../../ioc/types'; +import { IApplicationShell } from '../../../common/application/types'; +import { TestConfigurationManager } from '../../common/testConfigurationManager'; +import { ITestConfigSettingsService } from '../../common/types'; +import { PytestInstallationHelper } from '../pytestInstallationHelper'; +import { traceInfo } from '../../../logging'; + +export class ConfigurationManager extends TestConfigurationManager { + private readonly pytestInstallationHelper: PytestInstallationHelper; + + constructor(workspace: Uri, serviceContainer: IServiceContainer, cfg?: ITestConfigSettingsService) { + super(workspace, Product.pytest, serviceContainer, cfg); + const appShell = serviceContainer.get(IApplicationShell); + this.pytestInstallationHelper = new PytestInstallationHelper(appShell); + } + + public async requiresUserToConfigure(wkspace: Uri): Promise { + const configFiles = await this.getConfigFiles(wkspace.fsPath); + // If a config file exits, there's nothing to be configured. + if (configFiles.length > 0 && configFiles.length !== 1 && configFiles[0] !== 'setup.cfg') { + return false; + } + return true; + } + + public async configure(wkspace: Uri): Promise { + const args: string[] = []; + const configFileOptionLabel = 'Use existing config file'; + const options: QuickPickItem[] = []; + const configFiles = await this.getConfigFiles(wkspace.fsPath); + // If a config file exits, there's nothing to be configured. + if (configFiles.length > 0 && configFiles.length !== 1 && configFiles[0] !== 'setup.cfg') { + return; + } + + if (configFiles.length === 1 && configFiles[0] === 'setup.cfg') { + options.push({ + label: configFileOptionLabel, + description: 'setup.cfg', + }); + } + const subDirs = await this.getTestDirs(wkspace.fsPath); + const testDir = await this.selectTestDir(wkspace.fsPath, subDirs, options); + if (typeof testDir === 'string' && testDir !== configFileOptionLabel) { + args.push(testDir); + } + const installed = await this.installer.isInstalled(Product.pytest); + await this.testConfigSettingsService.updateTestArgs(wkspace.fsPath, Product.pytest, args); + if (!installed) { + // Check if Python Environments extension is available for enhanced installation flow + if (this.pytestInstallationHelper.isEnvExtensionAvailable()) { + traceInfo('pytest not installed, prompting user with environment extension integration'); + const installAttempted = await this.pytestInstallationHelper.promptToInstallPytest(wkspace); + if (!installAttempted) { + // User chose to ignore or installation failed + return; + } + } else { + // Fall back to traditional installer + traceInfo('pytest not installed, falling back to traditional installer'); + await this.installer.install(Product.pytest); + } + } + } + + private async getConfigFiles(rootDir: string): Promise { + const fs = this.serviceContainer.get(IFileSystem); + const promises = ['pytest.ini', 'tox.ini', 'setup.cfg'].map(async (cfg) => + (await fs.fileExists(path.join(rootDir, cfg))) ? cfg : '', + ); + const values = await Promise.all(promises); + return values.filter((exists) => exists.length > 0); + } +} diff --git a/src/client/testing/configuration/pytestInstallationHelper.ts b/src/client/testing/configuration/pytestInstallationHelper.ts new file mode 100644 index 000000000000..bd5fbcd5bb37 --- /dev/null +++ b/src/client/testing/configuration/pytestInstallationHelper.ts @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { Uri, l10n } from 'vscode'; +import { IApplicationShell } from '../../common/application/types'; +import { traceInfo, traceError } from '../../logging'; +import { useEnvExtension, getEnvExtApi } from '../../envExt/api.internal'; +import { getEnvironment } from '../../envExt/api.internal'; + +/** + * Helper class to handle pytest installation using the appropriate method + * based on whether the Python Environments extension is available. + */ +export class PytestInstallationHelper { + constructor(private readonly appShell: IApplicationShell) {} + + /** + * Prompts the user to install pytest with appropriate installation method. + * @param workspaceUri The workspace URI where pytest should be installed + * @returns Promise that resolves to true if installation was attempted, false otherwise + */ + async promptToInstallPytest(workspaceUri: Uri): Promise { + const message = l10n.t('pytest selected but not installed. Would you like to install pytest?'); + const installOption = l10n.t('Install pytest'); + + const selection = await this.appShell.showInformationMessage(message, { modal: true }, installOption); + + if (selection === installOption) { + return this.installPytest(workspaceUri); + } + + return false; + } + + /** + * Installs pytest using the appropriate method based on available extensions. + * @param workspaceUri The workspace URI where pytest should be installed + * @returns Promise that resolves to true if installation was successful, false otherwise + */ + private async installPytest(workspaceUri: Uri): Promise { + try { + if (useEnvExtension()) { + return this.installPytestWithEnvExtension(workspaceUri); + } else { + // Fall back to traditional installer if environments extension is not available + traceInfo( + 'Python Environments extension not available, installation cannot proceed via environment extension', + ); + return false; + } + } catch (error) { + traceError('Error installing pytest:', error); + return false; + } + } + + /** + * Installs pytest using the Python Environments extension. + * @param workspaceUri The workspace URI where pytest should be installed + * @returns Promise that resolves to true if installation was successful, false otherwise + */ + private async installPytestWithEnvExtension(workspaceUri: Uri): Promise { + try { + const envExtApi = await getEnvExtApi(); + const environment = await getEnvironment(workspaceUri); + + if (!environment) { + traceError('No Python environment found for workspace:', workspaceUri.fsPath); + await this.appShell.showErrorMessage( + l10n.t('No Python environment found. Please set up a Python environment first.'), + ); + return false; + } + + traceInfo('Installing pytest using Python Environments extension...'); + await envExtApi.managePackages(environment, { + install: ['pytest'], + }); + + traceInfo('pytest installation completed successfully'); + return true; + } catch (error) { + traceError('Failed to install pytest using Python Environments extension:', error); + return false; + } + } + + /** + * Checks if the Python Environments extension is available for package management. + * @returns True if the extension is available, false otherwise + */ + isEnvExtensionAvailable(): boolean { + return useEnvExtension(); + } +} diff --git a/src/client/testing/configuration/types.ts b/src/client/testing/configuration/types.ts new file mode 100644 index 000000000000..3b759bcb39e8 --- /dev/null +++ b/src/client/testing/configuration/types.ts @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +export interface ITestingSettings { + readonly promptToConfigure: boolean; + readonly debugPort: number; + readonly pytestEnabled: boolean; + pytestPath: string; + pytestArgs: string[]; + readonly unittestEnabled: boolean; + unittestArgs: string[]; + cwd?: string; + readonly autoTestDiscoverOnSaveEnabled: boolean; + readonly autoTestDiscoverOnSavePattern: string; +} + +export type TestSettingsPropertyNames = { + enabledName: keyof ITestingSettings; + argsName: keyof ITestingSettings; + pathName?: keyof ITestingSettings; +}; diff --git a/src/client/testing/configuration/unittest/testConfigurationManager.ts b/src/client/testing/configuration/unittest/testConfigurationManager.ts new file mode 100644 index 000000000000..b1482c2a42bc --- /dev/null +++ b/src/client/testing/configuration/unittest/testConfigurationManager.ts @@ -0,0 +1,37 @@ +import { Uri } from 'vscode'; +import { Product } from '../../../common/types'; +import { IServiceContainer } from '../../../ioc/types'; +import { TestConfigurationManager } from '../../common/testConfigurationManager'; +import { ITestConfigSettingsService } from '../../common/types'; + +export class ConfigurationManager extends TestConfigurationManager { + constructor(workspace: Uri, serviceContainer: IServiceContainer, cfg?: ITestConfigSettingsService) { + super(workspace, Product.unittest, serviceContainer, cfg); + } + + // eslint-disable-next-line class-methods-use-this + public async requiresUserToConfigure(_wkspace: Uri): Promise { + return true; + } + + public async configure(wkspace: Uri): Promise { + const args = ['-v']; + const subDirs = await this.getTestDirs(wkspace.fsPath); + const testDir = await this.selectTestDir(wkspace.fsPath, subDirs); + args.push('-s'); + if (typeof testDir === 'string' && testDir !== '.') { + args.push(`./${testDir}`); + } else { + args.push('.'); + } + + const testfilePattern = await this.selectTestFilePattern(); + args.push('-p'); + if (typeof testfilePattern === 'string') { + args.push(testfilePattern); + } else { + args.push('test*.py'); + } + await this.testConfigSettingsService.updateTestArgs(wkspace.fsPath, Product.unittest, args); + } +} diff --git a/src/client/testing/configurationFactory.ts b/src/client/testing/configurationFactory.ts index 72a9065260e9..b661a38d5779 100644 --- a/src/client/testing/configurationFactory.ts +++ b/src/client/testing/configurationFactory.ts @@ -7,10 +7,13 @@ import { inject, injectable } from 'inversify'; import { Uri } from 'vscode'; import { Product } from '../common/types'; import { IServiceContainer } from '../ioc/types'; -import * as nose from './nosetest/testConfigurationManager'; -import * as pytest from './pytest/testConfigurationManager'; -import { ITestConfigSettingsService, ITestConfigurationManager, ITestConfigurationManagerFactory } from './types'; -import * as unittest from './unittest/testConfigurationManager'; +import * as pytest from './configuration/pytest/testConfigurationManager'; +import { + ITestConfigSettingsService, + ITestConfigurationManager, + ITestConfigurationManagerFactory, +} from './common/types'; +import * as unittest from './configuration/unittest/testConfigurationManager'; @injectable() export class TestConfigurationManagerFactory implements ITestConfigurationManagerFactory { @@ -23,9 +26,6 @@ export class TestConfigurationManagerFactory implements ITestConfigurationManage case Product.pytest: { return new pytest.ConfigurationManager(wkspace, this.serviceContainer, cfg); } - case Product.nosetest: { - return new nose.ConfigurationManager(wkspace, this.serviceContainer, cfg); - } default: { throw new Error('Invalid test configuration'); } diff --git a/src/client/testing/display/main.ts b/src/client/testing/display/main.ts deleted file mode 100644 index 319ec5d2e3f9..000000000000 --- a/src/client/testing/display/main.ts +++ /dev/null @@ -1,224 +0,0 @@ -'use strict'; -import { inject, injectable } from 'inversify'; -import { Event, EventEmitter, StatusBarAlignment, StatusBarItem } from 'vscode'; -import { IApplicationShell, ICommandManager } from '../../common/application/types'; -import * as constants from '../../common/constants'; -import { isNotInstalledError } from '../../common/helpers'; -import { traceError } from '../../common/logger'; -import { IConfigurationService } from '../../common/types'; -import { Testing } from '../../common/utils/localize'; -import { noop } from '../../common/utils/misc'; -import { IServiceContainer } from '../../ioc/types'; -import { captureTelemetry } from '../../telemetry'; -import { EventName } from '../../telemetry/constants'; -import { CANCELLATION_REASON } from '../common/constants'; -import { ITestsHelper, Tests } from '../common/types'; -import { ITestResultDisplay } from '../types'; - -@injectable() -export class TestResultDisplay implements ITestResultDisplay { - private statusBar: StatusBarItem; - private discoverCounter = 0; - private ticker = ['|', '/', '-', '|', '/', '-', '\\']; - private progressTimeout: NodeJS.Timer | number | null = null; - private _enabled: boolean = false; - private progressPrefix!: string; - private readonly didChange = new EventEmitter(); - private readonly appShell: IApplicationShell; - private readonly testsHelper: ITestsHelper; - private readonly cmdManager: ICommandManager; - public get onDidChange(): Event { - return this.didChange.event; - } - - // tslint:disable-next-line:no-any - constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { - this.appShell = serviceContainer.get(IApplicationShell); - this.statusBar = this.appShell.createStatusBarItem(StatusBarAlignment.Left); - this.testsHelper = serviceContainer.get(ITestsHelper); - this.cmdManager = serviceContainer.get(ICommandManager); - } - public dispose() { - this.clearProgressTicker(); - this.statusBar.dispose(); - } - public get enabled() { - return this._enabled; - } - public set enabled(enable: boolean) { - this._enabled = enable; - if (enable) { - this.statusBar.show(); - } else { - this.statusBar.hide(); - } - } - public displayProgressStatus(testRunResult: Promise, debug: boolean = false) { - this.displayProgress( - 'Running Tests', - 'Running Tests (Click to Stop)', - constants.Commands.Tests_Ask_To_Stop_Test - ); - testRunResult - .then((tests) => this.updateTestRunWithSuccess(tests, debug)) - .catch(this.updateTestRunWithFailure.bind(this)) - // We don't care about any other exceptions returned by updateTestRunWithFailure - .catch(noop); - } - public displayDiscoverStatus(testDiscovery: Promise, quietMode: boolean = false) { - this.displayProgress( - 'Discovering Tests', - 'Discovering tests (click to stop)', - constants.Commands.Tests_Ask_To_Stop_Discovery - ); - return testDiscovery - .then((tests) => { - this.updateWithDiscoverSuccess(tests, quietMode); - return tests; - }) - .catch((reason) => { - this.updateWithDiscoverFailure(reason); - return Promise.reject(reason); - }); - } - - private updateTestRunWithSuccess(tests: Tests, debug: boolean = false): Tests { - this.clearProgressTicker(); - - // Treat errors as a special case, as we generally wouldn't have any errors - const statusText: string[] = []; - const toolTip: string[] = []; - - if (tests.summary.passed > 0) { - statusText.push(`${constants.Octicons.Test_Pass} ${tests.summary.passed}`); - toolTip.push(`${tests.summary.passed} Passed`); - } - if (tests.summary.skipped > 0) { - statusText.push(`${constants.Octicons.Test_Skip} ${tests.summary.skipped}`); - toolTip.push(`${tests.summary.skipped} Skipped`); - } - if (tests.summary.failures > 0) { - statusText.push(`${constants.Octicons.Test_Fail} ${tests.summary.failures}`); - toolTip.push(`${tests.summary.failures} Failed`); - } - if (tests.summary.errors > 0) { - statusText.push(`${constants.Octicons.Test_Error} ${tests.summary.errors}`); - toolTip.push(`${tests.summary.errors} Error${tests.summary.errors > 1 ? 's' : ''}`); - } - this.statusBar.tooltip = toolTip.length === 0 ? 'No Tests Ran' : `${toolTip.join(', ')} (Tests)`; - this.statusBar.text = statusText.length === 0 ? 'No Tests Ran' : statusText.join(' '); - this.statusBar.command = constants.Commands.Tests_View_UI; - this.didChange.fire(); - if (statusText.length === 0 && !debug) { - this.appShell.showWarningMessage('No tests ran, please check the configuration settings for the tests.'); - } - return tests; - } - - // tslint:disable-next-line:no-any - private updateTestRunWithFailure(reason: any): Promise { - this.clearProgressTicker(); - this.statusBar.command = constants.Commands.Tests_View_UI; - if (reason === CANCELLATION_REASON) { - this.statusBar.text = '$(zap) Run Tests'; - this.statusBar.tooltip = 'Run Tests'; - } else { - this.statusBar.text = '$(alert) Tests Failed'; - this.statusBar.tooltip = 'Running Tests Failed'; - this.testsHelper.displayTestErrorMessage('There was an error in running the tests.'); - } - return Promise.reject(reason); - } - - private displayProgress(message: string, tooltip: string, command: string) { - this.progressPrefix = this.statusBar.text = `$(stop) ${message}`; - this.statusBar.command = command; - this.statusBar.tooltip = tooltip; - this.statusBar.show(); - this.clearProgressTicker(); - this.progressTimeout = setInterval(() => this.updateProgressTicker(), 1000); - } - private updateProgressTicker() { - const text = `${this.progressPrefix} ${this.ticker[this.discoverCounter % 7]}`; - this.discoverCounter += 1; - this.statusBar.text = text; - } - private clearProgressTicker() { - if (this.progressTimeout) { - // tslint:disable-next-line: no-any - clearInterval(this.progressTimeout as any); - } - this.progressTimeout = null; - this.discoverCounter = 0; - } - - @captureTelemetry(EventName.UNITTEST_DISABLE) - // tslint:disable-next-line:no-any - private async disableTests(): Promise { - const configurationService = this.serviceContainer.get(IConfigurationService); - const settingsToDisable = [ - 'testing.promptToConfigure', - 'testing.pytestEnabled', - 'testing.unittestEnabled', - 'testing.nosetestsEnabled' - ]; - - for (const setting of settingsToDisable) { - await configurationService.updateSetting(setting, false).catch(noop); - } - this.cmdManager.executeCommand('setContext', 'testsDiscovered', false); - } - - private updateWithDiscoverSuccess(tests: Tests, quietMode: boolean = false) { - this.clearProgressTicker(); - const haveTests = tests && tests.testFunctions.length > 0; - this.statusBar.text = '$(zap) Run Tests'; - this.statusBar.tooltip = 'Run Tests'; - this.statusBar.command = constants.Commands.Tests_View_UI; - this.statusBar.show(); - if (this.didChange) { - this.didChange.fire(); - } - - if (!haveTests && !quietMode) { - this.appShell - .showInformationMessage( - 'No tests discovered, please check the configuration settings for the tests.', - Testing.disableTests(), - Testing.configureTests() - ) - .then((item) => { - if (item === Testing.disableTests()) { - this.disableTests().catch((ex) => traceError('Python Extension: disableTests', ex)); - } else if (item === Testing.configureTests()) { - this.cmdManager - .executeCommand(constants.Commands.Tests_Configure, undefined, undefined, undefined) - .then(noop); - } - }); - } - } - - // tslint:disable-next-line:no-any - private updateWithDiscoverFailure(reason: any) { - this.clearProgressTicker(); - this.statusBar.text = '$(zap) Discover Tests'; - this.statusBar.tooltip = 'Discover Tests'; - this.statusBar.command = constants.Commands.Tests_Discover; - this.statusBar.show(); - if (reason !== CANCELLATION_REASON) { - this.statusBar.text = '$(alert) Test discovery failed'; - this.statusBar.tooltip = "Discovering Tests failed (view 'Python Test Log' output panel for details)"; - // tslint:disable-next-line:no-suspicious-comment - // TODO: ignore this quitemode, always display the error message (inform the user). - if (!isNotInstalledError(reason)) { - // tslint:disable-next-line:no-suspicious-comment - // TODO: show an option that will invoke a command 'python.test.configureTest' or similar. - // This will be hanlded by main.ts that will capture input from user and configure the tests. - this.appShell.showErrorMessage( - 'Test discovery error, please check the configuration settings for the tests.' - ); - } - } - } -} diff --git a/src/client/testing/display/picker.ts b/src/client/testing/display/picker.ts deleted file mode 100644 index 7ad8657d337f..000000000000 --- a/src/client/testing/display/picker.ts +++ /dev/null @@ -1,353 +0,0 @@ -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { QuickPickItem, Uri } from 'vscode'; -import { IApplicationShell, ICommandManager } from '../../common/application/types'; -import * as constants from '../../common/constants'; -import { IFileSystem } from '../../common/platform/types'; -import { IServiceContainer } from '../../ioc/types'; -import { CommandSource } from '../common/constants'; -import { - FlattenedTestFunction, - ITestCollectionStorageService, - TestFile, - TestFunction, - Tests, - TestStatus, - TestsToRun -} from '../common/types'; -import { ITestDisplay } from '../types'; - -@injectable() -export class TestDisplay implements ITestDisplay { - private readonly testCollectionStorage: ITestCollectionStorageService; - private readonly appShell: IApplicationShell; - constructor( - @inject(IServiceContainer) private readonly serviceRegistry: IServiceContainer, - @inject(ICommandManager) private readonly commandManager: ICommandManager - ) { - this.testCollectionStorage = serviceRegistry.get(ITestCollectionStorageService); - this.appShell = serviceRegistry.get(IApplicationShell); - } - public displayStopTestUI(workspace: Uri, message: string) { - this.appShell.showQuickPick([message]).then((item) => { - if (item === message) { - this.commandManager.executeCommand(constants.Commands.Tests_Stop, undefined, workspace); - } - }); - } - public displayTestUI(cmdSource: CommandSource, wkspace: Uri) { - const tests = this.testCollectionStorage.getTests(wkspace); - this.appShell - .showQuickPick(buildItems(tests), { matchOnDescription: true, matchOnDetail: true }) - .then((item) => - item ? onItemSelected(this.commandManager, cmdSource, wkspace, item, false) : Promise.resolve() - ); - } - public selectTestFunction(rootDirectory: string, tests: Tests): Promise { - return new Promise((resolve, reject) => { - this.appShell - .showQuickPick(buildItemsForFunctions(rootDirectory, tests.testFunctions), { - matchOnDescription: true, - matchOnDetail: true - }) - .then((item) => { - if (item && item.fn) { - return resolve(item.fn); - } - return reject(); - }, reject); - }); - } - public selectTestFile(rootDirectory: string, tests: Tests): Promise { - return new Promise((resolve, reject) => { - this.appShell - .showQuickPick(buildItemsForTestFiles(rootDirectory, tests.testFiles), { - matchOnDescription: true, - matchOnDetail: true - }) - .then((item) => { - if (item && item.testFile) { - return resolve(item.testFile); - } - return reject(); - }, reject); - }); - } - public displayFunctionTestPickerUI( - cmdSource: CommandSource, - wkspace: Uri, - rootDirectory: string, - file: Uri, - testFunctions: TestFunction[], - debug?: boolean - ) { - const tests = this.testCollectionStorage.getTests(wkspace); - if (!tests) { - return; - } - const fileName = file.fsPath; - const fs = this.serviceRegistry.get(IFileSystem); - const testFile = tests.testFiles.find( - (item) => item.name === fileName || fs.arePathsSame(item.fullPath, fileName) - ); - if (!testFile) { - return; - } - const flattenedFunctions = tests.testFunctions.filter((fn) => { - return ( - fn.parentTestFile.name === testFile.name && - testFunctions.some((testFunc) => testFunc.nameToRun === fn.testFunction.nameToRun) - ); - }); - const runAllItem = buildRunAllParametrizedItem(flattenedFunctions, debug); - const functionItems = buildItemsForFunctions(rootDirectory, flattenedFunctions, undefined, undefined, debug); - this.appShell - .showQuickPick(runAllItem.concat(...functionItems), { matchOnDescription: true, matchOnDetail: true }) - .then((testItem) => - testItem ? onItemSelected(this.commandManager, cmdSource, wkspace, testItem, debug) : Promise.resolve() - ); - } -} - -export enum Type { - RunAll = 0, - ReDiscover = 1, - RunFailed = 2, - RunFolder = 3, - RunFile = 4, - RunClass = 5, - RunMethod = 6, - ViewTestOutput = 7, - Null = 8, - SelectAndRunMethod = 9, - DebugMethod = 10, - Configure = 11, - RunParametrized = 12 -} -const statusIconMapping = new Map(); -statusIconMapping.set(TestStatus.Pass, constants.Octicons.Test_Pass); -statusIconMapping.set(TestStatus.Fail, constants.Octicons.Test_Fail); -statusIconMapping.set(TestStatus.Error, constants.Octicons.Test_Error); -statusIconMapping.set(TestStatus.Skipped, constants.Octicons.Test_Skip); - -type TestItem = QuickPickItem & { - type: Type; - fn?: FlattenedTestFunction; - fns?: TestFunction[]; -}; - -type TestFileItem = QuickPickItem & { - type: Type; - testFile?: TestFile; -}; - -function getSummary(tests?: Tests) { - if (!tests || !tests.summary) { - return ''; - } - const statusText: string[] = []; - if (tests.summary.passed > 0) { - statusText.push(`${constants.Octicons.Test_Pass} ${tests.summary.passed} Passed`); - } - if (tests.summary.failures > 0) { - statusText.push(`${constants.Octicons.Test_Fail} ${tests.summary.failures} Failed`); - } - if (tests.summary.errors > 0) { - const plural = tests.summary.errors === 1 ? '' : 's'; - statusText.push(`${constants.Octicons.Test_Error} ${tests.summary.errors} Error${plural}`); - } - if (tests.summary.skipped > 0) { - statusText.push(`${constants.Octicons.Test_Skip} ${tests.summary.skipped} Skipped`); - } - return statusText.join(', ').trim(); -} -function buildItems(tests?: Tests): TestItem[] { - const items: TestItem[] = []; - items.push({ description: '', label: 'Run All Tests', type: Type.RunAll }); - items.push({ description: '', label: 'Discover Tests', type: Type.ReDiscover }); - items.push({ description: '', label: 'Run Test Method ...', type: Type.SelectAndRunMethod }); - items.push({ description: '', label: 'Configure Tests', type: Type.Configure }); - - const summary = getSummary(tests); - items.push({ description: '', label: 'View Test Output', type: Type.ViewTestOutput, detail: summary }); - - if (tests && tests.summary.failures > 0) { - items.push({ - description: '', - label: 'Run Failed Tests', - type: Type.RunFailed, - detail: `${constants.Octicons.Test_Fail} ${tests.summary.failures} Failed` - }); - } - - return items; -} - -const statusSortPrefix = { - [TestStatus.Error]: '1', - [TestStatus.Fail]: '2', - [TestStatus.Skipped]: '3', - [TestStatus.Pass]: '4', - [TestStatus.Discovering]: undefined, - [TestStatus.Idle]: undefined, - [TestStatus.Running]: undefined, - [TestStatus.Unknown]: undefined -}; - -function buildRunAllParametrizedItem(tests: FlattenedTestFunction[], debug: boolean = false): TestItem[] { - const testFunctions: TestFunction[] = []; - tests.forEach((fn) => { - testFunctions.push(fn.testFunction); - }); - return [ - { - description: '', - label: debug ? 'Debug All' : 'Run All', - type: Type.RunParametrized, - fns: testFunctions - } - ]; -} -function buildItemsForFunctions( - rootDirectory: string, - tests: FlattenedTestFunction[], - sortBasedOnResults: boolean = false, - displayStatusIcons: boolean = false, - debug: boolean = false -): TestItem[] { - const functionItems: TestItem[] = []; - tests.forEach((fn) => { - let icon = ''; - if (displayStatusIcons && fn.testFunction.status && statusIconMapping.has(fn.testFunction.status)) { - icon = `${statusIconMapping.get(fn.testFunction.status)} `; - } - - functionItems.push({ - description: '', - detail: path.relative(rootDirectory, fn.parentTestFile.fullPath), - label: icon + fn.testFunction.name, - type: debug === true ? Type.DebugMethod : Type.RunMethod, - fn: fn - }); - }); - functionItems.sort((a, b) => { - let sortAPrefix = '5-'; - let sortBPrefix = '5-'; - if (sortBasedOnResults && a.fn && a.fn.testFunction.status && b.fn && b.fn.testFunction.status) { - sortAPrefix = statusSortPrefix[a.fn.testFunction.status] - ? statusSortPrefix[a.fn.testFunction.status]! - : sortAPrefix; - sortBPrefix = statusSortPrefix[b.fn.testFunction.status] - ? statusSortPrefix[b.fn.testFunction.status]! - : sortBPrefix; - } - if (`${sortAPrefix}${a.detail}${a.label}` < `${sortBPrefix}${b.detail}${b.label}`) { - return -1; - } - if (`${sortAPrefix}${a.detail}${a.label}` > `${sortBPrefix}${b.detail}${b.label}`) { - return 1; - } - return 0; - }); - return functionItems; -} -function buildItemsForTestFiles(rootDirectory: string, testFiles: TestFile[]): TestFileItem[] { - const fileItems: TestFileItem[] = testFiles.map((testFile) => { - return { - description: '', - detail: path.relative(rootDirectory, testFile.fullPath), - type: Type.RunFile, - label: path.basename(testFile.fullPath), - testFile: testFile - }; - }); - fileItems.sort((a, b) => { - if (!a.detail && !b.detail) { - return 0; - } - if (!a.detail || a.detail < b.detail!) { - return -1; - } - if (!b.detail || a.detail! > b.detail) { - return 1; - } - return 0; - }); - return fileItems; -} -export function onItemSelected( - commandManager: ICommandManager, - cmdSource: CommandSource, - wkspace: Uri, - selection: TestItem, - debug?: boolean -) { - if (!selection || typeof selection.type !== 'number') { - return; - } - switch (selection.type) { - case Type.Null: { - return; - } - case Type.RunAll: { - return commandManager.executeCommand( - constants.Commands.Tests_Run, - undefined, - cmdSource, - wkspace, - undefined - ); - } - case Type.RunParametrized: { - return commandManager.executeCommand( - constants.Commands.Tests_Run_Parametrized, - undefined, - cmdSource, - wkspace, - selection.fns!, - debug! - ); - } - case Type.ReDiscover: { - return commandManager.executeCommand(constants.Commands.Tests_Discover, undefined, cmdSource, wkspace); - } - case Type.ViewTestOutput: { - return commandManager.executeCommand(constants.Commands.Tests_ViewOutput, undefined, cmdSource); - } - case Type.RunFailed: { - return commandManager.executeCommand(constants.Commands.Tests_Run_Failed, undefined, cmdSource, wkspace); - } - case Type.SelectAndRunMethod: { - const cmd = debug - ? constants.Commands.Tests_Select_And_Debug_Method - : constants.Commands.Tests_Select_And_Run_Method; - return commandManager.executeCommand(cmd, undefined, cmdSource, wkspace); - } - case Type.RunMethod: { - const testsToRun: TestsToRun = { testFunction: [selection.fn!.testFunction] }; - return commandManager.executeCommand( - constants.Commands.Tests_Run, - undefined, - cmdSource, - wkspace, - testsToRun - ); - } - case Type.DebugMethod: { - const testsToRun: TestsToRun = { testFunction: [selection.fn!.testFunction] }; - return commandManager.executeCommand( - constants.Commands.Tests_Debug, - undefined, - cmdSource, - wkspace, - testsToRun - ); - } - case Type.Configure: { - return commandManager.executeCommand(constants.Commands.Tests_Configure, undefined, cmdSource, wkspace); - } - default: { - return; - } - } -} diff --git a/src/client/testing/explorer/commandHandlers.ts b/src/client/testing/explorer/commandHandlers.ts deleted file mode 100644 index 2dd96039c491..000000000000 --- a/src/client/testing/explorer/commandHandlers.ts +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { ICommandManager } from '../../common/application/types'; -import { Commands } from '../../common/constants'; -import { traceDecorators } from '../../common/logger'; -import { IDisposable } from '../../common/types'; -import { swallowExceptions } from '../../common/utils/decorators'; -import { CommandSource } from '../common/constants'; -import { getTestDataItemType } from '../common/testUtils'; -import { TestFile, TestFolder, TestFunction, TestsToRun, TestSuite } from '../common/types'; -import { ITestExplorerCommandHandler } from '../navigation/types'; -import { ITestDataItemResource, TestDataItem, TestDataItemType } from '../types'; - -type NavigationCommands = - | typeof Commands.navigateToTestFile - | typeof Commands.navigateToTestFunction - | typeof Commands.navigateToTestSuite; -const testNavigationCommandMapping: { [key: string]: NavigationCommands } = { - [TestDataItemType.file]: Commands.navigateToTestFile, - [TestDataItemType.function]: Commands.navigateToTestFunction, - [TestDataItemType.suite]: Commands.navigateToTestSuite -}; - -@injectable() -export class TestExplorerCommandHandler implements ITestExplorerCommandHandler { - private readonly disposables: IDisposable[] = []; - constructor( - @inject(ICommandManager) private readonly cmdManager: ICommandManager, - @inject(ITestDataItemResource) private readonly testResource: ITestDataItemResource - ) {} - public register(): void { - this.disposables.push(this.cmdManager.registerCommand(Commands.runTestNode, this.onRunTestNode, this)); - this.disposables.push(this.cmdManager.registerCommand(Commands.debugTestNode, this.onDebugTestNode, this)); - this.disposables.push( - this.cmdManager.registerCommand(Commands.openTestNodeInEditor, this.onOpenTestNodeInEditor, this) - ); - } - public dispose(): void { - this.disposables.forEach((item) => item.dispose()); - } - @swallowExceptions('Run test node') - @traceDecorators.error('Run test node failed') - protected async onRunTestNode(item: TestDataItem): Promise { - await this.runDebugTestNode(item, 'run'); - } - @swallowExceptions('Debug test node') - @traceDecorators.error('Debug test node failed') - protected async onDebugTestNode(item: TestDataItem): Promise { - await this.runDebugTestNode(item, 'debug'); - } - @swallowExceptions('Open test node in Editor') - @traceDecorators.error('Open test node in editor failed') - protected async onOpenTestNodeInEditor(item: TestDataItem): Promise { - const testType = getTestDataItemType(item); - if (testType === TestDataItemType.folder) { - throw new Error('Unknown Test Type'); - } - const command = testNavigationCommandMapping[testType]; - const testUri = this.testResource.getResource(item); - if (!command) { - throw new Error('Unknown Test Type'); - } - this.cmdManager.executeCommand(command, testUri, item, true); - } - - protected async runDebugTestNode(item: TestDataItem, runType: 'run' | 'debug'): Promise { - let testToRun: TestsToRun; - - switch (getTestDataItemType(item)) { - case TestDataItemType.file: { - testToRun = { testFile: [item as TestFile] }; - break; - } - case TestDataItemType.folder: { - testToRun = { testFolder: [item as TestFolder] }; - break; - } - case TestDataItemType.suite: { - testToRun = { testSuite: [item as TestSuite] }; - break; - } - case TestDataItemType.function: { - testToRun = { testFunction: [item as TestFunction] }; - break; - } - default: - throw new Error('Unknown Test Type'); - } - const testUri = this.testResource.getResource(item); - const cmd = runType === 'run' ? Commands.Tests_Run : Commands.Tests_Debug; - this.cmdManager.executeCommand(cmd, undefined, CommandSource.testExplorer, testUri, testToRun); - } -} diff --git a/src/client/testing/explorer/failedTestHandler.ts b/src/client/testing/explorer/failedTestHandler.ts deleted file mode 100644 index ee2ae7a3ba7b..000000000000 --- a/src/client/testing/explorer/failedTestHandler.ts +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { Uri } from 'vscode'; -import { IExtensionSingleActivationService } from '../../activation/types'; -import { ICommandManager } from '../../common/application/types'; -import { Commands } from '../../common/constants'; -import '../../common/extensions'; -import { IDisposable, IDisposableRegistry } from '../../common/types'; -import { debounceAsync } from '../../common/utils/decorators'; -import { getTestDataItemType } from '../common/testUtils'; -import { ITestCollectionStorageService, TestStatus } from '../common/types'; -import { TestDataItem, TestDataItemType } from '../types'; - -@injectable() -export class FailedTestHandler implements IExtensionSingleActivationService, IDisposable { - private readonly disposables: IDisposable[] = []; - private readonly failedItems: TestDataItem[] = []; - constructor( - @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, - @inject(ICommandManager) private readonly commandManager: ICommandManager, - @inject(ITestCollectionStorageService) private readonly storage: ITestCollectionStorageService - ) { - disposableRegistry.push(this); - } - public dispose() { - this.disposables.forEach((d) => d.dispose()); - } - public async activate(): Promise { - this.storage.onDidChange(this.onDidChangeTestData, this, this.disposables); - } - public onDidChangeTestData(args: { uri: Uri; data?: TestDataItem }): void { - if ( - args.data && - (args.data.status === TestStatus.Error || args.data.status === TestStatus.Fail) && - getTestDataItemType(args.data) === TestDataItemType.function - ) { - this.failedItems.push(args.data); - this.revealFailedNodes().ignoreErrors(); - } - } - - @debounceAsync(500) - private async revealFailedNodes(): Promise { - while (this.failedItems.length > 0) { - const item = this.failedItems.pop()!; - await this.commandManager.executeCommand(Commands.Test_Reveal_Test_Item, item); - } - } -} diff --git a/src/client/testing/explorer/testTreeViewItem.ts b/src/client/testing/explorer/testTreeViewItem.ts deleted file mode 100644 index a9c48b67d1a0..000000000000 --- a/src/client/testing/explorer/testTreeViewItem.ts +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -// tslint:disable:max-classes-per-file - -import { ThemeIcon, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode'; -import { Commands } from '../../common/constants'; -import { getIcon } from '../../common/utils/icons'; -import { noop } from '../../common/utils/misc'; -import { Icons } from '../common/constants'; -import { getTestDataItemType, isSubtestsParent } from '../common/testUtils'; -import { TestResult, TestStatus, TestSuite } from '../common/types'; -import { TestDataItem, TestDataItemType } from '../types'; - -function getDefaultCollapsibleState(data: TestDataItem): TreeItemCollapsibleState { - return getTestDataItemType(data) === TestDataItemType.function - ? TreeItemCollapsibleState.None - : TreeItemCollapsibleState.Collapsed; -} - -/** - * Class that represents a visual node on the - * Test Explorer tree view. Is essentially a wrapper for the underlying - * TestDataItem. - */ -export class TestTreeItem extends TreeItem { - public readonly testType: TestDataItemType; - - constructor( - public readonly resource: Uri, - public readonly data: Readonly, - collapsibleStatue: TreeItemCollapsibleState = getDefaultCollapsibleState(data) - ) { - super(data.name, collapsibleStatue); - this.testType = getTestDataItemType(this.data); - this.setCommand(); - } - - // @ts-ignore https://devblogs.microsoft.com/typescript/announcing-typescript-4-0-rc/#properties-overridding-accessors-and-vice-versa-is-an-error - public get contextValue(): string { - return this.testType; - } - - // @ts-ignore https://devblogs.microsoft.com/typescript/announcing-typescript-4-0-rc/#properties-overridding-accessors-and-vice-versa-is-an-error - public get iconPath(): string | Uri | { light: string | Uri; dark: string | Uri } | ThemeIcon { - if (this.testType === TestDataItemType.workspaceFolder) { - return ThemeIcon.Folder; - } - if (!this.data) { - return ''; - } - const status = this.data.status; - switch (status) { - case TestStatus.Error: - case TestStatus.Fail: { - return getIcon(Icons.failed); - } - case TestStatus.Pass: { - return getIcon(Icons.passed); - } - case TestStatus.Discovering: - case TestStatus.Running: { - return getIcon(Icons.discovering); - } - case TestStatus.Idle: - case TestStatus.Unknown: { - return getIcon(Icons.unknown); - } - default: { - return getIcon(Icons.unknown); - } - } - } - - // @ts-ignore https://devblogs.microsoft.com/typescript/announcing-typescript-4-0-rc/#properties-overridding-accessors-and-vice-versa-is-an-error - public get tooltip(): string { - if (!this.data || this.testType === TestDataItemType.workspaceFolder) { - return ''; - } - const result = this.data as TestResult; - if ( - !result.status || - result.status === TestStatus.Idle || - result.status === TestStatus.Unknown || - result.status === TestStatus.Skipped - ) { - return ''; - } - if (this.testType !== TestDataItemType.function) { - if (result.functionsPassed === undefined) { - return ''; - } - if (result.functionsDidNotRun) { - return `${result.functionsFailed} failed, ${result.functionsDidNotRun} not run and ${result.functionsPassed} passed`; - } - return `${result.functionsFailed} failed, ${result.functionsPassed} passed`; - } - switch (this.data.status) { - case TestStatus.Error: - case TestStatus.Fail: { - return `Failed in ${+result.time.toFixed(3)} seconds`; - } - case TestStatus.Pass: { - return `Passed in ${+result.time.toFixed(3)} seconds`; - } - case TestStatus.Discovering: - case TestStatus.Running: { - return 'Loading...'; - } - default: { - return ''; - } - } - } - - /** - * Tooltip for our tree nodes is the test status - */ - public get testStatus(): string { - return this.data.status ? this.data.status : TestStatus.Unknown; - } - - private setCommand() { - switch (this.testType) { - case TestDataItemType.file: { - this.command = { - command: Commands.navigateToTestFile, - title: 'Open', - arguments: [this.resource, this.data] - }; - break; - } - case TestDataItemType.function: { - this.command = { - command: Commands.navigateToTestFunction, - title: 'Open', - arguments: [this.resource, this.data, false] - }; - break; - } - case TestDataItemType.suite: { - if (isSubtestsParent(this.data as TestSuite)) { - this.command = { - command: Commands.navigateToTestFunction, - title: 'Open', - arguments: [this.resource, this.data, false] - }; - break; - } - this.command = { - command: Commands.navigateToTestSuite, - title: 'Open', - arguments: [this.resource, this.data, false] - }; - break; - } - default: { - noop(); - } - } - } -} diff --git a/src/client/testing/explorer/testTreeViewProvider.ts b/src/client/testing/explorer/testTreeViewProvider.ts deleted file mode 100644 index aa5aa4405f5a..000000000000 --- a/src/client/testing/explorer/testTreeViewProvider.ts +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { Event, EventEmitter, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode'; -import { ICommandManager, IWorkspaceService } from '../../common/application/types'; -import { Commands } from '../../common/constants'; -import { IDisposable, IDisposableRegistry } from '../../common/types'; -import { sendTelemetryEvent } from '../../telemetry'; -import { EventName } from '../../telemetry/constants'; -import { CommandSource } from '../common/constants'; -import { getChildren, getParent, getTestDataItemType } from '../common/testUtils'; -import { ITestCollectionStorageService, Tests, TestStatus } from '../common/types'; -import { - ITestDataItemResource, - ITestManagementService, - ITestTreeViewProvider, - TestDataItem, - TestDataItemType, - TestWorkspaceFolder, - WorkspaceTestStatus -} from '../types'; -import { TestTreeItem } from './testTreeViewItem'; - -@injectable() -export class TestTreeViewProvider implements ITestTreeViewProvider, ITestDataItemResource, IDisposable { - public readonly onDidChangeTreeData: Event; - public readonly discovered = new Set(); - public readonly testsAreBeingDiscovered: Map; - - private _onDidChangeTreeData = new EventEmitter(); - private disposables: IDisposable[] = []; - - constructor( - @inject(ITestCollectionStorageService) private testStore: ITestCollectionStorageService, - @inject(ITestManagementService) private testService: ITestManagementService, - @inject(IWorkspaceService) private readonly workspace: IWorkspaceService, - @inject(ICommandManager) private readonly commandManager: ICommandManager, - @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry - ) { - this.onDidChangeTreeData = this._onDidChangeTreeData.event; - - disposableRegistry.push(this); - this.testsAreBeingDiscovered = new Map(); - this.disposables.push(this.testService.onDidStatusChange(this.onTestStatusChanged, this)); - this.testStore.onDidChange((e) => this._onDidChangeTreeData.fire(e.data), this, this.disposables); - this.workspace.onDidChangeWorkspaceFolders( - () => this._onDidChangeTreeData.fire(undefined), - this, - this.disposables - ); - - if (Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length > 0) { - this.refresh(workspace.workspaceFolders[0].uri); - } - } - - /** - * We need a way to map a given TestDataItem to a Uri, so that other consumers (such - * as the commandHandler for the Test Explorer) have a way of accessing the Uri outside - * the purview off the TestTreeView. - * - * @param testData Test data item to map to a Uri - * @returns A Uri representing the workspace that the test data item exists within - */ - public getResource(testData: Readonly): Uri { - return testData.resource; - } - - /** - * As the TreeViewProvider itself is getting disposed, ensure all registered listeners are disposed - * from our internal emitter. - */ - public dispose() { - this.disposables.forEach((d) => d.dispose()); - this._onDidChangeTreeData.dispose(); - } - - /** - * Get [TreeItem](#TreeItem) representation of the `element` - * - * @param element The element for which [TreeItem](#TreeItem) representation is asked for. - * @return [TreeItem](#TreeItem) representation of the element - */ - public async getTreeItem(element: TestDataItem): Promise { - const defaultCollapsibleState = (await this.shouldElementBeExpandedByDefault(element)) - ? TreeItemCollapsibleState.Expanded - : undefined; - return new TestTreeItem(element.resource, element, defaultCollapsibleState); - } - - /** - * Get the children of `element` or root if no element is passed. - * - * @param element The element from which the provider gets children. Can be `undefined`. - * @return Children of `element` or root if no element is passed. - */ - public async getChildren(element?: TestDataItem): Promise { - if (element) { - if (element instanceof TestWorkspaceFolder) { - let tests = this.testStore.getTests(element.workspaceFolder.uri); - if (!tests && !this.discovered.has(element.workspaceFolder.uri.fsPath)) { - this.discovered.add(element.workspaceFolder.uri.fsPath); - await this.commandManager.executeCommand( - Commands.Tests_Discover, - element, - CommandSource.testExplorer, - undefined - ); - tests = this.testStore.getTests(element.workspaceFolder.uri); - } - return this.getRootNodes(tests); - } - return getChildren(element!); - } - - if (!Array.isArray(this.workspace.workspaceFolders) || this.workspace.workspaceFolders.length === 0) { - return []; - } - - sendTelemetryEvent(EventName.UNITTEST_EXPLORER_WORK_SPACE_COUNT, undefined, { - count: this.workspace.workspaceFolders.length - }); - - // If we are in a single workspace - if (this.workspace.workspaceFolders.length === 1) { - const tests = this.testStore.getTests(this.workspace.workspaceFolders[0].uri); - return this.getRootNodes(tests); - } - - // If we are in a mult-root workspace, then nest the test data within a - // virtual node, represending the workspace folder. - return this.workspace.workspaceFolders.map((workspaceFolder) => new TestWorkspaceFolder(workspaceFolder)); - } - - /** - * Optional method to return the parent of `element`. - * Return `null` or `undefined` if `element` is a child of root. - * - * **NOTE:** This method should be implemented in order to access [reveal](#TreeView.reveal) API. - * - * @param element The element for which the parent has to be returned. - * @return Parent of `element`. - */ - public async getParent(element: TestDataItem): Promise { - if (element instanceof TestWorkspaceFolder) { - return; - } - const tests = this.testStore.getTests(element.resource); - return tests ? getParent(tests, element) : undefined; - } - /** - * If we have test files directly in root directory, return those. - * If we have test folders and no test files under the root directory, then just return the test directories. - * The goal is not avoid returning an empty root node, when all it contains are child nodes for folders. - * - * @param {Tests} [tests] - * @returns - * @memberof TestTreeViewProvider - */ - public getRootNodes(tests?: Tests) { - if (tests && tests.rootTestFolders && tests.rootTestFolders.length === 1) { - return [...tests.rootTestFolders[0].testFiles, ...tests.rootTestFolders[0].folders]; - } - return tests ? tests.rootTestFolders : []; - } - /** - * Refresh the view by rebuilding the model and signaling the tree view to update itself. - * - * @param resource The resource 'root' for this refresh to occur under. - */ - public refresh(resource: Uri): void { - const workspaceFolder = this.workspace.getWorkspaceFolder(resource); - if (!workspaceFolder) { - return; - } - const tests = this.testStore.getTests(resource); - if (tests && tests.testFolders) { - this._onDidChangeTreeData.fire(new TestWorkspaceFolder(workspaceFolder)); - } - } - - /** - * Event handler for TestStatusChanged (coming from the ITestManagementService). - * ThisThe TreeView needs to know when we begin discovery and when discovery completes. - * - * @param e The event payload containing context for the status change - */ - private onTestStatusChanged(e: WorkspaceTestStatus) { - if (e.status === TestStatus.Discovering) { - this.testsAreBeingDiscovered.set(e.workspace.fsPath, true); - return; - } - if (!this.testsAreBeingDiscovered.get(e.workspace.fsPath)) { - return; - } - this.testsAreBeingDiscovered.set(e.workspace.fsPath, false); - this.refresh(e.workspace); - } - - private async shouldElementBeExpandedByDefault(element: TestDataItem) { - const parent = await this.getParent(element); - if (!parent || getTestDataItemType(parent) === TestDataItemType.workspaceFolder) { - return true; - } - return false; - } -} diff --git a/src/client/testing/explorer/treeView.ts b/src/client/testing/explorer/treeView.ts deleted file mode 100644 index 0c683ca307db..000000000000 --- a/src/client/testing/explorer/treeView.ts +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { TreeView } from 'vscode'; -import { IExtensionSingleActivationService } from '../../activation/types'; -import { IApplicationShell, ICommandManager } from '../../common/application/types'; -import { Commands } from '../../common/constants'; -import { IDisposable, IDisposableRegistry } from '../../common/types'; -import { ITestTreeViewProvider, TestDataItem } from '../types'; - -@injectable() -export class TreeViewService implements IExtensionSingleActivationService, IDisposable { - private _treeView!: TreeView; - private readonly disposables: IDisposable[] = []; - public get treeView(): TreeView { - return this._treeView; - } - constructor( - @inject(ITestTreeViewProvider) private readonly treeViewProvider: ITestTreeViewProvider, - @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, - @inject(IApplicationShell) private readonly appShell: IApplicationShell, - @inject(ICommandManager) private readonly commandManager: ICommandManager - ) { - disposableRegistry.push(this); - } - public dispose() { - this.disposables.forEach((d) => d.dispose()); - } - public async activate(): Promise { - this._treeView = this.appShell.createTreeView('python_tests', { - showCollapseAll: true, - treeDataProvider: this.treeViewProvider - }); - this.disposables.push(this._treeView); - this.disposables.push( - this.commandManager.registerCommand(Commands.Test_Reveal_Test_Item, this.onRevealTestItem, this) - ); - } - public async onRevealTestItem(testItem: TestDataItem): Promise { - await this.treeView.reveal(testItem, { select: false }); - } -} diff --git a/src/client/testing/main.ts b/src/client/testing/main.ts index f4763bd74b23..eed4d70e852c 100644 --- a/src/client/testing/main.ts +++ b/src/client/testing/main.ts @@ -1,621 +1,230 @@ 'use strict'; -// tslint:disable:no-duplicate-imports no-unnecessary-callback-wrapper - import { inject, injectable } from 'inversify'; import { ConfigurationChangeEvent, Disposable, - DocumentSymbolProvider, - Event, - EventEmitter, - OutputChannel, - TextDocument, - Uri + Uri, + tests, + TestResultState, + WorkspaceFolder, + Command, + TestItem, } from 'vscode'; -import { IApplicationShell, ICommandManager, IDocumentManager, IWorkspaceService } from '../common/application/types'; +import { IApplicationShell, ICommandManager, IContextKeyManager, IWorkspaceService } from '../common/application/types'; import * as constants from '../common/constants'; -import { AlwaysDisplayTestExplorerGroups } from '../common/experiments/groups'; import '../common/extensions'; -import { traceError } from '../common/logger'; -import { - IConfigurationService, - IDisposableRegistry, - IExperimentsManager, - IOutputChannel, - Resource -} from '../common/types'; -import { noop } from '../common/utils/misc'; +import { IDisposableRegistry, Product } from '../common/types'; import { IInterpreterService } from '../interpreter/contracts'; import { IServiceContainer } from '../ioc/types'; import { EventName } from '../telemetry/constants'; -import { captureTelemetry, sendTelemetryEvent } from '../telemetry/index'; -import { activateCodeLenses } from './codeLenses/main'; -import { CANCELLATION_REASON, CommandSource, TEST_OUTPUT_CHANNEL } from './common/constants'; +import { sendTelemetryEvent } from '../telemetry/index'; import { selectTestWorkspace } from './common/testUtils'; -import { - ITestCollectionStorageService, - ITestManager, - IWorkspaceTestManagerService, - TestFile, - TestFunction, - TestStatus, - TestsToRun -} from './common/types'; -import { - ITestConfigurationService, - ITestDisplay, - ITestManagementService, - ITestResultDisplay, - TestWorkspaceFolder, - WorkspaceTestStatus -} from './types'; - -// tslint:disable:no-any +import { TestSettingsPropertyNames } from './configuration/types'; +import { ITestConfigurationService, ITestsHelper } from './common/types'; +import { ITestingService } from './types'; +import { IExtensionActivationService } from '../activation/types'; +import { ITestController } from './testController/common/types'; +import { DelayedTrigger, IDelayedTrigger } from '../common/utils/delayTrigger'; +import { ExtensionContextKey } from '../common/application/contextKeys'; +import { checkForFailedTests, updateTestResultMap } from './testController/common/testItemUtilities'; +import { Testing } from '../common/utils/localize'; +import { traceVerbose, traceWarn } from '../logging'; +import { writeTestIdToClipboard } from './utils'; @injectable() -export class UnitTestManagementService implements ITestManagementService, Disposable { - private readonly outputChannel: OutputChannel; - private activatedOnce: boolean = false; - private readonly disposableRegistry: Disposable[]; - private workspaceTestManagerService?: IWorkspaceTestManagerService; - private documentManager: IDocumentManager; - private workspaceService: IWorkspaceService; - private testResultDisplay?: ITestResultDisplay; - private autoDiscoverTimer?: NodeJS.Timer | number; - private configChangedTimer?: NodeJS.Timer | number; - private testManagers = new Set(); - private readonly _onDidStatusChange: EventEmitter = new EventEmitter(); - - constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { - this.disposableRegistry = serviceContainer.get(IDisposableRegistry); - this.outputChannel = serviceContainer.get(IOutputChannel, TEST_OUTPUT_CHANNEL); - this.workspaceService = serviceContainer.get(IWorkspaceService); - this.documentManager = serviceContainer.get(IDocumentManager); +export class TestingService implements ITestingService { + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {} - this.disposableRegistry.push(this); - } - public dispose() { - if (this.workspaceTestManagerService) { - this.workspaceTestManagerService.dispose(); - } - if (this.configChangedTimer) { - clearTimeout(this.configChangedTimer as any); - this.configChangedTimer = undefined; - } - if (this.autoDiscoverTimer) { - clearTimeout(this.autoDiscoverTimer as any); - this.autoDiscoverTimer = undefined; - } - } - public get onDidStatusChange(): Event { - return this._onDidStatusChange.event; + public getSettingsPropertyNames(product: Product): TestSettingsPropertyNames { + const helper = this.serviceContainer.get(ITestsHelper); + return helper.getSettingsPropertyNames(product); } - public async activate(symbolProvider: DocumentSymbolProvider): Promise { - if (this.activatedOnce) { - return; - } - this.activatedOnce = true; - this.workspaceTestManagerService = this.serviceContainer.get( - IWorkspaceTestManagerService - ); +} + +/** + * Registers command handlers but defers service resolution until the commands are actually invoked, + * allowing registration to happen before all services are fully initialized. + */ +export function registerTestCommands(serviceContainer: IServiceContainer): void { + // Resolve only the essential services needed for command registration itself + const disposableRegistry = serviceContainer.get(IDisposableRegistry); + const commandManager = serviceContainer.get(ICommandManager); + + // Helper function to configure tests - services are resolved when invoked, not at registration time + const configureTestsHandler = async (resource?: Uri) => { + sendTelemetryEvent(EventName.UNITTEST_CONFIGURE); + + // Resolve services lazily when the command is invoked + const workspaceService = serviceContainer.get(IWorkspaceService); - this.registerHandlers(); - this.registerCommands(); - this.checkExperiments(); - this.autoDiscoverTests(undefined).catch((ex) => - traceError('Failed to auto discover tests upon activation', ex) - ); - await this.registerSymbolProvider(symbolProvider); - } - public checkExperiments() { - const experiments = this.serviceContainer.get(IExperimentsManager); - if (experiments.inExperiment(AlwaysDisplayTestExplorerGroups.experiment)) { - const commandManager = this.serviceContainer.get(ICommandManager); - commandManager.executeCommand('setContext', 'testsDiscovered', true).then(noop, noop); - } else { - experiments.sendTelemetryIfInExperiment(AlwaysDisplayTestExplorerGroups.control); - } - } - public async getTestManager( - displayTestNotConfiguredMessage: boolean, - resource?: Uri - ): Promise { let wkspace: Uri | undefined; if (resource) { - const wkspaceFolder = this.workspaceService.getWorkspaceFolder(resource); + const wkspaceFolder = workspaceService.getWorkspaceFolder(resource); wkspace = wkspaceFolder ? wkspaceFolder.uri : undefined; } else { - const appShell = this.serviceContainer.get(IApplicationShell); + const appShell = serviceContainer.get(IApplicationShell); wkspace = await selectTestWorkspace(appShell); } if (!wkspace) { return; } - const testManager = this.workspaceTestManagerService!.getTestManager(wkspace); - if (testManager) { - if (!this.testManagers.has(testManager)) { - this.testManagers.add(testManager); - const handler = testManager.onDidStatusChange((e) => this._onDidStatusChange.fire(e)); - this.disposableRegistry.push(handler); - } - return testManager; - } - if (displayTestNotConfiguredMessage) { - const configurationService = this.serviceContainer.get( - ITestConfigurationService - ); - await configurationService.displayTestFrameworkError(wkspace); - } - } - public async configurationChangeHandler(eventArgs: ConfigurationChangeEvent) { - // If there's one workspace, then stop the tests and restart, - // else let the user do this manually. - if (!this.workspaceService.hasWorkspaceFolders || this.workspaceService.workspaceFolders!.length > 1) { - return; - } - if (!Array.isArray(this.workspaceService.workspaceFolders)) { - return; - } - const workspaceFolderUri = this.workspaceService.workspaceFolders.find((w) => - eventArgs.affectsConfiguration('python.testing', w.uri) - ); - if (!workspaceFolderUri) { - return; - } - const workspaceUri = workspaceFolderUri.uri; - const settings = this.serviceContainer - .get(IConfigurationService) - .getSettings(workspaceUri); - if ( - !settings.testing.nosetestsEnabled && - !settings.testing.pytestEnabled && - !settings.testing.unittestEnabled - ) { - if (this.testResultDisplay) { - this.testResultDisplay.enabled = false; - } - // tslint:disable-next-line:no-suspicious-comment - // TODO: Why are we disposing, what happens when tests are enabled. - if (this.workspaceTestManagerService) { - this.workspaceTestManagerService.dispose(); + const interpreterService = serviceContainer.get(IInterpreterService); + const cmdManager = serviceContainer.get(ICommandManager); + if (!(await interpreterService.getActiveInterpreter(wkspace))) { + cmdManager.executeCommand(constants.Commands.TriggerEnvironmentSelection, wkspace); + return; + } + const configurationService = serviceContainer.get(ITestConfigurationService); + await configurationService.promptToEnableAndConfigureTestFramework(wkspace); + }; + + disposableRegistry.push( + // Command: python.configureTests - prompts user to configure test framework + commandManager.registerCommand( + constants.Commands.Tests_Configure, + (_, _cmdSource: constants.CommandSource = constants.CommandSource.commandPalette, resource?: Uri) => { + // Invoke configuration handler (errors are ignored as this can be called from multiple places) + configureTestsHandler(resource).ignoreErrors(); + traceVerbose('Testing: Trigger refresh after config change'); + // Refresh test data if test controller is available (resolved lazily) + if (tests && !!tests.createTestController) { + const testController = serviceContainer.get(ITestController); + testController?.refreshTestData(resource, { forceRefresh: true }); + } + }, + ), + // Command: python.tests.copilotSetup - Copilot integration for test setup + commandManager.registerCommand(constants.Commands.Tests_CopilotSetup, (resource?: Uri): + | { message: string; command: Command } + | undefined => { + // Resolve services lazily when the command is invoked + const workspaceService = serviceContainer.get(IWorkspaceService); + const wkspaceFolder = + workspaceService.getWorkspaceFolder(resource) || workspaceService.workspaceFolders?.at(0); + if (!wkspaceFolder) { + return undefined; } - return; - } - if (this.testResultDisplay) { - this.testResultDisplay.enabled = true; - } - this.autoDiscoverTests(workspaceUri).catch((ex) => - traceError('Failed to auto discover tests upon activation', ex) - ); - } - - public async discoverTestsForDocument(doc: TextDocument): Promise { - const testManager = await this.getTestManager(false, doc.uri); - if (!testManager) { - return; - } - const tests = await testManager.discoverTests(CommandSource.auto, false, true); - if (!tests || !Array.isArray(tests.testFiles) || tests.testFiles.length === 0) { - return; - } - if (tests.testFiles.findIndex((f: TestFile) => f.fullPath === doc.uri.fsPath) === -1) { - return; - } - if (this.autoDiscoverTimer) { - clearTimeout(this.autoDiscoverTimer as any); - } - this.autoDiscoverTimer = setTimeout( - () => this.discoverTests(CommandSource.auto, doc.uri, true, false, true), - 1000 - ); - } - public async autoDiscoverTests(resource: Resource) { - if (!this.workspaceService.hasWorkspaceFolders) { - return; - } - // Default to discovering tests in first folder if none specified. - if (!resource) { - resource = this.workspaceService.workspaceFolders![0].uri; - } - const configurationService = this.serviceContainer.get(IConfigurationService); - const settings = configurationService.getSettings(resource); - if ( - !settings.testing.nosetestsEnabled && - !settings.testing.pytestEnabled && - !settings.testing.unittestEnabled - ) { - return; - } + const configurationService = serviceContainer.get(ITestConfigurationService); + if (configurationService.hasConfiguredTests(wkspaceFolder.uri)) { + return undefined; + } - this.discoverTests(CommandSource.auto, resource, true).ignoreErrors(); - } - public async discoverTests( - cmdSource: CommandSource, - resource?: Uri, - ignoreCache?: boolean, - userInitiated?: boolean, - quietMode?: boolean, - clearTestStatus?: boolean - ) { - const testManager = await this.getTestManager(true, resource); - if (!testManager) { - return; - } + return { + message: Testing.copilotSetupMessage, + command: { + title: Testing.configureTests, + command: constants.Commands.Tests_Configure, + arguments: [undefined, constants.CommandSource.ui, resource], + }, + }; + }), + // Command: python.copyTestId - copies test ID to clipboard + commandManager.registerCommand(constants.Commands.CopyTestId, async (testItem: TestItem) => { + writeTestIdToClipboard(testItem); + }), + ); +} - if (testManager.status === TestStatus.Discovering || testManager.status === TestStatus.Running) { - return; - } +@injectable() +export class UnitTestManagementService implements IExtensionActivationService { + private activatedOnce: boolean = false; + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; + private readonly disposableRegistry: Disposable[]; + private workspaceService: IWorkspaceService; + private context: IContextKeyManager; + private testController: ITestController | undefined; + private configChangeTrigger: IDelayedTrigger; - if (!this.testResultDisplay) { - this.testResultDisplay = this.serviceContainer.get(ITestResultDisplay); - } - const discoveryPromise = testManager.discoverTests( - cmdSource, - ignoreCache, - quietMode, - userInitiated, - clearTestStatus - ); - this.testResultDisplay - .displayDiscoverStatus(discoveryPromise, quietMode) - .catch((ex) => traceError('Python Extension: displayDiscoverStatus', ex)); - await discoveryPromise; - } - public async stopTests(resource: Uri) { - sendTelemetryEvent(EventName.UNITTEST_STOP); - const testManager = await this.getTestManager(true, resource); - if (testManager) { - testManager.stop(); - } - } - public async displayStopUI(message: string): Promise { - const testManager = await this.getTestManager(true); - if (!testManager) { - return; - } + // This is temporarily needed until the proposed API settles for this part + private testStateMap: Map = new Map(); - const testDisplay = this.serviceContainer.get(ITestDisplay); - testDisplay.displayStopTestUI(testManager.workspaceFolder, message); - } - public async displayUI(cmdSource: CommandSource) { - const testManager = await this.getTestManager(true); - if (!testManager) { - return; - } + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { + this.disposableRegistry = serviceContainer.get(IDisposableRegistry); + this.workspaceService = serviceContainer.get(IWorkspaceService); + this.context = this.serviceContainer.get(IContextKeyManager); - const testDisplay = this.serviceContainer.get(ITestDisplay); - testDisplay.displayTestUI(cmdSource, testManager.workspaceFolder); - } - public async displayPickerUI(cmdSource: CommandSource, file: Uri, testFunctions: TestFunction[], debug?: boolean) { - const testManager = await this.getTestManager(true, file); - if (!testManager) { - return; + if (tests && !!tests.createTestController) { + this.testController = serviceContainer.get(ITestController); } - const testDisplay = this.serviceContainer.get(ITestDisplay); - testDisplay.displayFunctionTestPickerUI( - cmdSource, - testManager.workspaceFolder, - testManager.workingDirectory, - file, - testFunctions, - debug + const configChangeTrigger = new DelayedTrigger( + this.configurationChangeHandler.bind(this), + 500, + 'Test Configuration Change', ); + this.configChangeTrigger = configChangeTrigger; + this.disposableRegistry.push(configChangeTrigger); } - public async runParametrizedTests( - cmdSource: CommandSource, - resource: Uri, - testFunctions: TestFunction[], - debug?: boolean - ) { - const testManager = await this.getTestManager(true, resource); - if (!testManager) { - return; - } - await this.runTestsImpl(cmdSource, resource, { testFunction: testFunctions }, undefined, debug); - } - public viewOutput(_cmdSource: CommandSource) { - sendTelemetryEvent(EventName.UNITTEST_VIEW_OUTPUT); - this.outputChannel.show(); - } - public async selectAndRunTestMethod(cmdSource: CommandSource, resource: Uri, debug?: boolean) { - const testManager = await this.getTestManager(true, resource); - if (!testManager) { - return; - } - try { - await testManager.discoverTests(cmdSource, true, true, true); - } catch (ex) { - return; - } - const testCollectionStorage = this.serviceContainer.get( - ITestCollectionStorageService - ); - const tests = testCollectionStorage.getTests(testManager.workspaceFolder)!; - const testDisplay = this.serviceContainer.get(ITestDisplay); - const selectedTestFn = await testDisplay.selectTestFunction(testManager.workspaceFolder.fsPath, tests); - if (!selectedTestFn) { - return; - } - // tslint:disable-next-line:prefer-type-cast - await this.runTestsImpl( - cmdSource, - testManager.workspaceFolder, - // tslint:disable-next-line:no-object-literal-type-assertion - { testFunction: [selectedTestFn.testFunction] } as TestsToRun, - false, - debug - ); - } - public async selectAndRunTestFile(cmdSource: CommandSource) { - const testManager = await this.getTestManager(true); - if (!testManager) { - return; - } - try { - await testManager.discoverTests(cmdSource, true, true, true); - } catch (ex) { + public async activate(): Promise { + if (this.activatedOnce) { return; } + this.activatedOnce = true; - const testCollectionStorage = this.serviceContainer.get( - ITestCollectionStorageService - ); - const tests = testCollectionStorage.getTests(testManager.workspaceFolder)!; - const testDisplay = this.serviceContainer.get(ITestDisplay); - const selectedFile = await testDisplay.selectTestFile(testManager.workspaceFolder.fsPath, tests); - if (!selectedFile) { - return; - } - await this.runTestsImpl(cmdSource, testManager.workspaceFolder, { testFile: [selectedFile] }); - } - public async runCurrentTestFile(cmdSource: CommandSource) { - if (!this.documentManager.activeTextEditor) { - return; - } - const testManager = await this.getTestManager(true, this.documentManager.activeTextEditor.document.uri); - if (!testManager) { - return; - } - try { - await testManager.discoverTests(cmdSource, true, true, true); - } catch (ex) { - return; - } - const testCollectionStorage = this.serviceContainer.get( - ITestCollectionStorageService - ); - const tests = testCollectionStorage.getTests(testManager.workspaceFolder)!; - const testFiles = tests.testFiles.filter((testFile) => { - return testFile.fullPath === this.documentManager.activeTextEditor!.document.uri.fsPath; - }); - if (testFiles.length < 1) { - return; - } - await this.runTestsImpl(cmdSource, testManager.workspaceFolder, { testFile: [testFiles[0]] }); - } + this.registerHandlers(); - public async runTestsImpl( - cmdSource: CommandSource, - resource?: Uri, - testsToRun?: TestsToRun, - runFailedTests?: boolean, - debug: boolean = false - ) { - const testManager = await this.getTestManager(true, resource); - if (!testManager) { - return; + if (!!tests.testResults) { + await this.updateTestUIButtons(); + this.disposableRegistry.push( + tests.onDidChangeTestResults(() => { + this.updateTestUIButtons(); + }), + ); } - if (!this.testResultDisplay) { - this.testResultDisplay = this.serviceContainer.get(ITestResultDisplay); + if (this.testController) { + this.testController.onRefreshingStarted(async () => { + await this.context.setContext(ExtensionContextKey.RefreshingTests, true); + }); + this.testController.onRefreshingCompleted(async () => { + await this.context.setContext(ExtensionContextKey.RefreshingTests, false); + }); + this.testController.onRunWithoutConfiguration(async (unconfigured: WorkspaceFolder[]) => { + const workspaces = this.workspaceService.workspaceFolders ?? []; + if (unconfigured.length === workspaces.length) { + const commandManager = this.serviceContainer.get(ICommandManager); + await commandManager.executeCommand('workbench.view.testing.focus'); + traceWarn( + 'Testing: Run attempted but no test configurations found for any workspace, use command palette to configure tests for python if desired.', + ); + } + }); } - - const promise = testManager.runTest(cmdSource, testsToRun, runFailedTests, debug).catch((reason) => { - if (reason !== CANCELLATION_REASON) { - this.outputChannel.appendLine(`Error: ${reason}`); - } - return Promise.reject(reason); - }); - - this.testResultDisplay.displayProgressStatus(promise, debug); - await promise; } - public async registerSymbolProvider(symbolProvider: DocumentSymbolProvider): Promise { - const testCollectionStorage = this.serviceContainer.get( - ITestCollectionStorageService - ); - const event = new EventEmitter(); - this.disposableRegistry.push(event); - const handler = this._onDidStatusChange.event((e) => { - if (e.status !== TestStatus.Discovering && e.status !== TestStatus.Running) { - event.fire(); - } - }); - this.disposableRegistry.push(handler); - this.disposableRegistry.push( - activateCodeLenses(event, symbolProvider, testCollectionStorage, this.serviceContainer) - ); - } + private async updateTestUIButtons() { + // See if we already have stored tests results from previous runs. + // The tests results currently has a historical test status based on runs. To get a + // full picture of the tests state these need to be reduced by test id. + updateTestResultMap(this.testStateMap, tests.testResults); - @captureTelemetry(EventName.UNITTEST_CONFIGURE, undefined, false) - public async configureTests(resource?: Uri) { - let wkspace: Uri | undefined; - if (resource) { - const wkspaceFolder = this.workspaceService.getWorkspaceFolder(resource); - wkspace = wkspaceFolder ? wkspaceFolder.uri : undefined; - } else { - const appShell = this.serviceContainer.get(IApplicationShell); - wkspace = await selectTestWorkspace(appShell); - } - if (!wkspace) { - return; - } - const configurationService = this.serviceContainer.get(ITestConfigurationService); - await configurationService.promptToEnableAndConfigureTestFramework(wkspace!); + const hasFailedTests = checkForFailedTests(this.testStateMap); + await this.context.setContext(ExtensionContextKey.HasFailedTests, hasFailedTests); } - // tslint:disable-next-line: max-func-body-length - public registerCommands(): void { - const disposablesRegistry = this.serviceContainer.get(IDisposableRegistry); - const commandManager = this.serviceContainer.get(ICommandManager); - const disposables = [ - commandManager.registerCommand( - constants.Commands.Tests_Discover, - ( - treeNode?: TestWorkspaceFolder, - cmdSource: CommandSource = CommandSource.commandPalette, - resource?: Uri - ) => { - if (treeNode && treeNode instanceof TestWorkspaceFolder) { - resource = treeNode.resource; - cmdSource = CommandSource.testExplorer; - } - // Ignore the exceptions returned. - // This command will be invoked from other places of the extension. - return this.discoverTests(cmdSource, resource, true, true, false, true).ignoreErrors(); - } - ), - commandManager.registerCommand( - constants.Commands.Tests_Configure, - (_, _cmdSource: CommandSource = CommandSource.commandPalette, resource?: Uri) => { - // Ignore the exceptions returned. - // This command will be invoked from other places of the extension. - this.configureTests(resource).ignoreErrors(); - } - ), - commandManager.registerCommand( - constants.Commands.Tests_Run_Failed, - (_, cmdSource: CommandSource = CommandSource.commandPalette, resource: Uri) => - this.runTestsImpl(cmdSource, resource, undefined, true) - ), - commandManager.registerCommand( - constants.Commands.Tests_Run, - ( - treeNode?: TestWorkspaceFolder, - cmdSource: CommandSource = CommandSource.commandPalette, - resource?: Uri, - testToRun?: TestsToRun - ) => { - if (treeNode && treeNode instanceof TestWorkspaceFolder) { - resource = treeNode.resource; - cmdSource = CommandSource.testExplorer; - } - return this.runTestsImpl(cmdSource, resource, testToRun); - } - ), - commandManager.registerCommand( - constants.Commands.Tests_Debug, - ( - treeNode?: TestWorkspaceFolder, - cmdSource: CommandSource = CommandSource.commandPalette, - resource?: Uri, - testToRun?: TestsToRun - ) => { - if (treeNode && treeNode instanceof TestWorkspaceFolder) { - resource = treeNode.resource; - cmdSource = CommandSource.testExplorer; - } - return this.runTestsImpl(cmdSource, resource, testToRun, false, true); - } - ), - commandManager.registerCommand(constants.Commands.Tests_View_UI, () => - this.displayUI(CommandSource.commandPalette) - ), - commandManager.registerCommand( - constants.Commands.Tests_Picker_UI, - ( - _, - cmdSource: CommandSource = CommandSource.commandPalette, - file: Uri, - testFunctions: TestFunction[] - ) => this.displayPickerUI(cmdSource, file, testFunctions) - ), - commandManager.registerCommand( - constants.Commands.Tests_Picker_UI_Debug, - ( - _, - cmdSource: CommandSource = CommandSource.commandPalette, - file: Uri, - testFunctions: TestFunction[] - ) => this.displayPickerUI(cmdSource, file, testFunctions, true) - ), - commandManager.registerCommand( - constants.Commands.Tests_Run_Parametrized, - ( - _, - cmdSource: CommandSource = CommandSource.commandPalette, - resource: Uri, - testFunctions: TestFunction[], - debug: boolean - ) => this.runParametrizedTests(cmdSource, resource, testFunctions, debug) - ), - commandManager.registerCommand(constants.Commands.Tests_Stop, (_, resource: Uri) => - this.stopTests(resource) - ), - commandManager.registerCommand( - constants.Commands.Tests_ViewOutput, - (_, cmdSource: CommandSource = CommandSource.commandPalette) => this.viewOutput(cmdSource) - ), - commandManager.registerCommand(constants.Commands.Tests_Ask_To_Stop_Discovery, () => - this.displayStopUI('Stop discovering tests') - ), - commandManager.registerCommand(constants.Commands.Tests_Ask_To_Stop_Test, () => - this.displayStopUI('Stop running tests') - ), - commandManager.registerCommand( - constants.Commands.Tests_Select_And_Run_Method, - (_, cmdSource: CommandSource = CommandSource.commandPalette, resource: Uri) => - this.selectAndRunTestMethod(cmdSource, resource) - ), - commandManager.registerCommand( - constants.Commands.Tests_Select_And_Debug_Method, - (_, cmdSource: CommandSource = CommandSource.commandPalette, resource: Uri) => - this.selectAndRunTestMethod(cmdSource, resource, true) - ), - commandManager.registerCommand( - constants.Commands.Tests_Select_And_Run_File, - (_, cmdSource: CommandSource = CommandSource.commandPalette) => this.selectAndRunTestFile(cmdSource) - ), - commandManager.registerCommand( - constants.Commands.Tests_Run_Current_File, - (_, cmdSource: CommandSource = CommandSource.commandPalette) => this.runCurrentTestFile(cmdSource) - ), - commandManager.registerCommand(constants.Commands.Tests_Discovering, noop) - ]; + private async configurationChangeHandler(eventArgs: ConfigurationChangeEvent) { + const workspaces = this.workspaceService.workspaceFolders ?? []; + const changedWorkspaces: Uri[] = workspaces + .filter((w) => eventArgs.affectsConfiguration('python.testing', w.uri)) + .map((w) => w.uri); - disposablesRegistry.push(...disposables); + await Promise.all(changedWorkspaces.map((u) => this.testController?.refreshTestData(u))); } - public onDocumentSaved(doc: TextDocument) { - const settings = this.serviceContainer.get(IConfigurationService).getSettings(doc.uri); - if (!settings.testing.autoTestDiscoverOnSaveEnabled) { - return; - } - this.discoverTestsForDocument(doc).ignoreErrors(); - } - public registerHandlers() { - const documentManager = this.serviceContainer.get(IDocumentManager); - const interpreterService = this.serviceContainer.get(IInterpreterService); - this.disposableRegistry.push(documentManager.onDidSaveTextDocument(this.onDocumentSaved.bind(this))); + private registerHandlers() { + const interpreterService = this.serviceContainer.get(IInterpreterService); this.disposableRegistry.push( this.workspaceService.onDidChangeConfiguration((e) => { - if (this.configChangedTimer) { - clearTimeout(this.configChangedTimer as any); - } - this.configChangedTimer = setTimeout(() => this.configurationChangeHandler(e), 1000); - }) - ); - this.disposableRegistry.push( - interpreterService.onDidChangeInterpreter(() => - this.autoDiscoverTests(undefined).catch((ex) => - traceError('Failed to auto discover tests upon changing interpreter', ex) - ) - ) + this.configChangeTrigger.trigger(e); + }), + interpreterService.onDidChangeInterpreter(async () => { + traceVerbose('Testing: Triggered refresh due to interpreter change.'); + sendTelemetryEvent(EventName.UNITTEST_DISCOVERY_TRIGGER, undefined, { trigger: 'interpreter' }); + await this.testController?.refreshTestData(undefined, { forceRefresh: true }); + }), ); } } diff --git a/src/client/testing/navigation/commandHandler.ts b/src/client/testing/navigation/commandHandler.ts deleted file mode 100644 index 27df132391bb..000000000000 --- a/src/client/testing/navigation/commandHandler.ts +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable, named } from 'inversify'; -import { ICommandManager } from '../../common/application/types'; -import { Commands } from '../../common/constants'; -import { IDisposable, IDisposableRegistry } from '../../common/types'; -import { ITestCodeNavigator, ITestCodeNavigatorCommandHandler, NavigableItemType } from './types'; - -@injectable() -export class TestCodeNavigatorCommandHandler implements ITestCodeNavigatorCommandHandler { - private disposables: IDisposable[] = []; - constructor( - @inject(ICommandManager) private readonly commandManager: ICommandManager, - @inject(ITestCodeNavigator) - @named(NavigableItemType.testFile) - private readonly testFileNavigator: ITestCodeNavigator, - @inject(ITestCodeNavigator) - @named(NavigableItemType.testFunction) - private readonly testFunctionNavigator: ITestCodeNavigator, - @inject(ITestCodeNavigator) - @named(NavigableItemType.testSuite) - private readonly testSuiteNavigator: ITestCodeNavigator, - @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry - ) { - disposableRegistry.push(this); - } - public dispose() { - this.disposables.forEach((item) => item.dispose()); - } - public register(): void { - if (this.disposables.length > 0) { - return; - } - let disposable = this.commandManager.registerCommand( - Commands.navigateToTestFile, - this.testFileNavigator.navigateTo, - this.testFileNavigator - ); - this.disposables.push(disposable); - disposable = this.commandManager.registerCommand( - Commands.navigateToTestFunction, - this.testFunctionNavigator.navigateTo, - this.testFunctionNavigator - ); - this.disposables.push(disposable); - disposable = this.commandManager.registerCommand( - Commands.navigateToTestSuite, - this.testSuiteNavigator.navigateTo, - this.testSuiteNavigator - ); - this.disposables.push(disposable); - } -} diff --git a/src/client/testing/navigation/fileNavigator.ts b/src/client/testing/navigation/fileNavigator.ts deleted file mode 100644 index 12f5ac69f8ab..000000000000 --- a/src/client/testing/navigation/fileNavigator.ts +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { Uri } from 'vscode'; -import { swallowExceptions } from '../../common/utils/decorators'; -import { captureTelemetry } from '../../telemetry'; -import { EventName } from '../../telemetry/constants'; -import { TestFile } from '../common/types'; -import { ITestCodeNavigator, ITestNavigatorHelper } from './types'; - -@injectable() -export class TestFileCodeNavigator implements ITestCodeNavigator { - constructor(@inject(ITestNavigatorHelper) private readonly helper: ITestNavigatorHelper) {} - @swallowExceptions('Navigate to test file') - @captureTelemetry(EventName.UNITTEST_NAVIGATE, { byFile: true }) - public async navigateTo(_: Uri, item: TestFile, __: boolean): Promise { - await this.helper.openFile(Uri.file(item.fullPath)); - } -} diff --git a/src/client/testing/navigation/functionNavigator.ts b/src/client/testing/navigation/functionNavigator.ts deleted file mode 100644 index 0c95e42128b6..000000000000 --- a/src/client/testing/navigation/functionNavigator.ts +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { CancellationTokenSource, Range, SymbolInformation, SymbolKind, TextEditorRevealType, Uri } from 'vscode'; -import { IDocumentManager } from '../../common/application/types'; -import { traceError } from '../../common/logger'; -import { swallowExceptions } from '../../common/utils/decorators'; -import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; -import { EventName } from '../../telemetry/constants'; -import { ITestCollectionStorageService, TestFunction } from '../common/types'; -import { ITestCodeNavigator, ITestNavigatorHelper } from './types'; - -@injectable() -export class TestFunctionCodeNavigator implements ITestCodeNavigator { - private cancellationToken?: CancellationTokenSource; - constructor( - @inject(ITestNavigatorHelper) private readonly helper: ITestNavigatorHelper, - @inject(IDocumentManager) private readonly docManager: IDocumentManager, - @inject(ITestCollectionStorageService) private readonly storage: ITestCollectionStorageService - ) {} - @swallowExceptions('Navigate to test function') - @captureTelemetry(EventName.UNITTEST_NAVIGATE, { byFunction: true }, true) // To measure execution time. - public async navigateTo(resource: Uri, fn: TestFunction, focus: boolean = true): Promise { - sendTelemetryEvent(EventName.UNITTEST_NAVIGATE, undefined, { focus_code: focus, byFunction: true }); - if (this.cancellationToken) { - this.cancellationToken.cancel(); - } - const item = this.storage.findFlattendTestFunction(resource, fn); - if (!item) { - throw new Error('Flattend test function not found'); - } - this.cancellationToken = new CancellationTokenSource(); - const [doc, editor] = await this.helper.openFile(Uri.file(item.parentTestFile.fullPath)); - let range: Range | undefined; - if (item.testFunction.line) { - range = new Range(item.testFunction.line, 0, item.testFunction.line, 0); - } else { - const predicate = (s: SymbolInformation) => - s.name === item.testFunction.name && (s.kind === SymbolKind.Method || s.kind === SymbolKind.Function); - const symbol = await this.helper.findSymbol(doc, predicate, this.cancellationToken.token); - range = symbol ? symbol.location.range : undefined; - } - if (!range) { - traceError('Unable to navigate to test function', new Error('Test Function not found')); - return; - } - if (focus) { - range = new Range(range.start.line, range.start.character, range.start.line, range.start.character); - await this.docManager.showTextDocument(doc, { preserveFocus: false, selection: range }); - } else { - editor.revealRange(range, TextEditorRevealType.Default); - } - } -} diff --git a/src/client/testing/navigation/helper.ts b/src/client/testing/navigation/helper.ts deleted file mode 100644 index 7c5e092ea4a7..000000000000 --- a/src/client/testing/navigation/helper.ts +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable, named } from 'inversify'; -import { CancellationToken, SymbolInformation, TextDocument, TextEditor, Uri } from 'vscode'; -import { IDocumentManager } from '../../common/application/types'; -import { traceError } from '../../common/logger'; -import { IDocumentSymbolProvider } from '../../common/types'; -import { ITestNavigatorHelper, SymbolSearch } from './types'; - -@injectable() -export class TestNavigatorHelper implements ITestNavigatorHelper { - constructor( - @inject(IDocumentManager) private readonly documentManager: IDocumentManager, - @inject(IDocumentSymbolProvider) @named('test') private readonly symbolProvider: IDocumentSymbolProvider - ) {} - public async openFile(file?: Uri): Promise<[TextDocument, TextEditor]> { - if (!file) { - throw new Error('Unable to navigate to an undefined test file'); - } - const doc = await this.documentManager.openTextDocument(file); - const editor = await this.documentManager.showTextDocument(doc); - return [doc, editor]; - } - public async findSymbol( - doc: TextDocument, - search: SymbolSearch, - token: CancellationToken - ): Promise { - const symbols = (await this.symbolProvider.provideDocumentSymbols(doc, token)) as SymbolInformation[]; - if (!Array.isArray(symbols) || symbols.length === 0) { - traceError('Symbol information not found', new Error('Symbol information not found')); - return; - } - return symbols.find(search); - } -} diff --git a/src/client/testing/navigation/serviceRegistry.ts b/src/client/testing/navigation/serviceRegistry.ts deleted file mode 100644 index bccf480a5b9f..000000000000 --- a/src/client/testing/navigation/serviceRegistry.ts +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { IDocumentSymbolProvider } from '../../common/types'; -import { IServiceManager } from '../../ioc/types'; -import { TestCodeNavigatorCommandHandler } from './commandHandler'; -import { TestFileCodeNavigator } from './fileNavigator'; -import { TestFunctionCodeNavigator } from './functionNavigator'; -import { TestNavigatorHelper } from './helper'; -import { TestSuiteCodeNavigator } from './suiteNavigator'; -import { TestFileSymbolProvider } from './symbolProvider'; -import { ITestCodeNavigator, ITestCodeNavigatorCommandHandler, ITestNavigatorHelper, NavigableItemType } from './types'; - -export function registerTypes(serviceManager: IServiceManager) { - serviceManager.addSingleton(ITestNavigatorHelper, TestNavigatorHelper); - serviceManager.addSingleton( - ITestCodeNavigatorCommandHandler, - TestCodeNavigatorCommandHandler - ); - serviceManager.addSingleton( - ITestCodeNavigator, - TestFileCodeNavigator, - NavigableItemType.testFile - ); - serviceManager.addSingleton( - ITestCodeNavigator, - TestFunctionCodeNavigator, - NavigableItemType.testFunction - ); - serviceManager.addSingleton( - ITestCodeNavigator, - TestSuiteCodeNavigator, - NavigableItemType.testSuite - ); - serviceManager.addSingleton(IDocumentSymbolProvider, TestFileSymbolProvider, 'test'); -} diff --git a/src/client/testing/navigation/suiteNavigator.ts b/src/client/testing/navigation/suiteNavigator.ts deleted file mode 100644 index 0230daa95e56..000000000000 --- a/src/client/testing/navigation/suiteNavigator.ts +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { CancellationTokenSource, Range, SymbolInformation, SymbolKind, TextEditorRevealType, Uri } from 'vscode'; -import { IDocumentManager } from '../../common/application/types'; -import { traceError } from '../../common/logger'; -import { swallowExceptions } from '../../common/utils/decorators'; -import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; -import { EventName } from '../../telemetry/constants'; -import { ITestCollectionStorageService, TestSuite } from '../common/types'; -import { ITestCodeNavigator, ITestNavigatorHelper } from './types'; - -@injectable() -export class TestSuiteCodeNavigator implements ITestCodeNavigator { - private cancellationToken?: CancellationTokenSource; - constructor( - @inject(ITestNavigatorHelper) private readonly helper: ITestNavigatorHelper, - @inject(IDocumentManager) private readonly docManager: IDocumentManager, - @inject(ITestCollectionStorageService) private readonly storage: ITestCollectionStorageService - ) {} - @swallowExceptions('Navigate to test suite') - @captureTelemetry(EventName.UNITTEST_NAVIGATE, { bySuite: true }, true) // For measuring execution time. - public async navigateTo(resource: Uri, suite: TestSuite, focus: boolean = true): Promise { - sendTelemetryEvent(EventName.UNITTEST_NAVIGATE, undefined, { focus_code: focus, bySuite: true }); - if (this.cancellationToken) { - this.cancellationToken.cancel(); - } - const item = this.storage.findFlattendTestSuite(resource, suite); - if (!item) { - throw new Error('Flattened test suite not found'); - } - this.cancellationToken = new CancellationTokenSource(); - const [doc, editor] = await this.helper.openFile(Uri.file(item.parentTestFile.fullPath)); - let range: Range | undefined; - if (item.testSuite.line) { - range = new Range(item.testSuite.line, 0, item.testSuite.line, 0); - } else { - const predicate = (s: SymbolInformation) => s.name === item.testSuite.name && s.kind === SymbolKind.Class; - const symbol = await this.helper.findSymbol(doc, predicate, this.cancellationToken.token); - range = symbol ? symbol.location.range : undefined; - } - if (!range) { - traceError('Unable to navigate to test suite', new Error('Test Suite not found')); - return; - } - if (focus) { - range = new Range(range.start.line, range.start.character, range.start.line, range.start.character); - await this.docManager.showTextDocument(doc, { preserveFocus: false, selection: range }); - } else { - editor.revealRange(range, TextEditorRevealType.Default); - } - } -} diff --git a/src/client/testing/navigation/symbolProvider.ts b/src/client/testing/navigation/symbolProvider.ts deleted file mode 100644 index 806776d89245..000000000000 --- a/src/client/testing/navigation/symbolProvider.ts +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { - CancellationToken, - DocumentSymbolProvider, - Location, - Range, - SymbolInformation, - SymbolKind, - TextDocument, - Uri -} from 'vscode'; -import { traceError } from '../../common/logger'; -import * as internalScripts from '../../common/process/internal/scripts'; -import { IPythonExecutionFactory } from '../../common/process/types'; - -type RawSymbol = { namespace: string; name: string; range: Range }; -type Symbols = { - classes: RawSymbol[]; - methods: RawSymbol[]; - functions: RawSymbol[]; -}; - -@injectable() -export class TestFileSymbolProvider implements DocumentSymbolProvider { - constructor(@inject(IPythonExecutionFactory) private readonly pythonServiceFactory: IPythonExecutionFactory) {} - public async provideDocumentSymbols( - document: TextDocument, - token: CancellationToken - ): Promise { - const rawSymbols = await this.getSymbols(document, token); - if (!rawSymbols) { - return []; - } - return [ - ...rawSymbols.classes.map((item) => this.parseRawSymbol(document.uri, item, SymbolKind.Class)), - ...rawSymbols.methods.map((item) => this.parseRawSymbol(document.uri, item, SymbolKind.Method)), - ...rawSymbols.functions.map((item) => this.parseRawSymbol(document.uri, item, SymbolKind.Function)) - ]; - } - private parseRawSymbol(uri: Uri, symbol: RawSymbol, kind: SymbolKind): SymbolInformation { - const range = new Range( - symbol.range.start.line, - symbol.range.start.character, - symbol.range.end.line, - symbol.range.end.character - ); - return { - containerName: symbol.namespace, - kind, - name: symbol.name, - location: new Location(uri, range) - }; - } - private async getSymbols(document: TextDocument, token: CancellationToken): Promise { - try { - if (document.isUntitled) { - return; - } - const [args, parse] = internalScripts.symbolProvider( - document.uri.fsPath, - document.isDirty ? document.getText() : undefined - ); - const pythonService = await this.pythonServiceFactory.create({ resource: document.uri }); - const proc = await pythonService.exec(args, { throwOnStdErr: true, token }); - - return (parse(proc.stdout) as unknown) as Symbols; - } catch (ex) { - traceError('Python: Failed to get symbols', ex); - return; - } - } -} diff --git a/src/client/testing/navigation/types.ts b/src/client/testing/navigation/types.ts deleted file mode 100644 index 62b9e4b809c4..000000000000 --- a/src/client/testing/navigation/types.ts +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { CancellationToken, SymbolInformation, TextDocument, TextEditor, Uri } from 'vscode'; -import { IDisposable } from '../../common/types'; -import { TestFile, TestFunction, TestSuite } from '../common/types'; - -export const ITestCodeNavigatorCommandHandler = Symbol('ITestCodeNavigatorCommandHandler'); -export interface ITestCodeNavigatorCommandHandler extends IDisposable { - register(): void; -} -export type NavigableItem = TestFile | TestFunction | TestSuite; -export enum NavigableItemType { - testFile = 'testFile', - testFunction = 'testFunction', - testSuite = 'testSuite' -} - -export const ITestCodeNavigator = Symbol('ITestCodeNavigator'); -export interface ITestCodeNavigator { - navigateTo(resource: Uri, item: NavigableItem, focus: boolean): Promise; -} - -export const ITestNavigatorHelper = Symbol('ITestNavigatorHelper'); -export interface ITestNavigatorHelper { - openFile(file?: Uri): Promise<[TextDocument, TextEditor]>; - findSymbol( - doc: TextDocument, - predicate: SymbolSearch, - token: CancellationToken - ): Promise; -} -export type SymbolSearch = (item: SymbolInformation) => boolean; - -export const ITestExplorerCommandHandler = Symbol('ITestExplorerCommandHandler'); -export interface ITestExplorerCommandHandler extends IDisposable { - register(): void; -} diff --git a/src/client/testing/nosetest/main.ts b/src/client/testing/nosetest/main.ts deleted file mode 100644 index e8bccbf26bf9..000000000000 --- a/src/client/testing/nosetest/main.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { inject, injectable } from 'inversify'; -import { Uri } from 'vscode'; -import { Product } from '../../common/types'; -import { IServiceContainer } from '../../ioc/types'; -import { NOSETEST_PROVIDER } from '../common/constants'; -import { BaseTestManager } from '../common/managers/baseTestManager'; -import { ITestsHelper, TestDiscoveryOptions, TestRunOptions, Tests, TestsToRun } from '../common/types'; -import { IArgumentsService, ITestManagerRunner, TestFilter } from '../types'; - -@injectable() -export class TestManager extends BaseTestManager { - private readonly argsService: IArgumentsService; - private readonly helper: ITestsHelper; - private readonly runner: ITestManagerRunner; - public get enabled() { - return this.settings.testing.nosetestsEnabled; - } - constructor( - workspaceFolder: Uri, - rootDirectory: string, - @inject(IServiceContainer) serviceContainer: IServiceContainer - ) { - super(NOSETEST_PROVIDER, Product.nosetest, workspaceFolder, rootDirectory, serviceContainer); - this.argsService = this.serviceContainer.get(IArgumentsService, this.testProvider); - this.helper = this.serviceContainer.get(ITestsHelper); - this.runner = this.serviceContainer.get(ITestManagerRunner, this.testProvider); - } - public getDiscoveryOptions(ignoreCache: boolean): TestDiscoveryOptions { - const args = this.settings.testing.nosetestArgs.slice(0); - return { - workspaceFolder: this.workspaceFolder, - cwd: this.rootDirectory, - args, - token: this.testDiscoveryCancellationToken!, - ignoreCache, - outChannel: this.outputChannel - }; - } - public runTestImpl( - tests: Tests, - testsToRun?: TestsToRun, - runFailedTests?: boolean, - debug?: boolean - ): Promise { - let args: string[]; - - const runAllTests = this.helper.shouldRunAllTests(testsToRun); - if (debug) { - args = this.argsService.filterArguments( - this.settings.testing.nosetestArgs, - runAllTests ? TestFilter.debugAll : TestFilter.debugSpecific - ); - } else { - args = this.argsService.filterArguments( - this.settings.testing.nosetestArgs, - runAllTests ? TestFilter.runAll : TestFilter.runSpecific - ); - } - - if (runFailedTests === true && args.indexOf('--failed') === -1) { - args.splice(0, 0, '--failed'); - } - if (!runFailedTests && args.indexOf('--with-id') === -1) { - args.splice(0, 0, '--with-id'); - } - const options: TestRunOptions = { - workspaceFolder: Uri.file(this.rootDirectory), - cwd: this.rootDirectory, - tests, - args, - testsToRun, - token: this.testRunnerCancellationToken!, - outChannel: this.outputChannel, - debug - }; - return this.runner.runTest(this.testResultsService, options, this); - } -} diff --git a/src/client/testing/nosetest/runner.ts b/src/client/testing/nosetest/runner.ts deleted file mode 100644 index e29a95e155ce..000000000000 --- a/src/client/testing/nosetest/runner.ts +++ /dev/null @@ -1,124 +0,0 @@ -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IFileSystem, TemporaryFile } from '../../common/platform/types'; -import { noop } from '../../common/utils/misc'; -import { IServiceContainer } from '../../ioc/types'; -import { NOSETEST_PROVIDER } from '../common/constants'; -import { Options } from '../common/runner'; -import { - ITestDebugLauncher, - ITestManager, - ITestResultsService, - ITestRunner, - IXUnitParser, - LaunchOptions, - TestRunOptions, - Tests -} from '../common/types'; -import { IArgumentsHelper, IArgumentsService, ITestManagerRunner } from '../types'; - -const WITH_XUNIT = '--with-xunit'; -const XUNIT_FILE = '--xunit-file'; - -@injectable() -export class TestManagerRunner implements ITestManagerRunner { - private readonly argsService: IArgumentsService; - private readonly argsHelper: IArgumentsHelper; - private readonly testRunner: ITestRunner; - private readonly xUnitParser: IXUnitParser; - private readonly fs: IFileSystem; - constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { - this.argsService = serviceContainer.get(IArgumentsService, NOSETEST_PROVIDER); - this.argsHelper = serviceContainer.get(IArgumentsHelper); - this.testRunner = serviceContainer.get(ITestRunner); - this.xUnitParser = this.serviceContainer.get(IXUnitParser); - this.fs = this.serviceContainer.get(IFileSystem); - } - public async runTest( - testResultsService: ITestResultsService, - options: TestRunOptions, - _: ITestManager - ): Promise { - let testPaths: string[] = []; - if (options.testsToRun && options.testsToRun.testFolder) { - testPaths = testPaths.concat(options.testsToRun.testFolder.map((f) => f.nameToRun)); - } - if (options.testsToRun && options.testsToRun.testFile) { - testPaths = testPaths.concat(options.testsToRun.testFile.map((f) => f.nameToRun)); - } - if (options.testsToRun && options.testsToRun.testSuite) { - testPaths = testPaths.concat(options.testsToRun.testSuite.map((f) => f.nameToRun)); - } - if (options.testsToRun && options.testsToRun.testFunction) { - testPaths = testPaths.concat(options.testsToRun.testFunction.map((f) => f.nameToRun)); - } - - let deleteJUnitXmlFile: Function = noop; - const args = options.args; - // Check if '--with-xunit' is in args list - if (args.indexOf(WITH_XUNIT) === -1) { - args.splice(0, 0, WITH_XUNIT); - } - - try { - const xmlLogResult = await this.getUnitXmlFile(args); - const xmlLogFile = xmlLogResult.filePath; - deleteJUnitXmlFile = xmlLogResult.dispose; - // Remove the '--unixml' if it exists, and add it with our path. - const testArgs = this.argsService.filterArguments(args, [XUNIT_FILE]); - testArgs.splice(0, 0, `${XUNIT_FILE}=${xmlLogFile}`); - - // Positional arguments control the tests to be run. - testArgs.push(...testPaths); - - if (options.debug === true) { - const debugLauncher = this.serviceContainer.get(ITestDebugLauncher); - const debuggerArgs = [options.cwd, 'nose'].concat(testArgs); - const launchOptions: LaunchOptions = { - cwd: options.cwd, - args: debuggerArgs, - token: options.token, - outChannel: options.outChannel, - testProvider: NOSETEST_PROVIDER - }; - await debugLauncher.launchDebugger(launchOptions); - } else { - const runOptions: Options = { - args: testArgs.concat(testPaths), - cwd: options.cwd, - outChannel: options.outChannel, - token: options.token, - workspaceFolder: options.workspaceFolder - }; - await this.testRunner.run(NOSETEST_PROVIDER, runOptions); - } - - return options.debug - ? options.tests - : await this.updateResultsFromLogFiles(options.tests, xmlLogFile, testResultsService); - } catch (ex) { - return Promise.reject(ex); - } finally { - deleteJUnitXmlFile(); - } - } - - private async updateResultsFromLogFiles( - tests: Tests, - outputXmlFile: string, - testResultsService: ITestResultsService - ): Promise { - await this.xUnitParser.updateResultsFromXmlLogFile(tests, outputXmlFile); - testResultsService.updateResults(tests); - return tests; - } - private async getUnitXmlFile(args: string[]): Promise { - const xmlFile = this.argsHelper.getOptionValues(args, XUNIT_FILE); - if (typeof xmlFile === 'string') { - return { filePath: xmlFile, dispose: noop }; - } - - return this.fs.createTemporaryFile('.xml'); - } -} diff --git a/src/client/testing/nosetest/services/argsService.ts b/src/client/testing/nosetest/services/argsService.ts deleted file mode 100644 index cba04ef343b2..000000000000 --- a/src/client/testing/nosetest/services/argsService.ts +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IServiceContainer } from '../../../ioc/types'; -import { IArgumentsHelper, IArgumentsService, TestFilter } from '../../types'; - -const OptionsWithArguments = [ - '--attr', - '--config', - '--cover-html-dir', - '--cover-min-percentage', - '--cover-package', - '--cover-xml-file', - '--debug', - '--debug-log', - '--doctest-extension', - '--doctest-fixtures', - '--doctest-options', - '--doctest-result-variable', - '--eval-attr', - '--exclude', - '--id-file', - '--ignore-files', - '--include', - '--log-config', - '--logging-config', - '--logging-datefmt', - '--logging-filter', - '--logging-format', - '--logging-level', - '--match', - '--process-timeout', - '--processes', - '--py3where', - '--testmatch', - '--tests', - '--verbosity', - '--where', - '--xunit-file', - '--xunit-testsuite-name', - '-A', - '-a', - '-c', - '-e', - '-i', - '-I', - '-l', - '-m', - '-w', - '--profile-restrict', - '--profile-sort', - '--profile-stats-file' -]; - -const OptionsWithoutArguments = [ - '-h', - '--help', - '-V', - '--version', - '-p', - '--plugins', - '-v', - '--verbose', - '--quiet', - '-x', - '--stop', - '-P', - '--no-path-adjustment', - '--exe', - '--noexe', - '--traverse-namespace', - '--first-package-wins', - '--first-pkg-wins', - '--1st-pkg-wins', - '--no-byte-compile', - '-s', - '--nocapture', - '--nologcapture', - '--logging-clear-handlers', - '--with-coverage', - '--cover-erase', - '--cover-tests', - '--cover-inclusive', - '--cover-html', - '--cover-branches', - '--cover-xml', - '--pdb', - '--pdb-failures', - '--pdb-errors', - '--no-deprecated', - '--with-doctest', - '--doctest-tests', - '--with-isolation', - '-d', - '--detailed-errors', - '--failure-detail', - '--no-skip', - '--with-id', - '--failed', - '--process-restartworker', - '--with-xunit', - '--all-modules', - '--collect-only', - '--with-profile' -]; - -@injectable() -export class ArgumentsService implements IArgumentsService { - private readonly helper: IArgumentsHelper; - constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - this.helper = serviceContainer.get(IArgumentsHelper); - } - public getKnownOptions(): { withArgs: string[]; withoutArgs: string[] } { - return { - withArgs: OptionsWithArguments, - withoutArgs: OptionsWithoutArguments - }; - } - public getOptionValue(args: string[], option: string): string | string[] | undefined { - return this.helper.getOptionValues(args, option); - } - // tslint:disable-next-line:max-func-body-length - public filterArguments(args: string[], argumentToRemoveOrFilter: string[] | TestFilter): string[] { - const optionsWithoutArgsToRemove: string[] = []; - const optionsWithArgsToRemove: string[] = []; - // Positional arguments in nosetest are test directories and files. - // So if we want to run a specific test, then remove positional args. - let removePositionalArgs = false; - if (Array.isArray(argumentToRemoveOrFilter)) { - argumentToRemoveOrFilter.forEach((item) => { - if (OptionsWithArguments.indexOf(item) >= 0) { - optionsWithArgsToRemove.push(item); - } - if (OptionsWithoutArguments.indexOf(item) >= 0) { - optionsWithoutArgsToRemove.push(item); - } - }); - } else { - switch (argumentToRemoveOrFilter) { - case TestFilter.removeTests: { - removePositionalArgs = true; - break; - } - case TestFilter.discovery: { - optionsWithoutArgsToRemove.push( - ...[ - '-v', - '--verbose', - '-q', - '--quiet', - '-x', - '--stop', - '--with-coverage', - ...OptionsWithoutArguments.filter((item) => item.startsWith('--cover')), - ...OptionsWithoutArguments.filter((item) => item.startsWith('--logging')), - ...OptionsWithoutArguments.filter((item) => item.startsWith('--pdb')), - ...OptionsWithoutArguments.filter((item) => item.indexOf('xunit') >= 0) - ] - ); - optionsWithArgsToRemove.push( - ...[ - '--verbosity', - '-l', - '--debug', - '--cover-package', - ...OptionsWithoutArguments.filter((item) => item.startsWith('--cover')), - ...OptionsWithArguments.filter((item) => item.startsWith('--logging')), - ...OptionsWithoutArguments.filter((item) => item.indexOf('xunit') >= 0) - ] - ); - break; - } - case TestFilter.debugAll: - case TestFilter.runAll: { - break; - } - case TestFilter.debugSpecific: - case TestFilter.runSpecific: { - removePositionalArgs = true; - break; - } - default: { - throw new Error(`Unsupported Filter '${argumentToRemoveOrFilter}'`); - } - } - } - - let filteredArgs = args.slice(); - if (removePositionalArgs) { - const positionalArgs = this.helper.getPositionalArguments( - filteredArgs, - OptionsWithArguments, - OptionsWithoutArguments - ); - filteredArgs = filteredArgs.filter((item) => positionalArgs.indexOf(item) === -1); - } - return this.helper.filterArguments(filteredArgs, optionsWithArgsToRemove, optionsWithoutArgsToRemove); - } - public getTestFolders(args: string[]): string[] { - return this.helper.getPositionalArguments(args, OptionsWithArguments, OptionsWithoutArguments); - } -} diff --git a/src/client/testing/nosetest/services/discoveryService.ts b/src/client/testing/nosetest/services/discoveryService.ts deleted file mode 100644 index 616b40d14f6c..000000000000 --- a/src/client/testing/nosetest/services/discoveryService.ts +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable, named } from 'inversify'; -import { CancellationTokenSource } from 'vscode'; -import { IServiceContainer } from '../../../ioc/types'; -import { NOSETEST_PROVIDER } from '../../common/constants'; -import { Options } from '../../common/runner'; -import { ITestDiscoveryService, ITestRunner, ITestsParser, TestDiscoveryOptions, Tests } from '../../common/types'; -import { IArgumentsService, TestFilter } from '../../types'; - -@injectable() -export class TestDiscoveryService implements ITestDiscoveryService { - private argsService: IArgumentsService; - private runner: ITestRunner; - constructor( - @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(ITestsParser) @named(NOSETEST_PROVIDER) private testParser: ITestsParser - ) { - this.argsService = this.serviceContainer.get(IArgumentsService, NOSETEST_PROVIDER); - this.runner = this.serviceContainer.get(ITestRunner); - } - public async discoverTests(options: TestDiscoveryOptions): Promise { - // Remove unwanted arguments. - const args = this.argsService.filterArguments(options.args, TestFilter.discovery); - - const token = options.token ? options.token : new CancellationTokenSource().token; - const runOptions: Options = { - args: ['--collect-only', '-vvv'].concat(args), - cwd: options.cwd, - workspaceFolder: options.workspaceFolder, - token, - outChannel: options.outChannel - }; - - const data = await this.runner.run(NOSETEST_PROVIDER, runOptions); - if (options.token && options.token.isCancellationRequested) { - return Promise.reject('cancelled'); - } - - return this.testParser.parse(data, options); - } -} diff --git a/src/client/testing/nosetest/services/parserService.ts b/src/client/testing/nosetest/services/parserService.ts deleted file mode 100644 index 80fb184dfc97..000000000000 --- a/src/client/testing/nosetest/services/parserService.ts +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -import * as os from 'os'; -import * as path from 'path'; -import { Uri } from 'vscode'; -import { convertFileToPackage, extractBetweenDelimiters } from '../../common/testUtils'; -import { - ITestsHelper, - ITestsParser, - ParserOptions, - TestFile, - TestFunction, - Tests, - TestSuite -} from '../../common/types'; - -const NOSE_WANT_FILE_PREFIX = 'nose.selector: DEBUG: wantFile '; -const NOSE_WANT_FILE_SUFFIX = '.py? True'; -const NOSE_WANT_FILE_SUFFIX_WITHOUT_EXT = '? True'; - -@injectable() -export class TestsParser implements ITestsParser { - constructor(@inject(ITestsHelper) private testsHelper: ITestsHelper) {} - public parse(content: string, options: ParserOptions): Tests { - let testFiles = this.getTestFiles(content, options); - // Exclude tests that don't have any functions or test suites. - testFiles = testFiles.filter((testFile) => testFile.suites.length > 0 || testFile.functions.length > 0); - return this.testsHelper.flattenTestFiles(testFiles, options.cwd); - } - - private getTestFiles(content: string, options: ParserOptions) { - let logOutputLines: string[] = ['']; - const testFiles: TestFile[] = []; - content.split(/\r?\n/g).forEach((line, index, lines) => { - if ( - (line.startsWith(NOSE_WANT_FILE_PREFIX) && line.endsWith(NOSE_WANT_FILE_SUFFIX)) || - index === lines.length - 1 - ) { - // process the previous lines. - this.parseNoseTestModuleCollectionResult(options.cwd, logOutputLines, testFiles); - logOutputLines = ['']; - } - - if (index === 0) { - if (content.startsWith(os.EOL) || lines.length > 1) { - this.appendLine(line, logOutputLines); - return; - } - logOutputLines[logOutputLines.length - 1] += line; - return; - } - if (index === lines.length - 1) { - logOutputLines[logOutputLines.length - 1] += line; - return; - } - this.appendLine(line, logOutputLines); - return; - }); - - return testFiles; - } - private appendLine(line: string, logOutputLines: string[]) { - const lastLineIndex = logOutputLines.length - 1; - logOutputLines[lastLineIndex] += line; - - // Check whether the previous line is something that we need. - // What we need is a line that ends with ? True, - // and starts with nose.selector: DEBUG: want. - if (logOutputLines[lastLineIndex].endsWith('? True')) { - logOutputLines.push(''); - } else { - // We don't need this line - logOutputLines[lastLineIndex] = ''; - } - } - - private parseNoseTestModuleCollectionResult(rootDirectory: string, lines: string[], testFiles: TestFile[]) { - let currentPackage: string = ''; - let fileName = ''; - let testFile: TestFile; - const resource = Uri.file(rootDirectory); - // tslint:disable-next-line: max-func-body-length - lines.forEach((line) => { - if (line.startsWith(NOSE_WANT_FILE_PREFIX) && line.endsWith(NOSE_WANT_FILE_SUFFIX)) { - fileName = line.substring(NOSE_WANT_FILE_PREFIX.length); - fileName = fileName.substring(0, fileName.lastIndexOf(NOSE_WANT_FILE_SUFFIX_WITHOUT_EXT)); - - // We need to display the path relative to the current directory. - fileName = fileName.substring(rootDirectory.length + 1); - // we don't care about the compiled file. - if (path.extname(fileName) === '.pyc' || path.extname(fileName) === '.pyo') { - fileName = fileName.substring(0, fileName.length - 1); - } - currentPackage = convertFileToPackage(fileName); - const fullyQualifiedName = path.isAbsolute(fileName) ? fileName : path.resolve(rootDirectory, fileName); - testFile = { - resource, - functions: [], - suites: [], - name: fileName, - nameToRun: fileName, - xmlName: currentPackage, - time: 0, - functionsFailed: 0, - functionsPassed: 0, - fullPath: fullyQualifiedName - }; - testFiles.push(testFile); - return; - } - - if (line.startsWith("nose.selector: DEBUG: wantClass ? True"); - const clsName = path.extname(name).substring(1); - const testSuite: TestSuite = { - resource, - name: clsName, - nameToRun: `${fileName}:${clsName}`, - functions: [], - suites: [], - xmlName: name, - time: 0, - isUnitTest: false, - isInstance: false, - functionsFailed: 0, - functionsPassed: 0 - }; - testFile.suites.push(testSuite); - return; - } - if (line.startsWith('nose.selector: DEBUG: wantClass ')) { - const name = extractBetweenDelimiters(line, 'nose.selector: DEBUG: wantClass ', '? True'); - const testSuite: TestSuite = { - resource, - name: path.extname(name).substring(1), - nameToRun: `${fileName}:.${name}`, - functions: [], - suites: [], - xmlName: name, - time: 0, - isUnitTest: false, - isInstance: false, - functionsFailed: 0, - functionsPassed: 0 - }; - testFile.suites.push(testSuite); - return; - } - if (line.startsWith('nose.selector: DEBUG: wantMethod ? True' - ); - const fnName = path.extname(name).substring(1); - const clsName = path.basename(name, path.extname(name)); - const fn: TestFunction = { - resource, - name: fnName, - nameToRun: `${fileName}:${clsName}.${fnName}`, - time: 0, - functionsFailed: 0, - functionsPassed: 0 - }; - - const cls = testFile.suites.find((suite) => suite.name === clsName); - if (cls) { - cls.functions.push(fn); - } - return; - } - if (line.startsWith('nose.selector: DEBUG: wantFunction { - const fs = this.serviceContainer.get(IFileSystem); - for (const cfg of ['.noserc', 'nose.cfg']) { - if (await fs.fileExists(path.join(wkspace.fsPath, cfg))) { - return true; - } - } - return false; - } - public async configure(wkspace: Uri): Promise { - const args: string[] = []; - const configFileOptionLabel = 'Use existing config file'; - // If a config file exits, there's nothing to be configured. - if (await this.requiresUserToConfigure(wkspace)) { - return; - } - const subDirs = await this.getTestDirs(wkspace.fsPath); - const testDir = await this.selectTestDir(wkspace.fsPath, subDirs); - if (typeof testDir === 'string' && testDir !== configFileOptionLabel) { - args.push(testDir); - } - const installed = await this.installer.isInstalled(Product.nosetest); - if (!installed) { - await this.installer.install(Product.nosetest); - } - await this.testConfigSettingsService.updateTestArgs(wkspace.fsPath, Product.nosetest, args); - } -} diff --git a/src/client/testing/pytest/main.ts b/src/client/testing/pytest/main.ts deleted file mode 100644 index 3873db1c5b1b..000000000000 --- a/src/client/testing/pytest/main.ts +++ /dev/null @@ -1,89 +0,0 @@ -'use strict'; - -import { Uri } from 'vscode'; -import { Product } from '../../common/types'; -import { IServiceContainer } from '../../ioc/types'; -import { PYTEST_PROVIDER } from '../common/constants'; -import { BaseTestManager } from '../common/managers/baseTestManager'; -import { - ITestMessageService, - ITestsHelper, - TestDiscoveryOptions, - TestRunOptions, - Tests, - TestsToRun -} from '../common/types'; -import { IArgumentsService, IPythonTestMessage, ITestManagerRunner, TestFilter } from '../types'; - -export class TestManager extends BaseTestManager { - private readonly argsService: IArgumentsService; - private readonly helper: ITestsHelper; - private readonly runner: ITestManagerRunner; - private readonly testMessageService: ITestMessageService; - public get enabled() { - return this.settings.testing.pytestEnabled; - } - constructor(workspaceFolder: Uri, rootDirectory: string, serviceContainer: IServiceContainer) { - super(PYTEST_PROVIDER, Product.pytest, workspaceFolder, rootDirectory, serviceContainer); - this.argsService = this.serviceContainer.get(IArgumentsService, this.testProvider); - this.helper = this.serviceContainer.get(ITestsHelper); - this.runner = this.serviceContainer.get(ITestManagerRunner, this.testProvider); - this.testMessageService = this.serviceContainer.get( - ITestMessageService, - this.testProvider - ); - } - public getDiscoveryOptions(ignoreCache: boolean): TestDiscoveryOptions { - const args = this.settings.testing.pytestArgs.slice(0); - return { - workspaceFolder: this.workspaceFolder, - cwd: this.rootDirectory, - args, - token: this.testDiscoveryCancellationToken!, - ignoreCache, - outChannel: this.outputChannel - }; - } - public async runTestImpl( - tests: Tests, - testsToRun?: TestsToRun, - runFailedTests?: boolean, - debug?: boolean - ): Promise { - let args: string[]; - - const runAllTests = this.helper.shouldRunAllTests(testsToRun); - if (debug) { - args = this.argsService.filterArguments( - this.settings.testing.pytestArgs, - runAllTests ? TestFilter.debugAll : TestFilter.debugSpecific - ); - } else { - args = this.argsService.filterArguments( - this.settings.testing.pytestArgs, - runAllTests ? TestFilter.runAll : TestFilter.runSpecific - ); - } - - if (runFailedTests === true && args.indexOf('--lf') === -1 && args.indexOf('--last-failed') === -1) { - args.splice(0, 0, '--last-failed'); - } - const options: TestRunOptions = { - workspaceFolder: this.workspaceFolder, - cwd: this.rootDirectory, - tests, - args, - testsToRun, - debug, - token: this.testRunnerCancellationToken!, - outChannel: this.outputChannel - }; - const testResults = await this.runner.runTest(this.testResultsService, options, this); - const messages: IPythonTestMessage[] = await this.testMessageService.getFilteredTestMessages( - this.rootDirectory, - testResults - ); - await this.updateDiagnostics(tests, messages); - return testResults; - } -} diff --git a/src/client/testing/pytest/runner.ts b/src/client/testing/pytest/runner.ts deleted file mode 100644 index cde8e4765125..000000000000 --- a/src/client/testing/pytest/runner.ts +++ /dev/null @@ -1,120 +0,0 @@ -'use strict'; -import { inject, injectable } from 'inversify'; -import { IFileSystem, TemporaryFile } from '../../common/platform/types'; -import { noop } from '../../common/utils/misc'; -import { IServiceContainer } from '../../ioc/types'; -import { PYTEST_PROVIDER } from '../common/constants'; -import { Options } from '../common/runner'; -import { - ITestDebugLauncher, - ITestManager, - ITestResultsService, - ITestRunner, - IXUnitParser, - LaunchOptions, - TestRunOptions, - Tests -} from '../common/types'; -import { IArgumentsHelper, IArgumentsService, ITestManagerRunner } from '../types'; - -const JunitXmlArgOld = '--junitxml'; -const JunitXmlArg = '--junit-xml'; -@injectable() -export class TestManagerRunner implements ITestManagerRunner { - private readonly argsService: IArgumentsService; - private readonly argsHelper: IArgumentsHelper; - private readonly testRunner: ITestRunner; - private readonly xUnitParser: IXUnitParser; - private readonly fs: IFileSystem; - constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { - this.argsService = serviceContainer.get(IArgumentsService, PYTEST_PROVIDER); - this.argsHelper = serviceContainer.get(IArgumentsHelper); - this.testRunner = serviceContainer.get(ITestRunner); - this.xUnitParser = this.serviceContainer.get(IXUnitParser); - this.fs = this.serviceContainer.get(IFileSystem); - } - public async runTest( - testResultsService: ITestResultsService, - options: TestRunOptions, - _: ITestManager - ): Promise { - let testPaths: string[] = []; - if (options.testsToRun && options.testsToRun.testFolder) { - testPaths = testPaths.concat(options.testsToRun.testFolder.map((f) => f.nameToRun)); - } - if (options.testsToRun && options.testsToRun.testFile) { - testPaths = testPaths.concat(options.testsToRun.testFile.map((f) => f.nameToRun)); - } - if (options.testsToRun && options.testsToRun.testSuite) { - testPaths = testPaths.concat(options.testsToRun.testSuite.map((f) => f.nameToRun)); - } - if (options.testsToRun && options.testsToRun.testFunction) { - testPaths = testPaths.concat(options.testsToRun.testFunction.map((f) => f.nameToRun)); - } - - let deleteJUnitXmlFile: Function = noop; - const args = options.args; - try { - const xmlLogResult = await this.getJUnitXmlFile(args); - const xmlLogFile = xmlLogResult.filePath; - deleteJUnitXmlFile = xmlLogResult.dispose; - // Remove the '--junitxml' or '--junit-xml' if it exists, and add it with our path. - const testArgs = this.argsService.filterArguments(args, [JunitXmlArg, JunitXmlArgOld]); - testArgs.splice(0, 0, `${JunitXmlArg}=${xmlLogFile}`); - - testArgs.splice(0, 0, '--rootdir', options.workspaceFolder.fsPath); - testArgs.splice(0, 0, '--override-ini', 'junit_family=xunit1'); - - // Positional arguments control the tests to be run. - testArgs.push(...testPaths); - - if (options.debug) { - const debugLauncher = this.serviceContainer.get(ITestDebugLauncher); - const debuggerArgs = [options.cwd, 'pytest'].concat(testArgs); - const launchOptions: LaunchOptions = { - cwd: options.cwd, - args: debuggerArgs, - token: options.token, - outChannel: options.outChannel, - testProvider: PYTEST_PROVIDER - }; - await debugLauncher.launchDebugger(launchOptions); - } else { - const runOptions: Options = { - args: testArgs, - cwd: options.cwd, - outChannel: options.outChannel, - token: options.token, - workspaceFolder: options.workspaceFolder - }; - await this.testRunner.run(PYTEST_PROVIDER, runOptions); - } - - return options.debug - ? options.tests - : await this.updateResultsFromLogFiles(options.tests, xmlLogFile, testResultsService); - } catch (ex) { - return Promise.reject(ex); - } finally { - deleteJUnitXmlFile(); - } - } - - private async updateResultsFromLogFiles( - tests: Tests, - outputXmlFile: string, - testResultsService: ITestResultsService - ): Promise { - await this.xUnitParser.updateResultsFromXmlLogFile(tests, outputXmlFile); - testResultsService.updateResults(tests); - return tests; - } - - private async getJUnitXmlFile(args: string[]): Promise { - const xmlFile = this.argsHelper.getOptionValues(args, JunitXmlArg); - if (typeof xmlFile === 'string') { - return { filePath: xmlFile, dispose: noop }; - } - return this.fs.createTemporaryFile('.xml'); - } -} diff --git a/src/client/testing/pytest/services/argsService.ts b/src/client/testing/pytest/services/argsService.ts deleted file mode 100644 index ffa80c329dfe..000000000000 --- a/src/client/testing/pytest/services/argsService.ts +++ /dev/null @@ -1,285 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IServiceContainer } from '../../../ioc/types'; -import { IArgumentsHelper, IArgumentsService, TestFilter } from '../../types'; - -const OptionsWithArguments = [ - '-c', - '-k', - '-m', - '-o', - '-p', - '-r', - '-W', - '-n', // -n is a pytest-xdist option - '--assert', - '--basetemp', - '--cache-show', - '--capture', - '--color', - '--confcutdir', - '--cov', - '--cov-config', - '--cov-fail-under', - '--cov-report', - '--deselect', - '--dist', - '--doctest-glob', - '--doctest-report', - '--durations', - '--ignore', - '--ignore-glob', - '--import-mode', - '--junit-prefix', - '--junit-xml', - '--last-failed-no-failures', - '--lfnf', - '--log-auto-indent', - '--log-cli-date-format', - '--log-cli-format', - '--log-cli-level', - '--log-date-format', - '--log-file', - '--log-file-date-format', - '--log-file-format', - '--log-file-level', - '--log-format', - '--log-level', - '--maxfail', - '--override-ini', - '--pastebin', - '--pdbcls', - '--pythonwarnings', - '--result-log', - '--rootdir', - '--show-capture', - '--tb', - '--verbosity', - '--max-slave-restart', - '--numprocesses', - '--rsyncdir', - '--rsyncignore', - '--tx' -]; - -const OptionsWithoutArguments = [ - '--cache-clear', - '--collect-in-virtualenv', - '--collect-only', - '--co', - '--continue-on-collection-errors', - '--cov-append', - '--cov-branch', - '--debug', - '--disable-pytest-warnings', - '--disable-warnings', - '--doctest-continue-on-failure', - '--doctest-ignore-import-errors', - '--doctest-modules', - '--exitfirst', - '--failed-first', - '--ff', - '--fixtures', - '--fixtures-per-test', - '--force-sugar', - '--full-trace', - '--funcargs', - '--help', - '--keep-duplicates', - '--last-failed', - '--lf', - '--markers', - '--new-first', - '--nf', - '--no-cov', - '--no-cov-on-fail', - '--no-print-logs', - '--noconftest', - '--old-summary', - '--pdb', - '--pyargs', - '-PyTest, Unittest-pyargs', - '--quiet', - '--runxfail', - '--setup-only', - '--setup-plan', - '--setup-show', - '--showlocals', - '--stepwise', - '--sw', - '--stepwise-skip', - '--strict', - '--strict-markers', - '--trace-config', - '--verbose', - '--version', - '-V', - '-h', - '-l', - '-q', - '-s', - '-v', - '-x', - '--boxed', - '--forked', - '--looponfail', - '--trace', - '--tx', - '-d' -]; - -@injectable() -export class ArgumentsService implements IArgumentsService { - private readonly helper: IArgumentsHelper; - constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - this.helper = serviceContainer.get(IArgumentsHelper); - } - public getKnownOptions(): { withArgs: string[]; withoutArgs: string[] } { - return { - withArgs: OptionsWithArguments, - withoutArgs: OptionsWithoutArguments - }; - } - public getOptionValue(args: string[], option: string): string | string[] | undefined { - return this.helper.getOptionValues(args, option); - } - // tslint:disable-next-line: max-func-body-length - public filterArguments(args: string[], argumentToRemoveOrFilter: string[] | TestFilter): string[] { - const optionsWithoutArgsToRemove: string[] = []; - const optionsWithArgsToRemove: string[] = []; - // Positional arguments in pytest are test directories and files. - // So if we want to run a specific test, then remove positional args. - let removePositionalArgs = false; - if (Array.isArray(argumentToRemoveOrFilter)) { - argumentToRemoveOrFilter.forEach((item) => { - if (OptionsWithArguments.indexOf(item) >= 0) { - optionsWithArgsToRemove.push(item); - } - if (OptionsWithoutArguments.indexOf(item) >= 0) { - optionsWithoutArgsToRemove.push(item); - } - }); - } else { - switch (argumentToRemoveOrFilter) { - case TestFilter.removeTests: { - optionsWithoutArgsToRemove.push( - ...['--lf', '--last-failed', '--ff', '--failed-first', '--nf', '--new-first'] - ); - optionsWithArgsToRemove.push(...['-k', '-m', '--lfnf', '--last-failed-no-failures']); - removePositionalArgs = true; - break; - } - case TestFilter.discovery: { - optionsWithoutArgsToRemove.push( - ...[ - '-x', - '--exitfirst', - '--fixtures', - '--funcargs', - '--fixtures-per-test', - '--pdb', - '--lf', - '--last-failed', - '--ff', - '--failed-first', - '--nf', - '--new-first', - '--cache-show', - '-v', - '--verbose', - '-q', - '-quiet', - '-l', - '--showlocals', - '--no-print-logs', - '--debug', - '--setup-only', - '--setup-show', - '--setup-plan', - '--trace' - ] - ); - optionsWithArgsToRemove.push( - ...[ - '-m', - '--maxfail', - '--pdbcls', - '--capture', - '--lfnf', - '--last-failed-no-failures', - '--verbosity', - '-r', - '--tb', - '--rootdir', - '--show-capture', - '--durations', - '--junit-xml', - '--junit-prefix', - '--result-log', - '-W', - '--pythonwarnings', - '--log-*' - ] - ); - removePositionalArgs = true; - break; - } - case TestFilter.debugAll: - case TestFilter.runAll: { - optionsWithoutArgsToRemove.push(...['--collect-only', '--trace']); - break; - } - case TestFilter.debugSpecific: - case TestFilter.runSpecific: { - optionsWithoutArgsToRemove.push( - ...[ - '--collect-only', - '--lf', - '--last-failed', - '--ff', - '--failed-first', - '--nf', - '--new-first', - '--trace' - ] - ); - optionsWithArgsToRemove.push(...['-k', '-m', '--lfnf', '--last-failed-no-failures']); - removePositionalArgs = true; - break; - } - default: { - throw new Error(`Unsupported Filter '${argumentToRemoveOrFilter}'`); - } - } - } - - let filteredArgs = args.slice(); - if (removePositionalArgs) { - const positionalArgs = this.helper.getPositionalArguments( - filteredArgs, - OptionsWithArguments, - OptionsWithoutArguments - ); - filteredArgs = filteredArgs.filter((item) => positionalArgs.indexOf(item) === -1); - } - return this.helper.filterArguments(filteredArgs, optionsWithArgsToRemove, optionsWithoutArgsToRemove); - } - public getTestFolders(args: string[]): string[] { - const testDirs = this.helper.getOptionValues(args, '--rootdir'); - if (typeof testDirs === 'string') { - return [testDirs]; - } - if (Array.isArray(testDirs) && testDirs.length > 0) { - return testDirs; - } - const positionalArgs = this.helper.getPositionalArguments(args, OptionsWithArguments, OptionsWithoutArguments); - // Positional args in pytest are files or directories. - // Remove files from the args, and what's left are test directories. - // If users enter test modules/methods, then its not supported. - return positionalArgs.filter((arg) => !arg.toUpperCase().endsWith('.PY')); - } -} diff --git a/src/client/testing/pytest/services/discoveryService.ts b/src/client/testing/pytest/services/discoveryService.ts deleted file mode 100644 index 965336217405..000000000000 --- a/src/client/testing/pytest/services/discoveryService.ts +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -import { CancellationTokenSource } from 'vscode'; -import { IServiceContainer } from '../../../ioc/types'; -import { PYTEST_PROVIDER } from '../../common/constants'; -import { ITestDiscoveryService, ITestsHelper, TestDiscoveryOptions, Tests } from '../../common/types'; -import { IArgumentsService, TestFilter } from '../../types'; - -@injectable() -export class TestDiscoveryService implements ITestDiscoveryService { - private argsService: IArgumentsService; - private helper: ITestsHelper; - constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { - this.argsService = this.serviceContainer.get(IArgumentsService, PYTEST_PROVIDER); - this.helper = this.serviceContainer.get(ITestsHelper); - } - public async discoverTests(options: TestDiscoveryOptions): Promise { - const args = this.buildTestCollectionArgs(options); - - // Collect tests for each test directory separately and merge. - const testDirectories = this.argsService.getTestFolders(options.args); - if (testDirectories.length === 0) { - const opts = { - ...options, - args - }; - return this.discoverTestsInTestDirectory(opts); - } - const results = await Promise.all( - testDirectories.map((testDir) => { - // Add test directory as a positional argument. - const opts = { - ...options, - args: [...args, testDir] - }; - return this.discoverTestsInTestDirectory(opts); - }) - ); - - return this.helper.mergeTests(results); - } - protected buildTestCollectionArgs(options: TestDiscoveryOptions) { - // Remove unwnted arguments (which happen to be test directories & test specific args). - const args = this.argsService.filterArguments(options.args, TestFilter.discovery); - if (options.ignoreCache && args.indexOf('--cache-clear') === -1) { - args.splice(0, 0, '--cache-clear'); - } - if (args.indexOf('-s') === -1) { - args.splice(0, 0, '-s'); - } - args.splice(0, 0, '--rootdir', options.workspaceFolder.fsPath); - return args; - } - protected async discoverTestsInTestDirectory(options: TestDiscoveryOptions): Promise { - const token = options.token ? options.token : new CancellationTokenSource().token; - const discoveryOptions = { ...options }; - discoveryOptions.args = ['discover', 'pytest', '--', ...options.args]; - discoveryOptions.token = token; - - const discoveryService = this.serviceContainer.get(ITestDiscoveryService, 'common'); - if (discoveryOptions.token && discoveryOptions.token.isCancellationRequested) { - return Promise.reject('cancelled'); - } - - return discoveryService.discoverTests(discoveryOptions); - } -} diff --git a/src/client/testing/pytest/services/testMessageService.ts b/src/client/testing/pytest/services/testMessageService.ts deleted file mode 100644 index 049833c001e9..000000000000 --- a/src/client/testing/pytest/services/testMessageService.ts +++ /dev/null @@ -1,279 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { Location, Position, Range, TextLine, Uri, workspace } from 'vscode'; -import '../../../common/extensions'; -import { ProductNames } from '../../../common/installer/productNames'; -import { IFileSystem } from '../../../common/platform/types'; -import { Product } from '../../../common/types'; -import { IServiceContainer } from '../../../ioc/types'; -import { FlattenedTestFunction, ITestMessageService, Tests, TestStatus } from '../../common/types'; -import { ILocationStackFrameDetails, IPythonTestMessage, PythonTestMessageSeverity } from '../../types'; - -@injectable() -export class TestMessageService implements ITestMessageService { - constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {} - /** - * Condense the test details down to just the potentially relevant information. Messages - * should only be created for tests that were actually run. - * - * @param rootDirectory - * @param testResults Details about all known tests. - */ - public async getFilteredTestMessages(rootDirectory: string, testResults: Tests): Promise { - const testFuncs = testResults.testFunctions.reduce((filtered, test) => { - if (test.testFunction.passed !== undefined || test.testFunction.status === TestStatus.Skipped) { - filtered.push(test); - } - return filtered; - }, []); - const messages: IPythonTestMessage[] = []; - for (const tf of testFuncs) { - const nameToRun = tf.testFunction.nameToRun; - const provider = ProductNames.get(Product.pytest)!; - const status = tf.testFunction.status!; - if (status === TestStatus.Pass) { - // If the test passed, there's not much to do with it. - const msg: IPythonTestMessage = { - code: nameToRun, - severity: PythonTestMessageSeverity.Pass, - provider: provider, - testTime: tf.testFunction.time, - status: status, - testFilePath: tf.parentTestFile.fullPath - }; - messages.push(msg); - } else { - // If the test did not pass, we need to parse the traceback to find each line in - // their respective files so they can be included as related information for the - // diagnostic. - const locationStack = await this.getLocationStack(rootDirectory, tf); - const message = tf.testFunction.message; - const testFilePath = tf.parentTestFile.fullPath; - let severity = PythonTestMessageSeverity.Error; - if (tf.testFunction.status === TestStatus.Skipped) { - severity = PythonTestMessageSeverity.Skip; - } - - const msg: IPythonTestMessage = { - code: nameToRun, - message: message, - severity: severity, - provider: provider, - traceback: tf.testFunction.traceback, - testTime: tf.testFunction.time, - testFilePath: testFilePath, - status: status, - locationStack: locationStack - }; - messages.push(msg); - } - } - return messages; - } - /** - * Given a FlattenedTestFunction, parse its traceback to piece together where each line in the - * traceback was in its respective file and grab the entire text of each line so they can be - * included in the Diagnostic as related information. - * - * @param testFunction The FlattenedTestFunction with the traceback that we need to parse. - */ - private async getLocationStack( - rootDirectory: string, - testFunction: FlattenedTestFunction - ): Promise { - const locationStack: ILocationStackFrameDetails[] = []; - if (testFunction.testFunction.traceback) { - const fileMatches = - testFunction.testFunction.traceback.match(/^((\.\.[\\\/])*.+\.py)\:(\d+)\:.*$/gim) || []; - for (const fileDetailsMatch of fileMatches) { - const fileDetails = fileDetailsMatch.split(':'); - let filePath = fileDetails[0]; - filePath = path.isAbsolute(filePath) ? filePath : path.resolve(rootDirectory, filePath); - const fileUri = Uri.file(filePath); - const file = await workspace.openTextDocument(fileUri); - const fileLineNum = parseInt(fileDetails[1], 10); - const line = file.lineAt(fileLineNum - 1); - const location = new Location( - fileUri, - new Range( - new Position(fileLineNum - 1, line.firstNonWhitespaceCharacterIndex), - new Position(fileLineNum - 1, line.text.length) - ) - ); - const stackFrame: ILocationStackFrameDetails = { - location: location, - lineText: file.getText(location.range) - }; - locationStack.push(stackFrame); - } - } - // Find where the file the test was defined. - let testSourceFilePath = testFunction.testFunction.file!; - testSourceFilePath = path.isAbsolute(testSourceFilePath) - ? testSourceFilePath - : path.resolve(rootDirectory, testSourceFilePath); - const testSourceFileUri = Uri.file(testSourceFilePath); - const testSourceFile = await workspace.openTextDocument(testSourceFileUri); - let testDefLine: TextLine | null = null; - let lineNum = testFunction.testFunction.line!; - let lineText: string = ''; - let trimmedLineText: string = ''; - const testDefPrefix = 'def '; - const testAsyncDefPrefix = 'async def '; - let prefix = ''; - - while (testDefLine === null) { - const possibleTestDefLine = testSourceFile.lineAt(lineNum); - lineText = possibleTestDefLine.text; - trimmedLineText = lineText.trimLeft()!; - if (trimmedLineText.toLowerCase().startsWith(testDefPrefix)) { - testDefLine = possibleTestDefLine; - prefix = testDefPrefix; - } else if (trimmedLineText.toLowerCase().startsWith(testAsyncDefPrefix)) { - testDefLine = possibleTestDefLine; - prefix = testAsyncDefPrefix; - } else { - // The test definition may have been decorated, and there may be multiple - // decorations, so move to the next line and check it. - lineNum += 1; - } - } - const matches = trimmedLineText!.slice(prefix.length).match(/[^ \(:]+/); - const testSimpleName = matches ? matches[0] : ''; - const testDefStartCharNum = lineText.length - trimmedLineText.length + prefix.length; - const testDefEndCharNum = testDefStartCharNum + testSimpleName.length; - const lineStart = new Position(testDefLine!.lineNumber, testDefStartCharNum); - const lineEnd = new Position(testDefLine!.lineNumber, testDefEndCharNum); - const lineRange = new Range(lineStart, lineEnd); - const testDefLocation = new Location(testSourceFileUri, lineRange); - const testSourceLocationDetails = { location: testDefLocation, lineText: testSourceFile.getText(lineRange) }; - locationStack.unshift(testSourceLocationDetails); - - // Put the class declaration at the top of the stack if the test was imported. - if (testFunction.parentTestSuite !== undefined) { - // This could be an imported test method - const fs = this.serviceContainer.get(IFileSystem); - if ( - !fs.arePathsSame( - Uri.file(testFunction.parentTestFile.fullPath).fsPath, - locationStack[0].location.uri.fsPath - ) - ) { - // test method was imported, so reference class declaration line. - // this should be the first thing in the stack to show where the failure/error originated. - locationStack.unshift(await this.getParentSuiteLocation(testFunction)); - } - } - return locationStack; - } - /** - * The test that's associated with the FlattenedtestFunction was imported from another file, as the file - * location found in the traceback that shows what file the test was actually defined in is different than - * the file that the test was executed in. This must also mean that the test was part of a class that was - * imported and then inherited by the class that was actually run in the file. - * - * Test classes can be defined inside of other test classes, and even nested test classes of those that were - * imported will be discovered and ran. Luckily, for pytest, the entire chain of classes is preserved in the - * test's ID. However, in order to keep the Diagnostic as relevant as possible, it should point only at the - * most-nested test class that exists in the file that the test was actually run in, in order to provide the - * most context. This method attempts to go as far down the chain as it can, and resolves to the - * LocationStackFrameDetails for that test class. - * - * @param testFunction The FlattenedTestFunction that was executed. - */ - private async getParentSuiteLocation(testFunction: FlattenedTestFunction): Promise { - const suiteStackWithFileAndTest = testFunction.testFunction.nameToRun.replace('::()', '').split('::'); - // Don't need the file location or the test's name. - const suiteStack = suiteStackWithFileAndTest.slice(1, suiteStackWithFileAndTest.length - 1); - const testFileUri = Uri.file(testFunction.parentTestFile.fullPath); - const testFile = await workspace.openTextDocument(testFileUri); - const testFileLines = testFile.getText().splitLines({ trim: false, removeEmptyEntries: false }); - const reversedTestFileLines = testFileLines.slice().reverse(); - // Track the end of the parent scope. - let parentScopeEndIndex = 0; - let parentScopeStartIndex = testFileLines.length; - let parentIndentation: number | undefined; - const suiteLocationStackFrameDetails: ILocationStackFrameDetails[] = []; - - const classPrefix = 'class '; - while (suiteStack.length > 0) { - let indentation: number = 0; - let prevLowestIndentation: number | undefined; - // Get the name of the suite on top of the stack so it can be located. - const suiteName = suiteStack.shift()!; - let suiteDefLineIndex: number | undefined; - for (let index = parentScopeEndIndex; index < parentScopeStartIndex; index += 1) { - const lineText = reversedTestFileLines[index]; - if (lineText.trim().length === 0) { - // This line is just whitespace. - continue; - } - const trimmedLineText = lineText.trimLeft()!; - if (!trimmedLineText.toLowerCase().startsWith(classPrefix)) { - // line is not a class declaration - continue; - } - const matches = trimmedLineText.slice(classPrefix.length).match(/[^ \(:]+/); - const lineClassName = matches ? matches[0] : undefined; - - // Check if the indentation is proper. - if (parentIndentation === undefined) { - // The parentIndentation hasn't been set yet, so we are looking for a class that was - // defined in the global scope of the module. - if (trimmedLineText.length === lineText.length) { - // This line doesn't start with whitespace. - if (lineClassName === suiteName) { - // This is the line that we want. - suiteDefLineIndex = index; - indentation = 0; - // We have our line for the root suite declaration, so move on to processing the Location. - break; - } else { - // This is not the line we want, but may be the line that ends the scope of the class we want. - parentScopeEndIndex = index + 1; - } - } - } else { - indentation = lineText.length - trimmedLineText.length; - if (indentation <= parentIndentation) { - // This is not the line we want, but may be the line that ends the scope of the parent class. - parentScopeEndIndex = index + 1; - continue; - } - if (prevLowestIndentation === undefined || indentation < prevLowestIndentation) { - if (lineClassName === suiteName) { - // This might be the line that we want. - suiteDefLineIndex = index; - prevLowestIndentation = indentation; - } else { - // This is not the line we want, but may be the line that ends the scope of the class we want. - parentScopeEndIndex = index + 1; - } - } - } - } - if (suiteDefLineIndex === undefined) { - // Could not find the suite declaration line, so give up and move on with the latest one that we found. - break; - } - // Found the line to process. - parentScopeStartIndex = suiteDefLineIndex; - parentIndentation = indentation!; - - // Invert the index to get the unreversed equivalent. - const realIndex = reversedTestFileLines.length - 1 - suiteDefLineIndex; - const startChar = indentation! + classPrefix.length; - const suiteStartPos = new Position(realIndex, startChar); - const suiteEndPos = new Position(realIndex, startChar + suiteName!.length); - const suiteRange = new Range(suiteStartPos, suiteEndPos); - const suiteLocation = new Location(testFileUri, suiteRange); - suiteLocationStackFrameDetails.push({ location: suiteLocation, lineText: testFile.getText(suiteRange) }); - } - return suiteLocationStackFrameDetails[suiteLocationStackFrameDetails.length - 1]; - } -} diff --git a/src/client/testing/pytest/testConfigurationManager.ts b/src/client/testing/pytest/testConfigurationManager.ts deleted file mode 100644 index 2c3b627342a1..000000000000 --- a/src/client/testing/pytest/testConfigurationManager.ts +++ /dev/null @@ -1,56 +0,0 @@ -import * as path from 'path'; -import { QuickPickItem, Uri } from 'vscode'; -import { IFileSystem } from '../../common/platform/types'; -import { Product } from '../../common/types'; -import { IServiceContainer } from '../../ioc/types'; -import { TestConfigurationManager } from '../common/managers/testConfigurationManager'; -import { ITestConfigSettingsService } from '../types'; - -export class ConfigurationManager extends TestConfigurationManager { - constructor(workspace: Uri, serviceContainer: IServiceContainer, cfg?: ITestConfigSettingsService) { - super(workspace, Product.pytest, serviceContainer, cfg); - } - public async requiresUserToConfigure(wkspace: Uri): Promise { - const configFiles = await this.getConfigFiles(wkspace.fsPath); - // If a config file exits, there's nothing to be configured. - if (configFiles.length > 0 && configFiles.length !== 1 && configFiles[0] !== 'setup.cfg') { - return false; - } - return true; - } - public async configure(wkspace: Uri) { - const args: string[] = []; - const configFileOptionLabel = 'Use existing config file'; - const options: QuickPickItem[] = []; - const configFiles = await this.getConfigFiles(wkspace.fsPath); - // If a config file exits, there's nothing to be configured. - if (configFiles.length > 0 && configFiles.length !== 1 && configFiles[0] !== 'setup.cfg') { - return; - } - - if (configFiles.length === 1 && configFiles[0] === 'setup.cfg') { - options.push({ - label: configFileOptionLabel, - description: 'setup.cfg' - }); - } - const subDirs = await this.getTestDirs(wkspace.fsPath); - const testDir = await this.selectTestDir(wkspace.fsPath, subDirs, options); - if (typeof testDir === 'string' && testDir !== configFileOptionLabel) { - args.push(testDir); - } - const installed = await this.installer.isInstalled(Product.pytest); - if (!installed) { - await this.installer.install(Product.pytest); - } - await this.testConfigSettingsService.updateTestArgs(wkspace.fsPath, Product.pytest, args); - } - private async getConfigFiles(rootDir: string): Promise { - const fs = this.serviceContainer.get(IFileSystem); - const promises = ['pytest.ini', 'tox.ini', 'setup.cfg'].map(async (cfg) => - (await fs.fileExists(path.join(rootDir, cfg))) ? cfg : '' - ); - const values = await Promise.all(promises); - return values.filter((exists) => exists.length > 0); - } -} diff --git a/src/client/testing/serviceRegistry.ts b/src/client/testing/serviceRegistry.ts index 5ebb8dc4f36f..d36fab7686f8 100644 --- a/src/client/testing/serviceRegistry.ts +++ b/src/client/testing/serviceRegistry.ts @@ -1,190 +1,38 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { Uri } from 'vscode'; -import { IExtensionActivationService, IExtensionSingleActivationService } from '../activation/types'; -import { IServiceContainer, IServiceManager } from '../ioc/types'; -import { ArgumentsHelper } from './common/argumentsHelper'; -import { NOSETEST_PROVIDER, PYTEST_PROVIDER, UNITTEST_PROVIDER } from './common/constants'; +import { IExtensionActivationService } from '../activation/types'; +import { IServiceManager } from '../ioc/types'; import { DebugLauncher } from './common/debugLauncher'; -import { EnablementTracker } from './common/enablementTracker'; -import { TestRunner } from './common/runner'; -import { TestConfigSettingsService } from './common/services/configSettingService'; -import { TestContextService } from './common/services/contextService'; -import { TestDiscoveredTestParser } from './common/services/discoveredTestParser'; -import { TestsDiscoveryService } from './common/services/discovery'; -import { TestCollectionStorageService } from './common/services/storageService'; -import { TestManagerService } from './common/services/testManagerService'; -import { TestResultsService } from './common/services/testResultsService'; -import { TestsStatusUpdaterService } from './common/services/testsStatusService'; -import { ITestDiscoveredTestParser } from './common/services/types'; -import { UnitTestDiagnosticService } from './common/services/unitTestDiagnosticService'; -import { WorkspaceTestManagerService } from './common/services/workspaceTestManagerService'; +import { TestConfigSettingsService } from './common/configSettingService'; import { TestsHelper } from './common/testUtils'; -import { TestFlatteningVisitor } from './common/testVisitors/flatteningVisitor'; -import { TestResultResetVisitor } from './common/testVisitors/resultResetVisitor'; import { - ITestCollectionStorageService, - ITestContextService, + ITestConfigSettingsService, + ITestConfigurationManagerFactory, + ITestConfigurationService, ITestDebugLauncher, - ITestDiscoveryService, - ITestManager, - ITestManagerFactory, - ITestManagerService, - ITestManagerServiceFactory, - ITestMessageService, - ITestResultsService, - ITestRunner, ITestsHelper, - ITestsParser, - ITestsStatusUpdaterService, - ITestVisitor, - IUnitTestSocketServer, - IWorkspaceTestManagerService, - IXUnitParser, - TestProvider } from './common/types'; -import { UpdateTestSettingService } from './common/updateTestSettings'; -import { XUnitParser } from './common/xUnitParser'; import { UnitTestConfigurationService } from './configuration'; import { TestConfigurationManagerFactory } from './configurationFactory'; -import { TestResultDisplay } from './display/main'; -import { TestDisplay } from './display/picker'; -import { TestExplorerCommandHandler } from './explorer/commandHandlers'; -import { FailedTestHandler } from './explorer/failedTestHandler'; -import { TestTreeViewProvider } from './explorer/testTreeViewProvider'; -import { TreeViewService } from './explorer/treeView'; -import { UnitTestManagementService } from './main'; -import { registerTypes as registerNavigationTypes } from './navigation/serviceRegistry'; -import { ITestExplorerCommandHandler } from './navigation/types'; -import { TestManager as NoseTestManager } from './nosetest/main'; -import { TestManagerRunner as NoseTestManagerRunner } from './nosetest/runner'; -import { ArgumentsService as NoseTestArgumentsService } from './nosetest/services/argsService'; -import { TestDiscoveryService as NoseTestDiscoveryService } from './nosetest/services/discoveryService'; -import { TestsParser as NoseTestTestsParser } from './nosetest/services/parserService'; -import { TestManager as PyTestTestManager } from './pytest/main'; -import { TestManagerRunner as PytestManagerRunner } from './pytest/runner'; -import { ArgumentsService as PyTestArgumentsService } from './pytest/services/argsService'; -import { TestDiscoveryService as PytestTestDiscoveryService } from './pytest/services/discoveryService'; -import { TestMessageService } from './pytest/services/testMessageService'; -import { - IArgumentsHelper, - IArgumentsService, - ITestConfigSettingsService, - ITestConfigurationManagerFactory, - ITestConfigurationService, - ITestDataItemResource, - ITestDiagnosticService, - ITestDisplay, - ITestManagementService, - ITestManagerRunner, - ITestResultDisplay, - ITestTreeViewProvider, - IUnitTestHelper -} from './types'; -import { UnitTestHelper } from './unittest/helper'; -import { TestManager as UnitTestTestManager } from './unittest/main'; -import { TestManagerRunner as UnitTestTestManagerRunner } from './unittest/runner'; -import { ArgumentsService as UnitTestArgumentsService } from './unittest/services/argsService'; -import { TestDiscoveryService as UnitTestTestDiscoveryService } from './unittest/services/discoveryService'; -import { TestsParser as UnitTestTestsParser } from './unittest/services/parserService'; -import { UnitTestSocketServer } from './unittest/socketServer'; +import { TestingService, UnitTestManagementService } from './main'; +import { ITestingService } from './types'; +import { registerTestControllerTypes } from './testController/serviceRegistry'; export function registerTypes(serviceManager: IServiceManager) { - registerNavigationTypes(serviceManager); serviceManager.addSingleton(ITestDebugLauncher, DebugLauncher); - serviceManager.addSingleton( - ITestCollectionStorageService, - TestCollectionStorageService - ); - serviceManager.addSingleton( - IWorkspaceTestManagerService, - WorkspaceTestManagerService - ); serviceManager.add(ITestsHelper, TestsHelper); - serviceManager.add(ITestDiscoveredTestParser, TestDiscoveredTestParser); - serviceManager.add(ITestDiscoveryService, TestsDiscoveryService, 'common'); - serviceManager.add(IUnitTestSocketServer, UnitTestSocketServer); - serviceManager.addSingleton(ITestContextService, TestContextService); - serviceManager.addSingleton(ITestsStatusUpdaterService, TestsStatusUpdaterService); - - serviceManager.add(ITestResultsService, TestResultsService); - - serviceManager.add(ITestVisitor, TestFlatteningVisitor, 'TestFlatteningVisitor'); - serviceManager.add(ITestVisitor, TestResultResetVisitor, 'TestResultResetVisitor'); - - serviceManager.add(ITestsParser, UnitTestTestsParser, UNITTEST_PROVIDER); - serviceManager.add(ITestsParser, NoseTestTestsParser, NOSETEST_PROVIDER); - - serviceManager.add(ITestDiscoveryService, UnitTestTestDiscoveryService, UNITTEST_PROVIDER); - serviceManager.add(ITestDiscoveryService, PytestTestDiscoveryService, PYTEST_PROVIDER); - serviceManager.add(ITestDiscoveryService, NoseTestDiscoveryService, NOSETEST_PROVIDER); - - serviceManager.add(IArgumentsHelper, ArgumentsHelper); - serviceManager.add(ITestRunner, TestRunner); - serviceManager.add(IXUnitParser, XUnitParser); - serviceManager.add(IUnitTestHelper, UnitTestHelper); - - serviceManager.add(IArgumentsService, PyTestArgumentsService, PYTEST_PROVIDER); - serviceManager.add(IArgumentsService, NoseTestArgumentsService, NOSETEST_PROVIDER); - serviceManager.add(IArgumentsService, UnitTestArgumentsService, UNITTEST_PROVIDER); - serviceManager.add(ITestManagerRunner, PytestManagerRunner, PYTEST_PROVIDER); - serviceManager.add(ITestManagerRunner, NoseTestManagerRunner, NOSETEST_PROVIDER); - serviceManager.add(ITestManagerRunner, UnitTestTestManagerRunner, UNITTEST_PROVIDER); serviceManager.addSingleton(ITestConfigurationService, UnitTestConfigurationService); - serviceManager.addSingleton(ITestManagementService, UnitTestManagementService); - serviceManager.addSingleton(ITestResultDisplay, TestResultDisplay); - serviceManager.addSingleton(ITestDisplay, TestDisplay); + serviceManager.addSingleton(ITestingService, TestingService); + serviceManager.addSingleton(ITestConfigSettingsService, TestConfigSettingsService); serviceManager.addSingleton( ITestConfigurationManagerFactory, - TestConfigurationManagerFactory + TestConfigurationManagerFactory, ); + serviceManager.addSingleton(IExtensionActivationService, UnitTestManagementService); - serviceManager.addSingleton(ITestDiagnosticService, UnitTestDiagnosticService); - serviceManager.addSingleton(ITestMessageService, TestMessageService, PYTEST_PROVIDER); - serviceManager.addSingleton(ITestTreeViewProvider, TestTreeViewProvider); - serviceManager.addSingleton(ITestDataItemResource, TestTreeViewProvider); - serviceManager.addSingleton(ITestExplorerCommandHandler, TestExplorerCommandHandler); - serviceManager.addSingleton(IExtensionSingleActivationService, TreeViewService); - serviceManager.addSingleton( - IExtensionSingleActivationService, - FailedTestHandler - ); - serviceManager.addSingleton( - IExtensionSingleActivationService, - EnablementTracker - ); - serviceManager.addSingleton(IExtensionActivationService, UpdateTestSettingService); - - serviceManager.addFactory(ITestManagerFactory, (context) => { - return (testProvider: TestProvider, workspaceFolder: Uri, rootDirectory: string) => { - const serviceContainer = context.container.get(IServiceContainer); - - switch (testProvider) { - case NOSETEST_PROVIDER: { - return new NoseTestManager(workspaceFolder, rootDirectory, serviceContainer); - } - case PYTEST_PROVIDER: { - return new PyTestTestManager(workspaceFolder, rootDirectory, serviceContainer); - } - case UNITTEST_PROVIDER: { - return new UnitTestTestManager(workspaceFolder, rootDirectory, serviceContainer); - } - default: { - throw new Error(`Unrecognized test provider '${testProvider}'`); - } - } - }; - }); - - serviceManager.addFactory(ITestManagerServiceFactory, (context) => { - return (workspaceFolder: Uri) => { - const serviceContainer = context.container.get(IServiceContainer); - const testsHelper = context.container.get(ITestsHelper); - return new TestManagerService(workspaceFolder, testsHelper, serviceContainer); - }; - }); + registerTestControllerTypes(serviceManager); } diff --git a/src/client/testing/testController/common/argumentsHelper.ts b/src/client/testing/testController/common/argumentsHelper.ts new file mode 100644 index 000000000000..c155d0197da7 --- /dev/null +++ b/src/client/testing/testController/common/argumentsHelper.ts @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { traceWarn } from '../../../logging'; + +export function getPositionalArguments( + args: string[], + optionsWithArguments: string[] = [], + optionsWithoutArguments: string[] = [], +): string[] { + const nonPositionalIndexes: number[] = []; + args.forEach((arg, index) => { + if (optionsWithoutArguments.indexOf(arg) !== -1) { + nonPositionalIndexes.push(index); + } else if (optionsWithArguments.indexOf(arg) !== -1) { + nonPositionalIndexes.push(index); + // Cuz the next item is the value. + nonPositionalIndexes.push(index + 1); + } else if (optionsWithArguments.findIndex((item) => arg.startsWith(`${item}=`)) !== -1) { + nonPositionalIndexes.push(index); + } else if (arg.startsWith('-')) { + // Ok this is an unknown option, lets treat this as one without values. + traceWarn( + `Unknown command line option passed into args parser for tests '${arg}'. Please report on https://github.com/Microsoft/vscode-python/issues/new`, + ); + nonPositionalIndexes.push(index); + } else if (arg.indexOf('=') > 0) { + // Ok this is an unknown option with a value + traceWarn( + `Unknown command line option passed into args parser for tests '${arg}'. Please report on https://github.com/Microsoft/vscode-python/issues/new`, + ); + nonPositionalIndexes.push(index); + } + }); + return args.filter((_, index) => nonPositionalIndexes.indexOf(index) === -1); +} + +export function filterArguments( + args: string[], + optionsWithArguments: string[] = [], + optionsWithoutArguments: string[] = [], +): string[] { + let ignoreIndex = -1; + return args.filter((arg, index) => { + if (ignoreIndex === index) { + return false; + } + // Options can use wild cards (with trailing '*') + if ( + optionsWithoutArguments.indexOf(arg) >= 0 || + optionsWithoutArguments.filter((option) => option.endsWith('*') && arg.startsWith(option.slice(0, -1))) + .length > 0 + ) { + return false; + } + // Ignore args that match exactly. + if (optionsWithArguments.indexOf(arg) >= 0) { + ignoreIndex = index + 1; + return false; + } + // Ignore args that match exactly with wild cards & do not have inline values. + if (optionsWithArguments.filter((option) => arg.startsWith(`${option}=`)).length > 0) { + return false; + } + // Ignore args that match a wild card (ending with *) and no inline values. + // Eg. arg='--log-cli-level' and optionsArguments=['--log-*'] + if ( + arg.indexOf('=') === -1 && + optionsWithoutArguments.filter((option) => option.endsWith('*') && arg.startsWith(option.slice(0, -1))) + .length > 0 + ) { + ignoreIndex = index + 1; + return false; + } + // Ignore args that match a wild card (ending with *) and have inline values. + // Eg. arg='--log-cli-level=XYZ' and optionsArguments=['--log-*'] + if ( + arg.indexOf('=') >= 0 && + optionsWithoutArguments.filter((option) => option.endsWith('*') && arg.startsWith(option.slice(0, -1))) + .length > 0 + ) { + return false; + } + return true; + }); +} diff --git a/src/client/testing/testController/common/discoveryHelpers.ts b/src/client/testing/testController/common/discoveryHelpers.ts new file mode 100644 index 000000000000..e170ad576ae8 --- /dev/null +++ b/src/client/testing/testController/common/discoveryHelpers.ts @@ -0,0 +1,137 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +import { CancellationToken, CancellationTokenSource, Disposable, Uri } from 'vscode'; +import { Deferred } from '../../../common/utils/async'; +import { traceError, traceInfo, traceVerbose } from '../../../logging'; +import { createDiscoveryErrorPayload, fixLogLinesNoTrailing, startDiscoveryNamedPipe } from './utils'; +import { DiscoveredTestPayload, ITestResultResolver } from './types'; + +/** + * Test provider type for logging purposes. + */ +export type TestProvider = 'pytest' | 'unittest'; + +/** + * Sets up the discovery named pipe and wires up cancellation. + * @param resultResolver The resolver to handle discovered test data + * @param token Optional cancellation token from the caller + * @param uri Workspace URI for logging + * @returns Object containing the pipe name, cancellation source, and disposable for the external token handler + */ +export async function setupDiscoveryPipe( + resultResolver: ITestResultResolver | undefined, + token: CancellationToken | undefined, + uri: Uri, +): Promise<{ pipeName: string; cancellation: CancellationTokenSource; tokenDisposable: Disposable | undefined }> { + const discoveryPipeCancellation = new CancellationTokenSource(); + + // Wire up cancellation from external token and store the disposable + const tokenDisposable = token?.onCancellationRequested(() => { + traceInfo(`Test discovery cancelled.`); + discoveryPipeCancellation.cancel(); + }); + + // Start the named pipe with the discovery listener + const discoveryPipeName = await startDiscoveryNamedPipe((data: DiscoveredTestPayload) => { + if (!token?.isCancellationRequested) { + resultResolver?.resolveDiscovery(data); + } + }, discoveryPipeCancellation.token); + + traceVerbose(`Created discovery pipe: ${discoveryPipeName} for workspace ${uri.fsPath}`); + + return { + pipeName: discoveryPipeName, + cancellation: discoveryPipeCancellation, + tokenDisposable, + }; +} + +/** + * Creates standard process event handlers for test discovery subprocess. + * Handles stdout/stderr logging and error reporting on process exit. + * + * @param testProvider - The test framework being used ('pytest' or 'unittest') + * @param uri - The workspace URI + * @param cwd - The current working directory + * @param resultResolver - Resolver for test discovery results + * @param deferredTillExecClose - Deferred to resolve when process closes + * @param allowedSuccessCodes - Additional exit codes to treat as success (e.g., pytest exit code 5 for no tests found) + */ +export function createProcessHandlers( + testProvider: TestProvider, + uri: Uri, + cwd: string, + resultResolver: ITestResultResolver | undefined, + deferredTillExecClose: Deferred, + allowedSuccessCodes: number[] = [], +): { + onStdout: (data: any) => void; + onStderr: (data: any) => void; + onExit: (code: number | null, signal: NodeJS.Signals | null) => void; + onClose: (code: number | null, signal: NodeJS.Signals | null) => void; +} { + const isSuccessCode = (code: number | null): boolean => { + return code === 0 || (code !== null && allowedSuccessCodes.includes(code)); + }; + + return { + onStdout: (data: any) => { + const out = fixLogLinesNoTrailing(data.toString()); + traceInfo(out); + }, + onStderr: (data: any) => { + const out = fixLogLinesNoTrailing(data.toString()); + traceError(out); + }, + onExit: (code: number | null, _signal: NodeJS.Signals | null) => { + // The 'exit' event fires when the process terminates, but streams may still be open. + // Only log verbose success message here; error handling happens in onClose. + if (isSuccessCode(code)) { + traceVerbose(`${testProvider} discovery subprocess exited successfully for workspace ${uri.fsPath}`); + } + }, + onClose: (code: number | null, signal: NodeJS.Signals | null) => { + // We resolve the deferred here to ensure all output has been captured. + if (!isSuccessCode(code)) { + traceError( + `${testProvider} discovery failed with exit code ${code} and signal ${signal} for workspace ${uri.fsPath}. Creating error payload.`, + ); + resultResolver?.resolveDiscovery(createDiscoveryErrorPayload(code, signal, cwd)); + } else { + traceVerbose(`${testProvider} discovery subprocess streams closed for workspace ${uri.fsPath}`); + } + deferredTillExecClose?.resolve(); + }, + }; +} + +/** + * Handles cleanup when test discovery is cancelled. + * Kills the subprocess (if running), resolves the completion deferred, and cancels the discovery pipe. + * + * @param testProvider - The test framework being used ('pytest' or 'unittest') + * @param proc - The process to kill + * @param processCompletion - Deferred to resolve + * @param pipeCancellation - Cancellation token source to cancel + * @param uri - The workspace URI + */ +export function cleanupOnCancellation( + testProvider: TestProvider, + proc: { kill: () => void } | undefined, + processCompletion: Deferred, + pipeCancellation: CancellationTokenSource, + uri: Uri, +): void { + traceInfo(`Test discovery cancelled, killing ${testProvider} subprocess for workspace ${uri.fsPath}`); + if (proc) { + traceVerbose(`Killing ${testProvider} subprocess for workspace ${uri.fsPath}`); + proc.kill(); + } else { + traceVerbose(`No ${testProvider} subprocess to kill for workspace ${uri.fsPath} (proc is undefined)`); + } + traceVerbose(`Resolving process completion deferred for ${testProvider} discovery in workspace ${uri.fsPath}`); + processCompletion.resolve(); + traceVerbose(`Cancelling discovery pipe for ${testProvider} discovery in workspace ${uri.fsPath}`); + pipeCancellation.cancel(); +} diff --git a/src/client/testing/testController/common/projectAdapter.ts b/src/client/testing/testController/common/projectAdapter.ts new file mode 100644 index 000000000000..cfffbf439ca6 --- /dev/null +++ b/src/client/testing/testController/common/projectAdapter.ts @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { TestItem, Uri } from 'vscode'; +import { TestProvider } from '../../types'; +import { ITestDiscoveryAdapter, ITestExecutionAdapter, ITestResultResolver } from './types'; +import { PythonEnvironment, PythonProject } from '../../../envExt/types'; + +/** + * Represents a single Python project with its own test infrastructure. + * A project is defined as a combination of a Python executable + URI (folder/file). + * Projects are uniquely identified by their projectUri (use projectUri.toString() for map keys). + */ +export interface ProjectAdapter { + // === IDENTITY === + /** + * Display name for the project (e.g., "alice (Python 3.11)"). + */ + projectName: string; + + /** + * URI of the project root folder or file. + * This is the unique identifier for the project. + */ + projectUri: Uri; + + /** + * Parent workspace URI containing this project. + */ + workspaceUri: Uri; + + // === API OBJECTS (from vscode-python-environments extension) === + /** + * The PythonProject object from the environment API. + */ + pythonProject: PythonProject; + + /** + * The resolved PythonEnvironment with execution details. + * Contains execInfo.run.executable for running tests. + */ + pythonEnvironment: PythonEnvironment; + + // === TEST INFRASTRUCTURE === + /** + * Test framework provider ('pytest' | 'unittest'). + */ + testProvider: TestProvider; + + /** + * Adapter for test discovery. + */ + discoveryAdapter: ITestDiscoveryAdapter; + + /** + * Adapter for test execution. + */ + executionAdapter: ITestExecutionAdapter; + + /** + * Result resolver for this project (maps test IDs and handles results). + */ + resultResolver: ITestResultResolver; + + /** + * Absolute paths of nested projects to ignore during discovery. + * Used to pass --ignore flags to pytest or exclusion filters to unittest. + * Only populated for parent projects that contain nested child projects. + */ + nestedProjectPathsToIgnore?: string[]; + + // === LIFECYCLE === + /** + * Whether discovery is currently running for this project. + */ + isDiscovering: boolean; + + /** + * Whether tests are currently executing for this project. + */ + isExecuting: boolean; + + /** + * Root TestItem for this project in the VS Code test tree. + * All project tests are children of this item. + */ + projectRootTestItem?: TestItem; +} diff --git a/src/client/testing/testController/common/projectTestExecution.ts b/src/client/testing/testController/common/projectTestExecution.ts new file mode 100644 index 000000000000..fe3b4f91491a --- /dev/null +++ b/src/client/testing/testController/common/projectTestExecution.ts @@ -0,0 +1,296 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { CancellationToken, FileCoverageDetail, TestItem, TestRun, TestRunProfileKind, TestRunRequest } from 'vscode'; +import { traceError, traceInfo, traceVerbose, traceWarn } from '../../../logging'; +import { sendTelemetryEvent } from '../../../telemetry'; +import { EventName } from '../../../telemetry/constants'; +import { IPythonExecutionFactory } from '../../../common/process/types'; +import { ITestDebugLauncher } from '../../common/types'; +import { ProjectAdapter } from './projectAdapter'; +import { TestProjectRegistry } from './testProjectRegistry'; +import { getProjectId } from './projectUtils'; +import { getEnvExtApi, useEnvExtension } from '../../../envExt/api.internal'; +import { isParentPath } from '../../../pythonEnvironments/common/externalDependencies'; + +/** Dependencies for project-based test execution. */ +export interface ProjectExecutionDependencies { + projectRegistry: TestProjectRegistry; + pythonExecFactory: IPythonExecutionFactory; + debugLauncher: ITestDebugLauncher; +} + +/** Executes tests for multiple projects, grouping by project and using each project's Python environment. */ +export async function executeTestsForProjects( + projects: ProjectAdapter[], + testItems: TestItem[], + runInstance: TestRun, + request: TestRunRequest, + token: CancellationToken, + deps: ProjectExecutionDependencies, +): Promise { + if (projects.length === 0) { + traceError(`[test-by-project] No projects provided for execution`); + return; + } + + // Early exit if already cancelled + if (token.isCancellationRequested) { + traceInfo(`[test-by-project] Execution cancelled before starting`); + return; + } + + // Group test items by project + const testsByProject = await groupTestItemsByProject(testItems, projects); + + const isDebugMode = request.profile?.kind === TestRunProfileKind.Debug; + traceInfo(`[test-by-project] Executing tests across ${testsByProject.size} project(s), debug=${isDebugMode}`); + + // Setup coverage once for all projects (single callback that routes by file path) + if (request.profile?.kind === TestRunProfileKind.Coverage) { + setupCoverageForProjects(request, projects); + } + + // Execute tests for each project in parallel + // For debug mode, multiple debug sessions will be launched in parallel + // Each execution respects cancellation via runInstance.token + const executions = Array.from(testsByProject.entries()).map(async ([_projectId, { project, items }]) => { + // Check for cancellation before starting each project + if (token.isCancellationRequested) { + traceInfo(`[test-by-project] Skipping ${project.projectName} - cancellation requested`); + return; + } + + if (items.length === 0) return; + + traceInfo(`[test-by-project] Executing ${items.length} test item(s) for project: ${project.projectName}`); + + sendTelemetryEvent(EventName.UNITTEST_RUN, undefined, { + tool: project.testProvider, + debugging: isDebugMode, + }); + + try { + await executeTestsForProject(project, items, runInstance, request, deps); + } catch (error) { + // Don't log cancellation as an error + if (!token.isCancellationRequested) { + traceError(`[test-by-project] Execution failed for project ${project.projectName}:`, error); + } + } + }); + + await Promise.all(executions); + + if (token.isCancellationRequested) { + traceInfo(`[test-by-project] Project executions cancelled`); + } else { + traceInfo(`[test-by-project] All project executions completed`); + } +} + +/** Lookup context for caching project lookups within a single test run. */ +interface ProjectLookupContext { + uriToAdapter: Map; + projectPathToAdapter: Map; +} + +/** Groups test items by owning project using env API or path-based matching as fallback. */ +export async function groupTestItemsByProject( + testItems: TestItem[], + projects: ProjectAdapter[], +): Promise> { + const result = new Map(); + + // Initialize entries for all projects + for (const project of projects) { + result.set(getProjectId(project.projectUri), { project, items: [] }); + } + + // Build lookup context for this run - O(p) one-time setup, enables O(1) lookups per item. + // When tests are from a single project, most lookups hit the cache after the first item. + const lookupContext: ProjectLookupContext = { + uriToAdapter: new Map(), + projectPathToAdapter: new Map(projects.map((p) => [p.projectUri.fsPath, p])), + }; + + // Assign each test item to its project + for (const item of testItems) { + const project = await findProjectForTestItem(item, projects, lookupContext); + if (project) { + const entry = result.get(getProjectId(project.projectUri)); + if (entry) { + entry.items.push(item); + } + } else { + // If no project matches, log it + traceWarn(`[test-by-project] Could not match test item ${item.id} to a project`); + } + } + + // Remove projects with no test items + for (const [projectId, entry] of result.entries()) { + if (entry.items.length === 0) { + result.delete(projectId); + } + } + + return result; +} + +/** Finds the project that owns a test item. */ +export async function findProjectForTestItem( + item: TestItem, + projects: ProjectAdapter[], + lookupContext?: ProjectLookupContext, +): Promise { + if (!item.uri) return undefined; + + const uriPath = item.uri.fsPath; + + // Check lookup context first - O(1) + if (lookupContext?.uriToAdapter.has(uriPath)) { + return lookupContext.uriToAdapter.get(uriPath); + } + + let result: ProjectAdapter | undefined; + + // Try using the Python Environment extension API first. + // Legacy path: when useEnvExtension() is false, this block is skipped and we go + // directly to findProjectByPath() below (path-based matching). + if (useEnvExtension()) { + try { + const envExtApi = await getEnvExtApi(); + const pythonProject = envExtApi.getPythonProject(item.uri); + if (pythonProject) { + // Use lookup context for O(1) adapter lookup instead of O(p) linear search + result = lookupContext?.projectPathToAdapter.get(pythonProject.uri.fsPath); + if (!result) { + // Fallback to linear search if lookup context not available + result = projects.find((p) => p.projectUri.fsPath === pythonProject.uri.fsPath); + } + } + } catch (error) { + traceVerbose(`[test-by-project] Failed to use env extension API, falling back to path matching: ${error}`); + } + } + + // Fallback: path-based matching when env API unavailable or didn't find a match. + // O(p) time complexity where p = number of projects. + if (!result) { + result = findProjectByPath(item, projects); + } + + // Store result for future lookups of same file within this run - O(1) + if (lookupContext) { + lookupContext.uriToAdapter.set(uriPath, result); + } + + return result; +} + +/** Fallback: finds project using path-based matching. */ +function findProjectByPath(item: TestItem, projects: ProjectAdapter[]): ProjectAdapter | undefined { + if (!item.uri) return undefined; + + const itemPath = item.uri.fsPath; + let bestMatch: ProjectAdapter | undefined; + let bestMatchLength = 0; + + for (const project of projects) { + const projectPath = project.projectUri.fsPath; + // Use isParentPath for safe path-boundary matching (handles separators and case normalization) + if (isParentPath(itemPath, projectPath) && projectPath.length > bestMatchLength) { + bestMatch = project; + bestMatchLength = projectPath.length; + } + } + + return bestMatch; +} + +/** Executes tests for a single project using the project's Python environment. */ +export async function executeTestsForProject( + project: ProjectAdapter, + testItems: TestItem[], + runInstance: TestRun, + request: TestRunRequest, + deps: ProjectExecutionDependencies, +): Promise { + const processedTestItemIds = new Set(); + const uniqueTestCaseIds = new Set(); + + // Mark items as started and collect test IDs (deduplicated to handle overlapping selections) + for (const item of testItems) { + const testCaseNodes = getTestCaseNodesRecursive(item); + for (const node of testCaseNodes) { + if (processedTestItemIds.has(node.id)) { + continue; + } + processedTestItemIds.add(node.id); + runInstance.started(node); + const runId = project.resultResolver.vsIdToRunId.get(node.id); + if (runId) { + uniqueTestCaseIds.add(runId); + } + } + } + + const testCaseIds = Array.from(uniqueTestCaseIds); + + if (testCaseIds.length === 0) { + traceVerbose(`[test-by-project] No test IDs found for project ${project.projectName}`); + return; + } + + traceInfo(`[test-by-project] Running ${testCaseIds.length} test(s) for project: ${project.projectName}`); + + // Execute tests using the project's execution adapter + await project.executionAdapter.runTests( + project.projectUri, + testCaseIds, + request.profile?.kind, + runInstance, + deps.pythonExecFactory, + deps.debugLauncher, + undefined, // interpreter not needed, project has its own environment + project, + ); +} + +/** Recursively gets all leaf test case nodes from a test item tree. */ +export function getTestCaseNodesRecursive(item: TestItem): TestItem[] { + const results: TestItem[] = []; + if (item.children.size === 0) { + // This is a leaf node (test case) + results.push(item); + } else { + // Recursively get children + item.children.forEach((child) => { + results.push(...getTestCaseNodesRecursive(child)); + }); + } + return results; +} + +/** Sets up detailed coverage loading that routes to the correct project by file path. */ +export function setupCoverageForProjects(request: TestRunRequest, projects: ProjectAdapter[]): void { + if (request.profile?.kind === TestRunProfileKind.Coverage) { + // Create a single callback that routes to the correct project's coverage map by file path + request.profile.loadDetailedCoverage = ( + _testRun: TestRun, + fileCoverage, + _token, + ): Thenable => { + const filePath = fileCoverage.uri.fsPath; + // Find the project that has coverage data for this file + for (const project of projects) { + const details = project.resultResolver.detailedCoverageMap.get(filePath); + if (details) { + return Promise.resolve(details); + } + } + return Promise.resolve([]); + }; + } +} diff --git a/src/client/testing/testController/common/projectUtils.ts b/src/client/testing/testController/common/projectUtils.ts new file mode 100644 index 000000000000..b104b7f6842d --- /dev/null +++ b/src/client/testing/testController/common/projectUtils.ts @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { Uri } from 'vscode'; +import { IConfigurationService } from '../../../common/types'; +import { IEnvironmentVariablesProvider } from '../../../common/variables/types'; +import { UNITTEST_PROVIDER } from '../../common/constants'; +import { TestProvider } from '../../types'; +import { ITestDiscoveryAdapter, ITestExecutionAdapter, ITestResultResolver } from './types'; +import { UnittestTestDiscoveryAdapter } from '../unittest/testDiscoveryAdapter'; +import { UnittestTestExecutionAdapter } from '../unittest/testExecutionAdapter'; +import { PytestTestDiscoveryAdapter } from '../pytest/pytestDiscoveryAdapter'; +import { PytestTestExecutionAdapter } from '../pytest/pytestExecutionAdapter'; + +/** + * Separator used to scope test IDs to a specific project. + * Format: {projectId}{SEPARATOR}{testPath} + * Example: "file:///workspace/project@@PROJECT@@test_file.py::test_name" + */ +export const PROJECT_ID_SEPARATOR = '@@vsc@@'; + +/** + * Gets the project ID from a project URI. + * The project ID is simply the string representation of the URI, matching how + * the Python Environments extension stores projects in Map. + * + * @param projectUri The project URI + * @returns The project ID (URI as string) + */ +export function getProjectId(projectUri: Uri): string { + return projectUri.toString(); +} + +/** + * Parses a project-scoped vsId back into its components. + * + * @param vsId The VS Code test item ID to parse + * @returns A tuple of [projectId, runId]. If the ID is not project-scoped, + * returns [undefined, vsId] (legacy format) + */ +export function parseVsId(vsId: string): [string | undefined, string] { + const separatorIndex = vsId.indexOf(PROJECT_ID_SEPARATOR); + if (separatorIndex === -1) { + return [undefined, vsId]; // Legacy ID without project scope + } + return [vsId.substring(0, separatorIndex), vsId.substring(separatorIndex + PROJECT_ID_SEPARATOR.length)]; +} + +/** + * Creates a display name for a project including Python version. + * Format: "{projectName} (Python {version})" + * + * @param projectName The name of the project + * @param pythonVersion The Python version string (e.g., "3.11.2") + * @returns Formatted display name + */ +export function createProjectDisplayName(projectName: string, pythonVersion: string): string { + // Extract major.minor version if full version provided + const versionMatch = pythonVersion.match(/^(\d+\.\d+)/); + const shortVersion = versionMatch ? versionMatch[1] : pythonVersion; + + return `${projectName} (Python ${shortVersion})`; +} + +/** + * Creates test adapters (discovery and execution) for a given test provider. + * + * @param testProvider The test framework provider ('pytest' | 'unittest') + * @param resultResolver The result resolver to use for test results + * @param configSettings The configuration service + * @param envVarsService The environment variables provider + * @returns An object containing the discovery and execution adapters + */ +export function createTestAdapters( + testProvider: TestProvider, + resultResolver: ITestResultResolver, + configSettings: IConfigurationService, + envVarsService: IEnvironmentVariablesProvider, +): { discoveryAdapter: ITestDiscoveryAdapter; executionAdapter: ITestExecutionAdapter } { + if (testProvider === UNITTEST_PROVIDER) { + return { + discoveryAdapter: new UnittestTestDiscoveryAdapter(configSettings, resultResolver, envVarsService), + executionAdapter: new UnittestTestExecutionAdapter(configSettings, resultResolver, envVarsService), + }; + } + + return { + discoveryAdapter: new PytestTestDiscoveryAdapter(configSettings, resultResolver, envVarsService), + executionAdapter: new PytestTestExecutionAdapter(configSettings, resultResolver, envVarsService), + }; +} diff --git a/src/client/testing/testController/common/resultResolver.ts b/src/client/testing/testController/common/resultResolver.ts new file mode 100644 index 000000000000..c126d233de1b --- /dev/null +++ b/src/client/testing/testController/common/resultResolver.ts @@ -0,0 +1,137 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { CancellationToken, TestController, TestItem, Uri, TestRun, FileCoverageDetail } from 'vscode'; +import { CoveragePayload, DiscoveredTestPayload, ExecutionTestPayload, ITestResultResolver } from './types'; +import { TestProvider } from '../../types'; +import { traceInfo } from '../../../logging'; +import { sendTelemetryEvent } from '../../../telemetry'; +import { EventName } from '../../../telemetry/constants'; +import { TestItemIndex } from './testItemIndex'; +import { TestDiscoveryHandler } from './testDiscoveryHandler'; +import { TestExecutionHandler } from './testExecutionHandler'; +import { TestCoverageHandler } from './testCoverageHandler'; + +export class PythonResultResolver implements ITestResultResolver { + testController: TestController; + + testProvider: TestProvider; + + private testItemIndex: TestItemIndex; + + // Shared singleton handlers + private static discoveryHandler: TestDiscoveryHandler = new TestDiscoveryHandler(); + private static executionHandler: TestExecutionHandler = new TestExecutionHandler(); + private static coverageHandler: TestCoverageHandler = new TestCoverageHandler(); + + public detailedCoverageMap = new Map(); + + /** + * Optional project ID for scoping test IDs. + * When set, all test IDs are prefixed with `{projectId}@@vsc@@` for project-based testing. + * When undefined, uses legacy workspace-level IDs for backward compatibility. + */ + private projectId?: string; + + /** + * Optional project display name for labeling the test tree root. + * When set, the root node label will be "project: {projectName}" instead of the folder name. + */ + private projectName?: string; + + constructor( + testController: TestController, + testProvider: TestProvider, + private workspaceUri: Uri, + projectId?: string, + projectName?: string, + ) { + this.testController = testController; + this.testProvider = testProvider; + this.projectId = projectId; + this.projectName = projectName; + // Initialize a new TestItemIndex which will be used to track test items in this workspace/project + this.testItemIndex = new TestItemIndex(); + } + + // Expose for backward compatibility (WorkspaceTestAdapter accesses these) + public get runIdToTestItem(): Map { + return this.testItemIndex.runIdToTestItemMap; + } + + public get runIdToVSid(): Map { + return this.testItemIndex.runIdToVSidMap; + } + + public get vsIdToRunId(): Map { + return this.testItemIndex.vsIdToRunIdMap; + } + + /** + * Gets the project ID for this resolver (if any). + * Used for project-scoped test ID generation. + */ + public getProjectId(): string | undefined { + return this.projectId; + } + + public resolveDiscovery(payload: DiscoveredTestPayload, token?: CancellationToken): void { + PythonResultResolver.discoveryHandler.processDiscovery( + payload, + this.testController, + this.testItemIndex, + this.workspaceUri, + this.testProvider, + token, + this.projectId, + this.projectName, + ); + sendTelemetryEvent(EventName.UNITTEST_DISCOVERY_DONE, undefined, { + tool: this.testProvider, + failed: false, + }); + } + + public _resolveDiscovery(payload: DiscoveredTestPayload, token?: CancellationToken): void { + // Delegate to the public method for backward compatibility + this.resolveDiscovery(payload, token); + } + + public resolveExecution(payload: ExecutionTestPayload | CoveragePayload, runInstance: TestRun): void { + if ('coverage' in payload) { + // coverage data is sent once per connection + traceInfo('Coverage data received, processing...'); + this.detailedCoverageMap = PythonResultResolver.coverageHandler.processCoverage( + payload as CoveragePayload, + runInstance, + ); + traceInfo('Coverage data processing complete.'); + } else { + PythonResultResolver.executionHandler.processExecution( + payload as ExecutionTestPayload, + runInstance, + this.testItemIndex, + this.testController, + ); + } + } + + public _resolveExecution(payload: ExecutionTestPayload, runInstance: TestRun): void { + // Delegate to the public method for backward compatibility + this.resolveExecution(payload, runInstance); + } + + public _resolveCoverage(payload: CoveragePayload, runInstance: TestRun): void { + // Delegate to the public method for backward compatibility + this.resolveExecution(payload, runInstance); + } + + /** + * Clean up stale test item references from the cache maps. + * Validates cached items and removes any that are no longer in the test tree. + * Delegates to TestItemIndex. + */ + public cleanupStaleReferences(): void { + this.testItemIndex.cleanupStaleReferences(this.testController); + } +} diff --git a/src/client/testing/testController/common/testCoverageHandler.ts b/src/client/testing/testController/common/testCoverageHandler.ts new file mode 100644 index 000000000000..81ec80579730 --- /dev/null +++ b/src/client/testing/testController/common/testCoverageHandler.ts @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { TestRun, Uri, TestCoverageCount, FileCoverage, FileCoverageDetail, StatementCoverage, Range } from 'vscode'; +import { CoveragePayload, FileCoverageMetrics } from './types'; + +/** + * Stateless handler for processing coverage payloads and creating coverage objects. + * This handler is shared across all workspaces and contains no instance state. + */ +export class TestCoverageHandler { + /** + * Process coverage payload + * Pure function - returns coverage data without storing it + */ + public processCoverage(payload: CoveragePayload, runInstance: TestRun): Map { + const detailedCoverageMap = new Map(); + + if (payload.result === undefined) { + return detailedCoverageMap; + } + + for (const [key, value] of Object.entries(payload.result)) { + const fileNameStr = key; + const fileCoverageMetrics: FileCoverageMetrics = value; + + // Create FileCoverage object and add to run instance + const fileCoverage = this.createFileCoverage(Uri.file(fileNameStr), fileCoverageMetrics); + runInstance.addCoverage(fileCoverage); + + // Create detailed coverage array for this file + const detailedCoverage = this.createDetailedCoverage( + fileCoverageMetrics.lines_covered ?? [], + fileCoverageMetrics.lines_missed ?? [], + ); + detailedCoverageMap.set(Uri.file(fileNameStr).fsPath, detailedCoverage); + } + + return detailedCoverageMap; + } + + /** + * Create FileCoverage object from metrics + */ + private createFileCoverage(uri: Uri, metrics: FileCoverageMetrics): FileCoverage { + const linesCovered = metrics.lines_covered ?? []; + const linesMissed = metrics.lines_missed ?? []; + const executedBranches = metrics.executed_branches; + const totalBranches = metrics.total_branches; + + const lineCoverageCount = new TestCoverageCount(linesCovered.length, linesCovered.length + linesMissed.length); + + if (totalBranches === -1) { + // branch coverage was not enabled and should not be displayed + return new FileCoverage(uri, lineCoverageCount); + } else { + const branchCoverageCount = new TestCoverageCount(executedBranches, totalBranches); + return new FileCoverage(uri, lineCoverageCount, branchCoverageCount); + } + } + + /** + * Create detailed coverage array for a file + * Only line coverage on detailed, not branch coverage + */ + private createDetailedCoverage(linesCovered: number[], linesMissed: number[]): FileCoverageDetail[] { + const detailedCoverageArray: FileCoverageDetail[] = []; + + // Add covered lines + for (const line of linesCovered) { + // line is 1-indexed, so we need to subtract 1 to get the 0-indexed line number + // true value means line is covered + const statementCoverage = new StatementCoverage( + true, + new Range(line - 1, 0, line - 1, Number.MAX_SAFE_INTEGER), + ); + detailedCoverageArray.push(statementCoverage); + } + + // Add missed lines + for (const line of linesMissed) { + // line is 1-indexed, so we need to subtract 1 to get the 0-indexed line number + // false value means line is NOT covered + const statementCoverage = new StatementCoverage( + false, + new Range(line - 1, 0, line - 1, Number.MAX_SAFE_INTEGER), + ); + detailedCoverageArray.push(statementCoverage); + } + + return detailedCoverageArray; + } +} diff --git a/src/client/testing/testController/common/testDiscoveryHandler.ts b/src/client/testing/testController/common/testDiscoveryHandler.ts new file mode 100644 index 000000000000..3f70e6b68594 --- /dev/null +++ b/src/client/testing/testController/common/testDiscoveryHandler.ts @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { CancellationToken, TestController, Uri, MarkdownString } from 'vscode'; +import * as util from 'util'; +import { DiscoveredTestPayload } from './types'; +import { TestProvider } from '../../types'; +import { traceError, traceWarn } from '../../../logging'; +import { Testing } from '../../../common/utils/localize'; +import { createErrorTestItem } from './testItemUtilities'; +import { buildErrorNodeOptions, populateTestTree } from './utils'; +import { TestItemIndex } from './testItemIndex'; +import { PROJECT_ID_SEPARATOR } from './projectUtils'; + +/** + * Stateless handler for processing discovery payloads and building/updating the TestItem tree. + * This handler is shared across all workspaces and contains no instance state. + */ +export class TestDiscoveryHandler { + /** + * Process discovery payload and update test tree + * Pure function - no instance state used + */ + public processDiscovery( + payload: DiscoveredTestPayload, + testController: TestController, + testItemIndex: TestItemIndex, + workspaceUri: Uri, + testProvider: TestProvider, + token?: CancellationToken, + projectId?: string, + projectName?: string, + ): void { + if (!payload) { + // No test data is available + return; + } + + const workspacePath = workspaceUri.fsPath; + const rawTestData = payload as DiscoveredTestPayload; + + // Check if there were any errors in the discovery process. + if (rawTestData.status === 'error') { + this.createErrorNode(testController, workspaceUri, rawTestData.error, testProvider, projectId, projectName); + } else { + // remove error node only if no errors exist. + const errorNodeId = projectId + ? `${projectId}${PROJECT_ID_SEPARATOR}DiscoveryError:${workspacePath}` + : `DiscoveryError:${workspacePath}`; + testController.items.delete(errorNodeId); + } + + if (rawTestData.tests || rawTestData.tests === null) { + // if any tests exist, they should be populated in the test tree, regardless of whether there were errors or not. + // parse and insert test data. + + // Clear existing mappings before rebuilding test tree + testItemIndex.clear(); + + // If the test root for this folder exists: Workspace refresh, update its children. + // Otherwise, it is a freshly discovered workspace, and we need to create a new test root and populate the test tree. + // Note: populateTestTree will call testItemIndex.registerTestItem() for each discovered test + populateTestTree( + testController, + rawTestData.tests, + undefined, + { + runIdToTestItem: testItemIndex.runIdToTestItemMap, + runIdToVSid: testItemIndex.runIdToVSidMap, + vsIdToRunId: testItemIndex.vsIdToRunIdMap, + }, + token, + projectId, + projectName, + ); + } + } + + /** + * Create an error node for discovery failures + */ + public createErrorNode( + testController: TestController, + workspaceUri: Uri, + error: string[] | undefined, + testProvider: TestProvider, + projectId?: string, + projectName?: string, + ): void { + const workspacePath = workspaceUri.fsPath; + const testingErrorConst = + testProvider === 'pytest' ? Testing.errorPytestDiscovery : Testing.errorUnittestDiscovery; + + traceError(testingErrorConst, 'for workspace: ', workspacePath, '\r\n', error?.join('\r\n\r\n') ?? ''); + + // For unittest in project-based mode, check if the error might be caused by nested project imports + // This helps users understand that import errors from nested projects can be safely ignored + // if those tests are covered by a different project with the correct environment. + if (testProvider === 'unittest' && projectId) { + const errorText = error?.join(' ') ?? ''; + const isImportError = + errorText.includes('ModuleNotFoundError') || + errorText.includes('ImportError') || + errorText.includes('No module named'); + + if (isImportError) { + const warningMessage = + '--- ' + + `[test-by-project] Import error during unittest discovery for project at ${workspacePath}. ` + + 'This may be caused by test files in nested project directories that require different dependencies. ' + + 'If these tests are discovered successfully by their own project (with the correct Python environment), ' + + 'this error can be safely ignored. To avoid this, consider excluding nested project paths from parent project discovery. ' + + '---'; + traceWarn(warningMessage); + } + } + + const errorNodeId = projectId + ? `${projectId}${PROJECT_ID_SEPARATOR}DiscoveryError:${workspacePath}` + : `DiscoveryError:${workspacePath}`; + let errorNode = testController.items.get(errorNodeId); + const message = util.format( + `${testingErrorConst} ${Testing.seePythonOutput}\r\n`, + error?.join('\r\n\r\n') ?? '', + ); + + if (errorNode === undefined) { + const options = buildErrorNodeOptions(workspaceUri, message, testProvider, projectName); + // Update the error node ID to include project scope if applicable + options.id = errorNodeId; + errorNode = createErrorTestItem(testController, options); + testController.items.add(errorNode); + } + + const errorNodeLabel: MarkdownString = new MarkdownString( + `[Show output](command:python.viewOutput) to view error logs`, + ); + errorNodeLabel.isTrusted = true; + errorNode.error = errorNodeLabel; + } +} diff --git a/src/client/testing/testController/common/testExecutionHandler.ts b/src/client/testing/testController/common/testExecutionHandler.ts new file mode 100644 index 000000000000..127e6980ae46 --- /dev/null +++ b/src/client/testing/testController/common/testExecutionHandler.ts @@ -0,0 +1,231 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { TestController, TestRun, TestMessage, Location } from 'vscode'; +import { ExecutionTestPayload } from './types'; +import { TestItemIndex } from './testItemIndex'; +import { splitLines } from '../../../common/stringUtils'; +import { splitTestNameWithRegex } from './utils'; +import { clearAllChildren } from './testItemUtilities'; + +/** + * Stateless handler for processing execution payloads and updating TestRun instances. + * This handler is shared across all workspaces and contains no instance state. + */ +export class TestExecutionHandler { + /** + * Process execution payload and update test run + * Pure function - no instance state used + */ + public processExecution( + payload: ExecutionTestPayload, + runInstance: TestRun, + testItemIndex: TestItemIndex, + testController: TestController, + ): void { + const rawTestExecData = payload as ExecutionTestPayload; + + if (rawTestExecData !== undefined && rawTestExecData.result !== undefined) { + for (const keyTemp of Object.keys(rawTestExecData.result)) { + const testItem = rawTestExecData.result[keyTemp]; + + // Delegate to specific outcome handlers + this.handleTestOutcome(keyTemp, testItem, runInstance, testItemIndex, testController); + } + } + } + + /** + * Handle a single test result based on outcome + */ + private handleTestOutcome( + runId: string, + testItem: any, + runInstance: TestRun, + testItemIndex: TestItemIndex, + testController: TestController, + ): void { + if (testItem.outcome === 'error') { + this.handleTestError(runId, testItem, runInstance, testItemIndex, testController); + } else if (testItem.outcome === 'failure' || testItem.outcome === 'passed-unexpected') { + this.handleTestFailure(runId, testItem, runInstance, testItemIndex, testController); + } else if (testItem.outcome === 'success' || testItem.outcome === 'expected-failure') { + this.handleTestSuccess(runId, runInstance, testItemIndex, testController); + } else if (testItem.outcome === 'skipped') { + this.handleTestSkipped(runId, runInstance, testItemIndex, testController); + } else if (testItem.outcome === 'subtest-failure') { + this.handleSubtestFailure(runId, testItem, runInstance, testItemIndex, testController); + } else if (testItem.outcome === 'subtest-success') { + this.handleSubtestSuccess(runId, runInstance, testItemIndex, testController); + } + } + + /** + * Handle test items that errored during execution + */ + private handleTestError( + runId: string, + testItem: any, + runInstance: TestRun, + testItemIndex: TestItemIndex, + testController: TestController, + ): void { + const rawTraceback = testItem.traceback ?? ''; + const traceback = splitLines(rawTraceback, { + trim: false, + removeEmptyEntries: true, + }).join('\r\n'); + const text = `${testItem.test} failed with error: ${testItem.message ?? testItem.outcome}\r\n${traceback}`; + const message = new TestMessage(text); + + const foundItem = testItemIndex.getTestItem(runId, testController); + + if (foundItem?.uri) { + if (foundItem.range) { + message.location = new Location(foundItem.uri, foundItem.range); + } + runInstance.errored(foundItem, message); + } + } + + /** + * Handle test items that failed during execution + */ + private handleTestFailure( + runId: string, + testItem: any, + runInstance: TestRun, + testItemIndex: TestItemIndex, + testController: TestController, + ): void { + const rawTraceback = testItem.traceback ?? ''; + const traceback = splitLines(rawTraceback, { + trim: false, + removeEmptyEntries: true, + }).join('\r\n'); + + const text = `${testItem.test} failed: ${testItem.message ?? testItem.outcome}\r\n${traceback}`; + const message = new TestMessage(text); + + const foundItem = testItemIndex.getTestItem(runId, testController); + + if (foundItem?.uri) { + if (foundItem.range) { + message.location = new Location(foundItem.uri, foundItem.range); + } + runInstance.failed(foundItem, message); + } + } + + /** + * Handle test items that passed during execution + */ + private handleTestSuccess( + runId: string, + runInstance: TestRun, + testItemIndex: TestItemIndex, + testController: TestController, + ): void { + const foundItem = testItemIndex.getTestItem(runId, testController); + + if (foundItem !== undefined && foundItem.uri) { + runInstance.passed(foundItem); + } + } + + /** + * Handle test items that were skipped during execution + */ + private handleTestSkipped( + runId: string, + runInstance: TestRun, + testItemIndex: TestItemIndex, + testController: TestController, + ): void { + const foundItem = testItemIndex.getTestItem(runId, testController); + + if (foundItem !== undefined && foundItem.uri) { + runInstance.skipped(foundItem); + } + } + + /** + * Handle subtest failures + */ + private handleSubtestFailure( + runId: string, + testItem: any, + runInstance: TestRun, + testItemIndex: TestItemIndex, + testController: TestController, + ): void { + const [parentTestCaseId, subtestId] = splitTestNameWithRegex(runId); + const parentTestItem = testItemIndex.getTestItem(parentTestCaseId, testController); + + if (parentTestItem) { + const stats = testItemIndex.getSubtestStats(parentTestCaseId); + if (stats) { + stats.failed += 1; + } else { + testItemIndex.setSubtestStats(parentTestCaseId, { + failed: 1, + passed: 0, + }); + clearAllChildren(parentTestItem); + } + + const subTestItem = testController?.createTestItem(subtestId, subtestId, parentTestItem.uri); + + if (subTestItem) { + const traceback = testItem.traceback ?? ''; + const text = `${testItem.subtest} failed: ${testItem.message ?? testItem.outcome}\r\n${traceback}`; + parentTestItem.children.add(subTestItem); + runInstance.started(subTestItem); + const message = new TestMessage(text); + if (parentTestItem.uri && parentTestItem.range) { + message.location = new Location(parentTestItem.uri, parentTestItem.range); + } + runInstance.failed(subTestItem, message); + } else { + throw new Error('Unable to create new child node for subtest'); + } + } else { + throw new Error('Parent test item not found'); + } + } + + /** + * Handle subtest successes + */ + private handleSubtestSuccess( + runId: string, + runInstance: TestRun, + testItemIndex: TestItemIndex, + testController: TestController, + ): void { + const [parentTestCaseId, subtestId] = splitTestNameWithRegex(runId); + const parentTestItem = testItemIndex.getTestItem(parentTestCaseId, testController); + + if (parentTestItem) { + const stats = testItemIndex.getSubtestStats(parentTestCaseId); + if (stats) { + stats.passed += 1; + } else { + testItemIndex.setSubtestStats(parentTestCaseId, { failed: 0, passed: 1 }); + clearAllChildren(parentTestItem); + } + + const subTestItem = testController?.createTestItem(subtestId, subtestId, parentTestItem.uri); + + if (subTestItem) { + parentTestItem.children.add(subTestItem); + runInstance.started(subTestItem); + runInstance.passed(subTestItem); + } else { + throw new Error('Unable to create new child node for subtest'); + } + } else { + throw new Error('Parent test item not found'); + } + } +} diff --git a/src/client/testing/testController/common/testItemIndex.ts b/src/client/testing/testController/common/testItemIndex.ts new file mode 100644 index 000000000000..448903eae7d5 --- /dev/null +++ b/src/client/testing/testController/common/testItemIndex.ts @@ -0,0 +1,225 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { TestController, TestItem } from 'vscode'; +import { traceError, traceVerbose } from '../../../logging'; +import { getTestCaseNodes } from './testItemUtilities'; + +export interface SubtestStats { + passed: number; + failed: number; +} + +/** + * Maintains persistent ID mappings between Python test IDs and VS Code TestItems. + * This is a stateful component that bridges discovery and execution phases. + * + * Lifecycle: + * - Created: When PythonResultResolver is instantiated (during workspace activation) + * - Populated: During discovery - each discovered test registers its mappings + * - Queried: During execution - to look up TestItems by Python run ID + * - Cleared: When discovery runs again (fresh start) or workspace is disposed + * - Cleaned: Periodically to remove stale references to deleted tests + */ +export class TestItemIndex { + // THE STATE - these maps persist across discovery and execution + private runIdToTestItem: Map; + private runIdToVSid: Map; + private vsIdToRunId: Map; + private subtestStatsMap: Map; + + constructor() { + this.runIdToTestItem = new Map(); + this.runIdToVSid = new Map(); + this.vsIdToRunId = new Map(); + this.subtestStatsMap = new Map(); + } + + /** + * Register a test item with its Python run ID and VS Code ID + * Called during DISCOVERY to populate the index + */ + public registerTestItem(runId: string, vsId: string, testItem: TestItem): void { + this.runIdToTestItem.set(runId, testItem); + this.runIdToVSid.set(runId, vsId); + this.vsIdToRunId.set(vsId, runId); + } + + /** + * Get TestItem by Python run ID (with validation and fallback strategies) + * Called during EXECUTION to look up tests + * + * Uses a three-tier approach: + * 1. Direct O(1) lookup in runIdToTestItem map + * 2. If stale, try vsId mapping and search by VS Code ID + * 3. Last resort: full tree search + */ + public getTestItem(runId: string, testController: TestController): TestItem | undefined { + // Try direct O(1) lookup first + const directItem = this.runIdToTestItem.get(runId); + if (directItem) { + // Validate the item is still in the test tree + if (this.isTestItemValid(directItem, testController)) { + return directItem; + } else { + // Clean up stale reference + this.runIdToTestItem.delete(runId); + } + } + + // Try vsId mapping as fallback + const vsId = this.runIdToVSid.get(runId); + if (vsId) { + // Search by VS Code ID in the controller + let foundItem: TestItem | undefined; + testController.items.forEach((item) => { + if (item.id === vsId) { + foundItem = item; + return; + } + if (!foundItem) { + item.children.forEach((child) => { + if (child.id === vsId) { + foundItem = child; + } + }); + } + }); + + if (foundItem) { + // Cache for future lookups + this.runIdToTestItem.set(runId, foundItem); + return foundItem; + } else { + // Clean up stale mapping + this.runIdToVSid.delete(runId); + this.vsIdToRunId.delete(vsId); + } + } + + // Last resort: full tree search + traceError(`Falling back to tree search for test: ${runId}`); + const testCases = this.collectAllTestCases(testController); + return testCases.find((item) => item.id === vsId); + } + + /** + * Get Python run ID from VS Code ID + * Called by WorkspaceTestAdapter.executeTests() to convert selected tests to Python IDs + */ + public getRunId(vsId: string): string | undefined { + return this.vsIdToRunId.get(vsId); + } + + /** + * Get VS Code ID from Python run ID + */ + public getVSId(runId: string): string | undefined { + return this.runIdToVSid.get(runId); + } + + /** + * Check if a TestItem reference is still valid in the tree + * + * Time Complexity: O(depth) where depth is the maximum nesting level of the test tree. + * In most cases this is O(1) to O(3) since test trees are typically shallow. + */ + public isTestItemValid(testItem: TestItem, testController: TestController): boolean { + // Simple validation: check if the item's parent chain leads back to the controller + let current: TestItem | undefined = testItem; + while (current?.parent) { + current = current.parent; + } + + // If we reached a root item, check if it's in the controller + if (current) { + return testController.items.get(current.id) === current; + } + + // If no parent chain, check if it's directly in the controller + return testController.items.get(testItem.id) === testItem; + } + + /** + * Get subtest statistics for a parent test case + * Returns undefined if no stats exist yet for this parent + */ + public getSubtestStats(parentId: string): SubtestStats | undefined { + return this.subtestStatsMap.get(parentId); + } + + /** + * Set subtest statistics for a parent test case + */ + public setSubtestStats(parentId: string, stats: SubtestStats): void { + this.subtestStatsMap.set(parentId, stats); + } + + /** + * Remove all mappings + * Called at the start of discovery to ensure clean state + */ + public clear(): void { + this.runIdToTestItem.clear(); + this.runIdToVSid.clear(); + this.vsIdToRunId.clear(); + this.subtestStatsMap.clear(); + } + + /** + * Clean up stale references that no longer exist in the test tree + * Called after test tree modifications + */ + public cleanupStaleReferences(testController: TestController): void { + const staleRunIds: string[] = []; + + // Check all runId->TestItem mappings + this.runIdToTestItem.forEach((testItem, runId) => { + if (!this.isTestItemValid(testItem, testController)) { + staleRunIds.push(runId); + } + }); + + // Remove stale entries + staleRunIds.forEach((runId) => { + const vsId = this.runIdToVSid.get(runId); + this.runIdToTestItem.delete(runId); + this.runIdToVSid.delete(runId); + if (vsId) { + this.vsIdToRunId.delete(vsId); + } + }); + + if (staleRunIds.length > 0) { + traceVerbose(`Cleaned up ${staleRunIds.length} stale test item references`); + } + } + + /** + * Collect all test case items from the test controller tree. + * Note: This performs full tree traversal - use cached lookups when possible. + */ + private collectAllTestCases(testController: TestController): TestItem[] { + const testCases: TestItem[] = []; + + testController.items.forEach((i) => { + const tempArr: TestItem[] = getTestCaseNodes(i); + testCases.push(...tempArr); + }); + + return testCases; + } + + // Expose maps for backward compatibility (read-only access) + public get runIdToTestItemMap(): Map { + return this.runIdToTestItem; + } + + public get runIdToVSidMap(): Map { + return this.runIdToVSid; + } + + public get vsIdToRunIdMap(): Map { + return this.vsIdToRunId; + } +} diff --git a/src/client/testing/testController/common/testItemUtilities.ts b/src/client/testing/testController/common/testItemUtilities.ts new file mode 100644 index 000000000000..43624bba2527 --- /dev/null +++ b/src/client/testing/testController/common/testItemUtilities.ts @@ -0,0 +1,583 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { + TestItem, + Uri, + Range, + Position, + TestController, + TestRunResult, + TestResultState, + TestResultSnapshot, + TestItemCollection, +} from 'vscode'; +import { CancellationToken } from 'vscode-jsonrpc'; +import { asyncForEach } from '../../../common/utils/arrayUtils'; +import { traceError, traceVerbose } from '../../../logging'; +import { + RawDiscoveredTests, + RawTest, + RawTestFile, + RawTestFolder, + RawTestFunction, + RawTestSuite, + TestData, + TestDataKinds, +} from './types'; + +// Todo: Use `TestTag` when the proposed API gets into stable. +export const RunTestTag = { id: 'python-run' }; +export const DebugTestTag = { id: 'python-debug' }; + +function testItemCollectionToArray(collection: TestItemCollection): TestItem[] { + const items: TestItem[] = []; + collection.forEach((c) => { + items.push(c); + }); + return items; +} + +export function removeItemByIdFromChildren( + idToRawData: Map, + item: TestItem, + childNodeIdsToRemove: string[], +): void { + childNodeIdsToRemove.forEach((id) => { + item.children.delete(id); + idToRawData.delete(id); + }); +} + +export type ErrorTestItemOptions = { id: string; label: string; error: string }; + +export function createErrorTestItem(testController: TestController, options: ErrorTestItemOptions): TestItem { + const testItem = testController.createTestItem(options.id, options.label); + testItem.canResolveChildren = false; + testItem.error = options.error; + testItem.tags = [RunTestTag, DebugTestTag]; + return testItem; +} + +export function createWorkspaceRootTestItem( + testController: TestController, + idToRawData: Map, + options: { id: string; label: string; uri: Uri; runId: string; parentId?: string; rawId?: string }, +): TestItem { + const testItem = testController.createTestItem(options.id, options.label, options.uri); + testItem.canResolveChildren = true; + idToRawData.set(options.id, { + ...options, + rawId: options.rawId ?? options.id, + kind: TestDataKinds.Workspace, + }); + testItem.tags = [RunTestTag, DebugTestTag]; + return testItem; +} + +function getParentIdFromRawParentId( + idToRawData: Map, + testRoot: string, + raw: { parentid: string }, +): string | undefined { + const parent = idToRawData.get(path.join(testRoot, raw.parentid)); + let parentId; + if (parent) { + parentId = parent.id === '.' ? testRoot : parent.id; + } + return parentId; +} + +function getRangeFromRawSource(raw: { source: string }): Range | undefined { + // We have to extract the line number from the source data. If it is available it + // saves us from running symbol script or querying language server for this info. + try { + const sourceLine = raw.source.substr(raw.source.indexOf(':') + 1); + const line = Number.parseInt(sourceLine, 10); + // Lines in raw data start at 1, vscode lines start at 0 + return new Range(new Position(line - 1, 0), new Position(line, 0)); + } catch (ex) { + // ignore + } + return undefined; +} + +export function getRunIdFromRawData(id: string): string { + // TODO: This is a temporary solution to normalize test ids. + // The current method is error prone and easy to break. When we + // re-write the test adapters we should make sure we consider this. + // This is the id that will be used to compare with the results. + const runId = id + .replace(/\.py[^\w\-]/g, '') // we want to get rid of the `.py` in file names + .replace(/[\\\:\/]/g, '.') + .replace(/\:\:/g, '.') + .replace(/\.\./g, '.'); + return runId.startsWith('.') ? runId.substr(1) : runId; +} + +function createFolderOrFileTestItem( + testController: TestController, + idToRawData: Map, + testRoot: string, + rawData: RawTestFolder | RawTestFile, +): TestItem { + const fullPath = path.join(testRoot, rawData.relpath); + const uri = Uri.file(fullPath); + + const parentId = getParentIdFromRawParentId(idToRawData, testRoot, rawData); + + const label = path.basename(fullPath); + const testItem = testController.createTestItem(fullPath, label, uri); + + testItem.canResolveChildren = true; + + idToRawData.set(testItem.id, { + id: testItem.id, + rawId: rawData.id, + runId: rawData.relpath, + uri, + kind: TestDataKinds.FolderOrFile, + parentId, + }); + testItem.tags = [RunTestTag, DebugTestTag]; + return testItem; +} + +function updateFolderOrFileTestItem( + item: TestItem, + idToRawData: Map, + testRoot: string, + rawData: RawTestFolder | RawTestFile, +): void { + const fullPath = path.join(testRoot, rawData.relpath); + const uri = Uri.file(fullPath); + + const parentId = getParentIdFromRawParentId(idToRawData, testRoot, rawData); + + item.label = path.basename(fullPath); + + item.canResolveChildren = true; + + idToRawData.set(item.id, { + id: item.id, + rawId: rawData.id, + runId: rawData.relpath, + uri, + kind: TestDataKinds.FolderOrFile, + parentId, + }); + item.tags = [RunTestTag, DebugTestTag]; +} + +function createCollectionTestItem( + testController: TestController, + idToRawData: Map, + testRoot: string, + rawData: RawTestSuite | RawTestFunction, +): TestItem { + // id can look like test_something.py::SomeClass + const id = path.join(testRoot, rawData.id); + + // We need the actual document path so we can set the location for the tests. This will be + // used to provide test result status next to the tests. + const documentPath = path.join(testRoot, rawData.id.substr(0, rawData.id.indexOf(':'))); + const uri = Uri.file(documentPath); + + const label = rawData.name; + + const parentId = getParentIdFromRawParentId(idToRawData, testRoot, rawData); + const runId = getRunIdFromRawData(rawData.id); + + const testItem = testController.createTestItem(id, label, uri); + + testItem.canResolveChildren = true; + + idToRawData.set(testItem.id, { + id: testItem.id, + rawId: rawData.id, + runId, + uri, + kind: TestDataKinds.Collection, + parentId, + }); + testItem.tags = [RunTestTag, DebugTestTag]; + return testItem; +} + +function updateCollectionTestItem( + item: TestItem, + idToRawData: Map, + testRoot: string, + rawData: RawTestSuite | RawTestFunction, +): void { + // We need the actual document path so we can set the location for the tests. This will be + // used to provide test result status next to the tests. + const documentPath = path.join(testRoot, rawData.id.substr(0, rawData.id.indexOf(':'))); + const uri = Uri.file(documentPath); + + item.label = rawData.name; + + const parentId = getParentIdFromRawParentId(idToRawData, testRoot, rawData); + const runId = getRunIdFromRawData(rawData.id); + + item.canResolveChildren = true; + + idToRawData.set(item.id, { + id: item.id, + rawId: rawData.id, + runId, + uri, + kind: TestDataKinds.Collection, + parentId, + }); + item.tags = [RunTestTag, DebugTestTag]; +} + +function createTestCaseItem( + testController: TestController, + idToRawData: Map, + testRoot: string, + rawData: RawTest, +): TestItem { + // id can look like: + // test_something.py::SomeClass::someTest + // test_something.py::SomeClass::someTest[x1] + const id = path.join(testRoot, rawData.id); + + // We need the actual document path so we can set the location for the tests. This will be + // used to provide test result status next to the tests. + const documentPath = path.join(testRoot, rawData.source.substr(0, rawData.source.indexOf(':'))); + const uri = Uri.file(documentPath); + + const label = rawData.name; + + const parentId = getParentIdFromRawParentId(idToRawData, testRoot, rawData); + const runId = getRunIdFromRawData(rawData.id); + + const testItem = testController.createTestItem(id, label, uri); + + testItem.canResolveChildren = false; + testItem.range = getRangeFromRawSource(rawData); + + idToRawData.set(testItem.id, { + id: testItem.id, + rawId: rawData.id, + runId, + uri, + kind: TestDataKinds.Case, + parentId, + }); + testItem.tags = [RunTestTag, DebugTestTag]; + return testItem; +} + +function updateTestCaseItem( + item: TestItem, + idToRawData: Map, + testRoot: string, + rawData: RawTest, +): void { + // We need the actual document path so we can set the location for the tests. This will be + // used to provide test result status next to the tests. + const documentPath = path.join(testRoot, rawData.source.substr(0, rawData.source.indexOf(':'))); + const uri = Uri.file(documentPath); + + item.label = rawData.name; + + const parentId = getParentIdFromRawParentId(idToRawData, testRoot, rawData); + const runId = getRunIdFromRawData(rawData.id); + + item.canResolveChildren = false; + item.range = getRangeFromRawSource(rawData); + + idToRawData.set(item.id, { + id: item.id, + rawId: rawData.id, + runId, + uri, + kind: TestDataKinds.Case, + parentId, + }); + item.tags = [RunTestTag, DebugTestTag]; +} + +async function updateTestItemFromRawDataInternal( + item: TestItem, + testController: TestController, + idToRawData: Map, + testRoot: string, + rawDataSet: RawDiscoveredTests[], + token?: CancellationToken, +): Promise { + if (token?.isCancellationRequested) { + return; + } + + const rawId = idToRawData.get(item.id)?.rawId; + if (!rawId) { + traceError(`Unknown node id: ${item.id}`); + return; + } + + const nodeRawData = rawDataSet.filter( + (r) => + r.root === rawId || + r.rootid === rawId || + r.parents.find((p) => p.id === rawId) || + r.tests.find((t) => t.id === rawId), + ); + + if (nodeRawData.length === 0 && item.parent) { + removeItemByIdFromChildren(idToRawData, item.parent, [item.id]); + traceVerbose(`Following test item was removed Reason: No-Raw-Data ${item.id}`); + return; + } + + if (nodeRawData.length > 1) { + // Something is wrong, there can only be one test node with that id + traceError(`Multiple (${nodeRawData.length}) raw data nodes had the same id: ${rawId}`); + return; + } + + if (rawId === nodeRawData[0].root || rawId === nodeRawData[0].rootid) { + // This is a test root node, we need to update the entire tree + // The update children and remove any child that does not have raw data. + + await asyncForEach(testItemCollectionToArray(item.children), async (c) => { + await updateTestItemFromRawData(c, testController, idToRawData, testRoot, nodeRawData, token); + }); + + // Create child nodes that are new. + // We only need to look at rawData.parents. Since at this level we either have folder or file. + const rawChildNodes = nodeRawData[0].parents.filter((p) => p.parentid === '.' || p.parentid === rawId); + const existingNodes: string[] = []; + item.children.forEach((c) => existingNodes.push(idToRawData.get(c.id)?.rawId ?? '')); + + await asyncForEach( + rawChildNodes.filter((r) => !existingNodes.includes(r.id)), + async (r) => { + const childItem = + r.kind === 'file' + ? createFolderOrFileTestItem(testController, idToRawData, testRoot, r as RawTestFile) + : createFolderOrFileTestItem(testController, idToRawData, testRoot, r as RawTestFolder); + item.children.add(childItem); + await updateTestItemFromRawData(childItem, testController, idToRawData, testRoot, nodeRawData, token); + }, + ); + + return; + } + + // First check if this is a parent node + const rawData = nodeRawData[0].parents.filter((r) => r.id === rawId); + if (rawData.length === 1) { + // This is either a File/Folder/Collection node + + // Update the node data + switch (rawData[0].kind) { + case 'file': + updateFolderOrFileTestItem(item, idToRawData, testRoot, rawData[0] as RawTestFile); + break; + case 'folder': + updateFolderOrFileTestItem(item, idToRawData, testRoot, rawData[0] as RawTestFolder); + break; + case 'suite': + updateCollectionTestItem(item, idToRawData, testRoot, rawData[0] as RawTestSuite); + break; + case 'function': + updateCollectionTestItem(item, idToRawData, testRoot, rawData[0] as RawTestFunction); + break; + default: + break; + } + + // The update children and remove any child that does not have raw data. + await asyncForEach(testItemCollectionToArray(item.children), async (c) => { + await updateTestItemFromRawData(c, testController, idToRawData, testRoot, nodeRawData, token); + }); + + // Create child nodes that are new. + // Get the existing child node ids so we can skip them + const existingNodes: string[] = []; + item.children.forEach((c) => existingNodes.push(idToRawData.get(c.id)?.rawId ?? '')); + + // We first look at rawData.parents. Since at this level we either have folder or file. + // The current node is potentially a parent of one of these "parent" nodes or it is a parent + // of test case nodes. We will handle Test case nodes after handling parents. + const rawChildNodes = nodeRawData[0].parents.filter((p) => p.parentid === rawId); + await asyncForEach( + rawChildNodes.filter((r) => !existingNodes.includes(r.id)), + async (r) => { + let childItem; + switch (r.kind) { + case 'file': + childItem = createFolderOrFileTestItem(testController, idToRawData, testRoot, r as RawTestFile); + break; + case 'folder': + childItem = createFolderOrFileTestItem( + testController, + idToRawData, + testRoot, + r as RawTestFolder, + ); + break; + case 'suite': + childItem = createCollectionTestItem(testController, idToRawData, testRoot, r as RawTestSuite); + break; + case 'function': + childItem = createCollectionTestItem( + testController, + idToRawData, + testRoot, + r as RawTestFunction, + ); + break; + default: + break; + } + if (childItem) { + item.children.add(childItem); + // This node can potentially have children. So treat it like a new node and update it. + await updateTestItemFromRawData( + childItem, + testController, + idToRawData, + testRoot, + nodeRawData, + token, + ); + } + }, + ); + + // Now we will look at test case nodes. Create any test case node that does not already exist. + const rawTestCaseNodes = nodeRawData[0].tests.filter((p) => p.parentid === rawId); + rawTestCaseNodes + .filter((r) => !existingNodes.includes(r.id)) + .forEach((r) => { + const childItem = createTestCaseItem(testController, idToRawData, testRoot, r); + item.children.add(childItem); + }); + + return; + } + + if (rawData.length > 1) { + // Something is wrong, there can only be one test node with that id + traceError(`Multiple (${rawData.length}) raw data nodes had the same id: ${rawId}`); + return; + } + + // We are here this means rawData.length === 0 + // The node is probably is test case node. Try and find it. + const rawCaseData = nodeRawData[0].tests.filter((r) => r.id === rawId); + + if (rawCaseData.length === 1) { + // This is a test case node + updateTestCaseItem(item, idToRawData, testRoot, rawCaseData[0]); + return; + } + + if (rawCaseData.length > 1) { + // Something is wrong, there can only be one test node with that id + traceError(`Multiple (${rawCaseData.length}) raw data nodes had the same id: ${rawId}`); + } +} + +export async function updateTestItemFromRawData( + item: TestItem, + testController: TestController, + idToRawData: Map, + testRoot: string, + rawDataSet: RawDiscoveredTests[], + token?: CancellationToken, +): Promise { + item.busy = true; + await updateTestItemFromRawDataInternal(item, testController, idToRawData, testRoot, rawDataSet, token); + item.busy = false; +} + +export function getTestCaseNodes(testNode: TestItem, collection: TestItem[] = []): TestItem[] { + if (!testNode.canResolveChildren && testNode.tags.length > 0) { + collection.push(testNode); + } + + testNode.children.forEach((c) => { + if (testNode.canResolveChildren) { + getTestCaseNodes(c, collection); + } else { + collection.push(testNode); + } + }); + return collection; +} + +export function getWorkspaceNode(testNode: TestItem, idToRawData: Map): TestItem | undefined { + const raw = idToRawData.get(testNode.id); + if (raw) { + if (raw.kind === TestDataKinds.Workspace) { + return testNode; + } + if (testNode.parent) { + return getWorkspaceNode(testNode.parent, idToRawData); + } + } + return undefined; +} + +export function getNodeByUri(root: TestItem, uri: Uri): TestItem | undefined { + if (root.uri?.fsPath === uri.fsPath) { + return root; + } + + const nodes: TestItem[] = []; + root.children.forEach((c) => nodes.push(c)); + + // Search at the current level + for (const node of nodes) { + if (node.uri?.fsPath === uri.fsPath) { + return node; + } + } + + // Search the children of the current level + for (const node of nodes) { + const found = getNodeByUri(node, uri); + if (found) { + return found; + } + } + return undefined; +} + +function updateTestResultMapForSnapshot(resultMap: Map, snapshot: TestResultSnapshot) { + for (const taskState of snapshot.taskStates) { + resultMap.set(snapshot.id, taskState.state); + } + snapshot.children.forEach((child) => updateTestResultMapForSnapshot(resultMap, child)); +} + +export function updateTestResultMap( + resultMap: Map, + testResults: readonly TestRunResult[], +): void { + const ordered = new Array(...testResults).sort((a, b) => a.completedAt - b.completedAt); + ordered.forEach((testResult) => { + testResult.results.forEach((snapshot) => updateTestResultMapForSnapshot(resultMap, snapshot)); + }); +} + +export function checkForFailedTests(resultMap: Map): boolean { + return ( + Array.from(resultMap.values()).find( + (state) => state === TestResultState.Failed || state === TestResultState.Errored, + ) !== undefined + ); +} + +export function clearAllChildren(testNode: TestItem): void { + const ids: string[] = []; + testNode.children.forEach((c) => ids.push(c.id)); + ids.forEach(testNode.children.delete); +} diff --git a/src/client/testing/testController/common/testProjectRegistry.ts b/src/client/testing/testController/common/testProjectRegistry.ts new file mode 100644 index 000000000000..4f0702ad584c --- /dev/null +++ b/src/client/testing/testController/common/testProjectRegistry.ts @@ -0,0 +1,330 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { TestController, Uri } from 'vscode'; +import { isParentPath } from '../../../common/platform/fs-paths'; +import { IConfigurationService } from '../../../common/types'; +import { IInterpreterService } from '../../../interpreter/contracts'; +import { traceError, traceInfo } from '../../../logging'; +import { UNITTEST_PROVIDER } from '../../common/constants'; +import { TestProvider } from '../../types'; +import { IEnvironmentVariablesProvider } from '../../../common/variables/types'; +import { PythonProject, PythonEnvironment } from '../../../envExt/types'; +import { getEnvExtApi, useEnvExtension } from '../../../envExt/api.internal'; +import { ProjectAdapter } from './projectAdapter'; +import { getProjectId, createProjectDisplayName, createTestAdapters } from './projectUtils'; +import { PythonResultResolver } from './resultResolver'; + +/** + * Registry for Python test projects within workspaces. + * + * Manages the lifecycle of test projects including: + * - Discovering Python projects via Python Environments API + * - Creating and storing ProjectAdapter instances per workspace + * - Computing nested project relationships for ignore lists + * - Fallback to default "legacy" project when API unavailable + * + * **Key concepts:** + * - **Workspace:** A VS Code workspace folder (may contain multiple projects) + * - **Project:** A Python project within a workspace (identified by pyproject.toml, setup.py, etc.) + * - **ProjectUri:** The unique identifier for a project (the URI of the project root directory) + * - Each project gets its own test tree root, Python environment, and test adapters + * + * **Project identification:** + * Projects are identified and tracked by their URI (projectUri.toString()). This matches + * how the Python Environments extension stores projects in its Map. + */ +export class TestProjectRegistry { + /** + * Map of workspace URI -> Map of project URI string -> ProjectAdapter + * + * Projects are keyed by their URI string (projectUri.toString()) which matches how + * the Python Environments extension identifies projects. This enables O(1) lookups + * when given a project URI. + */ + private readonly workspaceProjects: Map> = new Map(); + + constructor( + private readonly testController: TestController, + private readonly configSettings: IConfigurationService, + private readonly interpreterService: IInterpreterService, + private readonly envVarsService: IEnvironmentVariablesProvider, + ) {} + + /** + * Gets the projects map for a workspace, if it exists. + */ + public getWorkspaceProjects(workspaceUri: Uri): Map | undefined { + return this.workspaceProjects.get(workspaceUri); + } + + /** + * Checks if a workspace has been initialized with projects. + */ + public hasProjects(workspaceUri: Uri): boolean { + return this.workspaceProjects.has(workspaceUri); + } + + /** + * Gets all projects for a workspace as an array. + */ + public getProjectsArray(workspaceUri: Uri): ProjectAdapter[] { + const projectsMap = this.workspaceProjects.get(workspaceUri); + return projectsMap ? Array.from(projectsMap.values()) : []; + } + + /** + * Discovers and registers all Python projects for a workspace. + * Returns the discovered projects for the caller to use. + */ + public async discoverAndRegisterProjects(workspaceUri: Uri): Promise { + traceInfo(`[test-by-project] Discovering projects for workspace: ${workspaceUri.fsPath}`); + + const projects = await this.discoverProjects(workspaceUri); + + // Create map for this workspace, keyed by project URI + const projectsMap = new Map(); + projects.forEach((project) => { + projectsMap.set(getProjectId(project.projectUri), project); + }); + + this.workspaceProjects.set(workspaceUri, projectsMap); + traceInfo(`[test-by-project] Registered ${projects.length} project(s) for ${workspaceUri.fsPath}`); + + return projects; + } + + /** + * Computes and populates nested project ignore lists for all projects in a workspace. + * Must be called before discovery to ensure parent projects ignore nested children. + */ + public configureNestedProjectIgnores(workspaceUri: Uri): void { + const projectIgnores = this.computeNestedProjectIgnores(workspaceUri); + const projects = this.getProjectsArray(workspaceUri); + + for (const project of projects) { + const ignorePaths = projectIgnores.get(getProjectId(project.projectUri)); + if (ignorePaths && ignorePaths.length > 0) { + project.nestedProjectPathsToIgnore = ignorePaths; + traceInfo(`[test-by-project] ${project.projectName} will ignore nested: ${ignorePaths.join(', ')}`); + } + } + } + + /** + * Clears all projects for a workspace. + */ + public clearWorkspace(workspaceUri: Uri): void { + this.workspaceProjects.delete(workspaceUri); + } + + // ====== Private Methods ====== + + /** + * Discovers Python projects in a workspace using the Python Environment API. + * Falls back to creating a single default project if API is unavailable. + */ + private async discoverProjects(workspaceUri: Uri): Promise { + try { + if (!useEnvExtension()) { + traceInfo('[test-by-project] Python Environments API not available, using default project'); + return [await this.createDefaultProject(workspaceUri)]; + } + + const envExtApi = await getEnvExtApi(); + const allProjects = envExtApi.getPythonProjects(); + traceInfo(`[test-by-project] Found ${allProjects.length} total Python projects from API`); + + // Filter to projects within this workspace + const workspaceProjects = allProjects.filter((project) => + isParentPath(project.uri.fsPath, workspaceUri.fsPath), + ); + traceInfo(`[test-by-project] Filtered to ${workspaceProjects.length} projects in workspace`); + + if (workspaceProjects.length === 0) { + traceInfo('[test-by-project] No projects found, creating default project'); + return [await this.createDefaultProject(workspaceUri)]; + } + + // Create ProjectAdapter for each discovered project + const adapters: ProjectAdapter[] = []; + for (const pythonProject of workspaceProjects) { + try { + const adapter = await this.createProjectAdapter(pythonProject, workspaceUri); + adapters.push(adapter); + } catch (error) { + traceError(`[test-by-project] Failed to create adapter for ${pythonProject.uri.fsPath}:`, error); + } + } + + if (adapters.length === 0) { + traceInfo('[test-by-project] All adapters failed, falling back to default project'); + return [await this.createDefaultProject(workspaceUri)]; + } + + return adapters; + } catch (error) { + traceError('[test-by-project] Discovery failed, using default project:', error); + return [await this.createDefaultProject(workspaceUri)]; + } + } + + /** + * Creates a ProjectAdapter from a PythonProject. + * + * Each project gets its own isolated test infrastructure: + * - **ResultResolver:** Handles mapping test IDs and processing results for this project + * - **DiscoveryAdapter:** Discovers tests scoped to this project's root directory + * - **ExecutionAdapter:** Runs tests for this project using its Python environment + * + */ + private async createProjectAdapter(pythonProject: PythonProject, workspaceUri: Uri): Promise { + const projectId = getProjectId(pythonProject.uri); + traceInfo(`[test-by-project] Creating adapter for: ${pythonProject.name} at ${projectId}`); + + // Resolve Python environment + const envExtApi = await getEnvExtApi(); + const pythonEnvironment = await envExtApi.getEnvironment(pythonProject.uri); + if (!pythonEnvironment) { + throw new Error(`No Python environment found for project ${projectId}`); + } + + // Create test infrastructure + const testProvider = this.getTestProvider(workspaceUri); + const projectDisplayName = createProjectDisplayName(pythonProject.name, pythonEnvironment.version); + const resultResolver = new PythonResultResolver( + this.testController, + testProvider, + workspaceUri, + projectId, + pythonProject.name, // Use simple project name for test tree label (without version) + ); + const { discoveryAdapter, executionAdapter } = createTestAdapters( + testProvider, + resultResolver, + this.configSettings, + this.envVarsService, + ); + + return { + projectName: projectDisplayName, + projectUri: pythonProject.uri, + workspaceUri, + pythonProject, + pythonEnvironment, + testProvider, + discoveryAdapter, + executionAdapter, + resultResolver, + isDiscovering: false, + isExecuting: false, + }; + } + + /** + * Creates a default project for legacy/fallback mode. + */ + private async createDefaultProject(workspaceUri: Uri): Promise { + traceInfo(`[test-by-project] Creating default project for: ${workspaceUri.fsPath}`); + + const testProvider = this.getTestProvider(workspaceUri); + const resultResolver = new PythonResultResolver(this.testController, testProvider, workspaceUri); + const { discoveryAdapter, executionAdapter } = createTestAdapters( + testProvider, + resultResolver, + this.configSettings, + this.envVarsService, + ); + + const interpreter = await this.interpreterService.getActiveInterpreter(workspaceUri); + + const pythonEnvironment: PythonEnvironment = { + name: 'default', + displayName: interpreter?.displayName || 'Python', + shortDisplayName: interpreter?.displayName || 'Python', + displayPath: interpreter?.path || 'python', + version: interpreter?.version?.raw || '3.x', + environmentPath: Uri.file(interpreter?.path || 'python'), + sysPrefix: interpreter?.sysPrefix || '', + execInfo: { run: { executable: interpreter?.path || 'python' } }, + envId: { id: 'default', managerId: 'default' }, + }; + + const pythonProject: PythonProject = { + name: path.basename(workspaceUri.fsPath) || 'workspace', + uri: workspaceUri, + }; + + return { + projectName: pythonProject.name, + projectUri: workspaceUri, + workspaceUri, + pythonProject, + pythonEnvironment, + testProvider, + discoveryAdapter, + executionAdapter, + resultResolver, + isDiscovering: false, + isExecuting: false, + }; + } + + /** + * Identifies nested projects and returns ignore paths for parent projects. + * + * **Time complexity:** O(n²) where n is the number of projects in the workspace. + * For each project, checks all other projects to find nested relationships. + * + * Note: Uses path.normalize() to handle Windows path separator inconsistencies + * (e.g., paths from URI.fsPath may have mixed separators). + */ + private computeNestedProjectIgnores(workspaceUri: Uri): Map { + const ignoreMap = new Map(); + const projects = this.getProjectsArray(workspaceUri); + + if (projects.length === 0) return ignoreMap; + + for (const parent of projects) { + const nestedPaths: string[] = []; + + for (const child of projects) { + // Skip self-comparison using URI + if (parent.projectUri.toString() === child.projectUri.toString()) continue; + + // Normalize paths to handle Windows path separator inconsistencies + const parentNormalized = path.normalize(parent.projectUri.fsPath); + const childNormalized = path.normalize(child.projectUri.fsPath); + + // Add trailing separator to ensure we match directory boundaries + const parentWithSep = parentNormalized.endsWith(path.sep) + ? parentNormalized + : parentNormalized + path.sep; + const childWithSep = childNormalized.endsWith(path.sep) ? childNormalized : childNormalized + path.sep; + + // Check if child is inside parent (case-insensitive for Windows) + const childIsInsideParent = childWithSep.toLowerCase().startsWith(parentWithSep.toLowerCase()); + + if (childIsInsideParent) { + nestedPaths.push(child.projectUri.fsPath); + traceInfo(`[test-by-project] Nested: ${child.projectName} is inside ${parent.projectName}`); + } + } + + if (nestedPaths.length > 0) { + ignoreMap.set(getProjectId(parent.projectUri), nestedPaths); + } + } + + return ignoreMap; + } + + /** + * Determines the test provider based on workspace settings. + */ + private getTestProvider(workspaceUri: Uri): TestProvider { + const settings = this.configSettings.getSettings(workspaceUri); + return settings.testing.unittestEnabled ? UNITTEST_PROVIDER : 'pytest'; + } +} diff --git a/src/client/testing/testController/common/types.ts b/src/client/testing/testController/common/types.ts new file mode 100644 index 000000000000..017c41cf3d97 --- /dev/null +++ b/src/client/testing/testController/common/types.ts @@ -0,0 +1,251 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { + CancellationToken, + Event, + FileCoverageDetail, + OutputChannel, + TestController, + TestItem, + TestRun, + TestRunProfileKind, + Uri, + WorkspaceFolder, +} from 'vscode'; +import { ITestDebugLauncher } from '../../common/types'; +import { IPythonExecutionFactory } from '../../../common/process/types'; +import { PythonEnvironment } from '../../../pythonEnvironments/info'; +import { ProjectAdapter } from './projectAdapter'; + +export enum TestDataKinds { + Workspace, + FolderOrFile, + Collection, + Case, +} + +export interface TestData { + rawId: string; + runId: string; + id: string; + uri: Uri; + parentId?: string; + kind: TestDataKinds; +} + +export type TestRefreshOptions = { forceRefresh: boolean }; + +export const ITestController = Symbol('ITestController'); +export interface ITestController { + refreshTestData(resource?: Uri, options?: TestRefreshOptions): Promise; + stopRefreshing(): void; + onRefreshingCompleted: Event; + onRefreshingStarted: Event; + onRunWithoutConfiguration: Event; +} + +export const ITestFrameworkController = Symbol('ITestFrameworkController'); +export interface ITestFrameworkController { + resolveChildren(testController: TestController, item: TestItem, token?: CancellationToken): Promise; +} + +export const ITestsRunner = Symbol('ITestsRunner'); +export interface ITestsRunner {} + +// We expose these here as a convenience and to cut down on churn +// elsewhere in the code. +type RawTestNode = { + id: string; + name: string; + parentid: string; +}; +export type RawTestParent = RawTestNode & { + kind: 'folder' | 'file' | 'suite' | 'function' | 'workspace'; +}; +type RawTestFSNode = RawTestParent & { + kind: 'folder' | 'file'; + relpath: string; +}; +export type RawTestFolder = RawTestFSNode & { + kind: 'folder'; +}; +export type RawTestFile = RawTestFSNode & { + kind: 'file'; +}; +export type RawTestSuite = RawTestParent & { + kind: 'suite'; +}; +// function-as-a-container is for parameterized ("sub") tests. +export type RawTestFunction = RawTestParent & { + kind: 'function'; +}; +export type RawTest = RawTestNode & { + source: string; +}; +export type RawDiscoveredTests = { + rootid: string; + root: string; + parents: RawTestParent[]; + tests: RawTest[]; +}; + +// New test discovery adapter types + +export type DataReceivedEvent = { + uuid: string; + data: string; +}; + +export type TestDiscoveryCommand = { + script: string; + args: string[]; +}; + +export type TestExecutionCommand = { + script: string; + args: string[]; +}; + +export type TestCommandOptions = { + workspaceFolder: Uri; + cwd: string; + command: TestDiscoveryCommand | TestExecutionCommand; + token?: CancellationToken; + outChannel?: OutputChannel; + profileKind?: TestRunProfileKind; + testIds?: string[]; +}; + +// /** +// * Interface describing the server that will send test commands to the Python side, and process responses. +// * +// * Consumers will call sendCommand in order to execute Python-related code, +// * and will subscribe to the onDataReceived event to wait for the results. +// */ +// export interface ITestServer { +// readonly onDataReceived: Event; +// readonly onRunDataReceived: Event; +// readonly onDiscoveryDataReceived: Event; +// sendCommand( +// options: TestCommandOptions, +// env: EnvironmentVariables, +// runTestIdsPort?: string, +// runInstance?: TestRun, +// testIds?: string[], +// callback?: () => void, +// executionFactory?: IPythonExecutionFactory, +// ): Promise; +// serverReady(): Promise; +// getPort(): number; +// createUUID(cwd: string): string; +// deleteUUID(uuid: string): void; +// triggerRunDataReceivedEvent(data: DataReceivedEvent): void; +// triggerDiscoveryDataReceivedEvent(data: DataReceivedEvent): void; +// } + +/** + * Test item mapping interface used by populateTestTree. + * Contains only the maps needed for building the test tree. + */ +export interface ITestItemMappings { + runIdToVSid: Map; + runIdToTestItem: Map; + vsIdToRunId: Map; +} + +export interface ITestResultResolver extends ITestItemMappings { + detailedCoverageMap: Map; + + resolveDiscovery(payload: DiscoveredTestPayload, token?: CancellationToken): void; + resolveExecution(payload: ExecutionTestPayload | CoveragePayload, runInstance: TestRun): void; + _resolveDiscovery(payload: DiscoveredTestPayload, token?: CancellationToken): void; + _resolveExecution(payload: ExecutionTestPayload, runInstance: TestRun): void; + _resolveCoverage(payload: CoveragePayload, runInstance: TestRun): void; +} +export interface ITestDiscoveryAdapter { + discoverTests( + uri: Uri, + executionFactory: IPythonExecutionFactory, + token?: CancellationToken, + interpreter?: PythonEnvironment, + project?: ProjectAdapter, + ): Promise; +} + +// interface for execution/runner adapter +export interface ITestExecutionAdapter { + runTests( + uri: Uri, + testIds: string[], + profileKind: boolean | TestRunProfileKind | undefined, + runInstance: TestRun, + executionFactory: IPythonExecutionFactory, + debugLauncher?: ITestDebugLauncher, + interpreter?: PythonEnvironment, + project?: ProjectAdapter, + ): Promise; +} + +// Same types as in python_files/unittestadapter/utils.py +export type DiscoveredTestType = 'folder' | 'file' | 'class' | 'function' | 'test'; + +export type DiscoveredTestCommon = { + path: string; + name: string; + // Trailing underscore to avoid collision with the 'type' Python keyword. + type_: DiscoveredTestType; + id_: string; +}; + +export type DiscoveredTestItem = DiscoveredTestCommon & { + lineno: number | string; + runID: string; +}; + +export type DiscoveredTestNode = DiscoveredTestCommon & { + children: (DiscoveredTestNode | DiscoveredTestItem)[]; + lineno?: number | string; +}; + +export type DiscoveredTestPayload = { + cwd: string; + tests?: DiscoveredTestNode; + status: 'success' | 'error'; + error?: string[]; +}; + +export type CoveragePayload = { + coverage: boolean; + cwd: string; + result?: { + [filePathStr: string]: FileCoverageMetrics; + }; + error: string; +}; + +// using camel-case for these types to match the python side +export type FileCoverageMetrics = { + // eslint-disable-next-line camelcase + lines_covered: number[]; + // eslint-disable-next-line camelcase + lines_missed: number[]; + executed_branches: number; + total_branches: number; +}; + +export type ExecutionTestPayload = { + cwd: string; + status: 'success' | 'error'; + result?: { + [testRunID: string]: { + test?: string; + outcome?: string; + message?: string; + traceback?: string; + subtest?: string; + }; + }; + notFound?: string[]; + error: string; +}; diff --git a/src/client/testing/testController/common/utils.ts b/src/client/testing/testController/common/utils.ts new file mode 100644 index 000000000000..9782487d940b --- /dev/null +++ b/src/client/testing/testController/common/utils.ts @@ -0,0 +1,435 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +import * as path from 'path'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as crypto from 'crypto'; +import { CancellationToken, Position, TestController, TestItem, Uri, Range, Disposable } from 'vscode'; +import { Message } from 'vscode-jsonrpc'; +import { traceError, traceInfo, traceLog, traceVerbose } from '../../../logging'; +import { DebugTestTag, ErrorTestItemOptions, RunTestTag } from './testItemUtilities'; +import { + DiscoveredTestItem, + DiscoveredTestNode, + DiscoveredTestPayload, + ExecutionTestPayload, + ITestItemMappings, +} from './types'; +import { Deferred, createDeferred } from '../../../common/utils/async'; +import { createReaderPipe, generateRandomPipeName } from '../../../common/pipes/namedPipes'; +import { EXTENSION_ROOT_DIR } from '../../../constants'; +import { PROJECT_ID_SEPARATOR } from './projectUtils'; + +export function fixLogLinesNoTrailing(content: string): string { + const lines = content.split(/\r?\n/g); + return `${lines.join('\r\n')}`; +} +export function createTestingDeferred(): Deferred { + return createDeferred(); +} + +interface ExecutionResultMessage extends Message { + params: ExecutionTestPayload; +} + +/** + * Retrieves the path to the temporary directory. + * + * On Windows, it returns the default temporary directory. + * On macOS/Linux, it prefers the `XDG_RUNTIME_DIR` environment variable if set, + * otherwise, it falls back to the default temporary directory. + * + * @returns {string} The path to the temporary directory. + */ +function getTempDir(): string { + if (process.platform === 'win32') { + return os.tmpdir(); // Default Windows behavior + } + return process.env.XDG_RUNTIME_DIR || os.tmpdir(); // Prefer XDG_RUNTIME_DIR on macOS/Linux +} + +/** + * Writes an array of test IDs to a temporary file. + * + * @param testIds - The array of test IDs to write. + * @returns A promise that resolves to the file name of the temporary file. + */ +export async function writeTestIdsFile(testIds: string[]): Promise { + // temp file name in format of test-ids-.txt + const randomSuffix = crypto.randomBytes(10).toString('hex'); + const tempName = `test-ids-${randomSuffix}.txt`; + // create temp file + let tempFileName: string; + const tempDir: string = getTempDir(); + try { + traceLog('Attempting to use temp directory for test ids file, file name:', tempName); + tempFileName = path.join(tempDir, tempName); + // attempt access to written file to check permissions + await fs.promises.access(tempDir); + } catch (error) { + // Handle the error when accessing the temp directory + traceError('Error accessing temp directory:', error, ' Attempt to use extension root dir instead'); + // Make new temp directory in extension root dir + const tempDir = path.join(EXTENSION_ROOT_DIR, '.temp'); + await fs.promises.mkdir(tempDir, { recursive: true }); + tempFileName = path.join(EXTENSION_ROOT_DIR, '.temp', tempName); + traceLog('New temp file:', tempFileName); + } + // write test ids to file + await fs.promises.writeFile(tempFileName, testIds.join('\n')); + // return file name + return tempFileName; +} + +export async function startRunResultNamedPipe( + dataReceivedCallback: (payload: ExecutionTestPayload) => void, + deferredTillServerClose: Deferred, + cancellationToken?: CancellationToken, +): Promise { + traceVerbose('Starting Test Result named pipe'); + const pipeName: string = generateRandomPipeName('python-test-results'); + + const reader = await createReaderPipe(pipeName, cancellationToken); + traceVerbose(`Test Results named pipe ${pipeName} connected`); + let disposables: Disposable[] = []; + const disposable = new Disposable(() => { + traceVerbose(`Test Results named pipe ${pipeName} disposed`); + disposables.forEach((d) => d.dispose()); + disposables = []; + deferredTillServerClose.resolve(); + }); + + if (cancellationToken) { + disposables.push( + cancellationToken?.onCancellationRequested(() => { + traceLog(`Test Result named pipe ${pipeName} cancelled`); + disposable.dispose(); + }), + ); + } + disposables.push( + reader, + reader.listen((data: Message) => { + traceVerbose(`Test Result named pipe ${pipeName} received data`); + // if EOT, call decrement connection count (callback) + dataReceivedCallback((data as ExecutionResultMessage).params as ExecutionTestPayload); + }), + reader.onClose(() => { + // this is called once the server close, once per run instance + traceVerbose(`Test Result named pipe ${pipeName} closed. Disposing of listener/s.`); + // dispose of all data listeners and cancelation listeners + disposable.dispose(); + }), + reader.onError((error) => { + traceError(`Test Results named pipe ${pipeName} error:`, error); + }), + ); + + return pipeName; +} + +interface DiscoveryResultMessage extends Message { + params: DiscoveredTestPayload; +} + +export async function startDiscoveryNamedPipe( + callback: (payload: DiscoveredTestPayload) => void, + cancellationToken?: CancellationToken, +): Promise { + traceVerbose('Starting Test Discovery named pipe'); + // const pipeName: string = '/Users/eleanorboyd/testingFiles/inc_dec_example/temp33.txt'; + const pipeName: string = generateRandomPipeName('python-test-discovery'); + const reader = await createReaderPipe(pipeName, cancellationToken); + + traceVerbose(`Test Discovery named pipe ${pipeName} connected`); + let disposables: Disposable[] = []; + const disposable = new Disposable(() => { + traceVerbose(`Test Discovery named pipe ${pipeName} disposed`); + disposables.forEach((d) => d.dispose()); + disposables = []; + }); + + if (cancellationToken) { + disposables.push( + cancellationToken.onCancellationRequested(() => { + traceVerbose(`Test Discovery named pipe ${pipeName} cancelled`); + disposable.dispose(); + }), + ); + } + + disposables.push( + reader, + reader.listen((data: Message) => { + traceVerbose(`Test Discovery named pipe ${pipeName} received data`); + callback((data as DiscoveryResultMessage).params as DiscoveredTestPayload); + }), + reader.onClose(() => { + traceVerbose(`Test Discovery named pipe ${pipeName} closed`); + disposable.dispose(); + }), + reader.onError((error) => { + traceError(`Test Discovery named pipe ${pipeName} error:`, error); + }), + ); + return pipeName; +} + +/** + * Extracts the missing module name from a ModuleNotFoundError or ImportError message. + * @param message The error message to parse + * @returns The module name if found, undefined otherwise + */ +function extractMissingModuleName(message: string): string | undefined { + // Match patterns like: + // - No module named 'requests' + // - No module named "requests" + // - ModuleNotFoundError: No module named 'requests' + // - ImportError: No module named requests + const patterns = [/No module named ['"]([^'"]+)['"]/, /No module named (\S+)/]; + + for (const pattern of patterns) { + const match = message.match(pattern); + if (match) { + return match[1]; + } + } + return undefined; +} + +export function buildErrorNodeOptions( + uri: Uri, + message: string, + testType: string, + projectName?: string, +): ErrorTestItemOptions { + let labelText = testType === 'pytest' ? 'pytest Discovery Error' : 'Unittest Discovery Error'; + let errorMessage = message; + + // Check for missing module errors and provide specific messaging + const missingModule = extractMissingModuleName(message); + if (missingModule) { + labelText = `Missing Module: ${missingModule}`; + errorMessage = `The module '${missingModule}' is not installed in the selected Python environment. Please install it to enable test discovery.`; + } + + // Use project name for label if available (project-based testing), otherwise use folder name + const displayName = projectName ?? path.basename(uri.fsPath); + + return { + id: `DiscoveryError:${uri.fsPath}`, + label: `${labelText} [${displayName}]`, + error: errorMessage, + }; +} + +export function populateTestTree( + testController: TestController, + testTreeData: DiscoveredTestNode, + testRoot: TestItem | undefined, + testItemMappings: ITestItemMappings, + token?: CancellationToken, + projectId?: string, + projectName?: string, +): void { + // If testRoot is undefined, use the info of the root item of testTreeData to create a test item, and append it to the test controller. + if (!testRoot) { + // Create project-scoped ID if projectId is provided + const rootId = projectId ? `${projectId}${PROJECT_ID_SEPARATOR}${testTreeData.path}` : testTreeData.path; + // Use "Project: {name}" label for project-based testing, otherwise use folder name + const rootLabel = projectName ? `Project: ${projectName}` : testTreeData.name; + testRoot = testController.createTestItem(rootId, rootLabel, Uri.file(testTreeData.path)); + + testRoot.canResolveChildren = true; + testRoot.tags = [RunTestTag, DebugTestTag]; + + testController.items.add(testRoot); + } + + // Recursively populate the tree with test data. + testTreeData.children.forEach((child) => { + if (!token?.isCancellationRequested) { + if (isTestItem(child)) { + // Create project-scoped vsId + const vsId = projectId ? `${projectId}${PROJECT_ID_SEPARATOR}${child.id_}` : child.id_; + const testItem = testController.createTestItem(vsId, child.name, Uri.file(child.path)); + testItem.tags = [RunTestTag, DebugTestTag]; + + let range: Range | undefined; + if (child.lineno) { + if (Number(child.lineno) === 0) { + range = new Range(new Position(0, 0), new Position(0, 0)); + } else { + range = new Range( + new Position(Number(child.lineno) - 1, 0), + new Position(Number(child.lineno), 0), + ); + } + } + testItem.canResolveChildren = false; + testItem.range = range; + testItem.tags = [RunTestTag, DebugTestTag]; + + testRoot!.children.add(testItem); + // add to our map - use runID as key, vsId as value + testItemMappings.runIdToTestItem.set(child.runID, testItem); + testItemMappings.runIdToVSid.set(child.runID, vsId); + testItemMappings.vsIdToRunId.set(vsId, child.runID); + } else { + // Use project-scoped ID for non-test nodes and look up within the current root + const nodeId = projectId ? `${projectId}${PROJECT_ID_SEPARATOR}${child.id_}` : child.id_; + let node = testRoot!.children.get(nodeId); + + if (!node) { + node = testController.createTestItem(nodeId, child.name, Uri.file(child.path)); + + node.canResolveChildren = true; + node.tags = [RunTestTag, DebugTestTag]; + + // Set range for class nodes (and other nodes) if lineno is available + let range: Range | undefined; + if ('lineno' in child && child.lineno) { + if (Number(child.lineno) === 0) { + range = new Range(new Position(0, 0), new Position(0, 0)); + } else { + range = new Range( + new Position(Number(child.lineno) - 1, 0), + new Position(Number(child.lineno), 0), + ); + } + node.range = range; + } + + testRoot!.children.add(node); + } + populateTestTree(testController, child, node, testItemMappings, token, projectId, projectName); + } + } + }); +} + +function isTestItem(test: DiscoveredTestNode | DiscoveredTestItem): test is DiscoveredTestItem { + return test.type_ === 'test'; +} + +export function createExecutionErrorPayload( + code: number | null, + signal: NodeJS.Signals | null, + testIds: string[], + cwd: string, +): ExecutionTestPayload { + const etp: ExecutionTestPayload = { + cwd, + status: 'error', + error: `Test run failed, the python test process was terminated before it could exit on its own for workspace ${cwd}`, + result: {}, + }; + // add error result for each attempted test. + for (let i = 0; i < testIds.length; i = i + 1) { + const test = testIds[i]; + etp.result![test] = { + test, + outcome: 'error', + message: ` \n The python test process was terminated before it could exit on its own, the process errored with: Code: ${code}, Signal: ${signal}`, + }; + } + return etp; +} + +export function createDiscoveryErrorPayload( + code: number | null, + signal: NodeJS.Signals | null, + cwd: string, +): DiscoveredTestPayload { + return { + cwd, + status: 'error', + error: [ + ` \n The python test process was terminated before it could exit on its own, the process errored with: Code: ${code}, Signal: ${signal} for workspace ${cwd}`, + ], + }; +} + +/** + * Splits a test name into its parent test name and subtest unique section. + * + * @param testName The full test name string. + * @returns A tuple where the first item is the parent test name and the second item is the subtest section or `testName` if no subtest section exists. + */ +export function splitTestNameWithRegex(testName: string): [string, string] { + // If a match is found, return the parent test name and the subtest (whichever was captured between parenthesis or square brackets). + // Otherwise, return the entire testName for the parent and entire testName for the subtest. + const regex = /^(.*?) ([\[(].*[\])])$/; + const match = testName.match(regex); + if (match) { + return [match[1].trim(), match[2] || match[3] || testName]; + } + return [testName, testName]; +} + +/** + * Takes a list of arguments and adds an key-value pair to the list if the key doesn't already exist. Searches each element + * in the array for the key to see if it is contained within the element. + * @param args list of arguments to search + * @param argToAdd argument to add if it doesn't already exist + * @returns the list of arguments with the key-value pair added if it didn't already exist + */ +export function addValueIfKeyNotExist(args: string[], key: string, value: string | null): string[] { + for (const arg of args) { + if (arg.includes(key)) { + traceInfo(`arg: ${key} already exists in args, not adding.`); + return args; + } + } + if (value) { + args.push(`${key}=${value}`); + } else { + args.push(`${key}`); + } + return args; +} + +/** + * Checks if a key exists in a list of arguments. Searches each element in the array + * for the key to see if it is contained within the element. + * @param args list of arguments to search + * @param key string to search for + * @returns true if the key exists in the list of arguments, false otherwise + */ +export function argKeyExists(args: string[], key: string): boolean { + for (const arg of args) { + if (arg.includes(key)) { + return true; + } + } + return false; +} + +/** + * Checks recursively if any parent directories of the given path are symbolic links. + * @param {string} currentPath - The path to start checking from. + * @returns {Promise} - Returns true if any parent directory is a symlink, otherwise false. + */ +export async function hasSymlinkParent(currentPath: string): Promise { + try { + // Resolve the path to an absolute path + const absolutePath = path.resolve(currentPath); + // Get the parent directory + const parentDirectory = path.dirname(absolutePath); + // Check if the current directory is the root directory + if (parentDirectory === absolutePath) { + return false; + } + // Check if the parent directory is a symlink + const stats = await fs.promises.lstat(parentDirectory); + if (stats.isSymbolicLink()) { + traceLog(`Symlink found at: ${parentDirectory}`); + return true; + } + // Recurse up the directory tree + return await hasSymlinkParent(parentDirectory); + } catch (error) { + traceError('Error checking symlinks:', error); + return false; + } +} diff --git a/src/client/testing/testController/controller.ts b/src/client/testing/testController/controller.ts new file mode 100644 index 000000000000..04de209c171d --- /dev/null +++ b/src/client/testing/testController/controller.ts @@ -0,0 +1,1004 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable, named } from 'inversify'; +import { uniq } from 'lodash'; +import * as minimatch from 'minimatch'; +import { + CancellationToken, + TestController, + TestItem, + TestRunRequest, + tests, + WorkspaceFolder, + RelativePattern, + TestRunProfileKind, + CancellationTokenSource, + Uri, + EventEmitter, + TextDocument, + FileCoverageDetail, + TestRun, + MarkdownString, +} from 'vscode'; +import { IExtensionSingleActivationService } from '../../activation/types'; +import { ICommandManager, IWorkspaceService } from '../../common/application/types'; +import * as constants from '../../common/constants'; +import { IPythonExecutionFactory } from '../../common/process/types'; +import { IConfigurationService, IDisposableRegistry, Resource } from '../../common/types'; +import { DelayedTrigger, IDelayedTrigger } from '../../common/utils/delayTrigger'; +import { noop } from '../../common/utils/misc'; +import { IInterpreterService } from '../../interpreter/contracts'; +import { traceError, traceInfo, traceVerbose } from '../../logging'; +import { IEventNamePropertyMapping, sendTelemetryEvent } from '../../telemetry'; +import { EventName } from '../../telemetry/constants'; +import { PYTEST_PROVIDER, UNITTEST_PROVIDER } from '../common/constants'; +import { TestProvider } from '../types'; +import { createErrorTestItem, DebugTestTag, getNodeByUri, RunTestTag } from './common/testItemUtilities'; +import { buildErrorNodeOptions } from './common/utils'; +import { ITestController, ITestFrameworkController, TestRefreshOptions } from './common/types'; +import { WorkspaceTestAdapter } from './workspaceTestAdapter'; +import { ITestDebugLauncher } from '../common/types'; +import { PythonResultResolver } from './common/resultResolver'; +import { onDidSaveTextDocument } from '../../common/vscodeApis/workspaceApis'; +import { IEnvironmentVariablesProvider } from '../../common/variables/types'; +import { ProjectAdapter } from './common/projectAdapter'; +import { TestProjectRegistry } from './common/testProjectRegistry'; +import { createTestAdapters, getProjectId } from './common/projectUtils'; +import { executeTestsForProjects } from './common/projectTestExecution'; +import { useEnvExtension, getEnvExtApi } from '../../envExt/api.internal'; +import { DidChangePythonProjectsEventArgs, PythonProject } from '../../envExt/types'; + +// Types gymnastics to make sure that sendTriggerTelemetry only accepts the correct types. +type EventPropertyType = IEventNamePropertyMapping[EventName.UNITTEST_DISCOVERY_TRIGGER]; +type TriggerKeyType = keyof EventPropertyType; +type TriggerType = EventPropertyType[TriggerKeyType]; + +@injectable() +export class PythonTestController implements ITestController, IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; + + // Legacy: Single workspace test adapter per workspace (backward compatibility) + private readonly testAdapters: Map = new Map(); + + // Registry for multi-project testing (one registry instance manages all projects across workspaces) + private readonly projectRegistry: TestProjectRegistry; + + private readonly triggerTypes: TriggerType[] = []; + + private readonly testController: TestController; + + private readonly refreshData: IDelayedTrigger; + + private refreshCancellation: CancellationTokenSource; + + private readonly refreshingCompletedEvent: EventEmitter = new EventEmitter(); + + private readonly refreshingStartedEvent: EventEmitter = new EventEmitter(); + + private readonly runWithoutConfigurationEvent: EventEmitter = new EventEmitter< + WorkspaceFolder[] + >(); + + public readonly onRefreshingCompleted = this.refreshingCompletedEvent.event; + + public readonly onRefreshingStarted = this.refreshingStartedEvent.event; + + public readonly onRunWithoutConfiguration = this.runWithoutConfigurationEvent.event; + + private sendTestDisabledTelemetry = true; + + constructor( + @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, + @inject(IConfigurationService) private readonly configSettings: IConfigurationService, + @inject(ITestFrameworkController) @named(PYTEST_PROVIDER) private readonly pytest: ITestFrameworkController, + @inject(ITestFrameworkController) @named(UNITTEST_PROVIDER) private readonly unittest: ITestFrameworkController, + @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry, + @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, + @inject(ICommandManager) private readonly commandManager: ICommandManager, + @inject(IPythonExecutionFactory) private readonly pythonExecFactory: IPythonExecutionFactory, + @inject(ITestDebugLauncher) private readonly debugLauncher: ITestDebugLauncher, + @inject(IEnvironmentVariablesProvider) private readonly envVarsService: IEnvironmentVariablesProvider, + ) { + this.refreshCancellation = new CancellationTokenSource(); + + this.testController = tests.createTestController('python-tests', 'Python Tests'); + this.disposables.push(this.testController); + + // Initialize project registry for multi-project testing support + this.projectRegistry = new TestProjectRegistry( + this.testController, + this.configSettings, + this.interpreterService, + this.envVarsService, + ); + + const delayTrigger = new DelayedTrigger( + (uri: Uri, invalidate: boolean) => { + this.refreshTestDataInternal(uri); + if (invalidate) { + this.invalidateTests(uri); + } + }, + 250, // Delay running the refresh by 250 ms + 'Refresh Test Data', + ); + this.disposables.push(delayTrigger); + this.refreshData = delayTrigger; + + this.disposables.push( + this.testController.createRunProfile( + 'Run Tests', + TestRunProfileKind.Run, + this.runTests.bind(this), + true, + RunTestTag, + ), + this.testController.createRunProfile( + 'Debug Tests', + TestRunProfileKind.Debug, + this.runTests.bind(this), + true, + DebugTestTag, + ), + this.testController.createRunProfile( + 'Coverage Tests', + TestRunProfileKind.Coverage, + this.runTests.bind(this), + true, + RunTestTag, + ), + ); + + this.testController.resolveHandler = this.resolveChildren.bind(this); + this.testController.refreshHandler = (token: CancellationToken) => { + this.disposables.push( + token.onCancellationRequested(() => { + traceVerbose('Testing: Stop refreshing triggered'); + sendTelemetryEvent(EventName.UNITTEST_DISCOVERING_STOP); + this.stopRefreshing(); + }), + ); + + traceVerbose('Testing: Manually triggered test refresh'); + sendTelemetryEvent(EventName.UNITTEST_DISCOVERY_TRIGGER, undefined, { + trigger: constants.CommandSource.commandPalette, + }); + return this.refreshTestData(undefined, { forceRefresh: true }); + }; + } + + /** + * Determines the test provider (pytest or unittest) based on workspace settings. + */ + private getTestProvider(workspaceUri: Uri): TestProvider { + const settings = this.configSettings.getSettings(workspaceUri); + return settings.testing.unittestEnabled ? UNITTEST_PROVIDER : PYTEST_PROVIDER; + } + + /** + * Sets up file watchers for test discovery triggers. + */ + private setupFileWatchers(workspace: WorkspaceFolder): void { + const settings = this.configSettings.getSettings(workspace.uri); + if (settings.testing.autoTestDiscoverOnSaveEnabled) { + traceVerbose(`Testing: Setting up watcher for ${workspace.uri.fsPath}`); + this.watchForSettingsChanges(workspace); + this.watchForTestContentChangeOnSave(); + } + } + + /** + * Activates the test controller for all workspaces. + * + * Two activation modes: + * 1. **Project-based mode** (when Python Environments API available): + * 2. **Legacy mode** (fallback): + * + * Uses `Promise.allSettled` for resilient multi-workspace activation: + */ + public async activate(): Promise { + const workspaces: readonly WorkspaceFolder[] = this.workspaceService.workspaceFolders || []; + + // PROJECT-BASED MODE: Uses Python Environments API to discover projects + // Each project becomes its own test tree root with its own Python environment + if (useEnvExtension()) { + traceInfo('[test-by-project] Activating project-based testing mode'); + + // Discover projects in parallel across all workspaces + // Promise.allSettled ensures one workspace failure doesn't block others + const results = await Promise.allSettled( + Array.from(workspaces).map(async (workspace) => { + // Queries Python Environments API and creates ProjectAdapter instances + const projects = await this.projectRegistry.discoverAndRegisterProjects(workspace.uri); + return { workspace, projectCount: projects.length }; + }), + ); + + // Process results: successful workspaces get file watchers, failed ones fall back to legacy + results.forEach((result, index) => { + const workspace = workspaces[index]; + if (result.status === 'fulfilled') { + traceInfo( + `[test-by-project] Activated ${result.value.projectCount} project(s) for ${workspace.uri.fsPath}`, + ); + this.setupFileWatchers(workspace); + } else { + // Graceful degradation: if project discovery fails, use legacy single-adapter mode + traceError(`[test-by-project] Failed for ${workspace.uri.fsPath}:`, result.reason); + this.activateLegacyWorkspace(workspace); + } + }); + // Subscribe to project changes to update test tree when projects are added/removed + await this.subscribeToProjectChanges(); + return; + } + + // LEGACY MODE: Single WorkspaceTestAdapter per workspace (backward compatibility) + workspaces.forEach((workspace) => { + this.activateLegacyWorkspace(workspace); + }); + } + + /** + * Subscribes to Python project changes from the Python Environments API. + * When projects are added or removed, updates the test tree accordingly. + */ + private async subscribeToProjectChanges(): Promise { + try { + const envExtApi = await getEnvExtApi(); + this.disposables.push( + envExtApi.onDidChangePythonProjects((event: DidChangePythonProjectsEventArgs) => { + this.handleProjectChanges(event).catch((error) => { + traceError('[test-by-project] Error handling project changes:', error); + }); + }), + ); + traceInfo('[test-by-project] Subscribed to Python project changes'); + } catch (error) { + traceError('[test-by-project] Failed to subscribe to project changes:', error); + } + } + + /** + * Handles changes to Python projects (added or removed). + * Cleans up stale test items and re-discovers projects and tests for affected workspaces. + */ + private async handleProjectChanges(event: DidChangePythonProjectsEventArgs): Promise { + const { added, removed } = event; + + if (added.length === 0 && removed.length === 0) { + return; + } + + traceInfo(`[test-by-project] Project changes detected: ${added.length} added, ${removed.length} removed`); + + // Find all affected workspaces + const affectedWorkspaces = new Set(); + + const findWorkspace = (project: PythonProject): WorkspaceFolder | undefined => { + return this.workspaceService.getWorkspaceFolder(project.uri); + }; + + for (const project of [...added, ...removed]) { + const workspace = findWorkspace(project); + if (workspace) { + affectedWorkspaces.add(workspace); + } + } + + // For each affected workspace, clean up and re-discover + for (const workspace of affectedWorkspaces) { + traceInfo(`[test-by-project] Re-discovering projects for workspace: ${workspace.uri.fsPath}`); + + // Get the current projects before clearing to know what to clean up + const existingProjects = this.projectRegistry.getProjectsArray(workspace.uri); + + // Remove ALL test items for the affected workspace's projects + // This ensures no stale items remain from deleted/changed projects + this.removeWorkspaceProjectTestItems(workspace.uri, existingProjects); + + // Also explicitly remove test items for removed projects (in case they weren't tracked) + for (const project of removed) { + const projectWorkspace = findWorkspace(project); + if (projectWorkspace?.uri.toString() === workspace.uri.toString()) { + this.removeProjectTestItems(project); + } + } + + // Re-discover all projects and tests for the workspace in a single pass. + // discoverAllProjectsInWorkspace is responsible for clearing/re-registering + // projects and performing test discovery for the workspace. + await this.discoverAllProjectsInWorkspace(workspace.uri); + } + } + + /** + * Removes all test items associated with projects in a workspace. + * Used to clean up stale items before re-discovery. + */ + private removeWorkspaceProjectTestItems(workspaceUri: Uri, projects: ProjectAdapter[]): void { + const idsToRemove: string[] = []; + + // Collect IDs of test items belonging to any project in this workspace + for (const project of projects) { + const projectIdPrefix = getProjectId(project.projectUri); + const projectFsPath = project.projectUri.fsPath; + + this.testController.items.forEach((item) => { + // Match by project ID prefix (e.g., "file:///path@@vsc@@...") + if (item.id.startsWith(projectIdPrefix)) { + idsToRemove.push(item.id); + } + // Match by fsPath in ID (legacy items might use path directly) + else if (item.id.includes(projectFsPath)) { + idsToRemove.push(item.id); + } + // Match by item URI being within project directory + else if (item.uri && item.uri.fsPath.startsWith(projectFsPath)) { + idsToRemove.push(item.id); + } + }); + } + + // Also remove any items whose URI is within the workspace (catch-all for edge cases) + this.testController.items.forEach((item) => { + if ( + item.uri && + this.workspaceService.getWorkspaceFolder(item.uri)?.uri.toString() === workspaceUri.toString() + ) { + if (!idsToRemove.includes(item.id)) { + idsToRemove.push(item.id); + } + } + }); + + // Remove all collected items + for (const id of idsToRemove) { + this.testController.items.delete(id); + } + + traceInfo( + `[test-by-project] Cleaned up ${idsToRemove.length} test items for workspace: ${workspaceUri.fsPath}`, + ); + } + + /** + * Removes test items associated with a specific project from the test controller. + * Matches items by project ID prefix, fsPath, or URI. + */ + private removeProjectTestItems(project: PythonProject): void { + const projectId = getProjectId(project.uri); + const projectFsPath = project.uri.fsPath; + const idsToRemove: string[] = []; + + // Find all root items that belong to this project + this.testController.items.forEach((item) => { + // Match by project ID prefix (e.g., "file:///path@@vsc@@...") + if (item.id.startsWith(projectId)) { + idsToRemove.push(item.id); + } + // Match by fsPath in ID (items might use path directly without URI prefix) + else if (item.id.startsWith(projectFsPath) || item.id.includes(projectFsPath)) { + idsToRemove.push(item.id); + } + // Match by item URI being within project directory + else if (item.uri && item.uri.fsPath.startsWith(projectFsPath)) { + idsToRemove.push(item.id); + } + }); + + for (const id of idsToRemove) { + this.testController.items.delete(id); + traceVerbose(`[test-by-project] Removed test item: ${id}`); + } + + if (idsToRemove.length > 0) { + traceInfo(`[test-by-project] Removed ${idsToRemove.length} test items for project: ${project.name}`); + } + } + + /** + * Activates testing for a workspace using the legacy single-adapter approach. + * Used for backward compatibility when project-based testing is disabled or unavailable. + */ + private activateLegacyWorkspace(workspace: WorkspaceFolder): void { + const testProvider = this.getTestProvider(workspace.uri); + const resultResolver = new PythonResultResolver(this.testController, testProvider, workspace.uri); + const { discoveryAdapter, executionAdapter } = createTestAdapters( + testProvider, + resultResolver, + this.configSettings, + this.envVarsService, + ); + + const workspaceTestAdapter = new WorkspaceTestAdapter( + testProvider, + discoveryAdapter, + executionAdapter, + workspace.uri, + resultResolver, + ); + + this.testAdapters.set(workspace.uri, workspaceTestAdapter); + this.setupFileWatchers(workspace); + } + + public refreshTestData(uri?: Resource, options?: TestRefreshOptions): Promise { + if (options?.forceRefresh) { + if (uri === undefined) { + // This is a special case where we want everything to be re-discovered. + traceVerbose('Testing: Clearing all discovered tests'); + this.testController.items.forEach((item) => { + const ids: string[] = []; + item.children.forEach((child) => ids.push(child.id)); + ids.forEach((id) => item.children.delete(id)); + }); + + traceVerbose('Testing: Forcing test data refresh'); + return this.refreshTestDataInternal(undefined); + } + + traceVerbose('Testing: Forcing test data refresh'); + return this.refreshTestDataInternal(uri); + } + + this.refreshData.trigger(uri, false); + return Promise.resolve(); + } + + public stopRefreshing(): void { + this.refreshCancellation.cancel(); + this.refreshCancellation.dispose(); + this.refreshCancellation = new CancellationTokenSource(); + } + + public clearTestController(): void { + const ids: string[] = []; + this.testController.items.forEach((item) => ids.push(item.id)); + ids.forEach((id) => this.testController.items.delete(id)); + } + + private async refreshTestDataInternal(uri?: Resource): Promise { + this.refreshingStartedEvent.fire(); + try { + if (uri) { + await this.discoverTestsInWorkspace(uri); + } else { + await this.discoverTestsInAllWorkspaces(); + } + } finally { + this.refreshingCompletedEvent.fire(); + } + } + + /** + * Discovers tests for a single workspace. + * + * **Discovery flow:** + * 1. If the workspace has registered projects (via Python Environments API), + * uses project-based discovery: each project is discovered independently + * with its own Python environment and test adapters. + * 2. Otherwise, falls back to legacy mode: a single WorkspaceTestAdapter + * discovers all tests in the workspace using the active interpreter. + * + * In project-based mode, the test tree will have separate roots for each project. + * In legacy mode, the workspace folder is the single test tree root. + */ + private async discoverTestsInWorkspace(uri: Uri): Promise { + const workspace = this.workspaceService.getWorkspaceFolder(uri); + if (!workspace?.uri) { + traceError('Unable to find workspace for given file'); + return; + } + + const settings = this.configSettings.getSettings(uri); + traceVerbose(`Discover tests for workspace name: ${workspace.name} - uri: ${uri.fsPath}`); + + // Ensure we send test telemetry if it gets disabled again + this.sendTestDisabledTelemetry = true; + + // Check if any test framework is enabled BEFORE project-based discovery + // This ensures the config screen stays visible when testing is disabled + if (!settings.testing.pytestEnabled && !settings.testing.unittestEnabled) { + await this.handleNoTestProviderEnabled(workspace); + return; + } + + // Use project-based discovery if applicable (only reached if testing is enabled) + if (this.projectRegistry.hasProjects(workspace.uri)) { + await this.discoverAllProjectsInWorkspace(workspace.uri); + return; + } + + // Legacy mode: Single workspace adapter + if (settings.testing.pytestEnabled) { + await this.discoverWorkspaceTestsLegacy(workspace.uri, 'pytest'); + } else if (settings.testing.unittestEnabled) { + await this.discoverWorkspaceTestsLegacy(workspace.uri, 'unittest'); + } + } + + /** + * Discovers tests for all projects within a workspace (project-based mode). + * Re-discovers projects from the Python Environments API before running test discovery. + * This ensures the test tree stays in sync with project changes. + */ + private async discoverAllProjectsInWorkspace(workspaceUri: Uri): Promise { + // Defensive check: ensure testing is enabled (should be checked by caller, but be safe) + const settings = this.configSettings.getSettings(workspaceUri); + if (!settings.testing.pytestEnabled && !settings.testing.unittestEnabled) { + traceVerbose('[test-by-project] Skipping discovery - no test framework enabled'); + return; + } + + // Get existing projects before re-discovery for cleanup + const existingProjects = this.projectRegistry.getProjectsArray(workspaceUri); + + // Clean up all existing test items for this workspace + // This ensures stale items from deleted/changed projects are removed + this.removeWorkspaceProjectTestItems(workspaceUri, existingProjects); + + // Re-discover projects from Python Environments API + // This picks up any added/removed projects since last discovery + this.projectRegistry.clearWorkspace(workspaceUri); + const projects = await this.projectRegistry.discoverAndRegisterProjects(workspaceUri); + + if (projects.length === 0) { + traceError(`[test-by-project] No projects found for workspace: ${workspaceUri.fsPath}`); + return; + } + + traceInfo(`[test-by-project] Starting discovery for ${projects.length} project(s) in workspace`); + + try { + // Configure nested project exclusions before discovery + this.projectRegistry.configureNestedProjectIgnores(workspaceUri); + + // Track completion for progress logging + const projectsCompleted = new Set(); + + // Run discovery for all projects in parallel + await Promise.all(projects.map((project) => this.discoverTestsForProject(project, projectsCompleted))); + + traceInfo( + `[test-by-project] Discovery complete: ${projectsCompleted.size}/${projects.length} projects completed`, + ); + } catch (error) { + traceError(`[test-by-project] Discovery failed for workspace ${workspaceUri.fsPath}:`, error); + } + } + + /** + * Discovers tests for a single project (project-based mode). + * Creates test tree items rooted at the project's directory. + */ + private async discoverTestsForProject(project: ProjectAdapter, projectsCompleted: Set): Promise { + try { + traceInfo(`[test-by-project] Discovering tests for project: ${project.projectName}`); + project.isDiscovering = true; + + // In project-based mode, the discovery adapter uses the Python Environments API + // to get the environment directly, so we don't need to pass the interpreter + await project.discoveryAdapter.discoverTests( + project.projectUri, + this.pythonExecFactory, + this.refreshCancellation.token, + undefined, // Interpreter not needed; adapter uses Python Environments API + project, + ); + + // Mark project as completed (use URI string as unique key) + projectsCompleted.add(project.projectUri.toString()); + traceInfo(`[test-by-project] Project ${project.projectName} discovery completed`); + } catch (error) { + traceError(`[test-by-project] Discovery failed for project ${project.projectName}:`, error); + // Individual project failures don't block others + projectsCompleted.add(project.projectUri.toString()); // Still mark as completed + } finally { + project.isDiscovering = false; + } + } + + /** + * Discovers tests across all workspace folders. + * Iterates each workspace and triggers discovery. + */ + private async discoverTestsInAllWorkspaces(): Promise { + traceVerbose('Testing: Refreshing all test data'); + const workspaces: readonly WorkspaceFolder[] = this.workspaceService.workspaceFolders || []; + + await Promise.all( + workspaces.map(async (workspace) => { + // In project-based mode, each project has its own environment, + // so we don't require a global active interpreter + if (!useEnvExtension()) { + if (!(await this.interpreterService.getActiveInterpreter(workspace.uri))) { + this.commandManager + .executeCommand(constants.Commands.TriggerEnvironmentSelection, workspace.uri) + .then(noop, noop); + return; + } + } + await this.discoverTestsInWorkspace(workspace.uri); + }), + ); + } + + /** + * Discovers tests for a workspace using legacy single-adapter mode. + */ + private async discoverWorkspaceTestsLegacy(workspaceUri: Uri, expectedProvider: TestProvider): Promise { + const testAdapter = this.testAdapters.get(workspaceUri); + + if (!testAdapter) { + traceError('Unable to find test adapter for workspace.'); + return; + } + + const actualProvider = testAdapter.getTestProvider(); + if (actualProvider !== expectedProvider) { + traceError(`Test provider in adapter is not ${expectedProvider}. Please reload window.`); + this.surfaceErrorNode( + workspaceUri, + 'Test provider types are not aligned, please reload your VS Code window.', + expectedProvider, + ); + return; + } + + await testAdapter.discoverTests( + this.testController, + this.pythonExecFactory, + this.refreshCancellation.token, + await this.interpreterService.getActiveInterpreter(workspaceUri), + ); + } + + /** + * Handles the case when no test provider is enabled. + * Sends telemetry and removes test items for the workspace from the tree. + */ + private async handleNoTestProviderEnabled(workspace: WorkspaceFolder): Promise { + if (this.sendTestDisabledTelemetry) { + this.sendTestDisabledTelemetry = false; + sendTelemetryEvent(EventName.UNITTEST_DISABLED); + } + + this.removeTestItemsForWorkspace(workspace); + } + + /** + * Removes all test items belonging to a specific workspace from the test controller. + * This is used when test discovery is disabled for a workspace. + */ + private removeTestItemsForWorkspace(workspace: WorkspaceFolder): void { + const itemsToDelete: string[] = []; + + this.testController.items.forEach((testItem: TestItem) => { + const itemWorkspace = this.workspaceService.getWorkspaceFolder(testItem.uri); + if (itemWorkspace?.uri.fsPath === workspace.uri.fsPath) { + itemsToDelete.push(testItem.id); + } + }); + + itemsToDelete.forEach((id) => this.testController.items.delete(id)); + } + + private async resolveChildren(item: TestItem | undefined): Promise { + if (item) { + traceVerbose(`Testing: Resolving item ${item.id}`); + const settings = this.configSettings.getSettings(item.uri); + if (settings.testing.pytestEnabled) { + return this.pytest.resolveChildren(this.testController, item, this.refreshCancellation.token); + } + if (settings.testing.unittestEnabled) { + return this.unittest.resolveChildren(this.testController, item, this.refreshCancellation.token); + } + } else { + traceVerbose('Testing: Refreshing all test data'); + this.sendTriggerTelemetry('auto'); + const workspaces: readonly WorkspaceFolder[] = this.workspaceService.workspaceFolders || []; + await Promise.all( + workspaces.map(async (workspace) => { + // In project-based mode, each project has its own environment, + // so we don't require a global active interpreter + if (!useEnvExtension()) { + if (!(await this.interpreterService.getActiveInterpreter(workspace.uri))) { + traceError('Cannot trigger test discovery as a valid interpreter is not selected'); + return; + } + } + await this.refreshTestDataInternal(workspace.uri); + }), + ); + } + return Promise.resolve(); + } + + private async runTests(request: TestRunRequest, token: CancellationToken): Promise { + const workspaces = this.getWorkspacesForTestRun(request); + const runInstance = this.testController.createTestRun( + request, + `Running Tests for Workspace(s): ${workspaces.map((w) => w.uri.fsPath).join(';')}`, + true, + ); + + const dispose = token.onCancellationRequested(() => { + runInstance.appendOutput(`\nRun instance cancelled.\r\n`); + runInstance.end(); + }); + + const unconfiguredWorkspaces: WorkspaceFolder[] = []; + + try { + await Promise.all( + workspaces.map((workspace) => + this.runTestsForWorkspace(workspace, request, runInstance, token, unconfiguredWorkspaces), + ), + ); + } finally { + traceVerbose('Finished running tests, ending runInstance.'); + runInstance.appendOutput(`Finished running tests!\r\n`); + runInstance.end(); + dispose.dispose(); + if (unconfiguredWorkspaces.length > 0) { + this.runWithoutConfigurationEvent.fire(unconfiguredWorkspaces); + } + } + } + + /** + * Gets the list of workspaces to run tests for based on the test run request. + */ + private getWorkspacesForTestRun(request: TestRunRequest): WorkspaceFolder[] { + if (request.include) { + const workspaces: WorkspaceFolder[] = []; + uniq(request.include.map((r) => this.workspaceService.getWorkspaceFolder(r.uri))).forEach((w) => { + if (w) { + workspaces.push(w); + } + }); + return workspaces; + } + return Array.from(this.workspaceService.workspaceFolders || []); + } + + /** + * Runs tests for a single workspace. + */ + private async runTestsForWorkspace( + workspace: WorkspaceFolder, + request: TestRunRequest, + runInstance: TestRun, + token: CancellationToken, + unconfiguredWorkspaces: WorkspaceFolder[], + ): Promise { + if (!(await this.interpreterService.getActiveInterpreter(workspace.uri))) { + this.commandManager + .executeCommand(constants.Commands.TriggerEnvironmentSelection, workspace.uri) + .then(noop, noop); + return; + } + + const testItems = this.getTestItemsForWorkspace(workspace, request); + const settings = this.configSettings.getSettings(workspace.uri); + + if (testItems.length === 0) { + if (!settings.testing.pytestEnabled && !settings.testing.unittestEnabled) { + unconfiguredWorkspaces.push(workspace); + } + return; + } + + // Check if we're in project-based mode and should use project-specific execution + if (this.projectRegistry.hasProjects(workspace.uri)) { + const projects = this.projectRegistry.getProjectsArray(workspace.uri); + await executeTestsForProjects(projects, testItems, runInstance, request, token, { + projectRegistry: this.projectRegistry, + pythonExecFactory: this.pythonExecFactory, + debugLauncher: this.debugLauncher, + }); + return; + } + + // For unittest (or pytest when not in project mode), use the legacy WorkspaceTestAdapter. + // In project mode, legacy adapters may not be initialized, so create one on demand. + let testAdapter = this.testAdapters.get(workspace.uri); + if (!testAdapter) { + // Initialize legacy adapter on demand (needed for unittest in project mode) + this.activateLegacyWorkspace(workspace); + testAdapter = this.testAdapters.get(workspace.uri); + } + + if (!testAdapter) { + traceError(`[test] No test adapter available for workspace: ${workspace.uri.fsPath}`); + return; + } + + this.setupCoverageIfNeeded(request, testAdapter); + + if (settings.testing.pytestEnabled) { + await this.executeTestsForProvider( + workspace, + testAdapter, + testItems, + runInstance, + request, + token, + 'pytest', + ); + } else if (settings.testing.unittestEnabled) { + await this.executeTestsForProvider( + workspace, + testAdapter, + testItems, + runInstance, + request, + token, + 'unittest', + ); + } else { + unconfiguredWorkspaces.push(workspace); + } + } + + /** + * Gets test items that belong to a specific workspace from the run request. + */ + private getTestItemsForWorkspace(workspace: WorkspaceFolder, request: TestRunRequest): TestItem[] { + const testItems: TestItem[] = []; + // If the run request includes test items then collect only items that belong to + // `workspace`. If there are no items in the run request then just run the `workspace` + // root test node. Include will be `undefined` in the "run all" scenario. + (request.include ?? this.testController.items).forEach((i: TestItem) => { + const w = this.workspaceService.getWorkspaceFolder(i.uri); + if (w?.uri.fsPath === workspace.uri.fsPath) { + testItems.push(i); + } + }); + return testItems; + } + + /** + * Sets up detailed coverage loading if the run profile is for coverage. + */ + private setupCoverageIfNeeded(request: TestRunRequest, testAdapter: WorkspaceTestAdapter): void { + // no profile will have TestRunProfileKind.Coverage if rewrite isn't enabled + if (request.profile?.kind && request.profile?.kind === TestRunProfileKind.Coverage) { + request.profile.loadDetailedCoverage = ( + _testRun: TestRun, + fileCoverage, + _token, + ): Thenable => { + const details = testAdapter.resultResolver.detailedCoverageMap.get(fileCoverage.uri.fsPath); + if (details === undefined) { + // given file has no detailed coverage data + return Promise.resolve([]); + } + return Promise.resolve(details); + }; + } + } + + /** + * Executes tests using the test adapter for a specific test provider. + */ + private async executeTestsForProvider( + workspace: WorkspaceFolder, + testAdapter: WorkspaceTestAdapter, + testItems: TestItem[], + runInstance: TestRun, + request: TestRunRequest, + token: CancellationToken, + provider: TestProvider, + ): Promise { + sendTelemetryEvent(EventName.UNITTEST_RUN, undefined, { + tool: provider, + debugging: request.profile?.kind === TestRunProfileKind.Debug, + }); + + await testAdapter.executeTests( + this.testController, + runInstance, + testItems, + this.pythonExecFactory, + token, + request.profile?.kind, + this.debugLauncher, + await this.interpreterService.getActiveInterpreter(workspace.uri), + ); + } + + private invalidateTests(uri: Uri) { + this.testController.items.forEach((root) => { + const item = getNodeByUri(root, uri); + if (item && !!item.invalidateResults) { + // Minimize invalidating to test case nodes for the test file where + // the change occurred + item.invalidateResults(); + } + }); + } + + private watchForSettingsChanges(workspace: WorkspaceFolder): void { + const pattern = new RelativePattern(workspace, '**/{settings.json,pytest.ini,pyproject.toml,setup.cfg}'); + const watcher = this.workspaceService.createFileSystemWatcher(pattern); + this.disposables.push(watcher); + + this.disposables.push( + onDidSaveTextDocument(async (doc: TextDocument) => { + const file = doc.fileName; + // refresh on any settings file save + if ( + file.includes('settings.json') || + file.includes('pytest.ini') || + file.includes('setup.cfg') || + file.includes('pyproject.toml') + ) { + traceVerbose(`Testing: Trigger refresh after saving ${doc.uri.fsPath}`); + this.sendTriggerTelemetry('watching'); + this.refreshData.trigger(doc.uri, false); + } + }), + ); + /* Keep both watchers for create and delete since config files can change test behavior without content + due to their impact on pythonPath. */ + this.disposables.push( + watcher.onDidCreate((uri) => { + traceVerbose(`Testing: Trigger refresh after creating ${uri.fsPath}`); + this.sendTriggerTelemetry('watching'); + this.refreshData.trigger(uri, false); + }), + ); + this.disposables.push( + watcher.onDidDelete((uri) => { + traceVerbose(`Testing: Trigger refresh after deleting in ${uri.fsPath}`); + this.sendTriggerTelemetry('watching'); + this.refreshData.trigger(uri, false); + }), + ); + } + + private watchForTestContentChangeOnSave(): void { + this.disposables.push( + onDidSaveTextDocument(async (doc: TextDocument) => { + const settings = this.configSettings.getSettings(doc.uri); + if ( + settings.testing.autoTestDiscoverOnSaveEnabled && + minimatch.default(doc.uri.fsPath, settings.testing.autoTestDiscoverOnSavePattern) + ) { + traceVerbose(`Testing: Trigger refresh after saving ${doc.uri.fsPath}`); + this.sendTriggerTelemetry('watching'); + this.refreshData.trigger(doc.uri, false); + } + }), + ); + } + + /** + * Send UNITTEST_DISCOVERY_TRIGGER telemetry event only once per trigger type. + * + * @param triggerType The trigger type to send telemetry for. + */ + private sendTriggerTelemetry(trigger: TriggerType): void { + if (!this.triggerTypes.includes(trigger)) { + sendTelemetryEvent(EventName.UNITTEST_DISCOVERY_TRIGGER, undefined, { + trigger, + }); + this.triggerTypes.push(trigger); + } + } + + private surfaceErrorNode(workspaceUri: Uri, message: string, testProvider: TestProvider): void { + let errorNode = this.testController.items.get(`DiscoveryError:${workspaceUri.fsPath}`); + if (errorNode === undefined) { + const options = buildErrorNodeOptions(workspaceUri, message, testProvider); + errorNode = createErrorTestItem(this.testController, options); + this.testController.items.add(errorNode); + } + const errorNodeLabel: MarkdownString = new MarkdownString(message); + errorNodeLabel.isTrusted = true; + errorNode.error = errorNodeLabel; + } +} diff --git a/src/client/testing/testController/pytest/arguments.ts b/src/client/testing/testController/pytest/arguments.ts new file mode 100644 index 000000000000..2b4efbd56f42 --- /dev/null +++ b/src/client/testing/testController/pytest/arguments.ts @@ -0,0 +1,255 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { TestFilter } from '../../common/types'; +import { getPositionalArguments, filterArguments } from '../common/argumentsHelper'; + +const OptionsWithArguments = [ + '-c', + '-k', + '-m', + '-o', + '-p', + '-r', + '-W', + '-n', // -n is a pytest-xdist option + '--assert', + '--basetemp', + '--cache-show', + '--capture', + '--code-highlight', + '--color', + '--confcutdir', + '--cov', + '--cov-config', + '--cov-fail-under', + '--cov-report', + '--deselect', + '--dist', + '--doctest-glob', + '--doctest-report', + '--durations', + '--durations-min', + '--ignore', + '--ignore-glob', + '--import-mode', + '--junit-prefix', + '--junit-xml', + '--last-failed-no-failures', + '--lfnf', + '--log-auto-indent', + '--log-cli-date-format', + '--log-cli-format', + '--log-cli-level', + '--log-date-format', + '--log-file', + '--log-file-date-format', + '--log-file-format', + '--log-file-level', + '--log-format', + '--log-level', + '--maxfail', + '--override-ini', + '--pastebin', + '--pdbcls', + '--pythonwarnings', + '--result-log', + '--rootdir', + '--show-capture', + '--tb', + '--verbosity', + '--max-slave-restart', + '--numprocesses', + '--rsyncdir', + '--rsyncignore', + '--tx', +]; + +const OptionsWithoutArguments = [ + '--cache-clear', + '--collect-in-virtualenv', + '--collect-only', + '--co', + '--continue-on-collection-errors', + '--cov-append', + '--cov-branch', + '--debug', + '--disable-pytest-warnings', + '--disable-warnings', + '--doctest-continue-on-failure', + '--doctest-ignore-import-errors', + '--doctest-modules', + '--exitfirst', + '--failed-first', + '--ff', + '--fixtures', + '--fixtures-per-test', + '--force-sugar', + '--full-trace', + '--funcargs', + '--help', + '--keep-duplicates', + '--last-failed', + '--lf', + '--markers', + '--new-first', + '--nf', + '--no-cov', + '--no-cov-on-fail', + '--no-header', + '--no-print-logs', + '--no-summary', + '--noconftest', + '--old-summary', + '--pdb', + '--pyargs', + '-PyTest, Unittest-pyargs', + '--quiet', + '--runxfail', + '--setup-only', + '--setup-plan', + '--setup-show', + '--showlocals', + '--stepwise', + '--sw', + '--stepwise-skip', + '--strict', + '--strict-config', + '--strict-markers', + '--trace-config', + '--verbose', + '--version', + '-V', + '-h', + '-l', + '-q', + '-s', + '-v', + '-x', + '--boxed', + '--forked', + '--looponfail', + '--trace', + '--tx', + '-d', +]; + +export function removePositionalFoldersAndFiles(args: string[]): string[] { + return pytestFilterArguments(args, TestFilter.removeTests); +} + +function pytestFilterArguments(args: string[], argumentToRemoveOrFilter: string[] | TestFilter): string[] { + const optionsWithoutArgsToRemove: string[] = []; + const optionsWithArgsToRemove: string[] = []; + // Positional arguments in pytest are test directories and files. + // So if we want to run a specific test, then remove positional args. + let removePositionalArgs = false; + if (Array.isArray(argumentToRemoveOrFilter)) { + argumentToRemoveOrFilter.forEach((item) => { + if (OptionsWithArguments.indexOf(item) >= 0) { + optionsWithArgsToRemove.push(item); + } + if (OptionsWithoutArguments.indexOf(item) >= 0) { + optionsWithoutArgsToRemove.push(item); + } + }); + } else { + switch (argumentToRemoveOrFilter) { + case TestFilter.removeTests: { + optionsWithoutArgsToRemove.push( + ...['--lf', '--last-failed', '--ff', '--failed-first', '--nf', '--new-first'], + ); + optionsWithArgsToRemove.push(...['-k', '-m', '--lfnf', '--last-failed-no-failures']); + removePositionalArgs = true; + break; + } + case TestFilter.discovery: { + optionsWithoutArgsToRemove.push( + ...[ + '-x', + '--exitfirst', + '--fixtures', + '--funcargs', + '--fixtures-per-test', + '--pdb', + '--lf', + '--last-failed', + '--ff', + '--failed-first', + '--nf', + '--new-first', + '--cache-show', + '-v', + '--verbose', + '-q', + '-quiet', + '-l', + '--showlocals', + '--no-print-logs', + '--debug', + '--setup-only', + '--setup-show', + '--setup-plan', + '--trace', + ], + ); + optionsWithArgsToRemove.push( + ...[ + '-m', + '--maxfail', + '--pdbcls', + '--capture', + '--lfnf', + '--last-failed-no-failures', + '--verbosity', + '-r', + '--tb', + '--show-capture', + '--durations', + '--junit-xml', + '--junit-prefix', + '--result-log', + '-W', + '--pythonwarnings', + '--log-*', + ], + ); + removePositionalArgs = true; + break; + } + case TestFilter.debugAll: + case TestFilter.runAll: { + optionsWithoutArgsToRemove.push(...['--collect-only', '--trace']); + break; + } + case TestFilter.debugSpecific: + case TestFilter.runSpecific: { + optionsWithoutArgsToRemove.push( + ...[ + '--collect-only', + '--lf', + '--last-failed', + '--ff', + '--failed-first', + '--nf', + '--new-first', + '--trace', + ], + ); + optionsWithArgsToRemove.push(...['-k', '-m', '--lfnf', '--last-failed-no-failures']); + removePositionalArgs = true; + break; + } + default: { + throw new Error(`Unsupported Filter '${argumentToRemoveOrFilter}'`); + } + } + } + + let filteredArgs = args.slice(); + if (removePositionalArgs) { + const positionalArgs = getPositionalArguments(filteredArgs, OptionsWithArguments, OptionsWithoutArguments); + filteredArgs = filteredArgs.filter((item) => positionalArgs.indexOf(item) === -1); + } + return filterArguments(filteredArgs, optionsWithArgsToRemove, optionsWithoutArgsToRemove); +} diff --git a/src/client/testing/testController/pytest/pytestController.ts b/src/client/testing/testController/pytest/pytestController.ts new file mode 100644 index 000000000000..f75580c11236 --- /dev/null +++ b/src/client/testing/testController/pytest/pytestController.ts @@ -0,0 +1,142 @@ +/* eslint-disable class-methods-use-this */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable } from 'inversify'; +import * as path from 'path'; +import { CancellationToken, TestItem, Uri, TestController } from 'vscode'; +import { IWorkspaceService } from '../../../common/application/types'; +import { asyncForEach } from '../../../common/utils/arrayUtils'; +import { Deferred } from '../../../common/utils/async'; +import { + createWorkspaceRootTestItem, + getWorkspaceNode, + removeItemByIdFromChildren, + updateTestItemFromRawData, +} from '../common/testItemUtilities'; +import { ITestFrameworkController, TestData, RawDiscoveredTests } from '../common/types'; + +@injectable() +export class PytestController implements ITestFrameworkController { + private readonly testData: Map = new Map(); + + private discovering: Map> = new Map(); + + private idToRawData: Map = new Map(); + + constructor(@inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService) {} + + public async resolveChildren( + testController: TestController, + item: TestItem, + token?: CancellationToken, + ): Promise { + const workspace = this.workspaceService.getWorkspaceFolder(item.uri); + if (workspace) { + // if we are still discovering then wait + const discovery = this.discovering.get(workspace.uri.fsPath); + if (discovery) { + await discovery.promise; + } + + // see if we have raw test data + const rawTestData = this.testData.get(workspace.uri.fsPath); + if (rawTestData) { + // Refresh each node with new data + if (rawTestData.length === 0) { + const items: TestItem[] = []; + testController.items.forEach((i) => items.push(i)); + items.forEach((i) => testController.items.delete(i.id)); + return Promise.resolve(); + } + + const root = rawTestData.length === 1 ? rawTestData[0].root : workspace.uri.fsPath; + if (root === item.id) { + // This is the workspace root node + if (rawTestData.length === 1) { + if (rawTestData[0].tests.length > 0) { + await updateTestItemFromRawData( + item, + testController, + this.idToRawData, + item.id, + rawTestData, + token, + ); + } else { + this.idToRawData.delete(item.id); + testController.items.delete(item.id); + return Promise.resolve(); + } + } else { + // To figure out which top level nodes have to removed. First we get all the + // existing nodes. Then if they have data we keep those nodes, Nodes without + // data will be removed after we check the raw data. + let subRootWithNoData: string[] = []; + item.children.forEach((c) => subRootWithNoData.push(c.id)); + + await asyncForEach(rawTestData, async (data) => { + let subRootId = data.root; + let rawId; + if (data.root === root) { + const subRoot = data.parents.filter((p) => p.parentid === '.' || p.parentid === root); + subRootId = path.join(data.root, subRoot.length > 0 ? subRoot[0].id : ''); + rawId = subRoot.length > 0 ? subRoot[0].id : undefined; + } + + if (data.tests.length > 0) { + let subRootItem = item.children.get(subRootId); + if (!subRootItem) { + subRootItem = createWorkspaceRootTestItem(testController, this.idToRawData, { + id: subRootId, + label: path.basename(subRootId), + uri: Uri.file(subRootId), + runId: subRootId, + parentId: item.id, + rawId, + }); + item.children.add(subRootItem); + } + + // We found data for a node. Remove its id from the no-data list. + subRootWithNoData = subRootWithNoData.filter((s) => s !== subRootId); + await updateTestItemFromRawData( + subRootItem, + testController, + this.idToRawData, + root, // All the file paths are based on workspace root. + [data], + token, + ); + } else { + // This means there are no tests under this node + removeItemByIdFromChildren(this.idToRawData, item, [subRootId]); + } + }); + + // We did not find any data for these nodes, delete them. + removeItemByIdFromChildren(this.idToRawData, item, subRootWithNoData); + } + } else { + const workspaceNode = getWorkspaceNode(item, this.idToRawData); + if (workspaceNode) { + await updateTestItemFromRawData( + item, + testController, + this.idToRawData, + workspaceNode.id, + rawTestData, + token, + ); + } + } + } else { + const workspaceNode = getWorkspaceNode(item, this.idToRawData); + if (workspaceNode) { + testController.items.delete(workspaceNode.id); + } + } + } + return Promise.resolve(); + } +} diff --git a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts new file mode 100644 index 000000000000..16e27635e66c --- /dev/null +++ b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts @@ -0,0 +1,225 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +import * as path from 'path'; +import { CancellationToken, Disposable, Uri } from 'vscode'; +import { ChildProcess } from 'child_process'; +import { + ExecutionFactoryCreateWithEnvironmentOptions, + IPythonExecutionFactory, + SpawnOptions, +} from '../../../common/process/types'; +import { IConfigurationService } from '../../../common/types'; +import { Deferred } from '../../../common/utils/async'; +import { EXTENSION_ROOT_DIR } from '../../../constants'; +import { traceError, traceInfo, traceVerbose } from '../../../logging'; +import { ITestDiscoveryAdapter, ITestResultResolver } from '../common/types'; +import { createTestingDeferred } from '../common/utils'; +import { IEnvironmentVariablesProvider } from '../../../common/variables/types'; +import { PythonEnvironment } from '../../../pythonEnvironments/info'; +import { useEnvExtension, getEnvironment, runInBackground } from '../../../envExt/api.internal'; +import { buildPytestEnv as configureSubprocessEnv, handleSymlinkAndRootDir } from './pytestHelpers'; +import { cleanupOnCancellation, createProcessHandlers, setupDiscoveryPipe } from '../common/discoveryHelpers'; +import { ProjectAdapter } from '../common/projectAdapter'; + +/** + * Configures the subprocess environment for pytest discovery. + * @param envVarsService Service to retrieve environment variables + * @param uri Workspace URI + * @param discoveryPipeName Name of the discovery pipe to pass to the subprocess + * @returns Configured environment variables for the subprocess + */ +async function configureDiscoveryEnv( + envVarsService: IEnvironmentVariablesProvider | undefined, + uri: Uri, + discoveryPipeName: string, +): Promise { + const fullPluginPath = path.join(EXTENSION_ROOT_DIR, 'python_files'); + const envVars = await envVarsService?.getEnvironmentVariables(uri); + const mutableEnv = configureSubprocessEnv(envVars, fullPluginPath, discoveryPipeName); + return mutableEnv; +} + +/** + * Wrapper class for pytest test discovery. This is where we call the pytest subprocess. + */ +export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { + constructor( + public configSettings: IConfigurationService, + private readonly resultResolver?: ITestResultResolver, + private readonly envVarsService?: IEnvironmentVariablesProvider, + ) {} + + async discoverTests( + uri: Uri, + executionFactory: IPythonExecutionFactory, + token?: CancellationToken, + interpreter?: PythonEnvironment, + project?: ProjectAdapter, + ): Promise { + // Setup discovery pipe and cancellation + const { + pipeName: discoveryPipeName, + cancellation: discoveryPipeCancellation, + tokenDisposable, + } = await setupDiscoveryPipe(this.resultResolver, token, uri); + + // Setup process handlers deferred (used by both execution paths) + const deferredTillExecClose: Deferred = createTestingDeferred(); + + // Collect all disposables related to discovery to handle cleanup in finally block + const disposables: Disposable[] = []; + if (tokenDisposable) { + disposables.push(tokenDisposable); + } + + try { + // Build pytest command and arguments + const settings = this.configSettings.getSettings(uri); + let { pytestArgs } = settings.testing; + const cwd = settings.testing.cwd && settings.testing.cwd.length > 0 ? settings.testing.cwd : uri.fsPath; + pytestArgs = await handleSymlinkAndRootDir(cwd, pytestArgs); + + // Add --ignore flags for nested projects to prevent duplicate discovery + if (project?.nestedProjectPathsToIgnore?.length) { + const ignoreArgs = project.nestedProjectPathsToIgnore.map((nestedPath) => `--ignore=${nestedPath}`); + pytestArgs = [...pytestArgs, ...ignoreArgs]; + traceInfo( + `[test-by-project] Project ${project.projectName} ignoring nested project(s): ${ignoreArgs.join( + ' ', + )}`, + ); + } + + const commandArgs = ['-m', 'pytest', '-p', 'vscode_pytest', '--collect-only'].concat(pytestArgs); + traceVerbose( + `Running pytest discovery with command: ${commandArgs.join(' ')} for workspace ${uri.fsPath}.`, + ); + + // Configure subprocess environment + const mutableEnv = await configureDiscoveryEnv(this.envVarsService, uri, discoveryPipeName); + + // Set PROJECT_ROOT_PATH for project-based testing (tells Python where to root the test tree) + if (project) { + mutableEnv.PROJECT_ROOT_PATH = project.projectUri.fsPath; + } + + // Setup process handlers (shared by both execution paths) + const handlers = createProcessHandlers('pytest', uri, cwd, this.resultResolver, deferredTillExecClose, [5]); + + // Execute using environment extension if available + if (useEnvExtension()) { + traceInfo(`Using environment extension for pytest discovery in workspace ${uri.fsPath}`); + const pythonEnv = project?.pythonEnvironment ?? (await getEnvironment(uri)); + if (!pythonEnv) { + traceError( + `Python environment not found for workspace ${uri.fsPath}. Cannot proceed with test discovery.`, + ); + deferredTillExecClose.resolve(); + return; + } + traceVerbose(`Using Python environment: ${JSON.stringify(pythonEnv)}`); + + const proc = await runInBackground(pythonEnv, { + cwd, + args: commandArgs, + env: (mutableEnv as unknown) as { [key: string]: string }, + }); + traceInfo(`Started pytest discovery subprocess (environment extension) for workspace ${uri.fsPath}`); + + // Wire up cancellation and process events + const envExtCancellationHandler = token?.onCancellationRequested(() => { + cleanupOnCancellation('pytest', proc, deferredTillExecClose, discoveryPipeCancellation, uri); + }); + if (envExtCancellationHandler) { + disposables.push(envExtCancellationHandler); + } + proc.stdout.on('data', handlers.onStdout); + proc.stderr.on('data', handlers.onStderr); + proc.onExit((code, signal) => { + handlers.onExit(code, signal); + handlers.onClose(code, signal); + }); + + await deferredTillExecClose.promise; + traceInfo(`Pytest discovery completed for workspace ${uri.fsPath}`); + return; + } + + // Execute using execution factory (fallback path) + traceInfo(`Using execution factory for pytest discovery in workspace ${uri.fsPath}`); + const creationOptions: ExecutionFactoryCreateWithEnvironmentOptions = { + allowEnvironmentFetchExceptions: false, + resource: uri, + interpreter, + }; + const execService = await executionFactory.createActivatedEnvironment(creationOptions); + if (!execService) { + traceError( + `Failed to create execution service for workspace ${uri.fsPath}. Cannot proceed with test discovery.`, + ); + deferredTillExecClose.resolve(); + return; + } + const execInfo = await execService.getExecutablePath(); + traceVerbose(`Using Python executable: ${execInfo} for workspace ${uri.fsPath}`); + + // Check for cancellation before spawning process + if (token?.isCancellationRequested) { + traceInfo(`Pytest discovery cancelled before spawning process for workspace ${uri.fsPath}`); + deferredTillExecClose.resolve(); + return; + } + + const spawnOptions: SpawnOptions = { + cwd, + throwOnStdErr: true, + env: mutableEnv, + token, + }; + + let resultProc: ChildProcess | undefined; + + // Set up cancellation handler after all early return checks + const cancellationHandler = token?.onCancellationRequested(() => { + traceInfo(`Cancellation requested during pytest discovery for workspace ${uri.fsPath}`); + cleanupOnCancellation('pytest', resultProc, deferredTillExecClose, discoveryPipeCancellation, uri); + }); + if (cancellationHandler) { + disposables.push(cancellationHandler); + } + + try { + const result = execService.execObservable(commandArgs, spawnOptions); + resultProc = result?.proc; + + if (!resultProc) { + traceError(`Failed to spawn pytest discovery subprocess for workspace ${uri.fsPath}`); + deferredTillExecClose.resolve(); + return; + } + traceInfo(`Started pytest discovery subprocess (execution factory) for workspace ${uri.fsPath}`); + } catch (error) { + traceError(`Error spawning pytest discovery subprocess for workspace ${uri.fsPath}: ${error}`); + deferredTillExecClose.resolve(); + throw error; + } + resultProc.stdout?.on('data', handlers.onStdout); + resultProc.stderr?.on('data', handlers.onStderr); + resultProc.on('exit', handlers.onExit); + resultProc.on('close', handlers.onClose); + + traceVerbose(`Waiting for pytest discovery subprocess to complete for workspace ${uri.fsPath}`); + await deferredTillExecClose.promise; + traceInfo(`Pytest discovery completed for workspace ${uri.fsPath}`); + } catch (error) { + traceError(`Error during pytest discovery for workspace ${uri.fsPath}: ${error}`); + deferredTillExecClose.resolve(); + throw error; + } finally { + // Dispose all cancellation handlers and event subscriptions + disposables.forEach((d) => d.dispose()); + // Dispose the discovery pipe cancellation token + discoveryPipeCancellation.dispose(); + } + } +} diff --git a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts new file mode 100644 index 000000000000..102841c2e2dd --- /dev/null +++ b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts @@ -0,0 +1,304 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { CancellationTokenSource, DebugSessionOptions, TestRun, TestRunProfileKind, Uri } from 'vscode'; +import * as path from 'path'; +import { ChildProcess } from 'child_process'; +import { IConfigurationService } from '../../../common/types'; +import { Deferred } from '../../../common/utils/async'; +import { traceError, traceInfo, traceVerbose } from '../../../logging'; +import { ExecutionTestPayload, ITestExecutionAdapter, ITestResultResolver } from '../common/types'; +import { + ExecutionFactoryCreateWithEnvironmentOptions, + IPythonExecutionFactory, + SpawnOptions, +} from '../../../common/process/types'; +import { removePositionalFoldersAndFiles } from './arguments'; +import { ITestDebugLauncher, LaunchOptions } from '../../common/types'; +import { PYTEST_PROVIDER } from '../../common/constants'; +import { EXTENSION_ROOT_DIR } from '../../../common/constants'; +import * as utils from '../common/utils'; +import { IEnvironmentVariablesProvider } from '../../../common/variables/types'; +import { PythonEnvironment } from '../../../pythonEnvironments/info'; +import { getEnvironment, runInBackground, useEnvExtension } from '../../../envExt/api.internal'; +import { ProjectAdapter } from '../common/projectAdapter'; + +export class PytestTestExecutionAdapter implements ITestExecutionAdapter { + constructor( + public configSettings: IConfigurationService, + private readonly resultResolver?: ITestResultResolver, + private readonly envVarsService?: IEnvironmentVariablesProvider, + ) {} + + async runTests( + uri: Uri, + testIds: string[], + profileKind: boolean | TestRunProfileKind | undefined, + runInstance: TestRun, + executionFactory: IPythonExecutionFactory, + debugLauncher?: ITestDebugLauncher, + interpreter?: PythonEnvironment, + project?: ProjectAdapter, + ): Promise { + const deferredTillServerClose: Deferred = utils.createTestingDeferred(); + + // create callback to handle data received on the named pipe + const dataReceivedCallback = (data: ExecutionTestPayload) => { + if (runInstance && !runInstance.token.isCancellationRequested) { + this.resultResolver?.resolveExecution(data, runInstance); + } else { + traceError(`No run instance found, cannot resolve execution, for workspace ${uri.fsPath}.`); + } + }; + const cSource = new CancellationTokenSource(); + runInstance.token.onCancellationRequested(() => cSource.cancel()); + + const name = await utils.startRunResultNamedPipe( + dataReceivedCallback, // callback to handle data received + deferredTillServerClose, // deferred to resolve when server closes + cSource.token, // token to cancel + ); + runInstance.token.onCancellationRequested(() => { + traceInfo(`Test run cancelled, resolving 'TillServerClose' deferred for ${uri.fsPath}.`); + }); + + try { + await this.runTestsNew( + uri, + testIds, + name, + cSource, + runInstance, + profileKind, + executionFactory, + debugLauncher, + interpreter, + project, + ); + } finally { + await deferredTillServerClose.promise; + } + } + + private async runTestsNew( + uri: Uri, + testIds: string[], + resultNamedPipeName: string, + serverCancel: CancellationTokenSource, + runInstance: TestRun, + profileKind: boolean | TestRunProfileKind | undefined, + executionFactory: IPythonExecutionFactory, + debugLauncher?: ITestDebugLauncher, + interpreter?: PythonEnvironment, + project?: ProjectAdapter, + ): Promise { + const relativePathToPytest = 'python_files'; + const fullPluginPath = path.join(EXTENSION_ROOT_DIR, relativePathToPytest); + const settings = this.configSettings.getSettings(uri); + const { pytestArgs } = settings.testing; + const cwd = settings.testing.cwd && settings.testing.cwd.length > 0 ? settings.testing.cwd : uri.fsPath; + // get and edit env vars + const mutableEnv = { + ...(await this.envVarsService?.getEnvironmentVariables(uri)), + }; + // get python path from mutable env, it contains process.env as well + const pythonPathParts: string[] = mutableEnv.PYTHONPATH?.split(path.delimiter) ?? []; + const pythonPathCommand = [fullPluginPath, ...pythonPathParts].join(path.delimiter); + mutableEnv.PYTHONPATH = pythonPathCommand; + mutableEnv.TEST_RUN_PIPE = resultNamedPipeName; + + // Set PROJECT_ROOT_PATH for project-based testing (tells Python where to root the test tree) + if (project) { + mutableEnv.PROJECT_ROOT_PATH = project.projectUri.fsPath; + traceInfo(`[test-by-project] Setting PROJECT_ROOT_PATH=${project.projectUri.fsPath} for pytest execution`); + } + + if (profileKind && profileKind === TestRunProfileKind.Coverage) { + mutableEnv.COVERAGE_ENABLED = 'True'; + } + + const debugBool = profileKind && profileKind === TestRunProfileKind.Debug; + + // Create the Python environment in which to execute the command. + const creationOptions: ExecutionFactoryCreateWithEnvironmentOptions = { + allowEnvironmentFetchExceptions: false, + resource: uri, + interpreter, + }; + // need to check what will happen in the exec service is NOT defined and is null + const execService = await executionFactory.createActivatedEnvironment(creationOptions); + + const execInfo = await execService?.getExecutablePath(); + traceVerbose(`Executable path for pytest execution: ${execInfo}.`); + + try { + // Remove positional test folders and files, we will add as needed per node + let testArgs = removePositionalFoldersAndFiles(pytestArgs); + + // if user has provided `--rootdir` then use that, otherwise add `cwd` + // root dir is required so pytest can find the relative paths and for symlinks + utils.addValueIfKeyNotExist(testArgs, '--rootdir', cwd); + + // -s and --capture are both command line options that control how pytest captures output. + // if neither are set, then set --capture=no to prevent pytest from capturing output. + if (debugBool && !utils.argKeyExists(testArgs, '-s')) { + testArgs = utils.addValueIfKeyNotExist(testArgs, '--capture', 'no'); + } + + // create a file with the test ids and set the environment variable to the file name + const testIdsFileName = await utils.writeTestIdsFile(testIds); + mutableEnv.RUN_TEST_IDS_PIPE = testIdsFileName; + traceInfo( + `Environment variables set for pytest execution: PYTHONPATH=${mutableEnv.PYTHONPATH}, TEST_RUN_PIPE=${mutableEnv.TEST_RUN_PIPE}, RUN_TEST_IDS_PIPE=${mutableEnv.RUN_TEST_IDS_PIPE}`, + ); + + const spawnOptions: SpawnOptions = { + cwd, + throwOnStdErr: true, + env: mutableEnv, + token: runInstance.token, + }; + + if (debugBool) { + const launchOptions: LaunchOptions = { + cwd, + args: testArgs, + token: runInstance.token, + testProvider: PYTEST_PROVIDER, + runTestIdsPort: testIdsFileName, + pytestPort: resultNamedPipeName, + // Pass project for project-based debugging (Python path and session name derived from this) + project: project?.pythonProject, + }; + const sessionOptions: DebugSessionOptions = { + testRun: runInstance, + }; + traceInfo(`Running DEBUG pytest with arguments: ${testArgs} for workspace ${uri.fsPath} \r\n`); + await debugLauncher!.launchDebugger( + launchOptions, + () => { + serverCancel.cancel(); + }, + sessionOptions, + ); + } else if (useEnvExtension()) { + // For project-based execution, use the project's Python environment + // Otherwise, fall back to getting the environment from the URI + const pythonEnv = project?.pythonEnvironment ?? (await getEnvironment(uri)); + if (pythonEnv) { + const deferredTillExecClose: Deferred = utils.createTestingDeferred(); + + const scriptPath = path.join(fullPluginPath, 'vscode_pytest', 'run_pytest_script.py'); + const runArgs = [scriptPath, ...testArgs]; + traceInfo(`Running pytest with arguments: ${runArgs.join(' ')} for workspace ${uri.fsPath} \r\n`); + + const proc = await runInBackground(pythonEnv, { + cwd, + args: runArgs, + env: (mutableEnv as unknown) as { [key: string]: string }, + }); + runInstance.token.onCancellationRequested(() => { + traceInfo(`Test run cancelled, killing pytest subprocess for workspace ${uri.fsPath}`); + proc.kill(); + deferredTillExecClose.resolve(); + serverCancel.cancel(); + }); + proc.stdout.on('data', (data) => { + const out = utils.fixLogLinesNoTrailing(data.toString()); + runInstance.appendOutput(out); + }); + proc.stderr.on('data', (data) => { + const out = utils.fixLogLinesNoTrailing(data.toString()); + runInstance.appendOutput(out); + }); + proc.onExit((code, signal) => { + if (code !== 0) { + traceError( + `Subprocess exited unsuccessfully with exit code ${code} and signal ${signal} on workspace ${uri.fsPath}`, + ); + } + deferredTillExecClose.resolve(); + serverCancel.cancel(); + }); + await deferredTillExecClose.promise; + } else { + traceError(`Python Environment not found for: ${uri.fsPath}`); + } + } else { + // deferredTillExecClose is resolved when all stdout and stderr is read + const deferredTillExecClose: Deferred = utils.createTestingDeferred(); + // combine path to run script with run args + const scriptPath = path.join(fullPluginPath, 'vscode_pytest', 'run_pytest_script.py'); + const runArgs = [scriptPath, ...testArgs]; + traceInfo(`Running pytest with arguments: ${runArgs.join(' ')} for workspace ${uri.fsPath} \r\n`); + + let resultProc: ChildProcess | undefined; + + runInstance.token.onCancellationRequested(() => { + traceInfo(`Test run cancelled, killing pytest subprocess for workspace ${uri.fsPath}`); + // if the resultProc exists just call kill on it which will handle resolving the ExecClose deferred, otherwise resolve the deferred here. + if (resultProc) { + resultProc?.kill(); + } else { + deferredTillExecClose.resolve(); + serverCancel.cancel(); + } + }); + + const result = execService?.execObservable(runArgs, spawnOptions); + + // Take all output from the subprocess and add it to the test output channel. This will be the pytest output. + // Displays output to user and ensure the subprocess doesn't run into buffer overflow. + result?.proc?.stdout?.on('data', (data) => { + const out = utils.fixLogLinesNoTrailing(data.toString()); + runInstance.appendOutput(out); + }); + result?.proc?.stderr?.on('data', (data) => { + const out = utils.fixLogLinesNoTrailing(data.toString()); + runInstance.appendOutput(out); + }); + result?.proc?.on('exit', (code, signal) => { + if (code !== 0) { + traceError( + `Subprocess exited unsuccessfully with exit code ${code} and signal ${signal} on workspace ${uri.fsPath}`, + ); + } + }); + + result?.proc?.on('close', (code, signal) => { + traceVerbose('Test run finished, subprocess closed.'); + // if the child has testIds then this is a run request + // if the child process exited with a non-zero exit code, then we need to send the error payload. + if (code !== 0) { + traceError( + `Subprocess closed unsuccessfully with exit code ${code} and signal ${signal} for workspace ${uri.fsPath}. Creating and sending error execution payload \n`, + ); + + if (runInstance) { + this.resultResolver?.resolveExecution( + utils.createExecutionErrorPayload(code, signal, testIds, cwd), + runInstance, + ); + } + } + + // deferredTillEOT is resolved when all data sent on stdout and stderr is received, close event is only called when this occurs + // due to the sync reading of the output. + deferredTillExecClose.resolve(); + serverCancel.cancel(); + }); + await deferredTillExecClose.promise; + } + } catch (ex) { + traceError(`Error while running tests for workspace ${uri}: ${testIds}\r\n${ex}\r\n\r\n`); + return Promise.reject(ex); + } + + const executionPayload: ExecutionTestPayload = { + cwd, + status: 'success', + error: '', + }; + return executionPayload; + } +} diff --git a/src/client/testing/testController/pytest/pytestHelpers.ts b/src/client/testing/testController/pytest/pytestHelpers.ts new file mode 100644 index 000000000000..c6e748fb85a7 --- /dev/null +++ b/src/client/testing/testController/pytest/pytestHelpers.ts @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +import * as path from 'path'; +import * as fs from 'fs'; +import { traceInfo, traceWarn } from '../../../logging'; +import { addValueIfKeyNotExist, hasSymlinkParent } from '../common/utils'; + +/** + * Checks if the current working directory contains a symlink and ensures --rootdir is set in pytest args. + * This is required for pytest to correctly resolve relative paths in symlinked directories. + */ +export async function handleSymlinkAndRootDir(cwd: string, pytestArgs: string[]): Promise { + const stats = await fs.promises.lstat(cwd); + const resolvedPath = await fs.promises.realpath(cwd); + let isSymbolicLink = false; + if (stats.isSymbolicLink()) { + isSymbolicLink = true; + traceWarn(`Working directory is a symbolic link: ${cwd} -> ${resolvedPath}`); + } else if (resolvedPath !== cwd) { + traceWarn( + `Working directory resolves to different path: ${cwd} -> ${resolvedPath}. Checking for symlinks in parent directories.`, + ); + isSymbolicLink = await hasSymlinkParent(cwd); + } + if (isSymbolicLink) { + traceWarn( + `Symlink detected in path. Adding '--rootdir=${cwd}' to pytest args to ensure correct path resolution.`, + ); + pytestArgs = addValueIfKeyNotExist(pytestArgs, '--rootdir', cwd); + } + // if user has provided `--rootdir` then use that, otherwise add `cwd` + // root dir is required so pytest can find the relative paths and for symlinks + pytestArgs = addValueIfKeyNotExist(pytestArgs, '--rootdir', cwd); + return pytestArgs; +} + +/** + * Builds the environment variables required for pytest discovery. + * Sets PYTHONPATH to include the plugin path and TEST_RUN_PIPE for communication. + */ +export function buildPytestEnv( + envVars: { [key: string]: string | undefined } | undefined, + fullPluginPath: string, + discoveryPipeName: string, +): { [key: string]: string | undefined } { + const mutableEnv = { + ...envVars, + }; + // get python path from mutable env, it contains process.env as well + const pythonPathParts: string[] = mutableEnv.PYTHONPATH?.split(path.delimiter) ?? []; + const pythonPathCommand = [fullPluginPath, ...pythonPathParts].join(path.delimiter); + mutableEnv.PYTHONPATH = pythonPathCommand; + mutableEnv.TEST_RUN_PIPE = discoveryPipeName; + traceInfo( + `Environment variables set for pytest discovery: PYTHONPATH=${mutableEnv.PYTHONPATH}, TEST_RUN_PIPE=${mutableEnv.TEST_RUN_PIPE}`, + ); + return mutableEnv; +} diff --git a/src/client/testing/testController/serviceRegistry.ts b/src/client/testing/testController/serviceRegistry.ts new file mode 100644 index 000000000000..03bf883e8eb1 --- /dev/null +++ b/src/client/testing/testController/serviceRegistry.ts @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { IExtensionSingleActivationService } from '../../activation/types'; +import { IServiceManager } from '../../ioc/types'; +import { PYTEST_PROVIDER, UNITTEST_PROVIDER } from '../common/constants'; +import { ITestFrameworkController, ITestController } from './common/types'; +import { PythonTestController } from './controller'; +import { PytestController } from './pytest/pytestController'; +import { UnittestController } from './unittest/unittestController'; + +export function registerTestControllerTypes(serviceManager: IServiceManager): void { + serviceManager.addSingleton(ITestFrameworkController, PytestController, PYTEST_PROVIDER); + + serviceManager.addSingleton( + ITestFrameworkController, + UnittestController, + UNITTEST_PROVIDER, + ); + serviceManager.addSingleton(ITestController, PythonTestController); + serviceManager.addBinding(ITestController, IExtensionSingleActivationService); +} diff --git a/src/client/testing/testController/unittest/testDiscoveryAdapter.ts b/src/client/testing/testController/unittest/testDiscoveryAdapter.ts new file mode 100644 index 000000000000..558e01f3514d --- /dev/null +++ b/src/client/testing/testController/unittest/testDiscoveryAdapter.ts @@ -0,0 +1,211 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { CancellationToken, Disposable, Uri } from 'vscode'; +import { ChildProcess } from 'child_process'; +import { IConfigurationService } from '../../../common/types'; +import { EXTENSION_ROOT_DIR } from '../../../constants'; +import { ITestDiscoveryAdapter, ITestResultResolver } from '../common/types'; +import { IEnvironmentVariablesProvider } from '../../../common/variables/types'; +import { + ExecutionFactoryCreateWithEnvironmentOptions, + IPythonExecutionFactory, + SpawnOptions, +} from '../../../common/process/types'; +import { traceError, traceInfo, traceVerbose } from '../../../logging'; +import { getEnvironment, runInBackground, useEnvExtension } from '../../../envExt/api.internal'; +import { PythonEnvironment } from '../../../pythonEnvironments/info'; +import { createTestingDeferred } from '../common/utils'; +import { buildDiscoveryCommand, buildUnittestEnv as configureSubprocessEnv } from './unittestHelpers'; +import { cleanupOnCancellation, createProcessHandlers, setupDiscoveryPipe } from '../common/discoveryHelpers'; +import { ProjectAdapter } from '../common/projectAdapter'; + +/** + * Configures the subprocess environment for unittest discovery. + * @param envVarsService Service to retrieve environment variables + * @param uri Workspace URI + * @param discoveryPipeName Name of the discovery pipe to pass to the subprocess + * @returns Configured environment variables for the subprocess + */ +async function configureDiscoveryEnv( + envVarsService: IEnvironmentVariablesProvider | undefined, + uri: Uri, + discoveryPipeName: string, +): Promise { + const envVars = await envVarsService?.getEnvironmentVariables(uri); + const mutableEnv = configureSubprocessEnv(envVars, discoveryPipeName); + return mutableEnv; +} + +/** + * Wrapper class for unittest test discovery. + */ +export class UnittestTestDiscoveryAdapter implements ITestDiscoveryAdapter { + constructor( + public configSettings: IConfigurationService, + private readonly resultResolver?: ITestResultResolver, + private readonly envVarsService?: IEnvironmentVariablesProvider, + ) {} + + async discoverTests( + uri: Uri, + executionFactory: IPythonExecutionFactory, + token?: CancellationToken, + interpreter?: PythonEnvironment, + project?: ProjectAdapter, + ): Promise { + // Setup discovery pipe and cancellation + const { + pipeName: discoveryPipeName, + cancellation: discoveryPipeCancellation, + tokenDisposable, + } = await setupDiscoveryPipe(this.resultResolver, token, uri); + + // Setup process handlers deferred (used by both execution paths) + const deferredTillExecClose = createTestingDeferred(); + + // Collect all disposables for cleanup in finally block + const disposables: Disposable[] = []; + if (tokenDisposable) { + disposables.push(tokenDisposable); + } + try { + // Build unittest command and arguments + const settings = this.configSettings.getSettings(uri); + const { unittestArgs } = settings.testing; + const cwd = settings.testing.cwd && settings.testing.cwd.length > 0 ? settings.testing.cwd : uri.fsPath; + const execArgs = buildDiscoveryCommand(unittestArgs, EXTENSION_ROOT_DIR); + traceVerbose(`Running unittest discovery with command: ${execArgs.join(' ')} for workspace ${uri.fsPath}.`); + + // Configure subprocess environment + const mutableEnv = await configureDiscoveryEnv(this.envVarsService, uri, discoveryPipeName); + + // Set PROJECT_ROOT_PATH for project-based testing (tells Python where to root the test tree) + if (project) { + mutableEnv.PROJECT_ROOT_PATH = project.projectUri.fsPath; + traceInfo( + `[test-by-project] Setting PROJECT_ROOT_PATH=${project.projectUri.fsPath} for unittest discovery`, + ); + } + + // Setup process handlers (shared by both execution paths) + const handlers = createProcessHandlers('unittest', uri, cwd, this.resultResolver, deferredTillExecClose); + + // Execute using environment extension if available + if (useEnvExtension()) { + traceInfo(`Using environment extension for unittest discovery in workspace ${uri.fsPath}`); + const pythonEnv = project?.pythonEnvironment ?? (await getEnvironment(uri)); + if (!pythonEnv) { + traceError( + `Python environment not found for workspace ${uri.fsPath}. Cannot proceed with test discovery.`, + ); + deferredTillExecClose.resolve(); + return; + } + traceVerbose(`Using Python environment: ${JSON.stringify(pythonEnv)}`); + + const proc = await runInBackground(pythonEnv, { + cwd, + args: execArgs, + env: (mutableEnv as unknown) as { [key: string]: string }, + }); + traceInfo(`Started unittest discovery subprocess (environment extension) for workspace ${uri.fsPath}`); + + // Wire up cancellation and process events + const envExtCancellationHandler = token?.onCancellationRequested(() => { + cleanupOnCancellation('unittest', proc, deferredTillExecClose, discoveryPipeCancellation, uri); + }); + if (envExtCancellationHandler) { + disposables.push(envExtCancellationHandler); + } + proc.stdout.on('data', handlers.onStdout); + proc.stderr.on('data', handlers.onStderr); + proc.onExit((code, signal) => { + handlers.onExit(code, signal); + handlers.onClose(code, signal); + }); + + await deferredTillExecClose.promise; + traceInfo(`Unittest discovery completed for workspace ${uri.fsPath}`); + return; + } + + // Execute using execution factory (fallback path) + traceInfo(`Using execution factory for unittest discovery in workspace ${uri.fsPath}`); + const creationOptions: ExecutionFactoryCreateWithEnvironmentOptions = { + allowEnvironmentFetchExceptions: false, + resource: uri, + interpreter, + }; + const execService = await executionFactory.createActivatedEnvironment(creationOptions); + if (!execService) { + traceError( + `Failed to create execution service for workspace ${uri.fsPath}. Cannot proceed with test discovery.`, + ); + deferredTillExecClose.resolve(); + return; + } + const execInfo = await execService.getExecutablePath(); + traceVerbose(`Using Python executable: ${execInfo} for workspace ${uri.fsPath}`); + + // Check for cancellation before spawning process + if (token?.isCancellationRequested) { + traceInfo(`Unittest discovery cancelled before spawning process for workspace ${uri.fsPath}`); + deferredTillExecClose.resolve(); + return; + } + + const spawnOptions: SpawnOptions = { + cwd, + throwOnStdErr: true, + env: mutableEnv, + token, + }; + + let resultProc: ChildProcess | undefined; + + // Set up cancellation handler after all early return checks + const cancellationHandler = token?.onCancellationRequested(() => { + traceInfo(`Cancellation requested during unittest discovery for workspace ${uri.fsPath}`); + cleanupOnCancellation('unittest', resultProc, deferredTillExecClose, discoveryPipeCancellation, uri); + }); + if (cancellationHandler) { + disposables.push(cancellationHandler); + } + + try { + const result = execService.execObservable(execArgs, spawnOptions); + resultProc = result?.proc; + + if (!resultProc) { + traceError(`Failed to spawn unittest discovery subprocess for workspace ${uri.fsPath}`); + deferredTillExecClose.resolve(); + return; + } + traceInfo(`Started unittest discovery subprocess (execution factory) for workspace ${uri.fsPath}`); + } catch (error) { + traceError(`Error spawning unittest discovery subprocess for workspace ${uri.fsPath}: ${error}`); + deferredTillExecClose.resolve(); + throw error; + } + resultProc.stdout?.on('data', handlers.onStdout); + resultProc.stderr?.on('data', handlers.onStderr); + resultProc.on('exit', handlers.onExit); + resultProc.on('close', handlers.onClose); + + traceVerbose(`Waiting for unittest discovery subprocess to complete for workspace ${uri.fsPath}`); + await deferredTillExecClose.promise; + traceInfo(`Unittest discovery completed for workspace ${uri.fsPath}`); + } catch (error) { + traceError(`Error during unittest discovery for workspace ${uri.fsPath}: ${error}`); + deferredTillExecClose.resolve(); + throw error; + } finally { + traceVerbose(`Cleaning up unittest discovery resources for workspace ${uri.fsPath}`); + // Dispose all cancellation handlers and event subscriptions + disposables.forEach((d) => d.dispose()); + // Dispose the discovery pipe cancellation token + discoveryPipeCancellation.dispose(); + } + } +} diff --git a/src/client/testing/testController/unittest/testExecutionAdapter.ts b/src/client/testing/testController/unittest/testExecutionAdapter.ts new file mode 100644 index 000000000000..c7d21b768c5b --- /dev/null +++ b/src/client/testing/testController/unittest/testExecutionAdapter.ts @@ -0,0 +1,315 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { CancellationTokenSource, DebugSessionOptions, TestRun, TestRunProfileKind, Uri } from 'vscode'; +import { ChildProcess } from 'child_process'; +import { IConfigurationService } from '../../../common/types'; +import { Deferred, createDeferred } from '../../../common/utils/async'; +import { EXTENSION_ROOT_DIR } from '../../../constants'; +import { + ExecutionTestPayload, + ITestExecutionAdapter, + ITestResultResolver, + TestCommandOptions, + TestExecutionCommand, +} from '../common/types'; +import { traceError, traceInfo, traceLog, traceVerbose } from '../../../logging'; +import { fixLogLinesNoTrailing } from '../common/utils'; +import { EnvironmentVariables, IEnvironmentVariablesProvider } from '../../../common/variables/types'; +import { + ExecutionFactoryCreateWithEnvironmentOptions, + ExecutionResult, + IPythonExecutionFactory, + SpawnOptions, +} from '../../../common/process/types'; +import { ITestDebugLauncher, LaunchOptions } from '../../common/types'; +import { UNITTEST_PROVIDER } from '../../common/constants'; +import * as utils from '../common/utils'; +import { getEnvironment, runInBackground, useEnvExtension } from '../../../envExt/api.internal'; +import { PythonEnvironment } from '../../../pythonEnvironments/info'; +import { ProjectAdapter } from '../common/projectAdapter'; + +/** + * Wrapper Class for unittest test execution. This is where we call `runTestCommand`? + */ + +export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { + constructor( + public configSettings: IConfigurationService, + private readonly resultResolver?: ITestResultResolver, + private readonly envVarsService?: IEnvironmentVariablesProvider, + ) {} + + public async runTests( + uri: Uri, + testIds: string[], + profileKind: boolean | TestRunProfileKind | undefined, + runInstance: TestRun, + executionFactory: IPythonExecutionFactory, + debugLauncher?: ITestDebugLauncher, + _interpreter?: PythonEnvironment, + project?: ProjectAdapter, + ): Promise { + // deferredTillServerClose awaits named pipe server close + const deferredTillServerClose: Deferred = utils.createTestingDeferred(); + + // create callback to handle data received on the named pipe + const dataReceivedCallback = (data: ExecutionTestPayload) => { + if (runInstance && !runInstance.token.isCancellationRequested) { + this.resultResolver?.resolveExecution(data, runInstance); + } else { + traceError(`No run instance found, cannot resolve execution, for workspace ${uri.fsPath}.`); + } + }; + const cSource = new CancellationTokenSource(); + runInstance.token.onCancellationRequested(() => cSource.cancel()); + const name = await utils.startRunResultNamedPipe( + dataReceivedCallback, // callback to handle data received + deferredTillServerClose, // deferred to resolve when server closes + cSource.token, // token to cancel + ); + runInstance.token.onCancellationRequested(() => { + console.log(`Test run cancelled, resolving 'till TillAllServerClose' deferred for ${uri.fsPath}.`); + // if canceled, stop listening for results + deferredTillServerClose.resolve(); + }); + try { + await this.runTestsNew( + uri, + testIds, + name, + cSource, + runInstance, + profileKind, + executionFactory, + debugLauncher, + project, + ); + } catch (error) { + traceError(`Error in running unittest tests: ${error}`); + } finally { + await deferredTillServerClose.promise; + } + } + + private async runTestsNew( + uri: Uri, + testIds: string[], + resultNamedPipeName: string, + serverCancel: CancellationTokenSource, + runInstance: TestRun, + profileKind: boolean | TestRunProfileKind | undefined, + executionFactory: IPythonExecutionFactory, + debugLauncher?: ITestDebugLauncher, + project?: ProjectAdapter, + ): Promise { + const settings = this.configSettings.getSettings(uri); + const { unittestArgs } = settings.testing; + const cwd = settings.testing.cwd && settings.testing.cwd.length > 0 ? settings.testing.cwd : uri.fsPath; + + const command = buildExecutionCommand(unittestArgs); + let mutableEnv: EnvironmentVariables | undefined = await this.envVarsService?.getEnvironmentVariables(uri); + if (mutableEnv === undefined) { + mutableEnv = {} as EnvironmentVariables; + } + const pythonPathParts: string[] = mutableEnv.PYTHONPATH?.split(path.delimiter) ?? []; + const pythonPathCommand = [cwd, ...pythonPathParts].join(path.delimiter); + mutableEnv.PYTHONPATH = pythonPathCommand; + mutableEnv.TEST_RUN_PIPE = resultNamedPipeName; + + // Set PROJECT_ROOT_PATH for project-based testing (tells Python where to root the test tree) + if (project) { + mutableEnv.PROJECT_ROOT_PATH = project.projectUri.fsPath; + traceInfo( + `[test-by-project] Setting PROJECT_ROOT_PATH=${project.projectUri.fsPath} for unittest execution`, + ); + } + + if (profileKind && profileKind === TestRunProfileKind.Coverage) { + mutableEnv.COVERAGE_ENABLED = cwd; + } + + const options: TestCommandOptions = { + workspaceFolder: uri, + command, + cwd, + profileKind: typeof profileKind === 'boolean' ? undefined : profileKind, + testIds, + token: runInstance.token, + }; + traceLog(`Running UNITTEST execution for the following test ids: ${testIds}`); + + // create named pipe server to send test ids + const testIdsFileName = await utils.writeTestIdsFile(testIds); + mutableEnv.RUN_TEST_IDS_PIPE = testIdsFileName; + traceInfo( + `All environment variables set for unittest execution, PYTHONPATH: ${JSON.stringify( + mutableEnv.PYTHONPATH, + )}`, + ); + + const spawnOptions: SpawnOptions = { + token: options.token, + cwd: options.cwd, + throwOnStdErr: true, + env: mutableEnv, + }; + // Create the Python environment in which to execute the command. + const creationOptions: ExecutionFactoryCreateWithEnvironmentOptions = { + allowEnvironmentFetchExceptions: false, + resource: options.workspaceFolder, + }; + const execService = await executionFactory.createActivatedEnvironment(creationOptions); + + const execInfo = await execService?.getExecutablePath(); + traceVerbose(`Executable path for unittest execution: ${execInfo}.`); + + const args = [options.command.script].concat(options.command.args); + + if (options.outChannel) { + options.outChannel.appendLine(`python ${args.join(' ')}`); + } + + try { + if (options.profileKind && options.profileKind === TestRunProfileKind.Debug) { + const launchOptions: LaunchOptions = { + cwd: options.cwd, + args, + token: options.token, + testProvider: UNITTEST_PROVIDER, + runTestIdsPort: testIdsFileName, + pytestPort: resultNamedPipeName, // change this from pytest + // Pass project for project-based debugging (Python path and session name derived from this) + project: project?.pythonProject, + }; + const sessionOptions: DebugSessionOptions = { + testRun: runInstance, + }; + traceInfo(`Running DEBUG unittest for workspace ${options.cwd} with arguments: ${args}\r\n`); + + if (debugLauncher === undefined) { + traceError('Debug launcher is not defined'); + throw new Error('Debug launcher is not defined'); + } + await debugLauncher.launchDebugger( + launchOptions, + () => { + serverCancel.cancel(); + }, + sessionOptions, + ); + } else if (useEnvExtension()) { + const pythonEnv = project?.pythonEnvironment ?? (await getEnvironment(uri)); + if (pythonEnv) { + traceInfo(`Running unittest with arguments: ${args.join(' ')} for workspace ${uri.fsPath} \r\n`); + const deferredTillExecClose = createDeferred(); + + const proc = await runInBackground(pythonEnv, { + cwd, + args, + env: (mutableEnv as unknown) as { [key: string]: string }, + }); + runInstance.token.onCancellationRequested(() => { + traceInfo(`Test run cancelled, killing unittest subprocess for workspace ${uri.fsPath}`); + proc.kill(); + deferredTillExecClose.resolve(); + serverCancel.cancel(); + }); + proc.stdout.on('data', (data) => { + const out = utils.fixLogLinesNoTrailing(data.toString()); + runInstance.appendOutput(out); + }); + proc.stderr.on('data', (data) => { + const out = utils.fixLogLinesNoTrailing(data.toString()); + runInstance.appendOutput(out); + }); + proc.onExit((code, signal) => { + if (code !== 0) { + traceError( + `Subprocess exited unsuccessfully with exit code ${code} and signal ${signal} on workspace ${uri.fsPath}`, + ); + } + deferredTillExecClose.resolve(); + serverCancel.cancel(); + }); + await deferredTillExecClose.promise; + } else { + traceError(`Python Environment not found for: ${uri.fsPath}`); + } + } else { + // This means it is running the test + traceInfo(`Running unittests for workspace ${cwd} with arguments: ${args}\r\n`); + + const deferredTillExecClose = createDeferred>(); + + let resultProc: ChildProcess | undefined; + + runInstance.token.onCancellationRequested(() => { + traceInfo(`Test run cancelled, killing unittest subprocess for workspace ${cwd}.`); + // if the resultProc exists just call kill on it which will handle resolving the ExecClose deferred, otherwise resolve the deferred here. + if (resultProc) { + resultProc?.kill(); + } else { + deferredTillExecClose?.resolve(); + serverCancel.cancel(); + } + }); + + const result = execService?.execObservable(args, spawnOptions); + resultProc = result?.proc; + + // Displays output to user and ensure the subprocess doesn't run into buffer overflow. + + result?.proc?.stdout?.on('data', (data) => { + const out = fixLogLinesNoTrailing(data.toString()); + runInstance.appendOutput(`${out}`); + }); + result?.proc?.stderr?.on('data', (data) => { + const out = fixLogLinesNoTrailing(data.toString()); + runInstance.appendOutput(`${out}`); + }); + + result?.proc?.on('exit', (code, signal) => { + // if the child has testIds then this is a run request + if (code !== 0 && testIds) { + // This occurs when we are running the test and there is an error which occurs. + + traceError( + `Subprocess exited unsuccessfully with exit code ${code} and signal ${signal} for workspace ${options.cwd}. Creating and sending error execution payload \n`, + ); + if (runInstance) { + this.resultResolver?.resolveExecution( + utils.createExecutionErrorPayload(code, signal, testIds, cwd), + runInstance, + ); + } + } + deferredTillExecClose.resolve(); + serverCancel.cancel(); + }); + await deferredTillExecClose.promise; + } + } catch (ex) { + traceError(`Error while running tests for workspace ${uri}: ${testIds}\r\n${ex}\r\n\r\n`); + return Promise.reject(ex); + } + // placeholder until after the rewrite is adopted + // TODO: remove after adoption. + const executionPayload: ExecutionTestPayload = { + cwd, + status: 'success', + error: '', + }; + return executionPayload; + } +} + +function buildExecutionCommand(args: string[]): TestExecutionCommand { + const executionScript = path.join(EXTENSION_ROOT_DIR, 'python_files', 'unittestadapter', 'execution.py'); + + return { + script: executionScript, + args: ['--udiscovery', ...args], + }; +} diff --git a/src/client/testing/testController/unittest/unittestController.ts b/src/client/testing/testController/unittest/unittestController.ts new file mode 100644 index 000000000000..863f34abd514 --- /dev/null +++ b/src/client/testing/testController/unittest/unittestController.ts @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable } from 'inversify'; +import { CancellationToken, TestController, TestItem } from 'vscode'; +import { IWorkspaceService } from '../../../common/application/types'; +import { Deferred } from '../../../common/utils/async'; +import { ITestFrameworkController, RawDiscoveredTests, TestData } from '../common/types'; +import { getWorkspaceNode, updateTestItemFromRawData } from '../common/testItemUtilities'; + +@injectable() +export class UnittestController implements ITestFrameworkController { + private readonly testData: Map = new Map(); + + private discovering: Map> = new Map(); + + private idToRawData: Map = new Map(); + + constructor(@inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService) {} + + public async resolveChildren( + testController: TestController, + item: TestItem, + token?: CancellationToken, + ): Promise { + const workspace = this.workspaceService.getWorkspaceFolder(item.uri); + if (workspace) { + // if we are still discovering then wait + const discovery = this.discovering.get(workspace.uri.fsPath); + if (discovery) { + await discovery.promise; + } + + // see if we have raw test data + const rawTestData = this.testData.get(workspace.uri.fsPath); + if (rawTestData) { + if (rawTestData.root === item.id) { + if (rawTestData.tests.length === 0) { + testController.items.delete(item.id); + return Promise.resolve(); + } + + if (rawTestData.tests.length > 0) { + await updateTestItemFromRawData( + item, + testController, + this.idToRawData, + item.id, + [rawTestData], + token, + ); + } else { + this.idToRawData.delete(item.id); + testController.items.delete(item.id); + } + } else { + const workspaceNode = getWorkspaceNode(item, this.idToRawData); + if (workspaceNode) { + await updateTestItemFromRawData( + item, + testController, + this.idToRawData, + workspaceNode.id, + [rawTestData], + token, + ); + } + } + } else { + const workspaceNode = getWorkspaceNode(item, this.idToRawData); + if (workspaceNode) { + testController.items.delete(workspaceNode.id); + } + } + } + return Promise.resolve(); + } +} diff --git a/src/client/testing/testController/unittest/unittestHelpers.ts b/src/client/testing/testController/unittest/unittestHelpers.ts new file mode 100644 index 000000000000..249a78dda7b7 --- /dev/null +++ b/src/client/testing/testController/unittest/unittestHelpers.ts @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +import * as path from 'path'; +import { traceInfo } from '../../../logging'; + +/** + * Builds the environment variables required for unittest discovery. + * Sets TEST_RUN_PIPE for communication. + */ +export function buildUnittestEnv( + envVars: { [key: string]: string | undefined } | undefined, + discoveryPipeName: string, +): { [key: string]: string | undefined } { + const mutableEnv = { + ...envVars, + }; + mutableEnv.TEST_RUN_PIPE = discoveryPipeName; + traceInfo(`Environment variables set for unittest discovery: TEST_RUN_PIPE=${mutableEnv.TEST_RUN_PIPE}`); + return mutableEnv; +} + +/** + * Builds the unittest discovery command. + */ +export function buildDiscoveryCommand(args: string[], extensionRootDir: string): string[] { + const discoveryScript = path.join(extensionRootDir, 'python_files', 'unittestadapter', 'discovery.py'); + return [discoveryScript, '--udiscovery', ...args]; +} diff --git a/src/client/testing/testController/workspaceTestAdapter.ts b/src/client/testing/testController/workspaceTestAdapter.ts new file mode 100644 index 000000000000..f17687732f57 --- /dev/null +++ b/src/client/testing/testController/workspaceTestAdapter.ts @@ -0,0 +1,178 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as util from 'util'; +import { CancellationToken, TestController, TestItem, TestRun, TestRunProfileKind, Uri } from 'vscode'; +import { createDeferred, Deferred } from '../../common/utils/async'; +import { Testing } from '../../common/utils/localize'; +import { traceError } from '../../logging'; +import { sendTelemetryEvent } from '../../telemetry'; +import { EventName } from '../../telemetry/constants'; +import { TestProvider } from '../types'; +import { createErrorTestItem, getTestCaseNodes } from './common/testItemUtilities'; +import { ITestDiscoveryAdapter, ITestExecutionAdapter, ITestResultResolver } from './common/types'; +import { IPythonExecutionFactory } from '../../common/process/types'; +import { ITestDebugLauncher } from '../common/types'; +import { buildErrorNodeOptions } from './common/utils'; +import { PythonEnvironment } from '../../pythonEnvironments/info'; +import { ProjectAdapter } from './common/projectAdapter'; + +/** + * This class exposes a test-provider-agnostic way of discovering tests. + * + * It gets instantiated by the `PythonTestController` class in charge of reflecting test data in the UI, + * and then instantiates provider-specific adapters under the hood depending on settings. + * + * This class formats the JSON test data returned by the `[Unittest|Pytest]TestDiscoveryAdapter` into test UI elements, + * and uses them to insert/update/remove items in the `TestController` instance behind the testing UI whenever the `PythonTestController` requests a refresh. + */ +export class WorkspaceTestAdapter { + private discovering: Deferred | undefined; + + private executing: Deferred | undefined; + + constructor( + private testProvider: TestProvider, + private discoveryAdapter: ITestDiscoveryAdapter, + private executionAdapter: ITestExecutionAdapter, + private workspaceUri: Uri, + public resultResolver: ITestResultResolver, + ) {} + + public async executeTests( + testController: TestController, + runInstance: TestRun, + includes: TestItem[], + executionFactory: IPythonExecutionFactory, + token?: CancellationToken, + profileKind?: boolean | TestRunProfileKind, + debugLauncher?: ITestDebugLauncher, + interpreter?: PythonEnvironment, + project?: ProjectAdapter, + ): Promise { + if (this.executing) { + traceError('Test execution already in progress, not starting a new one.'); + return this.executing.promise; + } + + const deferred = createDeferred(); + this.executing = deferred; + + const testCaseNodes: TestItem[] = []; + const testCaseIdsSet = new Set(); + try { + // first fetch all the individual test Items that we necessarily want + includes.forEach((t) => { + const nodes = getTestCaseNodes(t); + testCaseNodes.push(...nodes); + }); + // iterate through testItems nodes and fetch their unittest runID to pass in as argument + testCaseNodes.forEach((node) => { + runInstance.started(node); // do the vscode ui test item start here before runtest + const runId = this.resultResolver.vsIdToRunId.get(node.id); + if (runId) { + testCaseIdsSet.add(runId); + } + }); + const testCaseIds = Array.from(testCaseIdsSet); + if (executionFactory === undefined) { + throw new Error('Execution factory is required for test execution'); + } + await this.executionAdapter.runTests( + this.workspaceUri, + testCaseIds, + profileKind, + runInstance, + executionFactory, + debugLauncher, + interpreter, + project, + ); + deferred.resolve(); + } catch (ex) { + // handle token and telemetry here + sendTelemetryEvent(EventName.UNITTEST_RUN_ALL_FAILED, undefined); + + let cancel = token?.isCancellationRequested + ? Testing.cancelUnittestExecution + : Testing.errorUnittestExecution; + if (this.testProvider === 'pytest') { + cancel = token?.isCancellationRequested ? Testing.cancelPytestExecution : Testing.errorPytestExecution; + } + traceError(`${cancel}\r\n`, ex); + + // Also report on the test view + const message = util.format(`${cancel} ${Testing.seePythonOutput}\r\n`, ex); + const options = buildErrorNodeOptions(this.workspaceUri, message, this.testProvider); + const errorNode = createErrorTestItem(testController, options); + testController.items.add(errorNode); + + deferred.reject(ex as Error); + } finally { + this.executing = undefined; + } + + return Promise.resolve(); + } + + public async discoverTests( + testController: TestController, + executionFactory: IPythonExecutionFactory, + token?: CancellationToken, + interpreter?: PythonEnvironment, + ): Promise { + sendTelemetryEvent(EventName.UNITTEST_DISCOVERING, undefined, { tool: this.testProvider }); + + // Discovery is expensive. If it is already running, use the existing promise. + if (this.discovering) { + traceError('Test discovery already in progress, not starting a new one.'); + return this.discovering.promise; + } + + const deferred = createDeferred(); + this.discovering = deferred; + + try { + if (executionFactory === undefined) { + throw new Error('Execution factory is required for test discovery'); + } + await this.discoveryAdapter.discoverTests(this.workspaceUri, executionFactory, token, interpreter); + deferred.resolve(); + } catch (ex) { + sendTelemetryEvent(EventName.UNITTEST_DISCOVERY_DONE, undefined, { tool: this.testProvider, failed: true }); + + let cancel = token?.isCancellationRequested + ? Testing.cancelUnittestDiscovery + : Testing.errorUnittestDiscovery; + if (this.testProvider === 'pytest') { + cancel = token?.isCancellationRequested ? Testing.cancelPytestDiscovery : Testing.errorPytestDiscovery; + } + + traceError(`${cancel} for workspace: ${this.workspaceUri} \r\n`, ex); + + // Report also on the test view. + const message = util.format(`${cancel} ${Testing.seePythonOutput}\r\n`, ex); + const options = buildErrorNodeOptions(this.workspaceUri, message, this.testProvider); + const errorNode = createErrorTestItem(testController, options); + testController.items.add(errorNode); + + return deferred.reject(ex as Error); + } finally { + // Discovery has finished running, we have the data, + // we don't need the deferred promise anymore. + this.discovering = undefined; + } + + sendTelemetryEvent(EventName.UNITTEST_DISCOVERY_DONE, undefined, { tool: this.testProvider, failed: false }); + return Promise.resolve(); + } + + /** + * Retrieves the current test provider instance. + * + * @returns {TestProvider} The instance of the test provider. + */ + public getTestProvider(): TestProvider { + return this.testProvider; + } +} diff --git a/src/client/testing/types.ts b/src/client/testing/types.ts index 3949e0c502f8..da308ee6998b 100644 --- a/src/client/testing/types.ts +++ b/src/client/testing/types.ts @@ -3,239 +3,15 @@ 'use strict'; -// tslint:disable-next-line:ordered-imports -import { - DiagnosticSeverity, - Disposable, - DocumentSymbolProvider, - Event, - Location, - ProviderResult, - TextDocument, - TreeDataProvider, - TreeItem, - Uri, - WorkspaceFolder -} from 'vscode'; -import { Product, Resource } from '../common/types'; -import { CommandSource } from './common/constants'; -import { - FlattenedTestFunction, - ITestManager, - ITestResultsService, - TestFile, - TestFolder, - TestFunction, - TestRunOptions, - Tests, - TestStatus, - TestsToRun, - TestSuite, - UnitTestProduct -} from './common/types'; +import { Product } from '../common/types'; +import { TestSettingsPropertyNames } from './configuration/types'; -export const ITestConfigurationService = Symbol('ITestConfigurationService'); -export interface ITestConfigurationService { - displayTestFrameworkError(wkspace: Uri): Promise; - selectTestRunner(placeHolderMessage: string): Promise; - enableTest(wkspace: Uri, product: UnitTestProduct): Promise; - promptToEnableAndConfigureTestFramework(wkspace: Uri): Promise; -} - -export const ITestResultDisplay = Symbol('ITestResultDisplay'); - -export interface ITestResultDisplay extends Disposable { - enabled: boolean; - readonly onDidChange: Event; - displayProgressStatus(testRunResult: Promise, debug?: boolean): void; - displayDiscoverStatus(testDiscovery: Promise, quietMode?: boolean): Promise; -} - -export const ITestDisplay = Symbol('ITestDisplay'); -export interface ITestDisplay { - displayStopTestUI(workspace: Uri, message: string): void; - displayTestUI(cmdSource: CommandSource, wkspace: Uri): void; - selectTestFunction(rootDirectory: string, tests: Tests): Promise; - selectTestFile(rootDirectory: string, tests: Tests): Promise; - displayFunctionTestPickerUI( - cmdSource: CommandSource, - wkspace: Uri, - rootDirectory: string, - file: Uri, - testFunctions: TestFunction[], - debug?: boolean - ): void; -} - -export const ITestManagementService = Symbol('ITestManagementService'); -export interface ITestManagementService { - readonly onDidStatusChange: Event; - activate(symbolProvider: DocumentSymbolProvider): Promise; - getTestManager(displayTestNotConfiguredMessage: boolean, resource?: Uri): Promise; - discoverTestsForDocument(doc: TextDocument): Promise; - autoDiscoverTests(resource: Resource): Promise; - discoverTests( - cmdSource: CommandSource, - resource?: Uri, - ignoreCache?: boolean, - userInitiated?: boolean, - quietMode?: boolean - ): Promise; - stopTests(resource: Uri): Promise; - displayStopUI(message: string): Promise; - displayUI(cmdSource: CommandSource): Promise; - displayPickerUI(cmdSource: CommandSource, file: Uri, testFunctions: TestFunction[], debug?: boolean): Promise; - runTestsImpl( - cmdSource: CommandSource, - resource?: Uri, - testsToRun?: TestsToRun, - runFailedTests?: boolean, - debug?: boolean - ): Promise; - runCurrentTestFile(cmdSource: CommandSource): Promise; - - selectAndRunTestFile(cmdSource: CommandSource): Promise; - - selectAndRunTestMethod(cmdSource: CommandSource, resource: Uri, debug?: boolean): Promise; - - viewOutput(cmdSource: CommandSource): void; -} - -export const ITestConfigSettingsService = Symbol('ITestConfigSettingsService'); -export interface ITestConfigSettingsService { - updateTestArgs(testDirectory: string | Uri, product: UnitTestProduct, args: string[]): Promise; - enable(testDirectory: string | Uri, product: UnitTestProduct): Promise; - disable(testDirectory: string | Uri, product: UnitTestProduct): Promise; - getTestEnablingSetting(product: UnitTestProduct): string; -} - -export interface ITestConfigurationManager { - requiresUserToConfigure(wkspace: Uri): Promise; - configure(wkspace: Uri): Promise; - enable(): Promise; - disable(): Promise; -} - -export const ITestConfigurationManagerFactory = Symbol('ITestConfigurationManagerFactory'); -export interface ITestConfigurationManagerFactory { - create(wkspace: Uri, product: Product, cfg?: ITestConfigSettingsService): ITestConfigurationManager; -} - -export enum TestFilter { - removeTests = 'removeTests', - discovery = 'discovery', - runAll = 'runAll', - runSpecific = 'runSpecific', - debugAll = 'debugAll', - debugSpecific = 'debugSpecific' -} -export const IArgumentsService = Symbol('IArgumentsService'); -export interface IArgumentsService { - getKnownOptions(): { withArgs: string[]; withoutArgs: string[] }; - getOptionValue(args: string[], option: string): string | string[] | undefined; - filterArguments(args: string[], argumentToRemove: string[]): string[]; - // tslint:disable-next-line:unified-signatures - filterArguments(args: string[], filter: TestFilter): string[]; - getTestFolders(args: string[]): string[]; -} -export const IArgumentsHelper = Symbol('IArgumentsHelper'); -export interface IArgumentsHelper { - getOptionValues(args: string[], option: string): string | string[] | undefined; - filterArguments(args: string[], optionsWithArguments?: string[], optionsWithoutArguments?: string[]): string[]; - getPositionalArguments( - args: string[], - optionsWithArguments?: string[], - optionsWithoutArguments?: string[] - ): string[]; -} - -export const ITestManagerRunner = Symbol('ITestManagerRunner'); -export interface ITestManagerRunner { - runTest( - testResultsService: ITestResultsService, - options: TestRunOptions, - testManager: ITestManager - ): Promise; -} - -export const IUnitTestHelper = Symbol('IUnitTestHelper'); -export interface IUnitTestHelper { - getStartDirectory(args: string[]): string; - getIdsOfTestsToRun(tests: Tests, testsToRun: TestsToRun): string[]; -} - -export const ITestDiagnosticService = Symbol('ITestDiagnosticService'); -export interface ITestDiagnosticService { - getMessagePrefix(status: TestStatus): string | undefined; - getSeverity(unitTestSeverity: PythonTestMessageSeverity): DiagnosticSeverity | undefined; -} - -export interface IPythonTestMessage { - code: string | undefined; - message?: string; - severity: PythonTestMessageSeverity; - provider: string | undefined; - traceback?: string; - testTime: number; - status?: TestStatus; - locationStack?: ILocationStackFrameDetails[]; - testFilePath: string; -} -export enum PythonTestMessageSeverity { - Error, - Failure, - Skip, - Pass -} -export enum DiagnosticMessageType { - Error, - Fail, - Skipped, - Pass -} - -export interface ILocationStackFrameDetails { - location: Location; - lineText: string; -} - -export type WorkspaceTestStatus = { workspace: Uri; status: TestStatus }; - -export enum TestDataItemType { - workspaceFolder = 'workspaceFolder', - folder = 'folder', - file = 'file', - suite = 'suite', - function = 'function' -} -export type TestDataItem = TestWorkspaceFolder | TestFolder | TestFile | TestSuite | TestFunction; - -export class TestWorkspaceFolder { - public status?: TestStatus; - public time?: number; - public functionsPassed?: number; - public functionsFailed?: number; - public functionsDidNotRun?: number; - public passed?: boolean; - constructor(public readonly workspaceFolder: WorkspaceFolder) {} - public get resource(): Uri { - return this.workspaceFolder.uri; - } - public get name(): string { - return this.workspaceFolder.name; - } -} - -export const ITestTreeViewProvider = Symbol('ITestTreeViewProvider'); -export interface ITestTreeViewProvider extends TreeDataProvider { - onDidChangeTreeData: Event; - getTreeItem(element: TestDataItem): Promise; - getChildren(element?: TestDataItem): ProviderResult; - refresh(resource: Uri): void; -} +export type TestProvider = 'pytest' | 'unittest'; -export const ITestDataItemResource = Symbol('ITestDataItemResource'); +// **************** +// interfaces -export interface ITestDataItemResource { - getResource(testData: Readonly): Uri; +export const ITestingService = Symbol('ITestingService'); +export interface ITestingService { + getSettingsPropertyNames(product: Product): TestSettingsPropertyNames; } diff --git a/src/client/testing/unittest/helper.ts b/src/client/testing/unittest/helper.ts deleted file mode 100644 index be77ed07a7eb..000000000000 --- a/src/client/testing/unittest/helper.ts +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IServiceContainer } from '../../ioc/types'; -import { Tests, TestsToRun } from '../common/types'; -import { IArgumentsHelper, IUnitTestHelper } from '../types'; - -@injectable() -export class UnitTestHelper implements IUnitTestHelper { - private readonly argsHelper: IArgumentsHelper; - constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - this.argsHelper = serviceContainer.get(IArgumentsHelper); - } - public getStartDirectory(args: string[]): string { - const shortValue = this.argsHelper.getOptionValues(args, '-s'); - if (typeof shortValue === 'string') { - return shortValue; - } - const longValue = this.argsHelper.getOptionValues(args, '--start-directory'); - if (typeof longValue === 'string') { - return longValue; - } - return '.'; - } - public getIdsOfTestsToRun(tests: Tests, testsToRun: TestsToRun): string[] { - const testIds: string[] = []; - if (testsToRun && testsToRun.testFolder) { - // Get test ids of files in these folders. - testsToRun.testFolder.forEach((folder) => { - tests.testFiles.forEach((f) => { - if (f.fullPath.startsWith(folder.name)) { - testIds.push(f.nameToRun); - } - }); - }); - } - if (testsToRun && testsToRun.testFile) { - testIds.push(...testsToRun.testFile.map((f) => f.nameToRun)); - } - if (testsToRun && testsToRun.testSuite) { - testIds.push(...testsToRun.testSuite.map((f) => f.nameToRun)); - } - if (testsToRun && testsToRun.testFunction) { - testIds.push(...testsToRun.testFunction.map((f) => f.nameToRun)); - } - return testIds; - } -} diff --git a/src/client/testing/unittest/main.ts b/src/client/testing/unittest/main.ts deleted file mode 100644 index cb81d3de5f23..000000000000 --- a/src/client/testing/unittest/main.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Uri } from 'vscode'; -import { Product } from '../../common/types'; -import { noop } from '../../common/utils/misc'; -import { IServiceContainer } from '../../ioc/types'; -import { CommandSource, UNITTEST_PROVIDER } from '../common/constants'; -import { BaseTestManager } from '../common/managers/baseTestManager'; -import { ITestsHelper, TestDiscoveryOptions, TestRunOptions, Tests, TestStatus, TestsToRun } from '../common/types'; -import { IArgumentsService, ITestManagerRunner, TestFilter } from '../types'; - -export class TestManager extends BaseTestManager { - private readonly argsService: IArgumentsService; - private readonly helper: ITestsHelper; - private readonly runner: ITestManagerRunner; - public get enabled() { - return this.settings.testing.unittestEnabled; - } - constructor(workspaceFolder: Uri, rootDirectory: string, serviceContainer: IServiceContainer) { - super(UNITTEST_PROVIDER, Product.unittest, workspaceFolder, rootDirectory, serviceContainer); - this.argsService = this.serviceContainer.get(IArgumentsService, this.testProvider); - this.helper = this.serviceContainer.get(ITestsHelper); - this.runner = this.serviceContainer.get(ITestManagerRunner, this.testProvider); - } - public configure() { - noop(); - } - public getDiscoveryOptions(ignoreCache: boolean): TestDiscoveryOptions { - const args = this.settings.testing.unittestArgs.slice(0); - return { - workspaceFolder: this.workspaceFolder, - cwd: this.rootDirectory, - args, - token: this.testDiscoveryCancellationToken!, - ignoreCache, - outChannel: this.outputChannel - }; - } - public async runTest( - cmdSource: CommandSource, - testsToRun?: TestsToRun, - runFailedTests?: boolean, - debug?: boolean - ): Promise { - if (runFailedTests === true && this.tests) { - testsToRun = { testFile: [], testFolder: [], testSuite: [], testFunction: [] }; - testsToRun.testFunction = this.tests.testFunctions - .filter((fn) => { - return fn.testFunction.status === TestStatus.Error || fn.testFunction.status === TestStatus.Fail; - }) - .map((fn) => fn.testFunction); - } - return super.runTest(cmdSource, testsToRun, runFailedTests, debug); - } - public async runTestImpl( - tests: Tests, - testsToRun?: TestsToRun, - _runFailedTests?: boolean, - debug?: boolean - ): Promise { - let args: string[]; - - const runAllTests = this.helper.shouldRunAllTests(testsToRun); - if (debug) { - args = this.argsService.filterArguments( - this.settings.testing.unittestArgs, - runAllTests ? TestFilter.debugAll : TestFilter.debugSpecific - ); - } else { - args = this.argsService.filterArguments( - this.settings.testing.unittestArgs, - runAllTests ? TestFilter.runAll : TestFilter.runSpecific - ); - } - - const options: TestRunOptions = { - workspaceFolder: this.workspaceFolder, - cwd: this.rootDirectory, - tests, - args, - testsToRun, - debug, - token: this.testRunnerCancellationToken!, - outChannel: this.outputChannel - }; - return this.runner.runTest(this.testResultsService, options, this); - } -} diff --git a/src/client/testing/unittest/runner.ts b/src/client/testing/unittest/runner.ts deleted file mode 100644 index 77f23ee3de54..000000000000 --- a/src/client/testing/unittest/runner.ts +++ /dev/null @@ -1,228 +0,0 @@ -'use strict'; - -import { inject, injectable } from 'inversify'; -import { traceError } from '../../common/logger'; -import * as internalScripts from '../../common/process/internal/scripts'; -import { IDisposableRegistry } from '../../common/types'; -import { createDeferred, Deferred } from '../../common/utils/async'; -import { noop } from '../../common/utils/misc'; -import { IServiceContainer } from '../../ioc/types'; -import { UNITTEST_PROVIDER } from '../common/constants'; -import { Options } from '../common/runner'; -import { - ITestDebugLauncher, - ITestManager, - ITestResultsService, - ITestRunner, - IUnitTestSocketServer, - LaunchOptions, - TestRunOptions, - Tests, - TestStatus -} from '../common/types'; -import { IArgumentsHelper, ITestManagerRunner, IUnitTestHelper } from '../types'; - -type TestStatusMap = { - status: TestStatus; - summaryProperty: 'passed' | 'failures' | 'errors' | 'skipped'; -}; - -const outcomeMapping = new Map(); -outcomeMapping.set('passed', { status: TestStatus.Pass, summaryProperty: 'passed' }); -outcomeMapping.set('failed', { status: TestStatus.Fail, summaryProperty: 'failures' }); -outcomeMapping.set('error', { status: TestStatus.Error, summaryProperty: 'errors' }); -outcomeMapping.set('skipped', { status: TestStatus.Skipped, summaryProperty: 'skipped' }); - -interface ITestData { - test: string; - message: string; - outcome: string; - traceback: string; -} - -@injectable() -export class TestManagerRunner implements ITestManagerRunner { - private readonly argsHelper: IArgumentsHelper; - private readonly helper: IUnitTestHelper; - private readonly testRunner: ITestRunner; - private readonly server: IUnitTestSocketServer; - private busy!: Deferred; - - constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { - this.argsHelper = serviceContainer.get(IArgumentsHelper); - this.testRunner = serviceContainer.get(ITestRunner); - this.server = this.serviceContainer.get(IUnitTestSocketServer); - this.helper = this.serviceContainer.get(IUnitTestHelper); - this.serviceContainer.get(IDisposableRegistry).push(this.server); - } - - // tslint:disable-next-line:max-func-body-length - public async runTest( - testResultsService: ITestResultsService, - options: TestRunOptions, - testManager: ITestManager - ): Promise { - if (this.busy && !this.busy.completed) { - return this.busy.promise; - } - this.busy = createDeferred(); - - options.tests.summary.errors = 0; - options.tests.summary.failures = 0; - options.tests.summary.passed = 0; - options.tests.summary.skipped = 0; - let failFast = false; - this.server.on('error', (message: string, ...data: string[]) => traceError(`${message} ${data.join(' ')}`)); - this.server.on('log', noop); - this.server.on('connect', noop); - this.server.on('start', noop); - this.server.on('result', (data: ITestData) => { - const test = options.tests.testFunctions.find((t) => t.testFunction.nameToRun === data.test); - const statusDetails = outcomeMapping.get(data.outcome)!; - if (test) { - test.testFunction.status = statusDetails.status; - switch (test.testFunction.status) { - case TestStatus.Error: - case TestStatus.Fail: { - test.testFunction.passed = false; - break; - } - case TestStatus.Pass: { - test.testFunction.passed = true; - break; - } - default: { - test.testFunction.passed = undefined; - } - } - test.testFunction.message = data.message; - test.testFunction.traceback = data.traceback; - options.tests.summary[statusDetails.summaryProperty] += 1; - - if ( - failFast && - (statusDetails.summaryProperty === 'failures' || statusDetails.summaryProperty === 'errors') - ) { - testManager.stop(); - } - } else { - if (statusDetails) { - options.tests.summary[statusDetails.summaryProperty] += 1; - } - } - }); - - const port = await this.server.start(); - const testPaths: string[] = this.helper.getIdsOfTestsToRun(options.tests, options.testsToRun!); - for (let counter = 0; counter < testPaths.length; counter += 1) { - testPaths[counter] = `-t${testPaths[counter].trim()}`; - } - - const runTestInternal = async (testFile: string = '', testId: string = '') => { - let testArgs = this.buildTestArgs(options.args); - failFast = testArgs.indexOf('--uf') >= 0; - testArgs = testArgs.filter((arg) => arg !== '--uf'); - - testArgs.push(`--result-port=${port}`); - if (testId.length > 0) { - testArgs.push(`-t${testId}`); - } - if (testFile.length > 0) { - testArgs.push(`--testFile=${testFile}`); - } - if (options.debug === true) { - const debugLauncher = this.serviceContainer.get(ITestDebugLauncher); - testArgs.push('--debug'); - const launchOptions: LaunchOptions = { - cwd: options.cwd, - args: testArgs, - token: options.token, - outChannel: options.outChannel, - testProvider: UNITTEST_PROVIDER - }; - return debugLauncher.launchDebugger(launchOptions); - } else { - const args = internalScripts.visualstudio_py_testlauncher(testArgs); - - const runOptions: Options = { - args: args, - cwd: options.cwd, - outChannel: options.outChannel, - token: options.token, - workspaceFolder: options.workspaceFolder - }; - await this.testRunner.run(UNITTEST_PROVIDER, runOptions); - } - }; - - // Test everything. - if (testPaths.length === 0) { - await this.removeListenersAfter(runTestInternal()); - } else { - // Ok, the test runner can only work with one test at a time. - if (options.testsToRun) { - if (Array.isArray(options.testsToRun.testFile)) { - for (const testFile of options.testsToRun.testFile) { - await runTestInternal(testFile.fullPath, testFile.nameToRun); - } - } - if (Array.isArray(options.testsToRun.testSuite)) { - for (const testSuite of options.testsToRun.testSuite) { - const item = options.tests.testSuites.find((t) => t.testSuite === testSuite); - if (item) { - const testFileName = item.parentTestFile.fullPath; - await runTestInternal(testFileName, testSuite.nameToRun); - } - } - } - if (Array.isArray(options.testsToRun.testFunction)) { - for (const testFn of options.testsToRun.testFunction) { - const item = options.tests.testFunctions.find((t) => t.testFunction === testFn); - if (item) { - const testFileName = item.parentTestFile.fullPath; - await runTestInternal(testFileName, testFn.nameToRun); - } - } - } - - await this.removeListenersAfter(Promise.resolve()); - } - } - - testResultsService.updateResults(options.tests); - this.busy.resolve(options.tests); - return options.tests; - } - - // remove all the listeners from the server after all tests are complete, - // and just pass the promise `after` through as we do not want to get in - // the way here. - // tslint:disable-next-line:no-any - private async removeListenersAfter(after: Promise): Promise { - return after - .then(() => this.server.removeAllListeners()) - .catch((err) => { - this.server.removeAllListeners(); - throw err; // keep propagating this downward - }); - } - - private buildTestArgs(args: string[]): string[] { - const startTestDiscoveryDirectory = this.helper.getStartDirectory(args); - let pattern = 'test*.py'; - const shortValue = this.argsHelper.getOptionValues(args, '-p'); - const longValueValue = this.argsHelper.getOptionValues(args, '--pattern'); - if (typeof shortValue === 'string') { - pattern = shortValue; - } else if (typeof longValueValue === 'string') { - pattern = longValueValue; - } - const failFast = args.some((arg) => arg.trim() === '-f' || arg.trim() === '--failfast'); - const verbosity = args.some((arg) => arg.trim().indexOf('-v') === 0) ? 2 : 1; - const testArgs = [`--us=${startTestDiscoveryDirectory}`, `--up=${pattern}`, `--uvInt=${verbosity}`]; - if (failFast) { - testArgs.push('--uf'); - } - return testArgs; - } -} diff --git a/src/client/testing/unittest/services/argsService.ts b/src/client/testing/unittest/services/argsService.ts deleted file mode 100644 index 73e27087a01e..000000000000 --- a/src/client/testing/unittest/services/argsService.ts +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { inject, injectable } from 'inversify'; -import { IServiceContainer } from '../../../ioc/types'; -import { IArgumentsHelper, IArgumentsService, TestFilter } from '../../types'; - -const OptionsWithArguments = ['-k', '-p', '-s', '-t', '--pattern', '--start-directory', '--top-level-directory']; - -const OptionsWithoutArguments = [ - '-b', - '-c', - '-f', - '-h', - '-q', - '-v', - '--buffer', - '--catch', - '--failfast', - '--help', - '--locals', - '--quiet', - '--verbose' -]; - -@injectable() -export class ArgumentsService implements IArgumentsService { - private readonly helper: IArgumentsHelper; - constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - this.helper = serviceContainer.get(IArgumentsHelper); - } - public getKnownOptions(): { withArgs: string[]; withoutArgs: string[] } { - return { - withArgs: OptionsWithArguments, - withoutArgs: OptionsWithoutArguments - }; - } - public getOptionValue(args: string[], option: string): string | string[] | undefined { - return this.helper.getOptionValues(args, option); - } - public filterArguments(args: string[], argumentToRemoveOrFilter: string[] | TestFilter): string[] { - const optionsWithoutArgsToRemove: string[] = []; - const optionsWithArgsToRemove: string[] = []; - // Positional arguments in pytest positional args are test directories and files. - // So if we want to run a specific test, then remove positional args. - let removePositionalArgs = false; - if (Array.isArray(argumentToRemoveOrFilter)) { - argumentToRemoveOrFilter.forEach((item) => { - if (OptionsWithArguments.indexOf(item) >= 0) { - optionsWithArgsToRemove.push(item); - } - if (OptionsWithoutArguments.indexOf(item) >= 0) { - optionsWithoutArgsToRemove.push(item); - } - }); - } else { - removePositionalArgs = true; - } - - let filteredArgs = args.slice(); - if (removePositionalArgs) { - const positionalArgs = this.helper.getPositionalArguments( - filteredArgs, - OptionsWithArguments, - OptionsWithoutArguments - ); - filteredArgs = filteredArgs.filter((item) => positionalArgs.indexOf(item) === -1); - } - return this.helper.filterArguments(filteredArgs, optionsWithArgsToRemove, optionsWithoutArgsToRemove); - } - public getTestFolders(args: string[]): string[] { - const shortValue = this.helper.getOptionValues(args, '-s'); - if (typeof shortValue === 'string') { - return [shortValue]; - } - const longValue = this.helper.getOptionValues(args, '--start-directory'); - if (typeof longValue === 'string') { - return [longValue]; - } - return ['.']; - } -} diff --git a/src/client/testing/unittest/services/discoveryService.ts b/src/client/testing/unittest/services/discoveryService.ts deleted file mode 100644 index f09901e37f5f..000000000000 --- a/src/client/testing/unittest/services/discoveryService.ts +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable, named } from 'inversify'; -import * as internalPython from '../../../common/process/internal/python'; -import { IServiceContainer } from '../../../ioc/types'; -import { UNITTEST_PROVIDER } from '../../common/constants'; -import { Options } from '../../common/runner'; -import { ITestDiscoveryService, ITestRunner, ITestsParser, TestDiscoveryOptions, Tests } from '../../common/types'; -import { IArgumentsHelper } from '../../types'; - -type UnitTestDiscoveryOptions = TestDiscoveryOptions & { - startDirectory: string; - pattern: string; -}; - -@injectable() -export class TestDiscoveryService implements ITestDiscoveryService { - private readonly argsHelper: IArgumentsHelper; - private readonly runner: ITestRunner; - constructor( - @inject(IServiceContainer) serviceContainer: IServiceContainer, - @inject(ITestsParser) @named(UNITTEST_PROVIDER) private testParser: ITestsParser - ) { - this.argsHelper = serviceContainer.get(IArgumentsHelper); - this.runner = serviceContainer.get(ITestRunner); - } - public async discoverTests(options: TestDiscoveryOptions): Promise { - const pythonScript = this.getDiscoveryScript(options); - const unitTestOptions = this.translateOptions(options); - const runOptions: Options = { - // unittest needs to load modules in the workspace - // isolating it breaks unittest discovery - args: internalPython.execCode(pythonScript, false), - cwd: options.cwd, - workspaceFolder: options.workspaceFolder, - token: options.token, - outChannel: options.outChannel - }; - - const data = await this.runner.run(UNITTEST_PROVIDER, runOptions); - - if (options.token && options.token.isCancellationRequested) { - return Promise.reject('cancelled'); - } - - return this.testParser.parse(data, unitTestOptions); - } - public getDiscoveryScript(options: TestDiscoveryOptions): string { - const unitTestOptions = this.translateOptions(options); - return ` -import unittest -loader = unittest.TestLoader() -suites = loader.discover("${unitTestOptions.startDirectory}", pattern="${unitTestOptions.pattern}") -print("start") #Don't remove this line -for suite in suites._tests: - for cls in suite._tests: - try: - for m in cls._tests: - print(m.id()) - except: - pass`; - } - public translateOptions(options: TestDiscoveryOptions): UnitTestDiscoveryOptions { - return { - ...options, - startDirectory: this.getStartDirectory(options), - pattern: this.getTestPattern(options) - }; - } - private getStartDirectory(options: TestDiscoveryOptions) { - const shortValue = this.argsHelper.getOptionValues(options.args, '-s'); - if (typeof shortValue === 'string') { - return shortValue; - } - const longValue = this.argsHelper.getOptionValues(options.args, '--start-directory'); - if (typeof longValue === 'string') { - return longValue; - } - return '.'; - } - private getTestPattern(options: TestDiscoveryOptions) { - const shortValue = this.argsHelper.getOptionValues(options.args, '-p'); - if (typeof shortValue === 'string') { - return shortValue; - } - const longValue = this.argsHelper.getOptionValues(options.args, '--pattern'); - if (typeof longValue === 'string') { - return longValue; - } - return 'test*.py'; - } -} diff --git a/src/client/testing/unittest/services/parserService.ts b/src/client/testing/unittest/services/parserService.ts deleted file mode 100644 index 41ad7c9fa28c..000000000000 --- a/src/client/testing/unittest/services/parserService.ts +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { Uri } from 'vscode'; -import { - ITestsHelper, - ITestsParser, - TestFile, - TestFunction, - Tests, - TestStatus, - UnitTestParserOptions -} from '../../common/types'; - -@injectable() -export class TestsParser implements ITestsParser { - constructor(@inject(ITestsHelper) private testsHelper: ITestsHelper) {} - public parse(content: string, options: UnitTestParserOptions): Tests { - const testIds = this.getTestIds(content); - let testsDirectory = options.cwd; - if (options.startDirectory.length > 1) { - testsDirectory = path.isAbsolute(options.startDirectory) - ? options.startDirectory - : path.resolve(options.cwd, options.startDirectory); - } - return this.parseTestIds(options.cwd, testsDirectory, testIds); - } - private getTestIds(content: string): string[] { - let startedCollecting = false; - return content - .split(/\r?\n/g) - .map((line) => { - if (!startedCollecting) { - if (line === 'start') { - startedCollecting = true; - } - return ''; - } - return line.trim(); - }) - .filter((line) => line.length > 0); - } - private parseTestIds(workspaceDirectory: string, testsDirectory: string, testIds: string[]): Tests { - const testFiles: TestFile[] = []; - testIds.forEach((testId) => this.addTestId(testsDirectory, testId, testFiles)); - - return this.testsHelper.flattenTestFiles(testFiles, workspaceDirectory); - } - - /** - * Add the test Ids into the array provided. - * TestIds are fully qualified including the method names. - * E.g. tone_test.Failing2Tests.test_failure - * Where tone_test = folder, Failing2Tests = class/suite, test_failure = method. - * @private - * @param {string} rootDirectory - * @param {string} testId - * @param {TestFile[]} testFiles - * @returns {Tests} - * @memberof TestsParser - */ - private addTestId(rootDirectory: string, testId: string, testFiles: TestFile[]) { - const testIdParts = testId.split('.'); - // We must have a file, class and function name - if (testIdParts.length <= 2) { - return null; - } - - const paths = testIdParts.slice(0, testIdParts.length - 2); - const filePath = `${path.join(rootDirectory, ...paths)}.py`; - const functionName = testIdParts.pop()!; - const suiteToRun = testIdParts.join('.'); - const className = testIdParts.pop()!; - const moduleName = testIdParts.join('.'); - const resource = Uri.file(rootDirectory); - - // Check if we already have this test file - let testFile = testFiles.find((test) => test.fullPath === filePath); - if (!testFile) { - testFile = { - resource, - name: path.basename(filePath), - fullPath: filePath, - functions: [], - suites: [], - nameToRun: moduleName, - xmlName: '', - status: TestStatus.Idle, - time: 0 - }; - testFiles.push(testFile); - } - - // Check if we already have this suite - // nameToRun = testId - method name - let testSuite = testFile.suites.find((cls) => cls.nameToRun === suiteToRun); - if (!testSuite) { - testSuite = { - resource, - name: className, - functions: [], - suites: [], - isUnitTest: true, - isInstance: false, - nameToRun: suiteToRun, - xmlName: '', - status: TestStatus.Idle, - time: 0 - }; - testFile.suites.push(testSuite!); - } - - const testFunction: TestFunction = { - resource, - name: functionName, - nameToRun: testId, - status: TestStatus.Idle, - time: 0 - }; - - testSuite!.functions.push(testFunction); - } -} diff --git a/src/client/testing/unittest/socketServer.ts b/src/client/testing/unittest/socketServer.ts deleted file mode 100644 index 9dc23b87e33a..000000000000 --- a/src/client/testing/unittest/socketServer.ts +++ /dev/null @@ -1,125 +0,0 @@ -'use strict'; -import { EventEmitter } from 'events'; -import { injectable } from 'inversify'; -import * as net from 'net'; -import { createDeferred, Deferred } from '../../common/utils/async'; -import { IUnitTestSocketServer } from '../common/types'; - -// tslint:disable:variable-name no-any -const MaxConnections = 100; - -@injectable() -export class UnitTestSocketServer extends EventEmitter implements IUnitTestSocketServer { - private server?: net.Server; - private startedDef?: Deferred; - private sockets: net.Socket[] = []; - private ipcBuffer: string = ''; - constructor() { - super(); - } - public get clientsConnected(): boolean { - return this.sockets.length > 0; - } - public dispose() { - this.stop(); - } - public stop() { - if (this.server) { - this.server!.close(); - this.server = undefined; - } - } - public start(options: { port?: number; host?: string } = { port: 0, host: 'localhost' }): Promise { - this.ipcBuffer = ''; - this.startedDef = createDeferred(); - this.server = net.createServer(this.connectionListener.bind(this)); - this.server!.maxConnections = MaxConnections; - this.server!.on('error', (err) => { - if (this.startedDef) { - this.startedDef.reject(err); - this.startedDef = undefined; - } - this.emit('error', err); - }); - this.log('starting server as', 'TCP'); - options.port = typeof options.port === 'number' ? options.port! : 0; - options.host = - typeof options.host === 'string' && options.host!.trim().length > 0 ? options.host!.trim() : 'localhost'; - this.server!.listen(options, (socket: net.Socket) => { - this.startedDef!.resolve((this.server!.address() as net.AddressInfo).port); - this.startedDef = undefined; - this.emit('start', socket); - }); - return this.startedDef!.promise; - } - - private connectionListener(socket: net.Socket) { - this.sockets.push(socket); - socket.setEncoding('utf8'); - this.log('## socket connection to server detected ##'); - socket.on('close', () => { - this.ipcBuffer = ''; - this.onCloseSocket(); - }); - socket.on('error', (err) => { - this.log('server socket error', err); - this.emit('error', err); - }); - socket.on('data', (data) => { - const sock = socket; - // Assume we have just one client socket connection - let dataStr = (this.ipcBuffer += data); - - // tslint:disable-next-line:no-constant-condition - while (true) { - const startIndex = dataStr.indexOf('{'); - if (startIndex === -1) { - return; - } - const lengthOfMessage = parseInt( - dataStr.slice(dataStr.indexOf(':') + 1, dataStr.indexOf('{')).trim(), - 10 - ); - if (dataStr.length < startIndex + lengthOfMessage) { - return; - } - // tslint:disable-next-line:no-any - let message: any; - try { - message = JSON.parse(dataStr.substring(startIndex, lengthOfMessage + startIndex)); - } catch (jsonErr) { - this.emit('error', jsonErr); - return; - } - dataStr = this.ipcBuffer = dataStr.substring(startIndex + lengthOfMessage); - this.emit(message.event, message.body, sock); - } - }); - this.emit('connect', socket); - } - private log(message: string, ...data: any[]) { - this.emit('log', message, ...data); - } - private onCloseSocket() { - // tslint:disable-next-line:one-variable-per-declaration - for (let i = 0, count = this.sockets.length; i < count; i += 1) { - const socket = this.sockets[i]; - let destroyedSocketId = false; - if (socket && socket.readable) { - continue; - } - // tslint:disable-next-line:no-any prefer-type-cast - if ((socket as any).id) { - // tslint:disable-next-line:no-any prefer-type-cast - destroyedSocketId = (socket as any).id; - } - this.log('socket disconnected', destroyedSocketId.toString()); - if (socket && socket.destroy) { - socket.destroy(); - } - this.sockets.splice(i, 1); - this.emit('socket.disconnected', socket, destroyedSocketId); - return; - } - } -} diff --git a/src/client/testing/unittest/testConfigurationManager.ts b/src/client/testing/unittest/testConfigurationManager.ts deleted file mode 100644 index df35f70d2365..000000000000 --- a/src/client/testing/unittest/testConfigurationManager.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Uri } from 'vscode'; -import { Product } from '../../common/types'; -import { IServiceContainer } from '../../ioc/types'; -import { TestConfigurationManager } from '../common/managers/testConfigurationManager'; -import { ITestConfigSettingsService } from '../types'; - -export class ConfigurationManager extends TestConfigurationManager { - constructor(workspace: Uri, serviceContainer: IServiceContainer, cfg?: ITestConfigSettingsService) { - super(workspace, Product.unittest, serviceContainer, cfg); - } - public async requiresUserToConfigure(_wkspace: Uri): Promise { - return true; - } - public async configure(wkspace: Uri) { - const args = ['-v']; - const subDirs = await this.getTestDirs(wkspace.fsPath); - const testDir = await this.selectTestDir(wkspace.fsPath, subDirs); - args.push('-s'); - if (typeof testDir === 'string' && testDir !== '.') { - args.push(`./${testDir}`); - } else { - args.push('.'); - } - - const testfilePattern = await this.selectTestFilePattern(); - args.push('-p'); - if (typeof testfilePattern === 'string') { - args.push(testfilePattern); - } else { - args.push('test*.py'); - } - await this.testConfigSettingsService.updateTestArgs(wkspace.fsPath, Product.unittest, args); - } -} diff --git a/src/client/testing/utils.ts b/src/client/testing/utils.ts new file mode 100644 index 000000000000..c1027d4a8dc1 --- /dev/null +++ b/src/client/testing/utils.ts @@ -0,0 +1,49 @@ +import { TestItem, env } from 'vscode'; +import { traceLog } from '../logging'; + +export async function writeTestIdToClipboard(testItem: TestItem): Promise { + if (testItem && typeof testItem.id === 'string') { + if (testItem.id.includes('\\') && testItem.id.indexOf('::') === -1) { + // Convert the id to a module.class.method format as this is a unittest + const moduleClassMethod = idToModuleClassMethod(testItem.id); + if (moduleClassMethod) { + await env.clipboard.writeText(moduleClassMethod); + traceLog('Testing: Copied test id to clipboard, id: ' + moduleClassMethod); + return; + } + } + // Otherwise use the id as is for pytest + await clipboardWriteText(testItem.id); + traceLog('Testing: Copied test id to clipboard, id: ' + testItem.id); + } +} + +export function idToModuleClassMethod(id: string): string | undefined { + // Split by backslash + const parts = id.split('\\'); + if (parts.length === 1) { + // Only one part, likely a parent folder or file + return parts[0]; + } + if (parts.length === 2) { + // Two parts: filePath and className + const [filePath, className] = parts.slice(-2); + const fileName = filePath.split(/[\\/]/).pop(); + if (!fileName) { + return undefined; + } + const module = fileName.replace(/\.py$/, ''); + return `${module}.${className}`; + } + // Three or more parts: filePath, className, methodName + const [filePath, className, methodName] = parts.slice(-3); + const fileName = filePath.split(/[\\/]/).pop(); + if (!fileName) { + return undefined; + } + const module = fileName.replace(/\.py$/, ''); + return `${module}.${className}.${methodName}`; +} +export function clipboardWriteText(text: string): Thenable { + return env.clipboard.writeText(text); +} diff --git a/src/client/typeFormatters/blockFormatProvider.ts b/src/client/typeFormatters/blockFormatProvider.ts deleted file mode 100644 index 822caa5632e9..000000000000 --- a/src/client/typeFormatters/blockFormatProvider.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { - CancellationToken, - FormattingOptions, - OnTypeFormattingEditProvider, - Position, - TextDocument, - TextEdit -} from 'vscode'; -import { CodeBlockFormatProvider } from './codeBlockFormatProvider'; -import { - ASYNC_DEF_REGEX, - ASYNC_FOR_IN_REGEX, - CLASS_REGEX, - DEF_REGEX, - ELIF_REGEX, - ELSE_REGEX, - EXCEPT_REGEX, - FINALLY_REGEX, - FOR_IN_REGEX, - IF_REGEX, - TRY_REGEX, - WHILE_REGEX -} from './contracts'; - -export class BlockFormatProviders implements OnTypeFormattingEditProvider { - private providers: CodeBlockFormatProvider[]; - constructor() { - this.providers = []; - const boundaryBlocks = [DEF_REGEX, ASYNC_DEF_REGEX, CLASS_REGEX]; - - const elseParentBlocks = [ - IF_REGEX, - ELIF_REGEX, - FOR_IN_REGEX, - ASYNC_FOR_IN_REGEX, - WHILE_REGEX, - TRY_REGEX, - EXCEPT_REGEX - ]; - this.providers.push(new CodeBlockFormatProvider(ELSE_REGEX, elseParentBlocks, boundaryBlocks)); - - const elifParentBlocks = [IF_REGEX, ELIF_REGEX]; - this.providers.push(new CodeBlockFormatProvider(ELIF_REGEX, elifParentBlocks, boundaryBlocks)); - - const exceptParentBlocks = [TRY_REGEX, EXCEPT_REGEX]; - this.providers.push(new CodeBlockFormatProvider(EXCEPT_REGEX, exceptParentBlocks, boundaryBlocks)); - - const finallyParentBlocks = [TRY_REGEX, EXCEPT_REGEX]; - this.providers.push(new CodeBlockFormatProvider(FINALLY_REGEX, finallyParentBlocks, boundaryBlocks)); - } - - public provideOnTypeFormattingEdits( - document: TextDocument, - position: Position, - ch: string, - options: FormattingOptions, - _token: CancellationToken - ): TextEdit[] { - if (position.line === 0) { - return []; - } - - const currentLine = document.lineAt(position.line); - const prevousLine = document.lineAt(position.line - 1); - - // We're only interested in cases where the current block is at the same indentation level as the previous line - // E.g. if we have an if..else block, generally the else statement would be at the same level as the code in the if... - if (currentLine.firstNonWhitespaceCharacterIndex !== prevousLine.firstNonWhitespaceCharacterIndex) { - return []; - } - - const currentLineText = currentLine.text; - const provider = this.providers.find((p) => p.canProvideEdits(currentLineText)); - if (provider) { - return provider.provideEdits(document, position, ch, options, currentLine); - } - - return []; - } -} diff --git a/src/client/typeFormatters/codeBlockFormatProvider.ts b/src/client/typeFormatters/codeBlockFormatProvider.ts deleted file mode 100644 index 25d677003bb9..000000000000 --- a/src/client/typeFormatters/codeBlockFormatProvider.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { FormattingOptions, Position, Range, TextDocument, TextEdit, TextLine } from 'vscode'; -import { BlockRegEx } from './contracts'; - -export class CodeBlockFormatProvider { - constructor( - private blockRegExp: BlockRegEx, - private previousBlockRegExps: BlockRegEx[], - private boundaryRegExps: BlockRegEx[] - ) {} - public canProvideEdits(line: string): boolean { - return this.blockRegExp.test(line); - } - - public provideEdits( - document: TextDocument, - position: Position, - _ch: string, - options: FormattingOptions, - line: TextLine - ): TextEdit[] { - // We can have else for the following blocks: - // if: - // elif x: - // for x in y: - // while x: - - // We need to find a block statement that is less than or equal to this statement block (but not greater) - for (let lineNumber = position.line - 1; lineNumber >= 0; lineNumber -= 1) { - const prevLine = document.lineAt(lineNumber); - const prevLineText = prevLine.text; - - // Oops, we've reached a boundary (like the function or class definition) - // Get out of here - if (this.boundaryRegExps.some((value) => value.test(prevLineText))) { - return []; - } - - const blockRegEx = this.previousBlockRegExps.find((value) => value.test(prevLineText)); - if (!blockRegEx) { - continue; - } - - const startOfBlockInLine = prevLine.firstNonWhitespaceCharacterIndex; - if (startOfBlockInLine > line.firstNonWhitespaceCharacterIndex) { - continue; - } - - const startPosition = new Position(position.line, 0); - const endPosition = new Position(position.line, line.firstNonWhitespaceCharacterIndex - startOfBlockInLine); - - if (startPosition.isEqual(endPosition)) { - // current block cannot be at the same level as a preivous block - continue; - } - - if (options.insertSpaces) { - return [TextEdit.delete(new Range(startPosition, endPosition))]; - } else { - // Delete everything before the block and insert the same characters we have in the previous block - const prefixOfPreviousBlock = prevLineText.substring(0, startOfBlockInLine); - - const startDeletePosition = new Position(position.line, 0); - const endDeletePosition = new Position(position.line, line.firstNonWhitespaceCharacterIndex); - - return [ - TextEdit.delete(new Range(startDeletePosition, endDeletePosition)), - TextEdit.insert(startDeletePosition, prefixOfPreviousBlock) - ]; - } - } - - return []; - } -} diff --git a/src/client/typeFormatters/contracts.ts b/src/client/typeFormatters/contracts.ts deleted file mode 100644 index 62bf68fd46a3..000000000000 --- a/src/client/typeFormatters/contracts.ts +++ /dev/null @@ -1,21 +0,0 @@ -export class BlockRegEx { - constructor(private regEx: RegExp, public startWord: String) {} - public test(value: string): boolean { - // Clear the cache - this.regEx.lastIndex = -1; - return this.regEx.test(value); - } -} - -export const IF_REGEX = new BlockRegEx(/^( |\t)*if +.*: *$/g, 'if'); -export const ELIF_REGEX = new BlockRegEx(/^( |\t)*elif +.*: *$/g, 'elif'); -export const ELSE_REGEX = new BlockRegEx(/^( |\t)*else *: *$/g, 'else'); -export const FOR_IN_REGEX = new BlockRegEx(/^( |\t)*for \w in .*: *$/g, 'for'); -export const ASYNC_FOR_IN_REGEX = new BlockRegEx(/^( |\t)*async *for \w in .*: *$/g, 'for'); -export const WHILE_REGEX = new BlockRegEx(/^( |\t)*while .*: *$/g, 'while'); -export const TRY_REGEX = new BlockRegEx(/^( |\t)*try *: *$/g, 'try'); -export const FINALLY_REGEX = new BlockRegEx(/^( |\t)*finally *: *$/g, 'finally'); -export const EXCEPT_REGEX = new BlockRegEx(/^( |\t)*except *\w* *(as)? *\w* *: *$/g, 'except'); -export const DEF_REGEX = new BlockRegEx(/^( |\t)*def \w *\(.*$/g, 'def'); -export const ASYNC_DEF_REGEX = new BlockRegEx(/^( |\t)*async *def \w *\(.*$/g, 'async'); -export const CLASS_REGEX = new BlockRegEx(/^( |\t)*class *\w* *.*: *$/g, 'class'); diff --git a/src/client/typeFormatters/dispatcher.ts b/src/client/typeFormatters/dispatcher.ts deleted file mode 100644 index 452eb2f38d79..000000000000 --- a/src/client/typeFormatters/dispatcher.ts +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { - CancellationToken, - FormattingOptions, - OnTypeFormattingEditProvider, - Position, - ProviderResult, - TextDocument, - TextEdit -} from 'vscode'; - -export class OnTypeFormattingDispatcher implements OnTypeFormattingEditProvider { - private readonly providers: Record; - - constructor(providers: Record) { - this.providers = providers; - } - - public provideOnTypeFormattingEdits( - document: TextDocument, - position: Position, - ch: string, - options: FormattingOptions, - cancellationToken: CancellationToken - ): ProviderResult { - const provider = this.providers[ch]; - - if (provider) { - return provider.provideOnTypeFormattingEdits(document, position, ch, options, cancellationToken); - } - - return []; - } - - public getTriggerCharacters(): { first: string; more: string[] } | undefined { - const keys = Object.keys(this.providers); - keys.sort(); // Make output deterministic - - const first = keys.shift(); - - if (first) { - return { - first: first, - more: keys - }; - } - - return undefined; - } -} diff --git a/src/client/typeFormatters/onEnterFormatter.ts b/src/client/typeFormatters/onEnterFormatter.ts deleted file mode 100644 index f4464b26cf6b..000000000000 --- a/src/client/typeFormatters/onEnterFormatter.ts +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { - CancellationToken, - FormattingOptions, - OnTypeFormattingEditProvider, - Position, - TextDocument, - TextEdit -} from 'vscode'; -import { LineFormatter } from '../formatters/lineFormatter'; -import { TokenizerMode, TokenType } from '../language/types'; -import { getDocumentTokens } from '../providers/providerUtilities'; - -export class OnEnterFormatter implements OnTypeFormattingEditProvider { - private readonly formatter = new LineFormatter(); - - public provideOnTypeFormattingEdits( - document: TextDocument, - position: Position, - _ch: string, - _options: FormattingOptions, - _cancellationToken: CancellationToken - ): TextEdit[] { - if (position.line === 0) { - return []; - } - - // Check case when the entire line belongs to a comment or string - const prevLine = document.lineAt(position.line - 1); - const tokens = getDocumentTokens(document, position, TokenizerMode.CommentsAndStrings); - const lineStartTokenIndex = tokens.getItemContaining(document.offsetAt(prevLine.range.start)); - const lineEndTokenIndex = tokens.getItemContaining(document.offsetAt(prevLine.range.end)); - if (lineStartTokenIndex >= 0 && lineStartTokenIndex === lineEndTokenIndex) { - const token = tokens.getItemAt(lineStartTokenIndex); - if (token.type === TokenType.Semicolon || token.type === TokenType.String) { - return []; - } - } - const formatted = this.formatter.formatLine(document, prevLine.lineNumber); - if (formatted === prevLine.text) { - return []; - } - return [new TextEdit(prevLine.range, formatted)]; - } -} diff --git a/src/client/types.ts b/src/client/types.ts new file mode 100644 index 000000000000..8235263e7bab --- /dev/null +++ b/src/client/types.ts @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +export type IStartupDurations = Record< + 'totalNonBlockingActivateTime' | 'totalActivateTime' | 'startActivateTime' | 'codeLoadingTime', + number +>; diff --git a/src/client/workspaceSymbols/contracts.ts b/src/client/workspaceSymbols/contracts.ts deleted file mode 100644 index ae447a4e2fbb..000000000000 --- a/src/client/workspaceSymbols/contracts.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Position, SymbolKind } from 'vscode'; - -export interface ITag { - fileName: string; - symbolName: string; - symbolKind: SymbolKind; - position: Position; - code: string; -} diff --git a/src/client/workspaceSymbols/generator.ts b/src/client/workspaceSymbols/generator.ts deleted file mode 100644 index e90db302d26f..000000000000 --- a/src/client/workspaceSymbols/generator.ts +++ /dev/null @@ -1,99 +0,0 @@ -import * as path from 'path'; -import { Disposable, OutputChannel, Uri } from 'vscode'; -import { IApplicationShell } from '../common/application/types'; -import { IFileSystem } from '../common/platform/types'; -import { IProcessServiceFactory } from '../common/process/types'; -import { IConfigurationService, IPythonSettings } from '../common/types'; -import { EXTENSION_ROOT_DIR } from '../constants'; -import { captureTelemetry } from '../telemetry'; -import { EventName } from '../telemetry/constants'; - -export class Generator implements Disposable { - private optionsFile: string; - private disposables: Disposable[]; - private pythonSettings: IPythonSettings; - public get tagFilePath(): string { - return this.pythonSettings.workspaceSymbols.tagFilePath; - } - public get enabled(): boolean { - return this.pythonSettings.workspaceSymbols.enabled; - } - constructor( - public readonly workspaceFolder: Uri, - private readonly output: OutputChannel, - private readonly appShell: IApplicationShell, - private readonly fs: IFileSystem, - private readonly processServiceFactory: IProcessServiceFactory, - configurationService: IConfigurationService - ) { - this.disposables = []; - this.optionsFile = path.join(EXTENSION_ROOT_DIR, 'resources', 'ctagOptions'); - this.pythonSettings = configurationService.getSettings(workspaceFolder); - } - - public dispose() { - this.disposables.forEach((d) => d.dispose()); - } - public async generateWorkspaceTags(): Promise { - if (!this.pythonSettings.workspaceSymbols.enabled) { - return; - } - return this.generateTags({ directory: this.workspaceFolder.fsPath }); - } - private buildCmdArgs(): string[] { - const exclusions = this.pythonSettings.workspaceSymbols.exclusionPatterns; - const excludes = exclusions.length === 0 ? [] : exclusions.map((pattern) => `--exclude=${pattern}`); - - return [`--options=${this.optionsFile}`, '--languages=Python'].concat(excludes); - } - @captureTelemetry(EventName.WORKSPACE_SYMBOLS_BUILD) - private async generateTags(source: { directory?: string; file?: string }): Promise { - const tagFile = path.normalize(this.pythonSettings.workspaceSymbols.tagFilePath); - const cmd = this.pythonSettings.workspaceSymbols.ctagsPath; - const args = this.buildCmdArgs(); - let outputFile = tagFile; - if (source.file && source.file.length > 0) { - source.directory = path.dirname(source.file); - } - - if (path.dirname(outputFile) === source.directory) { - outputFile = path.basename(outputFile); - } - const outputDir = path.dirname(outputFile); - if (!(await this.fs.directoryExists(outputDir))) { - await this.fs.createDirectory(outputDir); - } - args.push('-o', outputFile, '.'); - this.output.appendLine(`${'-'.repeat(10)}Generating Tags${'-'.repeat(10)}`); - this.output.appendLine(`${cmd} ${args.join(' ')}`); - const promise = new Promise(async (resolve, reject) => { - try { - const processService = await this.processServiceFactory.create(); - const result = processService.execObservable(cmd, args, { cwd: source.directory }); - let errorMsg = ''; - result.out.subscribe( - (output) => { - if (output.source === 'stderr') { - errorMsg += output.out; - } - this.output.append(output.out); - }, - reject, - () => { - if (errorMsg.length > 0) { - reject(new Error(errorMsg)); - } else { - resolve(); - } - } - ); - } catch (ex) { - reject(ex); - } - }); - - this.appShell.setStatusBarMessage('Generating Tags', promise); - - await promise; - } -} diff --git a/src/client/workspaceSymbols/main.ts b/src/client/workspaceSymbols/main.ts deleted file mode 100644 index 256373af7a9c..000000000000 --- a/src/client/workspaceSymbols/main.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { CancellationToken, Disposable, languages, OutputChannel, TextDocument } from 'vscode'; -import { IApplicationShell, ICommandManager, IDocumentManager, IWorkspaceService } from '../common/application/types'; -import { Commands, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; -import { isNotInstalledError } from '../common/helpers'; -import { IFileSystem } from '../common/platform/types'; -import { IProcessServiceFactory } from '../common/process/types'; -import { IConfigurationService, IInstaller, InstallerResponse, IOutputChannel, Product } from '../common/types'; -import { IServiceContainer } from '../ioc/types'; -import { Generator } from './generator'; -import { WorkspaceSymbolProvider } from './provider'; - -const MAX_NUMBER_OF_ATTEMPTS_TO_INSTALL_AND_BUILD = 2; - -export class WorkspaceSymbols implements Disposable { - private disposables: Disposable[]; - private generators: Generator[] = []; - private readonly outputChannel: OutputChannel; - private commandMgr: ICommandManager; - private fs: IFileSystem; - private workspace: IWorkspaceService; - private processFactory: IProcessServiceFactory; - private appShell: IApplicationShell; - private configurationService: IConfigurationService; - private documents: IDocumentManager; - - constructor(private serviceContainer: IServiceContainer) { - this.outputChannel = this.serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); - this.commandMgr = this.serviceContainer.get(ICommandManager); - this.fs = this.serviceContainer.get(IFileSystem); - this.workspace = this.serviceContainer.get(IWorkspaceService); - this.processFactory = this.serviceContainer.get(IProcessServiceFactory); - this.appShell = this.serviceContainer.get(IApplicationShell); - this.configurationService = this.serviceContainer.get(IConfigurationService); - this.documents = this.serviceContainer.get(IDocumentManager); - this.disposables = []; - this.disposables.push(this.outputChannel); - this.registerCommands(); - this.initializeGenerators(); - languages.registerWorkspaceSymbolProvider( - new WorkspaceSymbolProvider(this.fs, this.commandMgr, this.generators) - ); - this.disposables.push(this.workspace.onDidChangeWorkspaceFolders(() => this.initializeGenerators())); - this.disposables.push(this.documents.onDidSaveTextDocument((e) => this.onDocumentSaved(e))); - this.buildSymbolsOnStart(); - } - public dispose() { - this.disposables.forEach((d) => d.dispose()); - } - private initializeGenerators() { - while (this.generators.length > 0) { - const generator = this.generators.shift()!; - generator.dispose(); - } - - if (Array.isArray(this.workspace.workspaceFolders)) { - this.workspace.workspaceFolders.forEach((wkSpc) => { - this.generators.push( - new Generator( - wkSpc.uri, - this.outputChannel, - this.appShell, - this.fs, - this.processFactory, - this.configurationService - ) - ); - }); - } - } - - private buildSymbolsOnStart() { - if (Array.isArray(this.workspace.workspaceFolders)) { - this.workspace.workspaceFolders.forEach((workspaceFolder) => { - const pythonSettings = this.configurationService.getSettings(workspaceFolder.uri); - if (pythonSettings.workspaceSymbols.rebuildOnStart) { - const promises = this.buildWorkspaceSymbols(true); - return Promise.all(promises); - } - }); - } - } - - private registerCommands() { - this.disposables.push( - this.commandMgr.registerCommand( - Commands.Build_Workspace_Symbols, - async (rebuild: boolean = true, token?: CancellationToken) => { - const promises = this.buildWorkspaceSymbols(rebuild, token); - return Promise.all(promises); - } - ) - ); - } - - private onDocumentSaved(document: TextDocument) { - const workspaceFolder = this.workspace.getWorkspaceFolder(document.uri); - const pythonSettings = this.configurationService.getSettings(workspaceFolder?.uri); - if (pythonSettings.workspaceSymbols.rebuildOnFileSave) { - const promises = this.buildWorkspaceSymbols(true); - return Promise.all(promises); - } - } - - // tslint:disable-next-line:no-any - private buildWorkspaceSymbols(rebuild: boolean = true, token?: CancellationToken): Promise[] { - if (token && token.isCancellationRequested) { - return []; - } - if (this.generators.length === 0) { - return []; - } - - let promptPromise: Promise; - let promptResponse: InstallerResponse; - return this.generators.map(async (generator) => { - if (!generator.enabled) { - return; - } - const exists = await this.fs.fileExists(generator.tagFilePath); - // If file doesn't exist, then run the ctag generator, - // or check if required to rebuild. - if (!rebuild && exists) { - return; - } - for (let counter = 0; counter < MAX_NUMBER_OF_ATTEMPTS_TO_INSTALL_AND_BUILD; counter += 1) { - try { - await generator.generateWorkspaceTags(); - return; - } catch (error) { - if (!isNotInstalledError(error)) { - return; - } - } - if (!token || token.isCancellationRequested) { - return; - } - // Display prompt once for all workspaces. - if (promptPromise) { - promptResponse = await promptPromise; - continue; - } else { - const installer = this.serviceContainer.get(IInstaller); - promptPromise = installer.promptToInstall(Product.ctags, this.workspace.workspaceFolders![0]!.uri); - promptResponse = await promptPromise; - } - if (promptResponse !== InstallerResponse.Installed || !token || token.isCancellationRequested) { - return; - } - } - }); - } -} diff --git a/src/client/workspaceSymbols/parser.ts b/src/client/workspaceSymbols/parser.ts deleted file mode 100644 index 7ec49ba082ce..000000000000 --- a/src/client/workspaceSymbols/parser.ts +++ /dev/null @@ -1,172 +0,0 @@ -import * as path from 'path'; -import * as vscode from 'vscode'; -import { IFileSystem } from '../common/platform/types'; -import { ITag } from './contracts'; - -// tslint:disable:no-require-imports no-var-requires no-suspicious-comment -// tslint:disable:no-any -// TODO: Turn these into imports. -const LineByLineReader = require('line-by-line'); -const NamedRegexp = require('named-js-regexp'); -const fuzzy = require('fuzzy'); - -const IsFileRegEx = /\tkind:file\tline:\d+$/g; -const LINE_REGEX = '(?\\w+)\\t(?.*)\\t\\/\\^(?.*)\\$\\/;"\\tkind:(?\\w+)\\tline:(?\\d+)$'; - -export interface IRegexGroup { - name: string; - file: string; - code: string; - type: string; - line: number; -} - -export function matchNamedRegEx(data: String, regex: String): IRegexGroup | null { - const compiledRegexp = NamedRegexp(regex, 'g'); - const rawMatch = compiledRegexp.exec(data); - if (rawMatch !== null) { - return rawMatch.groups(); - } - - return null; -} - -const CTagKinMapping = new Map(); -CTagKinMapping.set('_array', vscode.SymbolKind.Array); -CTagKinMapping.set('_boolean', vscode.SymbolKind.Boolean); -CTagKinMapping.set('_class', vscode.SymbolKind.Class); -CTagKinMapping.set('_classes', vscode.SymbolKind.Class); -CTagKinMapping.set('_constant', vscode.SymbolKind.Constant); -CTagKinMapping.set('_constants', vscode.SymbolKind.Constant); -CTagKinMapping.set('_constructor', vscode.SymbolKind.Constructor); -CTagKinMapping.set('_enum', vscode.SymbolKind.Enum); -CTagKinMapping.set('_enums', vscode.SymbolKind.Enum); -CTagKinMapping.set('_enumeration', vscode.SymbolKind.Enum); -CTagKinMapping.set('_enumerations', vscode.SymbolKind.Enum); -CTagKinMapping.set('_field', vscode.SymbolKind.Field); -CTagKinMapping.set('_fields', vscode.SymbolKind.Field); -CTagKinMapping.set('_file', vscode.SymbolKind.File); -CTagKinMapping.set('_files', vscode.SymbolKind.File); -CTagKinMapping.set('_function', vscode.SymbolKind.Function); -CTagKinMapping.set('_functions', vscode.SymbolKind.Function); -CTagKinMapping.set('_member', vscode.SymbolKind.Function); -CTagKinMapping.set('_interface', vscode.SymbolKind.Interface); -CTagKinMapping.set('_interfaces', vscode.SymbolKind.Interface); -CTagKinMapping.set('_key', vscode.SymbolKind.Key); -CTagKinMapping.set('_keys', vscode.SymbolKind.Key); -CTagKinMapping.set('_method', vscode.SymbolKind.Method); -CTagKinMapping.set('_methods', vscode.SymbolKind.Method); -CTagKinMapping.set('_module', vscode.SymbolKind.Module); -CTagKinMapping.set('_modules', vscode.SymbolKind.Module); -CTagKinMapping.set('_namespace', vscode.SymbolKind.Namespace); -CTagKinMapping.set('_namespaces', vscode.SymbolKind.Namespace); -CTagKinMapping.set('_number', vscode.SymbolKind.Number); -CTagKinMapping.set('_numbers', vscode.SymbolKind.Number); -CTagKinMapping.set('_null', vscode.SymbolKind.Null); -CTagKinMapping.set('_object', vscode.SymbolKind.Object); -CTagKinMapping.set('_package', vscode.SymbolKind.Package); -CTagKinMapping.set('_packages', vscode.SymbolKind.Package); -CTagKinMapping.set('_property', vscode.SymbolKind.Property); -CTagKinMapping.set('_properties', vscode.SymbolKind.Property); -CTagKinMapping.set('_objects', vscode.SymbolKind.Object); -CTagKinMapping.set('_string', vscode.SymbolKind.String); -CTagKinMapping.set('_variable', vscode.SymbolKind.Variable); -CTagKinMapping.set('_variables', vscode.SymbolKind.Variable); -CTagKinMapping.set('_projects', vscode.SymbolKind.Package); -CTagKinMapping.set('_defines', vscode.SymbolKind.Module); -CTagKinMapping.set('_labels', vscode.SymbolKind.Interface); -CTagKinMapping.set('_macros', vscode.SymbolKind.Function); -CTagKinMapping.set('_types (structs and records)', vscode.SymbolKind.Class); -CTagKinMapping.set('_subroutine', vscode.SymbolKind.Method); -CTagKinMapping.set('_subroutines', vscode.SymbolKind.Method); -CTagKinMapping.set('_types', vscode.SymbolKind.Class); -CTagKinMapping.set('_programs', vscode.SymbolKind.Class); -CTagKinMapping.set("_Object's method", vscode.SymbolKind.Method); -CTagKinMapping.set('_Module or functor', vscode.SymbolKind.Module); -CTagKinMapping.set('_Global variable', vscode.SymbolKind.Variable); -CTagKinMapping.set('_Type name', vscode.SymbolKind.Class); -CTagKinMapping.set('_A function', vscode.SymbolKind.Function); -CTagKinMapping.set('_A constructor', vscode.SymbolKind.Constructor); -CTagKinMapping.set('_An exception', vscode.SymbolKind.Class); -CTagKinMapping.set("_A 'structure' field", vscode.SymbolKind.Field); -CTagKinMapping.set('_procedure', vscode.SymbolKind.Function); -CTagKinMapping.set('_procedures', vscode.SymbolKind.Function); -CTagKinMapping.set('_constant definitions', vscode.SymbolKind.Constant); -CTagKinMapping.set('_javascript functions', vscode.SymbolKind.Function); -CTagKinMapping.set('_singleton methods', vscode.SymbolKind.Method); - -const newValuesAndKeys = {}; -CTagKinMapping.forEach((value, key) => { - (newValuesAndKeys as any)[key.substring(1)] = value; -}); -Object.keys(newValuesAndKeys).forEach((key) => { - CTagKinMapping.set(key, (newValuesAndKeys as any)[key]); -}); - -export function parseTags( - workspaceFolder: string, - tagFile: string, - query: string, - token: vscode.CancellationToken, - fs: IFileSystem -): Promise { - return fs.fileExists(tagFile).then((exists) => { - if (!exists) { - return Promise.resolve([]); - } - - return new Promise((resolve, reject) => { - const lr = new LineByLineReader(tagFile); - let lineNumber = 0; - const tags: ITag[] = []; - - lr.on('error', (err: Error) => { - reject(err); - }); - - lr.on('line', (line: string) => { - lineNumber = lineNumber + 1; - if (token.isCancellationRequested) { - lr.close(); - return; - } - const tag = parseTagsLine(workspaceFolder, line, query); - if (tag) { - tags.push(tag); - } - if (tags.length >= 100) { - lr.close(); - } - }); - - lr.on('end', () => { - resolve(tags); - }); - }); - }); -} -function parseTagsLine(workspaceFolder: string, line: string, searchPattern: string): ITag | undefined { - if (IsFileRegEx.test(line)) { - return; - } - const match = matchNamedRegEx(line, LINE_REGEX); - if (!match) { - return; - } - if (!fuzzy.test(searchPattern, match.name)) { - return; - } - let file = match.file; - if (!path.isAbsolute(file)) { - file = path.resolve(workspaceFolder, '.vscode', file); - } - - const symbolKind = CTagKinMapping.get(match.type) || vscode.SymbolKind.Null; - return { - fileName: file, - code: match.code, - position: new vscode.Position(Number(match.line) - 1, 0), - symbolName: match.name, - symbolKind: symbolKind - }; -} diff --git a/src/client/workspaceSymbols/provider.ts b/src/client/workspaceSymbols/provider.ts deleted file mode 100644 index 1719445aa0ae..000000000000 --- a/src/client/workspaceSymbols/provider.ts +++ /dev/null @@ -1,76 +0,0 @@ -'use strict'; - -// tslint:disable-next-line:no-var-requires no-require-imports -const flatten = require('lodash/flatten') as typeof import('lodash/flatten'); -import { - CancellationToken, - Location, - SymbolInformation, - Uri, - WorkspaceSymbolProvider as IWorspaceSymbolProvider -} from 'vscode'; -import { ICommandManager } from '../common/application/types'; -import { Commands } from '../common/constants'; -import { IFileSystem } from '../common/platform/types'; -import { captureTelemetry } from '../telemetry'; -import { EventName } from '../telemetry/constants'; -import { Generator } from './generator'; -import { parseTags } from './parser'; - -export class WorkspaceSymbolProvider implements IWorspaceSymbolProvider { - public constructor( - private fs: IFileSystem, - private commands: ICommandManager, - private tagGenerators: Generator[] - ) {} - - @captureTelemetry(EventName.WORKSPACE_SYMBOLS_GO_TO) - public async provideWorkspaceSymbols(query: string, token: CancellationToken): Promise { - if (this.tagGenerators.length === 0) { - return []; - } - const generatorsWithTagFiles = await Promise.all( - this.tagGenerators.map((generator) => this.fs.fileExists(generator.tagFilePath)) - ); - if (generatorsWithTagFiles.filter((exists) => exists).length !== this.tagGenerators.length) { - await this.commands.executeCommand(Commands.Build_Workspace_Symbols, true, token); - } - - const generators: Generator[] = []; - await Promise.all( - this.tagGenerators.map(async (generator) => { - if (await this.fs.fileExists(generator.tagFilePath)) { - generators.push(generator); - } - }) - ); - - const promises = generators - .filter((generator) => generator !== undefined && generator.enabled) - .map(async (generator) => { - // load tags - const items = await parseTags( - generator!.workspaceFolder.fsPath, - generator!.tagFilePath, - query, - token, - this.fs - ); - if (!Array.isArray(items)) { - return []; - } - return items.map( - (item) => - new SymbolInformation( - item.symbolName, - item.symbolKind, - '', - new Location(Uri.file(item.fileName), item.position) - ) - ); - }); - - const symbols = await Promise.all(promises); - return flatten(symbols); - } -} diff --git a/src/datascience-ui/common/cellFactory.ts b/src/datascience-ui/common/cellFactory.ts deleted file mode 100644 index b65dff477de2..000000000000 --- a/src/datascience-ui/common/cellFactory.ts +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { nbformat } from '@jupyterlab/coreutils'; -// tslint:disable-next-line: no-require-imports no-var-requires -const cloneDeep = require('lodash/cloneDeep'); -import '../../client/common/extensions'; -import { appendLineFeed, generateMarkdownFromCodeLines } from './index'; - -function uncommentMagicCommands(line: string): string { - // Uncomment lines that are shell assignments (starting with #!), - // line magic (starting with #!%) or cell magic (starting with #!%%). - if (/^#\s*!/.test(line)) { - // If the regex test passes, it's either line or cell magic. - // Hence, remove the leading # and ! including possible white space. - if (/^#\s*!\s*%%?/.test(line)) { - return line.replace(/^#\s*!\s*/, ''); - } - // If the test didn't pass, it's a shell assignment. In this case, only - // remove leading # including possible white space. - return line.replace(/^#\s*/, ''); - } else { - // If it's regular Python code, just return it. - return line; - } -} - -export function createMarkdownCell(code: string | string[], useSourceAsIs: boolean = false): nbformat.IMarkdownCell { - code = Array.isArray(code) ? code : [code]; - return { - cell_type: 'markdown', - metadata: {}, - source: useSourceAsIs ? code : generateMarkdownFromCodeLines(code) - }; -} - -export function createErrorOutput(error: Partial): nbformat.IError { - return { - output_type: 'error', - ename: error.name || error.message || 'Error', - evalue: error.message || error.name || 'Error', - traceback: (error.stack || '').splitLines() - }; -} -export function createCodeCell(): nbformat.ICodeCell; -// tslint:disable-next-line: unified-signatures -export function createCodeCell(code: string): nbformat.ICodeCell; -export function createCodeCell(code: string[], outputs: nbformat.IOutput[]): nbformat.ICodeCell; -// tslint:disable-next-line: unified-signatures -export function createCodeCell(code: string[], magicCommandsAsComments: boolean): nbformat.ICodeCell; -export function createCodeCell(code?: string | string[], options?: boolean | nbformat.IOutput[]): nbformat.ICodeCell { - const magicCommandsAsComments = typeof options === 'boolean' ? options : false; - const outputs = typeof options === 'boolean' ? [] : options || []; - code = code || ''; - // If we get a string, then no need to append line feeds. Leave as is (to preserve existing functionality). - // If we get an array, the append a linefeed. - const source = Array.isArray(code) - ? appendLineFeed(code, magicCommandsAsComments ? uncommentMagicCommands : undefined) - : code; - return { - cell_type: 'code', - execution_count: null, - metadata: {}, - outputs, - source - }; -} -/** - * Clones a cell. - * Also dumps unrecognized attributes from cells. - * - * @export - * @template T - * @param {T} cell - * @returns {T} - */ -export function cloneCell(cell: T): T { - // Construct the cell by hand so we drop unwanted/unrecognized properties from cells. - // This way, the cell contains only the attributes that are valid (supported type). - const clonedCell = cloneDeep(cell); - const source = Array.isArray(clonedCell.source) || typeof clonedCell.source === 'string' ? clonedCell.source : ''; - switch (cell.cell_type) { - case 'code': { - const codeCell: nbformat.ICodeCell = { - cell_type: 'code', - // tslint:disable-next-line: no-any - metadata: (clonedCell.metadata ?? {}) as any, - execution_count: typeof clonedCell.execution_count === 'number' ? clonedCell.execution_count : null, - outputs: Array.isArray(clonedCell.outputs) ? (clonedCell.outputs as nbformat.IOutput[]) : [], - source - }; - // tslint:disable-next-line: no-any - return (codeCell as any) as T; - } - case 'markdown': { - const markdownCell: nbformat.IMarkdownCell = { - cell_type: 'markdown', - // tslint:disable-next-line: no-any - metadata: (clonedCell.metadata ?? {}) as any, - source, - // tslint:disable-next-line: no-any - attachments: clonedCell.attachments as any - }; - // tslint:disable-next-line: no-any - return (markdownCell as any) as T; - } - case 'raw': { - const rawCell: nbformat.IRawCell = { - cell_type: 'raw', - // tslint:disable-next-line: no-any - metadata: (clonedCell.metadata ?? {}) as any, - source, - // tslint:disable-next-line: no-any - attachments: clonedCell.attachments as any - }; - // tslint:disable-next-line: no-any - return (rawCell as any) as T; - } - default: { - // Possibly one of our cell types (`message`). - return clonedCell; - } - } -} - -export function createCellFrom( - source: nbformat.IBaseCell, - target: nbformat.CellType -): nbformat.ICodeCell | nbformat.IMarkdownCell | nbformat.IRawCell { - // If we're creating a new cell from the same base type, then ensure we preserve the metadata. - const baseCell: nbformat.IBaseCell = - source.cell_type === target - ? // tslint:disable-next-line: no-any - (cloneCell(source) as any) - : { - source: source.source, - cell_type: target, - // tslint:disable-next-line: no-any - metadata: cloneDeep(source.metadata) as any - }; - - switch (target) { - case 'code': { - // tslint:disable-next-line: no-unnecessary-local-variable no-any - const codeCell = (baseCell as any) as nbformat.ICodeCell; - codeCell.execution_count = null; - codeCell.outputs = []; - return codeCell; - } - case 'markdown': { - return baseCell as nbformat.IMarkdownCell; - } - case 'raw': { - return baseCell as nbformat.IRawCell; - } - default: { - throw new Error(`Unsupported target type, ${target}`); - } - } -} diff --git a/src/datascience-ui/common/index.css b/src/datascience-ui/common/index.css deleted file mode 100644 index b4cc7250b98c..000000000000 --- a/src/datascience-ui/common/index.css +++ /dev/null @@ -1,5 +0,0 @@ -body { - margin: 0; - padding: 0; - font-family: sans-serif; -} diff --git a/src/datascience-ui/common/index.ts b/src/datascience-ui/common/index.ts deleted file mode 100644 index c744c2d56464..000000000000 --- a/src/datascience-ui/common/index.ts +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { nbformat } from '@jupyterlab/coreutils/lib/nbformat'; -import { noop } from '../../client/common/utils/misc'; - -const SingleQuoteMultiline = "'''"; -const DoubleQuoteMultiline = '"""'; - -export function concatMultilineString(str: nbformat.MultilineString, trim?: boolean): string { - const nonLineFeedWhiteSpaceTrim = /(^[\t\f\v\r ]+|[\t\f\v\r ]+$)/g; // Local var so don't have to reset the lastIndex. - if (Array.isArray(str)) { - let result = ''; - for (let i = 0; i < str.length; i += 1) { - const s = str[i]; - if (i < str.length - 1 && !s.endsWith('\n')) { - result = result.concat(`${s}\n`); - } else { - result = result.concat(s); - } - } - - // Just trim whitespace. Leave \n in place - return trim ? result.replace(nonLineFeedWhiteSpaceTrim, '') : result; - } - return trim ? str.toString().replace(nonLineFeedWhiteSpaceTrim, '') : str.toString(); -} - -export function splitMultilineString(source: nbformat.MultilineString): string[] { - // Make sure a multiline string is back the way Jupyter expects it - if (Array.isArray(source)) { - return source as string[]; - } - const str = source.toString(); - if (str.length > 0) { - // Each line should be a separate entry, but end with a \n if not last entry - const arr = str.split('\n'); - return arr - .map((s, i) => { - if (i < arr.length - 1) { - return `${s}\n`; - } - return s; - }) - .filter((s) => s.length > 0); // Skip last one if empty (it's the only one that could be length 0) - } - return []; -} - -export function removeLinesFromFrontAndBack(code: string): string { - const lines = code.splitLines({ trim: false, removeEmptyEntries: false }); - let foundNonEmptyLine = false; - let lastNonEmptyLine = -1; - let result: string[] = []; - parseForComments( - lines, - (_s, i) => { - result.push(lines[i]); - lastNonEmptyLine = i; - }, - (s, i) => { - const trimmed = s.trim(); - if (foundNonEmptyLine || trimmed) { - result.push(lines[i]); - foundNonEmptyLine = true; - } - if (trimmed) { - lastNonEmptyLine = i; - } - } - ); - - // Remove empty lines off the bottom too - if (lastNonEmptyLine < lines.length - 1) { - result = result.slice(0, result.length - (lines.length - 1 - lastNonEmptyLine)); - } - - return result.join('\n'); -} - -// Strip out comment lines from code -export function stripComments(str: string): string { - let result: string = ''; - parseForComments( - str.splitLines({ trim: false, removeEmptyEntries: false }), - (_s) => noop, - (s) => (result = result.concat(`${s}\n`)) - ); - return result; -} - -// Took this from jupyter/notebook -// https://github.com/jupyter/notebook/blob/b8b66332e2023e83d2ee04f83d8814f567e01a4e/notebook/static/base/js/utils.js -// Remove characters that are overridden by backspace characters -function fixBackspace(txt: string) { - let tmp = txt; - do { - txt = tmp; - // Cancel out anything-but-newline followed by backspace - tmp = txt.replace(/[^\n]\x08/gm, ''); - } while (tmp.length < txt.length); - return txt; -} - -// Using our own version for fixCarriageReturn. The jupyter version seems to not work. -function fixCarriageReturn(str: string): string { - // Go through the string, looking for \r's that are not followed by \n. This is - // a special case that means replace the string before. This is necessary to - // get an html display of this string to behave correctly. - - // Note: According to this: - // https://jsperf.com/javascript-concat-vs-join/2. - // Concat is way faster than array join for building up a string. - let result = ''; - let previousLinePos = 0; - for (let i = 0; i < str.length; i += 1) { - if (str[i] === '\r') { - // See if this is a line feed. If so, leave alone. This is goofy windows \r\n - if (i < str.length - 1 && str[i + 1] === '\n') { - // This line is legit, output it and convert to '\n' only. - result += str.substr(previousLinePos, i - previousLinePos); - result += '\n'; - previousLinePos = i + 2; - i += 1; - } else { - // This line should replace the previous one. Skip our \r - previousLinePos = i + 1; - } - } else if (str[i] === '\n') { - // This line is legit, output it. (Single linefeed) - result += str.substr(previousLinePos, i - previousLinePos + 1); - previousLinePos = i + 1; - } - } - result += str.substr(previousLinePos, str.length - previousLinePos); - return result; -} - -export function formatStreamText(str: string): string { - // Do the same thing jupyter is doing - return fixCarriageReturn(fixBackspace(str)); -} - -export function appendLineFeed(arr: string[], modifier?: (s: string) => string) { - return arr.map((s: string, i: number) => { - const out = modifier ? modifier(s) : s; - return i === arr.length - 1 ? `${out}` : `${out}\n`; - }); -} - -export function generateMarkdownFromCodeLines(lines: string[]) { - // Generate markdown by stripping out the comments and markdown header - return appendLineFeed(extractComments(lines.slice(lines.length > 1 ? 1 : 0))); -} - -// tslint:disable-next-line: cyclomatic-complexity -export function parseForComments( - lines: string[], - foundCommentLine: (s: string, i: number) => void, - foundNonCommentLine: (s: string, i: number) => void -) { - // Check for either multiline or single line comments - let insideMultilineComment: string | undefined; - let insideMultilineQuote: string | undefined; - let pos = 0; - for (const l of lines) { - const trim = l.trim(); - // Multiline is triple quotes of either kind - const isMultilineComment = trim.startsWith(SingleQuoteMultiline) - ? SingleQuoteMultiline - : trim.startsWith(DoubleQuoteMultiline) - ? DoubleQuoteMultiline - : undefined; - const isMultilineQuote = trim.includes(SingleQuoteMultiline) - ? SingleQuoteMultiline - : trim.includes(DoubleQuoteMultiline) - ? DoubleQuoteMultiline - : undefined; - - // Check for ending quotes of multiline string - if (insideMultilineQuote) { - if (insideMultilineQuote === isMultilineQuote) { - insideMultilineQuote = undefined; - } - foundNonCommentLine(l, pos); - // Not inside quote, see if inside a comment - } else if (insideMultilineComment) { - if (insideMultilineComment === isMultilineComment) { - insideMultilineComment = undefined; - } - if (insideMultilineComment) { - foundCommentLine(l, pos); - } - // Not inside either, see if starting a quote - } else if (isMultilineQuote && !isMultilineComment) { - // Make sure doesn't begin and end on the same line. - const beginQuote = trim.indexOf(isMultilineQuote); - const endQuote = trim.lastIndexOf(isMultilineQuote); - insideMultilineQuote = endQuote !== beginQuote ? undefined : isMultilineQuote; - foundNonCommentLine(l, pos); - // Not starting a quote, might be starting a comment - } else if (isMultilineComment) { - // See if this line ends the comment too or not - const endIndex = trim.indexOf(isMultilineComment, 3); - insideMultilineComment = endIndex >= 0 ? undefined : isMultilineComment; - - // Might end with text too - if (trim.length > 3) { - foundCommentLine(trim.slice(3, endIndex >= 0 ? endIndex : undefined), pos); - } - } else { - // Normal line - if (trim.startsWith('#')) { - foundCommentLine(trim.slice(1), pos); - } else { - foundNonCommentLine(l, pos); - } - } - pos += 1; - } -} - -function extractComments(lines: string[]): string[] { - const result: string[] = []; - parseForComments( - lines, - (s) => result.push(s), - (_s) => noop() - ); - return result; -} diff --git a/src/datascience-ui/common/main.ts b/src/datascience-ui/common/main.ts deleted file mode 100644 index 870ae21034f8..000000000000 --- a/src/datascience-ui/common/main.ts +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -declare let __webpack_public_path__: string; - -// tslint:disable-next-line: no-any -if ((window as any).__PVSC_Public_Path) { - // This variable tells Webpack to this as the root path used to request webpack bundles. - // tslint:disable-next-line: no-any - __webpack_public_path__ = (window as any).__PVSC_Public_Path; -} diff --git a/src/datascience-ui/data-explorer/cellFormatter.css b/src/datascience-ui/data-explorer/cellFormatter.css deleted file mode 100644 index 10c63a79c8ac..000000000000 --- a/src/datascience-ui/data-explorer/cellFormatter.css +++ /dev/null @@ -1,8 +0,0 @@ -.number-formatter { - text-align: right; -} - -.cell-formatter { - /* Note: This is impacted by the RowHeightAdjustment in reactSlickGrid.tsx */ - margin: 0px 0px 0px 0px; -} \ No newline at end of file diff --git a/src/datascience-ui/data-explorer/cellFormatter.tsx b/src/datascience-ui/data-explorer/cellFormatter.tsx deleted file mode 100644 index 7c29d00a1265..000000000000 --- a/src/datascience-ui/data-explorer/cellFormatter.tsx +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import './cellFormatter.css'; - -import * as React from 'react'; -import * as ReactDOMServer from 'react-dom/server'; -import { ColumnType } from '../../client/datascience/data-viewing/types'; -import { ISlickRow } from './reactSlickGrid'; - -interface ICellFormatterProps { - value: string | number | object | boolean; - columnDef: Slick.Column; -} - -class CellFormatter extends React.Component { - constructor(props: ICellFormatterProps) { - super(props); - } - - public render() { - // Render based on type - if (this.props.value !== null && this.props.columnDef && this.props.columnDef.hasOwnProperty('type')) { - // tslint:disable-next-line: no-any - const columnType = (this.props.columnDef as any).type; - switch (columnType) { - case ColumnType.Bool: - return this.renderBool(this.props.value as boolean); - break; - - case ColumnType.Number: - return this.renderNumber(this.props.value as number); - break; - - default: - break; - } - } - - // Otherwise an unknown type or a string - const val = this.props.value !== null ? this.props.value.toString() : ''; - return ( -

- {val} -
- ); - } - - private renderBool(value: boolean) { - return ( -
- {value.toString()} -
- ); - } - - private renderNumber(value: number) { - const val = value.toString(); - return ( -
- {val} -
- ); - } -} - -export function cellFormatterFunc( - _row: number, - _cell: number, - // tslint:disable-next-line: no-any - value: any, - columnDef: Slick.Column, - _dataContext: Slick.SlickData -): string { - return ReactDOMServer.renderToString(); -} diff --git a/src/datascience-ui/data-explorer/emptyRowsView.tsx b/src/datascience-ui/data-explorer/emptyRowsView.tsx deleted file mode 100644 index 96eb82385872..000000000000 --- a/src/datascience-ui/data-explorer/emptyRowsView.tsx +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import './emptyRowsView.css'; - -import * as React from 'react'; -import { getLocString } from '../react-common/locReactSide'; - -export interface IEmptyRowsProps {} - -export const EmptyRows = (_props: IEmptyRowsProps) => { - const message = getLocString('DataScience.noRowsInDataViewer', 'No rows match current filter'); - - return
{message}
; -}; diff --git a/src/datascience-ui/data-explorer/globalJQueryImports.ts b/src/datascience-ui/data-explorer/globalJQueryImports.ts deleted file mode 100644 index d3bfae0a054b..000000000000 --- a/src/datascience-ui/data-explorer/globalJQueryImports.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -/* -This file exists for the sole purpose of ensuring jQuery and slickgrid load in the right sequence. -We need to first load jquery into window.jQuery. -After that we need to load slickgrid, and then the jQuery plugin from slickgrid event.drag. -*/ - -// Slickgrid requires jquery to be defined. Globally. So we do some hacks here. -// We need to manipulate the grid with the same jquery that it uses -// use slickgridJQ instead of the usual $ to make it clear that we need that JQ and not -// the one currently in node-modules - -// tslint:disable-next-line: no-var-requires no-require-imports -require('expose-loader?jQuery!slickgrid/lib/jquery-1.11.2.min'); - -// tslint:disable-next-line: no-var-requires no-require-imports -require('slickgrid/lib/jquery-1.11.2.min'); - -// tslint:disable-next-line: no-var-requires no-require-imports -require('expose-loader?jQuery.fn.drag!slickgrid/lib/jquery.event.drag-2.3.0'); diff --git a/src/datascience-ui/data-explorer/index.html b/src/datascience-ui/data-explorer/index.html deleted file mode 100644 index b94a027725e1..000000000000 --- a/src/datascience-ui/data-explorer/index.html +++ /dev/null @@ -1,356 +0,0 @@ - - - - - - - React App - - - - - -
- - - diff --git a/src/datascience-ui/data-explorer/index.tsx b/src/datascience-ui/data-explorer/index.tsx deleted file mode 100644 index aa6136295e4e..000000000000 --- a/src/datascience-ui/data-explorer/index.tsx +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -// This must be on top, do not change. Required by webpack. -import '../common/main'; -// This must be on top, do not change. Required by webpack. - -// tslint:disable-next-line: ordered-imports -import '../common/index.css'; - -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; - -import { IVsCodeApi } from '../react-common/postOffice'; -import { detectBaseTheme } from '../react-common/themeDetector'; -import { MainPanel } from './mainPanel'; - -// This special function talks to vscode from a web panel -export declare function acquireVsCodeApi(): IVsCodeApi; - -const baseTheme = detectBaseTheme(); - -// tslint:disable:no-typeof-undefined -ReactDOM.render( - , // Turn this back off when we have real variable explorer data - document.getElementById('root') as HTMLElement -); diff --git a/src/datascience-ui/data-explorer/mainPanel.css b/src/datascience-ui/data-explorer/mainPanel.css deleted file mode 100644 index 0616067bf249..000000000000 --- a/src/datascience-ui/data-explorer/mainPanel.css +++ /dev/null @@ -1,13 +0,0 @@ - -.main-panel { - position: absolute; - bottom: 0; - top: 0px; - left: 0px; - right: 0; - font-size: var(--code-font-size); - font-family: var(--code-font-family); - background-color: var(--vscode-editor-background); - overflow: hidden; -} - diff --git a/src/datascience-ui/data-explorer/mainPanel.tsx b/src/datascience-ui/data-explorer/mainPanel.tsx deleted file mode 100644 index 7df86002ae67..000000000000 --- a/src/datascience-ui/data-explorer/mainPanel.tsx +++ /dev/null @@ -1,336 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import './mainPanel.css'; - -import { JSONArray } from '@phosphor/coreutils'; -import * as React from 'react'; -import * as uuid from 'uuid/v4'; - -import { - CellFetchAllLimit, - CellFetchSizeFirst, - CellFetchSizeSubsequent, - ColumnType, - DataViewerMessages, - IDataFrameInfo, - IDataViewerMapping, - IGetRowsResponse, - IRowsResponse -} from '../../client/datascience/data-viewing/types'; -import { SharedMessages } from '../../client/datascience/messages'; -import { IDataScienceExtraSettings } from '../../client/datascience/types'; -import { getLocString, storeLocStrings } from '../react-common/locReactSide'; -import { IMessageHandler, PostOffice } from '../react-common/postOffice'; -import { Progress } from '../react-common/progress'; -import { StyleInjector } from '../react-common/styleInjector'; -import { cellFormatterFunc } from './cellFormatter'; -import { ISlickGridAdd, ISlickRow, ReactSlickGrid } from './reactSlickGrid'; -import { generateTestData } from './testData'; - -// Our css has to come after in order to override body styles -export interface IMainPanelProps { - skipDefault?: boolean; - baseTheme: string; - testMode?: boolean; -} - -//tslint:disable:no-any -interface IMainPanelState { - gridColumns: Slick.Column[]; - gridRows: ISlickRow[]; - fetchedRowCount: number; - totalRowCount: number; - filters: {}; - indexColumn: string; - styleReady: boolean; - settings?: IDataScienceExtraSettings; -} - -export class MainPanel extends React.Component implements IMessageHandler { - private container: React.Ref = React.createRef(); - private sentDone = false; - private postOffice: PostOffice = new PostOffice(); - private gridAddEvent: Slick.Event = new Slick.Event(); - private rowFetchSizeFirst: number = 0; - private rowFetchSizeSubsequent: number = 0; - private rowFetchSizeAll: number = 0; - // Just used for testing. - private grid: React.RefObject = React.createRef(); - private updateTimeout?: NodeJS.Timer | number; - - // tslint:disable-next-line:max-func-body-length - constructor(props: IMainPanelProps, _state: IMainPanelState) { - super(props); - - if (!this.props.skipDefault) { - const data = generateTestData(5000); - this.state = { - gridColumns: data.columns.map((c) => { - return { ...c, formatter: cellFormatterFunc }; - }), - gridRows: [], - totalRowCount: data.rows.length, - fetchedRowCount: -1, - filters: {}, - indexColumn: data.primaryKeys[0], - styleReady: false - }; - - // Fire off a timer to mimic dynamic loading - setTimeout(() => this.handleGetAllRowsResponse(data.rows), 1000); - } else { - this.state = { - gridColumns: [], - gridRows: [], - totalRowCount: 0, - fetchedRowCount: -1, - filters: {}, - indexColumn: 'index', - styleReady: false - }; - } - } - - public componentWillMount() { - // Add ourselves as a handler for the post office - this.postOffice.addHandler(this); - - // Tell the dataviewer code we have started. - this.postOffice.sendMessage(DataViewerMessages.Started); - } - - public componentWillUnmount() { - this.postOffice.removeHandler(this); - this.postOffice.dispose(); - } - - public render = () => { - if (!this.state.settings) { - return
; - } - - // Send our done message if we haven't yet and we just reached full capacity. Do it here so we - // can guarantee our render will run before somebody checks our rendered output. - if (this.state.totalRowCount && this.state.totalRowCount === this.state.fetchedRowCount && !this.sentDone) { - this.sentDone = true; - this.sendMessage(DataViewerMessages.CompletedData); - } - - const progressBar = this.state.totalRowCount > this.state.fetchedRowCount ? : undefined; - - return ( -
- - {progressBar} - {this.state.totalRowCount > 0 && this.state.styleReady && this.renderGrid()} -
- ); - }; - - // tslint:disable-next-line:no-any - public handleMessage = (msg: string, payload?: any) => { - switch (msg) { - case DataViewerMessages.InitializeData: - this.initializeData(payload); - break; - - case DataViewerMessages.GetAllRowsResponse: - this.handleGetAllRowsResponse(payload as IRowsResponse); - break; - - case DataViewerMessages.GetRowsResponse: - this.handleGetRowChunkResponse(payload as IGetRowsResponse); - break; - - case SharedMessages.UpdateSettings: - this.updateSettings(payload); - break; - - case SharedMessages.LocInit: - this.initializeLoc(payload); - break; - - default: - break; - } - - return false; - }; - - private initializeLoc(content: string) { - const locJSON = JSON.parse(content); - storeLocStrings(locJSON); - } - - private updateSettings(content: string) { - const newSettingsJSON = JSON.parse(content); - const newSettings = newSettingsJSON as IDataScienceExtraSettings; - this.setState({ - settings: newSettings - }); - } - - private saveReadyState = () => { - this.setState({ styleReady: true }); - }; - - private renderGrid() { - const filterRowsText = getLocString('DataScience.filterRowsButton', 'Filter Rows'); - const filterRowsTooltip = getLocString('DataScience.filterRowsTooltip', 'Click to filter.'); - - return ( - - ); - } - - // tslint:disable-next-line:no-any - private initializeData(payload: any) { - // Payload should be an IJupyterVariable with the first 100 rows filled out - if (payload) { - const variable = payload as IDataFrameInfo; - if (variable) { - const columns = this.generateColumns(variable); - const totalRowCount = variable.rowCount ? variable.rowCount : 0; - const initialRows: ISlickRow[] = []; - const indexColumn = variable.indexColumn ? variable.indexColumn : 'index'; - - this.setState({ - gridColumns: columns, - gridRows: initialRows, - totalRowCount, - fetchedRowCount: initialRows.length, - indexColumn: indexColumn - }); - - // Compute our row fetch sizes based on the number of columns - this.rowFetchSizeAll = Math.round(CellFetchAllLimit / columns.length); - this.rowFetchSizeFirst = Math.round(Math.max(2, CellFetchSizeFirst / columns.length)); - this.rowFetchSizeSubsequent = Math.round(Math.max(2, CellFetchSizeSubsequent / columns.length)); - - // Request the rest of the data if necessary - if (initialRows.length !== totalRowCount) { - // Get all at once if less than 1000 - if (totalRowCount < this.rowFetchSizeAll) { - this.getAllRows(); - } else { - this.getRowsInChunks(initialRows.length, totalRowCount); - } - } - } - } - } - - private getAllRows() { - this.sendMessage(DataViewerMessages.GetAllRowsRequest); - } - - private getRowsInChunks(startIndex: number, endIndex: number) { - // Ask for our first chunk. Don't spam jupyter though with all requests at once - // Instead, do them one at a time. - const chunkEnd = startIndex + Math.min(this.rowFetchSizeFirst, endIndex); - const chunkStart = startIndex; - this.sendMessage(DataViewerMessages.GetRowsRequest, { start: chunkStart, end: chunkEnd }); - } - - private handleGetAllRowsResponse(response: IRowsResponse) { - const rows = response ? (response as JSONArray) : []; - const normalized = this.normalizeRows(rows); - - // Update our fetched count and actual rows - this.setState({ - gridRows: this.state.gridRows.concat(normalized), - fetchedRowCount: this.state.totalRowCount - }); - - // Add all of these rows to the grid - this.updateRows(normalized); - } - - private handleGetRowChunkResponse(response: IGetRowsResponse) { - // We have a new fetched row count - const rows = response.rows ? (response.rows as JSONArray) : []; - const normalized = this.normalizeRows(rows); - const newFetched = this.state.fetchedRowCount + (response.end - response.start); - - // gridRows should have our entire list. We need to replace our part with our new results - const before = this.state.gridRows.slice(0, response.start); - const after = response.end < this.state.gridRows.length ? this.state.gridRows.slice(response.end) : []; - const newActual = before.concat(normalized.concat(after)); - - // Apply this to our state - this.setState({ - fetchedRowCount: newFetched, - gridRows: newActual - }); - - // Tell our grid about the new ros - this.updateRows(normalized); - - // Get the next chunk - if (newFetched < this.state.totalRowCount) { - const chunkStart = response.end; - const chunkEnd = Math.min(chunkStart + this.rowFetchSizeSubsequent, this.state.totalRowCount); - this.sendMessage(DataViewerMessages.GetRowsRequest, { start: chunkStart, end: chunkEnd }); - } - } - - private generateColumns(variable: IDataFrameInfo): Slick.Column[] { - if (variable.columns) { - return variable.columns.map((c: { key: string; type: ColumnType }, i: number) => { - return { - type: c.type, - field: c.key.toString(), - id: `${i}`, - name: c.key.toString(), - sortable: true, - formatter: cellFormatterFunc - }; - }); - } - return []; - } - - private normalizeRows(rows: JSONArray): ISlickRow[] { - // Make sure we have an index field and all rows have an item - return rows.map((r: any | undefined) => { - if (!r) { - r = {}; - } - if (!r.hasOwnProperty(this.state.indexColumn)) { - r[this.state.indexColumn] = uuid(); - } - return r; - }); - } - - private sendMessage(type: T, payload?: M[T]) { - this.postOffice.sendMessage(type, payload); - } - - private updateRows(newRows: ISlickRow[]) { - if (this.updateTimeout !== undefined) { - clearTimeout(this.updateTimeout as any); - this.updateTimeout = undefined; - } - if (!this.grid.current) { - // This might happen before we render the grid. Postpone till then. - this.updateTimeout = setTimeout(() => this.updateRows(newRows), 10); - } else { - this.gridAddEvent.notify({ newRows }); - } - } -} diff --git a/src/datascience-ui/data-explorer/progressBar.css b/src/datascience-ui/data-explorer/progressBar.css deleted file mode 100644 index 8249651f7029..000000000000 --- a/src/datascience-ui/data-explorer/progressBar.css +++ /dev/null @@ -1,9 +0,0 @@ -.progress-bar { - margin:2px; - text-align: center; -} - -.progress-container { - padding: 20px; - text-align:center; -} \ No newline at end of file diff --git a/src/datascience-ui/data-explorer/progressBar.tsx b/src/datascience-ui/data-explorer/progressBar.tsx deleted file mode 100644 index 97c279b5ca68..000000000000 --- a/src/datascience-ui/data-explorer/progressBar.tsx +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import './progressBar.css'; - -import * as React from 'react'; -import { getLocString } from '../react-common/locReactSide'; - -export interface IEmptyRowsProps { - total: number; - current: number; -} - -export const ProgressBar = (props: IEmptyRowsProps) => { - const percent = (props.current / props.total) * 100; - const percentText = `${Math.round(percent)}%`; - const style: React.CSSProperties = { - width: percentText - }; - const message = getLocString('DataScience.fetchingDataViewer', 'Fetching data ...'); - - return ( -
- {message} -
- {percentText} -
-
- ); -}; diff --git a/src/datascience-ui/data-explorer/reactSlickGrid.css b/src/datascience-ui/data-explorer/reactSlickGrid.css deleted file mode 100644 index b52165279386..000000000000 --- a/src/datascience-ui/data-explorer/reactSlickGrid.css +++ /dev/null @@ -1,87 +0,0 @@ -.outer-container { - width:auto; - height:100%; -} - -.react-grid-container { - border-color: var(--vscode-editor-inactiveSelectionBackground); - border-style: solid; - border-width: 1px; -} - -.react-grid-measure { - position: absolute; - bottom: 5px; -} - -.react-grid-filter-button { - background: var(--vscode-button-background); - color: var(--vscode-button-foreground); - padding: 10px; - border: none; - border-radius: 5px; - cursor: pointer; - margin:8px 4px; -} - -.react-grid-header-cell { - padding: 0px 4px; - background-color: var(--vscode-debugToolBar-background); - color: var(--vscode-editor-foreground); - text-align: left; - font-weight: bold; - border-right-color: var(--vscode-editor-inactiveSelectionBackground); -} - -.react-grid-cell { - padding: 0px 4px; - background-color: var(--vscode-editor-background); - color: var(--vscode-editor-foreground); - border-bottom-color: var(--vscode-editor-inactiveSelectionBackground); - border-right-color: var(--vscode-editor-inactiveSelectionBackground); - border-right-style: solid; - box-sizing: border-box; -} - -.react-grid-cell.active { - background-color: var(--vscode-button-background); - color: var(--vscode-button-foreground); -} - -/* Some overrides necessary to get the colors we want */ -.slick-headerrow-column { - background-color: var(--vscode-debugToolBar-background); - border-right-color: var(--vscode-editor-inactiveSelectionBackground); - border-right-style: solid; -} - -.slick-header-column.ui-state-default, .slick-group-header-column.ui-state-default { - border-right-color: var(--vscode-editor-inactiveSelectionBackground); -} - -.slick-sort-indicator { - float: right; - width: 0px; - margin-right: 12px; - margin-top: 1px; -} - -.react-grid-header-cell:hover { - background-color: var(--vscode-editor-inactiveSelectionBackground); -} - -.react-grid-header-cell > .slick-sort-indicator-asc::before { - background: none; - content: '▲'; - align-items: center; -} - -.react-grid-header-cell > .slick-sort-indicator-desc::before { - background: none; - content: '▼'; - align-items: center; -} - -.slick-row:hover > .react-grid-cell { - background-color: var(--override-selection-background, var(--vscode-editor-selectionBackground)); -} \ No newline at end of file diff --git a/src/datascience-ui/data-explorer/reactSlickGrid.tsx b/src/datascience-ui/data-explorer/reactSlickGrid.tsx deleted file mode 100644 index 12a67ede2f66..000000000000 --- a/src/datascience-ui/data-explorer/reactSlickGrid.tsx +++ /dev/null @@ -1,538 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import { ColumnType, MaxStringCompare } from '../../client/datascience/data-viewing/types'; -import { KeyCodes } from '../react-common/constants'; -import { measureText } from '../react-common/textMeasure'; -import './globalJQueryImports'; -import { ReactSlickGridFilterBox } from './reactSlickGridFilterBox'; - -/* -WARNING: Do not change the order of these imports. -Slick grid MUST be imported after we load jQuery and other stuff from `./globalJQueryImports` -*/ -// tslint:disable-next-line: no-var-requires no-require-imports -const slickgridJQ = require('slickgrid/lib/jquery-1.11.2.min'); - -// Adding comments to ensure order of imports does not change due to auto formatters. -// tslint:disable-next-line: ordered-imports -import 'slickgrid/slick.core'; -// Adding comments to ensure order of imports does not change due to auto formatters. -// tslint:disable-next-line: ordered-imports -import 'slickgrid/slick.dataview'; -// Adding comments to ensure order of imports does not change due to auto formatters. -// tslint:disable-next-line: ordered-imports -import 'slickgrid/slick.grid'; -// Adding comments to ensure order of imports does not change due to auto formatters. -// tslint:disable-next-line: ordered-imports -import 'slickgrid/plugins/slick.autotooltips'; -// Adding comments to ensure order of imports does not change due to auto formatters. -// tslint:disable-next-line: ordered-imports -import 'slickgrid/slick.grid.css'; -// Make sure our css comes after the slick grid css. We override some of its styles. -// tslint:disable-next-line: ordered-imports -import './reactSlickGrid.css'; -/* -WARNING: Do not change the order of these imports. -Slick grid MUST be imported after we load jQuery and other stuff from `./globalJQueryImports` -*/ - -const MinColumnWidth = 70; -const MaxColumnWidth = 500; - -export interface ISlickRow extends Slick.SlickData { - id: string; -} - -export interface ISlickGridAdd { - newRows: ISlickRow[]; -} - -// tslint:disable:no-any -export interface ISlickGridProps { - idProperty: string; - columns: Slick.Column[]; - rowsAdded: Slick.Event; - filterRowsText: string; - filterRowsTooltip: string; - forceHeight?: number; -} - -interface ISlickGridState { - grid?: Slick.Grid; - showingFilters?: boolean; - fontSize: number; -} - -class ColumnFilter { - private matchFunc: (v: any) => boolean; - private lessThanRegEx = /^\s*<\s*(\d+.*)/; - private lessThanEqualRegEx = /^\s*<=\s*(\d+.*).*/; - private greaterThanRegEx = /^\s*>\s*(\d+.*).*/; - private greaterThanEqualRegEx = /^\s*>=\s*(\d+.*).*/; - private equalThanRegEx = /^\s*=\s*(\d+.*).*/; - - constructor(text: string, column: Slick.Column) { - if (text && text.length > 0) { - const columnType = (column as any).type; - switch (columnType) { - case ColumnType.String: - default: - this.matchFunc = (v: any) => !v || v.toString().includes(text); - break; - - case ColumnType.Number: - this.matchFunc = this.generateNumericOperation(text); - break; - } - } else { - this.matchFunc = (_v: any) => true; - } - } - - public matches(value: any): boolean { - return this.matchFunc(value); - } - - private extractDigits(text: string, regex: RegExp): number { - const match = regex.exec(text); - if (match && match.length > 1) { - return parseFloat(match[1]); - } - return 0; - } - - private generateNumericOperation(text: string): (v: any) => boolean { - if (this.lessThanRegEx.test(text)) { - const n1 = this.extractDigits(text, this.lessThanRegEx); - return (v: any) => v !== undefined && v < n1; - } else if (this.lessThanEqualRegEx.test(text)) { - const n2 = this.extractDigits(text, this.lessThanEqualRegEx); - return (v: any) => v !== undefined && v <= n2; - } else if (this.greaterThanRegEx.test(text)) { - const n3 = this.extractDigits(text, this.greaterThanRegEx); - return (v: any) => v !== undefined && v > n3; - } else if (this.greaterThanEqualRegEx.test(text)) { - const n4 = this.extractDigits(text, this.greaterThanEqualRegEx); - return (v: any) => v !== undefined && v >= n4; - } else if (this.equalThanRegEx.test(text)) { - const n5 = this.extractDigits(text, this.equalThanRegEx); - return (v: any) => v !== undefined && v === n5; - } else { - const n6 = parseFloat(text); - return (v: any) => v !== undefined && v === n6; - } - } -} - -export class ReactSlickGrid extends React.Component { - private containerRef: React.RefObject; - private measureRef: React.RefObject; - private dataView: Slick.Data.DataView = new Slick.Data.DataView(); - private columnFilters: Map = new Map(); - private resizeTimer?: number; - private autoResizedColumns: boolean = false; - - constructor(props: ISlickGridProps) { - super(props); - this.state = { fontSize: 15 }; - this.containerRef = React.createRef(); - this.measureRef = React.createRef(); - this.props.rowsAdded.subscribe(this.addedRows); - } - - // tslint:disable-next-line:max-func-body-length - public componentDidMount = () => { - window.addEventListener('resize', this.windowResized); - - if (this.containerRef.current) { - // Compute font size. Default to 15 if not found. - let fontSize = parseInt( - getComputedStyle(this.containerRef.current).getPropertyValue('--code-font-size'), - 10 - ); - if (isNaN(fontSize)) { - fontSize = 15; - } - - // Setup options for the grid - const options: Slick.GridOptions = { - asyncEditorLoading: true, - editable: false, - enableCellNavigation: true, - showHeaderRow: true, - enableColumnReorder: false, - explicitInitialization: false, - viewportClass: 'react-grid', - rowHeight: this.getAppropiateRowHeight(fontSize) - }; - - // Transform columns so they are sortable and stylable - const columns = this.props.columns.map((c) => { - c.sortable = true; - c.headerCssClass = 'react-grid-header-cell'; - c.cssClass = 'react-grid-cell'; - return c; - }); - - // Create the grid - const grid = new Slick.Grid(this.containerRef.current, this.dataView, columns, options); - grid.registerPlugin(new Slick.AutoTooltips({ enableForCells: true, enableForHeaderCells: true })); - - // Setup our dataview - this.dataView.beginUpdate(); - this.dataView.setFilter(this.filter.bind(this)); - this.dataView.setItems([], this.props.idProperty); - this.dataView.endUpdate(); - - this.dataView.onRowCountChanged.subscribe((_e, _args) => { - grid.updateRowCount(); - grid.render(); - }); - - this.dataView.onRowsChanged.subscribe((_e, args) => { - grid.invalidateRows(args.rows); - grid.render(); - }); - - // Setup the filter render - grid.onHeaderRowCellRendered.subscribe(this.renderFilterCell); - - grid.onHeaderCellRendered.subscribe((_e, args) => { - // Add a tab index onto our header cell - args.node.tabIndex = 0; - }); - - // Unbind the slickgrid key handler from the canvas code - // We want to keep EnableCellNavigation on so that we can use the slickgrid - // public navigations functions, but we don't want the slickgrid keyhander - // to eat tab keys and prevent us from tabbing to input boxes or column headers - const canvasElement = grid.getCanvasNode(); - slickgridJQ(canvasElement).off('keydown'); - - if (this.containerRef && this.containerRef.current) { - // slickgrid creates empty focus sink div elements that capture tab input we don't want that - // so unhook their key handlers and remove their tabindex - const firstFocus = slickgridJQ('.react-grid-container').children().first(); - const lastFocus = slickgridJQ('.react-grid-container').children().last(); - slickgridJQ(firstFocus).off('keydown').removeAttr('tabindex'); - slickgridJQ(lastFocus).off('keydown').removeAttr('tabindex'); - - // Set our key handling on the actual grid viewport - slickgridJQ('.react-grid') - .on('keydown', this.slickgridHandleKeyDown) - .attr('role', 'grid') - .on('focusin', this.slickgridFocus); - slickgridJQ('.grid-canvas').on('keydown', this.slickgridHandleKeyDown); - } - - // Setup the sorting - grid.onSort.subscribe(this.sort); - - // Init to force the actual render. - grid.init(); - - // Set the initial sort column to our index column - const indexColumn = columns.find((c) => c.field === this.props.idProperty); - if (indexColumn && indexColumn.id) { - grid.setSortColumn(indexColumn.id, true); - } - - // Save in our state - this.setState({ grid, fontSize }); - } - - // Act like a resize happened to refresh the layout. - this.windowResized(); - }; - - public componentWillUnmount = () => { - if (this.resizeTimer) { - window.clearTimeout(this.resizeTimer); - } - window.removeEventListener('resize', this.windowResized); - if (this.state.grid) { - this.state.grid.destroy(); - } - }; - - public componentDidUpdate = (_prevProps: ISlickGridProps, prevState: ISlickGridState) => { - if (this.state.showingFilters && this.state.grid) { - this.state.grid.setHeaderRowVisibility(true); - } else if (this.state.showingFilters === false && this.state.grid) { - this.state.grid.setHeaderRowVisibility(false); - } - - // If this is our first time setting the grid, we need to dynanically modify the styles - // that the slickGrid generates for the rows. It's eliminating some of the height - if (!prevState.grid && this.state.grid && this.containerRef.current) { - this.updateCssStyles(); - } - }; - - public render() { - const style: React.CSSProperties = this.props.forceHeight - ? { - height: `${this.props.forceHeight}px`, - width: `${this.props.forceHeight}px` - } - : {}; - - return ( -
- -
-
-
- ); - } - - // public for testing - public sort = (_e: Slick.EventData, args: Slick.OnSortEventArgs) => { - // Note: dataView.fastSort is an IE workaround. Not necessary. - this.dataView.sort((l: any, r: any) => this.compareElements(l, r, args.sortCol), args.sortAsc); - args.grid.invalidateAllRows(); - args.grid.render(); - }; - - // Public for testing - public filterChanged = (text: string, column: Slick.Column) => { - if (column && column.field) { - this.columnFilters.set(column.field, new ColumnFilter(text, column)); - this.dataView.refresh(); - } - }; - - // These adjustments for the row height come from trial and error, by changing the font size in VS code, - // opening a new Data Viewer, and making sure the data is visible - // They were tested up to a font size of 60, and the row height still allows the content to be seen - private getAppropiateRowHeight(fontSize: number): number { - switch (true) { - case fontSize < 15: - return fontSize + 4; - case fontSize < 20: - return fontSize + 8; - case fontSize < 30: - return fontSize + 10; - default: - return fontSize + 12; - } - } - - // If the slickgrid gets focus and nothing is selected select the first item - // so that you can keyboard navigate from there - private slickgridFocus = (_e: any): void => { - if (this.state.grid) { - if (!this.state.grid.getActiveCell()) { - this.state.grid.setActiveCell(0, 0); - } - } - }; - - private slickgridHandleKeyDown = (e: KeyboardEvent): void => { - let handled: boolean = false; - - // Defined here: - // https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/Grid_Role#Keyboard_interactions - - if (this.state.grid) { - // The slickgrid version of jquery populates keyCode not code, so use the numerical values here - switch (e.keyCode) { - case KeyCodes.LeftArrow: - this.state.grid.navigateLeft(); - handled = true; - break; - case KeyCodes.UpArrow: - this.state.grid.navigateUp(); - handled = true; - break; - case KeyCodes.RightArrow: - this.state.grid.navigateRight(); - handled = true; - break; - case KeyCodes.DownArrow: - this.state.grid.navigateDown(); - handled = true; - break; - case KeyCodes.PageUp: - this.state.grid.navigatePageUp(); - handled = true; - break; - case KeyCodes.PageDown: - this.state.grid.navigatePageDown(); - handled = true; - break; - case KeyCodes.End: - e.ctrlKey ? this.state.grid.navigateBottom() : this.state.grid.navigateRowEnd(); - handled = true; - break; - case KeyCodes.Home: - e.ctrlKey ? this.state.grid.navigateTop() : this.state.grid.navigateRowStart(); - handled = true; - break; - default: - } - } - - if (handled) { - // Don't let the parent / browser do stuff if we handle it - // otherwise we'll both move the cell selection and scroll the window - // with up and down keys - e.stopPropagation(); - e.preventDefault(); - } - }; - - private updateCssStyles = () => { - if (this.state.grid && this.containerRef.current) { - const gridName = (this.state.grid as any).getUID() as string; - const document = this.containerRef.current.ownerDocument; - if (document) { - const cssOverrideNode = document.createElement('style'); - const rule = `.${gridName} .slick-cell {height: ${this.getAppropiateRowHeight( - this.state.fontSize - )}px;}`; - cssOverrideNode.setAttribute('type', 'text/css'); - cssOverrideNode.setAttribute('rel', 'stylesheet'); - cssOverrideNode.appendChild(document.createTextNode(rule)); - document.head.appendChild(cssOverrideNode); - } - } - }; - - private windowResized = () => { - if (this.resizeTimer) { - clearTimeout(this.resizeTimer); - } - this.resizeTimer = window.setTimeout(this.updateGridSize, 10); - }; - - private updateGridSize = () => { - if (this.state.grid && this.containerRef.current && this.measureRef.current) { - // We use a div at the bottom to figure out our expected height. Slickgrid isn't - // so good without a specific height set in the style. - const height = this.measureRef.current.offsetTop - this.containerRef.current.offsetTop; - this.containerRef.current.style.height = `${this.props.forceHeight ? this.props.forceHeight : height}px`; - this.state.grid.resizeCanvas(); - } - }; - - private autoResizeColumns(rows: ISlickRow[]) { - if (this.state.grid) { - const fontString = this.computeFont(); - const columns = this.state.grid.getColumns(); - columns.forEach((c) => { - let colWidth = MinColumnWidth; - rows.forEach((r: any) => { - const field = c.field ? r[c.field] : ''; - const fieldWidth = field ? measureText(field.toString(), fontString) : 0; - colWidth = Math.min(MaxColumnWidth, Math.max(colWidth, fieldWidth)); - }); - c.width = colWidth; - }); - this.state.grid.setColumns(columns); - - // We also need to update the styles as slickgrid will mess up the height of rows - // again - setTimeout(() => { - this.updateCssStyles(); - - // Hide the header row after we finally resize our columns - this.state.grid!.setHeaderRowVisibility(false); - }, 0); - } - } - - private computeFont(): string | null { - if (this.containerRef.current) { - const style = getComputedStyle(this.containerRef.current); - return style ? style.font : null; - } - return null; - } - - private addedRows = (_e: Slick.EventData, data: ISlickGridAdd) => { - // Add all of these new rows into our data. - this.dataView.beginUpdate(); - for (const row of data.newRows) { - this.dataView.addItem(row); - } - - // Update columns if we haven't already - if (!this.autoResizedColumns) { - this.autoResizedColumns = true; - this.autoResizeColumns(data.newRows); - } - - this.dataView.endUpdate(); - - // This should cause a rowsChanged event in the dataview that will - // refresh the grid. - }; - - // tslint:disable-next-line: no-any - private filter(item: any, _args: any): boolean { - const fields = Array.from(this.columnFilters.keys()); - for (const field of fields) { - if (field) { - const filter = this.columnFilters.get(field); - if (filter) { - if (!filter.matches(item[field])) { - return false; - } - } - } - } - return true; - } - - private clickFilterButton = (e: React.SyntheticEvent) => { - e.preventDefault(); - this.setState({ showingFilters: !this.state.showingFilters }); - }; - - private renderFilterCell = (_e: Slick.EventData, args: Slick.OnHeaderRowCellRenderedEventArgs) => { - ReactDOM.render(, args.node); - }; - - private compareElements(a: any, b: any, col?: Slick.Column): number { - if (col) { - const sortColumn = col.field; - if (sortColumn && col.hasOwnProperty('type')) { - const columnType = (col as any).type; - const isStringColumn = columnType === 'string' || columnType === 'object'; - if (isStringColumn) { - const aVal = a[sortColumn] ? a[sortColumn].toString() : ''; - const bVal = b[sortColumn] ? b[sortColumn].toString() : ''; - const aStr = aVal ? aVal.substring(0, Math.min(aVal.length, MaxStringCompare)) : aVal; - const bStr = bVal ? bVal.substring(0, Math.min(bVal.length, MaxStringCompare)) : bVal; - return aStr.localeCompare(bStr); - } else { - const aVal = a[sortColumn]; - const bVal = b[sortColumn]; - return aVal === bVal ? 0 : aVal > bVal ? 1 : -1; - } - } - } - - // No sort column, try index column - if (a.hasOwnProperty(this.props.idProperty) && b.hasOwnProperty(this.props.idProperty)) { - const sortColumn = this.props.idProperty; - const aVal = a[sortColumn]; - const bVal = b[sortColumn]; - return aVal === bVal ? 0 : aVal > bVal ? 1 : -1; - } - - return -1; - } -} diff --git a/src/datascience-ui/data-explorer/reactSlickGridFilterBox.css b/src/datascience-ui/data-explorer/reactSlickGridFilterBox.css deleted file mode 100644 index 0d5670650776..000000000000 --- a/src/datascience-ui/data-explorer/reactSlickGridFilterBox.css +++ /dev/null @@ -1,17 +0,0 @@ -.filter-box { - border-color: var(--vscode-editor-inactiveSelectionBackground); - border-style: solid; - border-width: 1px; - display: block; - position: relative; - left: -2px; - top: -3px; - width: 98%; - padding: 1px; - margin: 0px; -} - -.filter-box:focus { - border-color: var(--vscode-editor-selectionBackground); - outline: none; -} \ No newline at end of file diff --git a/src/datascience-ui/data-explorer/reactSlickGridFilterBox.tsx b/src/datascience-ui/data-explorer/reactSlickGridFilterBox.tsx deleted file mode 100644 index 12c1a9530cdd..000000000000 --- a/src/datascience-ui/data-explorer/reactSlickGridFilterBox.tsx +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import * as React from 'react'; - -import './reactSlickGridFilterBox.css'; - -interface IFilterProps { - column: Slick.Column; - onChange(val: string, column: Slick.Column): void; -} - -export class ReactSlickGridFilterBox extends React.Component { - constructor(props: IFilterProps) { - super(props); - } - - public render() { - return ( - - ); - } - - private updateInputValue = (evt: React.SyntheticEvent) => { - const element = evt.currentTarget as HTMLInputElement; - if (element) { - this.props.onChange(element.value, this.props.column); - } - }; -} diff --git a/src/datascience-ui/data-explorer/testData.ts b/src/datascience-ui/data-explorer/testData.ts deleted file mode 100644 index 963a702ee1a0..000000000000 --- a/src/datascience-ui/data-explorer/testData.ts +++ /dev/null @@ -1,12518 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -export interface ITestData { - columns: { id: string; name: string; type: string }[]; - primaryKeys: string[]; - rows: {}[]; - loadingRows: {}[]; -} - -// tslint:disable -export function generateTestData(_numberOfRows: number): ITestData { - const columns = [ - { id: 'PassengerId', name: 'PassengerId', field: 'PassengerId', type: 'integer' }, - { id: 'SibSp', name: 'SibSp', field: 'SibSp', type: 'integer' }, - { id: 'Ticket', name: 'Ticket', field: 'Ticket', type: 'string' }, - { id: 'Parch', name: 'Parch', field: 'Parch', type: 'integer' }, - { id: 'Cabin', name: 'Cabin', field: 'Cabin', type: 'string' }, - { id: 'Age', name: 'Age', field: 'Age', type: 'integer' }, - { id: 'Fare', name: 'Fare', field: 'Fare', type: 'number' }, - { id: 'Name', name: 'Name', field: 'Name', type: 'string' }, - { id: 'Survived', name: 'Survived', field: 'Survived', type: 'bool' }, - { id: 'Pclass', name: 'Pclass', field: 'Pclass', type: 'integer' }, - { id: 'Embarked', name: 'Embarked', field: 'Embarked', type: 'string' }, - { id: 'Sex', name: 'Sex', field: 'Sex', type: 'string' } - ]; - - const keys = ['PassengerId']; - - const rows: {}[] = titanicData; - - return { - columns, - primaryKeys: keys, - rows, - loadingRows: titanicData.map((_t) => { - return {}; - }) - }; -} - -const titanicData = [ - { - SibSp: 1, - Ticket: 'A/5 21171', - Parch: 0, - Cabin: null, - PassengerId: 1, - Age: 22, - Fare: 7.25, - Name: 'Braund, Mr. Owen Harris', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'PC 17599', - Parch: 0, - Cabin: 'C85', - PassengerId: 2, - Age: 38, - Fare: 71.2833, - Name: 'Cumings, Mrs. John Bradley (Florence Briggs Thayer)', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'STON/O2. 3101282', - Parch: 0, - Cabin: null, - PassengerId: 3, - Age: 26, - Fare: 7.925, - Name: 'Heikkinen, Miss. Laina', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '113803', - Parch: 0, - Cabin: 'C123', - PassengerId: 4, - Age: 35, - Fare: 53.1, - Name: 'Futrelle, Mrs. Jacques Heath (Lily May Peel)', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '373450', - Parch: 0, - Cabin: null, - PassengerId: 5, - Age: 35, - Fare: 8.05, - Name: 'Allen, Mr. William Henry', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '330877', - Parch: 0, - Cabin: null, - PassengerId: 6, - Age: null, - Fare: 8.4583, - Name: 'Moran, Mr. James', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '17463', - Parch: 0, - Cabin: 'E46', - PassengerId: 7, - Age: 54, - Fare: 51.8625, - Name: 'McCarthy, Mr. Timothy J', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 3, - Ticket: '349909', - Parch: 1, - Cabin: null, - PassengerId: 8, - Age: 2, - Fare: 21.075, - Name: 'Palsson, Master. Gosta Leonard', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '347742', - Parch: 2, - Cabin: null, - PassengerId: 9, - Age: 27, - Fare: 11.1333, - Name: 'Johnson, Mrs. Oscar W (Elisabeth Vilhelmina Berg)', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '237736', - Parch: 0, - Cabin: null, - PassengerId: 10, - Age: 14, - Fare: 30.0708, - Name: 'Nasser, Mrs. Nicholas (Adele Achem)', - Survived: true, - Pclass: 2, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: 'PP 9549', - Parch: 1, - Cabin: 'G6', - PassengerId: 11, - Age: 4, - Fare: 16.7, - Name: 'Sandstrom, Miss. Marguerite Rut', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '113783', - Parch: 0, - Cabin: 'C103', - PassengerId: 12, - Age: 58, - Fare: 26.55, - Name: 'Bonnell, Miss. Elizabeth', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'A/5. 2151', - Parch: 0, - Cabin: null, - PassengerId: 13, - Age: 20, - Fare: 8.05, - Name: 'Saundercock, Mr. William Henry', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '347082', - Parch: 5, - Cabin: null, - PassengerId: 14, - Age: 39, - Fare: 31.275, - Name: 'Andersson, Mr. Anders Johan', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '350406', - Parch: 0, - Cabin: null, - PassengerId: 15, - Age: 14, - Fare: 7.8542, - Name: 'Vestrom, Miss. Hulda Amanda Adolfina', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '248706', - Parch: 0, - Cabin: null, - PassengerId: 16, - Age: 55, - Fare: 16, - Name: 'Hewlett, Mrs. (Mary D Kingcome) ', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 4, - Ticket: '382652', - Parch: 1, - Cabin: null, - PassengerId: 17, - Age: 2, - Fare: 29.125, - Name: 'Rice, Master. Eugene', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '244373', - Parch: 0, - Cabin: null, - PassengerId: 18, - Age: null, - Fare: 13, - Name: 'Williams, Mr. Charles Eugene', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '345763', - Parch: 0, - Cabin: null, - PassengerId: 19, - Age: 31, - Fare: 18, - Name: 'Vander Planke, Mrs. Julius (Emelia Maria Vandemoortele)', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '2649', - Parch: 0, - Cabin: null, - PassengerId: 20, - Age: null, - Fare: 7.225, - Name: 'Masselmani, Mrs. Fatima', - Survived: true, - Pclass: 3, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '239865', - Parch: 0, - Cabin: null, - PassengerId: 21, - Age: 35, - Fare: 26, - Name: 'Fynney, Mr. Joseph J', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '248698', - Parch: 0, - Cabin: 'D56', - PassengerId: 22, - Age: 34, - Fare: 13, - Name: 'Beesley, Mr. Lawrence', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '330923', - Parch: 0, - Cabin: null, - PassengerId: 23, - Age: 15, - Fare: 8.0292, - Name: 'McGowan, Miss. Anna "Annie"', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '113788', - Parch: 0, - Cabin: 'A6', - PassengerId: 24, - Age: 28, - Fare: 35.5, - Name: 'Sloper, Mr. William Thompson', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 3, - Ticket: '349909', - Parch: 1, - Cabin: null, - PassengerId: 25, - Age: 8, - Fare: 21.075, - Name: 'Palsson, Miss. Torborg Danira', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '347077', - Parch: 5, - Cabin: null, - PassengerId: 26, - Age: 38, - Fare: 31.3875, - Name: 'Asplund, Mrs. Carl Oscar (Selma Augusta Emilia Johansson)', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '2631', - Parch: 0, - Cabin: null, - PassengerId: 27, - Age: null, - Fare: 7.225, - Name: 'Emir, Mr. Farred Chehab', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 3, - Ticket: '19950', - Parch: 2, - Cabin: 'C23 C25 C27', - PassengerId: 28, - Age: 19, - Fare: 263, - Name: 'Fortune, Mr. Charles Alexander', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '330959', - Parch: 0, - Cabin: null, - PassengerId: 29, - Age: null, - Fare: 7.8792, - Name: 'O\'Dwyer, Miss. Ellen "Nellie"', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '349216', - Parch: 0, - Cabin: null, - PassengerId: 30, - Age: null, - Fare: 7.8958, - Name: 'Todoroff, Mr. Lalio', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'PC 17601', - Parch: 0, - Cabin: null, - PassengerId: 31, - Age: 40, - Fare: 27.7208, - Name: 'Uruchurtu, Don. Manuel E', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'PC 17569', - Parch: 0, - Cabin: 'B78', - PassengerId: 32, - Age: null, - Fare: 146.5208, - Name: 'Spencer, Mrs. William Augustus (Marie Eugenie)', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '335677', - Parch: 0, - Cabin: null, - PassengerId: 33, - Age: null, - Fare: 7.75, - Name: 'Glynn, Miss. Mary Agatha', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'C.A. 24579', - Parch: 0, - Cabin: null, - PassengerId: 34, - Age: 66, - Fare: 10.5, - Name: 'Wheadon, Mr. Edward H', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'PC 17604', - Parch: 0, - Cabin: null, - PassengerId: 35, - Age: 28, - Fare: 82.1708, - Name: 'Meyer, Mr. Edgar Joseph', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '113789', - Parch: 0, - Cabin: null, - PassengerId: 36, - Age: 42, - Fare: 52, - Name: 'Holverson, Mr. Alexander Oskar', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '2677', - Parch: 0, - Cabin: null, - PassengerId: 37, - Age: null, - Fare: 7.2292, - Name: 'Mamee, Mr. Hanna', - Survived: true, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'A./5. 2152', - Parch: 0, - Cabin: null, - PassengerId: 38, - Age: 21, - Fare: 8.05, - Name: 'Cann, Mr. Ernest Charles', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 2, - Ticket: '345764', - Parch: 0, - Cabin: null, - PassengerId: 39, - Age: 18, - Fare: 18, - Name: 'Vander Planke, Miss. Augusta Maria', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '2651', - Parch: 0, - Cabin: null, - PassengerId: 40, - Age: 14, - Fare: 11.2417, - Name: 'Nicola-Yarred, Miss. Jamila', - Survived: true, - Pclass: 3, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '7546', - Parch: 0, - Cabin: null, - PassengerId: 41, - Age: 40, - Fare: 9.475, - Name: 'Ahlin, Mrs. Johan (Johanna Persdotter Larsson)', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '11668', - Parch: 0, - Cabin: null, - PassengerId: 42, - Age: 27, - Fare: 21, - Name: 'Turpin, Mrs. William John Robert (Dorothy Ann Wonnacott)', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '349253', - Parch: 0, - Cabin: null, - PassengerId: 43, - Age: null, - Fare: 7.8958, - Name: 'Kraeff, Mr. Theodor', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'SC/Paris 2123', - Parch: 2, - Cabin: null, - PassengerId: 44, - Age: 3, - Fare: 41.5792, - Name: 'Laroche, Miss. Simonne Marie Anne Andree', - Survived: true, - Pclass: 2, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '330958', - Parch: 0, - Cabin: null, - PassengerId: 45, - Age: 19, - Fare: 7.8792, - Name: 'Devaney, Miss. Margaret Delia', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'S.C./A.4. 23567', - Parch: 0, - Cabin: null, - PassengerId: 46, - Age: null, - Fare: 8.05, - Name: 'Rogers, Mr. William John', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '370371', - Parch: 0, - Cabin: null, - PassengerId: 47, - Age: null, - Fare: 15.5, - Name: 'Lennon, Mr. Denis', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '14311', - Parch: 0, - Cabin: null, - PassengerId: 48, - Age: null, - Fare: 7.75, - Name: "O'Driscoll, Miss. Bridget", - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 2, - Ticket: '2662', - Parch: 0, - Cabin: null, - PassengerId: 49, - Age: null, - Fare: 21.6792, - Name: 'Samaan, Mr. Youssef', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '349237', - Parch: 0, - Cabin: null, - PassengerId: 50, - Age: 18, - Fare: 17.8, - Name: 'Arnold-Franchi, Mrs. Josef (Josefine Franchi)', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 4, - Ticket: '3101295', - Parch: 1, - Cabin: null, - PassengerId: 51, - Age: 7, - Fare: 39.6875, - Name: 'Panula, Master. Juha Niilo', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'A/4. 39886', - Parch: 0, - Cabin: null, - PassengerId: 52, - Age: 21, - Fare: 7.8, - Name: 'Nosworthy, Mr. Richard Cater', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'PC 17572', - Parch: 0, - Cabin: 'D33', - PassengerId: 53, - Age: 49, - Fare: 76.7292, - Name: 'Harper, Mrs. Henry Sleeper (Myna Haxtun)', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '2926', - Parch: 0, - Cabin: null, - PassengerId: 54, - Age: 29, - Fare: 26, - Name: 'Faunthorpe, Mrs. Lizzie (Elizabeth Anne Wilkinson)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '113509', - Parch: 1, - Cabin: 'B30', - PassengerId: 55, - Age: 65, - Fare: 61.9792, - Name: 'Ostby, Mr. Engelhart Cornelius', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '19947', - Parch: 0, - Cabin: 'C52', - PassengerId: 56, - Age: null, - Fare: 35.5, - Name: 'Woolner, Mr. Hugh', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'C.A. 31026', - Parch: 0, - Cabin: null, - PassengerId: 57, - Age: 21, - Fare: 10.5, - Name: 'Rugg, Miss. Emily', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '2697', - Parch: 0, - Cabin: null, - PassengerId: 58, - Age: 28.5, - Fare: 7.2292, - Name: 'Novel, Mr. Mansouer', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'C.A. 34651', - Parch: 2, - Cabin: null, - PassengerId: 59, - Age: 5, - Fare: 27.75, - Name: 'West, Miss. Constance Mirium', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 5, - Ticket: 'CA 2144', - Parch: 2, - Cabin: null, - PassengerId: 60, - Age: 11, - Fare: 46.9, - Name: 'Goodwin, Master. William Frederick', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '2669', - Parch: 0, - Cabin: null, - PassengerId: 61, - Age: 22, - Fare: 7.2292, - Name: 'Sirayanian, Mr. Orsen', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '113572', - Parch: 0, - Cabin: 'B28', - PassengerId: 62, - Age: 38, - Fare: 80, - Name: 'Icard, Miss. Amelie', - Survived: true, - Pclass: 1, - Embarked: null, - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '36973', - Parch: 0, - Cabin: 'C83', - PassengerId: 63, - Age: 45, - Fare: 83.475, - Name: 'Harris, Mr. Henry Birkhardt', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 3, - Ticket: '347088', - Parch: 2, - Cabin: null, - PassengerId: 64, - Age: 4, - Fare: 27.9, - Name: 'Skoog, Master. Harald', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'PC 17605', - Parch: 0, - Cabin: null, - PassengerId: 65, - Age: null, - Fare: 27.7208, - Name: 'Stewart, Mr. Albert A', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '2661', - Parch: 1, - Cabin: null, - PassengerId: 66, - Age: null, - Fare: 15.2458, - Name: 'Moubarek, Master. Gerios', - Survived: true, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'C.A. 29395', - Parch: 0, - Cabin: 'F33', - PassengerId: 67, - Age: 29, - Fare: 10.5, - Name: 'Nye, Mrs. (Elizabeth Ramell)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'S.P. 3464', - Parch: 0, - Cabin: null, - PassengerId: 68, - Age: 19, - Fare: 8.1583, - Name: 'Crease, Mr. Ernest James', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 4, - Ticket: '3101281', - Parch: 2, - Cabin: null, - PassengerId: 69, - Age: 17, - Fare: 7.925, - Name: 'Andersson, Miss. Erna Alexandra', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 2, - Ticket: '315151', - Parch: 0, - Cabin: null, - PassengerId: 70, - Age: 26, - Fare: 8.6625, - Name: 'Kink, Mr. Vincenz', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'C.A. 33111', - Parch: 0, - Cabin: null, - PassengerId: 71, - Age: 32, - Fare: 10.5, - Name: 'Jenkin, Mr. Stephen Curnow', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 5, - Ticket: 'CA 2144', - Parch: 2, - Cabin: null, - PassengerId: 72, - Age: 16, - Fare: 46.9, - Name: 'Goodwin, Miss. Lillian Amy', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'S.O.C. 14879', - Parch: 0, - Cabin: null, - PassengerId: 73, - Age: 21, - Fare: 73.5, - Name: 'Hood, Mr. Ambrose Jr', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '2680', - Parch: 0, - Cabin: null, - PassengerId: 74, - Age: 26, - Fare: 14.4542, - Name: 'Chronopoulos, Mr. Apostolos', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '1601', - Parch: 0, - Cabin: null, - PassengerId: 75, - Age: 32, - Fare: 56.4958, - Name: 'Bing, Mr. Lee', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '348123', - Parch: 0, - Cabin: 'F G73', - PassengerId: 76, - Age: 25, - Fare: 7.65, - Name: 'Moen, Mr. Sigurd Hansen', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349208', - Parch: 0, - Cabin: null, - PassengerId: 77, - Age: null, - Fare: 7.8958, - Name: 'Staneff, Mr. Ivan', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '374746', - Parch: 0, - Cabin: null, - PassengerId: 78, - Age: null, - Fare: 8.05, - Name: 'Moutal, Mr. Rahamin Haim', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '248738', - Parch: 2, - Cabin: null, - PassengerId: 79, - Age: 0.83, - Fare: 29, - Name: 'Caldwell, Master. Alden Gates', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '364516', - Parch: 0, - Cabin: null, - PassengerId: 80, - Age: 30, - Fare: 12.475, - Name: 'Dowdell, Miss. Elizabeth', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '345767', - Parch: 0, - Cabin: null, - PassengerId: 81, - Age: 22, - Fare: 9, - Name: 'Waelens, Mr. Achille', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '345779', - Parch: 0, - Cabin: null, - PassengerId: 82, - Age: 29, - Fare: 9.5, - Name: 'Sheerlinck, Mr. Jan Baptist', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '330932', - Parch: 0, - Cabin: null, - PassengerId: 83, - Age: null, - Fare: 7.7875, - Name: 'McDermott, Miss. Brigdet Delia', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '113059', - Parch: 0, - Cabin: null, - PassengerId: 84, - Age: 28, - Fare: 47.1, - Name: 'Carrau, Mr. Francisco M', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'SO/C 14885', - Parch: 0, - Cabin: null, - PassengerId: 85, - Age: 17, - Fare: 10.5, - Name: 'Ilett, Miss. Bertha', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 3, - Ticket: '3101278', - Parch: 0, - Cabin: null, - PassengerId: 86, - Age: 33, - Fare: 15.85, - Name: 'Backstrom, Mrs. Karl Alfred (Maria Mathilda Gustafsson)', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: 'W./C. 6608', - Parch: 3, - Cabin: null, - PassengerId: 87, - Age: 16, - Fare: 34.375, - Name: 'Ford, Mr. William Neal', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'SOTON/OQ 392086', - Parch: 0, - Cabin: null, - PassengerId: 88, - Age: null, - Fare: 8.05, - Name: 'Slocovski, Mr. Selman Francis', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 3, - Ticket: '19950', - Parch: 2, - Cabin: 'C23 C25 C27', - PassengerId: 89, - Age: 23, - Fare: 263, - Name: 'Fortune, Miss. Mabel Helen', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '343275', - Parch: 0, - Cabin: null, - PassengerId: 90, - Age: 24, - Fare: 8.05, - Name: 'Celotti, Mr. Francesco', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '343276', - Parch: 0, - Cabin: null, - PassengerId: 91, - Age: 29, - Fare: 8.05, - Name: 'Christmann, Mr. Emil', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '347466', - Parch: 0, - Cabin: null, - PassengerId: 92, - Age: 20, - Fare: 7.8542, - Name: 'Andreasson, Mr. Paul Edvin', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'W.E.P. 5734', - Parch: 0, - Cabin: 'E31', - PassengerId: 93, - Age: 46, - Fare: 61.175, - Name: 'Chaffee, Mr. Herbert Fuller', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'C.A. 2315', - Parch: 2, - Cabin: null, - PassengerId: 94, - Age: 26, - Fare: 20.575, - Name: 'Dean, Mr. Bertram Frank', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '364500', - Parch: 0, - Cabin: null, - PassengerId: 95, - Age: 59, - Fare: 7.25, - Name: 'Coxon, Mr. Daniel', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '374910', - Parch: 0, - Cabin: null, - PassengerId: 96, - Age: null, - Fare: 8.05, - Name: 'Shorney, Mr. Charles Joseph', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'PC 17754', - Parch: 0, - Cabin: 'A5', - PassengerId: 97, - Age: 71, - Fare: 34.6542, - Name: 'Goldschmidt, Mr. George B', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'PC 17759', - Parch: 1, - Cabin: 'D10 D12', - PassengerId: 98, - Age: 23, - Fare: 63.3583, - Name: 'Greenfield, Mr. William Bertram', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '231919', - Parch: 1, - Cabin: null, - PassengerId: 99, - Age: 34, - Fare: 23, - Name: 'Doling, Mrs. John T (Ada Julia Bone)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '244367', - Parch: 0, - Cabin: null, - PassengerId: 100, - Age: 34, - Fare: 26, - Name: 'Kantor, Mr. Sinai', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349245', - Parch: 0, - Cabin: null, - PassengerId: 101, - Age: 28, - Fare: 7.8958, - Name: 'Petranec, Miss. Matilda', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '349215', - Parch: 0, - Cabin: null, - PassengerId: 102, - Age: null, - Fare: 7.8958, - Name: 'Petroff, Mr. Pastcho ("Pentcho")', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '35281', - Parch: 1, - Cabin: 'D26', - PassengerId: 103, - Age: 21, - Fare: 77.2875, - Name: 'White, Mr. Richard Frasar', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '7540', - Parch: 0, - Cabin: null, - PassengerId: 104, - Age: 33, - Fare: 8.6542, - Name: 'Johansson, Mr. Gustaf Joel', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 2, - Ticket: '3101276', - Parch: 0, - Cabin: null, - PassengerId: 105, - Age: 37, - Fare: 7.925, - Name: 'Gustafsson, Mr. Anders Vilhelm', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349207', - Parch: 0, - Cabin: null, - PassengerId: 106, - Age: 28, - Fare: 7.8958, - Name: 'Mionoff, Mr. Stoytcho', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '343120', - Parch: 0, - Cabin: null, - PassengerId: 107, - Age: 21, - Fare: 7.65, - Name: 'Salkjelsvik, Miss. Anna Kristine', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '312991', - Parch: 0, - Cabin: null, - PassengerId: 108, - Age: null, - Fare: 7.775, - Name: 'Moss, Mr. Albert Johan', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349249', - Parch: 0, - Cabin: null, - PassengerId: 109, - Age: 38, - Fare: 7.8958, - Name: 'Rekic, Mr. Tido', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '371110', - Parch: 0, - Cabin: null, - PassengerId: 110, - Age: null, - Fare: 24.15, - Name: 'Moran, Miss. Bertha', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '110465', - Parch: 0, - Cabin: 'C110', - PassengerId: 111, - Age: 47, - Fare: 52, - Name: 'Porter, Mr. Walter Chamberlain', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '2665', - Parch: 0, - Cabin: null, - PassengerId: 112, - Age: 14.5, - Fare: 14.4542, - Name: 'Zabour, Miss. Hileni', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '324669', - Parch: 0, - Cabin: null, - PassengerId: 113, - Age: 22, - Fare: 8.05, - Name: 'Barton, Mr. David John', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '4136', - Parch: 0, - Cabin: null, - PassengerId: 114, - Age: 20, - Fare: 9.825, - Name: 'Jussila, Miss. Katriina', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '2627', - Parch: 0, - Cabin: null, - PassengerId: 115, - Age: 17, - Fare: 14.4583, - Name: 'Attalah, Miss. Malake', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'STON/O 2. 3101294', - Parch: 0, - Cabin: null, - PassengerId: 116, - Age: 21, - Fare: 7.925, - Name: 'Pekoniemi, Mr. Edvard', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '370369', - Parch: 0, - Cabin: null, - PassengerId: 117, - Age: 70.5, - Fare: 7.75, - Name: 'Connors, Mr. Patrick', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '11668', - Parch: 0, - Cabin: null, - PassengerId: 118, - Age: 29, - Fare: 21, - Name: 'Turpin, Mr. William John Robert', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'PC 17558', - Parch: 1, - Cabin: 'B58 B60', - PassengerId: 119, - Age: 24, - Fare: 247.5208, - Name: 'Baxter, Mr. Quigg Edmond', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 4, - Ticket: '347082', - Parch: 2, - Cabin: null, - PassengerId: 120, - Age: 2, - Fare: 31.275, - Name: 'Andersson, Miss. Ellis Anna Maria', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 2, - Ticket: 'S.O.C. 14879', - Parch: 0, - Cabin: null, - PassengerId: 121, - Age: 21, - Fare: 73.5, - Name: 'Hickman, Mr. Stanley George', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'A4. 54510', - Parch: 0, - Cabin: null, - PassengerId: 122, - Age: null, - Fare: 8.05, - Name: 'Moore, Mr. Leonard Charles', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '237736', - Parch: 0, - Cabin: null, - PassengerId: 123, - Age: 32.5, - Fare: 30.0708, - Name: 'Nasser, Mr. Nicholas', - Survived: false, - Pclass: 2, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '27267', - Parch: 0, - Cabin: 'E101', - PassengerId: 124, - Age: 32.5, - Fare: 13, - Name: 'Webber, Miss. Susan', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '35281', - Parch: 1, - Cabin: 'D26', - PassengerId: 125, - Age: 54, - Fare: 77.2875, - Name: 'White, Mr. Percival Wayland', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '2651', - Parch: 0, - Cabin: null, - PassengerId: 126, - Age: 12, - Fare: 11.2417, - Name: 'Nicola-Yarred, Master. Elias', - Survived: true, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '370372', - Parch: 0, - Cabin: null, - PassengerId: 127, - Age: null, - Fare: 7.75, - Name: 'McMahon, Mr. Martin', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'C 17369', - Parch: 0, - Cabin: null, - PassengerId: 128, - Age: 24, - Fare: 7.1417, - Name: 'Madsen, Mr. Fridtjof Arne', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '2668', - Parch: 1, - Cabin: 'F E69', - PassengerId: 129, - Age: null, - Fare: 22.3583, - Name: 'Peter, Miss. Anna', - Survived: true, - Pclass: 3, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '347061', - Parch: 0, - Cabin: null, - PassengerId: 130, - Age: 45, - Fare: 6.975, - Name: 'Ekstrom, Mr. Johan', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349241', - Parch: 0, - Cabin: null, - PassengerId: 131, - Age: 33, - Fare: 7.8958, - Name: 'Drazenoic, Mr. Jozef', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'SOTON/O.Q. 3101307', - Parch: 0, - Cabin: null, - PassengerId: 132, - Age: 20, - Fare: 7.05, - Name: 'Coelho, Mr. Domingos Fernandeo', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'A/5. 3337', - Parch: 0, - Cabin: null, - PassengerId: 133, - Age: 47, - Fare: 14.5, - Name: 'Robins, Mrs. Alexander A (Grace Charity Laury)', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '228414', - Parch: 0, - Cabin: null, - PassengerId: 134, - Age: 29, - Fare: 26, - Name: 'Weisz, Mrs. Leopold (Mathilde Francoise Pede)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'C.A. 29178', - Parch: 0, - Cabin: null, - PassengerId: 135, - Age: 25, - Fare: 13, - Name: 'Sobey, Mr. Samuel James Hayden', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'SC/PARIS 2133', - Parch: 0, - Cabin: null, - PassengerId: 136, - Age: 23, - Fare: 15.0458, - Name: 'Richard, Mr. Emile', - Survived: false, - Pclass: 2, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '11752', - Parch: 2, - Cabin: 'D47', - PassengerId: 137, - Age: 19, - Fare: 26.2833, - Name: 'Newsom, Miss. Helen Monypeny', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '113803', - Parch: 0, - Cabin: 'C123', - PassengerId: 138, - Age: 37, - Fare: 53.1, - Name: 'Futrelle, Mr. Jacques Heath', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '7534', - Parch: 0, - Cabin: null, - PassengerId: 139, - Age: 16, - Fare: 9.2167, - Name: 'Osen, Mr. Olaf Elon', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'PC 17593', - Parch: 0, - Cabin: 'B86', - PassengerId: 140, - Age: 24, - Fare: 79.2, - Name: 'Giglio, Mr. Victor', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '2678', - Parch: 2, - Cabin: null, - PassengerId: 141, - Age: null, - Fare: 15.2458, - Name: 'Boulos, Mrs. Joseph (Sultana)', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '347081', - Parch: 0, - Cabin: null, - PassengerId: 142, - Age: 22, - Fare: 7.75, - Name: 'Nysten, Miss. Anna Sofia', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: 'STON/O2. 3101279', - Parch: 0, - Cabin: null, - PassengerId: 143, - Age: 24, - Fare: 15.85, - Name: 'Hakkarainen, Mrs. Pekka Pietari (Elin Matilda Dolck)', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '365222', - Parch: 0, - Cabin: null, - PassengerId: 144, - Age: 19, - Fare: 6.75, - Name: 'Burke, Mr. Jeremiah', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '231945', - Parch: 0, - Cabin: null, - PassengerId: 145, - Age: 18, - Fare: 11.5, - Name: 'Andrew, Mr. Edgardo Samuel', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'C.A. 33112', - Parch: 1, - Cabin: null, - PassengerId: 146, - Age: 19, - Fare: 36.75, - Name: 'Nicholls, Mr. Joseph Charles', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '350043', - Parch: 0, - Cabin: null, - PassengerId: 147, - Age: 27, - Fare: 7.7958, - Name: 'Andersson, Mr. August Edvard ("Wennerstrom")', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 2, - Ticket: 'W./C. 6608', - Parch: 2, - Cabin: null, - PassengerId: 148, - Age: 9, - Fare: 34.375, - Name: 'Ford, Miss. Robina Maggie "Ruby"', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '230080', - Parch: 2, - Cabin: 'F2', - PassengerId: 149, - Age: 36.5, - Fare: 26, - Name: 'Navratil, Mr. Michel ("Louis M Hoffman")', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '244310', - Parch: 0, - Cabin: null, - PassengerId: 150, - Age: 42, - Fare: 13, - Name: 'Byles, Rev. Thomas Roussel Davids', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'S.O.P. 1166', - Parch: 0, - Cabin: null, - PassengerId: 151, - Age: 51, - Fare: 12.525, - Name: 'Bateman, Rev. Robert James', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '113776', - Parch: 0, - Cabin: 'C2', - PassengerId: 152, - Age: 22, - Fare: 66.6, - Name: 'Pears, Mrs. Thomas (Edith Wearne)', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'A.5. 11206', - Parch: 0, - Cabin: null, - PassengerId: 153, - Age: 55.5, - Fare: 8.05, - Name: 'Meo, Mr. Alfonzo', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'A/5. 851', - Parch: 2, - Cabin: null, - PassengerId: 154, - Age: 40.5, - Fare: 14.5, - Name: 'van Billiard, Mr. Austin Blyler', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'Fa 265302', - Parch: 0, - Cabin: null, - PassengerId: 155, - Age: null, - Fare: 7.3125, - Name: 'Olsen, Mr. Ole Martin', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'PC 17597', - Parch: 1, - Cabin: null, - PassengerId: 156, - Age: 51, - Fare: 61.3792, - Name: 'Williams, Mr. Charles Duane', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '35851', - Parch: 0, - Cabin: null, - PassengerId: 157, - Age: 16, - Fare: 7.7333, - Name: 'Gilnagh, Miss. Katherine "Katie"', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'SOTON/OQ 392090', - Parch: 0, - Cabin: null, - PassengerId: 158, - Age: 30, - Fare: 8.05, - Name: 'Corn, Mr. Harry', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '315037', - Parch: 0, - Cabin: null, - PassengerId: 159, - Age: null, - Fare: 8.6625, - Name: 'Smiljanic, Mr. Mile', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 8, - Ticket: 'CA. 2343', - Parch: 2, - Cabin: null, - PassengerId: 160, - Age: null, - Fare: 69.55, - Name: 'Sage, Master. Thomas Henry', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '371362', - Parch: 1, - Cabin: null, - PassengerId: 161, - Age: 44, - Fare: 16.1, - Name: 'Cribb, Mr. John Hatfield', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'C.A. 33595', - Parch: 0, - Cabin: null, - PassengerId: 162, - Age: 40, - Fare: 15.75, - Name: 'Watt, Mrs. James (Elizabeth "Bessie" Inglis Milne)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '347068', - Parch: 0, - Cabin: null, - PassengerId: 163, - Age: 26, - Fare: 7.775, - Name: 'Bengtsson, Mr. John Viktor', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '315093', - Parch: 0, - Cabin: null, - PassengerId: 164, - Age: 17, - Fare: 8.6625, - Name: 'Calic, Mr. Jovo', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 4, - Ticket: '3101295', - Parch: 1, - Cabin: null, - PassengerId: 165, - Age: 1, - Fare: 39.6875, - Name: 'Panula, Master. Eino Viljami', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '363291', - Parch: 2, - Cabin: null, - PassengerId: 166, - Age: 9, - Fare: 20.525, - Name: 'Goldsmith, Master. Frank John William "Frankie"', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '113505', - Parch: 1, - Cabin: 'E33', - PassengerId: 167, - Age: null, - Fare: 55, - Name: 'Chibnall, Mrs. (Edith Martha Bowerman)', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '347088', - Parch: 4, - Cabin: null, - PassengerId: 168, - Age: 45, - Fare: 27.9, - Name: 'Skoog, Mrs. William (Anna Bernhardina Karlsson)', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'PC 17318', - Parch: 0, - Cabin: null, - PassengerId: 169, - Age: null, - Fare: 25.925, - Name: 'Baumann, Mr. John D', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '1601', - Parch: 0, - Cabin: null, - PassengerId: 170, - Age: 28, - Fare: 56.4958, - Name: 'Ling, Mr. Lee', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '111240', - Parch: 0, - Cabin: 'B19', - PassengerId: 171, - Age: 61, - Fare: 33.5, - Name: 'Van der hoef, Mr. Wyckoff', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 4, - Ticket: '382652', - Parch: 1, - Cabin: null, - PassengerId: 172, - Age: 4, - Fare: 29.125, - Name: 'Rice, Master. Arthur', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '347742', - Parch: 1, - Cabin: null, - PassengerId: 173, - Age: 1, - Fare: 11.1333, - Name: 'Johnson, Miss. Eleanor Ileen', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'STON/O 2. 3101280', - Parch: 0, - Cabin: null, - PassengerId: 174, - Age: 21, - Fare: 7.925, - Name: 'Sivola, Mr. Antti Wilhelm', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '17764', - Parch: 0, - Cabin: 'A7', - PassengerId: 175, - Age: 56, - Fare: 30.6958, - Name: 'Smith, Mr. James Clinch', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '350404', - Parch: 1, - Cabin: null, - PassengerId: 176, - Age: 18, - Fare: 7.8542, - Name: 'Klasen, Mr. Klas Albin', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 3, - Ticket: '4133', - Parch: 1, - Cabin: null, - PassengerId: 177, - Age: null, - Fare: 25.4667, - Name: 'Lefebre, Master. Henry Forbes', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'PC 17595', - Parch: 0, - Cabin: 'C49', - PassengerId: 178, - Age: 50, - Fare: 28.7125, - Name: 'Isham, Miss. Ann Elizabeth', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '250653', - Parch: 0, - Cabin: null, - PassengerId: 179, - Age: 30, - Fare: 13, - Name: 'Hale, Mr. Reginald', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'LINE', - Parch: 0, - Cabin: null, - PassengerId: 180, - Age: 36, - Fare: 0, - Name: 'Leonard, Mr. Lionel', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 8, - Ticket: 'CA. 2343', - Parch: 2, - Cabin: null, - PassengerId: 181, - Age: null, - Fare: 69.55, - Name: 'Sage, Miss. Constance Gladys', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'SC/PARIS 2131', - Parch: 0, - Cabin: null, - PassengerId: 182, - Age: null, - Fare: 15.05, - Name: 'Pernot, Mr. Rene', - Survived: false, - Pclass: 2, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 4, - Ticket: '347077', - Parch: 2, - Cabin: null, - PassengerId: 183, - Age: 9, - Fare: 31.3875, - Name: 'Asplund, Master. Clarence Gustaf Hugo', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 2, - Ticket: '230136', - Parch: 1, - Cabin: 'F4', - PassengerId: 184, - Age: 1, - Fare: 39, - Name: 'Becker, Master. Richard F', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '315153', - Parch: 2, - Cabin: null, - PassengerId: 185, - Age: 4, - Fare: 22.025, - Name: 'Kink-Heilmann, Miss. Luise Gretchen', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '113767', - Parch: 0, - Cabin: 'A32', - PassengerId: 186, - Age: null, - Fare: 50, - Name: 'Rood, Mr. Hugh Roscoe', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '370365', - Parch: 0, - Cabin: null, - PassengerId: 187, - Age: null, - Fare: 15.5, - Name: 'O\'Brien, Mrs. Thomas (Johanna "Hannah" Godfrey)', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '111428', - Parch: 0, - Cabin: null, - PassengerId: 188, - Age: 45, - Fare: 26.55, - Name: 'Romaine, Mr. Charles Hallace ("Mr C Rolmane")', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '364849', - Parch: 1, - Cabin: null, - PassengerId: 189, - Age: 40, - Fare: 15.5, - Name: 'Bourke, Mr. John', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349247', - Parch: 0, - Cabin: null, - PassengerId: 190, - Age: 36, - Fare: 7.8958, - Name: 'Turcin, Mr. Stjepan', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '234604', - Parch: 0, - Cabin: null, - PassengerId: 191, - Age: 32, - Fare: 13, - Name: 'Pinsky, Mrs. (Rosa)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '28424', - Parch: 0, - Cabin: null, - PassengerId: 192, - Age: 19, - Fare: 13, - Name: 'Carbines, Mr. William', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '350046', - Parch: 0, - Cabin: null, - PassengerId: 193, - Age: 19, - Fare: 7.8542, - Name: 'Andersen-Jensen, Miss. Carla Christine Nielsine', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '230080', - Parch: 1, - Cabin: 'F2', - PassengerId: 194, - Age: 3, - Fare: 26, - Name: 'Navratil, Master. Michel M', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'PC 17610', - Parch: 0, - Cabin: 'B4', - PassengerId: 195, - Age: 44, - Fare: 27.7208, - Name: 'Brown, Mrs. James Joseph (Margaret Tobin)', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'PC 17569', - Parch: 0, - Cabin: 'B80', - PassengerId: 196, - Age: 58, - Fare: 146.5208, - Name: 'Lurette, Miss. Elise', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '368703', - Parch: 0, - Cabin: null, - PassengerId: 197, - Age: null, - Fare: 7.75, - Name: 'Mernagh, Mr. Robert', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '4579', - Parch: 1, - Cabin: null, - PassengerId: 198, - Age: 42, - Fare: 8.4042, - Name: 'Olsen, Mr. Karl Siegwart Andreas', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '370370', - Parch: 0, - Cabin: null, - PassengerId: 199, - Age: null, - Fare: 7.75, - Name: 'Madigan, Miss. Margaret "Maggie"', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '248747', - Parch: 0, - Cabin: null, - PassengerId: 200, - Age: 24, - Fare: 13, - Name: 'Yrois, Miss. Henriette ("Mrs Harbeck")', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '345770', - Parch: 0, - Cabin: null, - PassengerId: 201, - Age: 28, - Fare: 9.5, - Name: 'Vande Walle, Mr. Nestor Cyriel', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 8, - Ticket: 'CA. 2343', - Parch: 2, - Cabin: null, - PassengerId: 202, - Age: null, - Fare: 69.55, - Name: 'Sage, Mr. Frederick', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '3101264', - Parch: 0, - Cabin: null, - PassengerId: 203, - Age: 34, - Fare: 6.4958, - Name: 'Johanson, Mr. Jakob Alfred', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '2628', - Parch: 0, - Cabin: null, - PassengerId: 204, - Age: 45.5, - Fare: 7.225, - Name: 'Youseff, Mr. Gerious', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'A/5 3540', - Parch: 0, - Cabin: null, - PassengerId: 205, - Age: 18, - Fare: 8.05, - Name: 'Cohen, Mr. Gurshon "Gus"', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '347054', - Parch: 1, - Cabin: 'G6', - PassengerId: 206, - Age: 2, - Fare: 10.4625, - Name: 'Strom, Miss. Telma Matilda', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '3101278', - Parch: 0, - Cabin: null, - PassengerId: 207, - Age: 32, - Fare: 15.85, - Name: 'Backstrom, Mr. Karl Alfred', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '2699', - Parch: 0, - Cabin: null, - PassengerId: 208, - Age: 26, - Fare: 18.7875, - Name: 'Albimona, Mr. Nassef Cassem', - Survived: true, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '367231', - Parch: 0, - Cabin: null, - PassengerId: 209, - Age: 16, - Fare: 7.75, - Name: 'Carr, Miss. Helen "Ellen"', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '112277', - Parch: 0, - Cabin: 'A31', - PassengerId: 210, - Age: 40, - Fare: 31, - Name: 'Blank, Mr. Henry', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'SOTON/O.Q. 3101311', - Parch: 0, - Cabin: null, - PassengerId: 211, - Age: 24, - Fare: 7.05, - Name: 'Ali, Mr. Ahmed', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'F.C.C. 13528', - Parch: 0, - Cabin: null, - PassengerId: 212, - Age: 35, - Fare: 21, - Name: 'Cameron, Miss. Clear Annie', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'A/5 21174', - Parch: 0, - Cabin: null, - PassengerId: 213, - Age: 22, - Fare: 7.25, - Name: 'Perkin, Mr. John Henry', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '250646', - Parch: 0, - Cabin: null, - PassengerId: 214, - Age: 30, - Fare: 13, - Name: 'Givard, Mr. Hans Kristensen', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '367229', - Parch: 0, - Cabin: null, - PassengerId: 215, - Age: null, - Fare: 7.75, - Name: 'Kiernan, Mr. Philip', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '35273', - Parch: 0, - Cabin: 'D36', - PassengerId: 216, - Age: 31, - Fare: 113.275, - Name: 'Newell, Miss. Madeleine', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'STON/O2. 3101283', - Parch: 0, - Cabin: null, - PassengerId: 217, - Age: 27, - Fare: 7.925, - Name: 'Honkanen, Miss. Eliina', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '243847', - Parch: 0, - Cabin: null, - PassengerId: 218, - Age: 42, - Fare: 27, - Name: 'Jacobsohn, Mr. Sidney Samuel', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '11813', - Parch: 0, - Cabin: 'D15', - PassengerId: 219, - Age: 32, - Fare: 76.2917, - Name: 'Bazzani, Miss. Albina', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'W/C 14208', - Parch: 0, - Cabin: null, - PassengerId: 220, - Age: 30, - Fare: 10.5, - Name: 'Harris, Mr. Walter', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'SOTON/OQ 392089', - Parch: 0, - Cabin: null, - PassengerId: 221, - Age: 16, - Fare: 8.05, - Name: 'Sunderland, Mr. Victor Francis', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '220367', - Parch: 0, - Cabin: null, - PassengerId: 222, - Age: 27, - Fare: 13, - Name: 'Bracken, Mr. James H', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '21440', - Parch: 0, - Cabin: null, - PassengerId: 223, - Age: 51, - Fare: 8.05, - Name: 'Green, Mr. George Henry', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349234', - Parch: 0, - Cabin: null, - PassengerId: 224, - Age: null, - Fare: 7.8958, - Name: 'Nenkoff, Mr. Christo', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '19943', - Parch: 0, - Cabin: 'C93', - PassengerId: 225, - Age: 38, - Fare: 90, - Name: 'Hoyt, Mr. Frederick Maxfield', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'PP 4348', - Parch: 0, - Cabin: null, - PassengerId: 226, - Age: 22, - Fare: 9.35, - Name: 'Berglund, Mr. Karl Ivar Sven', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'SW/PP 751', - Parch: 0, - Cabin: null, - PassengerId: 227, - Age: 19, - Fare: 10.5, - Name: 'Mellors, Mr. William John', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'A/5 21173', - Parch: 0, - Cabin: null, - PassengerId: 228, - Age: 20.5, - Fare: 7.25, - Name: 'Lovell, Mr. John Hall ("Henry")', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '236171', - Parch: 0, - Cabin: null, - PassengerId: 229, - Age: 18, - Fare: 13, - Name: 'Fahlstrom, Mr. Arne Jonas', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 3, - Ticket: '4133', - Parch: 1, - Cabin: null, - PassengerId: 230, - Age: null, - Fare: 25.4667, - Name: 'Lefebre, Miss. Mathilde', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '36973', - Parch: 0, - Cabin: 'C83', - PassengerId: 231, - Age: 35, - Fare: 83.475, - Name: 'Harris, Mrs. Henry Birkhardt (Irene Wallach)', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '347067', - Parch: 0, - Cabin: null, - PassengerId: 232, - Age: 29, - Fare: 7.775, - Name: 'Larsson, Mr. Bengt Edvin', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '237442', - Parch: 0, - Cabin: null, - PassengerId: 233, - Age: 59, - Fare: 13.5, - Name: 'Sjostedt, Mr. Ernst Adolf', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 4, - Ticket: '347077', - Parch: 2, - Cabin: null, - PassengerId: 234, - Age: 5, - Fare: 31.3875, - Name: 'Asplund, Miss. Lillian Gertrud', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'C.A. 29566', - Parch: 0, - Cabin: null, - PassengerId: 235, - Age: 24, - Fare: 10.5, - Name: 'Leyson, Mr. Robert William Norman', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'W./C. 6609', - Parch: 0, - Cabin: null, - PassengerId: 236, - Age: null, - Fare: 7.55, - Name: 'Harknett, Miss. Alice Phoebe', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '26707', - Parch: 0, - Cabin: null, - PassengerId: 237, - Age: 44, - Fare: 26, - Name: 'Hold, Mr. Stephen', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'C.A. 31921', - Parch: 2, - Cabin: null, - PassengerId: 238, - Age: 8, - Fare: 26.25, - Name: 'Collyer, Miss. Marjorie "Lottie"', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '28665', - Parch: 0, - Cabin: null, - PassengerId: 239, - Age: 19, - Fare: 10.5, - Name: 'Pengelly, Mr. Frederick William', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'SCO/W 1585', - Parch: 0, - Cabin: null, - PassengerId: 240, - Age: 33, - Fare: 12.275, - Name: 'Hunt, Mr. George Henry', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '2665', - Parch: 0, - Cabin: null, - PassengerId: 241, - Age: null, - Fare: 14.4542, - Name: 'Zabour, Miss. Thamine', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '367230', - Parch: 0, - Cabin: null, - PassengerId: 242, - Age: null, - Fare: 15.5, - Name: 'Murphy, Miss. Katherine "Kate"', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'W./C. 14263', - Parch: 0, - Cabin: null, - PassengerId: 243, - Age: 29, - Fare: 10.5, - Name: 'Coleridge, Mr. Reginald Charles', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'STON/O 2. 3101275', - Parch: 0, - Cabin: null, - PassengerId: 244, - Age: 22, - Fare: 7.125, - Name: 'Maenpaa, Mr. Matti Alexanteri', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '2694', - Parch: 0, - Cabin: null, - PassengerId: 245, - Age: 30, - Fare: 7.225, - Name: 'Attalah, Mr. Sleiman', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 2, - Ticket: '19928', - Parch: 0, - Cabin: 'C78', - PassengerId: 246, - Age: 44, - Fare: 90, - Name: 'Minahan, Dr. William Edward', - Survived: false, - Pclass: 1, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '347071', - Parch: 0, - Cabin: null, - PassengerId: 247, - Age: 25, - Fare: 7.775, - Name: 'Lindahl, Miss. Agda Thorilda Viktoria', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '250649', - Parch: 2, - Cabin: null, - PassengerId: 248, - Age: 24, - Fare: 14.5, - Name: 'Hamalainen, Mrs. William (Anna)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '11751', - Parch: 1, - Cabin: 'D35', - PassengerId: 249, - Age: 37, - Fare: 52.5542, - Name: 'Beckwith, Mr. Richard Leonard', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '244252', - Parch: 0, - Cabin: null, - PassengerId: 250, - Age: 54, - Fare: 26, - Name: 'Carter, Rev. Ernest Courtenay', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '362316', - Parch: 0, - Cabin: null, - PassengerId: 251, - Age: null, - Fare: 7.25, - Name: 'Reed, Mr. James George', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '347054', - Parch: 1, - Cabin: 'G6', - PassengerId: 252, - Age: 29, - Fare: 10.4625, - Name: 'Strom, Mrs. Wilhelm (Elna Matilda Persson)', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '113514', - Parch: 0, - Cabin: 'C87', - PassengerId: 253, - Age: 62, - Fare: 26.55, - Name: 'Stead, Mr. William Thomas', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'A/5. 3336', - Parch: 0, - Cabin: null, - PassengerId: 254, - Age: 30, - Fare: 16.1, - Name: 'Lobb, Mr. William Arthur', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '370129', - Parch: 2, - Cabin: null, - PassengerId: 255, - Age: 41, - Fare: 20.2125, - Name: 'Rosblom, Mrs. Viktor (Helena Wilhelmina)', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '2650', - Parch: 2, - Cabin: null, - PassengerId: 256, - Age: 29, - Fare: 15.2458, - Name: 'Touma, Mrs. Darwis (Hanne Youssef Razi)', - Survived: true, - Pclass: 3, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'PC 17585', - Parch: 0, - Cabin: null, - PassengerId: 257, - Age: null, - Fare: 79.2, - Name: 'Thorne, Mrs. Gertrude Maybelle', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '110152', - Parch: 0, - Cabin: 'B77', - PassengerId: 258, - Age: 30, - Fare: 86.5, - Name: 'Cherry, Miss. Gladys', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'PC 17755', - Parch: 0, - Cabin: null, - PassengerId: 259, - Age: 35, - Fare: 512.3292, - Name: 'Ward, Miss. Anna', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '230433', - Parch: 1, - Cabin: null, - PassengerId: 260, - Age: 50, - Fare: 26, - Name: 'Parrish, Mrs. (Lutie Davis)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '384461', - Parch: 0, - Cabin: null, - PassengerId: 261, - Age: null, - Fare: 7.75, - Name: 'Smith, Mr. Thomas', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 4, - Ticket: '347077', - Parch: 2, - Cabin: null, - PassengerId: 262, - Age: 3, - Fare: 31.3875, - Name: 'Asplund, Master. Edvin Rojj Felix', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '110413', - Parch: 1, - Cabin: 'E67', - PassengerId: 263, - Age: 52, - Fare: 79.65, - Name: 'Taussig, Mr. Emil', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '112059', - Parch: 0, - Cabin: 'B94', - PassengerId: 264, - Age: 40, - Fare: 0, - Name: 'Harrison, Mr. William', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '382649', - Parch: 0, - Cabin: null, - PassengerId: 265, - Age: null, - Fare: 7.75, - Name: 'Henry, Miss. Delia', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'C.A. 17248', - Parch: 0, - Cabin: null, - PassengerId: 266, - Age: 36, - Fare: 10.5, - Name: 'Reeves, Mr. David', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 4, - Ticket: '3101295', - Parch: 1, - Cabin: null, - PassengerId: 267, - Age: 16, - Fare: 39.6875, - Name: 'Panula, Mr. Ernesti Arvid', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '347083', - Parch: 0, - Cabin: null, - PassengerId: 268, - Age: 25, - Fare: 7.775, - Name: 'Persson, Mr. Ernst Ulrik', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'PC 17582', - Parch: 1, - Cabin: 'C125', - PassengerId: 269, - Age: 58, - Fare: 153.4625, - Name: 'Graham, Mrs. William Thompson (Edith Junkins)', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'PC 17760', - Parch: 0, - Cabin: 'C99', - PassengerId: 270, - Age: 35, - Fare: 135.6333, - Name: 'Bissette, Miss. Amelia', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '113798', - Parch: 0, - Cabin: null, - PassengerId: 271, - Age: null, - Fare: 31, - Name: 'Cairns, Mr. Alexander', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'LINE', - Parch: 0, - Cabin: null, - PassengerId: 272, - Age: 25, - Fare: 0, - Name: 'Tornquist, Mr. William Henry', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '250644', - Parch: 1, - Cabin: null, - PassengerId: 273, - Age: 41, - Fare: 19.5, - Name: 'Mellinger, Mrs. (Elizabeth Anne Maidment)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'PC 17596', - Parch: 1, - Cabin: 'C118', - PassengerId: 274, - Age: 37, - Fare: 29.7, - Name: 'Natsch, Mr. Charles H', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '370375', - Parch: 0, - Cabin: null, - PassengerId: 275, - Age: null, - Fare: 7.75, - Name: 'Healy, Miss. Hanora "Nora"', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '13502', - Parch: 0, - Cabin: 'D7', - PassengerId: 276, - Age: 63, - Fare: 77.9583, - Name: 'Andrews, Miss. Kornelia Theodosia', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '347073', - Parch: 0, - Cabin: null, - PassengerId: 277, - Age: 45, - Fare: 7.75, - Name: 'Lindblom, Miss. Augusta Charlotta', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '239853', - Parch: 0, - Cabin: null, - PassengerId: 278, - Age: null, - Fare: 0, - Name: 'Parkes, Mr. Francis "Frank"', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 4, - Ticket: '382652', - Parch: 1, - Cabin: null, - PassengerId: 279, - Age: 7, - Fare: 29.125, - Name: 'Rice, Master. Eric', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'C.A. 2673', - Parch: 1, - Cabin: null, - PassengerId: 280, - Age: 35, - Fare: 20.25, - Name: 'Abbott, Mrs. Stanton (Rosa Hunt)', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '336439', - Parch: 0, - Cabin: null, - PassengerId: 281, - Age: 65, - Fare: 7.75, - Name: 'Duane, Mr. Frank', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '347464', - Parch: 0, - Cabin: null, - PassengerId: 282, - Age: 28, - Fare: 7.8542, - Name: 'Olsson, Mr. Nils Johan Goransson', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '345778', - Parch: 0, - Cabin: null, - PassengerId: 283, - Age: 16, - Fare: 9.5, - Name: 'de Pelsmaeker, Mr. Alfons', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'A/5. 10482', - Parch: 0, - Cabin: null, - PassengerId: 284, - Age: 19, - Fare: 8.05, - Name: 'Dorking, Mr. Edward Arthur', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '113056', - Parch: 0, - Cabin: 'A19', - PassengerId: 285, - Age: null, - Fare: 26, - Name: 'Smith, Mr. Richard William', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349239', - Parch: 0, - Cabin: null, - PassengerId: 286, - Age: 33, - Fare: 8.6625, - Name: 'Stankovic, Mr. Ivan', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '345774', - Parch: 0, - Cabin: null, - PassengerId: 287, - Age: 30, - Fare: 9.5, - Name: 'de Mulder, Mr. Theodore', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349206', - Parch: 0, - Cabin: null, - PassengerId: 288, - Age: 22, - Fare: 7.8958, - Name: 'Naidenoff, Mr. Penko', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '237798', - Parch: 0, - Cabin: null, - PassengerId: 289, - Age: 42, - Fare: 13, - Name: 'Hosono, Mr. Masabumi', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '370373', - Parch: 0, - Cabin: null, - PassengerId: 290, - Age: 22, - Fare: 7.75, - Name: 'Connolly, Miss. Kate', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '19877', - Parch: 0, - Cabin: null, - PassengerId: 291, - Age: 26, - Fare: 78.85, - Name: 'Barber, Miss. Ellen "Nellie"', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '11967', - Parch: 0, - Cabin: 'B49', - PassengerId: 292, - Age: 19, - Fare: 91.0792, - Name: 'Bishop, Mrs. Dickinson H (Helen Walton)', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'SC/Paris 2163', - Parch: 0, - Cabin: 'D', - PassengerId: 293, - Age: 36, - Fare: 12.875, - Name: 'Levy, Mr. Rene Jacques', - Survived: false, - Pclass: 2, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349236', - Parch: 0, - Cabin: null, - PassengerId: 294, - Age: 24, - Fare: 8.85, - Name: 'Haas, Miss. Aloisia', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '349233', - Parch: 0, - Cabin: null, - PassengerId: 295, - Age: 24, - Fare: 7.8958, - Name: 'Mineff, Mr. Ivan', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'PC 17612', - Parch: 0, - Cabin: null, - PassengerId: 296, - Age: null, - Fare: 27.7208, - Name: 'Lewy, Mr. Ervin G', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '2693', - Parch: 0, - Cabin: null, - PassengerId: 297, - Age: 23.5, - Fare: 7.2292, - Name: 'Hanna, Mr. Mansour', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '113781', - Parch: 2, - Cabin: 'C22 C26', - PassengerId: 298, - Age: 2, - Fare: 151.55, - Name: 'Allison, Miss. Helen Loraine', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '19988', - Parch: 0, - Cabin: 'C106', - PassengerId: 299, - Age: null, - Fare: 30.5, - Name: 'Saalfeld, Mr. Adolphe', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'PC 17558', - Parch: 1, - Cabin: 'B58 B60', - PassengerId: 300, - Age: 50, - Fare: 247.5208, - Name: 'Baxter, Mrs. James (Helene DeLaudeniere Chaput)', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '9234', - Parch: 0, - Cabin: null, - PassengerId: 301, - Age: null, - Fare: 7.75, - Name: 'Kelly, Miss. Anna Katherine "Annie Kate"', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 2, - Ticket: '367226', - Parch: 0, - Cabin: null, - PassengerId: 302, - Age: null, - Fare: 23.25, - Name: 'McCoy, Mr. Bernard', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'LINE', - Parch: 0, - Cabin: null, - PassengerId: 303, - Age: 19, - Fare: 0, - Name: 'Johnson, Mr. William Cahoone Jr', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '226593', - Parch: 0, - Cabin: 'E101', - PassengerId: 304, - Age: null, - Fare: 12.35, - Name: 'Keane, Miss. Nora A', - Survived: true, - Pclass: 2, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'A/5 2466', - Parch: 0, - Cabin: null, - PassengerId: 305, - Age: null, - Fare: 8.05, - Name: 'Williams, Mr. Howard Hugh "Harry"', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '113781', - Parch: 2, - Cabin: 'C22 C26', - PassengerId: 306, - Age: 0.92, - Fare: 151.55, - Name: 'Allison, Master. Hudson Trevor', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '17421', - Parch: 0, - Cabin: null, - PassengerId: 307, - Age: null, - Fare: 110.8833, - Name: 'Fleming, Miss. Margaret', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: 'PC 17758', - Parch: 0, - Cabin: 'C65', - PassengerId: 308, - Age: 17, - Fare: 108.9, - Name: 'Penasco y Castellana, Mrs. Victor de Satode (Maria Josefa Perez de Soto y Vallejo)', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: 'P/PP 3381', - Parch: 0, - Cabin: null, - PassengerId: 309, - Age: 30, - Fare: 24, - Name: 'Abelson, Mr. Samuel', - Survived: false, - Pclass: 2, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'PC 17485', - Parch: 0, - Cabin: 'E36', - PassengerId: 310, - Age: 30, - Fare: 56.9292, - Name: 'Francatelli, Miss. Laura Mabel', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '11767', - Parch: 0, - Cabin: 'C54', - PassengerId: 311, - Age: 24, - Fare: 83.1583, - Name: 'Hays, Miss. Margaret Bechstein', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 2, - Ticket: 'PC 17608', - Parch: 2, - Cabin: 'B57 B59 B63 B66', - PassengerId: 312, - Age: 18, - Fare: 262.375, - Name: 'Ryerson, Miss. Emily Borie', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '250651', - Parch: 1, - Cabin: null, - PassengerId: 313, - Age: 26, - Fare: 26, - Name: 'Lahtinen, Mrs. William (Anna Sylfven)', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '349243', - Parch: 0, - Cabin: null, - PassengerId: 314, - Age: 28, - Fare: 7.8958, - Name: 'Hendekovic, Mr. Ignjac', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'F.C.C. 13529', - Parch: 1, - Cabin: null, - PassengerId: 315, - Age: 43, - Fare: 26.25, - Name: 'Hart, Mr. Benjamin', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '347470', - Parch: 0, - Cabin: null, - PassengerId: 316, - Age: 26, - Fare: 7.8542, - Name: 'Nilsson, Miss. Helmina Josefina', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '244367', - Parch: 0, - Cabin: null, - PassengerId: 317, - Age: 24, - Fare: 26, - Name: 'Kantor, Mrs. Sinai (Miriam Sternin)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '29011', - Parch: 0, - Cabin: null, - PassengerId: 318, - Age: 54, - Fare: 14, - Name: 'Moraweck, Dr. Ernest', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '36928', - Parch: 2, - Cabin: 'C7', - PassengerId: 319, - Age: 31, - Fare: 164.8667, - Name: 'Wick, Miss. Mary Natalie', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '16966', - Parch: 1, - Cabin: 'E34', - PassengerId: 320, - Age: 40, - Fare: 134.5, - Name: 'Spedden, Mrs. Frederic Oakley (Margaretta Corning Stone)', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'A/5 21172', - Parch: 0, - Cabin: null, - PassengerId: 321, - Age: 22, - Fare: 7.25, - Name: 'Dennis, Mr. Samuel', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349219', - Parch: 0, - Cabin: null, - PassengerId: 322, - Age: 27, - Fare: 7.8958, - Name: 'Danoff, Mr. Yoto', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '234818', - Parch: 0, - Cabin: null, - PassengerId: 323, - Age: 30, - Fare: 12.35, - Name: 'Slayter, Miss. Hilda Mary', - Survived: true, - Pclass: 2, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '248738', - Parch: 1, - Cabin: null, - PassengerId: 324, - Age: 22, - Fare: 29, - Name: 'Caldwell, Mrs. Albert Francis (Sylvia Mae Harbaugh)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 8, - Ticket: 'CA. 2343', - Parch: 2, - Cabin: null, - PassengerId: 325, - Age: null, - Fare: 69.55, - Name: 'Sage, Mr. George John Jr', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'PC 17760', - Parch: 0, - Cabin: 'C32', - PassengerId: 326, - Age: 36, - Fare: 135.6333, - Name: 'Young, Miss. Marie Grice', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '345364', - Parch: 0, - Cabin: null, - PassengerId: 327, - Age: 61, - Fare: 6.2375, - Name: 'Nysveen, Mr. Johan Hansen', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '28551', - Parch: 0, - Cabin: 'D', - PassengerId: 328, - Age: 36, - Fare: 13, - Name: 'Ball, Mrs. (Ada E Hall)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '363291', - Parch: 1, - Cabin: null, - PassengerId: 329, - Age: 31, - Fare: 20.525, - Name: 'Goldsmith, Mrs. Frank John (Emily Alice Brown)', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '111361', - Parch: 1, - Cabin: 'B18', - PassengerId: 330, - Age: 16, - Fare: 57.9792, - Name: 'Hippach, Miss. Jean Gertrude', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 2, - Ticket: '367226', - Parch: 0, - Cabin: null, - PassengerId: 331, - Age: null, - Fare: 23.25, - Name: 'McCoy, Miss. Agnes', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '113043', - Parch: 0, - Cabin: 'C124', - PassengerId: 332, - Age: 45.5, - Fare: 28.5, - Name: 'Partner, Mr. Austen', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'PC 17582', - Parch: 1, - Cabin: 'C91', - PassengerId: 333, - Age: 38, - Fare: 153.4625, - Name: 'Graham, Mr. George Edward', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 2, - Ticket: '345764', - Parch: 0, - Cabin: null, - PassengerId: 334, - Age: 16, - Fare: 18, - Name: 'Vander Planke, Mr. Leo Edmondus', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'PC 17611', - Parch: 0, - Cabin: null, - PassengerId: 335, - Age: null, - Fare: 133.65, - Name: 'Frauenthal, Mrs. Henry William (Clara Heinsheimer)', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '349225', - Parch: 0, - Cabin: null, - PassengerId: 336, - Age: null, - Fare: 7.8958, - Name: 'Denkoff, Mr. Mitto', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '113776', - Parch: 0, - Cabin: 'C2', - PassengerId: 337, - Age: 29, - Fare: 66.6, - Name: 'Pears, Mr. Thomas Clinton', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '16966', - Parch: 0, - Cabin: 'E40', - PassengerId: 338, - Age: 41, - Fare: 134.5, - Name: 'Burns, Miss. Elizabeth Margaret', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '7598', - Parch: 0, - Cabin: null, - PassengerId: 339, - Age: 45, - Fare: 8.05, - Name: 'Dahl, Mr. Karl Edwart', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '113784', - Parch: 0, - Cabin: 'T', - PassengerId: 340, - Age: 45, - Fare: 35.5, - Name: 'Blackwell, Mr. Stephen Weart', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '230080', - Parch: 1, - Cabin: 'F2', - PassengerId: 341, - Age: 2, - Fare: 26, - Name: 'Navratil, Master. Edmond Roger', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 3, - Ticket: '19950', - Parch: 2, - Cabin: 'C23 C25 C27', - PassengerId: 342, - Age: 24, - Fare: 263, - Name: 'Fortune, Miss. Alice Elizabeth', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '248740', - Parch: 0, - Cabin: null, - PassengerId: 343, - Age: 28, - Fare: 13, - Name: 'Collander, Mr. Erik Gustaf', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '244361', - Parch: 0, - Cabin: null, - PassengerId: 344, - Age: 25, - Fare: 13, - Name: 'Sedgwick, Mr. Charles Frederick Waddington', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '229236', - Parch: 0, - Cabin: null, - PassengerId: 345, - Age: 36, - Fare: 13, - Name: 'Fox, Mr. Stanley Hubert', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '248733', - Parch: 0, - Cabin: 'F33', - PassengerId: 346, - Age: 24, - Fare: 13, - Name: 'Brown, Miss. Amelia "Mildred"', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '31418', - Parch: 0, - Cabin: null, - PassengerId: 347, - Age: 40, - Fare: 13, - Name: 'Smith, Miss. Marion Elsie', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '386525', - Parch: 0, - Cabin: null, - PassengerId: 348, - Age: null, - Fare: 16.1, - Name: 'Davison, Mrs. Thomas Henry (Mary E Finck)', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: 'C.A. 37671', - Parch: 1, - Cabin: null, - PassengerId: 349, - Age: 3, - Fare: 15.9, - Name: 'Coutts, Master. William Loch "William"', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '315088', - Parch: 0, - Cabin: null, - PassengerId: 350, - Age: 42, - Fare: 8.6625, - Name: 'Dimic, Mr. Jovan', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '7267', - Parch: 0, - Cabin: null, - PassengerId: 351, - Age: 23, - Fare: 9.225, - Name: 'Odahl, Mr. Nils Martin', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '113510', - Parch: 0, - Cabin: 'C128', - PassengerId: 352, - Age: null, - Fare: 35, - Name: 'Williams-Lambert, Mr. Fletcher Fellows', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '2695', - Parch: 1, - Cabin: null, - PassengerId: 353, - Age: 15, - Fare: 7.2292, - Name: 'Elias, Mr. Tannous', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '349237', - Parch: 0, - Cabin: null, - PassengerId: 354, - Age: 25, - Fare: 17.8, - Name: 'Arnold-Franchi, Mr. Josef', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '2647', - Parch: 0, - Cabin: null, - PassengerId: 355, - Age: null, - Fare: 7.225, - Name: 'Yousif, Mr. Wazli', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '345783', - Parch: 0, - Cabin: null, - PassengerId: 356, - Age: 28, - Fare: 9.5, - Name: 'Vanden Steen, Mr. Leo Peter', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '113505', - Parch: 1, - Cabin: 'E33', - PassengerId: 357, - Age: 22, - Fare: 55, - Name: 'Bowerman, Miss. Elsie Edith', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '237671', - Parch: 0, - Cabin: null, - PassengerId: 358, - Age: 38, - Fare: 13, - Name: 'Funk, Miss. Annie Clemmer', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '330931', - Parch: 0, - Cabin: null, - PassengerId: 359, - Age: null, - Fare: 7.8792, - Name: 'McGovern, Miss. Mary', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '330980', - Parch: 0, - Cabin: null, - PassengerId: 360, - Age: null, - Fare: 7.8792, - Name: 'Mockler, Miss. Helen Mary "Ellie"', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '347088', - Parch: 4, - Cabin: null, - PassengerId: 361, - Age: 40, - Fare: 27.9, - Name: 'Skoog, Mr. Wilhelm', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'SC/PARIS 2167', - Parch: 0, - Cabin: null, - PassengerId: 362, - Age: 29, - Fare: 27.7208, - Name: 'del Carlo, Mr. Sebastiano', - Survived: false, - Pclass: 2, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '2691', - Parch: 1, - Cabin: null, - PassengerId: 363, - Age: 45, - Fare: 14.4542, - Name: 'Barbara, Mrs. (Catherine David)', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'SOTON/O.Q. 3101310', - Parch: 0, - Cabin: null, - PassengerId: 364, - Age: 35, - Fare: 7.05, - Name: 'Asim, Mr. Adola', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '370365', - Parch: 0, - Cabin: null, - PassengerId: 365, - Age: null, - Fare: 15.5, - Name: "O'Brien, Mr. Thomas", - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'C 7076', - Parch: 0, - Cabin: null, - PassengerId: 366, - Age: 30, - Fare: 7.25, - Name: 'Adahl, Mr. Mauritz Nils Martin', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '110813', - Parch: 0, - Cabin: 'D37', - PassengerId: 367, - Age: 60, - Fare: 75.25, - Name: 'Warren, Mrs. Frank Manley (Anna Sophia Atkinson)', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '2626', - Parch: 0, - Cabin: null, - PassengerId: 368, - Age: null, - Fare: 7.2292, - Name: 'Moussa, Mrs. (Mantoura Boulos)', - Survived: true, - Pclass: 3, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '14313', - Parch: 0, - Cabin: null, - PassengerId: 369, - Age: null, - Fare: 7.75, - Name: 'Jermyn, Miss. Annie', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'PC 17477', - Parch: 0, - Cabin: 'B35', - PassengerId: 370, - Age: 24, - Fare: 69.3, - Name: 'Aubart, Mme. Leontine Pauline', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '11765', - Parch: 0, - Cabin: 'E50', - PassengerId: 371, - Age: 25, - Fare: 55.4417, - Name: 'Harder, Mr. George Achilles', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '3101267', - Parch: 0, - Cabin: null, - PassengerId: 372, - Age: 18, - Fare: 6.4958, - Name: 'Wiklund, Mr. Jakob Alfred', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '323951', - Parch: 0, - Cabin: null, - PassengerId: 373, - Age: 19, - Fare: 8.05, - Name: 'Beavan, Mr. William Thomas', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'PC 17760', - Parch: 0, - Cabin: null, - PassengerId: 374, - Age: 22, - Fare: 135.6333, - Name: 'Ringhini, Mr. Sante', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 3, - Ticket: '349909', - Parch: 1, - Cabin: null, - PassengerId: 375, - Age: 3, - Fare: 21.075, - Name: 'Palsson, Miss. Stina Viola', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: 'PC 17604', - Parch: 0, - Cabin: null, - PassengerId: 376, - Age: null, - Fare: 82.1708, - Name: 'Meyer, Mrs. Edgar Joseph (Leila Saks)', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'C 7077', - Parch: 0, - Cabin: null, - PassengerId: 377, - Age: 22, - Fare: 7.25, - Name: 'Landergren, Miss. Aurora Adelia', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '113503', - Parch: 2, - Cabin: 'C82', - PassengerId: 378, - Age: 27, - Fare: 211.5, - Name: 'Widener, Mr. Harry Elkins', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '2648', - Parch: 0, - Cabin: null, - PassengerId: 379, - Age: 20, - Fare: 4.0125, - Name: 'Betros, Mr. Tannous', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '347069', - Parch: 0, - Cabin: null, - PassengerId: 380, - Age: 19, - Fare: 7.775, - Name: 'Gustafsson, Mr. Karl Gideon', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'PC 17757', - Parch: 0, - Cabin: null, - PassengerId: 381, - Age: 42, - Fare: 227.525, - Name: 'Bidois, Miss. Rosalie', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '2653', - Parch: 2, - Cabin: null, - PassengerId: 382, - Age: 1, - Fare: 15.7417, - Name: 'Nakid, Miss. Maria ("Mary")', - Survived: true, - Pclass: 3, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'STON/O 2. 3101293', - Parch: 0, - Cabin: null, - PassengerId: 383, - Age: 32, - Fare: 7.925, - Name: 'Tikkanen, Mr. Juho', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '113789', - Parch: 0, - Cabin: null, - PassengerId: 384, - Age: 35, - Fare: 52, - Name: 'Holverson, Mrs. Alexander Oskar (Mary Aline Towner)', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '349227', - Parch: 0, - Cabin: null, - PassengerId: 385, - Age: null, - Fare: 7.8958, - Name: 'Plotcharsky, Mr. Vasil', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'S.O.C. 14879', - Parch: 0, - Cabin: null, - PassengerId: 386, - Age: 18, - Fare: 73.5, - Name: 'Davies, Mr. Charles Henry', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 5, - Ticket: 'CA 2144', - Parch: 2, - Cabin: null, - PassengerId: 387, - Age: 1, - Fare: 46.9, - Name: 'Goodwin, Master. Sidney Leonard', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '27849', - Parch: 0, - Cabin: null, - PassengerId: 388, - Age: 36, - Fare: 13, - Name: 'Buss, Miss. Kate', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '367655', - Parch: 0, - Cabin: null, - PassengerId: 389, - Age: null, - Fare: 7.7292, - Name: 'Sadlier, Mr. Matthew', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'SC 1748', - Parch: 0, - Cabin: null, - PassengerId: 390, - Age: 17, - Fare: 12, - Name: 'Lehmann, Miss. Bertha', - Survived: true, - Pclass: 2, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '113760', - Parch: 2, - Cabin: 'B96 B98', - PassengerId: 391, - Age: 36, - Fare: 120, - Name: 'Carter, Mr. William Ernest', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '350034', - Parch: 0, - Cabin: null, - PassengerId: 392, - Age: 21, - Fare: 7.7958, - Name: 'Jansson, Mr. Carl Olof', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 2, - Ticket: '3101277', - Parch: 0, - Cabin: null, - PassengerId: 393, - Age: 28, - Fare: 7.925, - Name: 'Gustafsson, Mr. Johan Birger', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '35273', - Parch: 0, - Cabin: 'D36', - PassengerId: 394, - Age: 23, - Fare: 113.275, - Name: 'Newell, Miss. Marjorie', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'PP 9549', - Parch: 2, - Cabin: 'G6', - PassengerId: 395, - Age: 24, - Fare: 16.7, - Name: 'Sandstrom, Mrs. Hjalmar (Agnes Charlotta Bengtsson)', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '350052', - Parch: 0, - Cabin: null, - PassengerId: 396, - Age: 22, - Fare: 7.7958, - Name: 'Johansson, Mr. Erik', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '350407', - Parch: 0, - Cabin: null, - PassengerId: 397, - Age: 31, - Fare: 7.8542, - Name: 'Olsson, Miss. Elina', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '28403', - Parch: 0, - Cabin: null, - PassengerId: 398, - Age: 46, - Fare: 26, - Name: 'McKane, Mr. Peter David', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '244278', - Parch: 0, - Cabin: null, - PassengerId: 399, - Age: 23, - Fare: 10.5, - Name: 'Pain, Dr. Alfred', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '240929', - Parch: 0, - Cabin: null, - PassengerId: 400, - Age: 28, - Fare: 12.65, - Name: 'Trout, Mrs. William H (Jessie L)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'STON/O 2. 3101289', - Parch: 0, - Cabin: null, - PassengerId: 401, - Age: 39, - Fare: 7.925, - Name: 'Niskanen, Mr. Juha', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '341826', - Parch: 0, - Cabin: null, - PassengerId: 402, - Age: 26, - Fare: 8.05, - Name: 'Adams, Mr. John', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '4137', - Parch: 0, - Cabin: null, - PassengerId: 403, - Age: 21, - Fare: 9.825, - Name: 'Jussila, Miss. Mari Aina', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: 'STON/O2. 3101279', - Parch: 0, - Cabin: null, - PassengerId: 404, - Age: 28, - Fare: 15.85, - Name: 'Hakkarainen, Mr. Pekka Pietari', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '315096', - Parch: 0, - Cabin: null, - PassengerId: 405, - Age: 20, - Fare: 8.6625, - Name: 'Oreskovic, Miss. Marija', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '28664', - Parch: 0, - Cabin: null, - PassengerId: 406, - Age: 34, - Fare: 21, - Name: 'Gale, Mr. Shadrach', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '347064', - Parch: 0, - Cabin: null, - PassengerId: 407, - Age: 51, - Fare: 7.75, - Name: 'Widegren, Mr. Carl/Charles Peter', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '29106', - Parch: 1, - Cabin: null, - PassengerId: 408, - Age: 3, - Fare: 18.75, - Name: 'Richards, Master. William Rowe', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '312992', - Parch: 0, - Cabin: null, - PassengerId: 409, - Age: 21, - Fare: 7.775, - Name: 'Birkeland, Mr. Hans Martin Monsen', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 3, - Ticket: '4133', - Parch: 1, - Cabin: null, - PassengerId: 410, - Age: null, - Fare: 25.4667, - Name: 'Lefebre, Miss. Ida', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '349222', - Parch: 0, - Cabin: null, - PassengerId: 411, - Age: null, - Fare: 7.8958, - Name: 'Sdycoff, Mr. Todor', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '394140', - Parch: 0, - Cabin: null, - PassengerId: 412, - Age: null, - Fare: 6.8583, - Name: 'Hart, Mr. Henry', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '19928', - Parch: 0, - Cabin: 'C78', - PassengerId: 413, - Age: 33, - Fare: 90, - Name: 'Minahan, Miss. Daisy E', - Survived: true, - Pclass: 1, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '239853', - Parch: 0, - Cabin: null, - PassengerId: 414, - Age: null, - Fare: 0, - Name: 'Cunningham, Mr. Alfred Fleming', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'STON/O 2. 3101269', - Parch: 0, - Cabin: null, - PassengerId: 415, - Age: 44, - Fare: 7.925, - Name: 'Sundman, Mr. Johan Julian', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '343095', - Parch: 0, - Cabin: null, - PassengerId: 416, - Age: null, - Fare: 8.05, - Name: 'Meek, Mrs. Thomas (Annie Louise Rowley)', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '28220', - Parch: 1, - Cabin: null, - PassengerId: 417, - Age: 34, - Fare: 32.5, - Name: 'Drew, Mrs. James Vivian (Lulu Thorne Christian)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '250652', - Parch: 2, - Cabin: null, - PassengerId: 418, - Age: 18, - Fare: 13, - Name: 'Silven, Miss. Lyyli Karoliina', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '28228', - Parch: 0, - Cabin: null, - PassengerId: 419, - Age: 30, - Fare: 13, - Name: 'Matthews, Mr. William John', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '345773', - Parch: 2, - Cabin: null, - PassengerId: 420, - Age: 10, - Fare: 24.15, - Name: 'Van Impe, Miss. Catharina', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '349254', - Parch: 0, - Cabin: null, - PassengerId: 421, - Age: null, - Fare: 7.8958, - Name: 'Gheorgheff, Mr. Stanio', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'A/5. 13032', - Parch: 0, - Cabin: null, - PassengerId: 422, - Age: 21, - Fare: 7.7333, - Name: 'Charters, Mr. David', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '315082', - Parch: 0, - Cabin: null, - PassengerId: 423, - Age: 29, - Fare: 7.875, - Name: 'Zimmerman, Mr. Leo', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '347080', - Parch: 1, - Cabin: null, - PassengerId: 424, - Age: 28, - Fare: 14.4, - Name: 'Danbom, Mrs. Ernst Gilbert (Anna Sigrid Maria Brogren)', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '370129', - Parch: 1, - Cabin: null, - PassengerId: 425, - Age: 18, - Fare: 20.2125, - Name: 'Rosblom, Mr. Viktor Richard', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'A/4. 34244', - Parch: 0, - Cabin: null, - PassengerId: 426, - Age: null, - Fare: 7.25, - Name: 'Wiseman, Mr. Phillippe', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '2003', - Parch: 0, - Cabin: null, - PassengerId: 427, - Age: 28, - Fare: 26, - Name: 'Clarke, Mrs. Charles V (Ada Maria Winfield)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '250655', - Parch: 0, - Cabin: null, - PassengerId: 428, - Age: 19, - Fare: 26, - Name: 'Phillips, Miss. Kate Florence ("Mrs Kate Louise Phillips Marshall")', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '364851', - Parch: 0, - Cabin: null, - PassengerId: 429, - Age: null, - Fare: 7.75, - Name: 'Flynn, Mr. James', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'SOTON/O.Q. 392078', - Parch: 0, - Cabin: 'E10', - PassengerId: 430, - Age: 32, - Fare: 8.05, - Name: 'Pickard, Mr. Berk (Berk Trembisky)', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '110564', - Parch: 0, - Cabin: 'C52', - PassengerId: 431, - Age: 28, - Fare: 26.55, - Name: 'Bjornstrom-Steffansson, Mr. Mauritz Hakan', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '376564', - Parch: 0, - Cabin: null, - PassengerId: 432, - Age: null, - Fare: 16.1, - Name: 'Thorneycroft, Mrs. Percival (Florence Kate White)', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: 'SC/AH 3085', - Parch: 0, - Cabin: null, - PassengerId: 433, - Age: 42, - Fare: 26, - Name: 'Louch, Mrs. Charles Alexander (Alice Adelaide Slow)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'STON/O 2. 3101274', - Parch: 0, - Cabin: null, - PassengerId: 434, - Age: 17, - Fare: 7.125, - Name: 'Kallio, Mr. Nikolai Erland', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '13507', - Parch: 0, - Cabin: 'E44', - PassengerId: 435, - Age: 50, - Fare: 55.9, - Name: 'Silvey, Mr. William Baird', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '113760', - Parch: 2, - Cabin: 'B96 B98', - PassengerId: 436, - Age: 14, - Fare: 120, - Name: 'Carter, Miss. Lucile Polk', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 2, - Ticket: 'W./C. 6608', - Parch: 2, - Cabin: null, - PassengerId: 437, - Age: 21, - Fare: 34.375, - Name: 'Ford, Miss. Doolina Margaret "Daisy"', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 2, - Ticket: '29106', - Parch: 3, - Cabin: null, - PassengerId: 438, - Age: 24, - Fare: 18.75, - Name: 'Richards, Mrs. Sidney (Emily Hocking)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '19950', - Parch: 4, - Cabin: 'C23 C25 C27', - PassengerId: 439, - Age: 64, - Fare: 263, - Name: 'Fortune, Mr. Mark', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'C.A. 18723', - Parch: 0, - Cabin: null, - PassengerId: 440, - Age: 31, - Fare: 10.5, - Name: 'Kvillner, Mr. Johan Henrik Johannesson', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'F.C.C. 13529', - Parch: 1, - Cabin: null, - PassengerId: 441, - Age: 45, - Fare: 26.25, - Name: 'Hart, Mrs. Benjamin (Esther Ada Bloomfield)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '345769', - Parch: 0, - Cabin: null, - PassengerId: 442, - Age: 20, - Fare: 9.5, - Name: 'Hampe, Mr. Leon', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '347076', - Parch: 0, - Cabin: null, - PassengerId: 443, - Age: 25, - Fare: 7.775, - Name: 'Petterson, Mr. Johan Emil', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '230434', - Parch: 0, - Cabin: null, - PassengerId: 444, - Age: 28, - Fare: 13, - Name: 'Reynaldo, Ms. Encarnacion', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '65306', - Parch: 0, - Cabin: null, - PassengerId: 445, - Age: null, - Fare: 8.1125, - Name: 'Johannesen-Bratthammer, Mr. Bernt', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '33638', - Parch: 2, - Cabin: 'A34', - PassengerId: 446, - Age: 4, - Fare: 81.8583, - Name: 'Dodge, Master. Washington', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '250644', - Parch: 1, - Cabin: null, - PassengerId: 447, - Age: 13, - Fare: 19.5, - Name: 'Mellinger, Miss. Madeleine Violet', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '113794', - Parch: 0, - Cabin: null, - PassengerId: 448, - Age: 34, - Fare: 26.55, - Name: 'Seward, Mr. Frederic Kimber', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 2, - Ticket: '2666', - Parch: 1, - Cabin: null, - PassengerId: 449, - Age: 5, - Fare: 19.2583, - Name: 'Baclini, Miss. Marie Catherine', - Survived: true, - Pclass: 3, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '113786', - Parch: 0, - Cabin: 'C104', - PassengerId: 450, - Age: 52, - Fare: 30.5, - Name: 'Peuchen, Major. Arthur Godfrey', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'C.A. 34651', - Parch: 2, - Cabin: null, - PassengerId: 451, - Age: 36, - Fare: 27.75, - Name: 'West, Mr. Edwy Arthur', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '65303', - Parch: 0, - Cabin: null, - PassengerId: 452, - Age: null, - Fare: 19.9667, - Name: 'Hagland, Mr. Ingvald Olai Olsen', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '113051', - Parch: 0, - Cabin: 'C111', - PassengerId: 453, - Age: 30, - Fare: 27.75, - Name: 'Foreman, Mr. Benjamin Laventall', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '17453', - Parch: 0, - Cabin: 'C92', - PassengerId: 454, - Age: 49, - Fare: 89.1042, - Name: 'Goldenberg, Mr. Samuel L', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'A/5 2817', - Parch: 0, - Cabin: null, - PassengerId: 455, - Age: null, - Fare: 8.05, - Name: 'Peduzzi, Mr. Joseph', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349240', - Parch: 0, - Cabin: null, - PassengerId: 456, - Age: 29, - Fare: 7.8958, - Name: 'Jalsevac, Mr. Ivan', - Survived: true, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '13509', - Parch: 0, - Cabin: 'E38', - PassengerId: 457, - Age: 65, - Fare: 26.55, - Name: 'Millet, Mr. Francis Davis', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '17464', - Parch: 0, - Cabin: 'D21', - PassengerId: 458, - Age: null, - Fare: 51.8625, - Name: 'Kenyon, Mrs. Frederick R (Marion)', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'F.C.C. 13531', - Parch: 0, - Cabin: null, - PassengerId: 459, - Age: 50, - Fare: 10.5, - Name: 'Toomey, Miss. Ellen', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '371060', - Parch: 0, - Cabin: null, - PassengerId: 460, - Age: null, - Fare: 7.75, - Name: "O'Connor, Mr. Maurice", - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '19952', - Parch: 0, - Cabin: 'E12', - PassengerId: 461, - Age: 48, - Fare: 26.55, - Name: 'Anderson, Mr. Harry', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '364506', - Parch: 0, - Cabin: null, - PassengerId: 462, - Age: 34, - Fare: 8.05, - Name: 'Morley, Mr. William', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '111320', - Parch: 0, - Cabin: 'E63', - PassengerId: 463, - Age: 47, - Fare: 38.5, - Name: 'Gee, Mr. Arthur H', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '234360', - Parch: 0, - Cabin: null, - PassengerId: 464, - Age: 48, - Fare: 13, - Name: 'Milling, Mr. Jacob Christian', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'A/S 2816', - Parch: 0, - Cabin: null, - PassengerId: 465, - Age: null, - Fare: 8.05, - Name: 'Maisner, Mr. Simon', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'SOTON/O.Q. 3101306', - Parch: 0, - Cabin: null, - PassengerId: 466, - Age: 38, - Fare: 7.05, - Name: 'Goncalves, Mr. Manuel Estanslas', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '239853', - Parch: 0, - Cabin: null, - PassengerId: 467, - Age: null, - Fare: 0, - Name: 'Campbell, Mr. William', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '113792', - Parch: 0, - Cabin: null, - PassengerId: 468, - Age: 56, - Fare: 26.55, - Name: 'Smart, Mr. John Montgomery', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '36209', - Parch: 0, - Cabin: null, - PassengerId: 469, - Age: null, - Fare: 7.725, - Name: 'Scanlan, Mr. James', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 2, - Ticket: '2666', - Parch: 1, - Cabin: null, - PassengerId: 470, - Age: 0.75, - Fare: 19.2583, - Name: 'Baclini, Miss. Helene Barbara', - Survived: true, - Pclass: 3, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '323592', - Parch: 0, - Cabin: null, - PassengerId: 471, - Age: null, - Fare: 7.25, - Name: 'Keefe, Mr. Arthur', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '315089', - Parch: 0, - Cabin: null, - PassengerId: 472, - Age: 38, - Fare: 8.6625, - Name: 'Cacic, Mr. Luka', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'C.A. 34651', - Parch: 2, - Cabin: null, - PassengerId: 473, - Age: 33, - Fare: 27.75, - Name: 'West, Mrs. Edwy Arthur (Ada Mary Worth)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'SC/AH Basle 541', - Parch: 0, - Cabin: 'D', - PassengerId: 474, - Age: 23, - Fare: 13.7917, - Name: 'Jerwan, Mrs. Amin S (Marie Marthe Thuillard)', - Survived: true, - Pclass: 2, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '7553', - Parch: 0, - Cabin: null, - PassengerId: 475, - Age: 22, - Fare: 9.8375, - Name: 'Strandberg, Miss. Ida Sofia', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '110465', - Parch: 0, - Cabin: 'A14', - PassengerId: 476, - Age: null, - Fare: 52, - Name: 'Clifford, Mr. George Quincy', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '31027', - Parch: 0, - Cabin: null, - PassengerId: 477, - Age: 34, - Fare: 21, - Name: 'Renouf, Mr. Peter Henry', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '3460', - Parch: 0, - Cabin: null, - PassengerId: 478, - Age: 29, - Fare: 7.0458, - Name: 'Braund, Mr. Lewis Richard', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '350060', - Parch: 0, - Cabin: null, - PassengerId: 479, - Age: 22, - Fare: 7.5208, - Name: 'Karlsson, Mr. Nils August', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '3101298', - Parch: 1, - Cabin: null, - PassengerId: 480, - Age: 2, - Fare: 12.2875, - Name: 'Hirvonen, Miss. Hildur E', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 5, - Ticket: 'CA 2144', - Parch: 2, - Cabin: null, - PassengerId: 481, - Age: 9, - Fare: 46.9, - Name: 'Goodwin, Master. Harold Victor', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '239854', - Parch: 0, - Cabin: null, - PassengerId: 482, - Age: null, - Fare: 0, - Name: 'Frost, Mr. Anthony Wood "Archie"', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'A/5 3594', - Parch: 0, - Cabin: null, - PassengerId: 483, - Age: 50, - Fare: 8.05, - Name: 'Rouse, Mr. Richard Henry', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '4134', - Parch: 0, - Cabin: null, - PassengerId: 484, - Age: 63, - Fare: 9.5875, - Name: 'Turkula, Mrs. (Hedwig)', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '11967', - Parch: 0, - Cabin: 'B49', - PassengerId: 485, - Age: 25, - Fare: 91.0792, - Name: 'Bishop, Mr. Dickinson H', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 3, - Ticket: '4133', - Parch: 1, - Cabin: null, - PassengerId: 486, - Age: null, - Fare: 25.4667, - Name: 'Lefebre, Miss. Jeannie', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '19943', - Parch: 0, - Cabin: 'C93', - PassengerId: 487, - Age: 35, - Fare: 90, - Name: 'Hoyt, Mrs. Frederick Maxfield (Jane Anne Forby)', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '11771', - Parch: 0, - Cabin: 'B37', - PassengerId: 488, - Age: 58, - Fare: 29.7, - Name: 'Kent, Mr. Edward Austin', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'A.5. 18509', - Parch: 0, - Cabin: null, - PassengerId: 489, - Age: 30, - Fare: 8.05, - Name: 'Somerton, Mr. Francis William', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'C.A. 37671', - Parch: 1, - Cabin: null, - PassengerId: 490, - Age: 9, - Fare: 15.9, - Name: 'Coutts, Master. Eden Leslie "Neville"', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '65304', - Parch: 0, - Cabin: null, - PassengerId: 491, - Age: null, - Fare: 19.9667, - Name: 'Hagland, Mr. Konrad Mathias Reiersen', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'SOTON/OQ 3101317', - Parch: 0, - Cabin: null, - PassengerId: 492, - Age: 21, - Fare: 7.25, - Name: 'Windelov, Mr. Einar', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '113787', - Parch: 0, - Cabin: 'C30', - PassengerId: 493, - Age: 55, - Fare: 30.5, - Name: 'Molson, Mr. Harry Markland', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'PC 17609', - Parch: 0, - Cabin: null, - PassengerId: 494, - Age: 71, - Fare: 49.5042, - Name: 'Artagaveytia, Mr. Ramon', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'A/4 45380', - Parch: 0, - Cabin: null, - PassengerId: 495, - Age: 21, - Fare: 8.05, - Name: 'Stanley, Mr. Edward Roland', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '2627', - Parch: 0, - Cabin: null, - PassengerId: 496, - Age: null, - Fare: 14.4583, - Name: 'Yousseff, Mr. Gerious', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '36947', - Parch: 0, - Cabin: 'D20', - PassengerId: 497, - Age: 54, - Fare: 78.2667, - Name: 'Eustis, Miss. Elizabeth Mussey', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'C.A. 6212', - Parch: 0, - Cabin: null, - PassengerId: 498, - Age: null, - Fare: 15.1, - Name: 'Shellard, Mr. Frederick William', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '113781', - Parch: 2, - Cabin: 'C22 C26', - PassengerId: 499, - Age: 25, - Fare: 151.55, - Name: 'Allison, Mrs. Hudson J C (Bessie Waldo Daniels)', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '350035', - Parch: 0, - Cabin: null, - PassengerId: 500, - Age: 24, - Fare: 7.7958, - Name: 'Svensson, Mr. Olof', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '315086', - Parch: 0, - Cabin: null, - PassengerId: 501, - Age: 17, - Fare: 8.6625, - Name: 'Calic, Mr. Petar', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '364846', - Parch: 0, - Cabin: null, - PassengerId: 502, - Age: 21, - Fare: 7.75, - Name: 'Canavan, Miss. Mary', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '330909', - Parch: 0, - Cabin: null, - PassengerId: 503, - Age: null, - Fare: 7.6292, - Name: "O'Sullivan, Miss. Bridget Mary", - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '4135', - Parch: 0, - Cabin: null, - PassengerId: 504, - Age: 37, - Fare: 9.5875, - Name: 'Laitinen, Miss. Kristina Sofia', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '110152', - Parch: 0, - Cabin: 'B79', - PassengerId: 505, - Age: 16, - Fare: 86.5, - Name: 'Maioni, Miss. Roberta', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: 'PC 17758', - Parch: 0, - Cabin: 'C65', - PassengerId: 506, - Age: 18, - Fare: 108.9, - Name: 'Penasco y Castellana, Mr. Victor de Satode', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '26360', - Parch: 2, - Cabin: null, - PassengerId: 507, - Age: 33, - Fare: 26, - Name: 'Quick, Mrs. Frederick Charles (Jane Richards)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '111427', - Parch: 0, - Cabin: null, - PassengerId: 508, - Age: null, - Fare: 26.55, - Name: 'Bradley, Mr. George ("George Arthur Brayton")', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'C 4001', - Parch: 0, - Cabin: null, - PassengerId: 509, - Age: 28, - Fare: 22.525, - Name: 'Olsen, Mr. Henry Margido', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '1601', - Parch: 0, - Cabin: null, - PassengerId: 510, - Age: 26, - Fare: 56.4958, - Name: 'Lang, Mr. Fang', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '382651', - Parch: 0, - Cabin: null, - PassengerId: 511, - Age: 29, - Fare: 7.75, - Name: 'Daly, Mr. Eugene Patrick', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'SOTON/OQ 3101316', - Parch: 0, - Cabin: null, - PassengerId: 512, - Age: null, - Fare: 8.05, - Name: 'Webber, Mr. James', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'PC 17473', - Parch: 0, - Cabin: 'E25', - PassengerId: 513, - Age: 36, - Fare: 26.2875, - Name: 'McGough, Mr. James Robert', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'PC 17603', - Parch: 0, - Cabin: null, - PassengerId: 514, - Age: 54, - Fare: 59.4, - Name: 'Rothschild, Mrs. Martin (Elizabeth L. Barrett)', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '349209', - Parch: 0, - Cabin: null, - PassengerId: 515, - Age: 24, - Fare: 7.4958, - Name: 'Coleff, Mr. Satio', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '36967', - Parch: 0, - Cabin: 'D46', - PassengerId: 516, - Age: 47, - Fare: 34.0208, - Name: 'Walker, Mr. William Anderson', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'C.A. 34260', - Parch: 0, - Cabin: 'F33', - PassengerId: 517, - Age: 34, - Fare: 10.5, - Name: 'Lemore, Mrs. (Amelia Milley)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '371110', - Parch: 0, - Cabin: null, - PassengerId: 518, - Age: null, - Fare: 24.15, - Name: 'Ryan, Mr. Patrick', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '226875', - Parch: 0, - Cabin: null, - PassengerId: 519, - Age: 36, - Fare: 26, - Name: 'Angle, Mrs. William A (Florence "Mary" Agnes Hughes)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '349242', - Parch: 0, - Cabin: null, - PassengerId: 520, - Age: 32, - Fare: 7.8958, - Name: 'Pavlovic, Mr. Stefo', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '12749', - Parch: 0, - Cabin: 'B73', - PassengerId: 521, - Age: 30, - Fare: 93.5, - Name: 'Perreault, Miss. Anne', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '349252', - Parch: 0, - Cabin: null, - PassengerId: 522, - Age: 22, - Fare: 7.8958, - Name: 'Vovk, Mr. Janko', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '2624', - Parch: 0, - Cabin: null, - PassengerId: 523, - Age: null, - Fare: 7.225, - Name: 'Lahoud, Mr. Sarkis', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '111361', - Parch: 1, - Cabin: 'B18', - PassengerId: 524, - Age: 44, - Fare: 57.9792, - Name: 'Hippach, Mrs. Louis Albert (Ida Sophia Fischer)', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '2700', - Parch: 0, - Cabin: null, - PassengerId: 525, - Age: null, - Fare: 7.2292, - Name: 'Kassem, Mr. Fared', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '367232', - Parch: 0, - Cabin: null, - PassengerId: 526, - Age: 40.5, - Fare: 7.75, - Name: 'Farrell, Mr. James', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'W./C. 14258', - Parch: 0, - Cabin: null, - PassengerId: 527, - Age: 50, - Fare: 10.5, - Name: 'Ridsdale, Miss. Lucy', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'PC 17483', - Parch: 0, - Cabin: 'C95', - PassengerId: 528, - Age: null, - Fare: 221.7792, - Name: 'Farthing, Mr. John', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '3101296', - Parch: 0, - Cabin: null, - PassengerId: 529, - Age: 39, - Fare: 7.925, - Name: 'Salonen, Mr. Johan Werner', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 2, - Ticket: '29104', - Parch: 1, - Cabin: null, - PassengerId: 530, - Age: 23, - Fare: 11.5, - Name: 'Hocking, Mr. Richard George', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '26360', - Parch: 1, - Cabin: null, - PassengerId: 531, - Age: 2, - Fare: 26, - Name: 'Quick, Miss. Phyllis May', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '2641', - Parch: 0, - Cabin: null, - PassengerId: 532, - Age: null, - Fare: 7.2292, - Name: 'Toufik, Mr. Nakli', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '2690', - Parch: 1, - Cabin: null, - PassengerId: 533, - Age: 17, - Fare: 7.2292, - Name: 'Elias, Mr. Joseph Jr', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '2668', - Parch: 2, - Cabin: null, - PassengerId: 534, - Age: null, - Fare: 22.3583, - Name: 'Peter, Mrs. Catherine (Catherine Rizk)', - Survived: true, - Pclass: 3, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '315084', - Parch: 0, - Cabin: null, - PassengerId: 535, - Age: 30, - Fare: 8.6625, - Name: 'Cacic, Miss. Marija', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'F.C.C. 13529', - Parch: 2, - Cabin: null, - PassengerId: 536, - Age: 7, - Fare: 26.25, - Name: 'Hart, Miss. Eva Miriam', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '113050', - Parch: 0, - Cabin: 'B38', - PassengerId: 537, - Age: 45, - Fare: 26.55, - Name: 'Butt, Major. Archibald Willingham', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'PC 17761', - Parch: 0, - Cabin: null, - PassengerId: 538, - Age: 30, - Fare: 106.425, - Name: 'LeRoy, Miss. Bertha', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '364498', - Parch: 0, - Cabin: null, - PassengerId: 539, - Age: null, - Fare: 14.5, - Name: 'Risien, Mr. Samuel Beard', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '13568', - Parch: 2, - Cabin: 'B39', - PassengerId: 540, - Age: 22, - Fare: 49.5, - Name: 'Frolicher, Miss. Hedwig Margaritha', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'WE/P 5735', - Parch: 2, - Cabin: 'B22', - PassengerId: 541, - Age: 36, - Fare: 71, - Name: 'Crosby, Miss. Harriet R', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 4, - Ticket: '347082', - Parch: 2, - Cabin: null, - PassengerId: 542, - Age: 9, - Fare: 31.275, - Name: 'Andersson, Miss. Ingeborg Constanzia', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 4, - Ticket: '347082', - Parch: 2, - Cabin: null, - PassengerId: 543, - Age: 11, - Fare: 31.275, - Name: 'Andersson, Miss. Sigrid Elisabeth', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '2908', - Parch: 0, - Cabin: null, - PassengerId: 544, - Age: 32, - Fare: 26, - Name: 'Beane, Mr. Edward', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'PC 17761', - Parch: 0, - Cabin: 'C86', - PassengerId: 545, - Age: 50, - Fare: 106.425, - Name: 'Douglas, Mr. Walter Donald', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '693', - Parch: 0, - Cabin: null, - PassengerId: 546, - Age: 64, - Fare: 26, - Name: 'Nicholson, Mr. Arthur Ernest', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '2908', - Parch: 0, - Cabin: null, - PassengerId: 547, - Age: 19, - Fare: 26, - Name: 'Beane, Mrs. Edward (Ethel Clarke)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'SC/PARIS 2146', - Parch: 0, - Cabin: null, - PassengerId: 548, - Age: null, - Fare: 13.8625, - Name: 'Padro y Manent, Mr. Julian', - Survived: true, - Pclass: 2, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '363291', - Parch: 1, - Cabin: null, - PassengerId: 549, - Age: 33, - Fare: 20.525, - Name: 'Goldsmith, Mr. Frank John', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'C.A. 33112', - Parch: 1, - Cabin: null, - PassengerId: 550, - Age: 8, - Fare: 36.75, - Name: 'Davies, Master. John Morgan Jr', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '17421', - Parch: 2, - Cabin: 'C70', - PassengerId: 551, - Age: 17, - Fare: 110.8833, - Name: 'Thayer, Mr. John Borland Jr', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '244358', - Parch: 0, - Cabin: null, - PassengerId: 552, - Age: 27, - Fare: 26, - Name: 'Sharp, Mr. Percival James R', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '330979', - Parch: 0, - Cabin: null, - PassengerId: 553, - Age: null, - Fare: 7.8292, - Name: "O'Brien, Mr. Timothy", - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '2620', - Parch: 0, - Cabin: null, - PassengerId: 554, - Age: 22, - Fare: 7.225, - Name: 'Leeni, Mr. Fahim ("Philip Zenni")', - Survived: true, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '347085', - Parch: 0, - Cabin: null, - PassengerId: 555, - Age: 22, - Fare: 7.775, - Name: 'Ohman, Miss. Velin', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '113807', - Parch: 0, - Cabin: null, - PassengerId: 556, - Age: 62, - Fare: 26.55, - Name: 'Wright, Mr. George', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '11755', - Parch: 0, - Cabin: 'A16', - PassengerId: 557, - Age: 48, - Fare: 39.6, - Name: 'Duff Gordon, Lady. (Lucille Christiana Sutherland) ("Mrs Morgan")', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'PC 17757', - Parch: 0, - Cabin: null, - PassengerId: 558, - Age: null, - Fare: 227.525, - Name: 'Robbins, Mr. Victor', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '110413', - Parch: 1, - Cabin: 'E67', - PassengerId: 559, - Age: 39, - Fare: 79.65, - Name: 'Taussig, Mrs. Emil (Tillie Mandelbaum)', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '345572', - Parch: 0, - Cabin: null, - PassengerId: 560, - Age: 36, - Fare: 17.4, - Name: 'de Messemaeker, Mrs. Guillaume Joseph (Emma)', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '372622', - Parch: 0, - Cabin: null, - PassengerId: 561, - Age: null, - Fare: 7.75, - Name: 'Morrow, Mr. Thomas Rowan', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349251', - Parch: 0, - Cabin: null, - PassengerId: 562, - Age: 40, - Fare: 7.8958, - Name: 'Sivic, Mr. Husein', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '218629', - Parch: 0, - Cabin: null, - PassengerId: 563, - Age: 28, - Fare: 13.5, - Name: 'Norman, Mr. Robert Douglas', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'SOTON/OQ 392082', - Parch: 0, - Cabin: null, - PassengerId: 564, - Age: null, - Fare: 8.05, - Name: 'Simmons, Mr. John', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'SOTON/O.Q. 392087', - Parch: 0, - Cabin: null, - PassengerId: 565, - Age: null, - Fare: 8.05, - Name: 'Meanwell, Miss. (Marion Ogden)', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 2, - Ticket: 'A/4 48871', - Parch: 0, - Cabin: null, - PassengerId: 566, - Age: 24, - Fare: 24.15, - Name: 'Davies, Mr. Alfred J', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349205', - Parch: 0, - Cabin: null, - PassengerId: 567, - Age: 19, - Fare: 7.8958, - Name: 'Stoytcheff, Mr. Ilia', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349909', - Parch: 4, - Cabin: null, - PassengerId: 568, - Age: 29, - Fare: 21.075, - Name: 'Palsson, Mrs. Nils (Alma Cornelia Berglund)', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '2686', - Parch: 0, - Cabin: null, - PassengerId: 569, - Age: null, - Fare: 7.2292, - Name: 'Doharr, Mr. Tannous', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '350417', - Parch: 0, - Cabin: null, - PassengerId: 570, - Age: 32, - Fare: 7.8542, - Name: 'Jonsson, Mr. Carl', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'S.W./PP 752', - Parch: 0, - Cabin: null, - PassengerId: 571, - Age: 62, - Fare: 10.5, - Name: 'Harris, Mr. George', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 2, - Ticket: '11769', - Parch: 0, - Cabin: 'C101', - PassengerId: 572, - Age: 53, - Fare: 51.4792, - Name: 'Appleton, Mrs. Edward Dale (Charlotte Lamson)', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'PC 17474', - Parch: 0, - Cabin: 'E25', - PassengerId: 573, - Age: 36, - Fare: 26.3875, - Name: 'Flynn, Mr. John Irwin ("Irving")', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '14312', - Parch: 0, - Cabin: null, - PassengerId: 574, - Age: null, - Fare: 7.75, - Name: 'Kelly, Miss. Mary', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'A/4. 20589', - Parch: 0, - Cabin: null, - PassengerId: 575, - Age: 16, - Fare: 8.05, - Name: 'Rush, Mr. Alfred George John', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '358585', - Parch: 0, - Cabin: null, - PassengerId: 576, - Age: 19, - Fare: 14.5, - Name: 'Patchett, Mr. George', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '243880', - Parch: 0, - Cabin: null, - PassengerId: 577, - Age: 34, - Fare: 13, - Name: 'Garside, Miss. Ethel', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '13507', - Parch: 0, - Cabin: 'E44', - PassengerId: 578, - Age: 39, - Fare: 55.9, - Name: 'Silvey, Mrs. William Baird (Alice Munger)', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '2689', - Parch: 0, - Cabin: null, - PassengerId: 579, - Age: null, - Fare: 14.4583, - Name: 'Caram, Mrs. Joseph (Maria Elias)', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'STON/O 2. 3101286', - Parch: 0, - Cabin: null, - PassengerId: 580, - Age: 32, - Fare: 7.925, - Name: 'Jussila, Mr. Eiriik', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '237789', - Parch: 1, - Cabin: null, - PassengerId: 581, - Age: 25, - Fare: 30, - Name: 'Christy, Miss. Julie Rachel', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '17421', - Parch: 1, - Cabin: 'C68', - PassengerId: 582, - Age: 39, - Fare: 110.8833, - Name: 'Thayer, Mrs. John Borland (Marian Longstreth Morris)', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '28403', - Parch: 0, - Cabin: null, - PassengerId: 583, - Age: 54, - Fare: 26, - Name: 'Downton, Mr. William James', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '13049', - Parch: 0, - Cabin: 'A10', - PassengerId: 584, - Age: 36, - Fare: 40.125, - Name: 'Ross, Mr. John Hugo', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '3411', - Parch: 0, - Cabin: null, - PassengerId: 585, - Age: null, - Fare: 8.7125, - Name: 'Paulner, Mr. Uscher', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '110413', - Parch: 2, - Cabin: 'E68', - PassengerId: 586, - Age: 18, - Fare: 79.65, - Name: 'Taussig, Miss. Ruth', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '237565', - Parch: 0, - Cabin: null, - PassengerId: 587, - Age: 47, - Fare: 15, - Name: 'Jarvis, Mr. John Denzil', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '13567', - Parch: 1, - Cabin: 'B41', - PassengerId: 588, - Age: 60, - Fare: 79.2, - Name: 'Frolicher-Stehli, Mr. Maxmillian', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '14973', - Parch: 0, - Cabin: null, - PassengerId: 589, - Age: 22, - Fare: 8.05, - Name: 'Gilinski, Mr. Eliezer', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'A./5. 3235', - Parch: 0, - Cabin: null, - PassengerId: 590, - Age: null, - Fare: 8.05, - Name: 'Murdlin, Mr. Joseph', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'STON/O 2. 3101273', - Parch: 0, - Cabin: null, - PassengerId: 591, - Age: 35, - Fare: 7.125, - Name: 'Rintamaki, Mr. Matti', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '36947', - Parch: 0, - Cabin: 'D20', - PassengerId: 592, - Age: 52, - Fare: 78.2667, - Name: 'Stephenson, Mrs. Walter Bertram (Martha Eustis)', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'A/5 3902', - Parch: 0, - Cabin: null, - PassengerId: 593, - Age: 47, - Fare: 7.25, - Name: 'Elsbury, Mr. William James', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '364848', - Parch: 2, - Cabin: null, - PassengerId: 594, - Age: null, - Fare: 7.75, - Name: 'Bourke, Miss. Mary', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: 'SC/AH 29037', - Parch: 0, - Cabin: null, - PassengerId: 595, - Age: 37, - Fare: 26, - Name: 'Chapman, Mr. John Henry', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '345773', - Parch: 1, - Cabin: null, - PassengerId: 596, - Age: 36, - Fare: 24.15, - Name: 'Van Impe, Mr. Jean Baptiste', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '248727', - Parch: 0, - Cabin: null, - PassengerId: 597, - Age: null, - Fare: 33, - Name: 'Leitch, Miss. Jessie Wills', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'LINE', - Parch: 0, - Cabin: null, - PassengerId: 598, - Age: 49, - Fare: 0, - Name: 'Johnson, Mr. Alfred', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '2664', - Parch: 0, - Cabin: null, - PassengerId: 599, - Age: null, - Fare: 7.225, - Name: 'Boulos, Mr. Hanna', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'PC 17485', - Parch: 0, - Cabin: 'A20', - PassengerId: 600, - Age: 49, - Fare: 56.9292, - Name: 'Duff Gordon, Sir. Cosmo Edmund ("Mr Morgan")', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 2, - Ticket: '243847', - Parch: 1, - Cabin: null, - PassengerId: 601, - Age: 24, - Fare: 27, - Name: 'Jacobsohn, Mrs. Sidney Samuel (Amy Frances Christy)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '349214', - Parch: 0, - Cabin: null, - PassengerId: 602, - Age: null, - Fare: 7.8958, - Name: 'Slabenoff, Mr. Petco', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '113796', - Parch: 0, - Cabin: null, - PassengerId: 603, - Age: null, - Fare: 42.4, - Name: 'Harrington, Mr. Charles H', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '364511', - Parch: 0, - Cabin: null, - PassengerId: 604, - Age: 44, - Fare: 8.05, - Name: 'Torber, Mr. Ernst William', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '111426', - Parch: 0, - Cabin: null, - PassengerId: 605, - Age: 35, - Fare: 26.55, - Name: 'Homer, Mr. Harry ("Mr E Haven")', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '349910', - Parch: 0, - Cabin: null, - PassengerId: 606, - Age: 36, - Fare: 15.55, - Name: 'Lindell, Mr. Edvard Bengtsson', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349246', - Parch: 0, - Cabin: null, - PassengerId: 607, - Age: 30, - Fare: 7.8958, - Name: 'Karaic, Mr. Milan', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '113804', - Parch: 0, - Cabin: null, - PassengerId: 608, - Age: 27, - Fare: 30.5, - Name: 'Daniel, Mr. Robert Williams', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'SC/Paris 2123', - Parch: 2, - Cabin: null, - PassengerId: 609, - Age: 22, - Fare: 41.5792, - Name: 'Laroche, Mrs. Joseph (Juliette Marie Louise Lafargue)', - Survived: true, - Pclass: 2, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'PC 17582', - Parch: 0, - Cabin: 'C125', - PassengerId: 610, - Age: 40, - Fare: 153.4625, - Name: 'Shutes, Miss. Elizabeth W', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '347082', - Parch: 5, - Cabin: null, - PassengerId: 611, - Age: 39, - Fare: 31.275, - Name: 'Andersson, Mrs. Anders Johan (Alfrida Konstantia Brogren)', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'SOTON/O.Q. 3101305', - Parch: 0, - Cabin: null, - PassengerId: 612, - Age: null, - Fare: 7.05, - Name: 'Jardin, Mr. Jose Neto', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '367230', - Parch: 0, - Cabin: null, - PassengerId: 613, - Age: null, - Fare: 15.5, - Name: 'Murphy, Miss. Margaret Jane', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '370377', - Parch: 0, - Cabin: null, - PassengerId: 614, - Age: null, - Fare: 7.75, - Name: 'Horgan, Mr. John', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '364512', - Parch: 0, - Cabin: null, - PassengerId: 615, - Age: 35, - Fare: 8.05, - Name: 'Brocklebank, Mr. William Alfred', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '220845', - Parch: 2, - Cabin: null, - PassengerId: 616, - Age: 24, - Fare: 65, - Name: 'Herman, Miss. Alice', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '347080', - Parch: 1, - Cabin: null, - PassengerId: 617, - Age: 34, - Fare: 14.4, - Name: 'Danbom, Mr. Ernst Gilbert', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'A/5. 3336', - Parch: 0, - Cabin: null, - PassengerId: 618, - Age: 26, - Fare: 16.1, - Name: 'Lobb, Mrs. William Arthur (Cordelia K Stanlick)', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 2, - Ticket: '230136', - Parch: 1, - Cabin: 'F4', - PassengerId: 619, - Age: 4, - Fare: 39, - Name: 'Becker, Miss. Marion Louise', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '31028', - Parch: 0, - Cabin: null, - PassengerId: 620, - Age: 26, - Fare: 10.5, - Name: 'Gavey, Mr. Lawrence', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '2659', - Parch: 0, - Cabin: null, - PassengerId: 621, - Age: 27, - Fare: 14.4542, - Name: 'Yasbeck, Mr. Antoni', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '11753', - Parch: 0, - Cabin: 'D19', - PassengerId: 622, - Age: 42, - Fare: 52.5542, - Name: 'Kimball, Mr. Edwin Nelson Jr', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '2653', - Parch: 1, - Cabin: null, - PassengerId: 623, - Age: 20, - Fare: 15.7417, - Name: 'Nakid, Mr. Sahid', - Survived: true, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '350029', - Parch: 0, - Cabin: null, - PassengerId: 624, - Age: 21, - Fare: 7.8542, - Name: 'Hansen, Mr. Henry Damsgaard', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '54636', - Parch: 0, - Cabin: null, - PassengerId: 625, - Age: 21, - Fare: 16.1, - Name: 'Bowen, Mr. David John "Dai"', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '36963', - Parch: 0, - Cabin: 'D50', - PassengerId: 626, - Age: 61, - Fare: 32.3208, - Name: 'Sutton, Mr. Frederick', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '219533', - Parch: 0, - Cabin: null, - PassengerId: 627, - Age: 57, - Fare: 12.35, - Name: 'Kirkland, Rev. Charles Leonard', - Survived: false, - Pclass: 2, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '13502', - Parch: 0, - Cabin: 'D9', - PassengerId: 628, - Age: 21, - Fare: 77.9583, - Name: 'Longley, Miss. Gretchen Fiske', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '349224', - Parch: 0, - Cabin: null, - PassengerId: 629, - Age: 26, - Fare: 7.8958, - Name: 'Bostandyeff, Mr. Guentcho', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '334912', - Parch: 0, - Cabin: null, - PassengerId: 630, - Age: null, - Fare: 7.7333, - Name: "O'Connell, Mr. Patrick D", - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '27042', - Parch: 0, - Cabin: 'A23', - PassengerId: 631, - Age: 80, - Fare: 30, - Name: 'Barkworth, Mr. Algernon Henry Wilson', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '347743', - Parch: 0, - Cabin: null, - PassengerId: 632, - Age: 51, - Fare: 7.0542, - Name: 'Lundahl, Mr. Johan Svensson', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '13214', - Parch: 0, - Cabin: 'B50', - PassengerId: 633, - Age: 32, - Fare: 30.5, - Name: 'Stahelin-Maeglin, Dr. Max', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '112052', - Parch: 0, - Cabin: null, - PassengerId: 634, - Age: null, - Fare: 0, - Name: 'Parr, Mr. William Henry Marsh', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 3, - Ticket: '347088', - Parch: 2, - Cabin: null, - PassengerId: 635, - Age: 9, - Fare: 27.9, - Name: 'Skoog, Miss. Mabel', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '237668', - Parch: 0, - Cabin: null, - PassengerId: 636, - Age: 28, - Fare: 13, - Name: 'Davis, Miss. Mary', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'STON/O 2. 3101292', - Parch: 0, - Cabin: null, - PassengerId: 637, - Age: 32, - Fare: 7.925, - Name: 'Leinonen, Mr. Antti Gustaf', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'C.A. 31921', - Parch: 1, - Cabin: null, - PassengerId: 638, - Age: 31, - Fare: 26.25, - Name: 'Collyer, Mr. Harvey', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '3101295', - Parch: 5, - Cabin: null, - PassengerId: 639, - Age: 41, - Fare: 39.6875, - Name: 'Panula, Mrs. Juha (Maria Emilia Ojala)', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '376564', - Parch: 0, - Cabin: null, - PassengerId: 640, - Age: null, - Fare: 16.1, - Name: 'Thorneycroft, Mr. Percival', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '350050', - Parch: 0, - Cabin: null, - PassengerId: 641, - Age: 20, - Fare: 7.8542, - Name: 'Jensen, Mr. Hans Peder', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'PC 17477', - Parch: 0, - Cabin: 'B35', - PassengerId: 642, - Age: 24, - Fare: 69.3, - Name: 'Sagesser, Mlle. Emma', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 3, - Ticket: '347088', - Parch: 2, - Cabin: null, - PassengerId: 643, - Age: 2, - Fare: 27.9, - Name: 'Skoog, Miss. Margit Elizabeth', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '1601', - Parch: 0, - Cabin: null, - PassengerId: 644, - Age: null, - Fare: 56.4958, - Name: 'Foo, Mr. Choong', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 2, - Ticket: '2666', - Parch: 1, - Cabin: null, - PassengerId: 645, - Age: 0.75, - Fare: 19.2583, - Name: 'Baclini, Miss. Eugenie', - Survived: true, - Pclass: 3, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: 'PC 17572', - Parch: 0, - Cabin: 'D33', - PassengerId: 646, - Age: 48, - Fare: 76.7292, - Name: 'Harper, Mr. Henry Sleeper', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349231', - Parch: 0, - Cabin: null, - PassengerId: 647, - Age: 19, - Fare: 7.8958, - Name: 'Cor, Mr. Liudevit', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '13213', - Parch: 0, - Cabin: 'A26', - PassengerId: 648, - Age: 56, - Fare: 35.5, - Name: 'Simonius-Blumer, Col. Oberst Alfons', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'S.O./P.P. 751', - Parch: 0, - Cabin: null, - PassengerId: 649, - Age: null, - Fare: 7.55, - Name: 'Willey, Mr. Edward', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'CA. 2314', - Parch: 0, - Cabin: null, - PassengerId: 650, - Age: 23, - Fare: 7.55, - Name: 'Stanley, Miss. Amy Zillah Elsie', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '349221', - Parch: 0, - Cabin: null, - PassengerId: 651, - Age: null, - Fare: 7.8958, - Name: 'Mitkoff, Mr. Mito', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '231919', - Parch: 1, - Cabin: null, - PassengerId: 652, - Age: 18, - Fare: 23, - Name: 'Doling, Miss. Elsie', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '8475', - Parch: 0, - Cabin: null, - PassengerId: 653, - Age: 21, - Fare: 8.4333, - Name: 'Kalvik, Mr. Johannes Halvorsen', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '330919', - Parch: 0, - Cabin: null, - PassengerId: 654, - Age: null, - Fare: 7.8292, - Name: 'O\'Leary, Miss. Hanora "Norah"', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '365226', - Parch: 0, - Cabin: null, - PassengerId: 655, - Age: 18, - Fare: 6.75, - Name: 'Hegarty, Miss. Hanora "Nora"', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 2, - Ticket: 'S.O.C. 14879', - Parch: 0, - Cabin: null, - PassengerId: 656, - Age: 24, - Fare: 73.5, - Name: 'Hickman, Mr. Leonard Mark', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349223', - Parch: 0, - Cabin: null, - PassengerId: 657, - Age: null, - Fare: 7.8958, - Name: 'Radeff, Mr. Alexander', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '364849', - Parch: 1, - Cabin: null, - PassengerId: 658, - Age: 32, - Fare: 15.5, - Name: 'Bourke, Mrs. John (Catherine)', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '29751', - Parch: 0, - Cabin: null, - PassengerId: 659, - Age: 23, - Fare: 13, - Name: 'Eitemiller, Mr. George Floyd', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '35273', - Parch: 2, - Cabin: 'D48', - PassengerId: 660, - Age: 58, - Fare: 113.275, - Name: 'Newell, Mr. Arthur Webster', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 2, - Ticket: 'PC 17611', - Parch: 0, - Cabin: null, - PassengerId: 661, - Age: 50, - Fare: 133.65, - Name: 'Frauenthal, Dr. Henry William', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '2623', - Parch: 0, - Cabin: null, - PassengerId: 662, - Age: 40, - Fare: 7.225, - Name: 'Badt, Mr. Mohamed', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '5727', - Parch: 0, - Cabin: 'E58', - PassengerId: 663, - Age: 47, - Fare: 25.5875, - Name: 'Colley, Mr. Edward Pomeroy', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349210', - Parch: 0, - Cabin: null, - PassengerId: 664, - Age: 36, - Fare: 7.4958, - Name: 'Coleff, Mr. Peju', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'STON/O 2. 3101285', - Parch: 0, - Cabin: null, - PassengerId: 665, - Age: 20, - Fare: 7.925, - Name: 'Lindqvist, Mr. Eino William', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 2, - Ticket: 'S.O.C. 14879', - Parch: 0, - Cabin: null, - PassengerId: 666, - Age: 32, - Fare: 73.5, - Name: 'Hickman, Mr. Lewis', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '234686', - Parch: 0, - Cabin: null, - PassengerId: 667, - Age: 25, - Fare: 13, - Name: 'Butler, Mr. Reginald Fenton', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '312993', - Parch: 0, - Cabin: null, - PassengerId: 668, - Age: null, - Fare: 7.775, - Name: 'Rommetvedt, Mr. Knud Paust', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'A/5 3536', - Parch: 0, - Cabin: null, - PassengerId: 669, - Age: 43, - Fare: 8.05, - Name: 'Cook, Mr. Jacob', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '19996', - Parch: 0, - Cabin: 'C126', - PassengerId: 670, - Age: null, - Fare: 52, - Name: 'Taylor, Mrs. Elmer Zebley (Juliet Cummins Wright)', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '29750', - Parch: 1, - Cabin: null, - PassengerId: 671, - Age: 40, - Fare: 39, - Name: 'Brown, Mrs. Thomas William Solomon (Elizabeth Catherine Ford)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: 'F.C. 12750', - Parch: 0, - Cabin: 'B71', - PassengerId: 672, - Age: 31, - Fare: 52, - Name: 'Davidson, Mr. Thornton', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'C.A. 24580', - Parch: 0, - Cabin: null, - PassengerId: 673, - Age: 70, - Fare: 10.5, - Name: 'Mitchell, Mr. Henry Michael', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '244270', - Parch: 0, - Cabin: null, - PassengerId: 674, - Age: 31, - Fare: 13, - Name: 'Wilhelms, Mr. Charles', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '239856', - Parch: 0, - Cabin: null, - PassengerId: 675, - Age: null, - Fare: 0, - Name: 'Watson, Mr. Ennis Hastings', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349912', - Parch: 0, - Cabin: null, - PassengerId: 676, - Age: 18, - Fare: 7.775, - Name: 'Edvardsson, Mr. Gustaf Hjalmar', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '342826', - Parch: 0, - Cabin: null, - PassengerId: 677, - Age: 24.5, - Fare: 8.05, - Name: 'Sawyer, Mr. Frederick Charles', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '4138', - Parch: 0, - Cabin: null, - PassengerId: 678, - Age: 18, - Fare: 9.8417, - Name: 'Turja, Miss. Anna Sofia', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: 'CA 2144', - Parch: 6, - Cabin: null, - PassengerId: 679, - Age: 43, - Fare: 46.9, - Name: 'Goodwin, Mrs. Frederick (Augusta Tyler)', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'PC 17755', - Parch: 1, - Cabin: 'B51 B53 B55', - PassengerId: 680, - Age: 36, - Fare: 512.3292, - Name: 'Cardeza, Mr. Thomas Drake Martinez', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '330935', - Parch: 0, - Cabin: null, - PassengerId: 681, - Age: null, - Fare: 8.1375, - Name: 'Peters, Miss. Katie', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'PC 17572', - Parch: 0, - Cabin: 'D49', - PassengerId: 682, - Age: 27, - Fare: 76.7292, - Name: 'Hassab, Mr. Hammad', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '6563', - Parch: 0, - Cabin: null, - PassengerId: 683, - Age: 20, - Fare: 9.225, - Name: 'Olsvigen, Mr. Thor Anderson', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 5, - Ticket: 'CA 2144', - Parch: 2, - Cabin: null, - PassengerId: 684, - Age: 14, - Fare: 46.9, - Name: 'Goodwin, Mr. Charles Edward', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '29750', - Parch: 1, - Cabin: null, - PassengerId: 685, - Age: 60, - Fare: 39, - Name: 'Brown, Mr. Thomas William Solomon', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'SC/Paris 2123', - Parch: 2, - Cabin: null, - PassengerId: 686, - Age: 25, - Fare: 41.5792, - Name: 'Laroche, Mr. Joseph Philippe Lemercier', - Survived: false, - Pclass: 2, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 4, - Ticket: '3101295', - Parch: 1, - Cabin: null, - PassengerId: 687, - Age: 14, - Fare: 39.6875, - Name: 'Panula, Mr. Jaako Arnold', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349228', - Parch: 0, - Cabin: null, - PassengerId: 688, - Age: 19, - Fare: 10.1708, - Name: 'Dakic, Mr. Branko', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '350036', - Parch: 0, - Cabin: null, - PassengerId: 689, - Age: 18, - Fare: 7.7958, - Name: 'Fischer, Mr. Eberhard Thelander', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '24160', - Parch: 1, - Cabin: 'B5', - PassengerId: 690, - Age: 15, - Fare: 211.3375, - Name: 'Madill, Miss. Georgette Alexandra', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '17474', - Parch: 0, - Cabin: 'B20', - PassengerId: 691, - Age: 31, - Fare: 57, - Name: 'Dick, Mr. Albert Adrian', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349256', - Parch: 1, - Cabin: null, - PassengerId: 692, - Age: 4, - Fare: 13.4167, - Name: 'Karun, Miss. Manca', - Survived: true, - Pclass: 3, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '1601', - Parch: 0, - Cabin: null, - PassengerId: 693, - Age: null, - Fare: 56.4958, - Name: 'Lam, Mr. Ali', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '2672', - Parch: 0, - Cabin: null, - PassengerId: 694, - Age: 25, - Fare: 7.225, - Name: 'Saad, Mr. Khalil', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '113800', - Parch: 0, - Cabin: null, - PassengerId: 695, - Age: 60, - Fare: 26.55, - Name: 'Weir, Col. John', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '248731', - Parch: 0, - Cabin: null, - PassengerId: 696, - Age: 52, - Fare: 13.5, - Name: 'Chapman, Mr. Charles Henry', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '363592', - Parch: 0, - Cabin: null, - PassengerId: 697, - Age: 44, - Fare: 8.05, - Name: 'Kelly, Mr. James', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '35852', - Parch: 0, - Cabin: null, - PassengerId: 698, - Age: null, - Fare: 7.7333, - Name: 'Mullens, Miss. Katherine "Katie"', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '17421', - Parch: 1, - Cabin: 'C68', - PassengerId: 699, - Age: 49, - Fare: 110.8833, - Name: 'Thayer, Mr. John Borland', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '348121', - Parch: 0, - Cabin: 'F G63', - PassengerId: 700, - Age: 42, - Fare: 7.65, - Name: 'Humblen, Mr. Adolf Mathias Nicolai Olsen', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'PC 17757', - Parch: 0, - Cabin: 'C62 C64', - PassengerId: 701, - Age: 18, - Fare: 227.525, - Name: 'Astor, Mrs. John Jacob (Madeleine Talmadge Force)', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'PC 17475', - Parch: 0, - Cabin: 'E24', - PassengerId: 702, - Age: 35, - Fare: 26.2875, - Name: 'Silverthorne, Mr. Spencer Victor', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '2691', - Parch: 1, - Cabin: null, - PassengerId: 703, - Age: 18, - Fare: 14.4542, - Name: 'Barbara, Miss. Saiide', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '36864', - Parch: 0, - Cabin: null, - PassengerId: 704, - Age: 25, - Fare: 7.7417, - Name: 'Gallagher, Mr. Martin', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '350025', - Parch: 0, - Cabin: null, - PassengerId: 705, - Age: 26, - Fare: 7.8542, - Name: 'Hansen, Mr. Henrik Juul', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '250655', - Parch: 0, - Cabin: null, - PassengerId: 706, - Age: 39, - Fare: 26, - Name: 'Morley, Mr. Henry Samuel ("Mr Henry Marshall")', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '223596', - Parch: 0, - Cabin: null, - PassengerId: 707, - Age: 45, - Fare: 13.5, - Name: 'Kelly, Mrs. Florence "Fannie"', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'PC 17476', - Parch: 0, - Cabin: 'E24', - PassengerId: 708, - Age: 42, - Fare: 26.2875, - Name: 'Calderhead, Mr. Edward Pennington', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '113781', - Parch: 0, - Cabin: null, - PassengerId: 709, - Age: 22, - Fare: 151.55, - Name: 'Cleaver, Miss. Alice', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '2661', - Parch: 1, - Cabin: null, - PassengerId: 710, - Age: null, - Fare: 15.2458, - Name: 'Moubarek, Master. Halim Gonios ("William George")', - Survived: true, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'PC 17482', - Parch: 0, - Cabin: 'C90', - PassengerId: 711, - Age: 24, - Fare: 49.5042, - Name: 'Mayne, Mlle. Berthe Antonine ("Mrs de Villiers")', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '113028', - Parch: 0, - Cabin: 'C124', - PassengerId: 712, - Age: null, - Fare: 26.55, - Name: 'Klaber, Mr. Herman', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '19996', - Parch: 0, - Cabin: 'C126', - PassengerId: 713, - Age: 48, - Fare: 52, - Name: 'Taylor, Mr. Elmer Zebley', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '7545', - Parch: 0, - Cabin: null, - PassengerId: 714, - Age: 29, - Fare: 9.4833, - Name: 'Larsson, Mr. August Viktor', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '250647', - Parch: 0, - Cabin: null, - PassengerId: 715, - Age: 52, - Fare: 13, - Name: 'Greenberg, Mr. Samuel', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '348124', - Parch: 0, - Cabin: 'F G73', - PassengerId: 716, - Age: 19, - Fare: 7.65, - Name: 'Soholt, Mr. Peter Andreas Lauritz Andersen', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'PC 17757', - Parch: 0, - Cabin: 'C45', - PassengerId: 717, - Age: 38, - Fare: 227.525, - Name: 'Endres, Miss. Caroline Louise', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '34218', - Parch: 0, - Cabin: 'E101', - PassengerId: 718, - Age: 27, - Fare: 10.5, - Name: 'Troutt, Miss. Edwina Celia "Winnie"', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '36568', - Parch: 0, - Cabin: null, - PassengerId: 719, - Age: null, - Fare: 15.5, - Name: 'McEvoy, Mr. Michael', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '347062', - Parch: 0, - Cabin: null, - PassengerId: 720, - Age: 33, - Fare: 7.775, - Name: 'Johnson, Mr. Malkolm Joackim', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '248727', - Parch: 1, - Cabin: null, - PassengerId: 721, - Age: 6, - Fare: 33, - Name: 'Harper, Miss. Annie Jessie "Nina"', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '350048', - Parch: 0, - Cabin: null, - PassengerId: 722, - Age: 17, - Fare: 7.0542, - Name: 'Jensen, Mr. Svend Lauritz', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '12233', - Parch: 0, - Cabin: null, - PassengerId: 723, - Age: 34, - Fare: 13, - Name: 'Gillespie, Mr. William Henry', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '250643', - Parch: 0, - Cabin: null, - PassengerId: 724, - Age: 50, - Fare: 13, - Name: 'Hodges, Mr. Henry Price', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '113806', - Parch: 0, - Cabin: 'E8', - PassengerId: 725, - Age: 27, - Fare: 53.1, - Name: 'Chambers, Mr. Norman Campbell', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '315094', - Parch: 0, - Cabin: null, - PassengerId: 726, - Age: 20, - Fare: 8.6625, - Name: 'Oreskovic, Mr. Luka', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 3, - Ticket: '31027', - Parch: 0, - Cabin: null, - PassengerId: 727, - Age: 30, - Fare: 21, - Name: 'Renouf, Mrs. Peter Henry (Lillian Jefferys)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '36866', - Parch: 0, - Cabin: null, - PassengerId: 728, - Age: null, - Fare: 7.7375, - Name: 'Mannion, Miss. Margareth', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '236853', - Parch: 0, - Cabin: null, - PassengerId: 729, - Age: 25, - Fare: 26, - Name: 'Bryhl, Mr. Kurt Arnold Gottfrid', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'STON/O2. 3101271', - Parch: 0, - Cabin: null, - PassengerId: 730, - Age: 25, - Fare: 7.925, - Name: 'Ilmakangas, Miss. Pieta Sofia', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '24160', - Parch: 0, - Cabin: 'B5', - PassengerId: 731, - Age: 29, - Fare: 211.3375, - Name: 'Allen, Miss. Elisabeth Walton', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '2699', - Parch: 0, - Cabin: null, - PassengerId: 732, - Age: 11, - Fare: 18.7875, - Name: 'Hassan, Mr. Houssein G N', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '239855', - Parch: 0, - Cabin: null, - PassengerId: 733, - Age: null, - Fare: 0, - Name: 'Knight, Mr. Robert J', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '28425', - Parch: 0, - Cabin: null, - PassengerId: 734, - Age: 23, - Fare: 13, - Name: 'Berriman, Mr. William John', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '233639', - Parch: 0, - Cabin: null, - PassengerId: 735, - Age: 23, - Fare: 13, - Name: 'Troupiansky, Mr. Moses Aaron', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '54636', - Parch: 0, - Cabin: null, - PassengerId: 736, - Age: 28.5, - Fare: 16.1, - Name: 'Williams, Mr. Leslie', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'W./C. 6608', - Parch: 3, - Cabin: null, - PassengerId: 737, - Age: 48, - Fare: 34.375, - Name: 'Ford, Mrs. Edward (Margaret Ann Watson)', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'PC 17755', - Parch: 0, - Cabin: 'B101', - PassengerId: 738, - Age: 35, - Fare: 512.3292, - Name: 'Lesurer, Mr. Gustave J', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349201', - Parch: 0, - Cabin: null, - PassengerId: 739, - Age: null, - Fare: 7.8958, - Name: 'Ivanoff, Mr. Kanio', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349218', - Parch: 0, - Cabin: null, - PassengerId: 740, - Age: null, - Fare: 7.8958, - Name: 'Nankoff, Mr. Minko', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '16988', - Parch: 0, - Cabin: 'D45', - PassengerId: 741, - Age: null, - Fare: 30, - Name: 'Hawksford, Mr. Walter James', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '19877', - Parch: 0, - Cabin: 'C46', - PassengerId: 742, - Age: 36, - Fare: 78.85, - Name: 'Cavendish, Mr. Tyrell William', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 2, - Ticket: 'PC 17608', - Parch: 2, - Cabin: 'B57 B59 B63 B66', - PassengerId: 743, - Age: 21, - Fare: 262.375, - Name: 'Ryerson, Miss. Susan Parker "Suzette"', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '376566', - Parch: 0, - Cabin: null, - PassengerId: 744, - Age: 24, - Fare: 16.1, - Name: 'McNamee, Mr. Neal', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'STON/O 2. 3101288', - Parch: 0, - Cabin: null, - PassengerId: 745, - Age: 31, - Fare: 7.925, - Name: 'Stranden, Mr. Juho', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'WE/P 5735', - Parch: 1, - Cabin: 'B22', - PassengerId: 746, - Age: 70, - Fare: 71, - Name: 'Crosby, Capt. Edward Gifford', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'C.A. 2673', - Parch: 1, - Cabin: null, - PassengerId: 747, - Age: 16, - Fare: 20.25, - Name: 'Abbott, Mr. Rossmore Edward', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '250648', - Parch: 0, - Cabin: null, - PassengerId: 748, - Age: 30, - Fare: 13, - Name: 'Sinkkonen, Miss. Anna', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '113773', - Parch: 0, - Cabin: 'D30', - PassengerId: 749, - Age: 19, - Fare: 53.1, - Name: 'Marvin, Mr. Daniel Warner', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '335097', - Parch: 0, - Cabin: null, - PassengerId: 750, - Age: 31, - Fare: 7.75, - Name: 'Connaghton, Mr. Michael', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '29103', - Parch: 1, - Cabin: null, - PassengerId: 751, - Age: 4, - Fare: 23, - Name: 'Wells, Miss. Joan', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '392096', - Parch: 1, - Cabin: 'E121', - PassengerId: 752, - Age: 6, - Fare: 12.475, - Name: 'Moor, Master. Meier', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '345780', - Parch: 0, - Cabin: null, - PassengerId: 753, - Age: 33, - Fare: 9.5, - Name: 'Vande Velde, Mr. Johannes Joseph', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349204', - Parch: 0, - Cabin: null, - PassengerId: 754, - Age: 23, - Fare: 7.8958, - Name: 'Jonkoff, Mr. Lalio', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '220845', - Parch: 2, - Cabin: null, - PassengerId: 755, - Age: 48, - Fare: 65, - Name: 'Herman, Mrs. Samuel (Jane Laver)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '250649', - Parch: 1, - Cabin: null, - PassengerId: 756, - Age: 0.67, - Fare: 14.5, - Name: 'Hamalainen, Master. Viljo', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '350042', - Parch: 0, - Cabin: null, - PassengerId: 757, - Age: 28, - Fare: 7.7958, - Name: 'Carlsson, Mr. August Sigfrid', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '29108', - Parch: 0, - Cabin: null, - PassengerId: 758, - Age: 18, - Fare: 11.5, - Name: 'Bailey, Mr. Percy Andrew', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '363294', - Parch: 0, - Cabin: null, - PassengerId: 759, - Age: 34, - Fare: 8.05, - Name: 'Theobald, Mr. Thomas Leonard', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '110152', - Parch: 0, - Cabin: 'B77', - PassengerId: 760, - Age: 33, - Fare: 86.5, - Name: 'Rothes, the Countess. of (Lucy Noel Martha Dyer-Edwards)', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '358585', - Parch: 0, - Cabin: null, - PassengerId: 761, - Age: null, - Fare: 14.5, - Name: 'Garfirth, Mr. John', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'SOTON/O2 3101272', - Parch: 0, - Cabin: null, - PassengerId: 762, - Age: 41, - Fare: 7.125, - Name: 'Nirva, Mr. Iisakki Antino Aijo', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '2663', - Parch: 0, - Cabin: null, - PassengerId: 763, - Age: 20, - Fare: 7.2292, - Name: 'Barah, Mr. Hanna Assi', - Survived: true, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '113760', - Parch: 2, - Cabin: 'B96 B98', - PassengerId: 764, - Age: 36, - Fare: 120, - Name: 'Carter, Mrs. William Ernest (Lucile Polk)', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '347074', - Parch: 0, - Cabin: null, - PassengerId: 765, - Age: 16, - Fare: 7.775, - Name: 'Eklund, Mr. Hans Linus', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '13502', - Parch: 0, - Cabin: 'D11', - PassengerId: 766, - Age: 51, - Fare: 77.9583, - Name: 'Hogeboom, Mrs. John C (Anna Andrews)', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '112379', - Parch: 0, - Cabin: null, - PassengerId: 767, - Age: null, - Fare: 39.6, - Name: 'Brewe, Dr. Arthur Jackson', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '364850', - Parch: 0, - Cabin: null, - PassengerId: 768, - Age: 30.5, - Fare: 7.75, - Name: 'Mangan, Miss. Mary', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '371110', - Parch: 0, - Cabin: null, - PassengerId: 769, - Age: null, - Fare: 24.15, - Name: 'Moran, Mr. Daniel J', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '8471', - Parch: 0, - Cabin: null, - PassengerId: 770, - Age: 32, - Fare: 8.3625, - Name: 'Gronnestad, Mr. Daniel Danielsen', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '345781', - Parch: 0, - Cabin: null, - PassengerId: 771, - Age: 24, - Fare: 9.5, - Name: 'Lievens, Mr. Rene Aime', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '350047', - Parch: 0, - Cabin: null, - PassengerId: 772, - Age: 48, - Fare: 7.8542, - Name: 'Jensen, Mr. Niels Peder', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'S.O./P.P. 3', - Parch: 0, - Cabin: 'E77', - PassengerId: 773, - Age: 57, - Fare: 10.5, - Name: 'Mack, Mrs. (Mary)', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '2674', - Parch: 0, - Cabin: null, - PassengerId: 774, - Age: null, - Fare: 7.225, - Name: 'Elias, Mr. Dibo', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '29105', - Parch: 3, - Cabin: null, - PassengerId: 775, - Age: 54, - Fare: 23, - Name: 'Hocking, Mrs. Elizabeth (Eliza Needs)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '347078', - Parch: 0, - Cabin: null, - PassengerId: 776, - Age: 18, - Fare: 7.75, - Name: 'Myhrman, Mr. Pehr Fabian Oliver Malkolm', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '383121', - Parch: 0, - Cabin: 'F38', - PassengerId: 777, - Age: null, - Fare: 7.75, - Name: 'Tobin, Mr. Roger', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '364516', - Parch: 0, - Cabin: null, - PassengerId: 778, - Age: 5, - Fare: 12.475, - Name: 'Emanuel, Miss. Virginia Ethel', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '36865', - Parch: 0, - Cabin: null, - PassengerId: 779, - Age: null, - Fare: 7.7375, - Name: 'Kilgannon, Mr. Thomas J', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '24160', - Parch: 1, - Cabin: 'B3', - PassengerId: 780, - Age: 43, - Fare: 211.3375, - Name: 'Robert, Mrs. Edward Scott (Elisabeth Walton McMillan)', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '2687', - Parch: 0, - Cabin: null, - PassengerId: 781, - Age: 13, - Fare: 7.2292, - Name: 'Ayoub, Miss. Banoura', - Survived: true, - Pclass: 3, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '17474', - Parch: 0, - Cabin: 'B20', - PassengerId: 782, - Age: 17, - Fare: 57, - Name: 'Dick, Mrs. Albert Adrian (Vera Gillespie)', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '113501', - Parch: 0, - Cabin: 'D6', - PassengerId: 783, - Age: 29, - Fare: 30, - Name: 'Long, Mr. Milton Clyde', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'W./C. 6607', - Parch: 2, - Cabin: null, - PassengerId: 784, - Age: null, - Fare: 23.45, - Name: 'Johnston, Mr. Andrew G', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'SOTON/O.Q. 3101312', - Parch: 0, - Cabin: null, - PassengerId: 785, - Age: 25, - Fare: 7.05, - Name: 'Ali, Mr. William', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '374887', - Parch: 0, - Cabin: null, - PassengerId: 786, - Age: 25, - Fare: 7.25, - Name: 'Harmer, Mr. Abraham (David Lishin)', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '3101265', - Parch: 0, - Cabin: null, - PassengerId: 787, - Age: 18, - Fare: 7.4958, - Name: 'Sjoblom, Miss. Anna Sofia', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 4, - Ticket: '382652', - Parch: 1, - Cabin: null, - PassengerId: 788, - Age: 8, - Fare: 29.125, - Name: 'Rice, Master. George Hugh', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'C.A. 2315', - Parch: 2, - Cabin: null, - PassengerId: 789, - Age: 1, - Fare: 20.575, - Name: 'Dean, Master. Bertram Vere', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'PC 17593', - Parch: 0, - Cabin: 'B82 B84', - PassengerId: 790, - Age: 46, - Fare: 79.2, - Name: 'Guggenheim, Mr. Benjamin', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '12460', - Parch: 0, - Cabin: null, - PassengerId: 791, - Age: null, - Fare: 7.75, - Name: 'Keane, Mr. Andrew "Andy"', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '239865', - Parch: 0, - Cabin: null, - PassengerId: 792, - Age: 16, - Fare: 26, - Name: 'Gaskell, Mr. Alfred', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 8, - Ticket: 'CA. 2343', - Parch: 2, - Cabin: null, - PassengerId: 793, - Age: null, - Fare: 69.55, - Name: 'Sage, Miss. Stella Anna', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'PC 17600', - Parch: 0, - Cabin: null, - PassengerId: 794, - Age: null, - Fare: 30.6958, - Name: 'Hoyt, Mr. William Fisher', - Survived: false, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349203', - Parch: 0, - Cabin: null, - PassengerId: 795, - Age: 25, - Fare: 7.8958, - Name: 'Dantcheff, Mr. Ristiu', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '28213', - Parch: 0, - Cabin: null, - PassengerId: 796, - Age: 39, - Fare: 13, - Name: 'Otter, Mr. Richard', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '17465', - Parch: 0, - Cabin: 'D17', - PassengerId: 797, - Age: 49, - Fare: 25.9292, - Name: 'Leader, Dr. Alice (Farnham)', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '349244', - Parch: 0, - Cabin: null, - PassengerId: 798, - Age: 31, - Fare: 8.6833, - Name: 'Osman, Mrs. Mara', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '2685', - Parch: 0, - Cabin: null, - PassengerId: 799, - Age: 30, - Fare: 7.2292, - Name: 'Ibrahim Shawah, Mr. Yousseff', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '345773', - Parch: 1, - Cabin: null, - PassengerId: 800, - Age: 30, - Fare: 24.15, - Name: 'Van Impe, Mrs. Jean Baptiste (Rosalie Paula Govaert)', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '250647', - Parch: 0, - Cabin: null, - PassengerId: 801, - Age: 34, - Fare: 13, - Name: 'Ponesell, Mr. Martin', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'C.A. 31921', - Parch: 1, - Cabin: null, - PassengerId: 802, - Age: 31, - Fare: 26.25, - Name: 'Collyer, Mrs. Harvey (Charlotte Annie Tate)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '113760', - Parch: 2, - Cabin: 'B96 B98', - PassengerId: 803, - Age: 11, - Fare: 120, - Name: 'Carter, Master. William Thornton II', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '2625', - Parch: 1, - Cabin: null, - PassengerId: 804, - Age: 0.42, - Fare: 8.5167, - Name: 'Thomas, Master. Assad Alexander', - Survived: true, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '347089', - Parch: 0, - Cabin: null, - PassengerId: 805, - Age: 27, - Fare: 6.975, - Name: 'Hedman, Mr. Oskar Arvid', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '347063', - Parch: 0, - Cabin: null, - PassengerId: 806, - Age: 31, - Fare: 7.775, - Name: 'Johansson, Mr. Karl Johan', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '112050', - Parch: 0, - Cabin: 'A36', - PassengerId: 807, - Age: 39, - Fare: 0, - Name: 'Andrews, Mr. Thomas Jr', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '347087', - Parch: 0, - Cabin: null, - PassengerId: 808, - Age: 18, - Fare: 7.775, - Name: 'Pettersson, Miss. Ellen Natalia', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '248723', - Parch: 0, - Cabin: null, - PassengerId: 809, - Age: 39, - Fare: 13, - Name: 'Meyer, Mr. August', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '113806', - Parch: 0, - Cabin: 'E8', - PassengerId: 810, - Age: 33, - Fare: 53.1, - Name: 'Chambers, Mrs. Norman Campbell (Bertha Griggs)', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '3474', - Parch: 0, - Cabin: null, - PassengerId: 811, - Age: 26, - Fare: 7.8875, - Name: 'Alexander, Mr. William', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'A/4 48871', - Parch: 0, - Cabin: null, - PassengerId: 812, - Age: 39, - Fare: 24.15, - Name: 'Lester, Mr. James', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '28206', - Parch: 0, - Cabin: null, - PassengerId: 813, - Age: 35, - Fare: 10.5, - Name: 'Slemen, Mr. Richard James', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 4, - Ticket: '347082', - Parch: 2, - Cabin: null, - PassengerId: 814, - Age: 6, - Fare: 31.275, - Name: 'Andersson, Miss. Ebba Iris Alfrida', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '364499', - Parch: 0, - Cabin: null, - PassengerId: 815, - Age: 30.5, - Fare: 8.05, - Name: 'Tomlin, Mr. Ernest Portage', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '112058', - Parch: 0, - Cabin: 'B102', - PassengerId: 816, - Age: null, - Fare: 0, - Name: 'Fry, Mr. Richard', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'STON/O2. 3101290', - Parch: 0, - Cabin: null, - PassengerId: 817, - Age: 23, - Fare: 7.925, - Name: 'Heininen, Miss. Wendla Maria', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: 'S.C./PARIS 2079', - Parch: 1, - Cabin: null, - PassengerId: 818, - Age: 31, - Fare: 37.0042, - Name: 'Mallet, Mr. Albert', - Survived: false, - Pclass: 2, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'C 7075', - Parch: 0, - Cabin: null, - PassengerId: 819, - Age: 43, - Fare: 6.45, - Name: 'Holm, Mr. John Fredrik Alexander', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 3, - Ticket: '347088', - Parch: 2, - Cabin: null, - PassengerId: 820, - Age: 10, - Fare: 27.9, - Name: 'Skoog, Master. Karl Thorsten', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '12749', - Parch: 1, - Cabin: 'B69', - PassengerId: 821, - Age: 52, - Fare: 93.5, - Name: 'Hays, Mrs. Charles Melville (Clara Jennings Gregg)', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '315098', - Parch: 0, - Cabin: null, - PassengerId: 822, - Age: 27, - Fare: 8.6625, - Name: 'Lulic, Mr. Nikola', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '19972', - Parch: 0, - Cabin: null, - PassengerId: 823, - Age: 38, - Fare: 0, - Name: 'Reuchlin, Jonkheer. John George', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '392096', - Parch: 1, - Cabin: 'E121', - PassengerId: 824, - Age: 27, - Fare: 12.475, - Name: 'Moor, Mrs. (Beila)', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 4, - Ticket: '3101295', - Parch: 1, - Cabin: null, - PassengerId: 825, - Age: 2, - Fare: 39.6875, - Name: 'Panula, Master. Urho Abraham', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '368323', - Parch: 0, - Cabin: null, - PassengerId: 826, - Age: null, - Fare: 6.95, - Name: 'Flynn, Mr. John', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '1601', - Parch: 0, - Cabin: null, - PassengerId: 827, - Age: null, - Fare: 56.4958, - Name: 'Lam, Mr. Len', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'S.C./PARIS 2079', - Parch: 2, - Cabin: null, - PassengerId: 828, - Age: 1, - Fare: 37.0042, - Name: 'Mallet, Master. Andre', - Survived: true, - Pclass: 2, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '367228', - Parch: 0, - Cabin: null, - PassengerId: 829, - Age: null, - Fare: 7.75, - Name: 'McCormack, Mr. Thomas Joseph', - Survived: true, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '113572', - Parch: 0, - Cabin: 'B28', - PassengerId: 830, - Age: 62, - Fare: 80, - Name: 'Stone, Mrs. George Nelson (Martha Evelyn)', - Survived: true, - Pclass: 1, - Embarked: null, - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '2659', - Parch: 0, - Cabin: null, - PassengerId: 831, - Age: 15, - Fare: 14.4542, - Name: 'Yasbeck, Mrs. Antoni (Selini Alexander)', - Survived: true, - Pclass: 3, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '29106', - Parch: 1, - Cabin: null, - PassengerId: 832, - Age: 0.83, - Fare: 18.75, - Name: 'Richards, Master. George Sibley', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '2671', - Parch: 0, - Cabin: null, - PassengerId: 833, - Age: null, - Fare: 7.2292, - Name: 'Saad, Mr. Amin', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '347468', - Parch: 0, - Cabin: null, - PassengerId: 834, - Age: 23, - Fare: 7.8542, - Name: 'Augustsson, Mr. Albert', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '2223', - Parch: 0, - Cabin: null, - PassengerId: 835, - Age: 18, - Fare: 8.3, - Name: 'Allum, Mr. Owen George', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'PC 17756', - Parch: 1, - Cabin: 'E49', - PassengerId: 836, - Age: 39, - Fare: 83.1583, - Name: 'Compton, Miss. Sara Rebecca', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '315097', - Parch: 0, - Cabin: null, - PassengerId: 837, - Age: 21, - Fare: 8.6625, - Name: 'Pasic, Mr. Jakob', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '392092', - Parch: 0, - Cabin: null, - PassengerId: 838, - Age: null, - Fare: 8.05, - Name: 'Sirota, Mr. Maurice', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '1601', - Parch: 0, - Cabin: null, - PassengerId: 839, - Age: 32, - Fare: 56.4958, - Name: 'Chip, Mr. Chang', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '11774', - Parch: 0, - Cabin: 'C47', - PassengerId: 840, - Age: null, - Fare: 29.7, - Name: 'Marechal, Mr. Pierre', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'SOTON/O2 3101287', - Parch: 0, - Cabin: null, - PassengerId: 841, - Age: 20, - Fare: 7.925, - Name: 'Alhomaki, Mr. Ilmari Rudolf', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'S.O./P.P. 3', - Parch: 0, - Cabin: null, - PassengerId: 842, - Age: 16, - Fare: 10.5, - Name: 'Mudd, Mr. Thomas Charles', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '113798', - Parch: 0, - Cabin: null, - PassengerId: 843, - Age: 30, - Fare: 31, - Name: 'Serepeca, Miss. Augusta', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '2683', - Parch: 0, - Cabin: null, - PassengerId: 844, - Age: 34.5, - Fare: 6.4375, - Name: 'Lemberopolous, Mr. Peter L', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '315090', - Parch: 0, - Cabin: null, - PassengerId: 845, - Age: 17, - Fare: 8.6625, - Name: 'Culumovic, Mr. Jeso', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'C.A. 5547', - Parch: 0, - Cabin: null, - PassengerId: 846, - Age: 42, - Fare: 7.55, - Name: 'Abbing, Mr. Anthony', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 8, - Ticket: 'CA. 2343', - Parch: 2, - Cabin: null, - PassengerId: 847, - Age: null, - Fare: 69.55, - Name: 'Sage, Mr. Douglas Bullen', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349213', - Parch: 0, - Cabin: null, - PassengerId: 848, - Age: 35, - Fare: 7.8958, - Name: 'Markoff, Mr. Marin', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '248727', - Parch: 1, - Cabin: null, - PassengerId: 849, - Age: 28, - Fare: 33, - Name: 'Harper, Rev. John', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '17453', - Parch: 0, - Cabin: 'C92', - PassengerId: 850, - Age: null, - Fare: 89.1042, - Name: 'Goldenberg, Mrs. Samuel L (Edwiga Grabowska)', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 4, - Ticket: '347082', - Parch: 2, - Cabin: null, - PassengerId: 851, - Age: 4, - Fare: 31.275, - Name: 'Andersson, Master. Sigvard Harald Elias', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '347060', - Parch: 0, - Cabin: null, - PassengerId: 852, - Age: 74, - Fare: 7.775, - Name: 'Svensson, Mr. Johan', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '2678', - Parch: 1, - Cabin: null, - PassengerId: 853, - Age: 9, - Fare: 15.2458, - Name: 'Boulos, Miss. Nourelain', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'PC 17592', - Parch: 1, - Cabin: 'D28', - PassengerId: 854, - Age: 16, - Fare: 39.4, - Name: 'Lines, Miss. Mary Conover', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '244252', - Parch: 0, - Cabin: null, - PassengerId: 855, - Age: 44, - Fare: 26, - Name: 'Carter, Mrs. Ernest Courtenay (Lilian Hughes)', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '392091', - Parch: 1, - Cabin: null, - PassengerId: 856, - Age: 18, - Fare: 9.35, - Name: 'Aks, Mrs. Sam (Leah Rosen)', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: '36928', - Parch: 1, - Cabin: null, - PassengerId: 857, - Age: 45, - Fare: 164.8667, - Name: 'Wick, Mrs. George Dennick (Mary Hitchcock)', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '113055', - Parch: 0, - Cabin: 'E17', - PassengerId: 858, - Age: 51, - Fare: 26.55, - Name: 'Daly, Mr. Peter Denis ', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '2666', - Parch: 3, - Cabin: null, - PassengerId: 859, - Age: 24, - Fare: 19.2583, - Name: 'Baclini, Mrs. Solomon (Latifa Qurban)', - Survived: true, - Pclass: 3, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '2629', - Parch: 0, - Cabin: null, - PassengerId: 860, - Age: null, - Fare: 7.2292, - Name: 'Razi, Mr. Raihed', - Survived: false, - Pclass: 3, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 2, - Ticket: '350026', - Parch: 0, - Cabin: null, - PassengerId: 861, - Age: 41, - Fare: 14.1083, - Name: 'Hansen, Mr. Claus Peter', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '28134', - Parch: 0, - Cabin: null, - PassengerId: 862, - Age: 21, - Fare: 11.5, - Name: 'Giles, Mr. Frederick Edward', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '17466', - Parch: 0, - Cabin: 'D17', - PassengerId: 863, - Age: 48, - Fare: 25.9292, - Name: 'Swift, Mrs. Frederick Joel (Margaret Welles Barron)', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 8, - Ticket: 'CA. 2343', - Parch: 2, - Cabin: null, - PassengerId: 864, - Age: null, - Fare: 69.55, - Name: 'Sage, Miss. Dorothy Edith "Dolly"', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '233866', - Parch: 0, - Cabin: null, - PassengerId: 865, - Age: 24, - Fare: 13, - Name: 'Gill, Mr. John William', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '236852', - Parch: 0, - Cabin: null, - PassengerId: 866, - Age: 42, - Fare: 13, - Name: 'Bystrom, Mrs. (Karolina)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: 'SC/PARIS 2149', - Parch: 0, - Cabin: null, - PassengerId: 867, - Age: 27, - Fare: 13.8583, - Name: 'Duran y More, Miss. Asuncion', - Survived: true, - Pclass: 2, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'PC 17590', - Parch: 0, - Cabin: 'A24', - PassengerId: 868, - Age: 31, - Fare: 50.4958, - Name: 'Roebling, Mr. Washington Augustus II', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '345777', - Parch: 0, - Cabin: null, - PassengerId: 869, - Age: null, - Fare: 9.5, - Name: 'van Melkebeke, Mr. Philemon', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '347742', - Parch: 1, - Cabin: null, - PassengerId: 870, - Age: 4, - Fare: 11.1333, - Name: 'Johnson, Master. Harold Theodor', - Survived: true, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349248', - Parch: 0, - Cabin: null, - PassengerId: 871, - Age: 26, - Fare: 7.8958, - Name: 'Balkic, Mr. Cerin', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: '11751', - Parch: 1, - Cabin: 'D35', - PassengerId: 872, - Age: 47, - Fare: 52.5542, - Name: 'Beckwith, Mrs. Richard Leonard (Sallie Monypeny)', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '695', - Parch: 0, - Cabin: 'B51 B53 B55', - PassengerId: 873, - Age: 33, - Fare: 5, - Name: 'Carlsson, Mr. Frans Olof', - Survived: false, - Pclass: 1, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '345765', - Parch: 0, - Cabin: null, - PassengerId: 874, - Age: 47, - Fare: 9, - Name: 'Vander Cruyssen, Mr. Victor', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 1, - Ticket: 'P/PP 3381', - Parch: 0, - Cabin: null, - PassengerId: 875, - Age: 28, - Fare: 24, - Name: 'Abelson, Mrs. Samuel (Hannah Wizosky)', - Survived: true, - Pclass: 2, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '2667', - Parch: 0, - Cabin: null, - PassengerId: 876, - Age: 15, - Fare: 7.225, - Name: 'Najib, Miss. Adele Kiamie "Jane"', - Survived: true, - Pclass: 3, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '7534', - Parch: 0, - Cabin: null, - PassengerId: 877, - Age: 20, - Fare: 9.8458, - Name: 'Gustafsson, Mr. Alfred Ossian', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349212', - Parch: 0, - Cabin: null, - PassengerId: 878, - Age: 19, - Fare: 7.8958, - Name: 'Petroff, Mr. Nedelio', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '349217', - Parch: 0, - Cabin: null, - PassengerId: 879, - Age: null, - Fare: 7.8958, - Name: 'Laleff, Mr. Kristo', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '11767', - Parch: 1, - Cabin: 'C50', - PassengerId: 880, - Age: 56, - Fare: 83.1583, - Name: 'Potter, Mrs. Thomas Jr (Lily Alexenia Wilson)', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '230433', - Parch: 1, - Cabin: null, - PassengerId: 881, - Age: 25, - Fare: 26, - Name: 'Shelley, Mrs. William (Imanita Parrish Hall)', - Survived: true, - Pclass: 2, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '349257', - Parch: 0, - Cabin: null, - PassengerId: 882, - Age: 33, - Fare: 7.8958, - Name: 'Markun, Mr. Johann', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '7552', - Parch: 0, - Cabin: null, - PassengerId: 883, - Age: 22, - Fare: 10.5167, - Name: 'Dahlberg, Miss. Gerda Ulrika', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: 'C.A./SOTON 34068', - Parch: 0, - Cabin: null, - PassengerId: 884, - Age: 28, - Fare: 10.5, - Name: 'Banfield, Mr. Frederick James', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: 'SOTON/OQ 392076', - Parch: 0, - Cabin: null, - PassengerId: 885, - Age: 25, - Fare: 7.05, - Name: 'Sutehall, Mr. Henry Jr', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '382652', - Parch: 5, - Cabin: null, - PassengerId: 886, - Age: 39, - Fare: 29.125, - Name: 'Rice, Mrs. William (Margaret Norton)', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '211536', - Parch: 0, - Cabin: null, - PassengerId: 887, - Age: 27, - Fare: 13, - Name: 'Montvila, Rev. Juozas', - Survived: false, - Pclass: 2, - Embarked: 'S', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '112053', - Parch: 0, - Cabin: 'B42', - PassengerId: 888, - Age: 19, - Fare: 30, - Name: 'Graham, Miss. Margaret Edith', - Survived: true, - Pclass: 1, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 1, - Ticket: 'W./C. 6607', - Parch: 2, - Cabin: null, - PassengerId: 889, - Age: null, - Fare: 23.45, - Name: 'Johnston, Miss. Catherine Helen "Carrie"', - Survived: false, - Pclass: 3, - Embarked: 'S', - Sex: 'female' - }, - { - SibSp: 0, - Ticket: '111369', - Parch: 0, - Cabin: 'C148', - PassengerId: 890, - Age: 26, - Fare: 30, - Name: 'Behr, Mr. Karl Howell', - Survived: true, - Pclass: 1, - Embarked: 'C', - Sex: 'male' - }, - { - SibSp: 0, - Ticket: '370376', - Parch: 0, - Cabin: null, - PassengerId: 891, - Age: 32, - Fare: 7.75, - Name: 'Dooley, Mr. Patrick', - Survived: false, - Pclass: 3, - Embarked: 'Q', - Sex: 'male' - } -]; diff --git a/src/datascience-ui/history-react/index.html b/src/datascience-ui/history-react/index.html deleted file mode 100644 index 5f8c85aee905..000000000000 --- a/src/datascience-ui/history-react/index.html +++ /dev/null @@ -1,379 +0,0 @@ - - - - - - - React App - - - - -
- - - - - - - diff --git a/src/datascience-ui/history-react/index.tsx b/src/datascience-ui/history-react/index.tsx deleted file mode 100644 index e50937db6593..000000000000 --- a/src/datascience-ui/history-react/index.tsx +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -// This must be on top, do not change. Required by webpack. -import '../common/main'; -// This must be on top, do not change. Required by webpack. - -// tslint:disable-next-line: ordered-imports -import '../common/index.css'; - -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import { Provider } from 'react-redux'; - -import { WidgetManagerComponent } from '../ipywidgets/container'; -import { IVsCodeApi, PostOffice } from '../react-common/postOffice'; -import { detectBaseTheme } from '../react-common/themeDetector'; -import { getConnectedInteractiveEditor } from './interactivePanel'; -import { createStore } from './redux/store'; - -// This special function talks to vscode from a web panel -export declare function acquireVsCodeApi(): IVsCodeApi; -const baseTheme = detectBaseTheme(); -// tslint:disable-next-line: no-any -const testMode = (window as any).inTestMode; -// tslint:disable-next-line: no-typeof-undefined -const skipDefault = testMode ? false : typeof acquireVsCodeApi !== 'undefined'; - -// Create the redux store -const postOffice = new PostOffice(); -const store = createStore(skipDefault, baseTheme, testMode, postOffice); - -// Wire up a connected react control for our InteractiveEditor -const ConnectedInteractiveEditor = getConnectedInteractiveEditor(); - -// Stick them all together -// tslint:disable:no-typeof-undefined -ReactDOM.render( - - - - , - document.getElementById('root') as HTMLElement -); diff --git a/src/datascience-ui/history-react/interactiveCell.tsx b/src/datascience-ui/history-react/interactiveCell.tsx deleted file mode 100644 index 63a5e7bdf10f..000000000000 --- a/src/datascience-ui/history-react/interactiveCell.tsx +++ /dev/null @@ -1,464 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../client/common/extensions'; - -import { nbformat } from '@jupyterlab/coreutils'; -import * as fastDeepEqual from 'fast-deep-equal'; -import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; -import * as React from 'react'; -import { connect } from 'react-redux'; - -import { Identifiers } from '../../client/datascience/constants'; -import { CellState, IDataScienceExtraSettings } from '../../client/datascience/types'; -import { CellInput } from '../interactive-common/cellInput'; -import { CellOutput } from '../interactive-common/cellOutput'; -import { CollapseButton } from '../interactive-common/collapseButton'; -import { ExecutionCount } from '../interactive-common/executionCount'; -import { InformationMessages } from '../interactive-common/informationMessages'; -import { InputHistory } from '../interactive-common/inputHistory'; -import { ICellViewModel, IFont } from '../interactive-common/mainState'; -import { IKeyboardEvent } from '../react-common/event'; -import { Image, ImageName } from '../react-common/image'; -import { ImageButton } from '../react-common/imageButton'; -import { getLocString } from '../react-common/locReactSide'; -import { IMonacoModelContentChangeEvent } from '../react-common/monacoHelpers'; -import { actionCreators } from './redux/actions'; - -interface IInteractiveCellBaseProps { - role?: string; - cellVM: ICellViewModel; - language: string; - baseTheme: string; - codeTheme: string; - testMode?: boolean; - autoFocus: boolean; - maxTextSize?: number; - enableScroll?: boolean; - showWatermark: boolean; - monacoTheme: string | undefined; - editorOptions?: monacoEditor.editor.IEditorOptions; - editExecutionCount?: string; - editorMeasureClassName?: string; - font: IFont; - settings: IDataScienceExtraSettings; - focusPending: number; -} - -type IInteractiveCellProps = IInteractiveCellBaseProps & typeof actionCreators; - -// tslint:disable: react-this-binding-issue -export class InteractiveCell extends React.Component { - private codeRef: React.RefObject = React.createRef(); - private wrapperRef: React.RefObject = React.createRef(); - private inputHistory: InputHistory | undefined; - - constructor(prop: IInteractiveCellProps) { - super(prop); - this.state = { showingMarkdownEditor: false }; - if (prop.cellVM.cell.id === Identifiers.EditCellId) { - this.inputHistory = new InputHistory(); - } - } - - public render() { - if (this.props.cellVM.cell.data.cell_type === 'messages') { - return ; - } else { - return this.renderNormalCell(); - } - } - - public componentDidUpdate(prevProps: IInteractiveCellProps) { - if (this.props.cellVM.selected && !prevProps.cellVM.selected && !this.props.cellVM.focused) { - this.giveFocus(); - } - if (this.props.cellVM.scrollCount !== prevProps.cellVM.scrollCount) { - this.scrollAndFlash(); - } - } - - public shouldComponentUpdate(nextProps: IInteractiveCellProps): boolean { - return !fastDeepEqual(this.props, nextProps); - } - - private scrollAndFlash() { - if (this.wrapperRef && this.wrapperRef.current) { - // tslint:disable-next-line: no-any - if ((this.wrapperRef.current as any).scrollIntoView) { - this.wrapperRef.current.scrollIntoView({ behavior: 'auto', block: 'nearest', inline: 'nearest' }); - } - this.wrapperRef.current.classList.add('flash'); - setTimeout(() => { - if (this.wrapperRef.current) { - this.wrapperRef.current.classList.remove('flash'); - } - }, 1000); - } - } - - private giveFocus() { - // Start out with ourselves - if (this.wrapperRef && this.wrapperRef.current) { - // Give focus to the cell if not already owning focus - if (!this.wrapperRef.current.contains(document.activeElement)) { - this.wrapperRef.current.focus(); - } - - // Scroll into view (since we have focus). However this function - // is not supported on enzyme - // tslint:disable-next-line: no-any - if ((this.wrapperRef.current as any).scrollIntoView) { - this.wrapperRef.current.scrollIntoView({ behavior: 'auto', block: 'nearest', inline: 'nearest' }); - } - } - } - - private toggleInputBlock = () => { - const cellId: string = this.getCell().id; - this.props.toggleInputBlock(cellId); - }; - - private getCell = () => { - return this.props.cellVM.cell; - }; - - private isCodeCell = () => { - return this.props.cellVM.cell.data.cell_type === 'code'; - }; - - private renderNormalCell() { - const allowsPlainInput = - this.props.settings.showCellInputCode || this.props.cellVM.directInput || this.props.cellVM.editable; - const shouldRender = allowsPlainInput || this.shouldRenderResults(); - const cellOuterClass = this.props.cellVM.editable ? 'cell-outer-editable' : 'cell-outer'; - const cellWrapperClass = this.props.cellVM.editable ? 'cell-wrapper' : 'cell-wrapper cell-wrapper-noneditable'; - const themeMatplotlibPlots = this.props.settings.themeMatplotlibPlots ? true : false; - // Only render if we are allowed to. - if (shouldRender) { - return ( -
-
- {this.renderControls()} -
-
- {this.renderInput()} -
- -
-
-
-
-
- ); - } - - // Shouldn't be rendered because not allowing empty input and not a direct input cell - return null; - } - - private renderNormalToolbar = () => { - const cell = this.getCell(); - const cellId = cell.id; - const gotoCode = () => this.props.gotoCell(cellId); - const deleteCode = () => this.props.deleteCell(cellId); - const copyCode = () => this.props.copyCellCode(cellId); - const gatherCode = () => this.props.gatherCellToScript(cellId); - const hasNoSource = !cell || !cell.file || cell.file === Identifiers.EmptyFileName; - - return ( -
- - - - - - -
- ); - }; - - private onMouseClick = (ev: React.MouseEvent) => { - // When we receive a click, propagate upwards. Might change our state - ev.stopPropagation(); - this.props.clickCell(this.props.cellVM.cell.id); - }; - - private renderControls = () => { - const busy = - this.props.cellVM.cell.state === CellState.init || this.props.cellVM.cell.state === CellState.executing; - const collapseVisible = - this.props.cellVM.inputBlockCollapseNeeded && - this.props.cellVM.inputBlockShow && - !this.props.cellVM.editable && - this.isCodeCell(); - const executionCount = - this.props.cellVM && - this.props.cellVM.cell && - this.props.cellVM.cell.data && - this.props.cellVM.cell.data.execution_count - ? this.props.cellVM.cell.data.execution_count.toString() - : '-'; - const isEditOnlyCell = this.props.cellVM.cell.id === Identifiers.EditCellId; - const toolbar = isEditOnlyCell ? null : this.renderNormalToolbar(); - - return ( -
- - - {toolbar} -
- ); - }; - - private renderInput = () => { - if (this.isCodeCell()) { - return ( - - ); - } - return null; - }; - - private isEditCell(): boolean { - return this.getCell().id === Identifiers.EditCellId; - } - - private onUnfocused = () => { - this.props.unfocus(this.getCell().id); - }; - - private onCodeChange = (e: IMonacoModelContentChangeEvent) => { - this.props.editCell(this.getCell().id, e); - }; - - private onCodeCreated = (_code: string, _file: string, cellId: string, modelId: string) => { - this.props.codeCreated(cellId, modelId); - }; - - private hasOutput = () => { - return ( - this.getCell().state === CellState.finished || - this.getCell().state === CellState.error || - this.getCell().state === CellState.executing - ); - }; - - private getCodeCell = () => { - return this.props.cellVM.cell.data as nbformat.ICodeCell; - }; - - private shouldRenderResults(): boolean { - return ( - this.isCodeCell() && - this.hasOutput() && - this.getCodeCell().outputs && - this.getCodeCell().outputs.length > 0 && - !this.props.cellVM.hideOutput - ); - } - - private onKeyDown = (event: React.KeyboardEvent) => { - // Handle keydown events for the entire cell - if (this.getCell().id === Identifiers.EditCellId) { - const e: IKeyboardEvent = { - code: event.key, - shiftKey: event.shiftKey, - ctrlKey: event.ctrlKey, - metaKey: event.metaKey, - altKey: event.altKey, - target: event.target as HTMLDivElement, - stopPropagation: () => event.stopPropagation(), - preventDefault: () => event.preventDefault() - }; - this.onEditCellKeyDown(Identifiers.EditCellId, e); - } - }; - - private onKeyUp = (event: React.KeyboardEvent) => { - // Handle keydown events for the entire cell - if (this.getCell().id === Identifiers.EditCellId) { - const e: IKeyboardEvent = { - code: event.key, - shiftKey: event.shiftKey, - ctrlKey: event.ctrlKey, - metaKey: event.metaKey, - altKey: event.altKey, - target: event.target as HTMLDivElement, - stopPropagation: () => event.stopPropagation(), - preventDefault: () => event.preventDefault() - }; - this.onEditCellKeyUp(Identifiers.EditCellId, e); - } - }; - - private onEditCellKeyDown = (_cellId: string, e: IKeyboardEvent) => { - if (e.code === 'Enter' && e.shiftKey) { - this.editCellSubmit(e); - } else if (e.code === 'NumpadEnter' && e.shiftKey) { - this.editCellSubmit(e); - } else if (e.code === 'KeyU' && e.ctrlKey && e.editorInfo && !e.editorInfo.isSuggesting) { - e.editorInfo.clear(); - e.stopPropagation(); - e.preventDefault(); - } else if (e.code === 'Escape' && !e.shiftKey && e.editorInfo && !e.editorInfo.isSuggesting) { - e.editorInfo.clear(); - e.stopPropagation(); - e.preventDefault(); - } - }; - - private onEditCellKeyUp = (_cellId: string, e: IKeyboardEvent) => { - // Special case. Escape + Shift only comes as a key up because shift comes as the - // key down. - if (e.code === 'Escape' && e.shiftKey) { - this.editCellShiftEscape(e); - } - }; - - private editCellSubmit(e: IKeyboardEvent) { - if (e.editorInfo && e.editorInfo.contents) { - // Prevent shift+enter from turning into a enter - e.stopPropagation(); - e.preventDefault(); - - // Remove empty lines off the end - let endPos = e.editorInfo.contents.length - 1; - while (endPos >= 0 && e.editorInfo.contents[endPos] === '\n') { - endPos -= 1; - } - const content = e.editorInfo.contents.slice(0, endPos + 1); - - // Send to the input history too if necessary - if (this.inputHistory) { - this.inputHistory.add(content, e.editorInfo.isDirty); - } - - // Clear our editor - e.editorInfo.clear(); - - // Send to jupyter - this.props.submitInput(content, this.props.cellVM.cell.id); - } - } - - private findTabStop(direction: number, element: Element): HTMLElement | undefined { - if (element) { - const allFocusable = document.querySelectorAll('input, button, select, textarea, a[href]'); - if (allFocusable) { - const tabable = Array.prototype.filter.call(allFocusable, (i: HTMLElement) => i.tabIndex >= 0); - const self = tabable.indexOf(element); - return direction >= 0 ? tabable[self + 1] || tabable[0] : tabable[self - 1] || tabable[0]; - } - } - } - - private editCellShiftEscape = (e: IKeyboardEvent) => { - const focusedElement = document.activeElement; - if (focusedElement !== null) { - const nextTabStop = this.findTabStop(1, focusedElement); - if (nextTabStop) { - e.stopPropagation(); - e.preventDefault(); - nextTabStop.focus(); - } - } - }; - - private openLink = (uri: monacoEditor.Uri) => { - this.props.linkClick(uri.toString()); - }; -} - -// Main export, return a redux connected editor -export const InteractiveCellComponent = connect(null, actionCreators)(InteractiveCell); diff --git a/src/datascience-ui/history-react/interactivePanel.less b/src/datascience-ui/history-react/interactivePanel.less deleted file mode 100644 index 419fd1b5cfe9..000000000000 --- a/src/datascience-ui/history-react/interactivePanel.less +++ /dev/null @@ -1,74 +0,0 @@ -/* Import common styles and then override them below */ -@import '../interactive-common/common.css'; - -.toolbar-menu-bar-child { - background: var(--override-background, var(--vscode-editor-background)); - z-index: 10; -} - -#main-panel-content { - grid-area: content; - max-height: 100%; - overflow-x: hidden; - overflow-y: auto; -} - -.messages-result-container { - width: 100%; -} - -.messages-result-container pre { - white-space: pre-wrap; - font-family: monospace; - margin: 0px; - word-break: break-all; -} - -.cell-wrapper { - margin: 0px; - padding: 0px; - display: block; -} - -.cell-result-container { - margin: 0px; - display: grid; - grid-auto-columns: minmax(0, 1fr); -} - -.cell-outer { - display: grid; - grid-template-columns: auto minmax(0, 1fr) 8px; - grid-column-gap: 3px; - width: 100%; -} - -.cell-output { - margin: 0px; - width: 100%; - overflow-x: scroll; - background: transparent; -} - -.cell-output > div { - background: var(--override-widget-background, var(--vscode-notifications-background)); -} - -xmp { - margin: 0px; -} - -.cell-input { - margin: 0; -} - -.markdown-cell-output { - width: 100%; - overflow-x: scroll; -} - -.cell-output-text { - white-space: pre-wrap; - word-break: break-all; - overflow-x: hidden; -} diff --git a/src/datascience-ui/history-react/interactivePanel.tsx b/src/datascience-ui/history-react/interactivePanel.tsx deleted file mode 100644 index 8ceb1832ee47..000000000000 --- a/src/datascience-ui/history-react/interactivePanel.tsx +++ /dev/null @@ -1,436 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import * as React from 'react'; -import { connect } from 'react-redux'; -import { Identifiers } from '../../client/datascience/constants'; -import { buildSettingsCss } from '../interactive-common/buildSettingsCss'; -import { ContentPanel, IContentPanelProps } from '../interactive-common/contentPanel'; -import { handleLinkClick } from '../interactive-common/handlers'; -import { JupyterInfo } from '../interactive-common/jupyterInfo'; -import { ICellViewModel } from '../interactive-common/mainState'; -import { IMainWithVariables, IStore } from '../interactive-common/redux/store'; -import { IVariablePanelProps, VariablePanel } from '../interactive-common/variablePanel'; -import { ErrorBoundary } from '../react-common/errorBoundary'; -import { Image, ImageName } from '../react-common/image'; -import { ImageButton } from '../react-common/imageButton'; -import { getLocString } from '../react-common/locReactSide'; -import { Progress } from '../react-common/progress'; -import { InteractiveCellComponent } from './interactiveCell'; -import './interactivePanel.less'; -import { actionCreators } from './redux/actions'; - -// tslint:disable: no-suspicious-comment - -export type IInteractivePanelProps = IMainWithVariables & typeof actionCreators; - -function mapStateToProps(state: IStore): IMainWithVariables { - return { ...state.main, variableState: state.variables }; -} - -export class InteractivePanel extends React.Component { - private mainPanelRef: React.RefObject = React.createRef(); - private mainPanelToolbarRef: React.RefObject = React.createRef(); - private contentPanelRef: React.RefObject = React.createRef(); - private renderCount: number = 0; - private internalScrollCount: number = 0; - - constructor(props: IInteractivePanelProps) { - super(props); - } - - public componentDidMount() { - document.addEventListener('click', this.linkClick, true); - this.props.editorLoaded(); - } - - public componentWillUnmount() { - document.removeEventListener('click', this.linkClick); - this.props.editorUnmounted(); - } - - public render() { - const dynamicFont: React.CSSProperties = { - fontSize: this.props.font.size, - fontFamily: this.props.font.family - }; - - const progressBar = (this.props.busy || !this.props.loaded) && !this.props.testMode ? : undefined; - - // If in test mode, update our count. Use this to determine how many renders a normal update takes. - if (this.props.testMode) { - this.renderCount = this.renderCount + 1; - } - - return ( -
-
- -
-
- {this.renderToolbarPanel()} - {progressBar} -
-
- {this.renderVariablePanel(this.props.baseTheme)} -
-
- {this.renderContentPanel(this.props.baseTheme)} -
- -
- ); - } - - // Make the entire footer focus our input, instead of having to click directly on the monaco editor - private footerPanelClick = (_event: React.MouseEvent) => { - this.props.focusInput(); - }; - - // tslint:disable-next-line: max-func-body-length - private renderToolbarPanel() { - const variableExplorerTooltip = this.props.variableState.visible - ? getLocString('DataScience.collapseVariableExplorerTooltip', 'Hide variables active in jupyter kernel') - : getLocString('DataScience.expandVariableExplorerTooltip', 'Show variables active in jupyter kernel'); - - return ( -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- {this.renderKernelSelection()} -
-
- ); - } - - private renderKernelSelection() { - if (this.props.kernel.serverName === getLocString('DataScience.localJupyterServer', 'local')) { - return; - } - - return ( - - ); - } - - private renderVariablePanel(baseTheme: string) { - if (this.props.variableState.visible) { - const variableProps = this.getVariableProps(baseTheme); - return ; - } - - return null; - } - - private renderContentPanel(baseTheme: string) { - // Skip if the tokenizer isn't finished yet. It needs - // to finish loading so our code editors work. - if (!this.props.monacoReady && !this.props.testMode) { - return null; - } - - // Otherwise render our cells. - const contentProps = this.getContentProps(baseTheme); - return ; - } - - private renderFooterPanel(baseTheme: string) { - // Skip if the tokenizer isn't finished yet. It needs - // to finish loading so our code editors work. - if ( - !this.props.monacoReady || - !this.props.editCellVM || - !this.props.settings || - !this.props.editorOptions || - !this.props.settings.allowInput - ) { - return null; - } - - const executionCount = this.getInputExecutionCount(); - const editPanelClass = this.props.settings.colorizeInputBox ? 'edit-panel-colorized' : 'edit-panel'; - - return ( -
- - - -
- ); - } - - private getInputExecutionCount = (): number => { - return this.props.currentExecutionCount + 1; - }; - - private getContentProps = (baseTheme: string): IContentPanelProps => { - return { - baseTheme: baseTheme, - cellVMs: this.props.cellVMs, - testMode: this.props.testMode, - codeTheme: this.props.codeTheme, - submittedText: this.props.submittedText, - settings: this.props.settings, - skipNextScroll: this.props.skipNextScroll ? true : false, - editable: false, - renderCell: this.renderCell, - scrollToBottom: this.scrollDiv, - scrollBeyondLastLine: this.props.settings - ? this.props.settings.extraSettings.editor.scrollBeyondLastLine - : false - }; - }; - private getVariableProps = (baseTheme: string): IVariablePanelProps => { - let toolbarHeight = 0; - if (this.mainPanelToolbarRef.current) { - toolbarHeight = this.mainPanelToolbarRef.current.offsetHeight; - } - return { - gridHeight: this.props.variableState.gridHeight, - containerHeight: this.props.variableState.containerHeight, - variables: this.props.variableState.variables, - debugging: this.props.debugging, - busy: this.props.busy, - showDataExplorer: this.props.showDataViewer, - skipDefault: this.props.skipDefault, - testMode: this.props.testMode, - closeVariableExplorer: this.props.toggleVariableExplorer, - setVariableExplorerHeight: this.props.setVariableExplorerHeight, - baseTheme: baseTheme, - pageIn: this.pageInVariableData, - fontSize: this.props.font.size, - executionCount: this.props.currentExecutionCount, - refreshCount: this.props.variableState.refreshCount, - offsetHeight: toolbarHeight, - supportsDebugging: - this.props.settings && this.props.settings.variableOptions - ? this.props.settings.variableOptions.enableDuringDebugger - : false - }; - }; - - private pageInVariableData = (startIndex: number, pageSize: number) => { - this.props.getVariableData( - this.props.currentExecutionCount, - this.props.variableState.refreshCount, - startIndex, - pageSize - ); - }; - - private renderCell = ( - cellVM: ICellViewModel, - _index: number, - containerRef?: React.RefObject - ): JSX.Element | null => { - // Note: MaxOutputSize and enableScrollingForCellOutputs is being ignored on purpose for - // the interactive window. See bug: https://github.com/microsoft/vscode-python/issues/11421 - if (this.props.settings && this.props.editorOptions) { - // Disable hover for collapsed code blocks - const options = { ...this.props.editorOptions, hover: { enabled: cellVM.inputBlockOpen } }; - return ( -
- - - -
- ); - } else { - return null; - } - }; - - // This handles the scrolling. Its called from the props of contentPanel. - // We only scroll when the state indicates we are at the bottom of the interactive window, - // otherwise it sometimes scrolls when the user wasn't at the bottom. - private scrollDiv = (div: HTMLDivElement) => { - if (this.props.isAtBottom) { - this.internalScrollCount += 1; - // Force auto here as smooth scrolling can be canceled by updates to the window - // from elsewhere (and keeping track of these would make this hard to maintain) - if (div && div.scrollIntoView) { - div.scrollIntoView({ behavior: 'auto', block: 'nearest', inline: 'nearest' }); - } - } - }; - - private handleScroll = (e: React.UIEvent) => { - if (this.internalScrollCount > 0) { - this.internalScrollCount -= 1; - } else if (this.contentPanelRef.current) { - const isAtBottom = this.props.settings?.alwaysScrollOnNewCell - ? true - : this.contentPanelRef.current.computeIsAtBottom(e.currentTarget); - this.props.scroll(isAtBottom); - } - }; - - private linkClick = (ev: MouseEvent) => { - handleLinkClick(ev, this.props.linkClick); - }; -} - -// Main export, return a redux connected editor -export function getConnectedInteractiveEditor() { - return connect(mapStateToProps, actionCreators)(InteractivePanel); -} diff --git a/src/datascience-ui/history-react/redux/actions.ts b/src/datascience-ui/history-react/redux/actions.ts deleted file mode 100644 index 4a8083675c1c..000000000000 --- a/src/datascience-ui/history-react/redux/actions.ts +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import { - IInteractiveWindowMapping, - InteractiveWindowMessages -} from '../../../client/datascience/interactive-common/interactiveWindowTypes'; -import { IJupyterVariable, IJupyterVariablesRequest } from '../../../client/datascience/types'; -import { - CommonAction, - CommonActionType, - CommonActionTypeMapping, - ICellAction, - ICodeAction, - ICodeCreatedAction, - IEditCellAction, - ILinkClickAction, - IOpenSettingsAction, - IScrollAction, - IShowDataViewerAction, - IVariableExplorerHeight -} from '../../interactive-common/redux/reducers/types'; -import { IMonacoModelContentChangeEvent } from '../../react-common/monacoHelpers'; - -// This function isn't made common and not exported, to ensure it isn't used elsewhere. -function createIncomingActionWithPayload< - M extends IInteractiveWindowMapping & CommonActionTypeMapping, - K extends keyof M ->(type: K, data: M[K]): CommonAction { - // tslint:disable-next-line: no-any - return { type, payload: { data, messageDirection: 'incoming' } as any } as any; -} -// This function isn't made common and not exported, to ensure it isn't used elsewhere. -function createIncomingAction(type: CommonActionType | InteractiveWindowMessages): CommonAction { - return { type, payload: { messageDirection: 'incoming', data: undefined } }; -} - -// See https://react-redux.js.org/using-react-redux/connect-mapdispatch#defining-mapdispatchtoprops-as-an-object -export const actionCreators = { - focusInput: (): CommonAction => createIncomingAction(CommonActionType.FOCUS_INPUT), - restartKernel: (): CommonAction => createIncomingAction(CommonActionType.RESTART_KERNEL), - interruptKernel: (): CommonAction => createIncomingAction(CommonActionType.INTERRUPT_KERNEL), - deleteAllCells: (): CommonAction => createIncomingAction(InteractiveWindowMessages.DeleteAllCells), - deleteCell: (cellId: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.DELETE_CELL, { cellId }), - undo: (): CommonAction => createIncomingAction(InteractiveWindowMessages.Undo), - redo: (): CommonAction => createIncomingAction(InteractiveWindowMessages.Redo), - linkClick: (href: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.LINK_CLICK, { href }), - showPlot: (imageHtml: string) => createIncomingActionWithPayload(InteractiveWindowMessages.ShowPlot, imageHtml), - toggleInputBlock: (cellId: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.TOGGLE_INPUT_BLOCK, { cellId }), - gotoCell: (cellId: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.GOTO_CELL, { cellId }), - copyCellCode: (cellId: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.COPY_CELL_CODE, { cellId }), - gatherCell: (cellId: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.GATHER_CELL, { cellId }), - gatherCellToScript: (cellId: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.GATHER_CELL_TO_SCRIPT, { cellId }), - clickCell: (cellId: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.CLICK_CELL, { cellId }), - editCell: (cellId: string, e: IMonacoModelContentChangeEvent): CommonAction => - createIncomingActionWithPayload(CommonActionType.EDIT_CELL, { - cellId, - version: e.versionId, - modelId: e.model.id, - forward: e.forward, - reverse: e.reverse, - id: cellId, - code: e.model.getValue() - }), - submitInput: (code: string, cellId: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.SUBMIT_INPUT, { code, cellId }), - toggleVariableExplorer: (): CommonAction => createIncomingAction(CommonActionType.TOGGLE_VARIABLE_EXPLORER), - setVariableExplorerHeight: (containerHeight: number, gridHeight: number): CommonAction => - createIncomingActionWithPayload(CommonActionType.SET_VARIABLE_EXPLORER_HEIGHT, { containerHeight, gridHeight }), - expandAll: (): CommonAction => createIncomingAction(InteractiveWindowMessages.ExpandAll), - collapseAll: (): CommonAction => createIncomingAction(InteractiveWindowMessages.CollapseAll), - export: (): CommonAction => createIncomingAction(CommonActionType.EXPORT), - exportAs: (): CommonAction => createIncomingAction(CommonActionType.EXPORT_NOTEBOOK_AS), - showDataViewer: (variable: IJupyterVariable, columnSize: number): CommonAction => - createIncomingActionWithPayload(CommonActionType.SHOW_DATA_VIEWER, { variable, columnSize }), - editorLoaded: (): CommonAction => createIncomingAction(CommonActionType.EDITOR_LOADED), - scroll: (isAtBottom: boolean): CommonAction => - createIncomingActionWithPayload(CommonActionType.SCROLL, { isAtBottom }), - unfocus: (cellId: string | undefined): CommonAction => - createIncomingActionWithPayload(CommonActionType.UNFOCUS_CELL, { cellId }), - codeCreated: (cellId: string | undefined, modelId: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.CODE_CREATED, { cellId, modelId }), - editorUnmounted: (): CommonAction => createIncomingAction(CommonActionType.UNMOUNT), - selectKernel: (): CommonAction => createIncomingAction(InteractiveWindowMessages.SelectKernel), - selectServer: (): CommonAction => createIncomingAction(CommonActionType.SELECT_SERVER), - openSettings: (setting?: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.OPEN_SETTINGS, { setting }), - getVariableData: ( - newExecutionCount: number, - refreshCount: number, - startIndex: number = 0, - pageSize: number = 100 - ): CommonAction => - createIncomingActionWithPayload(CommonActionType.GET_VARIABLE_DATA, { - executionCount: newExecutionCount, - sortColumn: 'name', - sortAscending: true, - startIndex, - pageSize, - refreshCount - }), - widgetFailed: (ex: Error): CommonAction => - createIncomingActionWithPayload(CommonActionType.IPYWIDGET_RENDER_FAILURE, ex) -}; diff --git a/src/datascience-ui/history-react/redux/mapping.ts b/src/datascience-ui/history-react/redux/mapping.ts deleted file mode 100644 index 92002dfe409b..000000000000 --- a/src/datascience-ui/history-react/redux/mapping.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { - IInteractiveWindowMapping, - InteractiveWindowMessages -} from '../../../client/datascience/interactive-common/interactiveWindowTypes'; -import { BaseReduxActionPayload } from '../../../client/datascience/interactive-common/types'; -import { IMainState } from '../../interactive-common/mainState'; -import { CommonActionType, CommonActionTypeMapping } from '../../interactive-common/redux/reducers/types'; -import { ReducerArg, ReducerFunc } from '../../react-common/reduxUtils'; - -export type InteractiveReducerFunc = ReducerFunc< - IMainState, - CommonActionType | InteractiveWindowMessages, - BaseReduxActionPayload ->; - -export type InteractiveReducerArg = ReducerArg< - IMainState, - CommonActionType | InteractiveWindowMessages, - BaseReduxActionPayload ->; - -type InteractiveWindowReducerFunctions = { - [P in keyof T]: T[P] extends never | undefined ? InteractiveReducerFunc : InteractiveReducerFunc; -}; - -export type IInteractiveActionMapping = InteractiveWindowReducerFunctions & - InteractiveWindowReducerFunctions; diff --git a/src/datascience-ui/history-react/redux/reducers/creation.ts b/src/datascience-ui/history-react/redux/reducers/creation.ts deleted file mode 100644 index 5d309b6731c8..000000000000 --- a/src/datascience-ui/history-react/redux/reducers/creation.ts +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { Identifiers } from '../../../../client/datascience/constants'; -import { - IFinishCell, - InteractiveWindowMessages -} from '../../../../client/datascience/interactive-common/interactiveWindowTypes'; -import { ICell, IDataScienceExtraSettings } from '../../../../client/datascience/types'; -import { removeLinesFromFrontAndBack } from '../../../common'; -import { createCellVM, extractInputText, ICellViewModel, IMainState } from '../../../interactive-common/mainState'; -import { postActionToExtension } from '../../../interactive-common/redux/helpers'; -import { Helpers } from '../../../interactive-common/redux/reducers/helpers'; -import { IAddCellAction, ICellAction } from '../../../interactive-common/redux/reducers/types'; -import { InteractiveReducerArg } from '../mapping'; - -export namespace Creation { - function isCellSupported(state: IMainState, cell: ICell): boolean { - // Skip message cells in test mode - if (state.testMode) { - return cell.data.cell_type !== 'messages'; - } - return true; - } - - function extractInputBlockText(cellVM: ICellViewModel, settings?: IDataScienceExtraSettings) { - // Use the base function first - const text = extractInputText(cellVM, settings); - - // Then remove text on the front and back. We only do this for the interactive window - return removeLinesFromFrontAndBack(text); - } - - export function alterCellVM( - cellVM: ICellViewModel, - settings?: IDataScienceExtraSettings, - visible?: boolean, - expanded?: boolean - ): ICellViewModel { - if (cellVM.cell.data.cell_type === 'code') { - // If we are already in the correct state, return back our initial cell vm - if (cellVM.inputBlockShow === visible && cellVM.inputBlockOpen === expanded) { - return cellVM; - } - - const newCellVM = { ...cellVM }; - if (cellVM.inputBlockShow !== visible) { - if (visible) { - // Show the cell, the rest of the function will add on correct collapse state - newCellVM.inputBlockShow = true; - } else { - // Hide this cell - newCellVM.inputBlockShow = false; - } - } - - // No elseif as we want newly visible cells to pick up the correct expand / collapse state - if (cellVM.inputBlockOpen !== expanded && cellVM.inputBlockCollapseNeeded && cellVM.inputBlockShow) { - let newText = extractInputBlockText(cellVM, settings); - - // While extracting the text, we might eliminate all extra lines - if (newText.includes('\n')) { - if (expanded) { - // Expand the cell - newCellVM.inputBlockOpen = true; - newCellVM.inputBlockText = newText; - } else { - // Collapse the cell - if (newText.length > 0) { - newText = newText.split('\n', 1)[0]; - newText = newText.slice(0, 255); // Slice to limit length, slicing past length is fine - newText = newText.concat('...'); - } - - newCellVM.inputBlockOpen = false; - newCellVM.inputBlockText = newText; - } - } else { - // If all lines eliminated, get rid of the collapse bar. - newCellVM.inputBlockCollapseNeeded = false; - newCellVM.inputBlockOpen = true; - newCellVM.inputBlockText = newText; - } - } - - return newCellVM; - } - - return cellVM; - } - - export function prepareCellVM(cell: ICell, mainState: IMainState): ICellViewModel { - let cellVM: ICellViewModel = createCellVM(cell, mainState.settings, false, mainState.debugging); - - const visible = mainState.settings ? mainState.settings.showCellInputCode : false; - const expanded = !mainState.settings?.collapseCellInputCodeByDefault; - - // Set initial cell visibility and collapse - cellVM = alterCellVM(cellVM, mainState.settings, visible, expanded); - cellVM.hasBeenRun = true; - - return cellVM; - } - - export function startCell(arg: InteractiveReducerArg): IMainState { - if (isCellSupported(arg.prevState, arg.payload.data)) { - const result = Helpers.updateOrAdd(arg, prepareCellVM); - if ( - result.cellVMs.length > arg.prevState.cellVMs.length && - arg.payload.data.id !== Identifiers.EditCellId - ) { - const cellVM = result.cellVMs[result.cellVMs.length - 1]; - - // We're adding a new cell here. Tell the intellisense engine we have a new cell - postActionToExtension(arg, InteractiveWindowMessages.UpdateModel, { - source: 'user', - kind: 'add', - oldDirty: arg.prevState.dirty, - newDirty: true, - cell: cellVM.cell, - fullText: extractInputText(cellVM, result.settings), - currentText: cellVM.inputBlockText - }); - } - - return result; - } - return arg.prevState; - } - - export function updateCell(arg: InteractiveReducerArg): IMainState { - if (isCellSupported(arg.prevState, arg.payload.data)) { - return Helpers.updateOrAdd(arg, prepareCellVM); - } - return arg.prevState; - } - - export function finishCell(arg: InteractiveReducerArg): IMainState { - if (isCellSupported(arg.prevState, arg.payload.data.cell)) { - return Helpers.updateOrAdd( - { ...arg, payload: { ...arg.payload, data: arg.payload.data.cell } }, - prepareCellVM - ); - } - return arg.prevState; - } - - export function deleteAllCells(arg: InteractiveReducerArg): IMainState { - // Send messages to other side to indicate the deletes - postActionToExtension(arg, InteractiveWindowMessages.DeleteAllCells); - - return { - ...arg.prevState, - cellVMs: [], - undoStack: Helpers.pushStack(arg.prevState.undoStack, arg.prevState.cellVMs) - }; - } - - export function deleteCell(arg: InteractiveReducerArg): IMainState { - const index = arg.prevState.cellVMs.findIndex((c) => c.cell.id === arg.payload.data.cellId); - if (index >= 0 && arg.payload.data.cellId) { - // Send messages to other side to indicate the delete - postActionToExtension(arg, InteractiveWindowMessages.UpdateModel, { - source: 'user', - kind: 'remove', - index, - oldDirty: arg.prevState.dirty, - newDirty: true, - cell: arg.prevState.cellVMs[index].cell - }); - - const newVMs = arg.prevState.cellVMs.filter((_c, i) => i !== index); - return { - ...arg.prevState, - cellVMs: newVMs, - undoStack: Helpers.pushStack(arg.prevState.undoStack, arg.prevState.cellVMs) - }; - } - - return arg.prevState; - } - - export function unmount(arg: InteractiveReducerArg): IMainState { - return { - ...arg.prevState, - cellVMs: [], - undoStack: [], - redoStack: [], - editCellVM: undefined - }; - } - - export function loaded(arg: InteractiveReducerArg<{ cells: ICell[] }>): IMainState { - postActionToExtension(arg, InteractiveWindowMessages.LoadAllCellsComplete, { - cells: [] - }); - return { - ...arg.prevState, - loaded: true, - busy: false - }; - } -} diff --git a/src/datascience-ui/history-react/redux/reducers/effects.ts b/src/datascience-ui/history-react/redux/reducers/effects.ts deleted file mode 100644 index 83cf3d14104d..000000000000 --- a/src/datascience-ui/history-react/redux/reducers/effects.ts +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { Identifiers } from '../../../../client/datascience/constants'; -import { IScrollToCell } from '../../../../client/datascience/interactive-common/interactiveWindowTypes'; -import { CssMessages } from '../../../../client/datascience/messages'; -import { IDataScienceExtraSettings } from '../../../../client/datascience/types'; -import { IMainState } from '../../../interactive-common/mainState'; -import { postActionToExtension } from '../../../interactive-common/redux/helpers'; -import { Helpers } from '../../../interactive-common/redux/reducers/helpers'; -import { ICellAction, IScrollAction } from '../../../interactive-common/redux/reducers/types'; -import { computeEditorOptions } from '../../../react-common/settingsReactSide'; -import { InteractiveReducerArg } from '../mapping'; -import { Creation } from './creation'; - -export namespace Effects { - export function expandAll(arg: InteractiveReducerArg): IMainState { - if (arg.prevState.settings?.showCellInputCode) { - const newVMs = arg.prevState.cellVMs.map((c) => - Creation.alterCellVM({ ...c }, arg.prevState.settings, true, true) - ); - return { - ...arg.prevState, - cellVMs: newVMs - }; - } - return arg.prevState; - } - - export function collapseAll(arg: InteractiveReducerArg): IMainState { - if (arg.prevState.settings?.showCellInputCode) { - const newVMs = arg.prevState.cellVMs.map((c) => - Creation.alterCellVM({ ...c }, arg.prevState.settings, true, false) - ); - return { - ...arg.prevState, - cellVMs: newVMs - }; - } - return arg.prevState; - } - - export function toggleInputBlock(arg: InteractiveReducerArg): IMainState { - if (arg.payload.data.cellId) { - const newVMs = [...arg.prevState.cellVMs]; - const index = arg.prevState.cellVMs.findIndex((c) => c.cell.id === arg.payload.data.cellId); - const oldVM = arg.prevState.cellVMs[index]; - newVMs[index] = Creation.alterCellVM({ ...oldVM }, arg.prevState.settings, true, !oldVM.inputBlockOpen); - return { - ...arg.prevState, - cellVMs: newVMs - }; - } - return arg.prevState; - } - - export function updateSettings(arg: InteractiveReducerArg): IMainState { - // String arg should be the IDataScienceExtraSettings - const newSettingsJSON = JSON.parse(arg.payload.data); - const newSettings = newSettingsJSON; - const newEditorOptions = computeEditorOptions(newSettings); - const newFontFamily = newSettings.extraSettings - ? newSettings.extraSettings.editor.fontFamily - : arg.prevState.font.family; - const newFontSize = newSettings.extraSettings - ? newSettings.extraSettings.editor.fontSize - : arg.prevState.font.size; - - // Ask for new theme data if necessary - if ( - newSettings && - newSettings.extraSettings && - newSettings.extraSettings.theme !== arg.prevState.vscodeThemeName - ) { - const knownDark = Helpers.computeKnownDark(newSettings); - // User changed the current theme. Rerender - postActionToExtension(arg, CssMessages.GetCssRequest, { isDark: knownDark }); - postActionToExtension(arg, CssMessages.GetMonacoThemeRequest, { isDark: knownDark }); - } - - // Update our input cell state if the user changed this setting - let newVMs = arg.prevState.cellVMs; - if (newSettings.showCellInputCode !== arg.prevState.settings?.showCellInputCode) { - newVMs = arg.prevState.cellVMs.map((c) => - Creation.alterCellVM( - c, - newSettings, - newSettings.showCellInputCode, - !newSettings.collapseCellInputCodeByDefault - ) - ); - } - - return { - ...arg.prevState, - cellVMs: newVMs, - settings: newSettings, - editorOptions: newEditorOptions, - font: { - size: newFontSize, - family: newFontFamily - } - }; - } - - export function scrollToCell(arg: InteractiveReducerArg): IMainState { - // Up the scroll count on the necessary cell - const index = arg.prevState.cellVMs.findIndex((c) => c.cell.id === arg.payload.data.id); - if (index >= 0) { - const newVMs = [...arg.prevState.cellVMs]; - - // Scroll one cell and unscroll another. - newVMs[index] = { ...newVMs[index], scrollCount: newVMs[index].scrollCount + 1 }; - return { - ...arg.prevState, - cellVMs: newVMs - }; - } - - return arg.prevState; - } - - export function scrolled(arg: InteractiveReducerArg): IMainState { - return { - ...arg.prevState, - isAtBottom: arg.payload.data.isAtBottom - }; - } - - export function clickCell(arg: InteractiveReducerArg): IMainState { - if ( - arg.payload.data.cellId === Identifiers.EditCellId && - arg.prevState.editCellVM && - !arg.prevState.editCellVM.focused - ) { - return { - ...arg.prevState, - editCellVM: { - ...arg.prevState.editCellVM, - focused: true - } - }; - } else if (arg.prevState.editCellVM) { - return { - ...arg.prevState, - editCellVM: { - ...arg.prevState.editCellVM, - focused: false - } - }; - } - - return arg.prevState; - } - - export function unfocusCell(arg: InteractiveReducerArg): IMainState { - if ( - arg.payload.data.cellId === Identifiers.EditCellId && - arg.prevState.editCellVM && - arg.prevState.editCellVM.focused - ) { - return { - ...arg.prevState, - editCellVM: { - ...arg.prevState.editCellVM, - focused: false - } - }; - } - - return arg.prevState; - } -} diff --git a/src/datascience-ui/history-react/redux/reducers/execution.ts b/src/datascience-ui/history-react/redux/reducers/execution.ts deleted file mode 100644 index 1605cc79f8c5..000000000000 --- a/src/datascience-ui/history-react/redux/reducers/execution.ts +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -// tslint:disable-next-line: no-require-imports no-var-requires -const cloneDeep = require('lodash/cloneDeep'); -import * as uuid from 'uuid/v4'; - -import { CellMatcher } from '../../../../client/datascience/cellMatcher'; -import { InteractiveWindowMessages } from '../../../../client/datascience/interactive-common/interactiveWindowTypes'; -import { CellState } from '../../../../client/datascience/types'; -import { generateMarkdownFromCodeLines } from '../../../common'; -import { createCellFrom } from '../../../common/cellFactory'; -import { createCellVM, IMainState } from '../../../interactive-common/mainState'; -import { postActionToExtension } from '../../../interactive-common/redux/helpers'; -import { Helpers } from '../../../interactive-common/redux/reducers/helpers'; -import { ICodeAction } from '../../../interactive-common/redux/reducers/types'; -import { InteractiveReducerArg } from '../mapping'; -import { Creation } from './creation'; - -export namespace Execution { - export function undo(arg: InteractiveReducerArg): IMainState { - if (arg.prevState.undoStack.length > 0) { - // Pop one off of our undo stack and update our redo - const cells = arg.prevState.undoStack[arg.prevState.undoStack.length - 1]; - const undoStack = arg.prevState.undoStack.slice(0, arg.prevState.undoStack.length - 1); - const redoStack = Helpers.pushStack(arg.prevState.redoStack, arg.prevState.cellVMs); - postActionToExtension(arg, InteractiveWindowMessages.Undo); - return { - ...arg.prevState, - cellVMs: cells, - undoStack: undoStack, - redoStack: redoStack, - skipNextScroll: true - }; - } - - return arg.prevState; - } - - export function redo(arg: InteractiveReducerArg): IMainState { - if (arg.prevState.redoStack.length > 0) { - // Pop one off of our redo stack and update our undo - const cells = arg.prevState.redoStack[arg.prevState.redoStack.length - 1]; - const redoStack = arg.prevState.redoStack.slice(0, arg.prevState.redoStack.length - 1); - const undoStack = Helpers.pushStack(arg.prevState.undoStack, arg.prevState.cellVMs); - postActionToExtension(arg, InteractiveWindowMessages.Redo); - return { - ...arg.prevState, - cellVMs: cells, - undoStack: undoStack, - redoStack: redoStack, - skipNextScroll: true - }; - } - - return arg.prevState; - } - - export function startDebugging(arg: InteractiveReducerArg): IMainState { - return { - ...arg.prevState, - debugging: true - }; - } - - export function stopDebugging(arg: InteractiveReducerArg): IMainState { - return { - ...arg.prevState, - debugging: false - }; - } - - export function submitInput(arg: InteractiveReducerArg): IMainState { - // noop if the submitted code is just a cell marker - const matcher = new CellMatcher(arg.prevState.settings); - if (matcher.stripFirstMarker(arg.payload.data.code).length > 0 && arg.prevState.editCellVM) { - // This should be from the edit cell VM. Copy it and change the cell id - let newCell = cloneDeep(arg.prevState.editCellVM); - - // Change this editable cell to not editable. - newCell.cell.state = CellState.executing; - newCell.cell.data.source = arg.payload.data.code; - - // Change type to markdown if necessary - const split = arg.payload.data.code.splitLines({ trim: false }); - const firstLine = split[0]; - if (matcher.isMarkdown(firstLine)) { - newCell.cell.data = createCellFrom(newCell.cell.data, 'markdown'); - newCell.cell.data.source = generateMarkdownFromCodeLines(split); - newCell.cell.state = CellState.finished; - } else if (newCell.cell.data.cell_type === 'markdown') { - newCell.cell.state = CellState.finished; - } - - // Update input controls (always show expanded since we just edited it.) - newCell = createCellVM(newCell.cell, arg.prevState.settings, false, false); - const collapseInputs = arg.prevState.settings - ? arg.prevState.settings.collapseCellInputCodeByDefault - : false; - newCell = Creation.alterCellVM(newCell, arg.prevState.settings, true, !collapseInputs); - newCell.useQuickEdit = false; - - // Generate a new id - newCell.cell.id = uuid(); - - // Indicate this is direct input so that we don't hide it if the user has - // hide all inputs turned on. - newCell.directInput = true; - - // Send a message to execute this code if necessary. - if (newCell.cell.state !== CellState.finished) { - postActionToExtension(arg, InteractiveWindowMessages.SubmitNewCell, { - code: arg.payload.data.code, - id: newCell.cell.id - }); - } - - // Stick in a new cell at the bottom that's editable and update our state - // so that the last cell becomes busy - return { - ...arg.prevState, - cellVMs: [...arg.prevState.cellVMs, newCell], - undoStack: Helpers.pushStack(arg.prevState.undoStack, arg.prevState.cellVMs), - skipNextScroll: false, - submittedText: true - }; - } - return arg.prevState; - } -} diff --git a/src/datascience-ui/history-react/redux/reducers/index.ts b/src/datascience-ui/history-react/redux/reducers/index.ts deleted file mode 100644 index 4cf821b85bd0..000000000000 --- a/src/datascience-ui/history-react/redux/reducers/index.ts +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { InteractiveWindowMessages } from '../../../../client/datascience/interactive-common/interactiveWindowTypes'; -import { CssMessages, SharedMessages } from '../../../../client/datascience/messages'; -import { CommonEffects } from '../../../interactive-common/redux/reducers/commonEffects'; -import { Kernel } from '../../../interactive-common/redux/reducers/kernel'; -import { Transfer } from '../../../interactive-common/redux/reducers/transfer'; -import { CommonActionType } from '../../../interactive-common/redux/reducers/types'; -import { IInteractiveActionMapping } from '../mapping'; -import { Creation } from './creation'; -import { Effects } from './effects'; -import { Execution } from './execution'; - -// The list of reducers. 1 per message/action. -export const reducerMap: Partial = { - // State updates - [CommonActionType.RESTART_KERNEL]: Kernel.restartKernel, - [CommonActionType.INTERRUPT_KERNEL]: Kernel.interruptKernel, - [InteractiveWindowMessages.SelectKernel]: Kernel.selectKernel, - [CommonActionType.SELECT_SERVER]: Kernel.selectJupyterURI, - [CommonActionType.OPEN_SETTINGS]: CommonEffects.openSettings, - [CommonActionType.EXPORT]: Transfer.exportCells, - [CommonActionType.EXPORT_NOTEBOOK_AS]: Transfer.showExportAsMenu, - [CommonActionType.SAVE]: Transfer.save, - [CommonActionType.SHOW_DATA_VIEWER]: Transfer.showDataViewer, - [CommonActionType.DELETE_CELL]: Creation.deleteCell, - [InteractiveWindowMessages.ShowPlot]: Transfer.showPlot, - [CommonActionType.LINK_CLICK]: Transfer.linkClick, - [CommonActionType.GOTO_CELL]: Transfer.gotoCell, - [CommonActionType.TOGGLE_INPUT_BLOCK]: Effects.toggleInputBlock, - [CommonActionType.COPY_CELL_CODE]: Transfer.copyCellCode, - [CommonActionType.GATHER_CELL]: Transfer.gather, - [CommonActionType.GATHER_CELL_TO_SCRIPT]: Transfer.gatherToScript, - [CommonActionType.EDIT_CELL]: Transfer.editCell, - [CommonActionType.SUBMIT_INPUT]: Execution.submitInput, - [InteractiveWindowMessages.ExpandAll]: Effects.expandAll, - [CommonActionType.EDITOR_LOADED]: Transfer.started, - [InteractiveWindowMessages.LoadAllCells]: Creation.loaded, - [CommonActionType.SCROLL]: Effects.scrolled, - [CommonActionType.CLICK_CELL]: Effects.clickCell, - [CommonActionType.UNFOCUS_CELL]: Effects.unfocusCell, - [CommonActionType.UNMOUNT]: Creation.unmount, - [CommonActionType.FOCUS_INPUT]: CommonEffects.focusInput, - [CommonActionType.LOAD_IPYWIDGET_CLASS_SUCCESS]: CommonEffects.handleLoadIPyWidgetClassSuccess, - [CommonActionType.LOAD_IPYWIDGET_CLASS_FAILURE]: CommonEffects.handleLoadIPyWidgetClassFailure, - [CommonActionType.IPYWIDGET_WIDGET_VERSION_NOT_SUPPORTED]: CommonEffects.notifyAboutUnsupportedWidgetVersions, - [CommonActionType.IPYWIDGET_RENDER_FAILURE]: CommonEffects.handleIPyWidgetRenderFailure, - - // Messages from the webview (some are ignored) - [InteractiveWindowMessages.Undo]: Execution.undo, - [InteractiveWindowMessages.Redo]: Execution.redo, - [InteractiveWindowMessages.StartCell]: Creation.startCell, - [InteractiveWindowMessages.FinishCell]: Creation.finishCell, - [InteractiveWindowMessages.UpdateCellWithExecutionResults]: Creation.updateCell, - [InteractiveWindowMessages.Activate]: CommonEffects.activate, - [InteractiveWindowMessages.RestartKernel]: Kernel.handleRestarted, - [CssMessages.GetCssResponse]: CommonEffects.handleCss, - [InteractiveWindowMessages.MonacoReady]: CommonEffects.monacoReady, - [CssMessages.GetMonacoThemeResponse]: CommonEffects.monacoThemeChange, - [InteractiveWindowMessages.GetAllCells]: Transfer.getAllCells, - [InteractiveWindowMessages.ExpandAll]: Effects.expandAll, - [InteractiveWindowMessages.CollapseAll]: Effects.collapseAll, - [InteractiveWindowMessages.DeleteAllCells]: Creation.deleteAllCells, - [InteractiveWindowMessages.StartProgress]: CommonEffects.startProgress, - [InteractiveWindowMessages.StopProgress]: CommonEffects.stopProgress, - [SharedMessages.UpdateSettings]: Effects.updateSettings, - [InteractiveWindowMessages.StartDebugging]: Execution.startDebugging, - [InteractiveWindowMessages.StopDebugging]: Execution.stopDebugging, - [InteractiveWindowMessages.ScrollToCell]: Effects.scrollToCell, - [InteractiveWindowMessages.UpdateKernel]: Kernel.updateStatus, - [SharedMessages.LocInit]: CommonEffects.handleLocInit, - [InteractiveWindowMessages.UpdateDisplayData]: CommonEffects.handleUpdateDisplayData, - [InteractiveWindowMessages.HasCell]: Transfer.hasCell, - [InteractiveWindowMessages.Gathering]: Transfer.gathering -}; diff --git a/src/datascience-ui/history-react/redux/store.ts b/src/datascience-ui/history-react/redux/store.ts deleted file mode 100644 index c9b6551d26e5..000000000000 --- a/src/datascience-ui/history-react/redux/store.ts +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import * as ReduxCommon from '../../interactive-common/redux/store'; -import { PostOffice } from '../../react-common/postOffice'; -import { reducerMap } from './reducers'; - -// This special version uses the reducer map from the IInteractiveWindowMapping -export function createStore(skipDefault: boolean, baseTheme: string, testMode: boolean, postOffice: PostOffice) { - return ReduxCommon.createStore(skipDefault, baseTheme, testMode, false, false, reducerMap, postOffice); -} diff --git a/src/datascience-ui/interactive-common/buildSettingsCss.ts b/src/datascience-ui/interactive-common/buildSettingsCss.ts deleted file mode 100644 index 68a2eaa0e89e..000000000000 --- a/src/datascience-ui/interactive-common/buildSettingsCss.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import { IDataScienceExtraSettings } from '../../client/datascience/types'; - -// From a set of data science settings build up any settings that need to be -// inserted into our StyleSetter divs some things like pseudo elements -// can't be put into inline styles -export function buildSettingsCss(settings: IDataScienceExtraSettings | undefined): string { - return settings - ? `#main-panel-content::-webkit-scrollbar { - width: ${settings.extraSettings.editor.verticalScrollbarSize}px; -} - -.cell-output::-webkit-scrollbar { - height: ${settings.extraSettings.editor.horizontalScrollbarSize}px; -} - -.cell-output > *::-webkit-scrollbar { - width: ${settings.extraSettings.editor.verticalScrollbarSize}px; -}` - : ''; -} diff --git a/src/datascience-ui/interactive-common/cellInput.tsx b/src/datascience-ui/interactive-common/cellInput.tsx deleted file mode 100644 index 89cb3c2f1645..000000000000 --- a/src/datascience-ui/interactive-common/cellInput.tsx +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../client/common/extensions'; - -import { nbformat } from '@jupyterlab/coreutils'; -import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; -import * as React from 'react'; - -import { concatMultilineString } from '../common'; -import { IKeyboardEvent } from '../react-common/event'; -import { IMonacoModelContentChangeEvent } from '../react-common/monacoHelpers'; -import { Code } from './code'; -import { InputHistory } from './inputHistory'; -import { ICellViewModel, IFont } from './mainState'; -import { Markdown } from './markdown'; - -// tslint:disable-next-line: no-require-importss -interface ICellInputProps { - cellVM: ICellViewModel; - language: string; - codeVersion: number; - codeTheme: string; - testMode?: boolean; - history: InputHistory | undefined; - showWatermark: boolean; - monacoTheme: string | undefined; - editorOptions?: monacoEditor.editor.IEditorOptions; - editorMeasureClassName?: string; - showLineNumbers?: boolean; - font: IFont; - disableUndoStack: boolean; - isNotebookTrusted: boolean; - /** - * Only used in interactive window. - */ - focusPending: number; - onCodeChange(e: IMonacoModelContentChangeEvent): void; - onCodeCreated(code: string, file: string, cellId: string, modelId: string): void; - openLink(uri: monacoEditor.Uri): void; - keyDown?(cellId: string, e: IKeyboardEvent): void; - focused?(cellId: string): void; - unfocused?(cellId: string): void; -} - -// tslint:disable: react-this-binding-issue -export class CellInput extends React.Component { - private codeRef: React.RefObject = React.createRef(); - private markdownRef: React.RefObject = React.createRef(); - - constructor(prop: ICellInputProps) { - super(prop); - } - - public render() { - if (this.isCodeCell()) { - return this.renderCodeInputs(); - } else { - return this.renderMarkdownInputs(); - } - } - - public getContents(): string | undefined { - if (this.codeRef.current) { - return this.codeRef.current.getContents(); - } else if (this.markdownRef.current) { - return this.markdownRef.current.getContents(); - } - } - - private isCodeCell = () => { - return this.props.cellVM.cell.data.cell_type === 'code'; - }; - - private isMarkdownCell = () => { - return this.props.cellVM.cell.data.cell_type === 'markdown'; - }; - - private getMarkdownCell = () => { - return this.props.cellVM.cell.data as nbformat.IMarkdownCell; - }; - - private shouldRenderCodeEditor = (): boolean => { - return this.isCodeCell() && (this.props.cellVM.inputBlockShow || this.props.cellVM.editable); - }; - - private shouldRenderMarkdownEditor = (): boolean => { - return this.isMarkdownCell(); - }; - - private getRenderableInputCode = (): string => { - return this.props.cellVM.inputBlockText; - }; - - private renderCodeInputs = () => { - if (this.shouldRenderCodeEditor()) { - return ( -
- -
- ); - } - - return null; - }; - - private renderMarkdownInputs = () => { - if (this.shouldRenderMarkdownEditor()) { - const source = concatMultilineString(this.getMarkdownCell().source); - return ( -
- -
- ); - } - - return null; - }; - - private onKeyDown = (e: IKeyboardEvent) => { - if (this.props.keyDown) { - this.props.keyDown(this.props.cellVM.cell.id, e); - } - }; - - private onCodeFocused = () => { - if (this.props.focused) { - this.props.focused(this.props.cellVM.cell.id); - } - }; - - private onCodeUnfocused = () => { - if (this.props.unfocused) { - this.props.unfocused(this.props.cellVM.cell.id); - } - }; - - private onMarkdownFocused = () => { - if (this.props.focused) { - this.props.focused(this.props.cellVM.cell.id); - } - }; - - private onMarkdownUnfocused = () => { - if (this.props.unfocused) { - this.props.unfocused(this.props.cellVM.cell.id); - } - }; - - private onCodeCreated = (code: string, modelId: string) => { - this.props.onCodeCreated(code, this.props.cellVM.cell.file, this.props.cellVM.cell.id, modelId); - }; - - private getIpLine(): number | undefined { - if (this.props.cellVM.currentStack && this.props.cellVM.currentStack.length > 0) { - return this.props.cellVM.currentStack[0].line - 1; - } - } -} diff --git a/src/datascience-ui/interactive-common/cellOutput.tsx b/src/datascience-ui/interactive-common/cellOutput.tsx deleted file mode 100644 index fe9467110940..000000000000 --- a/src/datascience-ui/interactive-common/cellOutput.tsx +++ /dev/null @@ -1,598 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { nbformat } from '@jupyterlab/coreutils'; -import { JSONObject } from '@phosphor/coreutils'; -import ansiRegex from 'ansi-regex'; -import * as fastDeepEqual from 'fast-deep-equal'; -import * as React from 'react'; -import '../../client/common/extensions'; -import { Identifiers } from '../../client/datascience/constants'; -import { CellState } from '../../client/datascience/types'; -import { ClassType } from '../../client/ioc/types'; -import { WidgetManager } from '../ipywidgets'; -import { Image, ImageName } from '../react-common/image'; -import { ImageButton } from '../react-common/imageButton'; -import { getLocString } from '../react-common/locReactSide'; -import { ICellViewModel } from './mainState'; -import { fixMarkdown } from './markdownManipulation'; -import { getRichestMimetype, getTransform, isIPyWidgetOutput, isMimeTypeSupported } from './transforms'; - -// tslint:disable-next-line: no-var-requires no-require-imports -const ansiToHtml = require('ansi-to-html'); -// tslint:disable-next-line: no-var-requires no-require-imports -const lodashEscape = require('lodash/escape'); - -// tslint:disable-next-line: no-require-imports no-var-requires -const cloneDeep = require('lodash/cloneDeep'); -import { Widget } from '@phosphor/widgets'; -import { noop } from '../../client/common/utils/misc'; -import { WIDGET_MIMETYPE } from '../../client/datascience/ipywidgets/constants'; -import { concatMultilineString } from '../common'; -import { TrimmedOutputMessage } from './trimmedOutputLink'; - -interface ICellOutputProps { - cellVM: ICellViewModel; - baseTheme: string; - maxTextSize?: number; - enableScroll?: boolean; - hideOutput?: boolean; - themeMatplotlibPlots?: boolean; - expandImage(imageHtml: string): void; - widgetFailed(ex: Error): void; - openSettings(settings?: string): void; -} - -interface ICellOutputData { - mimeType: string; - data: nbformat.MultilineString | JSONObject; - mimeBundle: nbformat.IMimeBundle; - renderWithScrollbars: boolean; - isText: boolean; - isError: boolean; -} - -interface ICellOutput { - output: ICellOutputData; - extraButton: JSX.Element | null; // Extra button for plot viewing is stored here - outputSpanClassName?: string; // Wrap this output in a span with the following className, undefined to not wrap - doubleClick(): void; // Double click handler for plot viewing is stored here -} -// tslint:disable: react-this-binding-issue -export class CellOutput extends React.Component { - // tslint:disable-next-line: no-any - private static get ansiToHtmlClass(): ClassType { - if (!CellOutput.ansiToHtmlClass_ctor) { - // ansiToHtml is different between the tests running and webpack. figure out which one - if (ansiToHtml instanceof Function) { - CellOutput.ansiToHtmlClass_ctor = ansiToHtml; - } else { - CellOutput.ansiToHtmlClass_ctor = ansiToHtml.default; - } - } - return CellOutput.ansiToHtmlClass_ctor!; - } - // tslint:disable-next-line: no-any - private static ansiToHtmlClass_ctor: ClassType | undefined; - private ipyWidgetRef: React.RefObject; - private renderedViews = new Map>(); - private widgetManager: WidgetManager | undefined; - // tslint:disable-next-line: no-any - constructor(prop: ICellOutputProps) { - super(prop); - this.ipyWidgetRef = React.createRef(); - } - private static getAnsiToHtmlOptions(): { fg: string; bg: string; colors: string[] } { - // Here's the default colors for ansiToHtml. We need to use the - // colors from our current theme. - // const colors = { - // 0: '#000', - // 1: '#A00', - // 2: '#0A0', - // 3: '#A50', - // 4: '#00A', - // 5: '#A0A', - // 6: '#0AA', - // 7: '#AAA', - // 8: '#555', - // 9: '#F55', - // 10: '#5F5', - // 11: '#FF5', - // 12: '#55F', - // 13: '#F5F', - // 14: '#5FF', - // 15: '#FFF' - // }; - return { - fg: 'var(--vscode-terminal-foreground)', - bg: 'var(--vscode-terminal-background)', - colors: [ - 'var(--vscode-terminal-ansiBlack)', // 0 - 'var(--vscode-terminal-ansiBrightRed)', // 1 - 'var(--vscode-terminal-ansiGreen)', // 2 - 'var(--vscode-terminal-ansiYellow)', // 3 - 'var(--vscode-terminal-ansiBrightBlue)', // 4 - 'var(--vscode-terminal-ansiMagenta)', // 5 - 'var(--vscode-terminal-ansiCyan)', // 6 - 'var(--vscode-terminal-ansiBrightBlack)', // 7 - 'var(--vscode-terminal-ansiWhite)', // 8 - 'var(--vscode-terminal-ansiRed)', // 9 - 'var(--vscode-terminal-ansiBrightGreen)', // 10 - 'var(--vscode-terminal-ansiBrightYellow)', // 11 - 'var(--vscode-terminal-ansiBlue)', // 12 - 'var(--vscode-terminal-ansiBrightMagenta)', // 13 - 'var(--vscode-terminal-ansiBrightCyan)', // 14 - 'var(--vscode-terminal-ansiBrightWhite)' // 15 - ] - }; - } - public render() { - // Only render results if not an edit cell - if (this.props.cellVM.cell.id !== Identifiers.EditCellId) { - const outputClassNames = this.isCodeCell() - ? `cell-output cell-output-${this.props.baseTheme}` - : 'markdown-cell-output-container'; - - // Then combine them inside a div. IPyWidget ref has to be separate so we don't end up - // with a div in the way. If we try setting all div's background colors, we break - // some widgets - return ( -
- {this.renderResults()} -
-
- ); - } - return null; - } - public componentWillUnmount() { - this.destroyIPyWidgets(); - } - public componentDidMount() { - if (!this.isCodeCell() || !this.hasOutput() || !this.getCodeCell().outputs || this.props.hideOutput) { - return; - } - } - // tslint:disable-next-line: max-func-body-length - public componentDidUpdate(prevProps: ICellOutputProps) { - if (!this.isCodeCell() || !this.hasOutput() || !this.getCodeCell().outputs || this.props.hideOutput) { - return; - } - if (fastDeepEqual(this.props, prevProps)) { - return; - } - // Check if outupt has changed. - if ( - prevProps.cellVM.cell.data.cell_type === 'code' && - prevProps.cellVM.cell.state === this.getCell()!.state && - prevProps.hideOutput === this.props.hideOutput && - fastDeepEqual(this.props.cellVM.cell.data, prevProps.cellVM.cell.data) - ) { - return; - } - } - - public shouldComponentUpdate( - nextProps: Readonly, - _nextState: Readonly, - // tslint:disable-next-line: no-any - _nextContext: any - ): boolean { - if (nextProps === this.props) { - return false; - } - if (nextProps.baseTheme !== this.props.baseTheme) { - return true; - } - if (nextProps.maxTextSize !== this.props.maxTextSize) { - return true; - } - if (nextProps.themeMatplotlibPlots !== this.props.themeMatplotlibPlots) { - return true; - } - // If they are the same, then nothing has changed. - // Note, we're using redux, hence we'll never have the same reference object with different property values. - if (nextProps.cellVM === this.props.cellVM) { - return false; - } - if (nextProps.cellVM.cell.data.cell_type !== this.props.cellVM.cell.data.cell_type) { - return true; - } - if (nextProps.cellVM.cell.state !== this.props.cellVM.cell.state) { - return true; - } - if (nextProps.cellVM.cell.data.outputs !== this.props.cellVM.cell.data.outputs) { - return true; - } - if (nextProps.cellVM.uiSideError !== this.props.cellVM.uiSideError) { - return true; - } - if ( - !this.isCodeCell() && - nextProps.cellVM.cell.id !== Identifiers.EditCellId && - nextProps.cellVM.cell.data.source !== this.props.cellVM.cell.data.source - ) { - return true; - } - - return false; - } - // Public for testing - public getUnknownMimeTypeFormatString() { - return getLocString('DataScience.unknownMimeTypeFormat', 'Unknown Mime Type'); - } - private destroyIPyWidgets() { - this.renderedViews.forEach((viewPromise) => { - viewPromise.then((v) => v?.dispose()).ignoreErrors(); - }); - this.renderedViews.clear(); - } - - private getCell = () => { - return this.props.cellVM.cell; - }; - - private isCodeCell = () => { - return this.props.cellVM.cell.data.cell_type === 'code'; - }; - - private hasOutput = () => { - return ( - this.getCell().state === CellState.finished || - this.getCell().state === CellState.error || - this.getCell().state === CellState.executing - ); - }; - - private getCodeCell = () => { - return this.props.cellVM.cell.data as nbformat.ICodeCell; - }; - - private getMarkdownCell = () => { - return this.props.cellVM.cell.data as nbformat.IMarkdownCell; - }; - - private renderResults = (): JSX.Element[] => { - // Results depend upon the type of cell - if (this.isCodeCell()) { - return ( - this.renderCodeOutputs() - .filter((item) => !!item) - // tslint:disable-next-line: no-any - .map((item) => (item as any) as JSX.Element) - ); - } else if (this.props.cellVM.cell.id !== Identifiers.EditCellId) { - return this.renderMarkdownOutputs(); - } else { - return []; - } - }; - - private renderCodeOutputs = () => { - // return []; - if (this.isCodeCell() && this.hasOutput() && this.getCodeCell().outputs && !this.props.hideOutput) { - const trim = this.props.cellVM.cell.data.metadata.tags ? this.props.cellVM.cell.data.metadata.tags[0] : ''; - // Render the outputs - const outputs = this.renderOutputs(this.getCodeCell().outputs, trim); - - // Render any UI side errors - // tslint:disable: react-no-dangerous-html - if (this.props.cellVM.uiSideError) { - outputs.push( -
-
-
- ); - } - - return outputs; - } - return []; - }; - - private renderMarkdownOutputs = () => { - const markdown = this.getMarkdownCell(); - // React-markdown expects that the source is a string - const source = fixMarkdown(concatMultilineString(markdown.source)); - const Transform = getTransform('text/markdown'); - const MarkdownClassName = 'markdown-cell-output'; - - return [ -
- -
- ]; - }; - - private computeOutputData(output: nbformat.IOutput): ICellOutputData { - let isText = false; - let isError = false; - let mimeType = 'text/plain'; - let input = output.data; - let renderWithScrollbars = false; - - // Special case for json. Just turn into a string - if (input && input.hasOwnProperty('application/json')) { - input = JSON.stringify(output.data); - renderWithScrollbars = true; - isText = true; - } else if (output.output_type === 'stream') { - mimeType = 'text/html'; - isText = true; - isError = false; - renderWithScrollbars = true; - // Sonar is wrong, TS won't compile without this AS - const stream = output as nbformat.IStream; // NOSONAR - const concatted = lodashEscape(concatMultilineString(stream.text)); - input = { - 'text/html': concatted - }; - - // Output may have ascii colorization chars in it. - try { - if (ansiRegex().test(concatted)) { - const converter = new CellOutput.ansiToHtmlClass(CellOutput.getAnsiToHtmlOptions()); - const html = converter.toHtml(concatted); - input = { - 'text/html': html - }; - } - } catch { - noop(); - } - } else if (output.output_type === 'error') { - mimeType = 'text/html'; - isText = true; - isError = true; - renderWithScrollbars = true; - // Sonar is wrong, TS won't compile without this AS - const error = output as nbformat.IError; // NOSONAR - try { - const converter = new CellOutput.ansiToHtmlClass(CellOutput.getAnsiToHtmlOptions()); - // Modified traceback may exist. If so use that instead. It's only at run time though - const traceback: string[] = error.transient - ? (error.transient as string[]) - : error.traceback.map(lodashEscape); - const trace = traceback ? converter.toHtml(traceback.join('\n')) : error.evalue; - input = { - 'text/html': trace - }; - } catch { - // This can fail during unit tests, just use the raw data - input = { - 'text/html': lodashEscape(error.evalue) - }; - } - } else if (input) { - // Compute the mime type - mimeType = getRichestMimetype(input); - isText = mimeType === 'text/plain'; - } - - // Then parse the mime type - const mimeBundle = input as nbformat.IMimeBundle; // NOSONAR - let data: nbformat.MultilineString | JSONObject = mimeBundle[mimeType]; - - // For un-executed output we might get text or svg output as multiline string arrays - // we want to concat those so we don't display a bunch of weird commas as we expect - // Single strings in our output - if (Array.isArray(data)) { - data = concatMultilineString(data as nbformat.MultilineString, true); - } - - // Fixup latex to make sure it has the requisite $$ around it - if (mimeType === 'text/latex') { - data = fixMarkdown(concatMultilineString(data as nbformat.MultilineString, true), true); - } - - return { - isText, - isError, - renderWithScrollbars, - data: data, - mimeType, - mimeBundle - }; - } - - private transformOutput(output: nbformat.IOutput): ICellOutput { - // First make a copy of the outputs. - const copy = cloneDeep(output); - - // Then compute the data - const data = this.computeOutputData(copy); - let extraButton: JSX.Element | null = null; - - // Then parse the mime type - try { - // Text based mimeTypes don't get a white background - if (/^text\//.test(data.mimeType)) { - return { - output: data, - extraButton, - doubleClick: noop - }; - } else if (data.mimeType === 'image/svg+xml' || data.mimeType === 'image/png') { - // If we have a png or svg enable the plot viewer button - // There should be two mime bundles. Well if enablePlotViewer is turned on. See if we have both - const svg = data.mimeBundle['image/svg+xml']; - const png = data.mimeBundle['image/png']; - const buttonTheme = this.props.themeMatplotlibPlots ? this.props.baseTheme : 'vscode-light'; - let doubleClick: () => void = noop; - if (svg && png) { - // Save the svg in the extra button. - const openClick = () => { - this.props.expandImage(svg.toString()); - }; - extraButton = ( -
- - - -
- ); - - // Switch the data to the png - data.data = png; - data.mimeType = 'image/png'; - - // Switch double click to do the same thing as the extra button - doubleClick = openClick; - } - - // return the image - // If not theming plots then wrap in a span - return { - output: data, - extraButton, - doubleClick, - outputSpanClassName: this.props.themeMatplotlibPlots ? undefined : 'cell-output-plot-background' - }; - } else { - // For anything else just return it with a white plot background. This lets stuff like vega look good in - // dark mode - return { - output: data, - extraButton, - doubleClick: noop, - outputSpanClassName: this.props.themeMatplotlibPlots ? undefined : 'cell-output-plot-background' - }; - } - } catch (e) { - return { - output: { - data: e.toString(), - isText: true, - isError: false, - renderWithScrollbars: false, - mimeType: 'text/plain', - mimeBundle: {} - }, - extraButton: null, - doubleClick: noop - }; - } - } - - // tslint:disable-next-line: max-func-body-length - private renderOutputs(outputs: nbformat.IOutput[], trim: string): JSX.Element[] { - return [this.renderOutput(outputs, trim)]; - } - - private renderOutput = (outputs: nbformat.IOutput[], trim: string): JSX.Element => { - const buffer: JSX.Element[] = []; - const transformedList = outputs.map(this.transformOutput.bind(this)); - - transformedList.forEach((transformed, index) => { - const mimeType = transformed.output.mimeType; - if (isIPyWidgetOutput(transformed.output.mimeBundle)) { - // Create a view for this output if not already there. - this.renderWidget(transformed.output); - } else if (mimeType && isMimeTypeSupported(mimeType)) { - // If that worked, use the transform - // Get the matching React.Component for that mimetype - const Transform = getTransform(mimeType); - - let className = transformed.output.isText ? 'cell-output-text' : 'cell-output-html'; - className = transformed.output.isError ? `${className} cell-output-error` : className; - - // If we are not theming plots then wrap them in a white span - if (transformed.outputSpanClassName) { - buffer.push( -
- - {transformed.extraButton} - - -
- ); - } else { - if (trim === 'outputPrepend') { - buffer.push( -
- {transformed.extraButton} - - -
- ); - } else { - buffer.push( -
- {transformed.extraButton} - -
- ); - } - } - } else if ( - !mimeType || - mimeType.startsWith('application/scrapbook.scrap.') || - mimeType.startsWith('application/aml') - ) { - // Silently skip rendering of these mime types, render an empty div so the user sees the cell was executed. - buffer.push(
); - } else { - const str: string = this.getUnknownMimeTypeFormatString().format(mimeType); - buffer.push(
{str}
); - } - }); - - // Create a default set of properties - const style: React.CSSProperties = {}; - - // Create a scrollbar style if necessary - if (transformedList.some((transformed) => transformed.output.renderWithScrollbars) && this.props.enableScroll) { - style.overflowY = 'auto'; - style.maxHeight = `${this.props.maxTextSize}px`; - } - - return ( -
- {buffer} -
- ); - }; - - private renderWidget(widgetOutput: ICellOutputData) { - // Create a view for this widget if we haven't already - // tslint:disable-next-line: no-any - const widgetData: any = widgetOutput.mimeBundle[WIDGET_MIMETYPE]; - if (widgetData.model_id) { - if (!this.renderedViews.has(widgetData.model_id)) { - this.renderedViews.set(widgetData.model_id, this.createWidgetView(widgetData)); - } - } - } - - private async getWidgetManager() { - if (!this.widgetManager) { - const wm: WidgetManager | undefined = await new Promise((resolve) => - WidgetManager.instance.subscribe(resolve) - ); - this.widgetManager = wm; - if (wm) { - const oldDispose = wm.dispose.bind(wm); - wm.dispose = () => { - this.renderedViews.clear(); - this.widgetManager = undefined; - return oldDispose(); - }; - } - } - return this.widgetManager; - } - - private async createWidgetView(widgetData: nbformat.IMimeBundle & { model_id: string; version_major: number }) { - const wm = await this.getWidgetManager(); - const element = this.ipyWidgetRef.current!; - try { - return await wm?.renderWidget(widgetData, element); - } catch (ex) { - this.props.widgetFailed(ex); - } - } -} diff --git a/src/datascience-ui/interactive-common/code.tsx b/src/datascience-ui/interactive-common/code.tsx deleted file mode 100644 index dd8755e8cec6..000000000000 --- a/src/datascience-ui/interactive-common/code.tsx +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; -import * as React from 'react'; - -import { PYTHON_LANGUAGE } from '../../client/common/constants'; -import { InputHistory } from '../interactive-common/inputHistory'; -import { IKeyboardEvent } from '../react-common/event'; -import { getLocString } from '../react-common/locReactSide'; -import { IMonacoModelContentChangeEvent } from '../react-common/monacoHelpers'; -import { Editor } from './editor'; -import { CursorPos, IFont } from './mainState'; - -export interface ICodeProps { - code: string; - language: string | undefined; - version: number; - codeTheme: string; - testMode: boolean; - readOnly: boolean; - history: InputHistory | undefined; - showWatermark: boolean; - monacoTheme: string | undefined; - outermostParentClass: string; - editorOptions?: monacoEditor.editor.IEditorOptions; - editorMeasureClassName?: string; - showLineNumbers?: boolean; - useQuickEdit?: boolean; - font: IFont; - hasFocus: boolean; - cursorPos: CursorPos | monacoEditor.IPosition; - disableUndoStack: boolean; - focusPending: number; - ipLocation: number | undefined; - onCreated(code: string, modelId: string): void; - onChange(e: IMonacoModelContentChangeEvent): void; - openLink(uri: monacoEditor.Uri): void; - keyDown?(e: IKeyboardEvent): void; - focused?(): void; - unfocused?(): void; -} - -interface ICodeState { - allowWatermark: boolean; -} - -export class Code extends React.Component { - private editorRef: React.RefObject = React.createRef(); - - constructor(prop: ICodeProps) { - super(prop); - this.state = { allowWatermark: true }; - } - - public componentDidUpdate(prevProps: ICodeProps) { - if (prevProps.focusPending !== this.props.focusPending) { - this.giveFocus(CursorPos.Current); - } - } - - public render() { - const readOnly = this.props.readOnly; - const waterMarkClass = - this.props.showWatermark && this.state.allowWatermark && !readOnly ? 'code-watermark' : 'hide'; - const classes = readOnly ? 'code-area' : 'code-area code-area-editable'; - - return ( -
- -
- {this.getWatermarkString()} -
-
- ); - } - - public getContents(): string | undefined { - if (this.editorRef.current) { - return this.editorRef.current.getContents(); - } - } - - private giveFocus(cursorPos: CursorPos) { - if (this.editorRef && this.editorRef.current) { - this.editorRef.current.giveFocus(cursorPos); - } - } - - private clickWatermark = (ev: React.MouseEvent) => { - ev.stopPropagation(); - // Give focus to the editor - this.giveFocus(CursorPos.Current); - }; - - private getWatermarkString = (): string => { - return getLocString('DataScience.inputWatermark', 'Type code here and press shift-enter to run'); - }; - - private onModelChanged = (e: IMonacoModelContentChangeEvent) => { - if (!this.props.readOnly && e.model) { - this.setState({ allowWatermark: e.model.getValueLength() === 0 }); - } - this.props.onChange(e); - }; -} diff --git a/src/datascience-ui/interactive-common/collapseButton.tsx b/src/datascience-ui/interactive-common/collapseButton.tsx deleted file mode 100644 index 2410ad9eb046..000000000000 --- a/src/datascience-ui/interactive-common/collapseButton.tsx +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import * as React from 'react'; -import { getLocString } from '../react-common/locReactSide'; - -interface ICollapseButtonProps { - theme: string; - tooltip: string; - visible: boolean; - open: boolean; - label?: string; - onClick(): void; -} - -export class CollapseButton extends React.Component { - constructor(props: ICollapseButtonProps) { - super(props); - } - - public render() { - const collapseInputPolygonClassNames = `collapse-input-svg ${ - this.props.open ? ' collapse-input-svg-rotate' : '' - } collapse-input-svg-${this.props.theme}`; - const collapseInputClassNames = `collapse-input remove-style ${this.props.visible ? '' : ' invisible'}`; - const tooltip = this.props.open - ? getLocString('DataScience.collapseSingle', 'Collapse') - : getLocString('DataScience.expandSingle', 'Expand'); - const ariaExpanded = this.props.open ? 'true' : 'false'; - // https://reactjs.org/docs/conditional-rendering.html#inline-if-with-logical--operator - // Comment here just because the (boolean && statement) was new to me - return ( - - ); - } -} diff --git a/src/datascience-ui/interactive-common/common.css b/src/datascience-ui/interactive-common/common.css deleted file mode 100644 index 32522d88b894..000000000000 --- a/src/datascience-ui/interactive-common/common.css +++ /dev/null @@ -1,578 +0,0 @@ -body, -html { - height: 100%; - margin: 0; -} - -#root { - height: 100%; - overflow: hidden; -} - -#main-panel { - background: var(--override-background, var(--vscode-editor-background)); - color: var(--override-foreground, var(--vscode-editor-foreground)); - display: grid; - grid-template-rows: auto auto 1fr auto; - grid-template-columns: 1fr; - grid-template-areas: - 'toolbar' - 'variable' - 'content' - 'footer'; - height: 100%; - width: 100%; - position: absolute; - overflow: hidden; -} - -#main-panel-toolbar { - grid-area: toolbar; - overflow: hidden; -} - -#main-panel-variable { - grid-area: variable; - overflow: auto; -} -#main-panel-content { - grid-area: content; - max-height: 100%; - overflow: auto; -} - -#main-panel-footer { - grid-area: footer; - overflow: hidden; -} - -#content-panel-div.content-panel-scrollBeyondLastLine { - margin-bottom: 100vh; -} - -.hide { - display: none; -} - -.invisible { - visibility: hidden; -} - -.edit-panel { - min-height: 50px; - padding: 10px 0px 10px 0px; - width: 100%; - border-top-color: var(--override-badge-background, var(--vscode-badge-background)); - border-top-style: solid; - border-top-width: 2px; -} - -.edit-panel-colorized { - min-height: 50px; - padding: 10px 0px 10px 0px; - width: 100%; - border-top-color: var(--override-badge-background, var(--vscode-badge-background)); - border-top-style: solid; - border-top-width: 2px; - background-color: var(--override-peek-background, var(--vscode-peekViewEditor-background)); -} - -/* Cell */ -.cell-wrapper { - margin: 0px; - padding: 2px 5px 2px 2px; - display: block; -} - -.cell-wrapper:focus { - outline-width: 0px; - outline-color: var(--override-selection-background, var(--vscode-editor-selectionBackground)); -} - -.cell-wrapper-preview { - background-color: var(--override-peek-background, var(--vscode-peekViewEditor-background)); -} - -.cell-wrapper-noneditable { - border-bottom-color: var(--override-widget-background, var(--vscode-editorGroupHeader-tabsBackground)); - border-bottom-style: solid; - border-bottom-width: 1px; -} - -.cell-wrapper:after { - content: ''; - clear: both; - display: block; -} - -.edit-panel-colorized .cell-wrapper:focus { - outline-width: 0px; -} -.edit-panel .cell-wrapper:focus { - outline-width: 0px; -} - -.cell-outer { - display: grid; - grid-template-columns: auto 1fr; - grid-column-gap: 3px; - width: 100%; -} - -.cell-outer-editable { - display: grid; - grid-template-columns: auto 1fr; - grid-column-gap: 3px; - width: 100%; - margin-top: 16px; -} - -.content-div { - grid-column: 2; - width: 100%; -} - -.controls-div { - grid-column: 1; - grid-template-columns: max-content min-content; - justify-content: grid; - grid-template-rows: min-content max-content; - display: grid; -} - -.cell-result-container { - width: 100%; -} - -.cell-input { - margin: 0; -} - -.cell-input pre { - margin: 0px; - padding: 0px; -} - -.cell-output { - margin-top: 5px; - background: var(--override-widget-background, var(--vscode-notifications-background)); -} - -.cell-output-text { - white-space: pre-wrap; - font-family: var(--vscode-editor-font-family); - font-weight: var(--vscode-editor-font-weight); -} - -.cell-output-text pre { - white-space: pre-wrap; - font-family: var(--vscode-editor-font-family); - font-weight: var(--vscode-editor-font-weight); -} - -.cell-output-text xmp { - white-space: pre-wrap; - font-family: var(--vscode-editor-font-family); - font-weight: var(--vscode-editor-font-weight); -} - -.cell-output-html { - white-space: unset; - position: relative; -} - -.cell-output-plot-background { - background: white; - display: inline-block; - margin: 3px; -} - -.cell-output-ipywidget-background { - background: white !important; -} - -.cell-output-plot-background * { - margin: 0px; -} - -.cell-output table { - background-color: transparent; - border: none; - border-collapse: collapse; - border-spacing: 0px; - font-size: 12px; - table-layout: fixed; -} - -.cell-output thead { - border-bottom-color: var(--override-foreground, var(--vscode-editor-foreground)); - border-bottom-style: solid; - border-bottom-width: 1px; - vertical-align: bottom; -} - -.cell-output tr, -.cell-output th, -.cell-output td { - text-align: right; - vertical-align: middle; - padding: 0.5em 0.5em; - line-height: normal; - white-space: normal; - max-width: none; - border: none; -} -.cell-output th { - font-weight: bold; -} -.cell-output tbody tr:nth-child(even) { - background: var( - --override-background, - var(--vscode-editor-background) - ); /* Force to white because the default color for output is gray */ -} -.cell-output tbody tr:hover { - background: var(--override-selection-background, var(--vscode-editor-selectionBackground)); -} -.cell-output * + table { - margin-top: 1em; -} - -.center-img { - display: block; - margin: 0 auto; -} - -.cell-output-html .plot-open-button { - z-index: 10; - position: absolute; - left: 2px; - visibility: hidden; -} - -.cell-output-html:hover .plot-open-button { - visibility: visible; -} - -.cell-output-error { - background: var(--override-background, var(--vscode-editor-background)); -} - -/* Code */ -.code-area { - position: relative; - width: 100%; - margin-bottom: 16px; - top: -2px; /* Account for spacing removed from the monaco editor */ -} - -.code-area-editable { - margin-bottom: 10px; -} - -.code-watermark { - position: absolute; - top: 3px; - left: 3px; - z-index: 500; - font-style: italic; - color: var(--override-watermark-color, var(--vscode-panelTitle-inactiveForeground)); -} - -.code-watermark:focus { - outline-width: 0; -} - -.collapse-input-svg-rotate { - transform: rotate(45deg); - transform-origin: 0% 100%; -} - -.collapse-input-svg-vscode-light { - fill: black; -} - -.collapse-input-svg-vscode-dark { - fill: lightgray; -} - -.collapse-input { - grid-column: 2; - padding: 2px; - margin-top: 2px; - height: min-content; -} - -.remove-style { - background-color: transparent; - border: transparent; - font: inherit; -} - -.inputLabel { - background-color: var(--override-background, var(--vscode-editor-background)); - color: var(--override-foreground, var(--vscode-editor-foreground)); - font-family: var(--vscode-editor-font-family); - font-weight: var(--vscode-editor-font-weight); - margin: 3px; -} - -#cell-table { - display: block; - width: 100%; -} - -.flash { - animation-name: flash-animation; - animation-duration: 1s; -} - -@keyframes flash-animation { - from { - background: var(--override-peek-background, var(--vscode-peekViewEditor-background)); - } - to { - background: default; - } -} - -.messages-wrapper { - padding: 12px; - display: block; - border-bottom-color: var(--override-tabs-background, var(--vscode-editorGroupHeader-tabsBackground)); - border-bottom-style: solid; - border-bottom-width: 1px; -} - -.messages-outer { - background: var(--override-widget-background, var(--vscode-notifications-background)); - white-space: pre-wrap; - font-family: monospace; - width: 100%; -} - -.messages-outer-preview { - font-weight: bold; - background-color: var(--override-peek-background, var(--vscode-peekViewEditor-background)); - font-family: var(--code-font-family); -} - -.messages-wrapper-preview { - background-color: var(--override-peek-background, var(--vscode-peekViewEditor-background)); -} - -.messages-result-container pre { - white-space: pre-wrap; - font-family: monospace; - margin: 0px; -} - -.cell-menu-bar-outer { - justify-self: right; -} - -.cell-toolbar { - justify-self: end; - display: flex; - flex-flow: column; -} - -.cell-menu-bar-child-container { - margin-top: 2px; - margin-bottom: 2px; - display: flex; - flex-direction: column; -} - -#toolbar-panel { - margin-top: 2px; - margin-bottom: 2px; - margin-left: 2px; - margin-right: 2px; -} - -.toolbar-extra-button { - margin-left: auto; -} -#variable-panel-padding { - padding-top: 2px; - padding-left: 20px; - padding-right: 20px; - padding-bottom: 2px; -} -.variable-explorer-menu-bar { - display: flex; - width: 100%; - justify-content: end; -} -.variable-explorer-label { - margin-right: auto; -} -.variable-explorer-close-button { - justify-self: end; -} - -.execution-count { - grid-column: 1; - font-weight: bold; - display: flex; - color: var(--vscode-descriptionForeground); - font-family: var(--code-font-family); -} - -.execution-count-busy-outer { - grid-column: 1; - font-weight: bold; - color: var(--code-comment-color); - display: block; - width: 8px; - height: 8px; - white-space: nowrap; -} - -@keyframes execution-spin { - from { - transform: rotate(0); - } - to { - transform: rotate(360deg); - } -} - -.execution-count-busy-svg { - animation: execution-spin 4s linear infinite; -} - -.execution-count-busy-polyline { - fill: none; - stroke: var(--code-comment-color); - stroke-width: 1; -} - -@keyframes spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -.plain-editor { - background: transparent; - border: none; - outline: none; - width: 100%; -} - -.plain-editor:focus { - outline: none; -} - -.toolbar-divider { - width: 100%; - margin-top: 2px; - border-bottom-color: var(--override-badge-background, var(--vscode-badge-background)); - border-bottom-style: solid; - border-bottom-width: 2px; -} -.toolbar-menu-bar { - display: inline-flex; - width: 100%; -} - -.jupyter-info-section { - padding: 0px 5px; - align-self: center; - margin-top: 1px; - margin-bottom: 2px; - background-color: transparent; - border: none; - outline: none; - font-size: var(--vscode-font-size); - font-family: var(--vscode-font-family); -} -.jupyter-info-section-hoverable:hover { - background-color: var(--override-badge-background, var(--vscode-badge-background)); -} - -.kernel-status { - display: flex; - margin-left: auto; -} - -.kernel-status-section { - padding: 0px 5px; - align-self: center; - margin-top: 1px; - margin-bottom: 2px; -} - -.kernel-status-section-hoverable:hover { - background-color: var(--override-badge-background, var(--vscode-badge-background)); - cursor: pointer; -} - -.kernel-status-divider { - border-left: 1px solid var(--vscode-badge-background); - flex: 1; - max-width: 1px; -} - -.kernel-status-text { - padding-right: 5px; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - text-align: end; -} - -.kernel-status-server { - display: grid; - grid-template-columns: 1fr auto; -} - -.kernel-status-status { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -} - -.kernel-status-icon { - width: 16px; - height: 0px; - padding: 1px; - margin-top: 2px; -} - -.image-button-image { - cursor: pointer; -} - -.codicon-button { - cursor: pointer; - color: var(--vscode-editor-foreground); -} - -.image-button-inner-disabled-filter .image-button-image { - cursor: unset; -} - -.outputTrimmedSettingsLink { - text-decoration: underline; -} - -.toolbar-menu-bar .image-button { - margin-top: 8px; - margin-bottom: 9px; - margin-left: 7px; - margin-right: 7px; -} - -.image-button:focus { - outline: none; -} - -.rotate { - animation: spin 2s infinite linear; -} diff --git a/src/datascience-ui/interactive-common/contentPanel.tsx b/src/datascience-ui/interactive-common/contentPanel.tsx deleted file mode 100644 index f8653aa48fa4..000000000000 --- a/src/datascience-ui/interactive-common/contentPanel.tsx +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as React from 'react'; - -import * as fastDeepEqual from 'fast-deep-equal'; -import { IDataScienceExtraSettings } from '../../client/datascience/types'; -import { InputHistory } from './inputHistory'; -import { ICellViewModel } from './mainState'; - -// See the discussion here: https://github.com/Microsoft/tslint-microsoft-contrib/issues/676 -// tslint:disable: react-this-binding-issue -// tslint:disable-next-line:no-require-imports no-var-requires -const throttle = require('lodash/throttle') as typeof import('lodash/throttle'); - -export interface IContentPanelProps { - baseTheme: string; - cellVMs: ICellViewModel[]; - history?: InputHistory; - testMode?: boolean; - settings?: IDataScienceExtraSettings; - codeTheme: string; - submittedText: boolean; - skipNextScroll: boolean; - editable: boolean; - scrollBeyondLastLine: boolean; - renderCell(cellVM: ICellViewModel, index: number): JSX.Element | null; - scrollToBottom(div: HTMLDivElement): void; -} - -export class ContentPanel extends React.Component { - private bottomRef: React.RefObject = React.createRef(); - private containerRef: React.RefObject = React.createRef(); - private throttledScrollIntoView = throttle(this.scrollIntoView.bind(this), 100); - constructor(prop: IContentPanelProps) { - super(prop); - } - public componentDidMount() { - this.scrollToBottom(); - } - public componentWillReceiveProps(prevProps: IContentPanelProps) { - // Scroll if we suddenly finished or updated a cell. This should happen on - // finish, updating output, etc. - if (!fastDeepEqual(prevProps.cellVMs.map(this.outputCheckable), this.props.cellVMs.map(this.outputCheckable))) { - this.scrollToBottom(); - } - } - - public computeIsAtBottom(parent: HTMLDivElement): boolean { - if (this.bottomRef.current) { - // if the bottom div is on the screen, the content is at the bottom - return this.bottomRef.current.offsetTop - parent.offsetTop - 2 < parent.clientHeight + parent.scrollTop; - } - return false; - } - - public render() { - const className = `${this.props.scrollBeyondLastLine ? 'content-panel-scrollBeyondLastLine' : ''}`; - return ( -
-
- {this.renderCells()} -
-
-
- ); - } - - private outputCheckable = (cellVM: ICellViewModel) => { - // Return the properties that if they change means a cell updated something - return { - outputs: cellVM.cell.data.outputs, - state: cellVM.cell.state - }; - }; - - private renderCells = () => { - return this.props.cellVMs.map((cellVM: ICellViewModel, index: number) => { - return this.props.renderCell(cellVM, index); - }); - }; - - private scrollIntoView() { - if (this.bottomRef.current && this.props.scrollToBottom) { - this.props.scrollToBottom(this.bottomRef.current); - } - } - - private scrollToBottom() { - if (this.bottomRef.current && !this.props.skipNextScroll && !this.props.testMode && this.containerRef.current) { - // Make sure to debounce this so it doesn't take up too much time. - this.throttledScrollIntoView(); - } - } -} diff --git a/src/datascience-ui/interactive-common/editor.tsx b/src/datascience-ui/interactive-common/editor.tsx deleted file mode 100644 index 7eca634b2521..000000000000 --- a/src/datascience-ui/interactive-common/editor.tsx +++ /dev/null @@ -1,277 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; -import * as React from 'react'; - -import { noop } from '../../client/common/utils/misc'; -import { IKeyboardEvent } from '../react-common/event'; -import { MonacoEditor } from '../react-common/monacoEditor'; -import { IMonacoModelContentChangeEvent } from '../react-common/monacoHelpers'; -import { InputHistory } from './inputHistory'; -import { CursorPos, IFont } from './mainState'; - -const stickiness = monacoEditor.editor.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges; - -// we need a separate decoration for glyph margin, since we do not want it on each line of a multi line statement. -const TOP_STACK_FRAME_MARGIN: monacoEditor.editor.IModelDecorationOptions = { - glyphMarginClassName: 'codicon codicon-debug-stackframe', - stickiness -}; -const TOP_STACK_FRAME_DECORATION: monacoEditor.editor.IModelDecorationOptions = { - isWholeLine: true, - className: 'debug-top-stack-frame-line', - stickiness -}; - -// tslint:disable-next-line: import-name -export interface IEditorProps { - content: string; - version: number; - codeTheme: string; - readOnly: boolean; - testMode: boolean; - monacoTheme: string | undefined; - outermostParentClass: string; - editorOptions?: monacoEditor.editor.IEditorOptions; - history: InputHistory | undefined; - editorMeasureClassName?: string; - language: string; - showLineNumbers?: boolean; - useQuickEdit?: boolean; - font: IFont; - hasFocus: boolean; - cursorPos: CursorPos | monacoEditor.IPosition; - disableUndoStack: boolean; - ipLocation: number | undefined; - onCreated(code: string, modelId: string): void; - onChange(e: IMonacoModelContentChangeEvent): void; - openLink(uri: monacoEditor.Uri): void; - keyDown?(e: IKeyboardEvent): void; - focused?(): void; - unfocused?(): void; -} - -export class Editor extends React.Component { - private subscriptions: monacoEditor.IDisposable[] = []; - private lastCleanVersionId: number = 0; - private monacoRef: React.RefObject = React.createRef(); - private modelRef: monacoEditor.editor.ITextModel | null = null; - private editorRef: monacoEditor.editor.IStandaloneCodeEditor | null = null; - private decorationIds: string[] = []; - - constructor(prop: IEditorProps) { - super(prop); - } - - public componentWillUnmount = () => { - this.subscriptions.forEach((d) => d.dispose()); - }; - - public componentDidUpdate(prevProps: IEditorProps) { - if (this.modelRef) { - if (prevProps.ipLocation !== this.props.ipLocation) { - if (this.props.ipLocation) { - const newDecorations = this.createIpDelta(); - this.decorationIds = this.modelRef.deltaDecorations(this.decorationIds, newDecorations); - } else if (this.decorationIds.length) { - this.decorationIds = this.modelRef.deltaDecorations(this.decorationIds, []); - } - if (this.editorRef && this.props.ipLocation) { - this.editorRef.setPosition({ lineNumber: this.props.ipLocation, column: 1 }); - } - } - } - if (prevProps.readOnly === true && this.props.readOnly === false && this.editorRef) { - this.subscriptions.push(this.editorRef.onKeyDown(this.onKeyDown)); - this.subscriptions.push(this.editorRef.onKeyUp(this.onKeyUp)); - } - } - - public render() { - const classes = this.props.readOnly ? 'editor-area' : 'editor-area editor-area-editable'; - const renderEditor = this.renderMonacoEditor; - return
{renderEditor()}
; - } - - public giveFocus(cursorPos: CursorPos | monacoEditor.IPosition) { - if (this.monacoRef.current) { - this.monacoRef.current.giveFocus(cursorPos); - } - } - - public getContents(): string { - if (this.monacoRef.current) { - return this.monacoRef.current.getContents(); - } - return ''; - } - - private createIpDelta(): monacoEditor.editor.IModelDeltaDecoration[] { - const result: monacoEditor.editor.IModelDeltaDecoration[] = []; - if (this.props.ipLocation) { - const columnUntilEOLRange = new monacoEditor.Range( - this.props.ipLocation, - 1, - this.props.ipLocation, - 1 << 30 - ); - const range = new monacoEditor.Range(this.props.ipLocation, 1, this.props.ipLocation, 2); - - result.push({ - options: TOP_STACK_FRAME_MARGIN, - range - }); - - result.push({ - options: TOP_STACK_FRAME_DECORATION, - range: columnUntilEOLRange - }); - } - return result; - } - - private renderMonacoEditor = (): JSX.Element => { - const readOnly = this.props.readOnly; - const options: monacoEditor.editor.IEditorConstructionOptions = { - minimap: { - enabled: false - }, - glyphMargin: false, - wordWrap: 'on', - scrollBeyondLastLine: false, - scrollbar: { - vertical: 'hidden', - horizontal: 'hidden' - }, - lineNumbers: this.props.showLineNumbers ? 'on' : 'off', - renderLineHighlight: 'none', - highlightActiveIndentGuide: false, - renderIndentGuides: false, - overviewRulerBorder: false, - overviewRulerLanes: 0, - hideCursorInOverviewRuler: true, - folding: false, - readOnly: readOnly, - occurrencesHighlight: false, - selectionHighlight: false, - lineDecorationsWidth: 0, - contextmenu: false, - matchBrackets: false, - fontSize: this.props.font.size, - fontFamily: this.props.font.family, - ...this.props.editorOptions - }; - - return ( - - ); - }; - - private editorDidMount = (editor: monacoEditor.editor.IStandaloneCodeEditor) => { - this.editorRef = editor; - const model = editor.getModel(); - this.modelRef = model; - - // Disable undo/redo on the model if asked - // tslint:disable: no-any - if (this.props.disableUndoStack && (model as any).undo && (model as any).redo) { - (model as any).undo = noop; - (model as any).redo = noop; - } - - // List for key up/down events if not read only - if (!this.props.readOnly) { - this.subscriptions.push(editor.onKeyDown(this.onKeyDown)); - this.subscriptions.push(editor.onKeyUp(this.onKeyUp)); - } - - // Indicate we're ready - this.props.onCreated(this.props.content, model!.id); - - // Track focus changes - this.subscriptions.push(editor.onDidFocusEditorWidget(this.props.focused ? this.props.focused : noop)); - this.subscriptions.push(editor.onDidBlurEditorWidget(this.props.unfocused ? this.props.unfocused : noop)); - }; - - // tslint:disable-next-line: cyclomatic-complexity - private onKeyDown = (e: monacoEditor.IKeyboardEvent) => { - if (this.monacoRef.current) { - const cursor = this.monacoRef.current.getPosition(); - const currentLine = this.monacoRef.current.getCurrentVisibleLine(); - const visibleLineCount = this.monacoRef.current.getVisibleLineCount(); - const isSuggesting = this.monacoRef.current.isSuggesting(); - const isFirstLine = currentLine === 0; - const isLastLine = currentLine === visibleLineCount - 1; - const isDirty = this.monacoRef.current.getVersionId() > this.lastCleanVersionId; - - // See if we need to use the history or not - if (cursor && this.props.history && e.code === 'ArrowUp' && isFirstLine && !isSuggesting) { - const currentValue = this.getContents(); - const newValue = this.props.history.completeUp(currentValue); - if (newValue !== currentValue) { - this.monacoRef.current.setValue(newValue, CursorPos.Top); - this.lastCleanVersionId = this.monacoRef.current.getVersionId(); - e.stopPropagation(); - } - } else if (cursor && this.props.history && e.code === 'ArrowDown' && isLastLine && !isSuggesting) { - const currentValue = this.getContents(); - const newValue = this.props.history.completeDown(currentValue); - if (newValue !== currentValue) { - this.monacoRef.current.setValue(newValue, CursorPos.Bottom); - this.lastCleanVersionId = this.monacoRef.current.getVersionId(); - e.stopPropagation(); - } - } else if (this.props.keyDown) { - // Forward up the chain - this.props.keyDown({ - code: e.code, - shiftKey: e.shiftKey, - altKey: e.altKey, - ctrlKey: e.ctrlKey, - target: e.target, - metaKey: e.metaKey, - editorInfo: { - isFirstLine, - isLastLine, - isDirty, - isSuggesting, - contents: this.getContents(), - clear: this.clear - }, - stopPropagation: () => e.stopPropagation(), - preventDefault: () => e.preventDefault() - }); - } - } - }; - - private onKeyUp = (e: monacoEditor.IKeyboardEvent) => { - if (e.shiftKey && e.keyCode === monacoEditor.KeyCode.Enter) { - // Shift enter was hit - e.stopPropagation(); - e.preventDefault(); - } - }; - - private clear = () => { - if (this.monacoRef.current) { - this.monacoRef.current.setValue('', CursorPos.Top); - } - }; -} diff --git a/src/datascience-ui/interactive-common/executionCount.tsx b/src/datascience-ui/interactive-common/executionCount.tsx deleted file mode 100644 index 538b7ac96cbb..000000000000 --- a/src/datascience-ui/interactive-common/executionCount.tsx +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; -import * as React from 'react'; - -interface IExecutionCountProps { - isBusy: boolean; - count: string; - visible: boolean; -} - -export class ExecutionCount extends React.Component { - constructor(props: IExecutionCountProps) { - super(props); - } - - public render() { - if (this.props.visible) { - return this.props.isBusy ? ( -
- [ - - - - ] -
- ) : ( -
{`[${this.props.count}]`}
- ); - } else { - return null; - } - } -} diff --git a/src/datascience-ui/interactive-common/handlers.ts b/src/datascience-ui/interactive-common/handlers.ts deleted file mode 100644 index 059ace4951ac..000000000000 --- a/src/datascience-ui/interactive-common/handlers.ts +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -export function handleLinkClick(ev: MouseEvent, linkClick: (href: string) => void) { - // If this is an anchor element, forward the click as Jupyter does. - let anchor = ev.target as HTMLAnchorElement; - if (anchor && anchor.href) { - // Href may be redirected to an inner anchor - if (anchor.href.startsWith('vscode') || anchor.href.startsWith(anchor.baseURI)) { - const inner = anchor.getElementsByTagName('a'); - if (inner && inner.length > 0) { - anchor = inner[0]; - } - } - if (!anchor || !anchor.href || anchor.href.startsWith('vscode')) { - return; - } - - // Don't want a link click to cause a refresh of the webpage - ev.stopPropagation(); - ev.preventDefault(); - - // Look for a blob link. - if (!anchor.href.startsWith('blob:')) { - linkClick(anchor.href); - } else { - // We an have an image (as a blob) and the reference is blob://null: - // We need to get the blob, for that make a http request and the response will be the Blob - // Next convert the blob into something that can be sent to the client side. - // Just send an inlined base64 image to `linkClick`, such as `data:image/png;base64,xxxxx` - const xhr = new XMLHttpRequest(); - xhr.open('GET', anchor.href, true); - xhr.responseType = 'blob'; - xhr.onload = () => { - const blob = xhr.response; - const reader = new FileReader(); - reader.readAsDataURL(blob); - reader.onload = () => { - if (typeof reader.result === 'string') { - linkClick(reader.result); - } - }; - }; - xhr.send(); - } - } -} diff --git a/src/datascience-ui/interactive-common/images.d.ts b/src/datascience-ui/interactive-common/images.d.ts deleted file mode 100644 index f83b33cbc711..000000000000 --- a/src/datascience-ui/interactive-common/images.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -// tslint:disable:copyright -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -declare module '*.svg'; -declare module '*.png'; -declare module '*.jpg'; diff --git a/src/datascience-ui/interactive-common/informationMessages.tsx b/src/datascience-ui/interactive-common/informationMessages.tsx deleted file mode 100644 index 3d7a6599477d..000000000000 --- a/src/datascience-ui/interactive-common/informationMessages.tsx +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import * as React from 'react'; - -// tslint:disable-next-line:match-default-export-name import-name -interface IInformationMessagesProps { - messages: string[]; -} - -export class InformationMessages extends React.Component { - constructor(prop: IInformationMessagesProps) { - super(prop); - } - - public render() { - const output = this.props.messages.join('\n'); - const wrapperClassName = 'messages-wrapper'; - const outerClassName = 'messages-outer'; - - return ( -
-
-
-
-                            {output}
-                        
-
-
-
- ); - } -} diff --git a/src/datascience-ui/interactive-common/inputHistory.ts b/src/datascience-ui/interactive-common/inputHistory.ts deleted file mode 100644 index d75dbb4861f7..000000000000 --- a/src/datascience-ui/interactive-common/inputHistory.ts +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -export class InputHistory { - private historyStack: string[] = []; - private up: number | undefined; - private down: number | undefined; - private last: number | undefined; - - public completeUp(code: string): string { - // If going up, only move if anything in the history - if (this.historyStack.length > 0) { - if (this.up === undefined) { - this.up = 0; - } - - const result = this.up < this.historyStack.length ? this.historyStack[this.up] : code; - this.adjustCursors(this.up); - return result; - } - - return code; - } - - public completeDown(code: string): string { - // If going down, move and then return something if we have a position - if (this.historyStack.length > 0 && this.down !== undefined) { - const result = this.historyStack[this.down]; - this.adjustCursors(this.down); - return result; - } - - return code; - } - - public add(code: string, typed: boolean) { - // Compute our new history. Behavior depends upon if the user typed it in or - // just used the arrows - - // Only skip adding a dupe if it's the same as the top item. Otherwise - // add it as normal. - this.historyStack = - this.last === 0 && this.historyStack.length > 0 && this.historyStack[this.last] === code - ? this.historyStack - : [code, ...this.historyStack]; - - // Position is more complicated. If we typed something start over - if (typed) { - this.reset(); - } else { - // We want our next up push to match the index of the item that was - // actually entered. - if (this.last === 0) { - this.up = undefined; - this.down = undefined; - } else if (this.last) { - this.up = this.last + 1; - this.down = this.last - 1; - } - } - } - - private reset() { - this.up = undefined; - this.down = undefined; - } - - private adjustCursors(currentPos: number) { - // Save last position we entered. - this.last = currentPos; - - // For a single item, ony up works. But never modify it. - if (this.historyStack.length > 1) { - if (currentPos < this.historyStack.length) { - this.up = currentPos + 1; - } else { - this.up = this.historyStack.length; - - // If we go off the end, don't make the down go up to the last. - // CMD prompt behaves this way. Down is always one off. - currentPos = this.historyStack.length - 1; - } - if (currentPos > 0) { - this.down = currentPos - 1; - } else { - this.down = undefined; - } - } - } -} diff --git a/src/datascience-ui/interactive-common/intellisenseProvider.ts b/src/datascience-ui/interactive-common/intellisenseProvider.ts deleted file mode 100644 index 10636423b75c..000000000000 --- a/src/datascience-ui/interactive-common/intellisenseProvider.ts +++ /dev/null @@ -1,254 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; -import * as uuid from 'uuid/v4'; - -import { IDisposable } from '../../client/common/types'; -import { createDeferred, Deferred } from '../../client/common/utils/async'; -import { noop } from '../../client/common/utils/misc'; -import { Identifiers } from '../../client/datascience/constants'; -import { - IInteractiveWindowMapping, - InteractiveWindowMessages, - IProvideCompletionItemsResponse, - IProvideHoverResponse, - IProvideSignatureHelpResponse, - IResolveCompletionItemResponse -} from '../../client/datascience/interactive-common/interactiveWindowTypes'; - -interface IRequestData { - promise: Deferred; - cancelDisposable: monacoEditor.IDisposable; -} - -export class IntellisenseProvider - implements - monacoEditor.languages.CompletionItemProvider, - monacoEditor.languages.HoverProvider, - monacoEditor.languages.SignatureHelpProvider, - IDisposable { - public triggerCharacters?: string[] | undefined = ['.']; - public readonly signatureHelpTriggerCharacters?: ReadonlyArray = ['(', ',', '<']; - public readonly signatureHelpRetriggerCharacters?: ReadonlyArray = [')']; - private completionRequests: Map> = new Map< - string, - IRequestData - >(); - private resolveCompletionRequests: Map> = new Map< - string, - IRequestData - >(); - private hoverRequests: Map> = new Map< - string, - IRequestData - >(); - private signatureHelpRequests: Map> = new Map< - string, - IRequestData - >(); - private registerDisposables: monacoEditor.IDisposable[] = []; - private monacoIdToCellId: Map = new Map(); - private cellIdToMonacoId: Map = new Map(); - private disposed = false; - constructor( - private messageSender: ( - type: T, - payload?: M[T] - ) => void, - readonly language: string - ) { - // Register a completion provider - this.registerDisposables.push(monacoEditor.languages.registerCompletionItemProvider(language, this)); - this.registerDisposables.push(monacoEditor.languages.registerHoverProvider(language, this)); - this.registerDisposables.push(monacoEditor.languages.registerSignatureHelpProvider(language, this)); - } - - public provideCompletionItems( - model: monacoEditor.editor.ITextModel, - position: monacoEditor.Position, - context: monacoEditor.languages.CompletionContext, - token: monacoEditor.CancellationToken - ): monacoEditor.languages.ProviderResult { - // Emit a new request - const requestId = uuid(); - const promise = createDeferred(); - - const cancelDisposable = token.onCancellationRequested(() => { - promise.resolve(); - this.sendMessage(InteractiveWindowMessages.CancelCompletionItemsRequest, { requestId }); - }); - - this.completionRequests.set(requestId, { promise, cancelDisposable }); - this.sendMessage(InteractiveWindowMessages.ProvideCompletionItemsRequest, { - position, - context, - requestId, - cellId: this.getCellId(model.id) - }); - - return promise.promise; - } - - public async resolveCompletionItem( - model: monacoEditor.editor.ITextModel, - position: monacoEditor.Position, - item: monacoEditor.languages.CompletionItem, - token: monacoEditor.CancellationToken - ): Promise { - // If the item has already resolved documentation (as with MS LS) we don't need to do this - if (!item.documentation) { - // Emit a new request - const requestId = uuid(); - const promise = createDeferred(); - - const cancelDisposable = token.onCancellationRequested(() => { - promise.resolve(); - this.sendMessage(InteractiveWindowMessages.CancelResolveCompletionItemRequest, { requestId }); - }); - - this.resolveCompletionRequests.set(requestId, { promise, cancelDisposable }); - this.sendMessage(InteractiveWindowMessages.ResolveCompletionItemRequest, { - position, - item, - requestId, - cellId: this.getCellId(model.id) - }); - - const newItem = await promise.promise; - // Our code strips out _documentPosition and possibly other items that are too large to send - // so instead of returning the new resolve completion item, just return the old item with documentation added in - // which is what we are resolving the item to get - return Promise.resolve({ ...item, documentation: newItem?.documentation }); - } else { - return Promise.resolve(item); - } - } - - public provideHover( - model: monacoEditor.editor.ITextModel, - position: monacoEditor.Position, - token: monacoEditor.CancellationToken - ): monacoEditor.languages.ProviderResult { - // Emit a new request - const requestId = uuid(); - const promise = createDeferred(); - const wordAtPosition = model.getWordAtPosition(position); - - const cancelDisposable = token.onCancellationRequested(() => { - promise.resolve(); - this.sendMessage(InteractiveWindowMessages.CancelCompletionItemsRequest, { requestId }); - }); - - this.hoverRequests.set(requestId, { promise, cancelDisposable }); - this.sendMessage(InteractiveWindowMessages.ProvideHoverRequest, { - position, - requestId, - cellId: this.getCellId(model.id), - wordAtPosition: wordAtPosition ? wordAtPosition.word : undefined - }); - - return promise.promise; - } - - public provideSignatureHelp( - model: monacoEditor.editor.ITextModel, - position: monacoEditor.Position, - token: monacoEditor.CancellationToken, - context: monacoEditor.languages.SignatureHelpContext - ): monacoEditor.languages.ProviderResult { - // Emit a new request - const requestId = uuid(); - const promise = createDeferred(); - - const cancelDisposable = token.onCancellationRequested(() => { - promise.resolve(); - this.sendMessage(InteractiveWindowMessages.CancelSignatureHelpRequest, { requestId }); - }); - - this.signatureHelpRequests.set(requestId, { promise, cancelDisposable }); - this.sendMessage(InteractiveWindowMessages.ProvideSignatureHelpRequest, { - position, - context, - requestId, - cellId: this.getCellId(model.id) - }); - - return promise.promise; - } - - public dispose() { - this.disposed = true; - this.registerDisposables.forEach((r) => r.dispose()); - this.completionRequests.forEach((r) => r.promise.resolve()); - this.resolveCompletionRequests.forEach((r) => r.promise.resolve()); - this.hoverRequests.forEach((r) => r.promise.resolve()); - - this.registerDisposables = []; - this.completionRequests.clear(); - this.hoverRequests.clear(); - } - - public mapCellIdToModelId(cellId: string, modelId: string) { - this.cellIdToMonacoId.set(cellId, modelId); - this.monacoIdToCellId.set(modelId, cellId); - } - - // Handle completion response - public handleCompletionResponse(response: IProvideCompletionItemsResponse) { - // Resolve our waiting promise if we have one - const waiting = this.completionRequests.get(response.requestId); - if (waiting) { - waiting.promise.resolve(response.list); - this.completionRequests.delete(response.requestId); - } - } - - // Handle hover response - public handleHoverResponse(response: IProvideHoverResponse) { - // Resolve our waiting promise if we have one - const waiting = this.hoverRequests.get(response.requestId); - if (waiting) { - waiting.promise.resolve(response.hover); - this.hoverRequests.delete(response.requestId); - } - } - - // Handle signature response - public handleSignatureHelpResponse(response: IProvideSignatureHelpResponse) { - // Resolve our waiting promise if we have one - const waiting = this.signatureHelpRequests.get(response.requestId); - if (waiting) { - waiting.promise.resolve({ - value: response.signatureHelp, - dispose: noop - }); - this.signatureHelpRequests.delete(response.requestId); - } - } - - public handleResolveCompletionItemResponse(response: IResolveCompletionItemResponse) { - // Resolve our waiting promise if we have one - const waiting = this.resolveCompletionRequests.get(response.requestId); - if (waiting) { - waiting.promise.resolve(response.item); - this.completionRequests.delete(response.requestId); - } - } - - private getCellId(monacoId: string): string { - const result = this.monacoIdToCellId.get(monacoId); - if (result) { - return result; - } - - // Just assume it's the edit cell if not found. - return Identifiers.EditCellId; - } - - private sendMessage(type: T, payload?: M[T]): void { - if (!this.disposed) { - this.messageSender(type, payload); - } - } -} diff --git a/src/datascience-ui/interactive-common/jupyterInfo.tsx b/src/datascience-ui/interactive-common/jupyterInfo.tsx deleted file mode 100644 index 62d16d111053..000000000000 --- a/src/datascience-ui/interactive-common/jupyterInfo.tsx +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as React from 'react'; -import { Image, ImageName } from '../react-common/image'; -import { getLocString } from '../react-common/locReactSide'; -import { IFont, IServerState, ServerStatus } from './mainState'; -import { TrustMessage } from './trustMessage'; -import { getMaxWidth } from './utils'; - -export interface IJupyterInfoProps { - baseTheme: string; - font: IFont; - kernel: IServerState; - isNotebookTrusted?: boolean; - shouldShowTrustMessage: boolean; - selectServer(): void; - launchNotebookTrustPrompt?(): void; // Native editor-specific - selectKernel(): void; -} - -export class JupyterInfo extends React.Component { - private get isKernelSelectionAllowed() { - return ( - this.props.isNotebookTrusted !== false && - this.props.kernel.jupyterServerStatus !== ServerStatus.Restarting && - this.props.kernel.jupyterServerStatus !== ServerStatus.Starting - ); - } - constructor(prop: IJupyterInfoProps) { - super(prop); - this.selectKernel = this.selectKernel.bind(this); - this.selectServer = this.selectServer.bind(this); - } - - public render() { - const jupyterServerDisplayName: string = this.props.kernel.serverName; - const serverTextSize = - getLocString('DataScience.jupyterServer', 'Jupyter Server').length + jupyterServerDisplayName.length + 4; // plus 4 for the icon - const displayNameTextSize = this.props.kernel.kernelName.length + this.props.kernel.jupyterServerStatus.length; - const dynamicFont: React.CSSProperties = { - fontSize: 'var(--vscode-font-size)', // Use the same font and size as the menu - fontFamily: 'var(--vscode-font-family)', - maxWidth: getMaxWidth(serverTextSize + displayNameTextSize + 5) // plus 5 for the line and margins - }; - const serverTextWidth: React.CSSProperties = { - maxWidth: getMaxWidth(serverTextSize) - }; - const displayNameTextWidth: React.CSSProperties = { - maxWidth: getMaxWidth(displayNameTextSize) - }; - - const ariaDisabled = this.props.isNotebookTrusted === undefined ? false : this.props.isNotebookTrusted; - return ( -
- {this.renderTrustMessage()} -
-
- {getLocString('DataScience.jupyterServer', 'Jupyter Server')}: {jupyterServerDisplayName} -
- -
-
- {this.renderKernelStatus(displayNameTextWidth)} -
- ); - } - - private renderKernelStatus(displayNameTextWidth: React.CSSProperties) { - const ariaDisabled = this.props.isNotebookTrusted === undefined ? false : this.props.isNotebookTrusted; - if (this.isKernelSelectionAllowed) { - return ( -
- {this.props.kernel.kernelName}: {this.props.kernel.jupyterServerStatus} -
- ); - } else { - const displayName = this.props.kernel.kernelName ?? getLocString('DataScience.noKernel', 'No Kernel'); - return ( -
- {displayName}: {this.props.kernel.jupyterServerStatus} -
- ); - } - } - - private renderTrustMessage() { - if (this.props.shouldShowTrustMessage) { - return ( - - ); - } - } - - private selectKernel() { - this.props.selectKernel(); - } - private getIcon(): ImageName { - return this.props.kernel.jupyterServerStatus === ServerStatus.NotStarted - ? ImageName.JupyterServerDisconnected - : ImageName.JupyterServerConnected; - } - - private getStatus() { - return this.props.kernel.jupyterServerStatus === ServerStatus.NotStarted - ? getLocString('DataScience.disconnected', 'Disconnected') - : getLocString('DataScience.connected', 'Connected'); - } - - private selectServer(): void { - this.props.selectServer(); - } -} diff --git a/src/datascience-ui/interactive-common/mainState.ts b/src/datascience-ui/interactive-common/mainState.ts deleted file mode 100644 index f84713746568..000000000000 --- a/src/datascience-ui/interactive-common/mainState.ts +++ /dev/null @@ -1,609 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { nbformat } from '@jupyterlab/coreutils'; -// tslint:disable-next-line: no-require-imports no-var-requires -const cloneDeep = require('lodash/cloneDeep'); -import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; -import * as path from 'path'; - -import { DebugProtocol } from 'vscode-debugprotocol'; -import { PYTHON_LANGUAGE } from '../../client/common/constants'; -import { IDataScienceSettings } from '../../client/common/types'; -import { CellMatcher } from '../../client/datascience/cellMatcher'; -import { Identifiers } from '../../client/datascience/constants'; -import { IEditorPosition } from '../../client/datascience/interactive-common/interactiveWindowTypes'; -import { CellState, ICell, IDataScienceExtraSettings, IMessageCell } from '../../client/datascience/types'; -import { concatMultilineString, splitMultilineString } from '../common'; -import { createCodeCell } from '../common/cellFactory'; -import { getDefaultSettings } from '../react-common/settingsReactSide'; - -export enum CursorPos { - Top, - Bottom, - Current -} - -// The state we are in for run by line debugging -export enum DebugState { - Break, - Design, - Run -} - -export function activeDebugState(state: DebugState): boolean { - return state === DebugState.Break || state === DebugState.Run; -} - -export interface ICellViewModel { - cell: ICell; - inputBlockShow: boolean; - inputBlockOpen: boolean; - inputBlockText: string; - inputBlockCollapseNeeded: boolean; - editable: boolean; - directInput?: boolean; - showLineNumbers?: boolean; - hideOutput?: boolean; - useQuickEdit?: boolean; - selected: boolean; - focused: boolean; - scrollCount: number; - cursorPos: CursorPos | IEditorPosition; - hasBeenRun: boolean; - runDuringDebug?: boolean; - codeVersion?: number; - uiSideError?: string; - runningByLine: DebugState; - currentStack?: DebugProtocol.StackFrame[]; - gathering: boolean; -} - -export type IMainState = { - cellVMs: ICellViewModel[]; - editCellVM: ICellViewModel | undefined; - busy: boolean; - skipNextScroll?: boolean; - undoStack: ICellViewModel[][]; - redoStack: ICellViewModel[][]; - submittedText: boolean; - rootStyle?: string; - rootCss?: string; - font: IFont; - vscodeThemeName?: string; - baseTheme: string; - monacoTheme?: string; - knownDark: boolean; - editorOptions?: monacoEditor.editor.IEditorOptions; - currentExecutionCount: number; - debugging: boolean; - dirty: boolean; - isAtBottom: boolean; - newCellId?: string; - loadTotal?: number; - skipDefault?: boolean; - testMode?: boolean; - codeTheme: string; - settings?: IDataScienceExtraSettings; - focusPending: number; - monacoReady: boolean; - loaded: boolean; - kernel: IServerState; - isNotebookTrusted: boolean; -}; - -export type SelectionAndFocusedInfo = { - selectedCellId?: string; - selectedCellIndex?: number; - focusedCellId?: string; - focusedCellIndex?: number; -}; - -/** - * Returns the cell id and index of selected and focused cells. - */ -export function getSelectedAndFocusedInfo(state: { cellVMs: ICellViewModel[] }): SelectionAndFocusedInfo { - const info: { - selectedCellId?: string; - selectedCellIndex?: number; - focusedCellId?: string; - focusedCellIndex?: number; - } = {}; - for (let index = 0; index < state.cellVMs.length; index += 1) { - const cell = state.cellVMs[index]; - if (cell.selected) { - info.selectedCellId = cell.cell.id; - info.selectedCellIndex = index; - } - if (cell.focused) { - info.focusedCellId = cell.cell.id; - info.focusedCellIndex = index; - } - if (info.selectedCellId && info.focusedCellId) { - break; - } - } - - return info; -} - -export interface IFont { - size: number; - family: string; -} - -export interface IServerState { - jupyterServerStatus: ServerStatus; - serverName: string; - kernelName: string; - language: string; -} - -export enum ServerStatus { - NotStarted = 'Not Started', - Busy = 'Busy', - Idle = 'Idle', - Dead = 'Dead', - Starting = 'Starting', - Restarting = 'Restarting' -} - -// tslint:disable-next-line: no-multiline-string -const darkStyle = ` - :root { - --code-comment-color: #6A9955; - --code-numeric-color: #b5cea8; - --code-string-color: #ce9178; - --code-variable-color: #9CDCFE; - --code-type-color: #4EC9B0; - --code-font-family: Consolas, 'Courier New', monospace; - --code-font-size: 14px; - } -`; - -// This function generates test state when running under a browser instead of inside of -export function generateTestState(filePath: string = '', editable: boolean = false): IMainState { - const defaultSettings = getDefaultSettings(); - - return { - cellVMs: generateTestVMs(filePath, editable), - editCellVM: createEditableCellVM(1), - busy: false, - skipNextScroll: false, - undoStack: [], - redoStack: [], - submittedText: false, - rootStyle: darkStyle, - editorOptions: {}, - currentExecutionCount: 0, - knownDark: false, - baseTheme: 'vscode-light', - debugging: false, - isAtBottom: false, - font: { - size: 14, - family: "Consolas, 'Courier New', monospace" - }, - dirty: false, - codeTheme: 'Foo', - settings: defaultSettings, - focusPending: 0, - monacoReady: true, - loaded: false, - testMode: true, - kernel: { - serverName: '', - kernelName: 'Python', - jupyterServerStatus: ServerStatus.NotStarted, - language: PYTHON_LANGUAGE - }, - isNotebookTrusted: true - }; -} - -export function createEmptyCell(id: string | undefined, executionCount: number | null): ICell { - const emptyCodeCell = createCodeCell(); - emptyCodeCell.execution_count = executionCount ?? null; - return { - data: emptyCodeCell, - id: id ? id : Identifiers.EditCellId, - file: Identifiers.EmptyFileName, - line: 0, - state: CellState.finished - }; -} - -export function createEditableCellVM(executionCount: number): ICellViewModel { - return { - cell: createEmptyCell(Identifiers.EditCellId, executionCount), - editable: true, - inputBlockOpen: true, - inputBlockShow: true, - inputBlockText: '', - inputBlockCollapseNeeded: false, - selected: false, - focused: false, - cursorPos: CursorPos.Current, - hasBeenRun: false, - scrollCount: 0, - runningByLine: DebugState.Design, - gathering: false - }; -} - -export function extractInputText(inputCellVM: ICellViewModel, settings: IDataScienceSettings | undefined): string { - const inputCell = inputCellVM.cell; - let source: string[] = []; - if (inputCell.data.source) { - source = splitMultilineString(cloneDeep(inputCell.data.source)); - } - const matcher = new CellMatcher(settings); - - // Eliminate the #%% on the front if it has nothing else on the line - if (source.length > 0) { - const title = matcher.exec(source[0].trim()); - if (title !== undefined && title.length <= 0) { - source.splice(0, 1); - } - // Eliminate the lines to hide if we're debugging - if (inputCell.extraLines) { - inputCell.extraLines.forEach((i) => source.splice(i, 1)); - inputCell.extraLines = undefined; - } - } - - // Eliminate breakpoint on the front if we're debugging and breakpoints are expected to be prefixed - if (source.length > 0 && inputCellVM.runDuringDebug && (!settings || settings.stopOnFirstLineWhileDebugging)) { - if (source[0].trim() === 'breakpoint()') { - source.splice(0, 1); - } - } - - return concatMultilineString(source); -} - -export function createCellVM( - inputCell: ICell, - settings: IDataScienceSettings | undefined, - editable: boolean, - runDuringDebug: boolean -): ICellViewModel { - const vm = { - cell: inputCell, - editable, - inputBlockOpen: true, - inputBlockShow: true, - inputBlockText: '', - inputBlockCollapseNeeded: false, - selected: false, - focused: false, - cursorPos: CursorPos.Current, - hasBeenRun: false, - scrollCount: 0, - runDuringDebug, - runningByLine: DebugState.Design, - gathering: false - }; - - // Update the input text - let inputLinesCount = 0; - // If the cell is markdown, initialize inputBlockText with the mardown value. - // `inputBlockText` will be used to maintain diffs of editor changes. So whether its markdown or code, we need to generate it. - const inputText = - inputCell.data.cell_type === 'code' - ? extractInputText(vm, settings) - : inputCell.data.cell_type === 'markdown' - ? concatMultilineString(vm.cell.data.source) - : ''; - if (inputText) { - inputLinesCount = inputText.split('\n').length; - } - - vm.inputBlockText = inputText; - vm.inputBlockCollapseNeeded = inputLinesCount > 1; - - return vm; -} - -function generateTestVMs(filePath: string, editable: boolean): ICellViewModel[] { - const cells = generateTestCells(filePath, 10); - return cells.map((cell: ICell) => { - const vm = createCellVM(cell, undefined, editable, false); - vm.useQuickEdit = false; - vm.hasBeenRun = true; - return vm; - }); -} - -export function generateTestCells(filePath: string, repetitions: number): ICell[] { - // Dupe a bunch times for perf reasons - let cellData: (nbformat.ICodeCell | nbformat.IMarkdownCell | nbformat.IRawCell | IMessageCell)[] = []; - for (let i = 0; i < repetitions; i += 1) { - cellData = [...cellData, ...generateCellData()]; - } - return cellData.map( - (data: nbformat.ICodeCell | nbformat.IMarkdownCell | nbformat.IRawCell | IMessageCell, key: number) => { - return { - id: key.toString(), - file: path.join(filePath, 'foo.py').toLowerCase(), - line: 1, - state: key === cellData.length - 1 ? CellState.executing : CellState.finished, - type: key === 3 ? 'preview' : 'execute', - data: data - }; - } - ); -} - -//tslint:disable:max-func-body-length -function generateCellData(): (nbformat.ICodeCell | nbformat.IMarkdownCell | nbformat.IRawCell | IMessageCell)[] { - // Hopefully new entries here can just be copied out of a jupyter notebook (ipynb) - return [ - { - cell_type: 'code', - execution_count: 467, - metadata: { - slideshow: { - slide_type: '-' - } - }, - outputs: [ - { - data: { - // tslint:disable-next-line: no-multiline-string - 'text/html': [ - ` -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
0123456789...2990299129922993299429952996299729982999
idx
2007-01-3137.06060437.06060437.06060437.06060437.06060437.06060437.06060437.06060437.06060437.060604...37.06060437.06060437.06060437.06060437.06060437.06060437.06060437.06060437.06060437.060604
2007-02-2820.60340720.60340720.60340720.60340720.60340720.60340720.60340720.60340720.60340720.603407...20.60340720.60340720.60340720.60340720.60340720.60340720.60340720.60340720.60340720.603407
2007-03-316.1420316.1420316.1420316.1420316.1420316.1420316.1420316.1420316.1420316.142031...6.1420316.1420316.1420316.1420316.1420316.1420316.1420316.1420316.1420316.142031
2007-04-306.9316356.9316356.9316356.9316356.9316356.9316356.9316356.9316356.9316356.931635...6.9316356.9316356.9316356.9316356.9316356.9316356.9316356.9316356.9316356.931635
2007-05-3152.64224352.64224352.64224352.64224352.64224352.64224352.64224352.64224352.64224352.642243...52.64224352.64224352.64224352.64224352.64224352.64224352.64224352.64224352.64224352.642243
-

5 rows × 3000 columns

-
` - ] - }, - execution_count: 4, - metadata: {}, - output_type: 'execute_result' - } - ], - source: [ - 'myvar = """ # Lorem Ipsum\n', - '\n', - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n', - 'Nullam eget varius ligula, eget fermentum mauris.\n', - 'Cras ultrices, enim sit amet iaculis ornare, nisl nibh aliquet elit, sed ultrices velit ipsum dignissim nisl.\n', - 'Nunc quis orci ante. Vivamus vel blandit velit.\n","Sed mattis dui diam, et blandit augue mattis vestibulum.\n', - 'Suspendisse ornare interdum velit. Suspendisse potenti.\n', - 'Morbi molestie lacinia sapien nec porttitor. Nam at vestibulum nisi.\n', - '"""' - ] - }, - { - cell_type: 'markdown', - metadata: {}, - source: ['## Cell 3\n', "Here's some markdown\n", '- A List\n', '- Of Items'] - }, - { - cell_type: 'code', - execution_count: 1, - metadata: {}, - outputs: [ - { - ename: 'NameError', - evalue: 'name "df" is not defined', - output_type: 'error', - traceback: [ - '\u001b[1;31m---------------------------------------------------------------------------\u001b[0m', - '\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)', - '\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mdf\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m', - '\u001b[1;31mNameError\u001b[0m: name "df" is not defined' - ] - } - ], - source: ['df'] - }, - { - cell_type: 'code', - execution_count: 1, - metadata: {}, - outputs: [ - { - ename: 'NameError', - evalue: 'name "df" is not defined', - output_type: 'error', - traceback: [ - '\u001b[1;31m---------------------------------------------------------------------------\u001b[0m', - '\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)', - '\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mdf\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m', - '\u001b[1;31mNameError\u001b[0m: name "df" is not defined' - ] - } - ], - source: ['df'] - } - ]; -} diff --git a/src/datascience-ui/interactive-common/markdown.tsx b/src/datascience-ui/interactive-common/markdown.tsx deleted file mode 100644 index a6a8e083a183..000000000000 --- a/src/datascience-ui/interactive-common/markdown.tsx +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; -import * as React from 'react'; - -import { IKeyboardEvent } from '../react-common/event'; -import { IMonacoModelContentChangeEvent } from '../react-common/monacoHelpers'; -import { Editor } from './editor'; -import { CursorPos, IFont } from './mainState'; - -export interface IMarkdownProps { - markdown: string; - version: number; - codeTheme: string; - testMode: boolean; - monacoTheme: string | undefined; - outermostParentClass: string; - editorOptions?: monacoEditor.editor.IEditorOptions; - editorMeasureClassName?: string; - showLineNumbers?: boolean; - useQuickEdit?: boolean; - font: IFont; - hasFocus: boolean; - cursorPos: CursorPos | monacoEditor.IPosition; - disableUndoStack: boolean; - readOnly: boolean; - onCreated(code: string, modelId: string): void; - onChange(e: IMonacoModelContentChangeEvent): void; - focused?(): void; - unfocused?(): void; - openLink(uri: monacoEditor.Uri): void; - keyDown?(e: IKeyboardEvent): void; -} - -export class Markdown extends React.Component { - private editorRef: React.RefObject = React.createRef(); - - constructor(prop: IMarkdownProps) { - super(prop); - } - - public render() { - const classes = 'markdown-editor-area'; - - return ( -
- -
- ); - } - - public getContents(): string | undefined { - if (this.editorRef.current) { - return this.editorRef.current.getContents(); - } - } -} diff --git a/src/datascience-ui/interactive-common/markdownManipulation.ts b/src/datascience-ui/interactive-common/markdownManipulation.ts deleted file mode 100644 index f35c06dc7ffd..000000000000 --- a/src/datascience-ui/interactive-common/markdownManipulation.ts +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// tslint:disable-next-line:no-require-imports no-var-requires -const _escapeRegExp = require('lodash/escapeRegExp') as typeof import('lodash/escapeRegExp'); - -export function fixMarkdown(input: string, wrapSingles: boolean = false): string { - const latexFixed = fixLatex(input, wrapSingles); - - try { - return fixLinks(latexFixed); - } catch { - return latexFixed; - } -} - -// Adds '$$' to latex formulas that don't have a '$', allowing users to input the formula directly. -// -// The general algorithm here is: -// Search for either $$ or $ or a \begin{name} item. -// If a $$ or $ is found, output up to the next dollar sign -// If a \begin{name} is found, find the matching \end{name}, wrap the section in $$ and output up to the \end. -// -// LaTeX seems to follow the pattern of \begin{name} or is escaped with $$ or $. See here for a bunch of examples: -// https://jupyter-notebook.readthedocs.io/en/stable/examples/Notebook/Typesetting%20Equations.html -export function fixLatex(input: string, wrapSingles: boolean = false): string { - const output: string[] = []; - - // change latex - // Search for begin/end pairs, outputting as we go - let start = 0; - - // Loop until we run out string - while (start < input.length) { - // Check $$, $ and begin - const dollars = /\$\$/.exec(input.substr(start)); - const dollar = /\$/.exec(input.substr(start)); - const begin = /\\begin\{([a-z,\*]+)\}/.exec(input.substr(start)); - let endRegex = /\$\$/; - let endRegexLength = 2; - - // Pick the first that matches - let match = dollars; - let isBeginMatch = false; - const isDollarsMatch = dollars?.index === dollar?.index; - if (!match || (dollar && dollar.index < match.index)) { - match = dollar; - endRegex = /\$/; - endRegexLength = 1; - } - if (!match || (begin && begin.index < match.index)) { - match = begin; - endRegex = begin ? new RegExp(`\\\\end\\{${_escapeRegExp(begin[1])}\\}`) : /\$/; - endRegexLength = begin ? `\\end{${begin[1]}}`.length : 1; - isBeginMatch = true; - } - - // Output this match - if (match) { - if (isBeginMatch) { - // Begin match is a little more complicated. - const offset = match.index + start; - const end = endRegex.exec(input.substr(start)); - if (end) { - const prefix = input.substr(start, match.index); - const wrapped = input.substr(offset, endRegexLength + end.index - match.index); - output.push(`${prefix}\n$$\n${wrapped}\n$$\n`); - start = start + prefix.length + wrapped.length; - } else { - // Invalid, just return - return input; - } - } else if (isDollarsMatch) { - // Output till the next $$ - const offset = match.index + 2 + start; - const endDollar = endRegex.exec(input.substr(offset)); - if (endDollar) { - const length = endDollar.index + 2; - const before = input.substr(start, offset - start); - const after = input.substr(offset, length); - output.push(`${before}${after}`); - start = offset + length; - } else { - // Invalid, just return - return input; - } - } else { - // Output till the next $ (wrapping in an extra $ so it works with latex cells too) - const offset = match.index + 1 + start; - const endDollar = endRegex.exec(input.substr(offset)); - if (endDollar) { - const length = endDollar.index + 1; - const before = input.substr(start, offset - start); - const after = input.substr(offset, length); - output.push(wrapSingles ? `${before}$${after}$` : `${before}${after}`); - start = offset + length; - } else { - // Invalid, just return - return input; - } - } - } else { - // No more matches - output.push(input.substr(start)); - start = input.length; - } - } - - return output.join(''); -} - -// Look for HTML 'A' tags to replace them with the Markdown format -export function fixLinks(input: string): string { - let linkStartIndex = input.indexOf('', linkStartIndex); - - if (textStartIndex < linkEndIndex) { - const text = input.substring(textStartIndex + 1, linkEndIndex); - input = input.replace( - input.substring(linkStartIndex, linkEndIndex + linkEnd.length), - `[${text}](${url})` - ); - } - } - - linkStartIndex = input.indexOf('; - // tslint:disable-next-line: no-any - payload?: BaseReduxActionPayload; -}; - -export function queueIncomingActionWithPayload< - M extends IInteractiveWindowMapping & CommonActionTypeMapping, - K extends keyof M ->(originalReducerArg: ReducerArg, type: K, data: M[K]): void { - if (!checkToPostBasedOnOriginalMessageType(originalReducerArg.payload?.messageType)) { - return; - } - - // tslint:disable-next-line: no-any - const action = { type, payload: { data, messageDirection: 'incoming' } as any } as any; - originalReducerArg.queueAction(action); -} - -export function queueIncomingAction( - originalReducerArg: ReducerArg, - type: K -): void { - // tslint:disable-next-line: no-any - queueIncomingActionWithPayload(originalReducerArg, type as any, undefined); -} - -/** - * Post a message to the extension (via dispatcher actions). - */ -export function postActionToExtension( - originalReducerArg: ReducerArg, - message: T, - payload?: M[T] -): void; -/** - * Post a message to the extension (via dispatcher actions). - */ -// tslint:disable-next-line: unified-signatures -export function postActionToExtension( - originalReducerArg: ReducerArg, - message: T, - payload?: M[T] -): void; -// tslint:disable-next-line: no-any -export function postActionToExtension(originalReducerArg: ReducerArg, message: any, payload?: any) { - if (!checkToPostBasedOnOriginalMessageType(originalReducerArg.payload?.messageType)) { - return; - } - - // tslint:disable-next-line: no-any - const newPayload: BaseReduxActionPayload = ({ - data: payload, - messageDirection: 'outgoing', - messageType: MessageType.other - // tslint:disable-next-line: no-any - } as any) as BaseReduxActionPayload; - const action = { type: CommonActionType.PostOutgoingMessage, payload: { payload: newPayload, type: message } }; - originalReducerArg.queueAction(action); -} -export function unwrapPostableAction( - action: Redux.AnyAction -): { type: keyof IInteractiveWindowMapping; payload?: BaseReduxActionPayload<{}> } { - // Unwrap the payload that was created in `createPostableAction`. - const type = action.type; - const payload: BaseReduxActionPayload<{}> | undefined = action.payload; - return { type, payload }; -} - -/** - * Whether this is a message type that indicates it is part of a scynchronization message. - */ -export function isSyncingMessage(messageType?: MessageType) { - if (!messageType) { - return false; - } - - return ( - (messageType && MessageType.syncAcrossSameNotebooks) === MessageType.syncAcrossSameNotebooks || - (messageType && MessageType.syncWithLiveShare) === MessageType.syncWithLiveShare - ); -} -export function reBroadcastMessageIfRequired( - dispatcher: Function, - message: InteractiveWindowMessages | SharedMessages | CommonActionType | CssMessages, - payload?: BaseReduxActionPayload<{}> -) { - const messageType = payload?.messageType || 0; - if ( - message === InteractiveWindowMessages.Sync || - (messageType && MessageType.syncAcrossSameNotebooks) === MessageType.syncAcrossSameNotebooks || - (messageType && MessageType.syncWithLiveShare) === MessageType.syncWithLiveShare || - payload?.messageDirection === 'outgoing' - ) { - return; - } - // Check if we need to re-broadcast this message to other editors/sessions. - // tslint:disable-next-line: no-any - const result = shouldRebroadcast(message as any, payload); - if (result[0]) { - // Mark message as incoming, to indicate this will be sent into the other webviews. - // tslint:disable-next-line: no-any - const syncPayloadData: BaseReduxActionPayload = { - data: payload?.data, - messageType: result[1], - messageDirection: 'incoming' - }; - // tslint:disable-next-line: no-any - const syncPayload: SyncPayload = { type: message, payload: syncPayloadData }; - // First focus on UX perf, hence the setTimeout (i.e. ensure other code in event loop executes). - setTimeout(() => dispatcher(InteractiveWindowMessages.Sync, syncPayload), 1); - } -} diff --git a/src/datascience-ui/interactive-common/redux/postOffice.ts b/src/datascience-ui/interactive-common/redux/postOffice.ts deleted file mode 100644 index bfddb6a37a90..000000000000 --- a/src/datascience-ui/interactive-common/redux/postOffice.ts +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as Redux from 'redux'; - -import { - IInteractiveWindowMapping, - IPyWidgetMessages -} from '../../../client/datascience/interactive-common/interactiveWindowTypes'; -import { BaseReduxActionPayload } from '../../../client/datascience/interactive-common/types'; -import { PostOffice } from '../../react-common/postOffice'; -import { isAllowedAction, reBroadcastMessageIfRequired, unwrapPostableAction } from './helpers'; -import { CommonActionType } from './reducers/types'; - -export const AllowedIPyWidgetMessages = [...Object.values(IPyWidgetMessages)]; - -export function generatePostOfficeSendReducer(postOffice: PostOffice): Redux.Reducer<{}, Redux.AnyAction> { - // tslint:disable-next-line: no-function-expression - return function (_state: {} | undefined, action: Redux.AnyAction): {} { - if (isAllowedAction(action)) { - // Make sure a valid message - if (action.type === CommonActionType.PostOutgoingMessage) { - const { type, payload } = unwrapPostableAction(action.payload); - // Just post this to the post office. - // tslint:disable-next-line: no-any - postOffice.sendMessage(type, payload?.data as any); - } else { - const payload: BaseReduxActionPayload<{}> | undefined = action.payload; - // Do not rebroadcast messages that have been sent through as part of a synchronization packet. - // If `messageType` is a number, then its some part of a synchronization packet. - if (payload?.messageDirection === 'incoming') { - reBroadcastMessageIfRequired(postOffice.sendMessage.bind(postOffice), action.type, action?.payload); - } - } - } - - // We don't modify the state. - return {}; - }; -} diff --git a/src/datascience-ui/interactive-common/redux/reducers/commonEffects.ts b/src/datascience-ui/interactive-common/redux/reducers/commonEffects.ts deleted file mode 100644 index 5ad40578894a..000000000000 --- a/src/datascience-ui/interactive-common/redux/reducers/commonEffects.ts +++ /dev/null @@ -1,312 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { nbformat } from '@jupyterlab/coreutils'; -import type { KernelMessage } from '@jupyterlab/services'; -import { Identifiers } from '../../../../client/datascience/constants'; -import { InteractiveWindowMessages } from '../../../../client/datascience/interactive-common/interactiveWindowTypes'; -import { IGetCssResponse } from '../../../../client/datascience/messages'; -import { IGetMonacoThemeResponse } from '../../../../client/datascience/monacoMessages'; -import { CellState, ICell } from '../../../../client/datascience/types'; -import { ICellViewModel, IMainState } from '../../../interactive-common/mainState'; -import { Helpers } from '../../../interactive-common/redux/reducers/helpers'; -import { getLocString, storeLocStrings } from '../../../react-common/locReactSide'; -import { postActionToExtension } from '../helpers'; -import { Transfer } from './transfer'; -import { - CommonActionType, - CommonReducerArg, - ILoadIPyWidgetClassFailureAction, - IOpenSettingsAction, - LoadIPyWidgetClassLoadAction, - NotifyIPyWidgeWidgetVersionNotSupportedAction -} from './types'; - -export namespace CommonEffects { - export function notebookDirty(arg: CommonReducerArg): IMainState { - return { - ...arg.prevState, - dirty: true - }; - } - - export function notebookClean(arg: CommonReducerArg): IMainState { - return { - ...arg.prevState, - dirty: false - }; - } - - export function trustNotebook(arg: CommonReducerArg): IMainState { - return { - ...arg.prevState, - isNotebookTrusted: true - }; - } - - export function startProgress(arg: CommonReducerArg): IMainState { - return { - ...arg.prevState, - busy: true - }; - } - - export function stopProgress(arg: CommonReducerArg): IMainState { - return { - ...arg.prevState, - busy: false - }; - } - - export function activate(arg: CommonReducerArg): IMainState { - return focusPending(arg.prevState); - } - - export function focusInput(arg: CommonReducerArg): IMainState { - return focusPending(arg.prevState); - } - - export function handleLocInit(arg: CommonReducerArg): IMainState { - // Read in the loc strings - const locJSON = JSON.parse(arg.payload.data); - storeLocStrings(locJSON); - return arg.prevState; - } - - export function handleCss(arg: CommonReducerArg): IMainState { - // Recompute our known dark value from the class name in the body - // VS code should update this dynamically when the theme changes - const computedKnownDark = Helpers.computeKnownDark(arg.prevState.settings); - - // We also get this in our response, but computing is more reliable - // than searching for it. - const newBaseTheme = - arg.prevState.knownDark !== computedKnownDark && !arg.prevState.testMode - ? computedKnownDark - ? 'vscode-dark' - : 'vscode-light' - : arg.prevState.baseTheme; - - let fontSize: number = 14; - let fontFamily: string = "Consolas, 'Courier New', monospace"; - const sizeSetting = '--code-font-size: '; - const familySetting = '--code-font-family: '; - const fontSizeIndex = arg.payload.data.css.indexOf(sizeSetting); - const fontFamilyIndex = arg.payload.data.css.indexOf(familySetting); - - if (fontSizeIndex > -1) { - const fontSizeEndIndex = arg.payload.data.css.indexOf('px;', fontSizeIndex + sizeSetting.length); - fontSize = parseInt( - arg.payload.data.css.substring(fontSizeIndex + sizeSetting.length, fontSizeEndIndex), - 10 - ); - } - - if (fontFamilyIndex > -1) { - const fontFamilyEndIndex = arg.payload.data.css.indexOf(';', fontFamilyIndex + familySetting.length); - fontFamily = arg.payload.data.css.substring(fontFamilyIndex + familySetting.length, fontFamilyEndIndex); - } - - return { - ...arg.prevState, - rootCss: arg.payload.data.css, - font: { - size: fontSize, - family: fontFamily - }, - vscodeThemeName: arg.payload.data.theme, - knownDark: computedKnownDark, - baseTheme: newBaseTheme - }; - } - - export function monacoReady(arg: CommonReducerArg): IMainState { - return { - ...arg.prevState, - monacoReady: true - }; - } - - export function monacoThemeChange(arg: CommonReducerArg): IMainState { - return { - ...arg.prevState, - monacoTheme: Identifiers.GeneratedThemeName - }; - } - - function focusPending(prevState: IMainState): IMainState { - return { - ...prevState, - // This is only applicable for interactive window & not native editor. - focusPending: prevState.focusPending + 1 - }; - } - - export function openSettings(arg: CommonReducerArg): IMainState { - postActionToExtension(arg, InteractiveWindowMessages.OpenSettings, arg.payload.data.setting); - return arg.prevState; - } - - export function handleUpdateDisplayData( - arg: CommonReducerArg - ): IMainState { - const newCells: ICell[] = []; - const oldCells: ICell[] = []; - - // Find any cells that have this display_id - const newVMs = arg.prevState.cellVMs.map((c: ICellViewModel) => { - if (c.cell.data.cell_type === 'code') { - let isMatch = false; - const data: nbformat.ICodeCell = c.cell.data as nbformat.ICodeCell; - const changedOutputs = data.outputs.map((o) => { - if ( - (o.output_type === 'display_data' || o.output_type === 'execute_result') && - o.transient && - // tslint:disable-next-line: no-any - (o.transient as any).display_id === arg.payload.data.content.transient.display_id - ) { - // Remember this as a match - isMatch = true; - - // If the output has this display_id, update the output - return { - ...o, - data: arg.payload.data.content.data, - metadata: arg.payload.data.content.metadata - }; - } else { - return o; - } - }); - - // Save in our new cell list so we can tell the extension - // about our update - const newCell = isMatch - ? Helpers.asCell({ - ...c.cell, - data: { - ...c.cell.data, - outputs: changedOutputs - } - }) - : c.cell; - if (isMatch) { - newCells.push(newCell); - } else { - oldCells.push(newCell); - } - return Helpers.asCellViewModel({ - ...c, - cell: newCell - }); - } else { - oldCells.push(c.cell); - return c; - } - }); - - // If we found the display id, then an update happened. Tell the model about it - if (newCells.length) { - Transfer.postModelCellUpdate(arg, newCells, oldCells); - } - - return { - ...arg.prevState, - cellVMs: newVMs - }; - } - export function handleLoadIPyWidgetClassSuccess( - arg: CommonReducerArg - ): IMainState { - // Make sure to tell the extension so it can log telemetry. - postActionToExtension(arg, InteractiveWindowMessages.IPyWidgetLoadSuccess, arg.payload.data); - return arg.prevState; - } - export function handleLoadIPyWidgetClassFailure( - arg: CommonReducerArg - ): IMainState { - // Find the first currently executing cell and add an error to its output - let index = arg.prevState.cellVMs.findIndex((c) => c.cell.state === CellState.executing); - - // If there isn't one, then find the latest that matches the current execution count. - if (index < 0) { - index = arg.prevState.cellVMs.findIndex( - (c) => c.cell.data.execution_count === arg.prevState.currentExecutionCount - ); - } - if (index >= 0 && arg.prevState.cellVMs[index].cell.data.cell_type === 'code') { - const newVMs = [...arg.prevState.cellVMs]; - const current = arg.prevState.cellVMs[index]; - - let errorMessage = arg.payload.data.error.toString(); - if (!arg.payload.data.isOnline) { - errorMessage = getLocString( - 'DataScience.loadClassFailedWithNoInternet', - 'Error loading {0}:{1}. Internet connection required for loading 3rd party widgets.' - ).format(arg.payload.data.moduleName, arg.payload.data.moduleVersion); - } else if (!arg.payload.data.cdnsUsed) { - errorMessage = getLocString( - 'DataScience.enableCDNForWidgetsSetting', - "Widgets require us to download supporting files from a 3rd party website. Click here to enable this or click here for more information. (Error loading {0}:{1})." - ).format(arg.payload.data.moduleName, arg.payload.data.moduleVersion); - } - // Preserve existing error messages. - const existingErrorMessage = current.uiSideError ? `${current.uiSideError}\n` : ''; - newVMs[index] = Helpers.asCellViewModel({ - ...current, - uiSideError: `${existingErrorMessage}${errorMessage}` - }); - - // Make sure to tell the extension so it can log telemetry. - postActionToExtension(arg, InteractiveWindowMessages.IPyWidgetLoadFailure, arg.payload.data); - - return { - ...arg.prevState, - cellVMs: newVMs - }; - } else { - return arg.prevState; - } - } - export function notifyAboutUnsupportedWidgetVersions( - arg: CommonReducerArg - ): IMainState { - // Find the first currently executing cell and add an error to its output - let index = arg.prevState.cellVMs.findIndex((c) => c.cell.state === CellState.executing); - - // If there isn't one, then find the latest that matches the current execution count. - if (index < 0) { - index = arg.prevState.cellVMs.findIndex( - (c) => c.cell.data.execution_count === arg.prevState.currentExecutionCount - ); - } - if (index >= 0 && arg.prevState.cellVMs[index].cell.data.cell_type === 'code') { - const newVMs = [...arg.prevState.cellVMs]; - const current = arg.prevState.cellVMs[index]; - - const errorMessage = getLocString( - 'DataScience.qgridWidgetScriptVersionCompatibilityWarning', - "Unable to load a compatible version of the widget 'qgrid'. Consider downgrading to version 1.1.1." - ); - newVMs[index] = Helpers.asCellViewModel({ - ...current, - uiSideError: errorMessage - }); - - // Make sure to tell the extension so it can log telemetry. - postActionToExtension(arg, InteractiveWindowMessages.IPyWidgetWidgetVersionNotSupported, arg.payload.data); - - return { - ...arg.prevState, - cellVMs: newVMs - }; - } else { - return arg.prevState; - } - } - export function handleIPyWidgetRenderFailure(arg: CommonReducerArg): IMainState { - // Make sure to tell the extension so it can log telemetry. - postActionToExtension(arg, InteractiveWindowMessages.IPyWidgetRenderFailure, arg.payload.data); - return arg.prevState; - } -} diff --git a/src/datascience-ui/interactive-common/redux/reducers/helpers.ts b/src/datascience-ui/interactive-common/redux/reducers/helpers.ts deleted file mode 100644 index 7d2261053822..000000000000 --- a/src/datascience-ui/interactive-common/redux/reducers/helpers.ts +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { min } from 'lodash'; -// tslint:disable-next-line: no-require-imports no-var-requires -const cloneDeep = require('lodash/cloneDeep'); - -import { CellState, ICell, IDataScienceExtraSettings } from '../../../../client/datascience/types'; -import { arePathsSame } from '../../../react-common/arePathsSame'; -import { detectBaseTheme } from '../../../react-common/themeDetector'; -import { DebugState, ICellViewModel, IMainState } from '../../mainState'; -import { CommonActionType, CommonReducerArg } from './types'; - -const StackLimit = 10; - -export namespace Helpers { - export function computeKnownDark(settings?: IDataScienceExtraSettings): boolean { - const ignore = settings?.ignoreVscodeTheme ? true : false; - const baseTheme = ignore ? 'vscode-light' : detectBaseTheme(); - return baseTheme !== 'vscode-light'; - } - - export function pushStack(stack: ICellViewModel[][], cells: ICellViewModel[]) { - // Get the undo stack up to the maximum length - const slicedUndo = stack.slice(0, min([stack.length, StackLimit])); - - // make a copy of the cells so that further changes don't modify them. - const copy = cloneDeep(cells); - return [...slicedUndo, copy]; - } - - export function firstCodeCellAbove(state: IMainState, cellId: string | undefined) { - const codeCells = state.cellVMs.filter((c) => c.cell.data.cell_type === 'code'); - const index = codeCells.findIndex((c) => c.cell.id === cellId); - if (index > 0) { - return codeCells[index - 1].cell.id; - } - return undefined; - } - - // This function is because the unit test typescript compiler can't handle ICell.metadata - // tslint:disable-next-line: no-any - export function asCellViewModel(cvm: any): ICellViewModel { - return cvm as ICellViewModel; - } - - // This function is because the unit test typescript compiler can't handle ICell.metadata - // tslint:disable-next-line: no-any - export function asCell(cell: any): ICell { - return cell as ICell; - } - - export function updateOrAdd( - arg: CommonReducerArg, - generateVM: (cell: ICell, mainState: IMainState) => ICellViewModel - ): IMainState { - // First compute new execution count. - const newExecutionCount = arg.payload.data.data.execution_count - ? Math.max( - arg.prevState.currentExecutionCount, - parseInt(arg.payload.data.data.execution_count.toString(), 10) - ) - : arg.prevState.currentExecutionCount; - - const index = arg.prevState.cellVMs.findIndex((c: ICellViewModel) => { - return ( - c.cell.id === arg.payload.data.id && - c.cell.line === arg.payload.data.line && - arePathsSame(c.cell.file, arg.payload.data.file) - ); - }); - if (index >= 0) { - // This means the cell existed already so it was actual executed code. - // Use its execution count to update our execution count. - const finished = - arg.payload.data.state === CellState.finished || arg.payload.data.state === CellState.error; - - // Have to make a copy of the cell VM array or - // we won't actually update. - const newVMs = [...arg.prevState.cellVMs]; - - // Live share has been disabled for now, see https://github.com/microsoft/vscode-python/issues/7972 - // Check to see if our code still matches for the cell (in liveshare it might be updated from the other side) - // if (concatMultilineString(arg.prevState.cellVMs[index].cell.data.source) !== concatMultilineString(cell.data.source)) { - - // Prevent updates to the source, as its possible we have recieved a response for a cell execution - // and the user has updated the cell text since then. - const newVM: ICellViewModel = { - ...newVMs[index], - hasBeenRun: true, - cell: { - ...newVMs[index].cell, - state: arg.payload.data.state, - data: { - ...arg.payload.data.data, - source: newVMs[index].cell.data.source - } - }, - runningByLine: finished ? DebugState.Design : newVMs[index].runningByLine - }; - newVMs[index] = newVM; - - return { - ...arg.prevState, - cellVMs: newVMs, - currentExecutionCount: newExecutionCount - }; - } else { - // This is an entirely new cell (it may have started out as finished) - const newVM = generateVM(arg.payload.data, arg.prevState); - const newVMs = [...arg.prevState.cellVMs, newVM]; - return { - ...arg.prevState, - cellVMs: newVMs, - undoStack: pushStack(arg.prevState.undoStack, arg.prevState.cellVMs), - currentExecutionCount: newExecutionCount - }; - } - } -} diff --git a/src/datascience-ui/interactive-common/redux/reducers/kernel.ts b/src/datascience-ui/interactive-common/redux/reducers/kernel.ts deleted file mode 100644 index c4f3f1de1387..000000000000 --- a/src/datascience-ui/interactive-common/redux/reducers/kernel.ts +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { InteractiveWindowMessages } from '../../../../client/datascience/interactive-common/interactiveWindowTypes'; -import { CellState } from '../../../../client/datascience/types'; -import { IMainState, IServerState } from '../../mainState'; -import { postActionToExtension } from '../helpers'; -import { CommonActionType, CommonReducerArg } from './types'; - -export namespace Kernel { - // tslint:disable-next-line: no-any - export function selectKernel( - arg: CommonReducerArg - ): IMainState { - postActionToExtension(arg, InteractiveWindowMessages.SelectKernel); - - return arg.prevState; - } - export function selectJupyterURI(arg: CommonReducerArg): IMainState { - postActionToExtension(arg, InteractiveWindowMessages.SelectJupyterServer); - - return arg.prevState; - } - export function restartKernel(arg: CommonReducerArg): IMainState { - postActionToExtension(arg, InteractiveWindowMessages.RestartKernel); - - return arg.prevState; - } - - export function interruptKernel(arg: CommonReducerArg): IMainState { - postActionToExtension(arg, InteractiveWindowMessages.Interrupt); - - return arg.prevState; - } - - export function updateStatus( - arg: CommonReducerArg - ): IMainState { - if (arg.payload.data) { - return { - ...arg.prevState, - kernel: { - serverName: arg.payload.data.serverName, - jupyterServerStatus: arg.payload.data.jupyterServerStatus, - kernelName: arg.payload.data.kernelName, - language: arg.payload.data.language - } - }; - } - return arg.prevState; - } - - export function handleRestarted(arg: CommonReducerArg) { - // When we restart, make sure to turn off all executing cells. They aren't executing anymore - const newVMs = [...arg.prevState.cellVMs]; - newVMs.forEach((vm, i) => { - if (vm.cell.state !== CellState.finished && vm.cell.state !== CellState.error) { - newVMs[i] = { ...vm, hasBeenRun: false, cell: { ...vm.cell, state: CellState.finished } }; - } - }); - - return { - ...arg.prevState, - cellVMs: newVMs, - pendingVariableCount: 0, - variables: [], - currentExecutionCount: 0 - }; - } -} diff --git a/src/datascience-ui/interactive-common/redux/reducers/monaco.ts b/src/datascience-ui/interactive-common/redux/reducers/monaco.ts deleted file mode 100644 index 532fff8ce1cd..000000000000 --- a/src/datascience-ui/interactive-common/redux/reducers/monaco.ts +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; -import { Reducer } from 'redux'; - -import { PYTHON_LANGUAGE } from '../../../../client/common/constants'; -import { createDeferred } from '../../../../client/common/utils/async'; -import { Identifiers } from '../../../../client/datascience/constants'; -import { - ILoadTmLanguageResponse, - InteractiveWindowMessages, - IProvideCompletionItemsResponse, - IProvideHoverResponse, - IProvideSignatureHelpResponse, - IResolveCompletionItemResponse -} from '../../../../client/datascience/interactive-common/interactiveWindowTypes'; -import { deserializeLanguageConfiguration } from '../../../../client/datascience/interactive-common/serialization'; -import { BaseReduxActionPayload } from '../../../../client/datascience/interactive-common/types'; -import { CssMessages } from '../../../../client/datascience/messages'; -import { IGetMonacoThemeResponse } from '../../../../client/datascience/monacoMessages'; -import { PostOffice } from '../../../react-common/postOffice'; -import { combineReducers, QueuableAction, ReducerArg, ReducerFunc } from '../../../react-common/reduxUtils'; -import { IntellisenseProvider } from '../../intellisenseProvider'; -import { IServerState } from '../../mainState'; -import { Tokenizer } from '../../tokenizer'; -import { postActionToExtension, queueIncomingAction } from '../helpers'; -import { CommonActionType, ICodeCreatedAction, IEditCellAction } from './types'; - -// Global state so we only load the onigasm bits once. -const onigasmPromise = createDeferred(); - -export interface IMonacoState { - testMode: boolean; - intellisenseProvider: IntellisenseProvider | undefined; - postOffice: PostOffice; - language: string; -} - -type MonacoReducerFunc = ReducerFunc< - IMonacoState, - CommonActionType | InteractiveWindowMessages, - BaseReduxActionPayload ->; - -type MonacoReducerArg = ReducerArg< - IMonacoState, - CommonActionType | InteractiveWindowMessages, - BaseReduxActionPayload ->; - -function handleLoaded(arg: MonacoReducerArg): IMonacoState { - // Send the requests to get the onigasm and tmlanguage data if necessary - if (!Tokenizer.hasOnigasm()) { - postActionToExtension(arg, InteractiveWindowMessages.LoadOnigasmAssemblyRequest); - } - if (arg.prevState.language && !Tokenizer.hasLanguage(arg.prevState.language)) { - postActionToExtension(arg, InteractiveWindowMessages.LoadTmLanguageRequest, arg.prevState.language); - } - // If have both, tell other side monaco is ready - if (Tokenizer.hasOnigasm() && Tokenizer.hasLanguage(arg.prevState.language)) { - onigasmPromise.resolve(true); - - // Both queue to the reducers and to the extension side that we're ready - queueIncomingAction(arg, InteractiveWindowMessages.MonacoReady); - postActionToExtension(arg, InteractiveWindowMessages.MonacoReady); - } - - return arg.prevState; -} - -function handleStarted(arg: MonacoReducerArg): IMonacoState { - // When the window is first starting up, create our intellisense provider - // - // Note: We're not using arg.queueAction to send messages because of two reasons - // 1) The queueAction would be used outside of a reducer. This is a no no because its state would be off - // 2) A reducer can cause an IntellisenseProvider update, this would mean we'd be dispatching inside of a reducer - // and that's not allowed in redux. - // So instead, just post messages directly. - if (!arg.prevState.intellisenseProvider && arg.prevState.postOffice) { - return { - ...arg.prevState, - intellisenseProvider: new IntellisenseProvider( - arg.prevState.postOffice.sendMessage.bind(arg.prevState.postOffice), - arg.prevState.language ?? PYTHON_LANGUAGE - ) - }; - } - - return arg.prevState; -} - -function handleLoadOnigasmResponse(arg: MonacoReducerArg): IMonacoState { - if (!Tokenizer.hasOnigasm()) { - // Have to convert the buffer into an ArrayBuffer for the tokenizer to load it. - let typedArray = new Uint8Array(arg.payload.data); - if (typedArray.length <= 0) { - // tslint:disable-next-line: no-any - typedArray = new Uint8Array((arg.payload.data as any).data); - } - Tokenizer.loadOnigasm(typedArray.buffer); - onigasmPromise.resolve(true); - } - - return arg.prevState; -} - -function handleLoadTmLanguageResponse(arg: MonacoReducerArg): IMonacoState { - // First make sure we have the onigasm data first. - onigasmPromise.promise - .then(async () => { - // Then load the language data - if (!Tokenizer.hasLanguage(arg.payload.data.languageId)) { - await Tokenizer.loadLanguage( - arg.payload.data.languageId, - arg.payload.data.extensions, - arg.payload.data.scopeName, - deserializeLanguageConfiguration(arg.payload.data.languageConfiguration), - arg.payload.data.languageJSON - ); - } - - // Both queue to the reducers and to the extension side that we're ready - queueIncomingAction(arg, InteractiveWindowMessages.MonacoReady); - postActionToExtension(arg, InteractiveWindowMessages.MonacoReady); - }) - .ignoreErrors(); - - return arg.prevState; -} - -function handleKernelUpdate(arg: MonacoReducerArg): IMonacoState { - const newLanguage = arg.payload.data?.language ?? PYTHON_LANGUAGE; - if (newLanguage !== arg.prevState.language) { - if (!Tokenizer.hasLanguage(newLanguage)) { - postActionToExtension(arg, InteractiveWindowMessages.LoadTmLanguageRequest, newLanguage); - } - - // Recreate the intellisense provider - arg.prevState.intellisenseProvider?.dispose(); // NOSONAR - return { - ...arg.prevState, - language: newLanguage, - intellisenseProvider: new IntellisenseProvider( - arg.prevState.postOffice.sendMessage.bind(arg.prevState.postOffice), - newLanguage - ) - }; - } - - return arg.prevState; -} - -function handleThemeResponse(arg: MonacoReducerArg): IMonacoState { - // Tell monaco we have a new theme. THis is like a state update for monaco - monacoEditor.editor.defineTheme(Identifiers.GeneratedThemeName, arg.payload.data.theme); - return arg.prevState; -} - -function handleCompletionItemsResponse(arg: MonacoReducerArg): IMonacoState { - const ensuredProvider = handleStarted(arg); - ensuredProvider.intellisenseProvider!.handleCompletionResponse(arg.payload.data); - return ensuredProvider; -} - -function handleResolveCompletionItemResponse(arg: MonacoReducerArg): IMonacoState { - const ensuredProvider = handleStarted(arg); - ensuredProvider.intellisenseProvider!.handleResolveCompletionItemResponse(arg.payload.data); - return ensuredProvider; -} - -function handleSignatureHelpResponse(arg: MonacoReducerArg): IMonacoState { - const ensuredProvider = handleStarted(arg); - ensuredProvider.intellisenseProvider!.handleSignatureHelpResponse(arg.payload.data); - return ensuredProvider; -} - -function handleHoverResponse(arg: MonacoReducerArg): IMonacoState { - const ensuredProvider = handleStarted(arg); - ensuredProvider.intellisenseProvider!.handleHoverResponse(arg.payload.data); - return ensuredProvider; -} - -function handleCodeCreated(arg: MonacoReducerArg): IMonacoState { - const ensuredProvider = handleStarted(arg); - if (arg.payload.data.cellId) { - ensuredProvider.intellisenseProvider!.mapCellIdToModelId(arg.payload.data.cellId, arg.payload.data.modelId); - } - return ensuredProvider; -} - -function handleEditCell(arg: MonacoReducerArg): IMonacoState { - const ensuredProvider = handleStarted(arg); - if (arg.payload.data.cellId) { - ensuredProvider.intellisenseProvider!.mapCellIdToModelId(arg.payload.data.cellId, arg.payload.data.modelId); - } - return ensuredProvider; -} - -function handleUnmount(arg: MonacoReducerArg): IMonacoState { - if (arg.prevState.intellisenseProvider) { - arg.prevState.intellisenseProvider.dispose(); - } - - return arg.prevState; -} - -// type MonacoReducerFunctions = { -// [P in keyof T]: T[P] extends never | undefined ? MonacoReducerFunc : MonacoReducerFunc; -// }; - -// type IMonacoActionMapping = MonacoReducerFunctions & MonacoReducerFunctions; -// Create a mapping between message and reducer type -class IMonacoActionMapping { - public [InteractiveWindowMessages.Started]: MonacoReducerFunc; - public [InteractiveWindowMessages.LoadOnigasmAssemblyResponse]: MonacoReducerFunc; - public [InteractiveWindowMessages.LoadTmLanguageResponse]: MonacoReducerFunc; - public [CssMessages.GetMonacoThemeResponse]: MonacoReducerFunc; - public [InteractiveWindowMessages.ProvideCompletionItemsResponse]: MonacoReducerFunc< - IProvideCompletionItemsResponse - >; - public [InteractiveWindowMessages.ProvideSignatureHelpResponse]: MonacoReducerFunc; - public [InteractiveWindowMessages.ProvideHoverResponse]: MonacoReducerFunc; - public [InteractiveWindowMessages.ResolveCompletionItemResponse]: MonacoReducerFunc; - public [InteractiveWindowMessages.UpdateKernel]: MonacoReducerFunc; - - public [CommonActionType.CODE_CREATED]: MonacoReducerFunc; - public [CommonActionType.EDIT_CELL]: MonacoReducerFunc; - public [CommonActionType.UNMOUNT]: MonacoReducerFunc; - public [CommonActionType.EDITOR_LOADED]: MonacoReducerFunc; -} - -// Create the map between message type and the actual function to call to update state -const reducerMap: IMonacoActionMapping = { - [InteractiveWindowMessages.Started]: handleStarted, - [InteractiveWindowMessages.LoadOnigasmAssemblyResponse]: handleLoadOnigasmResponse, - [InteractiveWindowMessages.LoadTmLanguageResponse]: handleLoadTmLanguageResponse, - [CssMessages.GetMonacoThemeResponse]: handleThemeResponse, - [InteractiveWindowMessages.ProvideCompletionItemsResponse]: handleCompletionItemsResponse, - [InteractiveWindowMessages.ProvideSignatureHelpResponse]: handleSignatureHelpResponse, - [InteractiveWindowMessages.ProvideHoverResponse]: handleHoverResponse, - [InteractiveWindowMessages.ResolveCompletionItemResponse]: handleResolveCompletionItemResponse, - [InteractiveWindowMessages.UpdateKernel]: handleKernelUpdate, - [CommonActionType.CODE_CREATED]: handleCodeCreated, - [CommonActionType.EDIT_CELL]: handleEditCell, - [CommonActionType.UNMOUNT]: handleUnmount, - [CommonActionType.EDITOR_LOADED]: handleLoaded -}; - -export function generateMonacoReducer( - testMode: boolean, - postOffice: PostOffice -): Reducer> { - // First create our default state. - const defaultState: IMonacoState = { - testMode, - intellisenseProvider: undefined, - postOffice, - language: PYTHON_LANGUAGE - }; - - // Then combine that with our map of state change message to reducer - return combineReducers(defaultState, reducerMap); -} diff --git a/src/datascience-ui/interactive-common/redux/reducers/transfer.ts b/src/datascience-ui/interactive-common/redux/reducers/transfer.ts deleted file mode 100644 index 31835412722b..000000000000 --- a/src/datascience-ui/interactive-common/redux/reducers/transfer.ts +++ /dev/null @@ -1,351 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { Identifiers } from '../../../../client/datascience/constants'; -import { - IEditorContentChange, - InteractiveWindowMessages, - NotebookModelChange -} from '../../../../client/datascience/interactive-common/interactiveWindowTypes'; -import { CssMessages } from '../../../../client/datascience/messages'; -import { ICell } from '../../../../client/datascience/types'; -import { extractInputText, getSelectedAndFocusedInfo, ICellViewModel, IMainState } from '../../mainState'; -import { isSyncingMessage, postActionToExtension } from '../helpers'; -import { Helpers } from './helpers'; -import { - CommonActionType, - CommonReducerArg, - ICellAction, - IChangeGatherStatus, - IEditCellAction, - ILinkClickAction, - ISendCommandAction, - IShowDataViewerAction -} from './types'; - -// These are all reducers that don't actually change state. They merely dispatch a message to the other side. -export namespace Transfer { - export function exportCells(arg: CommonReducerArg): IMainState { - const cellContents = arg.prevState.cellVMs.map((v) => v.cell); - postActionToExtension(arg, InteractiveWindowMessages.Export, cellContents); - - // Indicate busy - return { - ...arg.prevState, - busy: true - }; - } - - export function showExportAsMenu(arg: CommonReducerArg): IMainState { - const cellContents = arg.prevState.cellVMs.map((v) => v.cell); - postActionToExtension(arg, InteractiveWindowMessages.ExportNotebookAs, cellContents); - - return { - ...arg.prevState - }; - } - - export function save(arg: CommonReducerArg): IMainState { - // Note: this is assuming editor contents have already been saved. That should happen as a result of focus change - - // Actually waiting for save results before marking as not dirty, so don't do it here. - postActionToExtension(arg, InteractiveWindowMessages.SaveAll, { - cells: arg.prevState.cellVMs.map((cvm) => cvm.cell) - }); - return arg.prevState; - } - - export function showDataViewer(arg: CommonReducerArg): IMainState { - postActionToExtension(arg, InteractiveWindowMessages.ShowDataViewer, { - variable: arg.payload.data.variable, - columnSize: arg.payload.data.columnSize - }); - return arg.prevState; - } - - export function sendCommand(arg: CommonReducerArg): IMainState { - postActionToExtension(arg, InteractiveWindowMessages.NativeCommand, { - command: arg.payload.data.command - }); - return arg.prevState; - } - - export function showPlot( - arg: CommonReducerArg - ): IMainState { - if (arg.payload.data) { - postActionToExtension(arg, InteractiveWindowMessages.ShowPlot, arg.payload.data); - } - return arg.prevState; - } - - export function launchNotebookTrustPrompt(arg: CommonReducerArg) { - postActionToExtension(arg, InteractiveWindowMessages.LaunchNotebookTrustPrompt); - return arg.prevState; - } - - export function linkClick(arg: CommonReducerArg): IMainState { - if (arg.payload.data.href.startsWith('data:image/png')) { - postActionToExtension(arg, InteractiveWindowMessages.SavePng, arg.payload.data.href); - } else { - postActionToExtension(arg, InteractiveWindowMessages.OpenLink, arg.payload.data.href); - } - return arg.prevState; - } - - export function getAllCells(arg: CommonReducerArg): IMainState { - const cells = arg.prevState.cellVMs.map((c) => c.cell); - postActionToExtension(arg, InteractiveWindowMessages.ReturnAllCells, cells); - return arg.prevState; - } - - export function hasCell(arg: CommonReducerArg): IMainState { - const foundCell = arg.prevState.cellVMs.find((c) => c.cell.id === arg.payload.data); - postActionToExtension(arg, InteractiveWindowMessages.HasCellResponse, { - id: arg.payload.data, - result: foundCell !== undefined - }); - return arg.prevState; - } - - export function gotoCell(arg: CommonReducerArg): IMainState { - const cellVM = arg.prevState.cellVMs.find((c) => c.cell.id === arg.payload.data.cellId); - if (cellVM && cellVM.cell.data.cell_type === 'code') { - postActionToExtension(arg, InteractiveWindowMessages.GotoCodeCell, { - file: cellVM.cell.file, - line: cellVM.cell.line - }); - } - return arg.prevState; - } - - export function copyCellCode(arg: CommonReducerArg): IMainState { - let cellVM = arg.prevState.cellVMs.find((c) => c.cell.id === arg.payload.data.cellId); - if (!cellVM && arg.prevState.editCellVM && arg.payload.data.cellId === arg.prevState.editCellVM.cell.id) { - cellVM = arg.prevState.editCellVM; - } - - // Send a message to the other side to jump to a particular cell - if (cellVM) { - postActionToExtension(arg, InteractiveWindowMessages.CopyCodeCell, { - source: extractInputText(cellVM, arg.prevState.settings) - }); - } - - return arg.prevState; - } - - export function gather(arg: CommonReducerArg): IMainState { - const cellVM = arg.prevState.cellVMs.find((c) => c.cell.id === arg.payload.data.cellId); - if (cellVM) { - postActionToExtension(arg, InteractiveWindowMessages.GatherCode, cellVM.cell); - } - return arg.prevState; - } - - export function gatherToScript(arg: CommonReducerArg): IMainState { - const cellVM = arg.prevState.cellVMs.find((c) => c.cell.id === arg.payload.data.cellId); - if (cellVM) { - postActionToExtension(arg, InteractiveWindowMessages.GatherCodeToScript, cellVM.cell); - } - return arg.prevState; - } - - function postModelUpdate(arg: CommonReducerArg, update: NotebookModelChange) { - postActionToExtension(arg, InteractiveWindowMessages.UpdateModel, update); - } - - export function postModelEdit( - arg: CommonReducerArg, - forward: IEditorContentChange[], - reverse: IEditorContentChange[], - id: string - ) { - postModelUpdate(arg, { - source: 'user', - kind: 'edit', - newDirty: true, - oldDirty: arg.prevState.dirty, - forward, - reverse, - id - }); - } - - export function postModelInsert( - arg: CommonReducerArg, - index: number, - cell: ICell, - codeCellAboveId?: string - ) { - postModelUpdate(arg, { - source: 'user', - kind: 'insert', - newDirty: true, - oldDirty: arg.prevState.dirty, - index, - cell, - codeCellAboveId - }); - } - - export function changeCellType(arg: CommonReducerArg, cell: ICell) { - postModelUpdate(arg, { - source: 'user', - kind: 'changeCellType', - newDirty: true, - oldDirty: arg.prevState.dirty, - cell - }); - } - - export function postModelRemove(arg: CommonReducerArg, index: number, cell: ICell) { - postModelUpdate(arg, { - source: 'user', - kind: 'remove', - oldDirty: arg.prevState.dirty, - newDirty: true, - cell, - index - }); - } - - export function postModelClearOutputs(arg: CommonReducerArg) { - postModelUpdate(arg, { - source: 'user', - kind: 'clear', - oldDirty: arg.prevState.dirty, - newDirty: true, - // tslint:disable-next-line: no-any - oldCells: arg.prevState.cellVMs.map((c) => c.cell as any) as ICell[] - }); - } - - export function postModelCellUpdate( - arg: CommonReducerArg, - newCells: ICell[], - oldCells: ICell[] - ) { - postModelUpdate(arg, { - source: 'user', - kind: 'modify', - newCells, - oldCells, - oldDirty: arg.prevState.dirty, - newDirty: true - }); - } - - export function postModelRemoveAll(arg: CommonReducerArg, newCellId: string) { - postModelUpdate(arg, { - source: 'user', - kind: 'remove_all', - oldDirty: arg.prevState.dirty, - newDirty: true, - // tslint:disable-next-line: no-any - oldCells: arg.prevState.cellVMs.map((c) => c.cell as any) as ICell[], - newCellId - }); - } - - export function postModelSwap( - arg: CommonReducerArg, - firstCellId: string, - secondCellId: string - ) { - postModelUpdate(arg, { - source: 'user', - kind: 'swap', - oldDirty: arg.prevState.dirty, - newDirty: true, - firstCellId, - secondCellId - }); - } - - export function editCell(arg: CommonReducerArg): IMainState { - const cellVM = - arg.payload.data.cellId === Identifiers.EditCellId - ? arg.prevState.editCellVM - : arg.prevState.cellVMs.find((c) => c.cell.id === arg.payload.data.cellId); - if (cellVM) { - // Tell the underlying model on the extension side - postModelEdit(arg, arg.payload.data.forward, arg.payload.data.reverse, cellVM.cell.id); - - // Update the uncommitted text on the cell view model - // We keep this saved here so we don't re-render and we put this code into the input / code data - // when focus is lost - const index = arg.prevState.cellVMs.findIndex((c) => c.cell.id === arg.payload.data.cellId); - const selectionInfo = getSelectedAndFocusedInfo(arg.prevState); - // If this is the focused cell, then user is editing it, hence it needs to be updated. - const isThisTheFocusedCell = selectionInfo.focusedCellId === arg.payload.data.cellId; - // If this edit is part of a sycning comging from another notebook, then we need to update it again. - const isSyncFromAnotherNotebook = isSyncingMessage(arg.payload.messageType); - if (index >= 0 && (isThisTheFocusedCell || isSyncFromAnotherNotebook)) { - const newVMs = [...arg.prevState.cellVMs]; - const current = arg.prevState.cellVMs[index]; - const newCell = { - ...current, - inputBlockText: arg.payload.data.code, - cell: { - ...current.cell, - data: { - ...current.cell.data, - source: arg.payload.data.code - } - }, - codeVersion: arg.payload.data.version - }; - - // tslint:disable-next-line: no-any - newVMs[index] = Helpers.asCellViewModel(newCell); // This is because IMessageCell doesn't fit in here - return { - ...arg.prevState, - cellVMs: newVMs - }; - } - } - return arg.prevState; - } - - export function started(arg: CommonReducerArg): IMainState { - // Send all of our initial requests - postActionToExtension(arg, InteractiveWindowMessages.Started); - postActionToExtension(arg, CssMessages.GetCssRequest, { isDark: arg.prevState.baseTheme !== 'vscode-light' }); - postActionToExtension(arg, CssMessages.GetMonacoThemeRequest, { - isDark: arg.prevState.baseTheme !== 'vscode-light' - }); - return arg.prevState; - } - - export function loadedAllCells(arg: CommonReducerArg): IMainState { - postActionToExtension(arg, InteractiveWindowMessages.LoadAllCellsComplete, { - cells: arg.prevState.cellVMs.map((c) => c.cell) - }); - if (!arg.prevState.isNotebookTrusted) { - // As soon as an untrusted notebook is loaded, prompt the user to trust it - postActionToExtension(arg, InteractiveWindowMessages.LaunchNotebookTrustPrompt); - } - return arg.prevState; - } - - export function gathering(arg: CommonReducerArg): IMainState { - const index = arg.prevState.cellVMs.findIndex((c) => c.cell.id === arg.payload.data.cellId); - if (index >= 0) { - const cellVMs = [...arg.prevState.cellVMs]; - const current = arg.prevState.cellVMs[index]; - const newCell: ICellViewModel = { - ...current, - gathering: arg.payload.data.gathering - }; - cellVMs[index] = newCell; - - return { - ...arg.prevState, - cellVMs - }; - } - - return arg.prevState; - } -} diff --git a/src/datascience-ui/interactive-common/redux/reducers/types.ts b/src/datascience-ui/interactive-common/redux/reducers/types.ts deleted file mode 100644 index 40b9b9c140da..000000000000 --- a/src/datascience-ui/interactive-common/redux/reducers/types.ts +++ /dev/null @@ -1,265 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import { NativeKeyboardCommandTelemetry, NativeMouseCommandTelemetry } from '../../../../client/datascience/constants'; -import { - IEditorContentChange, - InteractiveWindowMessages, - IShowDataViewer -} from '../../../../client/datascience/interactive-common/interactiveWindowTypes'; -import { BaseReduxActionPayload } from '../../../../client/datascience/interactive-common/types'; -import { IJupyterVariablesRequest } from '../../../../client/datascience/types'; -import { ActionWithPayload, ReducerArg } from '../../../react-common/reduxUtils'; -import { CursorPos, IMainState } from '../../mainState'; - -/** - * How to add a new state change: - * 1) Add a new to CommonActionType (preferably `InteractiveWindowMessages` - to keep messages in the same place). - * 2) Add a new interface (or reuse 1 below) if the action takes any parameters (ex: ICellAction) - * 3) Add a new actionCreator function (this is how you use it from a react control) to the - * appropriate actionCreator list (one for native and one for interactive). - * The creator should 'create' an instance of the action. - * 4) Add an entry into the appropriate mapping.ts. This is how the type of the list of reducers is enforced. - * 5) Add a new handler for the action under the 'reducer's folder. Handle the expected state change - * 6) Add the handler to the main reducer map in reducers\index.ts - */ -export enum CommonActionType { - ADD_AND_FOCUS_NEW_CELL = 'action.add_new_cell_and_focus_cell', - INSERT_ABOVE_AND_FOCUS_NEW_CELL = 'action.insert_above_and_focus_cell', - INSERT_BELOW_AND_FOCUS_NEW_CELL = 'action.insert_below_and_focus_cell', - INSERT_ABOVE_FIRST_AND_FOCUS_NEW_CELL = 'action.insert_above_first_and_focus_cell', - ADD_NEW_CELL = 'action.add_new_cell', - ARROW_DOWN = 'action.arrow_down', - ARROW_UP = 'action.arrow_up', - CHANGE_CELL_TYPE = 'action.change_cell_type', - CLICK_CELL = 'action.click_cell', - CODE_CREATED = 'action.code_created', - CONTINUE = 'action.continue', - COPY_CELL_CODE = 'action.copy_cell_code', - DELETE_CELL = 'action.delete_cell', - EDITOR_LOADED = 'action.editor_loaded', - EDIT_CELL = 'action.edit_cell', - EXECUTE_ABOVE = 'action.execute_above', - EXECUTE_ALL_CELLS = 'action.execute_all_cells', - EXECUTE_CELL = 'action.execute_cell', - EXECUTE_CELL_AND_ADVANCE = 'action.execute_cell_and_advance', - EXECUTE_CELL_AND_BELOW = 'action.execute_cell_and_below', - EXPORT = 'action.export', - EXPORT_NOTEBOOK_AS = 'action.export_As', - FOCUS_CELL = 'action.focus_cell', - FOCUS_INPUT = 'action.focus_input', - GATHER_CELL = 'action.gather_cell', - GATHER_CELL_TO_SCRIPT = 'action.gather_cell_to_script', - GET_VARIABLE_DATA = 'action.get_variable_data', - GOTO_CELL = 'action.goto_cell', - INSERT_ABOVE = 'action.insert_above', - INSERT_ABOVE_FIRST = 'action.insert_above_first', - INSERT_BELOW = 'action.insert_below', - INTERRUPT_KERNEL = 'action.interrupt_kernel_action', - IPYWIDGET_RENDER_FAILURE = 'action.ipywidget_render_failure', - LAUNCH_NOTEBOOK_TRUST_PROMPT = 'action.launch_notebook_trust_prompt', - LOAD_IPYWIDGET_CLASS_SUCCESS = 'action.load_ipywidget_class_success', - LOAD_IPYWIDGET_CLASS_FAILURE = 'action.load_ipywidget_class_failure', - IPYWIDGET_WIDGET_VERSION_NOT_SUPPORTED = 'action.ipywidget_widget_version_not_supported', - LOADED_ALL_CELLS = 'action.loaded_all_cells', - LINK_CLICK = 'action.link_click', - MOVE_CELL_DOWN = 'action.move_cell_down', - MOVE_CELL_UP = 'action.move_cell_up', - OPEN_SETTINGS = 'action.open_settings', - PostOutgoingMessage = 'action.postOutgoingMessage', - REFRESH_VARIABLES = 'action.refresh_variables', - RESTART_KERNEL = 'action.restart_kernel_action', - RUN_BY_LINE = 'action.run_by_line', - SAVE = 'action.save', - SCROLL = 'action.scroll', - SELECT_CELL = 'action.select_cell', - SELECT_SERVER = 'action.select_server', - SET_VARIABLE_EXPLORER_HEIGHT = 'action.set_variable_explorer_height', - SEND_COMMAND = 'action.send_command', - SHOW_DATA_VIEWER = 'action.show_data_viewer', - STEP = 'action.step', - SUBMIT_INPUT = 'action.submit_input', - TOGGLE_INPUT_BLOCK = 'action.toggle_input_block', - TOGGLE_LINE_NUMBERS = 'action.toggle_line_numbers', - TOGGLE_OUTPUT = 'action.toggle_output', - TOGGLE_VARIABLE_EXPLORER = 'action.toggle_variable_explorer', - UNFOCUS_CELL = 'action.unfocus_cell', - UNMOUNT = 'action.unmount' -} - -export type CommonActionTypeMapping = { - [CommonActionType.ADD_AND_FOCUS_NEW_CELL]: IAddCellAction; - [CommonActionType.INSERT_ABOVE]: ICellAction & IAddCellAction; - [CommonActionType.INSERT_BELOW]: ICellAction & IAddCellAction; - [CommonActionType.INSERT_ABOVE_FIRST]: IAddCellAction; - [CommonActionType.INSERT_ABOVE_FIRST_AND_FOCUS_NEW_CELL]: IAddCellAction; - [CommonActionType.INSERT_BELOW_AND_FOCUS_NEW_CELL]: ICellAction & IAddCellAction; - [CommonActionType.INSERT_ABOVE_AND_FOCUS_NEW_CELL]: ICellAction & IAddCellAction; - [CommonActionType.FOCUS_CELL]: ICellAndCursorAction; - [CommonActionType.UNFOCUS_CELL]: ICellAction | ICodeAction; - [CommonActionType.ADD_NEW_CELL]: IAddCellAction; - [CommonActionType.EDIT_CELL]: IEditCellAction; - [CommonActionType.EXECUTE_CELL_AND_ADVANCE]: IExecuteAction; - [CommonActionType.EXECUTE_CELL]: IExecuteAction; - [CommonActionType.EXECUTE_ALL_CELLS]: never | undefined; - [CommonActionType.EXECUTE_ABOVE]: ICellAction; - [CommonActionType.EXECUTE_CELL_AND_BELOW]: ICellAction; - [CommonActionType.RESTART_KERNEL]: never | undefined; - [CommonActionType.INTERRUPT_KERNEL]: never | undefined; - [CommonActionType.EXPORT]: never | undefined; - [CommonActionType.EXPORT_NOTEBOOK_AS]: never | undefined; - [CommonActionType.SAVE]: never | undefined; - [CommonActionType.SHOW_DATA_VIEWER]: IShowDataViewerAction; - [CommonActionType.SEND_COMMAND]: ISendCommandAction; - [CommonActionType.SELECT_CELL]: ICellAndCursorAction; - [CommonActionType.MOVE_CELL_UP]: ICellAction; - [CommonActionType.MOVE_CELL_DOWN]: ICellAction; - [CommonActionType.TOGGLE_LINE_NUMBERS]: ICellAction; - [CommonActionType.TOGGLE_OUTPUT]: ICellAction; - [CommonActionType.ARROW_UP]: ICodeAction; - [CommonActionType.ARROW_DOWN]: ICodeAction; - [CommonActionType.CHANGE_CELL_TYPE]: IChangeCellTypeAction; - [CommonActionType.LINK_CLICK]: ILinkClickAction; - [CommonActionType.GOTO_CELL]: ICellAction; - [CommonActionType.TOGGLE_INPUT_BLOCK]: ICellAction; - [CommonActionType.SUBMIT_INPUT]: ICodeAction; - [CommonActionType.SCROLL]: IScrollAction; - [CommonActionType.CLICK_CELL]: ICellAction; - [CommonActionType.COPY_CELL_CODE]: ICellAction; - [CommonActionType.DELETE_CELL]: ICellAction; - [CommonActionType.GATHER_CELL]: ICellAction; - [CommonActionType.GATHER_CELL_TO_SCRIPT]: ICellAction; - [CommonActionType.EDITOR_LOADED]: never | undefined; - [CommonActionType.LOADED_ALL_CELLS]: never | undefined; - [CommonActionType.UNMOUNT]: never | undefined; - [CommonActionType.SELECT_SERVER]: never | undefined; - [CommonActionType.CODE_CREATED]: ICodeCreatedAction; - [CommonActionType.GET_VARIABLE_DATA]: IJupyterVariablesRequest; - [CommonActionType.TOGGLE_VARIABLE_EXPLORER]: never | undefined; - [CommonActionType.SET_VARIABLE_EXPLORER_HEIGHT]: IVariableExplorerHeight; - [CommonActionType.PostOutgoingMessage]: never | undefined; - [CommonActionType.REFRESH_VARIABLES]: never | undefined; - [CommonActionType.OPEN_SETTINGS]: IOpenSettingsAction; - [CommonActionType.FOCUS_INPUT]: never | undefined; - [CommonActionType.LAUNCH_NOTEBOOK_TRUST_PROMPT]: never | undefined; - [CommonActionType.LOAD_IPYWIDGET_CLASS_SUCCESS]: LoadIPyWidgetClassLoadAction; - [CommonActionType.LOAD_IPYWIDGET_CLASS_FAILURE]: ILoadIPyWidgetClassFailureAction; - [CommonActionType.IPYWIDGET_WIDGET_VERSION_NOT_SUPPORTED]: NotifyIPyWidgeWidgetVersionNotSupportedAction; - [CommonActionType.IPYWIDGET_RENDER_FAILURE]: Error; - [CommonActionType.STEP]: ICellAction; - [CommonActionType.CONTINUE]: ICellAction; - [CommonActionType.RUN_BY_LINE]: ICellAction; -}; - -export interface IShowDataViewerAction extends IShowDataViewer {} - -export interface ILinkClickAction { - href: string; -} - -export interface IScrollAction { - isAtBottom: boolean; -} - -// tslint:disable-next-line: no-any -export type CommonReducerArg = ReducerArg< - IMainState, - AT, - BaseReduxActionPayload ->; - -export interface ICellAction { - cellId: string | undefined; -} - -export interface IAddCellAction { - /** - * Id of the new cell that is to be added. - * If none provided, then generate a new id. - */ - newCellId: string; -} - -export interface ICodeAction extends ICellAction { - code: string; -} - -export interface IEditCellAction extends ICodeAction { - forward: IEditorContentChange[]; - reverse: IEditorContentChange[]; - id: string; - modelId: string; - version: number; -} - -// I.e. when using the operation `add`, we need the corresponding `IAddCellAction`. -// They are mutually exclusive, if not `add`, then there's no `newCellId`. -export type IExecuteAction = ICellAction & { - moveOp: 'select' | 'none' | 'add'; -}; - -export interface ICodeCreatedAction extends ICellAction { - modelId: string; -} - -export interface ICellAndCursorAction extends ICellAction { - cursorPos: CursorPos; -} - -export interface IRefreshVariablesAction { - newExecutionCount?: number; -} - -export interface IShowDataViewerAction extends IShowDataViewer {} - -export interface ISendCommandAction { - command: NativeKeyboardCommandTelemetry | NativeMouseCommandTelemetry; -} - -export interface IChangeCellTypeAction { - cellId: string; -} - -export interface IOpenSettingsAction { - setting: string | undefined; -} - -export interface ILoadIPyWidgetClassFailureAction { - className: string; - moduleName: string; - moduleVersion: string; - cdnsUsed: boolean; - isOnline: boolean; - // tslint:disable-next-line: no-any - error: any; - timedout: boolean; -} - -export interface IVariableExplorerHeight { - containerHeight: number; - gridHeight: number; -} - -export type LoadIPyWidgetClassDisabledAction = { - className: string; - moduleName: string; - moduleVersion: string; -}; - -export type LoadIPyWidgetClassLoadAction = { - className: string; - moduleName: string; - moduleVersion: string; -}; -export type NotifyIPyWidgeWidgetVersionNotSupportedAction = { - moduleName: 'qgrid'; - moduleVersion: string; -}; - -export interface IChangeGatherStatus { - cellId: string; - gathering: boolean; -} - -export type CommonAction = ActionWithPayload; diff --git a/src/datascience-ui/interactive-common/redux/reducers/variables.ts b/src/datascience-ui/interactive-common/redux/reducers/variables.ts deleted file mode 100644 index a2b87187f9a8..000000000000 --- a/src/datascience-ui/interactive-common/redux/reducers/variables.ts +++ /dev/null @@ -1,310 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { Reducer } from 'redux'; -import { - IFinishCell, - IInteractiveWindowMapping, - InteractiveWindowMessages -} from '../../../../client/datascience/interactive-common/interactiveWindowTypes'; -import { BaseReduxActionPayload } from '../../../../client/datascience/interactive-common/types'; -import { - IJupyterVariable, - IJupyterVariablesRequest, - IJupyterVariablesResponse -} from '../../../../client/datascience/types'; -import { combineReducers, QueuableAction, ReducerArg, ReducerFunc } from '../../../react-common/reduxUtils'; -import { postActionToExtension } from '../helpers'; -import { CommonActionType, CommonActionTypeMapping, ICellAction, IVariableExplorerHeight } from './types'; - -export type IVariableState = { - currentExecutionCount: number; - visible: boolean; - sortColumn: string; - sortAscending: boolean; - variables: IJupyterVariable[]; - pageSize: number; - containerHeight: number; - gridHeight: number; - refreshCount: number; - showVariablesOnDebug: boolean; -}; - -type VariableReducerFunc = ReducerFunc< - IVariableState, - InteractiveWindowMessages, - BaseReduxActionPayload ->; -type VariableReducerArg = ReducerArg< - IVariableState, - InteractiveWindowMessages, - BaseReduxActionPayload ->; - -function handleRequest(arg: VariableReducerArg): IVariableState { - const newExecutionCount = - arg.payload.data.executionCount !== undefined - ? arg.payload.data.executionCount - : arg.prevState.currentExecutionCount; - postActionToExtension(arg, InteractiveWindowMessages.GetVariablesRequest, { - executionCount: newExecutionCount, - sortColumn: arg.payload.data.sortColumn, - startIndex: arg.payload.data.startIndex, - sortAscending: arg.payload.data.sortAscending, - pageSize: arg.payload.data.pageSize, - refreshCount: arg.payload.data.refreshCount - }); - return { - ...arg.prevState, - pageSize: Math.max(arg.prevState.pageSize, arg.payload.data.pageSize) - }; -} - -function toggleVariableExplorer(arg: VariableReducerArg): IVariableState { - const newState: IVariableState = { - ...arg.prevState, - visible: !arg.prevState.visible, - showVariablesOnDebug: false // If user does any toggling don't auto open this. - }; - - postActionToExtension(arg, InteractiveWindowMessages.VariableExplorerToggle, newState.visible); - - // If going visible for the first time, refresh our variables - if (newState.visible) { - return handleRequest({ - ...arg, - prevState: newState, - payload: { - ...arg.payload, - data: { - executionCount: arg.prevState.currentExecutionCount, - sortColumn: 'name', - sortAscending: true, - startIndex: 0, - pageSize: arg.prevState.pageSize, - refreshCount: arg.prevState.refreshCount - } - } - }); - } else { - return newState; - } -} - -function handleVariableExplorerHeightResponse(arg: VariableReducerArg): IVariableState { - if (arg.payload.data) { - const containerHeight = arg.payload.data.containerHeight; - const gridHeight = arg.payload.data.gridHeight; - if (containerHeight && gridHeight) { - return { - ...arg.prevState, - containerHeight: containerHeight, - gridHeight: gridHeight - }; - } - } - return { - ...arg.prevState - }; -} - -function setVariableExplorerHeight(arg: VariableReducerArg): IVariableState { - const containerHeight = arg.payload.data.containerHeight; - const gridHeight = arg.payload.data.gridHeight; - - if (containerHeight && gridHeight) { - postActionToExtension(arg, InteractiveWindowMessages.SetVariableExplorerHeight, { - containerHeight, - gridHeight - }); - return { - ...arg.prevState, - containerHeight: containerHeight, - gridHeight: gridHeight - }; - } - return { - ...arg.prevState - }; -} - -function handleResponse(arg: VariableReducerArg): IVariableState { - const response = arg.payload.data; - - // Check to see if we have moved to a new execution count - if ( - response.executionCount > arg.prevState.currentExecutionCount || - response.refreshCount > arg.prevState.refreshCount || - (response.executionCount === arg.prevState.currentExecutionCount && arg.prevState.variables.length === 0) || - (response.refreshCount === arg.prevState.refreshCount && arg.prevState.variables.length === 0) - ) { - // Should be an entirely new request. Make an empty list - const variables = Array(response.totalCount); - - // Replace the page with the values returned - for (let i = 0; i < response.pageResponse.length; i += 1) { - variables[i + response.pageStartIndex] = response.pageResponse[i]; - } - - // Also update the execution count. - return { - ...arg.prevState, - currentExecutionCount: response.executionCount, - refreshCount: response.refreshCount, - variables - }; - } else if ( - response.executionCount === arg.prevState.currentExecutionCount && - response.refreshCount === arg.prevState.refreshCount - ) { - // This is a response for a page in an already existing list. - const variables = [...arg.prevState.variables]; - - // See if we need to remove any from this page - const removeCount = Math.max(0, arg.prevState.variables.length - response.totalCount); - if (removeCount) { - variables.splice(response.pageStartIndex, removeCount); - } - - // Replace the page with the values returned - for (let i = 0; i < response.pageResponse.length; i += 1) { - variables[i + response.pageStartIndex] = response.pageResponse[i]; - } - - return { - ...arg.prevState, - variables - }; - } - - // Otherwise this response is for an old execution. - return arg.prevState; -} - -function handleRestarted(arg: VariableReducerArg): IVariableState { - const result = handleRequest({ - ...arg, - payload: { - ...arg.payload, - data: { - executionCount: 0, - sortColumn: 'name', - sortAscending: true, - startIndex: 0, - pageSize: arg.prevState.pageSize, - refreshCount: 0 - } - } - }); - return { - ...result, - currentExecutionCount: -1, - variables: [] - }; -} - -function handleFinishCell(arg: VariableReducerArg): IVariableState { - const executionCount = arg.payload.data.cell.data.execution_count - ? parseInt(arg.payload.data.cell.data.execution_count.toString(), 10) - : undefined; - - // If the variables are visible, refresh them - if (arg.prevState.visible && executionCount) { - return handleRequest({ - ...arg, - payload: { - ...arg.payload, - data: { - executionCount, - sortColumn: 'name', - sortAscending: true, - startIndex: 0, - pageSize: arg.prevState.pageSize, - refreshCount: arg.prevState.refreshCount - } - } - }); - } - return { - ...arg.prevState, - currentExecutionCount: executionCount ? executionCount : arg.prevState.currentExecutionCount - }; -} - -function handleRefresh(arg: VariableReducerArg): IVariableState { - // If the variables are visible, refresh them - if (arg.prevState.visible) { - return handleRequest({ - ...arg, - payload: { - ...arg.payload, - data: { - executionCount: arg.prevState.currentExecutionCount, - sortColumn: 'name', - sortAscending: true, - startIndex: 0, - pageSize: arg.prevState.pageSize, - refreshCount: arg.prevState.refreshCount + 1 - } - }, - prevState: arg.prevState - }); - } - return { - ...arg.prevState, - variables: [] - }; -} - -function handleDebugStart(arg: VariableReducerArg): IVariableState { - // If we haven't already turned on variables, do so now - if (arg.prevState.showVariablesOnDebug) { - return { - ...arg.prevState, - showVariablesOnDebug: false, - visible: true - }; - } - return arg.prevState; -} - -type VariableReducerFunctions = { - [P in keyof T]: T[P] extends never | undefined ? VariableReducerFunc : VariableReducerFunc; -}; - -type VariableActionMapping = VariableReducerFunctions & - VariableReducerFunctions; - -// Create the map between message type and the actual function to call to update state -const reducerMap: Partial = { - [InteractiveWindowMessages.RestartKernel]: handleRestarted, - [InteractiveWindowMessages.FinishCell]: handleFinishCell, - [InteractiveWindowMessages.ForceVariableRefresh]: handleRefresh, - [CommonActionType.TOGGLE_VARIABLE_EXPLORER]: toggleVariableExplorer, - [CommonActionType.SET_VARIABLE_EXPLORER_HEIGHT]: setVariableExplorerHeight, - [InteractiveWindowMessages.VariableExplorerHeightResponse]: handleVariableExplorerHeightResponse, - [CommonActionType.GET_VARIABLE_DATA]: handleRequest, - [InteractiveWindowMessages.GetVariablesResponse]: handleResponse, - [CommonActionType.RUN_BY_LINE]: handleDebugStart -}; - -export function generateVariableReducer( - showVariablesOnDebug: boolean -): Reducer>> { - // First create our default state. - const defaultState: IVariableState = { - currentExecutionCount: 0, - variables: [], - visible: false, - sortAscending: true, - sortColumn: 'name', - pageSize: 5, - containerHeight: 0, - gridHeight: 200, - refreshCount: 0, - showVariablesOnDebug - }; - - // Then combine that with our map of state change message to reducer - return combineReducers>(defaultState, reducerMap); -} diff --git a/src/datascience-ui/interactive-common/redux/store.ts b/src/datascience-ui/interactive-common/redux/store.ts deleted file mode 100644 index 7ee0e58627ee..000000000000 --- a/src/datascience-ui/interactive-common/redux/store.ts +++ /dev/null @@ -1,458 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as fastDeepEqual from 'fast-deep-equal'; -import * as path from 'path'; -import * as Redux from 'redux'; -import { createLogger } from 'redux-logger'; - -import { PYTHON_LANGUAGE } from '../../../client/common/constants'; -import { EXTENSION_ROOT_DIR } from '../../../client/constants'; -import { Identifiers } from '../../../client/datascience/constants'; -import { InteractiveWindowMessages } from '../../../client/datascience/interactive-common/interactiveWindowTypes'; -import { MessageType } from '../../../client/datascience/interactive-common/synchronization'; -import { BaseReduxActionPayload } from '../../../client/datascience/interactive-common/types'; -import { CssMessages } from '../../../client/datascience/messages'; -import { CellState } from '../../../client/datascience/types'; -import { - activeDebugState, - DebugState, - getSelectedAndFocusedInfo, - ICellViewModel, - IMainState, - ServerStatus -} from '../../interactive-common/mainState'; -import { getLocString } from '../../react-common/locReactSide'; -import { PostOffice } from '../../react-common/postOffice'; -import { combineReducers, createQueueableActionMiddleware, QueuableAction } from '../../react-common/reduxUtils'; -import { computeEditorOptions, getDefaultSettings } from '../../react-common/settingsReactSide'; -import { createEditableCellVM, generateTestState } from '../mainState'; -import { forceLoad } from '../transforms'; -import { isAllowedAction, isAllowedMessage, postActionToExtension } from './helpers'; -import { generatePostOfficeSendReducer } from './postOffice'; -import { generateMonacoReducer, IMonacoState } from './reducers/monaco'; -import { CommonActionType } from './reducers/types'; -import { generateVariableReducer, IVariableState } from './reducers/variables'; - -function generateDefaultState( - skipDefault: boolean, - testMode: boolean, - baseTheme: string, - editable: boolean -): IMainState { - if (!skipDefault) { - return generateTestState('', editable); - } else { - return { - // tslint:disable-next-line: no-typeof-undefined - skipDefault, - testMode, - baseTheme: baseTheme, - cellVMs: [], - busy: true, - undoStack: [], - redoStack: [], - submittedText: false, - currentExecutionCount: 0, - debugging: false, - knownDark: false, - dirty: false, - editCellVM: editable ? undefined : createEditableCellVM(0), - isAtBottom: true, - font: { - size: 14, - family: "Consolas, 'Courier New', monospace" - }, - codeTheme: Identifiers.GeneratedThemeName, - focusPending: 0, - monacoReady: testMode, // When testing, monaco starts out ready - loaded: false, - kernel: { - kernelName: getLocString('DataScience.noKernel', 'No Kernel'), - serverName: getLocString('DataScience.serverNotStarted', 'Not Started'), - jupyterServerStatus: ServerStatus.NotStarted, - language: PYTHON_LANGUAGE - }, - settings: testMode ? getDefaultSettings() : undefined, // When testing, we don't send (or wait) for the real settings. - editorOptions: testMode ? computeEditorOptions(getDefaultSettings()) : undefined, - isNotebookTrusted: true - }; - } -} - -function generateMainReducer( - skipDefault: boolean, - testMode: boolean, - baseTheme: string, - editable: boolean, - reducerMap: M -): Redux.Reducer> { - // First create our default state. - const defaultState = generateDefaultState(skipDefault, testMode, baseTheme, editable); - - // Then combine that with our map of state change message to reducer - return combineReducers(defaultState, reducerMap); -} - -function createSendInfoMiddleware(): Redux.Middleware<{}, IStore> { - return (store) => (next) => (action) => { - const prevState = store.getState(); - const res = next(action); - const afterState = store.getState(); - - // If the action is part of a sync message, then do not send it to the extension. - const messageType = (action?.payload as BaseReduxActionPayload).messageType ?? MessageType.other; - const isSyncMessage = - (messageType & MessageType.syncAcrossSameNotebooks) === MessageType.syncAcrossSameNotebooks && - (messageType & MessageType.syncAcrossSameNotebooks) === MessageType.syncWithLiveShare; - if (isSyncMessage) { - return res; - } - - // If cell vm count changed or selected cell changed, send the message - if (!action.type || action.type !== CommonActionType.UNMOUNT) { - const currentSelection = getSelectedAndFocusedInfo(afterState.main); - if ( - prevState.main.cellVMs.length !== afterState.main.cellVMs.length || - getSelectedAndFocusedInfo(prevState.main).selectedCellId !== currentSelection.selectedCellId || - prevState.main.undoStack.length !== afterState.main.undoStack.length || - prevState.main.redoStack.length !== afterState.main.redoStack.length - ) { - postActionToExtension({ queueAction: store.dispatch }, InteractiveWindowMessages.SendInfo, { - cellCount: afterState.main.cellVMs.length, - undoCount: afterState.main.undoStack.length, - redoCount: afterState.main.redoStack.length, - selectedCell: currentSelection.selectedCellId - }); - } - } - return res; - }; -} - -function createTestLogger() { - const logFileEnv = process.env.VSC_PYTHON_WEBVIEW_LOG_FILE; - if (logFileEnv) { - // tslint:disable-next-line: no-require-imports - const log4js = require('log4js') as typeof import('log4js'); - const logFilePath = path.isAbsolute(logFileEnv) ? logFileEnv : path.join(EXTENSION_ROOT_DIR, logFileEnv); - log4js.configure({ - appenders: { reduxLogger: { type: 'file', filename: logFilePath } }, - categories: { default: { appenders: ['reduxLogger'], level: 'debug' } } - }); - return log4js.getLogger(); - } -} - -function createTestMiddleware(): Redux.Middleware<{}, IStore> { - // Make sure all dynamic imports are loaded. - const transformPromise = forceLoad(); - - // tslint:disable-next-line: cyclomatic-complexity - return (store) => (next) => (action) => { - const prevState = store.getState(); - const res = next(action); - const afterState = store.getState(); - // tslint:disable-next-line: no-any - const sendMessage = (message: any, payload?: any) => { - setTimeout(() => { - transformPromise - .then(() => postActionToExtension({ queueAction: store.dispatch }, message, payload)) - .ignoreErrors(); - }); - }; - - if (!action.type || action.type !== CommonActionType.UNMOUNT) { - // Special case for focusing a cell - const previousSelection = getSelectedAndFocusedInfo(prevState.main); - const currentSelection = getSelectedAndFocusedInfo(afterState.main); - if (previousSelection.focusedCellId !== currentSelection.focusedCellId && currentSelection.focusedCellId) { - // Send async so happens after render state changes (so our enzyme wrapper is up to date) - sendMessage(InteractiveWindowMessages.FocusedCellEditor, { cellId: action.payload.cellId }); - } - if ( - previousSelection.selectedCellId !== currentSelection.selectedCellId && - currentSelection.selectedCellId - ) { - // Send async so happens after render state changes (so our enzyme wrapper is up to date) - sendMessage(InteractiveWindowMessages.SelectedCell, { cellId: action.payload.cellId }); - } - // Special case for unfocusing a cell - if (previousSelection.focusedCellId !== currentSelection.focusedCellId && !currentSelection.focusedCellId) { - // Send async so happens after render state changes (so our enzyme wrapper is up to date) - sendMessage(InteractiveWindowMessages.UnfocusedCellEditor); - } - } - - // Indicate settings updates - if (!fastDeepEqual(prevState.main.settings, afterState.main.settings)) { - // Send async so happens after render state changes (so our enzyme wrapper is up to date) - sendMessage(InteractiveWindowMessages.SettingsUpdated); - } - - // Indicate clean changes - if (prevState.main.dirty && !afterState.main.dirty) { - sendMessage(InteractiveWindowMessages.NotebookClean); - } - - // Indicate dirty changes - if (!prevState.main.dirty && afterState.main.dirty) { - sendMessage(InteractiveWindowMessages.NotebookDirty); - } - - // Indicate variables complete - if ( - (!fastDeepEqual(prevState.variables.variables, afterState.variables.variables) || - prevState.variables.currentExecutionCount !== afterState.variables.currentExecutionCount || - prevState.variables.refreshCount !== afterState.variables.refreshCount) && - action.type === InteractiveWindowMessages.GetVariablesResponse - ) { - sendMessage(InteractiveWindowMessages.VariablesComplete); - } - - // Indicate update from extension side - if (action.type && action.type === InteractiveWindowMessages.UpdateModel) { - sendMessage(InteractiveWindowMessages.ReceivedUpdateModel); - } - - // Special case for rendering complete - if ( - action.type && - action.type === InteractiveWindowMessages.FinishCell && - action.payload.data && - action.payload.data.cell.data?.cell_type === 'code' - ) { - // Send async so happens after the render is actually finished. - sendMessage(InteractiveWindowMessages.ExecutionRendered); - } - - if ( - !action.type || - (action.type !== InteractiveWindowMessages.FinishCell && action.type !== CommonActionType.UNMOUNT) - ) { - // Might be a non finish but still update cells (like an undo or a delete) - const prevFinished = prevState.main.cellVMs - .filter((c) => c.cell.state === CellState.finished || c.cell.state === CellState.error) - .map((c) => c.cell.id); - const afterFinished = afterState.main.cellVMs - .filter((c) => c.cell.state === CellState.finished || c.cell.state === CellState.error) - .map((c) => c.cell.id); - if ( - afterFinished.length > prevFinished.length || - (afterFinished.length !== prevFinished.length && - afterState.main.cellVMs.length !== prevState.main.cellVMs.length) - ) { - // Send async so happens after the render is actually finished. - sendMessage(InteractiveWindowMessages.ExecutionRendered); - } - } - - // Hiding/displaying output - const prevHidingOutput = prevState.main.cellVMs.filter((c) => c.hideOutput).map((c) => c.cell.id); - const afterHidingOutput = afterState.main.cellVMs.filter((c) => c.hideOutput).map((c) => c.cell.id); - if (!fastDeepEqual(prevHidingOutput, afterHidingOutput)) { - // Send async so happens after the render is actually finished. - sendMessage(InteractiveWindowMessages.OutputToggled); - } - - // Entering break state in a native cell - const prevBreak = prevState.main.cellVMs.find((cvm) => cvm.currentStack); - const newBreak = afterState.main.cellVMs.find((cvm) => cvm.currentStack); - if (prevBreak !== newBreak || !fastDeepEqual(prevBreak?.currentStack, newBreak?.currentStack)) { - sendMessage(InteractiveWindowMessages.ShowingIp); - } - - // Kernel state changing - const afterKernel = afterState.main.kernel; - const prevKernel = prevState.main.kernel; - if ( - afterKernel.jupyterServerStatus !== prevKernel.jupyterServerStatus && - afterKernel.jupyterServerStatus === ServerStatus.Idle - ) { - sendMessage(InteractiveWindowMessages.KernelIdle); - } - - // Debug state changing - const oldState = getDebugState(prevState.main.cellVMs); - const newState = getDebugState(afterState.main.cellVMs); - if (oldState !== newState) { - sendMessage(InteractiveWindowMessages.DebugStateChange, { oldState, newState }); - } - - if (action.type !== 'action.postOutgoingMessage') { - sendMessage(`DISPATCHED_ACTION_${action.type}`, {}); - } - return res; - }; -} - -// Find the debug state for cell view models -function getDebugState(vms: ICellViewModel[]): DebugState { - const firstNonDesign = vms.find((cvm) => activeDebugState(cvm.runningByLine)); - - return firstNonDesign ? firstNonDesign.runningByLine : DebugState.Design; -} - -function createMiddleWare(testMode: boolean): Redux.Middleware<{}, IStore>[] { - // Create the middleware that modifies actions to queue new actions - const queueableActions = createQueueableActionMiddleware(); - - // Create the update context middle ware. It handles the 'sendInfo' message that - // requires sending on every cell vm length change - const updateContext = createSendInfoMiddleware(); - - // Create the test middle ware. It sends messages that are used for testing only - // Or if testing in UI Test. - // tslint:disable-next-line: no-any - const acquireVsCodeApi = (window as any).acquireVsCodeApi as Function; - const isUITest = acquireVsCodeApi && acquireVsCodeApi().handleMessage ? true : false; - const testMiddleware = testMode || isUITest ? createTestMiddleware() : undefined; - - // Create the logger if we're not in production mode or we're forcing logging - const reduceLogMessage = ''; - const actionsWithLargePayload = [ - InteractiveWindowMessages.LoadOnigasmAssemblyResponse, - CssMessages.GetCssResponse, - InteractiveWindowMessages.LoadTmLanguageResponse - ]; - const logger = createLogger({ - // tslint:disable-next-line: no-any - stateTransformer: (state: any) => { - if (!state || typeof state !== 'object') { - return state; - } - // tslint:disable-next-line: no-any - const rootState = { ...state } as any; - if ('main' in rootState && typeof rootState.main === 'object') { - // tslint:disable-next-line: no-any - const main = (rootState.main = ({ ...rootState.main } as any) as Partial); - main.rootCss = reduceLogMessage; - main.rootStyle = reduceLogMessage; - // tslint:disable-next-line: no-any - main.editorOptions = reduceLogMessage as any; - // tslint:disable-next-line: no-any - main.settings = reduceLogMessage as any; - } - rootState.monaco = reduceLogMessage; - - return rootState; - }, - // tslint:disable-next-line: no-any - actionTransformer: (action: any) => { - if (!action) { - return action; - } - if (actionsWithLargePayload.indexOf(action.type) >= 0) { - return { ...action, payload: reduceLogMessage }; - } - return action; - }, - logger: testMode ? createTestLogger() : window.console - }); - const loggerMiddleware = - process.env.VSC_PYTHON_FORCE_LOGGING !== undefined && !process.env.VSC_PYTHON_DS_NO_REDUX_LOGGING - ? logger - : undefined; - - const results: Redux.Middleware<{}, IStore>[] = []; - results.push(queueableActions); - results.push(updateContext); - if (testMiddleware) { - results.push(testMiddleware); - } - if (loggerMiddleware) { - results.push(loggerMiddleware); - } - - return results; -} - -export interface IStore { - main: IMainState; - variables: IVariableState; - monaco: IMonacoState; - post: {}; -} - -export interface IMainWithVariables extends IMainState { - variableState: IVariableState; -} - -/** - * Middleware that will ensure all actions have `messageDirection` property. - */ -const addMessageDirectionMiddleware: Redux.Middleware = (_store) => (next) => (action: Redux.AnyAction) => { - if (isAllowedAction(action)) { - // Ensure all dispatched messages have been flagged as `incoming`. - const payload: BaseReduxActionPayload<{}> = action.payload || {}; - if (!payload.messageDirection) { - action.payload = { ...payload, messageDirection: 'incoming' }; - } - } - - return next(action); -}; - -export function createStore( - skipDefault: boolean, - baseTheme: string, - testMode: boolean, - editable: boolean, - showVariablesOnDebug: boolean, - reducerMap: M, - postOffice: PostOffice -) { - // Create reducer for the main react UI - const mainReducer = generateMainReducer(skipDefault, testMode, baseTheme, editable, reducerMap); - - // Create reducer to pass window messages to the other side - const postOfficeReducer = generatePostOfficeSendReducer(postOffice); - - // Create another reducer for handling monaco state - const monacoReducer = generateMonacoReducer(testMode, postOffice); - - // Create another reducer for handling variable state - const variableReducer = generateVariableReducer(showVariablesOnDebug); - - // Combine these together - const rootReducer = Redux.combineReducers({ - main: mainReducer, - variables: variableReducer, - monaco: monacoReducer, - post: postOfficeReducer - }); - - // Create our middleware - const middleware = createMiddleWare(testMode).concat([addMessageDirectionMiddleware]); - - // Use this reducer and middle ware to create a store - const store = Redux.createStore(rootReducer, Redux.applyMiddleware(...middleware)); - - // Make all messages from the post office dispatch to the store, changing the type to - // turn them into actions. - postOffice.addHandler({ - // tslint:disable-next-line: no-any - handleMessage(message: string, payload?: any): boolean { - // Double check this is one of our messages. React will actually post messages here too during development - if (isAllowedMessage(message)) { - const basePayload: BaseReduxActionPayload = { data: payload }; - if (message === InteractiveWindowMessages.Sync) { - // This is a message that has been sent from extension purely for synchronization purposes. - // Unwrap the message. - message = payload.type; - // This is a message that came in as a result of an outgoing message from another view. - basePayload.messageDirection = 'outgoing'; - basePayload.messageType = payload.payload.messageType ?? MessageType.syncAcrossSameNotebooks; - basePayload.data = payload.payload.data; - } else { - // Messages result of some user action. - basePayload.messageType = basePayload.messageType ?? MessageType.other; - } - store.dispatch({ type: message, payload: basePayload }); - } - return true; - } - }); - - return store; -} diff --git a/src/datascience-ui/interactive-common/tokenizer.ts b/src/datascience-ui/interactive-common/tokenizer.ts deleted file mode 100644 index a1e353ed715f..000000000000 --- a/src/datascience-ui/interactive-common/tokenizer.ts +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { wireTmGrammars } from 'monaco-editor-textmate'; -import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; -import { Registry } from 'monaco-textmate'; -import { loadWASM } from 'onigasm'; - -// Map of grammars to tmlanguage contents -const grammarMap = new Map(); - -// Map of language ids to scope names -const languageMap = new Map(); - -async function getGrammarDefinition(scopeName: string) { - const mappedGrammar = grammarMap.get(scopeName); - if (mappedGrammar) { - return { - format: 'json', - content: mappedGrammar - }; - } - return { - format: 'json', - content: '{}' - // tslint:disable-next-line: no-any - } as any; -} - -// Loading the onigasm bundles is process wide, so don't bother doing it more than once. Creates memory leaks -// when running tests. -let onigasmData: ArrayBuffer | undefined; -let loadedOnigasm = false; - -// Global registry for grammars -const registry = new Registry({ getGrammarDefinition: getGrammarDefinition }); - -export namespace Tokenizer { - // Export for loading language data. - export async function loadLanguage( - languageId: string, - extensions: string[], - scopeName: string, - config: monacoEditor.languages.LanguageConfiguration, - languageJSON: string - ) { - // See if this language was already registered or not. - if (!grammarMap.has(scopeName)) { - grammarMap.set(scopeName, languageJSON); - monacoEditor.languages.register({ id: languageId, extensions }); - monacoEditor.languages.setLanguageConfiguration(languageId, config); - - // Load the web assembly if necessary - if (onigasmData && onigasmData.byteLength !== 0 && !loadedOnigasm) { - loadedOnigasm = true; - await loadWASM(onigasmData); - } - - // add scope map - languageMap.set(languageId, scopeName); - - // Wire everything together. - await wireTmGrammars(monacoEditor, registry, languageMap); - } - } - - // Export for saving onigasm data - export function loadOnigasm(onigasm: ArrayBuffer) { - if (!onigasmData) { - onigasmData = onigasm; - } - } - - export function hasOnigasm(): boolean { - return loadedOnigasm; - } - - export function hasLanguage(languageId: string): boolean { - return languageMap.has(languageId); - } -} diff --git a/src/datascience-ui/interactive-common/transforms.tsx b/src/datascience-ui/interactive-common/transforms.tsx deleted file mode 100644 index d9ac734a6e72..000000000000 --- a/src/datascience-ui/interactive-common/transforms.tsx +++ /dev/null @@ -1,170 +0,0 @@ -/* tslint:disable */ -import * as React from 'react'; -import Loadable, { LoadableComponent } from '@loadable/component'; -import { getLocString } from '../react-common/locReactSide'; - -class TransformData { - private cachedPromise: undefined | Promise; - constructor(public mimeType: string, private importer: () => Promise) {} - public getComponent(): Promise { - if (!this.cachedPromise) { - this.cachedPromise = this.importer(); - } - return this.cachedPromise; - } -} - -// Hardcode mimeType here so we can do a quick lookup without loading all of the -// other components. -const mimeTypeToImport: TransformData[] = [ - new TransformData('application/vnd.vega.v2+json', async () => { - const module = await import(/* webpackChunkName: "vega" */ '@nteract/transform-vega'); - return module.Vega2; - }), - new TransformData('application/vnd.vega.v3+json', async () => { - const module = await import(/* webpackChunkName: "vega" */ '@nteract/transform-vega'); - return module.Vega3; - }), - new TransformData('application/vnd.vega.v4+json', async () => { - const module = await import(/* webpackChunkName: "vega" */ '@nteract/transform-vega'); - return module.Vega4; - }), - new TransformData('application/vnd.vega.v5+json', async () => { - const module = await import(/* webpackChunkName: "vega" */ '@nteract/transform-vega'); - return module.Vega5; - }), - new TransformData('application/vnd.vegalite.v1+json', async () => { - const module = await import(/* webpackChunkName: "vega" */ '@nteract/transform-vega'); - return module.VegaLite1; - }), - new TransformData('application/vnd.vegalite.v2+json', async () => { - const module = await import(/* webpackChunkName: "vega" */ '@nteract/transform-vega'); - return module.VegaLite2; - }), - new TransformData('application/vnd.vegalite.v3+json', async () => { - const module = await import(/* webpackChunkName: "vega" */ '@nteract/transform-vega'); - return module.VegaLite3; - }), - new TransformData('application/vnd.vegalite.v4+json', async () => { - const module = await import(/* webpackChunkName: "vega" */ '@nteract/transform-vega'); - return module.VegaLite3; - }), - new TransformData('application/geo+json', async () => { - const module = await import(/* webpackChunkName: "geojson" */ '@nteract/transform-geojson'); - return module.GeoJSONTransform; - }), - new TransformData('application/vnd.dataresource+json', async () => { - const module = await import(/* webpackChunkName: "dataresource" */ '@nteract/transform-dataresource'); - return module.DataResourceTransform; - }), - new TransformData('application/x-nteract-model-debug+json', async () => { - const module = await import(/* webpackChunkName: "modeldebug" */ '@nteract/transform-model-debug'); - return module.default; - }), - new TransformData('text/vnd.plotly.v1+html', async () => { - const module = await import(/* webpackChunkName: "plotly" */ '@nteract/transform-plotly'); - return module.PlotlyNullTransform; - }), - new TransformData('application/vnd.plotly.v1+json', async () => { - const module = await import(/* webpackChunkName: "plotly" */ '@nteract/transform-plotly'); - return module.PlotlyTransform; - }), - new TransformData('image/svg+xml', async () => { - const module = await import(/* webpackChunkName: "nteract_transforms" */ '@nteract/transforms'); - return module.SVGTransform; - }), - new TransformData('image/png', async () => { - const module = await import(/* webpackChunkName: "nteract_transforms" */ '@nteract/transforms'); - return module.PNGTransform; - }), - new TransformData('image/gif', async () => { - const module = await import(/* webpackChunkName: "nteract_transforms" */ '@nteract/transforms'); - return module.GIFTransform; - }), - new TransformData('image/jpeg', async () => { - const module = await import(/* webpackChunkName: "nteract_transforms" */ '@nteract/transforms'); - return module.JPEGTransform; - }), - new TransformData('application/json', async () => { - const module = await import(/* webpackChunkName: "nteract_transforms" */ '@nteract/transforms'); - return module.JSONTransform; - }), - new TransformData('application/javascript', async () => { - const module = await import(/* webpackChunkName: "nteract_transforms" */ '@nteract/transforms'); - return module.JavaScriptTransform; - }), - new TransformData('application/vdom.v1+json', async () => { - const module = await import(/* webpackChunkName: "nteract_transforms_vsdom" */ '@nteract/transform-vdom'); - return module.VDOM; - }), - new TransformData('text/markdown', async () => { - const module = await import(/* webpackChunkName: "nteract_transforms" */ '@nteract/transforms'); - return module.MarkdownTransform; - }), - new TransformData('text/latex', async () => { - const module = await import(/* webpackChunkName: "nteract_transforms" */ '@nteract/transforms'); - return module.LaTeXTransform; - }), - new TransformData('text/html', async () => { - const module = await import(/* webpackChunkName: "nteract_transforms" */ '@nteract/transforms'); - return module.HTMLTransform; - }), - new TransformData('text/plain', async () => { - const module = await import(/* webpackChunkName: "nteract_transforms" */ '@nteract/transforms'); - return module.TextTransform; - }) -]; - -export function getRichestMimetype(data: any): string { - // Go through the keys of this object and find their index in the map - let index = mimeTypeToImport.length; - const keys = Object.keys(data); - keys.forEach((k) => { - const keyIndex = mimeTypeToImport.findIndex((m) => m.mimeType === k); - if (keyIndex >= 0 && keyIndex < index) { - // If higher up the chain, pick the higher up key - index = keyIndex; - } - }); - - // If this index is found, return the mimetype to use. - if (index < mimeTypeToImport.length) { - return mimeTypeToImport[index].mimeType; - } - - // Don't know which to pick, just pick the first. - return keys[0]; -} - -export function getTransform(mimeType: string): LoadableComponent<{ data: any }> { - return Loadable<{ data: any }>( - async () => { - const match = mimeTypeToImport.find((m) => m.mimeType === mimeType); - if (match) { - const transform = await match.getComponent(); - return transform; - } - - return
`Transform not found for mimetype ${mimeType}`
; - }, - { fallback:
{getLocString('DataScience.variableLoadingValue', 'Loading...')}
} - ); -} - -export async function forceLoad() { - // Used for tests to make sure we don't end up with 'Loading ...' anywhere in a test - await Promise.all(mimeTypeToImport.map((m) => m.getComponent())); -} - -export function isMimeTypeSupported(mimeType: string): boolean { - const match = mimeTypeToImport.find((m) => m.mimeType === mimeType); - return match ? true : false; -} - -export function isIPyWidgetOutput(data: {}): boolean { - return ( - data && - (data as Object).hasOwnProperty && - (data as Object).hasOwnProperty('application/vnd.jupyter.widget-view+json') - ); -} diff --git a/src/datascience-ui/interactive-common/trimmedOutputLink.tsx b/src/datascience-ui/interactive-common/trimmedOutputLink.tsx deleted file mode 100644 index 9300356204c1..000000000000 --- a/src/datascience-ui/interactive-common/trimmedOutputLink.tsx +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import * as React from 'react'; -import { getLocString } from '../react-common/locReactSide'; - -interface ITrimmedOutputMessage { - openSettings(setting?: string): void; -} - -export class TrimmedOutputMessage extends React.PureComponent { - constructor(props: ITrimmedOutputMessage) { - super(props); - } - - public render() { - const newLine = '\n...\n'; - return ( - - {getLocString( - 'DataScience.trimmedOutput', - 'Output was trimmed for performance reasons.\nTo see the full output set the setting "python.dataScience.textOutputLimit" to 0.' - ) + newLine} - - ); - } - private changeTextOutputLimit = () => { - this.props.openSettings('python.dataScience.textOutputLimit'); - }; -} diff --git a/src/datascience-ui/interactive-common/trustMessage.tsx b/src/datascience-ui/interactive-common/trustMessage.tsx deleted file mode 100644 index c97a97e6c6cb..000000000000 --- a/src/datascience-ui/interactive-common/trustMessage.tsx +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as React from 'react'; -import { getLocString } from '../react-common/locReactSide'; -import { getMaxWidth } from './utils'; - -interface ITrustMessageProps { - shouldShowTrustMessage: boolean; - isNotebookTrusted?: boolean; - launchNotebookTrustPrompt?(): void; // Native editor-specific -} - -export class TrustMessage extends React.PureComponent { - public render() { - const text = this.props.isNotebookTrusted - ? getLocString('DataScience.notebookIsTrusted', 'Trusted') - : getLocString('DataScience.notebookIsNotTrusted', 'Not Trusted'); - const textSize = text.length; - const maxWidth: React.CSSProperties = { - maxWidth: getMaxWidth(textSize + 5) // plus 5 for the line and margins, - }; - const dynamicStyle: React.CSSProperties = { - maxWidth: getMaxWidth(textSize), - color: this.props.isNotebookTrusted - ? 'var(--vscode-editor-foreground)' - : 'var(--vscode-editorError-foreground)', - cursor: this.props.isNotebookTrusted ? undefined : 'pointer' - }; - - return ( -
- -
-
- ); - } -} diff --git a/src/datascience-ui/interactive-common/utils.ts b/src/datascience-ui/interactive-common/utils.ts deleted file mode 100644 index 11e62d3b8a70..000000000000 --- a/src/datascience-ui/interactive-common/utils.ts +++ /dev/null @@ -1,6 +0,0 @@ -export function getMaxWidth(charLength: number): string { - // This comes from a linear regression - const width = 0.57674 * charLength + 1.70473; - const unit = 'em'; - return Math.round(width).toString() + unit; -} diff --git a/src/datascience-ui/interactive-common/variableExplorer.css b/src/datascience-ui/interactive-common/variableExplorer.css deleted file mode 100644 index 5a47b522b165..000000000000 --- a/src/datascience-ui/interactive-common/variableExplorer.css +++ /dev/null @@ -1,24 +0,0 @@ -.variable-explorer { - margin: 5px; - background: var(--override-background, var(--vscode-editor-background)); - color: var(--override-foreground, var(--vscode-editor-foreground)); - overflow: hidden; -} - -#variable-explorer-data-grid { - margin: 4px; -} - -.span-debug-message { - margin: 4px; -} - -#variable-divider { - width: 100%; - border-top-color: var(--override-badge-background, var(--vscode-badge-background)); - border-top-style: solid; - border-top-width: 2px; - cursor: ns-resize; - z-index: 999; - position: fixed; -} diff --git a/src/datascience-ui/interactive-common/variableExplorer.tsx b/src/datascience-ui/interactive-common/variableExplorer.tsx deleted file mode 100644 index 7d4ea054d751..000000000000 --- a/src/datascience-ui/interactive-common/variableExplorer.tsx +++ /dev/null @@ -1,460 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import './variableExplorer.css'; - -import * as fastDeepEqual from 'fast-deep-equal'; -import * as React from 'react'; - -import { RegExpValues } from '../../client/datascience/constants'; -import { IJupyterVariable } from '../../client/datascience/types'; -import { Image, ImageName } from '../react-common/image'; -import { ImageButton } from '../react-common/imageButton'; -import { getLocString } from '../react-common/locReactSide'; -import { IButtonCellValue, VariableExplorerButtonCellFormatter } from './variableExplorerButtonCellFormatter'; -import { CellStyle, VariableExplorerCellFormatter } from './variableExplorerCellFormatter'; -import { VariableExplorerEmptyRowsView } from './variableExplorerEmptyRows'; - -import * as AdazzleReactDataGrid from 'react-data-grid'; -import { VariableExplorerHeaderCellFormatter } from './variableExplorerHeaderCellFormatter'; -import { VariableExplorerRowRenderer } from './variableExplorerRowRenderer'; - -// tslint:disable-next-line: import-name -import Draggable from 'react-draggable'; - -import { IVariableState } from './redux/reducers/variables'; -import './variableExplorerGrid.less'; - -interface IVariableExplorerProps { - baseTheme: string; - skipDefault?: boolean; - variables: IJupyterVariable[]; - debugging: boolean; - supportsDebugging: boolean; - fontSize: number; - executionCount: number; - refreshCount: number; - offsetHeight: number; - gridHeight: number; - containerHeight: number; - showDataExplorer(targetVariable: IJupyterVariable, numberOfColumns: number): void; - closeVariableExplorer(): void; - setVariableExplorerHeight(containerHeight: number, gridHeight: number): void; - pageIn(startIndex: number, pageSize: number): void; -} - -const defaultColumnProperties = { - filterable: false, - sortable: false, - resizable: true -}; - -interface IFormatterArgs { - isScrolling?: boolean; - value?: string | number | object | boolean; - row?: IGridRow; -} - -interface IGridRow { - // tslint:disable-next-line:no-any - name: string; - type: string; - size: string; - value: string | undefined; - index: number; - buttons: IButtonCellValue; -} - -interface IVariableExplorerState { - containerHeight: number; - gridHeight: number; -} - -// tslint:disable:no-any -export class VariableExplorer extends React.Component { - private variableExplorerRef: React.RefObject; - private variableExplorerMenuBarRef: React.RefObject; - private variablePanelRef: React.RefObject; - - private pageSize: number = -1; - - // These values keep track of variable requests so we don't make the same ones over and over again - // Note: This isn't in the redux state because the requests will come before the state - // has been updated. We don't want to force a wait for redraw to determine if a request - // has been sent or not. - private requestedPages: number[] = []; - private requestedPagesExecutionCount: number = 0; - private requestedRefreshCount: number = 0; - private gridColumns: { - key: string; - name: string; - type: string; - width: number; - formatter: any; - headerRenderer?: JSX.Element; - sortable?: boolean; - resizable?: boolean; - }[]; - - constructor(prop: IVariableExplorerProps) { - super(prop); - - this.state = { - containerHeight: this.props.containerHeight, - gridHeight: this.props.gridHeight - }; - - this.handleResizeMouseMove = this.handleResizeMouseMove.bind(this); - this.setInitialHeight = this.setInitialHeight.bind(this); - this.saveCurrentSize = this.saveCurrentSize.bind(this); - - this.gridColumns = [ - { - key: 'buttons', - name: '', - type: 'boolean', - width: 36, - sortable: false, - resizable: false, - formatter: ( - - ) - }, - { - key: 'name', - name: getLocString('DataScience.variableExplorerNameColumn', 'Name'), - type: 'string', - width: 120, - formatter: this.formatNameColumn, - headerRenderer: - }, - { - key: 'type', - name: getLocString('DataScience.variableExplorerTypeColumn', 'Type'), - type: 'string', - width: 120, - formatter: , - headerRenderer: - }, - { - key: 'size', - name: getLocString('DataScience.variableExplorerCountColumn', 'Size'), - type: 'string', - width: 120, - formatter: , - headerRenderer: - }, - { - key: 'value', - name: getLocString('DataScience.variableExplorerValueColumn', 'Value'), - type: 'string', - width: 300, - formatter: , - headerRenderer: - } - ]; - - this.variableExplorerRef = React.createRef(); - this.variablePanelRef = React.createRef(); - this.variableExplorerMenuBarRef = React.createRef(); - } - - public componentDidMount() { - if (this.state.containerHeight === 0) { - this.setInitialHeight(); - } - } - - public shouldComponentUpdate(nextProps: IVariableExplorerProps, prevState: IVariableState): boolean { - if (this.props.fontSize !== nextProps.fontSize) { - // Size has changed, recompute page size - this.pageSize = -1; - return true; - } - if (!fastDeepEqual(this.props.variables, nextProps.variables)) { - return true; - } - if ( - prevState.containerHeight !== this.state.containerHeight || - prevState.gridHeight !== this.state.gridHeight - ) { - return true; - } - - return false; - } - - public render() { - const contentClassName = `variable-explorer-content`; - const containerHeight = this.state.containerHeight; - let variableExplorerStyles: React.CSSProperties = { fontSize: `${this.props.fontSize.toString()}px` }; - - // add properties to explorer styles - if (containerHeight !== 0) { - variableExplorerStyles = { ...variableExplorerStyles, height: containerHeight }; - } - - return ( - - -
-
-
-
- - - - -
-
{this.renderGrid()}
-
-
-
-
- - - ); - } - - private renderGrid() { - if (this.props.debugging && !this.props.supportsDebugging) { - return ( - - {getLocString( - 'DataScience.variableExplorerDisabledDuringDebugging', - "Please see the Debug Side Bar's VARIABLES section." - )} - - ); - } else { - return ( -
- { - return { ...defaultColumnProperties, ...c }; - })} - // tslint:disable-next-line: react-this-binding-issue - rowGetter={this.getRow} - rowsCount={this.props.variables.length} - minHeight={this.state.gridHeight} - headerRowHeight={this.getRowHeight()} - rowHeight={this.getRowHeight()} - onRowDoubleClick={this.rowDoubleClick} - emptyRowsView={VariableExplorerEmptyRowsView} - rowRenderer={VariableExplorerRowRenderer} - /> -
- ); - } - } - - private saveCurrentSize() { - this.props.setVariableExplorerHeight(this.state.containerHeight, this.state.gridHeight); - } - - private getRowHeight() { - return this.props.fontSize + 11; - } - - private setInitialHeight() { - const variablePanel = this.variablePanelRef.current; - if (!variablePanel) { - return; - } - this.setState({ - containerHeight: variablePanel.offsetHeight - }); - } - - private handleResizeMouseMove(e: any) { - this.setVariableExplorerHeight(e); - this.setVariableGridHeight(); - } - - private setVariableExplorerHeight(e: MouseEvent) { - const variableExplorerMenuBar = this.variableExplorerMenuBarRef.current; - const variablePanel = this.variablePanelRef.current; - const variableExplorer = this.variableExplorerRef.current; - - if (!variableExplorerMenuBar || !variablePanel || !variableExplorer) { - return; - } - - const relY = e.pageY - variableExplorer.clientTop; - const addHeight = relY - variablePanel.offsetHeight - this.props.offsetHeight; - const updatedHeight = this.state.containerHeight + addHeight; - - // min height is one row of visible data - const minHeight = this.getRowHeight() * 2 + variableExplorerMenuBar.clientHeight; - const maxHeight = document.body.scrollHeight - this.props.offsetHeight - variableExplorerMenuBar.clientHeight; - - if (updatedHeight >= minHeight && updatedHeight <= maxHeight) { - this.setState({ - containerHeight: updatedHeight - }); - } - } - - private setVariableGridHeight() { - const variableExplorerMenuBar = this.variableExplorerMenuBarRef.current; - - if (!variableExplorerMenuBar) { - return; - } - - const updatedHeight = this.state.containerHeight - variableExplorerMenuBar.clientHeight; - - this.setState({ - gridHeight: updatedHeight - }); - } - - private formatNameColumn = (args: IFormatterArgs): JSX.Element => { - if (!args.isScrolling && args.row !== undefined && !args.value) { - this.ensureLoaded(args.row.index); - } - - return ; - }; - - private getRow = (index: number): IGridRow => { - if (index >= 0 && index < this.props.variables.length) { - const variable = this.props.variables[index]; - if (variable && variable.value) { - let newSize = ''; - if (variable.shape && variable.shape !== '') { - newSize = variable.shape; - } else if (variable.count) { - newSize = variable.count.toString(); - } - return { - buttons: { - name: variable.name, - supportsDataExplorer: variable.supportsDataExplorer, - variable, - numberOfColumns: this.getColumnCountFromShape(variable.shape) - }, - name: variable.name, - type: variable.type, - size: newSize, - index, - value: variable.value - ? variable.value - : getLocString('DataScience.variableLoadingValue', 'Loading...') - }; - } - } - - return { - buttons: { supportsDataExplorer: false, name: '', numberOfColumns: 0, variable: undefined }, - name: '', - type: '', - size: '', - index, - value: getLocString('DataScience.variableLoadingValue', 'Loading...') - }; - }; - - private computePageSize(): number { - if (this.pageSize === -1) { - // Based on font size and height of the main div - if (this.variableExplorerRef.current) { - this.pageSize = Math.max( - 16, - Math.round(this.variableExplorerRef.current.offsetHeight / this.props.fontSize) - ); - } else { - this.pageSize = 50; - } - } - return this.pageSize; - } - - private ensureLoaded = (index: number) => { - // Figure out how many items in a page - const pageSize = this.computePageSize(); - - // Skip if already pending or already have a value - const haveValue = this.props.variables[index]?.value; - const newExecution = - this.props.executionCount !== this.requestedPagesExecutionCount || - this.props.refreshCount !== this.requestedRefreshCount; - // tslint:disable-next-line: restrict-plus-operands - const notRequested = !this.requestedPages.find((n) => n <= index && index < n + pageSize); - if (!haveValue && (newExecution || notRequested)) { - // Try to find a page of data around this index. - let pageIndex = index; - while ( - pageIndex >= 0 && - pageIndex > index - pageSize / 2 && - (!this.props.variables[pageIndex] || !this.props.variables[pageIndex].value) - ) { - pageIndex -= 1; - } - - // Clear out requested pages if new requested execution - if ( - this.requestedPagesExecutionCount !== this.props.executionCount || - this.requestedRefreshCount !== this.props.refreshCount - ) { - this.requestedPages = []; - } - - // Save in the list of requested pages - this.requestedPages.push(pageIndex + 1); - - // Save the execution count for this request so we can verify we can skip it on next request. - this.requestedPagesExecutionCount = this.props.executionCount; - this.requestedRefreshCount = this.props.refreshCount; - - // Load this page. - this.props.pageIn(pageIndex + 1, pageSize); - } - }; - - private getColumnCountFromShape(shape: string | undefined): number { - if (shape) { - // Try to match on the second value if there is one - const matches = RegExpValues.ShapeSplitterRegEx.exec(shape); - if (matches && matches.length > 1) { - return parseInt(matches[1], 10); - } - } - return 0; - } - - private rowDoubleClick = (_rowIndex: number, row: IGridRow) => { - // On row double click, see if data explorer is supported and open it if it is - if ( - row.buttons && - row.buttons.supportsDataExplorer !== undefined && - row.buttons.name && - row.buttons.supportsDataExplorer && - row.buttons.variable - ) { - this.props.showDataExplorer(row.buttons.variable, row.buttons.numberOfColumns); - } - }; -} diff --git a/src/datascience-ui/interactive-common/variableExplorerButtonCellFormatter.css b/src/datascience-ui/interactive-common/variableExplorerButtonCellFormatter.css deleted file mode 100644 index 8ad219b7558c..000000000000 --- a/src/datascience-ui/interactive-common/variableExplorerButtonCellFormatter.css +++ /dev/null @@ -1,4 +0,0 @@ -.variable-explorer-button-cell { - height: 18px; - width: 18px; -} diff --git a/src/datascience-ui/interactive-common/variableExplorerButtonCellFormatter.tsx b/src/datascience-ui/interactive-common/variableExplorerButtonCellFormatter.tsx deleted file mode 100644 index cda13175141f..000000000000 --- a/src/datascience-ui/interactive-common/variableExplorerButtonCellFormatter.tsx +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as React from 'react'; - -import { Image, ImageName } from '../react-common/image'; -import { ImageButton } from '../react-common/imageButton'; -import { getLocString } from '../react-common/locReactSide'; - -import { IJupyterVariable } from '../../client/datascience/types'; -import './variableExplorerButtonCellFormatter.css'; - -export interface IButtonCellValue { - supportsDataExplorer: boolean; - name: string; - variable?: IJupyterVariable; - numberOfColumns: number; -} - -interface IVariableExplorerButtonCellFormatterProps { - baseTheme: string; - value?: IButtonCellValue; - showDataExplorer(targetVariable: IJupyterVariable, numberOfColumns: number): void; -} - -export class VariableExplorerButtonCellFormatter extends React.Component { - public shouldComponentUpdate(nextProps: IVariableExplorerButtonCellFormatterProps) { - return nextProps.value !== this.props.value; - } - - public render() { - const className = 'variable-explorer-button-cell'; - if (this.props.value !== null && this.props.value !== undefined) { - if (this.props.value.supportsDataExplorer) { - return ( -
- - - -
- ); - } else { - return null; - } - } - return []; - } - - private onDataExplorerClick = () => { - if (this.props.value !== null && this.props.value !== undefined && this.props.value.variable) { - this.props.showDataExplorer(this.props.value.variable, this.props.value.numberOfColumns); - } - }; -} diff --git a/src/datascience-ui/interactive-common/variableExplorerCellFormatter.css b/src/datascience-ui/interactive-common/variableExplorerCellFormatter.css deleted file mode 100644 index ffdc679ccacb..000000000000 --- a/src/datascience-ui/interactive-common/variableExplorerCellFormatter.css +++ /dev/null @@ -1,32 +0,0 @@ -.react-grid-variable-explorer-cell-variable { - color: var(--code-variable-color, var(--vscode-editor-foreground)); -} - -.react-grid-variable-explorer-cell-type { - color: var(--code-type-color, var(--vscode-editor-foreground)); -} - -.react-grid-variable-explorer-cell-string { - color: var(--code-string-color, var(--vscode-editor-foreground)); -} - -.react-grid-variable-explorer-cell-numeric { - color: var(--code-numeric-color, var(--vscode-editor-foreground)); -} - -/* High contrast uses an inverse for selection highlights, so just use foreground when hovering */ -body.vscode-high-contrast .react-grid-Row:hover .react-grid-variable-explorer-cell-variable { - color: var(--override-selection-foreground, var(--vscode-editor-selectionForeground)); -} - -body.vscode-high-contrast .react-grid-Row:hover .react-grid-variable-explorer-cell-type { - color: var(--override-selection-foreground, var(--vscode-editor-selectionForeground)); -} - -body.vscode-high-contrast .react-grid-Row:hover .react-grid-variable-explorer-cell-string { - color: var(--override-selection-foreground, var(--vscode-editor-selectionForeground)); -} - -body.vscode-high-contrast .react-grid-Row:hover .react-grid-variable-explorer-cell-numeric { - color: var(--override-selection-foreground, var(--vscode-editor-selectionForeground)); -} \ No newline at end of file diff --git a/src/datascience-ui/interactive-common/variableExplorerCellFormatter.tsx b/src/datascience-ui/interactive-common/variableExplorerCellFormatter.tsx deleted file mode 100644 index eea7b1db1b66..000000000000 --- a/src/datascience-ui/interactive-common/variableExplorerCellFormatter.tsx +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import './variableExplorerCellFormatter.css'; - -import * as React from 'react'; - -export enum CellStyle { - variable = 'variable', - type = 'type', - string = 'string', - numeric = 'numeric' -} - -interface IVariableExplorerCellFormatterProps { - cellStyle: CellStyle; - // value gets populated by the default cell formatter props - value?: string | number | object | boolean; - role?: string; -} - -// Our formatter for cells in the variable explorer. Allow for different styles per column type -export class VariableExplorerCellFormatter extends React.Component { - public shouldComponentUpdate(nextProps: IVariableExplorerCellFormatterProps) { - return nextProps.value !== this.props.value; - } - - public render() { - const className = `react-grid-variable-explorer-cell-${this.props.cellStyle.toString()}`; - if (this.props.value !== null && this.props.value !== undefined) { - return ( -
- {this.props.value} -
- ); - } - return []; - } -} diff --git a/src/datascience-ui/interactive-common/variableExplorerEmptyRows.css b/src/datascience-ui/interactive-common/variableExplorerEmptyRows.css deleted file mode 100644 index 23fe2407049e..000000000000 --- a/src/datascience-ui/interactive-common/variableExplorerEmptyRows.css +++ /dev/null @@ -1,4 +0,0 @@ -#variable-explorer-empty-rows { - margin: 5px; - font-family: var(--code-font-family); -} \ No newline at end of file diff --git a/src/datascience-ui/interactive-common/variableExplorerEmptyRows.tsx b/src/datascience-ui/interactive-common/variableExplorerEmptyRows.tsx deleted file mode 100644 index 1668e1d35c99..000000000000 --- a/src/datascience-ui/interactive-common/variableExplorerEmptyRows.tsx +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import './variableExplorerEmptyRows.css'; - -import * as React from 'react'; -import { getLocString } from '../react-common/locReactSide'; - -export const VariableExplorerEmptyRowsView = () => { - const message = getLocString('DataScience.noRowsInVariableExplorer', 'No variables defined'); - - return
{message}
; -}; diff --git a/src/datascience-ui/interactive-common/variableExplorerGrid.less b/src/datascience-ui/interactive-common/variableExplorerGrid.less deleted file mode 100644 index eb261d0ba265..000000000000 --- a/src/datascience-ui/interactive-common/variableExplorerGrid.less +++ /dev/null @@ -1,127 +0,0 @@ -/* Import bootstrap, but prefix it all with our grid div so we don't clobber our interactive windows styles */ -#variable-explorer-data-grid { - @import "~bootstrap-less/bootstrap/bootstrap.less"; -} - -#variable-explorer-data-grid .form-control { - height: auto; - padding: 0px; - font-size: inherit; - font-weight: inherit; - line-height: inherit; - border-radius: 0px; -} - -#variable-explorer-data-grid .react-grid-Main { - font-family: var(--code-font-family); - background-color: var(--override-background, var(--vscode-editor-background)); - color: var(--override-foreground, var(--vscode-editor-foreground)); - outline: none; -} - -#variable-explorer-data-grid .react-grid-Grid { - background-color: var(--override-background, var(--vscode-editor-background)); - color: var(--override-foreground, var(--vscode-editor-foreground)); - border-style: none; -} - -#variable-explorer-data-grid .react-grid-Canvas { - background-color: var(--override-background, var(--vscode-editor-background)); - color: var(--override-foreground, var(--vscode-editor-foreground)); -} - -#variable-explorer-data-grid .react-grid-Header { - background-color: var(--override-tabs-background, var(--vscode-notifications-background)); -} - -#variable-explorer-data-grid .react-grid-HeaderCell { - background-color: var(--override-lineHighlightBorder, var(--vscode-editor-lineHighlightBorder)); - color: var(--override-foreground, var(--vscode-editor-foreground)); - border-style: solid; - border-color: var(--override-badge-background, var(--vscode-badge-background)); - padding: 2px; -} - - -#variable-explorer-data-grid .react-grid-Row--even { - background-color: var(--override-background, var(--vscode-editor-background)); - color: var(--override-foreground, var(--vscode-editor-foreground)); - border-right-color: var(--override-badge-background, var(--vscode-badge-background)); - border-bottom-color: var(--override-badge-background, var(--vscode-badge-background)); -} - -#variable-explorer-data-grid .react-grid-Row--odd { - background-color: var(--override-lineHighlightBorder, var(--vscode-editor-lineHighlightBorder)); - color: var(--override-foreground, var(--vscode-editor-foreground)); - border-right-color: var(--override-badge-background, var(--vscode-badge-background)); - border-bottom-color: var(--override-badge-background, var(--vscode-badge-background)); -} - -#variable-explorer-data-grid .react-grid-Cell { - background-color: transparent; - color: var(--override-foreground, var(--vscode-editor-foreground)); - border-style: none; -} - -#variable-explorer-data-grid .react-grid-Cell:hover { - background-color: var(--override-selection-background, var(--vscode-editor-selectionBackground)); -} - -#variable-explorer-data-grid .react-grid-Row:hover { - background-color: var(--override-selection-background, var(--vscode-editor-selectionBackground)); -} - -#variable-explorer-data-grid .react-grid-Row:hover .react-grid-Cell { - background-color: var(--override-selection-background, var(--vscode-editor-selectionBackground)); -} - -#variable-explorer-data-grid .rdg-selected { - visibility: hidden; -} - -// High contrast theme changes - -// Notifications-background and line-highlight-border don't work in high contrast mode, so skip the header color in high contrast themes -body.vscode-high-contrast #variable-explorer-data-grid .react-grid-Header { - background-color: var(--override-background, var(--vscode-editor-background)); - border-color: var(--override-foreground, var(--vscode-editor-foreground)); -} - -body.vscode-high-contrast #variable-explorer-data-grid .react-grid-HeaderCell { - background-color: var(--override-background, var(--vscode-editor-background)); - border-color: var(--override-foreground, var(--vscode-editor-foreground)); -} - -body.vscode-high-contrast #variable-explorer-data-grid .react-grid-Row--odd { - background-color: var(--override-background, var(--vscode-editor-background)); - border-color: var(--override-foreground, var(--vscode-editor-foreground)); -} - -// Since we have removed zebra striping in HC mode, add back in grid lines -body.vscode-high-contrast #variable-explorer-data-grid .react-grid-Cell { - border-style: solid; - border-width: 1px; - border-color: var(--override-foreground, var(--vscode-editor-foreground)); -} - - -// HC inverts selection colors, so use the selection foreground color on hover -body.vscode-high-contrast #variable-explorer-data-grid .react-grid-Cell:hover { - color: var(--override-selection-foreground, var(--vscode-editor-selectionForeground)); -} - -body.vscode-high-contrast #variable-explorer-data-grid .react-grid-Row:hover { - color: var(--override-selection-foreground, var(--vscode-editor-selectionForeground)); -} - -body.vscode-high-contrast #variable-explorer-data-grid .react-grid-Row:hover .react-grid-Cell { - color: var(--override-selection-foreground, var(--vscode-editor-selectionForeground)); -} - -// HC inverts selection, which messes up our icons, just keep them with the same foreground / background combo -body.vscode-high-contrast #variable-explorer-data-grid .react-grid-Row:hover .react-grid-Cell .variable-explorer-button-cell { - color: var(--override-foreground, var(--vscode-editor-foreground)); - background-color: var(--override-background, var(--vscode-editor-background)); -} - - diff --git a/src/datascience-ui/interactive-common/variableExplorerHeaderCellFormatter.tsx b/src/datascience-ui/interactive-common/variableExplorerHeaderCellFormatter.tsx deleted file mode 100644 index 69840df5d7bb..000000000000 --- a/src/datascience-ui/interactive-common/variableExplorerHeaderCellFormatter.tsx +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as React from 'react'; - -interface IVariableExplorerHeaderCellFormatterProps { - // value gets populated by the default cell formatter props - column?: { - name: string; - }; -} - -// Our formatter for cells in the variable explorer. Allow for different styles per column type -export class VariableExplorerHeaderCellFormatter extends React.Component { - public render() { - if (this.props.column) { - return ( -
- {this.props.column.name} -
- ); - } - } -} diff --git a/src/datascience-ui/interactive-common/variableExplorerRowRenderer.tsx b/src/datascience-ui/interactive-common/variableExplorerRowRenderer.tsx deleted file mode 100644 index c346a7ef3ead..000000000000 --- a/src/datascience-ui/interactive-common/variableExplorerRowRenderer.tsx +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import * as React from 'react'; - -// tslint:disable:no-any -interface IVariableExplorerRowProps { - renderBaseRow(props: any): JSX.Element; -} - -export const VariableExplorerRowRenderer: React.SFC = (props) => { - return
{props.renderBaseRow(props)}
; -}; diff --git a/src/datascience-ui/interactive-common/variablePanel.tsx b/src/datascience-ui/interactive-common/variablePanel.tsx deleted file mode 100644 index dbd577c54ee7..000000000000 --- a/src/datascience-ui/interactive-common/variablePanel.tsx +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import * as React from 'react'; - -import { IJupyterVariable } from '../../client/datascience/types'; -import { VariableExplorer } from './variableExplorer'; - -export interface IVariablePanelProps { - baseTheme: string; - busy: boolean; - skipDefault?: boolean; - testMode?: boolean; - variables: IJupyterVariable[]; - executionCount: number; - refreshCount: number; - debugging: boolean; - supportsDebugging: boolean; - fontSize: number; - offsetHeight: number; - gridHeight: number; - containerHeight: number; - showDataExplorer(targetVariable: IJupyterVariable, numberOfColumns: number): void; - closeVariableExplorer(): void; - // tslint:disable-next-line: no-any - setVariableExplorerHeight(containerHeight: number, gridHeight: number): any; - pageIn(startIndex: number, pageSize: number): void; -} - -export class VariablePanel extends React.Component { - constructor(prop: IVariablePanelProps) { - super(prop); - } - - public render() { - return ( - - ); - } -} diff --git a/src/datascience-ui/ipywidgets/README.md b/src/datascience-ui/ipywidgets/README.md deleted file mode 100644 index 1162960b7ba5..000000000000 --- a/src/datascience-ui/ipywidgets/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Hosting ipywidgets in non-notebook context - -- Much of the work is influenced by sample `web3` from https://github.com/jupyter-widgets/ipywidgets/blob/master/examples/web3 -- Displaying `ipywidgets` in non notebook context requires 3 things: - - [requirejs](https://requirejs.org) - - [HTML Manager](https://github.com/jupyter-widgets/ipywidgets/blob/master/examples/web3/src/manager.ts) - - Live Kerne (the widget manager plugs directly into the `kernel` messages to read/write and build data for displaying the data) - -# Kernel - -- As the kernel connection is only available in back end (`extension code`), the HTML manager will not work. -- To get this working, all we need to do is create a `proxy kernel` connection in the `react` layer. -- Thats what the code in this folder does (wraps the html manager + custom kernel connection) -- Kernel messages from the extension are sent to this layer using the `postoffice` -- Similarly messages from sent from html manager via the kernel are sent to the actual kernel via the postoffice. -- However, the sequence and massaging of the kernel messages requires a lot of work. Basically majority of the message processing from `/node_modules/@jupyterlab/services/lib/kernel/*.js` - - Much of the message processing logic is borrowed from `comm.js`, `default.js`, `future.js`, `kernel.js` and `manager.js`. diff --git a/src/datascience-ui/ipywidgets/container.tsx b/src/datascience-ui/ipywidgets/container.tsx deleted file mode 100644 index ef5fed6a19d5..000000000000 --- a/src/datascience-ui/ipywidgets/container.tsx +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as isonline from 'is-online'; -import * as React from 'react'; -import { Store } from 'redux'; -import '../../client/common/extensions'; -import { createDeferred, Deferred } from '../../client/common/utils/async'; -import { - IInteractiveWindowMapping, - IPyWidgetMessages -} from '../../client/datascience/interactive-common/interactiveWindowTypes'; -import { WidgetScriptSource } from '../../client/datascience/ipywidgets/types'; -import { SharedMessages } from '../../client/datascience/messages'; -import { IDataScienceExtraSettings } from '../../client/datascience/types'; -import { - CommonAction, - CommonActionType, - ILoadIPyWidgetClassFailureAction, - LoadIPyWidgetClassLoadAction, - NotifyIPyWidgeWidgetVersionNotSupportedAction -} from '../interactive-common/redux/reducers/types'; -import { IStore } from '../interactive-common/redux/store'; -import { PostOffice } from '../react-common/postOffice'; -import { warnAboutWidgetVersionsThatAreNotSupported } from './incompatibleWidgetHandler'; -import { WidgetManager } from './manager'; -import { registerScripts } from './requirejsRegistry'; - -type Props = { - postOffice: PostOffice; - widgetContainerId: string; - store: Store & { dispatch: unknown }; -}; - -export class WidgetManagerComponent extends React.Component { - private readonly widgetManager: WidgetManager; - private readonly widgetSourceRequests = new Map< - string, - { deferred: Deferred; timer: NodeJS.Timeout | number | undefined } - >(); - private readonly registeredWidgetSources = new Map(); - private timedoutWaitingForWidgetsToGetLoaded?: boolean; - private widgetsCanLoadFromCDN: boolean = false; - private readonly loaderSettings = { - // Total time to wait for a script to load. This includes ipywidgets making a request to extension for a Uri of a widget, - // then extension replying back with the Uri (max 5 seconds round trip time). - // If expires, then Widget downloader will attempt to download with what ever information it has (potentially failing). - // Note, we might have a message displayed at the user end (asking for consent to use CDN). - // Hence use 60 seconds. - timeoutWaitingForScriptToLoad: 60_000, - // List of widgets that must always be loaded using requirejs instead of using a CDN or the like. - widgetsRegisteredInRequireJs: new Set(), - // Callback when loading a widget fails. - errorHandler: this.handleLoadError.bind(this), - // Callback when requesting a module be registered with requirejs (if possible). - loadWidgetScript: this.loadWidgetScript.bind(this), - successHandler: this.handleLoadSuccess.bind(this) - }; - constructor(props: Props) { - super(props); - this.widgetManager = new WidgetManager( - document.getElementById(this.props.widgetContainerId)!, - this.props.postOffice, - this.loaderSettings - ); - - props.postOffice.addHandler({ - // tslint:disable-next-line: no-any - handleMessage: (type: string, payload?: any) => { - if (type === SharedMessages.UpdateSettings) { - const settings = JSON.parse(payload) as IDataScienceExtraSettings; - this.widgetsCanLoadFromCDN = settings.widgetScriptSources.length > 0; - } else if (type === IPyWidgetMessages.IPyWidgets_WidgetScriptSourceResponse) { - this.registerScriptSourceInRequirejs(payload as WidgetScriptSource); - } else if ( - type === IPyWidgetMessages.IPyWidgets_kernelOptions || - type === IPyWidgetMessages.IPyWidgets_onKernelChanged - ) { - // This happens when we have restarted a kernel. - // If user changed the kernel, then some widgets might exist now and some might now. - this.widgetSourceRequests.clear(); - this.registeredWidgetSources.clear(); - } - return true; - } - }); - } - public render() { - return null; - } - public componentWillUnmount() { - this.widgetManager.dispose(); - } - /** - * Given a list of the widgets along with the sources, we will need to register them with requirejs. - * IPyWidgets uses requirejs to dynamically load modules. - * (https://requirejs.org/docs/api.html) - * All we're doing here is given a widget (module) name, we register the path where the widget (module) can be loaded from. - * E.g. - * requirejs.config({ paths:{ - * 'widget_xyz': '' - * }}); - */ - private registerScriptSourcesInRequirejs(sources: WidgetScriptSource[]) { - if (!Array.isArray(sources) || sources.length === 0) { - return; - } - - registerScripts(sources); - - // Now resolve promises (anything that was waiting for modules to get registered can carry on). - sources.forEach((source) => { - this.registeredWidgetSources.set(source.moduleName, source); - // We have fetched the script sources for all of these modules. - // In some cases we might not have the source, meaning we don't have it or couldn't find it. - let request = this.widgetSourceRequests.get(source.moduleName); - if (!request) { - request = { - deferred: createDeferred(), - timer: undefined - }; - this.widgetSourceRequests.set(source.moduleName, request); - } - request.deferred.resolve(); - if (request.timer !== undefined) { - // tslint:disable-next-line: no-any - clearTimeout(request.timer as any); // This is to make this work on Node and Browser - } - }); - } - private registerScriptSourceInRequirejs(source?: WidgetScriptSource) { - if (!source) { - return; - } - this.registerScriptSourcesInRequirejs([source]); - } - private createLoadSuccessAction( - className: string, - moduleName: string, - moduleVersion: string - ): CommonAction { - return { - type: CommonActionType.LOAD_IPYWIDGET_CLASS_SUCCESS, - payload: { messageDirection: 'incoming', data: { className, moduleName, moduleVersion } } - }; - } - - private createLoadErrorAction( - className: string, - moduleName: string, - moduleVersion: string, - isOnline: boolean, - // tslint:disable-next-line: no-any - error: any, - timedout: boolean - ): CommonAction { - return { - type: CommonActionType.LOAD_IPYWIDGET_CLASS_FAILURE, - payload: { - messageDirection: 'incoming', - data: { - className, - moduleName, - moduleVersion, - isOnline, - timedout, - error, - cdnsUsed: this.widgetsCanLoadFromCDN - } - } - }; - } - private createWidgetVersionNotSupportedErrorAction( - moduleName: 'qgrid', - moduleVersion: string - ): CommonAction { - return { - type: CommonActionType.IPYWIDGET_WIDGET_VERSION_NOT_SUPPORTED, - payload: { - messageDirection: 'incoming', - data: { - moduleName, - moduleVersion - } - } - }; - } - private async handleLoadError( - className: string, - moduleName: string, - moduleVersion: string, - // tslint:disable-next-line: no-any - error: any, - timedout: boolean = false - ) { - const isOnline = await isonline.default({ timeout: 1000 }); - this.props.store.dispatch( - this.createLoadErrorAction(className, moduleName, moduleVersion, isOnline, error, timedout) - ); - } - - /** - * Method called by ipywidgets to get the source for a widget. - * When we get a source for the widget, we register it in requriejs. - * We need to check if it is avaialble on CDN, if not then fallback to local FS. - * Or check local FS then fall back to CDN (depending on the order defined by the user). - */ - private loadWidgetScript(moduleName: string, moduleVersion: string): Promise { - // tslint:disable-next-line: no-console - console.log(`Fetch IPyWidget source for ${moduleName}`); - let request = this.widgetSourceRequests.get(moduleName); - if (!request) { - request = { - deferred: createDeferred(), - timer: undefined - }; - - // If we timeout, then resolve this promise. - // We don't want the calling code to unnecessary wait for too long. - // Else UI will not get rendered due to blocking ipywidets (at the end of the day ipywidgets gets loaded via kernel) - // And kernel blocks the UI from getting processed. - // Also, if we timeout once, then for subsequent attempts, wait for just 1 second. - // Possible user has ignored some UI prompt and things are now in a state of limbo. - // This way thigns will fall over sooner due to missing widget sources. - const timeoutTime = this.timedoutWaitingForWidgetsToGetLoaded - ? 5_000 - : this.loaderSettings.timeoutWaitingForScriptToLoad; - - request.timer = setTimeout(() => { - if (request && !request.deferred.resolved) { - // tslint:disable-next-line: no-console - console.error(`Timeout waiting to get widget source for ${moduleName}, ${moduleVersion}`); - this.handleLoadError( - '', - moduleName, - moduleVersion, - new Error(`Timeout getting source for ${moduleName}:${moduleVersion}`), - true - ).ignoreErrors(); - request.deferred.resolve(); - this.timedoutWaitingForWidgetsToGetLoaded = true; - } - }, timeoutTime); - - this.widgetSourceRequests.set(moduleName, request); - } - // Whether we have the scripts or not, send message to extension. - // Useful telemetry and also we know it was explicity requestd by ipywidgest. - this.props.postOffice.sendMessage( - IPyWidgetMessages.IPyWidgets_WidgetScriptSourceRequest, - { moduleName, moduleVersion } - ); - - return request.deferred.promise - .then(() => { - const widgetSource = this.registeredWidgetSources.get(moduleName); - if (widgetSource) { - warnAboutWidgetVersionsThatAreNotSupported( - widgetSource, - moduleVersion, - this.widgetsCanLoadFromCDN, - (info) => - this.props.store.dispatch( - this.createWidgetVersionNotSupportedErrorAction(info.moduleName, info.moduleVersion) - ) - ); - } - }) - .catch((ex) => - // tslint:disable-next-line: no-console - console.error(`Failed to load Widget Script from Extension for for ${moduleName}, ${moduleVersion}`, ex) - ); - } - - private handleLoadSuccess(className: string, moduleName: string, moduleVersion: string) { - this.props.store.dispatch(this.createLoadSuccessAction(className, moduleName, moduleVersion)); - } -} diff --git a/src/datascience-ui/ipywidgets/incompatibleWidgetHandler.ts b/src/datascience-ui/ipywidgets/incompatibleWidgetHandler.ts deleted file mode 100644 index a6d3ce4e1207..000000000000 --- a/src/datascience-ui/ipywidgets/incompatibleWidgetHandler.ts +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as semver from 'semver'; -import { WidgetScriptSource } from '../../client/datascience/ipywidgets/types'; -const supportedVersionOfQgrid = '1.1.1'; -const qgridModuleName = 'qgrid'; - -/** - * For now only warns about qgrid. - * Warn user about qgrid versions > 1.1.1 (we know CDN isn't available for newer versions and local widget source will not work). - * Recommend to downgrade to 1.1.1. - * Returns `true` if a warning has been displayed. - */ -export function warnAboutWidgetVersionsThatAreNotSupported( - widgetSource: WidgetScriptSource, - moduleVersion: string, - cdnSupported: boolean, - errorDispatcher: (info: { moduleName: typeof qgridModuleName; moduleVersion: string }) => void -) { - // if widget exists on CDN or CDN is disabled, get out. - if (widgetSource.source === 'cdn' || !cdnSupported) { - return false; - } - // Warn about qrid. - if (widgetSource.moduleName !== qgridModuleName) { - return false; - } - // We're only interested in versions > 1.1.1. - try { - // If we have an exact version, & if that is <= 1.1.1, then no warning needs to be displayed. - if (!moduleVersion.startsWith('^') && semver.compare(moduleVersion, supportedVersionOfQgrid) <= 0) { - return false; - } - // If we have a version range, then check the range. - // Basically if our minimum version 1.1.1 is met, then nothing to do. - // Eg. requesting script source for version `^1.3.0`. - if (moduleVersion.startsWith('^') && semver.satisfies(supportedVersionOfQgrid, moduleVersion)) { - return false; - } - } catch { - return false; - } - errorDispatcher({ moduleName: widgetSource.moduleName, moduleVersion }); -} diff --git a/src/datascience-ui/ipywidgets/index.ts b/src/datascience-ui/ipywidgets/index.ts deleted file mode 100644 index 6d243f31da68..000000000000 --- a/src/datascience-ui/ipywidgets/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -export { WidgetManager } from './manager'; -export { WidgetManagerComponent } from './container'; diff --git a/src/datascience-ui/ipywidgets/kernel.ts b/src/datascience-ui/ipywidgets/kernel.ts deleted file mode 100644 index c3efa52b5724..000000000000 --- a/src/datascience-ui/ipywidgets/kernel.ts +++ /dev/null @@ -1,514 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { Kernel, KernelMessage, ServerConnection } from '@jupyterlab/services'; -import { DefaultKernel } from '@jupyterlab/services/lib/kernel/default'; -import type { ISignal, Signal } from '@phosphor/signaling'; -import * as WebSocketWS from 'ws'; -import { createDeferred, Deferred } from '../../client/common/utils/async'; -import { deserializeDataViews, serializeDataViews } from '../../client/common/utils/serializers'; -import { - IInteractiveWindowMapping, - IPyWidgetMessages -} from '../../client/datascience/interactive-common/interactiveWindowTypes'; -import { KernelSocketOptions } from '../../client/datascience/types'; -import { IMessageHandler, PostOffice } from '../react-common/postOffice'; - -// tslint:disable:no-any - -// tslint:disable: no-any -// Proxy kernel that wraps the default kernel. We need this entire class because -// we can't derive from DefaultKernel. -class ProxyKernel implements IMessageHandler, Kernel.IKernel { - private readonly _ioPubMessageSignal: Signal; - public get iopubMessage(): ISignal { - return this._ioPubMessageSignal; - } - public get terminated() { - return this.realKernel.terminated as any; - } - public get statusChanged() { - return this.realKernel.statusChanged as any; - } - public get unhandledMessage() { - return this.realKernel.unhandledMessage as any; - } - public get anyMessage() { - return this.realKernel.anyMessage as any; - } - public get serverSettings(): ServerConnection.ISettings { - return this.realKernel.serverSettings; - } - public get id(): string { - return this.realKernel.id; - } - public get name(): string { - return this.realKernel.name; - } - public get model(): Kernel.IModel { - return this.realKernel.model; - } - public get username(): string { - return this.realKernel.username; - } - public get clientId(): string { - return this.realKernel.clientId; - } - public get status(): Kernel.Status { - return this.realKernel.status; - } - public get info(): KernelMessage.IInfoReply | null { - return this.realKernel.info; - } - public get isReady(): boolean { - return this.realKernel.isReady; - } - public get ready(): Promise { - return this.realKernel.ready; - } - public get handleComms(): boolean { - return this.realKernel.handleComms; - } - public get isDisposed(): boolean { - return this.realKernel.isDisposed; - } - private realKernel: Kernel.IKernel; - private hookResults = new Map>(); - private websocket: WebSocketWS & { sendEnabled: boolean }; - private messageHook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike; - private messageHooks: Map boolean | PromiseLike>; - private lastHookedMessageId: string | undefined; - // Messages that are awaiting extension messages to be fully handled - private awaitingExtensionMessage: Map>; - constructor(options: KernelSocketOptions, private postOffice: PostOffice) { - // Dummy websocket we give to the underlying real kernel - let proxySocketInstance: any; - class ProxyWebSocket { - public onopen?: ((this: ProxyWebSocket) => any) | null; - public onmessage?: ((this: ProxyWebSocket, ev: MessageEvent) => any) | null; - public sendEnabled: boolean = true; - constructor() { - proxySocketInstance = this; - } - public close(_code?: number | undefined, _reason?: string | undefined): void { - // Nothing. - } - public send(data: string | ArrayBuffer | SharedArrayBuffer | Blob | ArrayBufferView): void { - // This is a command being sent from the UI kernel to the websocket. We mirror that to - // the extension side. - if (this.sendEnabled) { - if (typeof data === 'string') { - postOffice.sendMessage(IPyWidgetMessages.IPyWidgets_msg, data); - } else { - // Serialize binary data properly before sending to extension. - postOffice.sendMessage( - IPyWidgetMessages.IPyWidgets_binary_msg, - serializeDataViews([data as any]) - ); - } - } - } - } - const settings = ServerConnection.makeSettings({ WebSocket: ProxyWebSocket as any, wsUrl: 'BOGUS_PVSC' }); - - this.awaitingExtensionMessage = new Map>(); - - // This is crucial, the clientId must match the real kernel in extension. - // All messages contain the clientId as `session` in the request. - // If this doesn't match the actual value, then things can and will go wrong. - this.realKernel = new DefaultKernel( - { - name: options.model.name, - serverSettings: settings, - clientId: options.clientId, - handleComms: true, - username: options.userName - }, - options.id - ); - - // Hook up to watch iopub messages from the real kernel - // tslint:disable-next-line: no-require-imports - const signaling = require('@phosphor/signaling') as typeof import('@phosphor/signaling'); - this._ioPubMessageSignal = new signaling.Signal(this); - this.realKernel.iopubMessage.connect(this.onIOPubMessage, this); - - postOffice.addHandler(this); - this.websocket = proxySocketInstance; - this.messageHook = this.messageHookInterceptor.bind(this); - this.messageHooks = new Map boolean | PromiseLike>(); - this.fakeOpenSocket(); - } - - public shutdown(): Promise { - return this.realKernel.shutdown(); - } - public getSpec(): Promise { - return this.realKernel.getSpec(); - } - public sendShellMessage( - msg: KernelMessage.IShellMessage, - expectReply?: boolean, - disposeOnDone?: boolean - ): Kernel.IShellFuture< - KernelMessage.IShellMessage, - KernelMessage.IShellMessage - > { - return this.realKernel.sendShellMessage(msg, expectReply, disposeOnDone); - } - public sendControlMessage( - msg: KernelMessage.IControlMessage, - expectReply?: boolean, - disposeOnDone?: boolean - ): Kernel.IControlFuture< - KernelMessage.IControlMessage, - KernelMessage.IControlMessage - > { - return this.realKernel.sendControlMessage(msg, expectReply, disposeOnDone); - } - public reconnect(): Promise { - return this.realKernel.reconnect(); - } - public interrupt(): Promise { - return this.realKernel.interrupt(); - } - public restart(): Promise { - return this.realKernel.restart(); - } - public requestKernelInfo(): Promise { - return this.realKernel.requestKernelInfo(); - } - public requestComplete(content: { code: string; cursor_pos: number }): Promise { - return this.realKernel.requestComplete(content); - } - public requestInspect(content: { - code: string; - cursor_pos: number; - detail_level: 0 | 1; - }): Promise { - return this.realKernel.requestInspect(content); - } - public requestHistory( - content: - | KernelMessage.IHistoryRequestRange - | KernelMessage.IHistoryRequestSearch - | KernelMessage.IHistoryRequestTail - ): Promise { - return this.realKernel.requestHistory(content); - } - public requestExecute( - content: { - code: string; - silent?: boolean; - store_history?: boolean; - user_expressions?: import('@phosphor/coreutils').JSONObject; - allow_stdin?: boolean; - stop_on_error?: boolean; - }, - disposeOnDone?: boolean, - metadata?: import('@phosphor/coreutils').JSONObject - ): Kernel.IShellFuture { - return this.realKernel.requestExecute(content, disposeOnDone, metadata); - } - public requestDebug( - // tslint:disable-next-line: no-banned-terms - content: { seq: number; type: 'request'; command: string; arguments?: any }, - disposeOnDone?: boolean - ): Kernel.IControlFuture { - return this.realKernel.requestDebug(content, disposeOnDone); - } - public requestIsComplete(content: { code: string }): Promise { - return this.realKernel.requestIsComplete(content); - } - public requestCommInfo(content: { - target_name?: string; - target?: string; - }): Promise { - return this.realKernel.requestCommInfo(content); - } - public sendInputReply(content: KernelMessage.ReplyContent): void { - return this.realKernel.sendInputReply(content); - } - public connectToComm(targetName: string, commId?: string): Kernel.IComm { - return this.realKernel.connectToComm(targetName, commId); - } - public registerCommTarget( - targetName: string, - callback: (comm: Kernel.IComm, msg: KernelMessage.ICommOpenMsg) => void | PromiseLike - ): void { - // When a comm target has been registered, we need to register this in the real kernel in extension side. - // Hence send that message to extension. - this.postOffice.sendMessage( - IPyWidgetMessages.IPyWidgets_registerCommTarget, - targetName - ); - return this.realKernel.registerCommTarget(targetName, callback); - } - public removeCommTarget( - targetName: string, - callback: (comm: Kernel.IComm, msg: KernelMessage.ICommOpenMsg) => void | PromiseLike - ): void { - return this.realKernel.removeCommTarget(targetName, callback); - } - public dispose(): void { - this.postOffice.removeHandler(this); - return this.realKernel.dispose(); - } - public handleMessage(type: string, payload?: any): boolean { - // Handle messages as they come in. Note: Do not await anything here. THey have to be inorder. - // If not, we could switch to message chaining or an observable instead. - switch (type) { - case IPyWidgetMessages.IPyWidgets_MessageHookCall: - this.sendHookResult(payload); - break; - - case IPyWidgetMessages.IPyWidgets_msg: - if (this.websocket && this.websocket.onmessage) { - this.websocket.onmessage({ target: this.websocket, data: payload.data, type: '' }); - } - this.sendResponse(payload.id); - break; - - case IPyWidgetMessages.IPyWidgets_binary_msg: - if (this.websocket && this.websocket.onmessage) { - const deserialized = deserializeDataViews(payload.data)![0]; - this.websocket.onmessage({ target: this.websocket, data: deserialized as any, type: '' }); - } - this.sendResponse(payload.id); - break; - - case IPyWidgetMessages.IPyWidgets_mirror_execute: - this.handleMirrorExecute(payload); - break; - - case IPyWidgetMessages.IPyWidgets_ExtensionOperationHandled: - this.extensionOperationFinished(payload); - break; - - default: - break; - } - return true; - } - public registerMessageHook( - msgId: string, - hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike - ): void { - // We don't want to finish our processing of this message until the extension has told us that it has finished - // With the extension side registering of the message hook - const waitPromise = createDeferred(); - - // A message could cause multiple callback waits, so use id+type as key - const key = this.generateExtensionResponseKey( - msgId, - IPyWidgetMessages.IPyWidgets_RegisterMessageHook.toString() - ); - this.awaitingExtensionMessage.set(key, waitPromise); - - // Tell the other side about this. - this.postOffice.sendMessage(IPyWidgetMessages.IPyWidgets_RegisterMessageHook, msgId); - - // Save the real hook so we can call it - this.messageHooks.set(msgId, hook); - - // Wrap the hook and send it to the real kernel - window.console.log(`Registering hook for ${msgId}`); - this.realKernel.registerMessageHook(msgId, this.messageHook); - } - - public removeMessageHook( - msgId: string, - _hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike - ): void { - // We don't want to finish our processing of this message until the extension has told us that it has finished - // With the extension side removing of the message hook - const waitPromise = createDeferred(); - - // A message could cause multiple callback waits, so use id+type as key - const key = this.generateExtensionResponseKey(msgId, IPyWidgetMessages.IPyWidgets_RemoveMessageHook.toString()); - this.awaitingExtensionMessage.set(key, waitPromise); - - this.postOffice.sendMessage(IPyWidgetMessages.IPyWidgets_RemoveMessageHook, { - hookMsgId: msgId, - lastHookedMsgId: this.lastHookedMessageId - }); - - // Remove our mapping - this.messageHooks.delete(msgId); - this.lastHookedMessageId = undefined; - - // Remove from the real kernel - window.console.log(`Removing hook for ${msgId}`); - this.realKernel.removeMessageHook(msgId, this.messageHook); - } - - // Called when the extension has finished an operation that we are waiting for in message processing - private extensionOperationFinished(payload: any) { - //const key = payload.id + payload.type; - const key = `${payload.id}${payload.type}`; - - const waitPromise = this.awaitingExtensionMessage.get(key); - - if (waitPromise) { - waitPromise.resolve(); - this.awaitingExtensionMessage.delete(key); - } - } - - private sendResponse(id: string) { - this.postOffice.sendMessage(IPyWidgetMessages.IPyWidgets_msg_received, { - id - }); - } - - private generateExtensionResponseKey(msgId: string, msgType: string): string { - return `${msgId}${msgType}`; - } - - private fakeOpenSocket() { - // This is kind of the hand shake. - // As soon as websocket opens up, the kernel sends a request to check if it is alive. - // If it gets a response, then it is deemed ready. - const originalRequestKernelInfo = this.realKernel.requestKernelInfo.bind(this.realKernel); - this.realKernel.requestKernelInfo = () => { - this.realKernel.requestKernelInfo = originalRequestKernelInfo; - return Promise.resolve() as any; - }; - if (this.websocket) { - this.websocket.onopen({ target: this.websocket }); - } - this.realKernel.requestKernelInfo = originalRequestKernelInfo; - } - private messageHookInterceptor(msg: KernelMessage.IIOPubMessage): boolean | PromiseLike { - try { - window.console.log( - `Message hook callback for ${(msg as any).header.msg_type} and ${(msg.parent_header as any).msg_id}` - ); - // Save the active message that is currently being hooked. The Extension - // side needs this information during removeMessageHook so it can delay removal until after a message is called - this.lastHookedMessageId = msg.header.msg_id; - - const hook = this.messageHooks.get((msg.parent_header as any).msg_id); - if (hook) { - // When the kernel calls the hook, save the result for this message. The other side will ask for it - const result = hook(msg); - this.hookResults.set(msg.header.msg_id, result); - if ((result as any).then) { - return (result as any).then((r: boolean) => { - return r; - }); - } - - // When not a promise reset right after - return result; - } - } catch (ex) { - // Swallow exceptions so processing continues - } - return false; - } - - private sendHookResult(args: { requestId: string; parentId: string; msg: KernelMessage.IIOPubMessage }) { - const result = this.hookResults.get(args.msg.header.msg_id); - if (result !== undefined) { - this.hookResults.delete(args.msg.header.msg_id); - - // tslint:disable-next-line: no-any - if ((result as any).then) { - // tslint:disable-next-line: no-any - (result as any).then((r: boolean) => { - this.postOffice.sendMessage( - IPyWidgetMessages.IPyWidgets_MessageHookResult, - { - requestId: args.requestId, - parentId: args.parentId, - msgType: args.msg.header.msg_type, - result: r - } - ); - }); - } else { - this.postOffice.sendMessage(IPyWidgetMessages.IPyWidgets_MessageHookResult, { - requestId: args.requestId, - parentId: args.parentId, - msgType: args.msg.header.msg_type, - result: result === true - }); - } - } else { - // If no hook registered, make sure not to remove messages. - this.postOffice.sendMessage(IPyWidgetMessages.IPyWidgets_MessageHookResult, { - requestId: args.requestId, - parentId: args.parentId, - msgType: args.msg.header.msg_type, - result: true - }); - } - } - - private handleMirrorExecute(payload: { id: string; msg: KernelMessage.IExecuteRequestMsg }) { - // Special case. This is a mirrored execute. We want this to go to the real kernel, but not send a message - // back to the websocket. This should cause the appropriate futures to be generated. - try { - this.websocket.sendEnabled = false; - // Make sure we don't dispose on done (that will eliminate the future when it's done) - this.realKernel.sendShellMessage(payload.msg, false, payload.msg.content.silent); - } finally { - this.websocket.sendEnabled = true; - } - this.sendResponse(payload.id); - } - - // When the real kernel handles iopub messages notify the Extension side and then forward on the message - // Note, this message comes from the kernel after it is done handling the message async - private onIOPubMessage(_sender: Kernel.IKernel, message: KernelMessage.IIOPubMessage) { - // If we are not waiting for anything on the extension just send it - if (this.awaitingExtensionMessage.size <= 0) { - this.finishIOPubMessage(message); - } else { - // If we are waiting for something from the extension, wait for all that to finish before - // we send the message that we are done handling this message - // Since the Extension is blocking waiting for this message to be handled we know all extension message are - // related to this message or before and should be resolved before we move on - const extensionPromises = Array.from(this.awaitingExtensionMessage.values()).map((value) => { - return value.promise; - }); - Promise.all(extensionPromises) - .then(() => { - // Fine to wait and send this in the catch as the Extension is blocking new messages for this and the UI kernel - // has already finished handling it - this.finishIOPubMessage(message); - }) - .catch(() => { - window.console.log('Failed to send iopub_msg_handled message'); - }); - } - } - - // Finish an iopub message by sending a message to the UI and then emitting that we are done with it - private finishIOPubMessage(message: KernelMessage.IIOPubMessage) { - this.postOffice.sendMessage(IPyWidgetMessages.IPyWidgets_iopub_msg_handled, { - id: message.header.msg_id - }); - this._ioPubMessageSignal.emit(message); - } -} - -/** - * Creates a kernel from a websocket. - * Check code in `node_modules/@jupyterlab/services/lib/kernel/default.js`. - * The `_createSocket` method basically connects to a websocket and listens to messages. - * Hence to create a kernel, all we need is a socket connection (class with onMessage and postMessage methods). - */ -export function create( - options: KernelSocketOptions, - postOffice: PostOffice, - pendingMessages: { message: string; payload: any }[] -): Kernel.IKernel { - const result = new ProxyKernel(options, postOffice); - // Make sure to handle all the missed messages - pendingMessages.forEach((m) => result.handleMessage(m.message, m.payload)); - return result; -} diff --git a/src/datascience-ui/ipywidgets/manager.ts b/src/datascience-ui/ipywidgets/manager.ts deleted file mode 100644 index 88d4b5a07750..000000000000 --- a/src/datascience-ui/ipywidgets/manager.ts +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import '@jupyter-widgets/controls/css/labvariables.css'; - -import type { Kernel, KernelMessage } from '@jupyterlab/services'; -import type { nbformat } from '@jupyterlab/services/node_modules/@jupyterlab/coreutils'; -import { Widget } from '@phosphor/widgets'; -import * as fastDeepEqual from 'fast-deep-equal'; -import 'rxjs/add/operator/concatMap'; -import { Observable } from 'rxjs/Observable'; -import { ReplaySubject } from 'rxjs/ReplaySubject'; -import { createDeferred, Deferred } from '../../client/common/utils/async'; -import { - IInteractiveWindowMapping, - InteractiveWindowMessages, - IPyWidgetMessages -} from '../../client/datascience/interactive-common/interactiveWindowTypes'; -import { WIDGET_MIMETYPE } from '../../client/datascience/ipywidgets/constants'; -import { KernelSocketOptions } from '../../client/datascience/types'; -import { IMessageHandler, PostOffice } from '../react-common/postOffice'; -import { create as createKernel } from './kernel'; -import { IIPyWidgetManager, IJupyterLabWidgetManager, IJupyterLabWidgetManagerCtor } from './types'; - -// tslint:disable: no-any - -export class WidgetManager implements IIPyWidgetManager, IMessageHandler { - public static get instance(): Observable { - return WidgetManager._instance; - } - private static _instance = new ReplaySubject(); - private manager?: IJupyterLabWidgetManager; - private proxyKernel?: Kernel.IKernel; - private options?: KernelSocketOptions; - private pendingMessages: { message: string; payload: any }[] = []; - /** - * Contains promises related to model_ids that need to be displayed. - * When we receive a message from the kernel of type = `display_data` for a widget (`application/vnd.jupyter.widget-view+json`), - * then its time to display this. - * We need to keep track of this. A boolean is sufficient, but we're using a promise so we can be notified when it is ready. - * - * @private - * @memberof WidgetManager - */ - private modelIdsToBeDisplayed = new Map>(); - constructor( - private readonly widgetContainer: HTMLElement, - private readonly postOffice: PostOffice, - private readonly scriptLoader: { - readonly widgetsRegisteredInRequireJs: Readonly>; - // tslint:disable-next-line: no-any - errorHandler(className: string, moduleName: string, moduleVersion: string, error: any): void; - loadWidgetScript(moduleName: string, moduleVersion: string): void; - successHandler(className: string, moduleName: string, moduleVersion: string): void; - } - ) { - // tslint:disable-next-line: no-any - this.postOffice.addHandler(this); - - // Handshake. - this.postOffice.sendMessage(IPyWidgetMessages.IPyWidgets_Ready); - } - public dispose(): void { - this.proxyKernel?.dispose(); // NOSONAR - this.postOffice.removeHandler(this); - this.clear().ignoreErrors(); - } - public async clear(): Promise { - await this.manager?.clear_state(); - } - public handleMessage(message: string, payload?: any) { - if (message === IPyWidgetMessages.IPyWidgets_kernelOptions) { - this.initializeKernelAndWidgetManager(payload); - } else if (message === IPyWidgetMessages.IPyWidgets_onRestartKernel) { - // Kernel was restarted. - this.manager?.dispose(); // NOSONAR - this.manager = undefined; - this.proxyKernel?.dispose(); // NOSONAR - this.proxyKernel = undefined; - WidgetManager._instance.next(undefined); - } else if (!this.proxyKernel) { - this.pendingMessages.push({ message, payload }); - } - return true; - } - - /** - * Renders a widget and returns a disposable (to remove the widget). - * - * @param {(nbformat.IMimeBundle & {model_id: string; version_major: number})} data - * @param {HTMLElement} ele - * @returns {Promise<{ dispose: Function }>} - * @memberof WidgetManager - */ - public async renderWidget( - data: nbformat.IMimeBundle & { model_id: string; version_major: number }, - ele: HTMLElement - ): Promise { - if (!data) { - throw new Error( - "application/vnd.jupyter.widget-view+json not in msg.content.data, as msg.content.data is 'undefined'." - ); - } - if (!this.manager) { - throw new Error('DS IPyWidgetManager not initialized.'); - } - - if (!data || data.version_major !== 2) { - console.warn('Widget data not avaialble to render an ipywidget'); - return undefined; - } - - const modelId = data.model_id as string; - // Check if we have processed the data for this model. - // If not wait. - if (!this.modelIdsToBeDisplayed.has(modelId)) { - this.modelIdsToBeDisplayed.set(modelId, createDeferred()); - } - // Wait until it is flagged as ready to be processed. - // This widget manager must have recieved this message and performed all operations before this. - // Once all messages prior to this have been processed in sequence and this message is receievd, - // then, and only then are we ready to render the widget. - // I.e. this is a way of synchronzing the render with the processing of the messages. - await this.modelIdsToBeDisplayed.get(modelId)!.promise; - - const modelPromise = this.manager.get_model(data.model_id); - if (!modelPromise) { - console.warn('Widget model not avaialble to render an ipywidget'); - return undefined; - } - - // ipywdigets may not have completed creating the model. - // ipywidgets have a promise, as the model may get created by a 3rd party library. - // That 3rd party library may not be available and may have to be downloaded. - // Hence the promise to wait until it has been created. - const model = await modelPromise; - const view = await this.manager.create_view(model, { el: ele }); - // tslint:disable-next-line: no-any - return this.manager.display_view(data, view, { node: ele }); - } - private initializeKernelAndWidgetManager(options: KernelSocketOptions) { - if (this.proxyKernel && fastDeepEqual(options, this.options)) { - return; - } - this.proxyKernel?.dispose(); // NOSONAR - this.proxyKernel = createKernel(options, this.postOffice, this.pendingMessages); - this.pendingMessages = []; - - // Dispose any existing managers. - this.manager?.dispose(); // NOSONAR - try { - // The JupyterLabWidgetManager will be exposed in the global variable `window.ipywidgets.main` (check webpack config - src/ipywidgets/webpack.config.js). - // tslint:disable-next-line: no-any - const JupyterLabWidgetManager = (window as any).vscIPyWidgets.WidgetManager as IJupyterLabWidgetManagerCtor; - if (!JupyterLabWidgetManager) { - throw new Error('JupyterLabWidgetManadger not defined. Please include/check ipywidgets.js file'); - } - // Create the real manager and point it at our proxy kernel. - this.manager = new JupyterLabWidgetManager(this.proxyKernel, this.widgetContainer, this.scriptLoader); - - // Listen for display data messages so we can prime the model for a display data - this.proxyKernel.iopubMessage.connect(this.handleDisplayDataMessage.bind(this)); - - // Listen for unhandled IO pub so we can forward to the extension - this.manager.onUnhandledIOPubMessage.connect(this.handleUnhanldedIOPubMessage.bind(this)); - - // Tell the observable about our new manager - WidgetManager._instance.next(this); - } catch (ex) { - // tslint:disable-next-line: no-console - console.error('Failed to initialize WidgetManager', ex); - } - } - /** - * Ensure we create the model for the display data. - */ - private handleDisplayDataMessage(_sender: any, payload: KernelMessage.IIOPubMessage) { - // tslint:disable-next-line:no-require-imports - const jupyterLab = require('@jupyterlab/services') as typeof import('@jupyterlab/services'); // NOSONAR - - if (!jupyterLab.KernelMessage.isDisplayDataMsg(payload)) { - return; - } - const displayMsg = payload as KernelMessage.IDisplayDataMsg; - - if (displayMsg.content && displayMsg.content.data && displayMsg.content.data[WIDGET_MIMETYPE]) { - // tslint:disable-next-line: no-any - const data = displayMsg.content.data[WIDGET_MIMETYPE] as any; - const modelId = data.model_id; - let deferred = this.modelIdsToBeDisplayed.get(modelId); - if (!deferred) { - deferred = createDeferred(); - this.modelIdsToBeDisplayed.set(modelId, deferred); - } - if (!this.manager) { - throw new Error('DS IPyWidgetManager not initialized'); - } - const modelPromise = this.manager.get_model(data.model_id); - if (modelPromise) { - modelPromise.then((_m) => deferred?.resolve()).catch((e) => deferred?.reject(e)); - } else { - deferred.resolve(); - } - } - } - - private handleUnhanldedIOPubMessage(_manager: any, msg: KernelMessage.IIOPubMessage) { - // Send this to the other side - this.postOffice.sendMessage( - InteractiveWindowMessages.IPyWidgetUnhandledKernelMessage, - msg - ); - } -} diff --git a/src/datascience-ui/ipywidgets/requirejsRegistry.ts b/src/datascience-ui/ipywidgets/requirejsRegistry.ts deleted file mode 100644 index bf5c7755ccec..000000000000 --- a/src/datascience-ui/ipywidgets/requirejsRegistry.ts +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { WidgetScriptSource } from '../../client/datascience/ipywidgets/types'; - -type NonPartial = { - [P in keyof T]-?: T[P]; -}; - -// Key = module name, value = path to script. -const scriptsAlreadyRegisteredInRequireJs = new Map(); - -function getScriptsToBeRegistered(scripts: WidgetScriptSource[]) { - return scripts.filter((script) => { - // Ignore scripts that have already been registered once before. - if ( - scriptsAlreadyRegisteredInRequireJs.has(script.moduleName) && - scriptsAlreadyRegisteredInRequireJs.get(script.moduleName) === script.scriptUri - ) { - return false; - } - return true; - }); -} - -function getScriptsWithAValidScriptUriToBeRegistered(scripts: WidgetScriptSource[]) { - return scripts - .filter((source) => { - if (source.scriptUri) { - // tslint:disable-next-line: no-console - console.log( - `Source for IPyWidget ${source.moduleName} found in ${source.source} @ ${source.scriptUri}.` - ); - return true; - } else { - // tslint:disable-next-line: no-console - console.error(`Source for IPyWidget ${source.moduleName} not found.`); - return false; - } - }) - .map((source) => source as NonPartial); -} - -function registerScriptsInRequireJs(scripts: NonPartial[]) { - // tslint:disable-next-line: no-any - const requirejs = (window as any).requirejs as { config: Function }; - if (!requirejs) { - window.console.error('Requirejs not found'); - throw new Error('Requirejs not found'); - } - const config: { paths: Record } = { - paths: {} - }; - scripts.forEach((script) => { - scriptsAlreadyRegisteredInRequireJs.set(script.moduleName, script.scriptUri); - // Drop the `.js` from the scriptUri. - const scriptUri = script.scriptUri.toLowerCase().endsWith('.js') - ? script.scriptUri.substring(0, script.scriptUri.length - 3) - : script.scriptUri; - // Register the script source into requirejs so it gets loaded via requirejs. - config.paths[script.moduleName] = scriptUri; - }); - - requirejs.config(config); -} - -export function registerScripts(scripts: WidgetScriptSource[]) { - const scriptsToRegister = getScriptsToBeRegistered(scripts); - const validScriptsToRegister = getScriptsWithAValidScriptUriToBeRegistered(scriptsToRegister); - registerScriptsInRequireJs(validScriptsToRegister); -} diff --git a/src/datascience-ui/ipywidgets/types.ts b/src/datascience-ui/ipywidgets/types.ts deleted file mode 100644 index 528eb0035d69..000000000000 --- a/src/datascience-ui/ipywidgets/types.ts +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as jupyterlab from '@jupyter-widgets/base/lib'; -import type { Kernel, KernelMessage } from '@jupyterlab/services'; -import type { nbformat } from '@jupyterlab/services/node_modules/@jupyterlab/coreutils'; -import { ISignal } from '@phosphor/signaling'; -import { Widget } from '@phosphor/widgets'; -import { IInteractiveWindowMapping } from '../../client/datascience/interactive-common/interactiveWindowTypes'; - -export interface IMessageSender { - sendMessage(type: T, payload?: M[T]): void; -} - -export type CommTargetCallback = (comm: Kernel.IComm, msg: KernelMessage.ICommOpenMsg) => void | PromiseLike; - -export type IJupyterLabWidgetManagerCtor = new ( - kernel: Kernel.IKernelConnection, - el: HTMLElement, - scriptLoader: { - // tslint:disable-next-line: no-any - errorHandler(className: string, moduleName: string, moduleVersion: string, error: any): void; - } -) => IJupyterLabWidgetManager; - -export interface IJupyterLabWidgetManager { - /** - * Signal emitted when a view emits an IO Pub message but nothing handles it. - */ - readonly onUnhandledIOPubMessage: ISignal; - dispose(): void; - /** - * Close all widgets and empty the widget state. - * @return Promise that resolves when the widget state is cleared. - */ - clear_state(): Promise; - /** - * Get a promise for a model by model id. - * - * #### Notes - * If a model is not found, undefined is returned (NOT a promise). However, - * the calling code should also deal with the case where a rejected promise - * is returned, and should treat that also as a model not found. - */ - get_model(model_id: string): Promise | undefined; - /** - * Display a DOMWidget view. - * - */ - // tslint:disable-next-line: no-any - display_view(msg: any, view: Backbone.View, options: any): Promise; - /** - * Creates a promise for a view of a given model - * - * Make sure the view creation is not out of order with - * any state updates. - */ - // tslint:disable-next-line: no-any - create_view(model: jupyterlab.DOMWidgetModel, options: any): Promise; -} - -// export interface IIPyWidgetManager extends IMessageHandler { -export interface IIPyWidgetManager { - dispose(): void; - /** - * Clears/removes all the widgets - * - * @memberof IIPyWidgetManager - */ - clear(): Promise; - /** - * Displays a widget for the mesasge with header.msg_type === 'display_data'. - * The widget is rendered in a given HTML element. - * Returns a disposable that can be used to dispose/remove the rendered Widget. - * The message must - * - * @param {KernelMessage.IIOPubMessage} msg - * @param {HTMLElement} ele - * @returns {Promise<{ dispose: Function }>} - * @memberof IIPyWidgetManager - */ - renderWidget(data: nbformat.IMimeBundle, ele: HTMLElement): Promise; -} diff --git a/src/datascience-ui/native-editor/addCellLine.tsx b/src/datascience-ui/native-editor/addCellLine.tsx deleted file mode 100644 index f5513aafecc9..000000000000 --- a/src/datascience-ui/native-editor/addCellLine.tsx +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; -import * as React from 'react'; -import { Image, ImageName } from '../react-common/image'; -import { getLocString } from '../react-common/locReactSide'; - -interface IAddCellLineProps { - baseTheme: string; - includePlus: boolean; - className: string; - isNotebookTrusted: boolean; - click(): void; -} - -export class AddCellLine extends React.Component { - constructor(props: IAddCellLineProps) { - super(props); - } - - public render() { - const className = `add-cell-line ${this.props.className}`; - const tooltip = getLocString('DataScience.insertBelow', 'Insert cell below'); - const plus = this.props.includePlus ? ( - - ) : null; - const disabled = !this.props.isNotebookTrusted; - const innerFilter = disabled ? 'image-button-inner-disabled-filter' : ''; - return ( -
- -
- ); - } -} diff --git a/src/datascience-ui/native-editor/index.html b/src/datascience-ui/native-editor/index.html deleted file mode 100644 index dbc0b671d27a..000000000000 --- a/src/datascience-ui/native-editor/index.html +++ /dev/null @@ -1,392 +0,0 @@ - - - - - - - React App - - - - -
- - - - - - - diff --git a/src/datascience-ui/native-editor/index.tsx b/src/datascience-ui/native-editor/index.tsx deleted file mode 100644 index 22c592903b56..000000000000 --- a/src/datascience-ui/native-editor/index.tsx +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -// This must be on top, do not change. Required by webpack. -import '../common/main'; -// This must be on top, do not change. Required by webpack. - -// tslint:disable-next-line: ordered-imports -import '../common/index.css'; - -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import { Provider } from 'react-redux'; - -import { WidgetManagerComponent } from '../ipywidgets/container'; -import { IVsCodeApi, PostOffice } from '../react-common/postOffice'; -import { detectBaseTheme } from '../react-common/themeDetector'; -import { getConnectedNativeEditor } from './nativeEditor'; -import { createStore } from './redux/store'; - -// This special function talks to vscode from a web panel -export declare function acquireVsCodeApi(): IVsCodeApi; -const baseTheme = detectBaseTheme(); -// tslint:disable-next-line: no-any -const testMode = (window as any).inTestMode; -// tslint:disable-next-line: no-typeof-undefined -const skipDefault = testMode ? false : typeof acquireVsCodeApi !== 'undefined'; - -// Create the redux store -const postOffice = new PostOffice(); -const store = createStore(skipDefault, baseTheme, testMode, postOffice); - -// Wire up a connected react control for our NativeEditor -const ConnectedNativeEditor = getConnectedNativeEditor(); - -// Stick them all together -ReactDOM.render( - - - - , - document.getElementById('root') as HTMLElement -); diff --git a/src/datascience-ui/native-editor/nativeCell.tsx b/src/datascience-ui/native-editor/nativeCell.tsx deleted file mode 100644 index ad367cfca7cf..000000000000 --- a/src/datascience-ui/native-editor/nativeCell.tsx +++ /dev/null @@ -1,904 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import '../../client/common/extensions'; - -import { nbformat } from '@jupyterlab/coreutils'; -import * as fastDeepEqual from 'fast-deep-equal'; -import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; -import * as React from 'react'; -import { connect } from 'react-redux'; - -import { OSType } from '../../client/common/utils/platform'; -import { - Identifiers, - NativeKeyboardCommandTelemetry, - NativeMouseCommandTelemetry -} from '../../client/datascience/constants'; -import { CellState } from '../../client/datascience/types'; -import { concatMultilineString } from '../common'; -import { CellInput } from '../interactive-common/cellInput'; -import { CellOutput } from '../interactive-common/cellOutput'; -import { ExecutionCount } from '../interactive-common/executionCount'; -import { InformationMessages } from '../interactive-common/informationMessages'; -import { activeDebugState, CursorPos, DebugState, ICellViewModel, IFont } from '../interactive-common/mainState'; -import { getOSType } from '../react-common/constants'; -import { IKeyboardEvent } from '../react-common/event'; -import { Image, ImageName } from '../react-common/image'; -import { ImageButton } from '../react-common/imageButton'; -import { getLocString } from '../react-common/locReactSide'; -import { IMonacoModelContentChangeEvent } from '../react-common/monacoHelpers'; -import { AddCellLine } from './addCellLine'; -import { actionCreators } from './redux/actions'; - -namespace CssConstants { - export const CellOutputWrapper = 'cell-output-wrapper'; - export const CellOutputWrapperClass = `.${CellOutputWrapper}`; - export const ImageButtonClass = '.image-button'; -} - -interface INativeCellBaseProps { - role?: string; - cellVM: ICellViewModel; - language: string; - - baseTheme: string; - codeTheme: string; - testMode?: boolean; - maxTextSize?: number; - enableScroll?: boolean; - monacoTheme: string | undefined; - lastCell: boolean; - firstCell: boolean; - font: IFont; - allowUndo: boolean; - gatherIsInstalled: boolean; - editorOptions: monacoEditor.editor.IEditorOptions; - themeMatplotlibPlots: boolean | undefined; - focusPending: number; - busy: boolean; - useCustomEditorApi: boolean; - runningByLine: DebugState; - supportsRunByLine: boolean; - isNotebookTrusted: boolean; -} - -type INativeCellProps = INativeCellBaseProps & typeof actionCreators; - -// tslint:disable: react-this-binding-issue -export class NativeCell extends React.Component { - private inputRef: React.RefObject = React.createRef(); - private wrapperRef: React.RefObject = React.createRef(); - private lastKeyPressed: string | undefined; - - constructor(prop: INativeCellProps) { - super(prop); - } - - public render() { - if (this.props.cellVM.cell.data.cell_type === 'messages') { - return ; - } else { - return this.renderNormalCell(); - } - } - - public componentDidUpdate(prevProps: INativeCellProps) { - if (this.props.cellVM.selected && !prevProps.cellVM.selected && !this.props.cellVM.focused) { - this.giveFocus(); - } - - // Anytime we update, reset the key. This object will be reused for different cell ids - this.lastKeyPressed = undefined; - } - - public shouldComponentUpdate(nextProps: INativeCellProps): boolean { - return !fastDeepEqual(this.props, nextProps); - } - - // Public for testing - public getUnknownMimeTypeFormatString() { - return getLocString('DataScience.unknownMimeTypeFormat', 'Unknown Mime Type'); - } - - private giveFocus() { - if (this.wrapperRef && this.wrapperRef.current) { - // Give focus to the cell if not already owning focus - if (!this.wrapperRef.current.contains(document.activeElement)) { - this.wrapperRef.current.focus(); - } - - // Scroll into view (since we have focus). However this function - // is not supported on enzyme - // tslint:disable-next-line: no-any - if ((this.wrapperRef.current as any).scrollIntoView) { - this.wrapperRef.current.scrollIntoView({ behavior: 'auto', block: 'nearest', inline: 'nearest' }); - } - } - } - - private getCell = () => { - return this.props.cellVM.cell; - }; - - private isCodeCell = () => { - return this.props.cellVM.cell.data.cell_type === 'code'; - }; - - private isMarkdownCell = () => { - return this.props.cellVM.cell.data.cell_type === 'markdown'; - }; - - private isSelected = () => { - return this.props.cellVM.selected; - }; - - private isNotebookTrusted = () => { - return this.props.isNotebookTrusted; - }; - - private isFocused = () => { - return this.props.cellVM.focused; - }; - - private isError = () => { - return this.props.cellVM.cell.state === CellState.error; - }; - - private renderNormalCell() { - const cellOuterClass = this.props.cellVM.editable ? 'cell-outer-editable' : 'cell-outer'; - let cellWrapperClass = this.props.cellVM.editable ? 'cell-wrapper' : 'cell-wrapper cell-wrapper-noneditable'; - if (this.isSelected() && !this.isFocused()) { - cellWrapperClass += ' cell-wrapper-selected'; - } - if (this.isFocused()) { - cellWrapperClass += ' cell-wrapper-focused'; - } - - // Content changes based on if a markdown cell or not. - const content = - this.isMarkdownCell() && !this.isShowingMarkdownEditor() ? ( -
-
- {this.renderCollapseBar(false)} - {this.renderOutput()} -
- {this.renderAddDivider(false)} -
- ) : ( -
-
- {this.renderCollapseBar(true)} - {this.renderControls()} - {this.renderInput()} -
- {this.renderAddDivider(true)} -
- {this.renderCollapseBar(false)} - {this.renderOutput()} -
-
- ); - - return ( -
-
- {this.renderNavbar()} -
{content}
-
-
- ); - } - - private allowClickPropagation(elem: HTMLElement): boolean { - if (this.isMarkdownCell()) { - return true; - } - if (!elem.closest(CssConstants.ImageButtonClass) && !elem.closest(CssConstants.CellOutputWrapperClass)) { - return true; - } - return false; - } - - private onMouseClick = (ev: React.MouseEvent) => { - if (ev.nativeEvent.target) { - const elem = ev.nativeEvent.target as HTMLElement; - if (this.allowClickPropagation(elem)) { - // Not a click on an button in a toolbar or in output, select the cell. - ev.stopPropagation(); - this.lastKeyPressed = undefined; - this.props.selectCell(this.cellId); - } - } - }; - - private onMouseDoubleClick = (ev: React.MouseEvent) => { - const elem = ev.nativeEvent.target as HTMLElement; - if (this.allowClickPropagation(elem)) { - // When we receive double click, propagate upwards. Might change our state - ev.stopPropagation(); - this.props.focusCell(this.cellId, CursorPos.Current); - } - }; - - private shouldRenderCodeEditor = (): boolean => { - return this.isCodeCell() && (this.props.cellVM.inputBlockShow || this.props.cellVM.editable); - }; - - private shouldRenderMarkdownEditor = (): boolean => { - return ( - this.isMarkdownCell() && - (this.isShowingMarkdownEditor() || this.props.cellVM.cell.id === Identifiers.EditCellId) - ); - }; - - private isShowingMarkdownEditor = (): boolean => { - return this.isMarkdownCell() && (this.props.cellVM.focused || !this.isNotebookTrusted()); - }; - - private shouldRenderInput(): boolean { - return this.shouldRenderCodeEditor() || this.shouldRenderMarkdownEditor(); - } - - private hasOutput = () => { - return ( - this.getCell().state === CellState.finished || - this.getCell().state === CellState.error || - this.getCell().state === CellState.executing - ); - }; - - private getCodeCell = () => { - return this.props.cellVM.cell.data as nbformat.ICodeCell; - }; - - private shouldRenderOutput(): boolean { - if (!this.isNotebookTrusted()) { - return false; - } - if (this.isCodeCell()) { - const cell = this.getCodeCell(); - return ( - this.hasOutput() && - cell.outputs && - !this.props.cellVM.hideOutput && - Array.isArray(cell.outputs) && - cell.outputs.length !== 0 - ); - } else if (this.isMarkdownCell()) { - return !this.isShowingMarkdownEditor(); - } - return false; - } - - // tslint:disable-next-line: cyclomatic-complexity max-func-body-length - private keyDownInput = (cellId: string, e: IKeyboardEvent) => { - if (!this.isNotebookTrusted() && !isCellNavigationKeyboardEvent(e)) { - return; - } - const isFocusedWhenNotSuggesting = this.isFocused() && e.editorInfo && !e.editorInfo.isSuggesting; - switch (e.code) { - case 'ArrowUp': - case 'k': - if ((isFocusedWhenNotSuggesting && e.editorInfo!.isFirstLine && !e.shiftKey) || !this.isFocused()) { - this.arrowUpFromCell(e); - } - break; - case 'ArrowDown': - case 'j': - if ((isFocusedWhenNotSuggesting && e.editorInfo!.isLastLine && !e.shiftKey) || !this.isFocused()) { - this.arrowDownFromCell(e); - } - break; - case 's': - if ((e.ctrlKey && getOSType() !== OSType.OSX) || (e.metaKey && getOSType() === OSType.OSX)) { - // This is save, save our cells - this.props.save(); - } - break; - - case 'Escape': - if (isFocusedWhenNotSuggesting) { - this.escapeCell(e); - } - break; - case 'y': - if (!this.isFocused() && this.isSelected() && this.isMarkdownCell()) { - e.stopPropagation(); - e.preventDefault(); - this.props.changeCellType(cellId); - this.props.sendCommand(NativeKeyboardCommandTelemetry.ChangeToCode); - } - break; - case 'm': - if (!this.isFocused() && this.isSelected() && this.isCodeCell()) { - e.stopPropagation(); - e.preventDefault(); - this.props.changeCellType(cellId); - this.props.sendCommand(NativeKeyboardCommandTelemetry.ChangeToMarkdown); - } - break; - case 'l': - if (!this.isFocused() && this.isSelected()) { - e.stopPropagation(); - e.preventDefault(); - this.props.toggleLineNumbers(cellId); - this.props.sendCommand(NativeKeyboardCommandTelemetry.ToggleLineNumbers); - } - break; - case 'o': - if (!this.isFocused() && this.isSelected()) { - e.stopPropagation(); - e.preventDefault(); - this.props.toggleOutput(cellId); - this.props.sendCommand(NativeKeyboardCommandTelemetry.ToggleOutput); - } - break; - case 'NumpadEnter': - case 'Enter': - if (e.shiftKey) { - this.shiftEnterCell(e); - } else if (e.ctrlKey) { - this.ctrlEnterCell(e); - } else if (e.altKey) { - this.altEnterCell(e); - } else { - this.enterCell(e); - } - break; - case 'd': - if (this.lastKeyPressed === 'd' && !this.isFocused() && this.isSelected()) { - e.stopPropagation(); - this.lastKeyPressed = undefined; // Reset it so we don't keep deleting - this.props.deleteCell(cellId); - this.props.sendCommand(NativeKeyboardCommandTelemetry.DeleteCell); - } - break; - case 'a': - if (!this.isFocused()) { - e.stopPropagation(); - e.preventDefault(); - setTimeout(() => this.props.insertAbove(cellId), 1); - this.props.sendCommand(NativeKeyboardCommandTelemetry.InsertAbove); - } - break; - case 'b': - if (!this.isFocused()) { - e.stopPropagation(); - e.preventDefault(); - setTimeout(() => this.props.insertBelow(cellId), 1); - this.props.sendCommand(NativeKeyboardCommandTelemetry.InsertBelow); - } - break; - case 'z': - case 'Z': - if (!this.isFocused() && !this.props.useCustomEditorApi) { - if (e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) { - e.stopPropagation(); - this.props.redo(); - this.props.sendCommand(NativeKeyboardCommandTelemetry.Redo); - } else if (!e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey) { - e.stopPropagation(); - this.props.undo(); - this.props.sendCommand(NativeKeyboardCommandTelemetry.Undo); - } - } - break; - default: - break; - } - - this.lastKeyPressed = e.code; - }; - - private get cellId(): string { - return this.props.cellVM.cell.id; - } - - private escapeCell = (e: IKeyboardEvent) => { - // Unfocus the current cell by giving focus to the cell itself - if (this.wrapperRef && this.wrapperRef.current && this.isFocused()) { - e.stopPropagation(); - this.wrapperRef.current.focus(); - this.props.sendCommand(NativeKeyboardCommandTelemetry.Unfocus); - } - }; - - private arrowUpFromCell = (e: IKeyboardEvent) => { - e.stopPropagation(); - e.preventDefault(); - this.props.arrowUp(this.cellId, this.getCurrentCode()); - this.props.sendCommand(NativeKeyboardCommandTelemetry.ArrowUp); - }; - - private arrowDownFromCell = (e: IKeyboardEvent) => { - e.stopPropagation(); - e.preventDefault(); - this.props.arrowDown(this.cellId, this.getCurrentCode()); - this.props.sendCommand(NativeKeyboardCommandTelemetry.ArrowDown); - }; - - private enterCell = (e: IKeyboardEvent) => { - // If focused, then ignore this call. It should go to the focused cell instead. - if (!this.isFocused() && !e.editorInfo && this.wrapperRef && this.wrapperRef && this.isSelected()) { - e.stopPropagation(); - e.preventDefault(); - this.props.focusCell(this.cellId, CursorPos.Current); - } - }; - - private shiftEnterCell = (e: IKeyboardEvent) => { - // Prevent shift enter from add an enter - e.stopPropagation(); - e.preventDefault(); - - // Submit and move to the next. - this.runAndMove(); - - this.props.sendCommand(NativeKeyboardCommandTelemetry.RunAndMove); - }; - - private altEnterCell = (e: IKeyboardEvent) => { - // Prevent shift enter from add an enter - e.stopPropagation(); - e.preventDefault(); - - // Submit this cell - this.runAndAdd(); - - this.props.sendCommand(NativeKeyboardCommandTelemetry.RunAndAdd); - }; - - private runAndMove() { - // Submit this cell - this.submitCell(this.props.lastCell ? 'add' : 'select'); - } - - private runAndAdd() { - // Submit this cell - this.submitCell('add'); - } - - private ctrlEnterCell = (e: IKeyboardEvent) => { - // Prevent shift enter from add an enter - e.stopPropagation(); - e.preventDefault(); - - // Escape the current cell if it is markdown to make it render - if (this.isMarkdownCell()) { - this.escapeCell(e); - } - - // Submit this cell - this.submitCell('none'); - this.props.sendCommand(NativeKeyboardCommandTelemetry.Run); - }; - - private submitCell = (moveOp: 'add' | 'select' | 'none') => { - this.props.executeCell(this.cellId, moveOp); - }; - - private addNewCell = () => { - setTimeout(() => this.props.insertBelow(this.cellId), 1); - this.props.sendCommand(NativeMouseCommandTelemetry.AddToEnd); - }; - private addNewCellBelow = () => { - setTimeout(() => this.props.insertBelow(this.cellId), 1); - this.props.sendCommand(NativeMouseCommandTelemetry.InsertBelow); - }; - - private renderNavbar = () => { - const moveUp = () => { - this.props.moveCellUp(this.cellId); - this.props.sendCommand(NativeMouseCommandTelemetry.MoveCellUp); - }; - const moveDown = () => { - this.props.moveCellDown(this.cellId); - this.props.sendCommand(NativeMouseCommandTelemetry.MoveCellDown); - }; - const addButtonRender = !this.props.lastCell ? ( -
- - - -
- ) : null; - - return ( -
-
- - - -
-
- - - -
- {addButtonRender} -
- ); - }; - - private renderAddDivider = (checkOutput: boolean) => { - // Skip on the last cell - if (!this.props.lastCell) { - // Divider should only show if no output - if (!checkOutput || !this.shouldRenderOutput()) { - return ( - - ); - } - } - - return null; - }; - - private getCurrentCode(): string { - // Input may not be open at this time. If not, then use current cell contents. - const contents = this.inputRef.current ? this.inputRef.current.getContents() : undefined; - return contents || concatMultilineString(this.props.cellVM.cell.data.source); - } - - private renderMiddleToolbar = () => { - const cellId = this.props.cellVM.cell.id; - const runCell = () => { - this.runAndMove(); - this.props.sendCommand(NativeMouseCommandTelemetry.Run); - }; - const gatherCell = () => { - this.props.gatherCell(cellId); - }; - const deleteCell = () => { - this.props.deleteCell(cellId); - this.props.sendCommand(NativeMouseCommandTelemetry.DeleteCell); - }; - const runbyline = () => { - this.props.focusCell(cellId); - this.props.runByLine(cellId); - }; - const stop = () => { - this.props.interruptKernel(); - }; - const step = () => { - this.props.focusCell(cellId); - this.props.step(cellId); - }; - const gatherDisabled = - this.props.cellVM.cell.data.execution_count === null || - this.props.cellVM.hasBeenRun === null || - this.props.cellVM.hasBeenRun === false || - this.props.cellVM.cell.state === CellState.executing || - this.isError() || - this.isMarkdownCell() || - !this.props.gatherIsInstalled; - const switchTooltip = - this.props.cellVM.cell.data.cell_type === 'code' - ? getLocString('DataScience.switchToMarkdown', 'Change to markdown') - : getLocString('DataScience.switchToCode', 'Change to code'); - const otherCellType = this.props.cellVM.cell.data.cell_type === 'code' ? 'markdown' : 'code'; - const otherCellTypeCommand = - otherCellType === 'markdown' - ? NativeMouseCommandTelemetry.ChangeToMarkdown - : NativeMouseCommandTelemetry.ChangeToCode; - const otherCellImage = otherCellType === 'markdown' ? ImageName.SwitchToMarkdown : ImageName.SwitchToCode; - const switchCellType = (event: React.MouseEvent) => { - // Prevent this mouse click from stealing focus so that we - // can give focus to the cell input. - event.stopPropagation(); - event.preventDefault(); - this.props.changeCellType(cellId); - this.props.sendCommand(otherCellTypeCommand); - }; - const toolbarClassName = this.props.cellVM.cell.data.cell_type === 'code' ? '' : 'markdown-toolbar'; - - if (activeDebugState(this.props.runningByLine) && !this.isMarkdownCell()) { - return ( -
-
- - - -
-
-
- ); - } - return ( -
-
- - - - - - - - - -
-
-
- ); - }; - - private renderControls = () => { - const busy = - this.props.cellVM.cell.state === CellState.init || this.props.cellVM.cell.state === CellState.executing; - const executionCount = - this.props.cellVM && - this.props.cellVM.cell && - this.props.cellVM.cell.data && - this.props.cellVM.cell.data.execution_count - ? this.props.cellVM.cell.data.execution_count.toString() - : '-'; - - return ( -
- -
- ); - }; - - private renderInput = () => { - if (this.shouldRenderInput()) { - // Make sure the glyph margin is always there for native cells. - // We need it for debugging. - const options = { - ...this.props.editorOptions, - glyphMargin: true - }; - return ( -
- {this.renderMiddleToolbar()} - -
- ); - } - return null; - }; - - private onCodeFocused = () => { - this.props.focusCell(this.cellId, CursorPos.Current); - }; - - private onCodeUnfocused = () => { - // Make sure to save the code from the editor into the cell - this.props.unfocusCell(this.cellId, this.getCurrentCode()); - }; - - private onCodeChange = (e: IMonacoModelContentChangeEvent) => { - this.props.editCell(this.getCell().id, e); - }; - - private onCodeCreated = (_code: string, _file: string, cellId: string, modelId: string) => { - this.props.codeCreated(cellId, modelId); - }; - - private renderOutput = (): JSX.Element | null => { - const themeMatplotlibPlots = this.props.themeMatplotlibPlots ? true : false; - const toolbar = this.props.cellVM.cell.data.cell_type === 'markdown' ? this.renderMiddleToolbar() : null; - if (this.shouldRenderOutput()) { - return ( -
- {toolbar} - -
- ); - } - return null; - }; - - private onOuterKeyDown = (event: React.KeyboardEvent) => { - // Handle keydown events for the entire cell when we don't have focus - if (event.key !== 'Tab' && !this.isFocused() && !this.focusInOutput()) { - this.keyDownInput(this.props.cellVM.cell.id, { - code: event.key, - shiftKey: event.shiftKey, - ctrlKey: event.ctrlKey, - metaKey: event.metaKey, - altKey: event.altKey, - target: event.target as HTMLDivElement, - stopPropagation: () => event.stopPropagation(), - preventDefault: () => event.preventDefault() - }); - } - }; - - private focusInOutput(): boolean { - const focusedElement = document.activeElement as HTMLElement; - if (focusedElement) { - return focusedElement.closest(CssConstants.CellOutputWrapperClass) !== null; - } - return false; - } - - private renderCollapseBar = (input: boolean) => { - let classes = 'collapse-bar'; - - if (this.isSelected() && !this.isFocused()) { - classes += ' collapse-bar-selected'; - } - if (this.isFocused()) { - classes += ' collapse-bar-focused'; - } - - if (input) { - return
; - } - - if (this.props.cellVM.cell.data.cell_type === 'markdown') { - classes += ' collapse-bar-markdown'; - } else if ( - Array.isArray(this.props.cellVM.cell.data.outputs) && - this.props.cellVM.cell.data.outputs.length !== 0 - ) { - classes += ' collapse-bar-output'; - } else { - return null; - } - - return
; - }; - - private openLink = (uri: monacoEditor.Uri) => { - this.props.linkClick(uri.toString()); - }; -} - -// Main export, return a redux connected editor -export function getConnectedNativeCell() { - return connect(null, actionCreators)(NativeCell); -} - -function isCellNavigationKeyboardEvent(e: IKeyboardEvent) { - return ( - ((e.code === 'Enter' || e.code === 'NumpadEnter') && !e.shiftKey && !e.ctrlKey && !e.altKey) || - e.code === 'ArrowUp' || - e.code === 'k' || - e.code === 'ArrowDown' || - e.code === 'j' || - e.code === 'Escape' - ); -} diff --git a/src/datascience-ui/native-editor/nativeEditor.less b/src/datascience-ui/native-editor/nativeEditor.less deleted file mode 100644 index ac52ff444f02..000000000000 --- a/src/datascience-ui/native-editor/nativeEditor.less +++ /dev/null @@ -1,494 +0,0 @@ -/* Import common styles and then override them below */ -@import '../interactive-common/common.css'; - -.toolbar-panel-button { - border-width: 1px; - border-style: solid; - border-color: var(--override-badge-background, var(--vscode-badge-background)); - color: var(--override-foreground, var(--vscode-editor-foreground)); - text-align: center; - overflow: hidden; - margin-left: 2px; - padding: 2px; - background-color: transparent; - cursor: hand; -} - -.cell-wrapper { - margin: 2px 2px 0px 0px; - position: relative; - min-height: 55px; -} - -.cell-wrapper-focused { - margin: 2px 2px 0px 0px; -} - -.cell-wrapper-selected { - margin: 2px 2px 0px 0px; -} - -.cell-menu-bar-outer { - justify-self: right; -} - -.cell-output-wrapper { - grid-row: 1; - grid-column: 3; -} - -.cell-output { - margin-top: 5px; - background: transparent; - width: 100%; - overflow-x: scroll; -} - -.cell-output-text { - white-space: pre-wrap; - word-break: break-all; - overflow-x: hidden; -} - -.markdown-cell-output-container { - grid-row: 1; - grid-column: 3; -} - -.markdown-cell-output { - width: 100%; - overflow-x: scroll; -} - -.cell-outer { - display: grid; - grid-template-columns: auto auto minmax(0, 1fr); -} - -.cell-outer-editable { - display: grid; - grid-template-columns: auto auto minmax(0, 1fr); - margin-top: 0px; -} - -.cell-state-selector { - border-width: 1px; - border-style: solid; - border-color: var(--override-badge-background, var(--vscode-badge-background)); - color: var(--override-foreground, var(--vscode-editor-foreground)); - text-align: center; - overflow: hidden; - margin-left: 2px; - padding: 2px; - background-color: transparent; - cursor: hand; -} - -.cell-state-selector-option { - border-width: 1px; - border-style: solid; - border-color: var(--override-badge-background, var(--vscode-badge-background)); - color: var(--override-foreground, var(--vscode-editor-foreground)); - background-color: var(--override-background, var(--vscode-editor-background)); -} - -.code-area { - position: relative; - width: 100%; - padding-right: 8px; - margin-bottom: 0px; - padding-left: 2px; - padding-top: 2px; - background: var(--override-widget-background, var(--vscode-notifications-background)); -} - -.markdown-editor-area { - position: relative; - width: 100%; - padding-right: 8px; - margin-bottom: 0px; - padding-left: 2px; - padding-top: 2px; - background: var(--override-widget-background, var(--vscode-notifications-background)); -} - -.code-watermark { - top: 5px; /* Account for extra padding and border in native editor */ -} - -.cell-input-wrapper { - grid-column: 3; - grid-row: 1; -} - -.cell-input { - margin: 2px 10px 0px 0px; -} - -.content-div { - grid-column: 3; -} - -.controls-div { - min-width: 34px; - padding-left: 4px; - padding-right: 4px; - display: block; - grid-column: 2; - grid-row: 1; -} - -.navbar-div { - grid-column: 1; - visibility: hidden; - display: grid; - grid-template-rows: var(--button-size) var(--button-size) auto; -} - -.navbar-add-button { - align-self: end; -} - -.execution-count { - justify-self: end; - margin-bottom: 10px; - margin-top: 1px; -} - -.execution-count-busy-outer { - justify-self: end; -} - -.native-editor-celltoolbar-inner { - justify-self: center; - grid-column: 1; -} - -.native-editor-celltoolbar-middle { - display: flex; - grid-column: 3; - grid-row: 2; - justify-items: left; - background: var(--vscode-notifications-background); -} - -.native-editor-celltoolbar-divider { - background-color: var(--vscode-badge-background); - height: 2px; -} - -.code-toolbar { - visibility: visible; -} - -.markdown-toolbar { - visibility: collapse; -} - -.hover-cell-button { - visibility: collapse; -} - -.cell-wrapper:hover .hover-cell-button { - visibility: visible; -} - -.cell-wrapper-selected .hover-cell-button { - visibility: visible; -} - -.cell-wrapper-focused .hover-cell-button { - visibility: visible; -} - -.delete-cell-button { - right: 2px; - position: absolute; - visibility: collapse; -} - -.cell-wrapper:hover .navbar-div { - visibility: visible; -} - -.cell-wrapper-selected .navbar-div { - visibility: visible; -} - -.cell-wrapper-focused .navbar-div { - visibility: visible; -} - -.cell-wrapper:hover .markdown-toolbar { - visibility: visible; -} - -.cell-wrapper-selected .markdown-toolbar { - visibility: visible; -} - -.cell-wrapper-focused .markdown-toolbar { - visibility: visible; -} - -// .cell-wrapper:hover .native-editor-celltoolbar-middle { -// visibility: visible; -// } - -// .cell-wrapper-selected .native-editor-celltoolbar-middle { -// visibility: visible; -// } - -// .cell-wrapper-focused .native-editor-celltoolbar-middle { -// visibility: visible; -// } - -.native-editor-flyout-button { - width: auto; - height: auto; - border-color: transparent; - background-color: transparent; - padding: 0px; - margin-left: 4px; - margin-right: 0px; - margin-top: 0px; - margin-bottom: 0px; - border-width: 0px; -} - -.native-editor-flyout-button:focus { - outline: none; -} - -.native-editor-cellflyout { - position: relative; - left: 20px; - top: -15px; - width: auto; - height: auto; - padding-top: 2px; - padding-right: 2px; - z-index: 100; -} - -.native-editor-cellflyout-selected { - background-color: var(--vscode-peekView-border); -} - -.native-editor-cellflyout-focused { - background-color: var(--vscode-editorInfo-foreground); -} - -.flyout-button-content { - color: var(--override-foreground, var(--vscode-editor-foreground)); -} - -.native-button { - background: transparent; - z-index: 10; -} - -#toolbar-panel { - margin-top: 2px; - margin-bottom: 2px; - margin-left: 0px; - margin-right: 0px; -} - -#content-panel-div { - overflow: hidden; -} - -/* Fix image buttons that are supposed to be hidden from showing up */ -.flyout-children-hidden .image-button { - width: 0px; - height: 0px; - margin-left: 0px; - padding: 0px; -} - -.add-cell-line { - display: flex; - justify-content: left; - margin-top: 5px; - margin-bottom: 0px; - margin-left: 5px; - margin-right: 5px; -} - -.add-cell-line:focus-within { - outline: 1px solid black; -} - -.add-cell-line-top { - margin-top: 2px; - margin-bottom: 0px; -} - -.add-cell-line-top-force-visible { - margin-top: 2px; - margin-bottom: 0px; -} - -.add-cell-line-top .add-cell-line-button { - visibility: hidden; -} - -.add-cell-line-button { - border-width: 0px; - border-style: solid; - text-align: center; - line-height: 16px; - background-color: transparent; - cursor: hand; - height: var(--button-size); - padding: 0px; - display: flex; -} - -.add-cell-line-button:focus { - outline: none; -} - -.add-cell-line-top:hover .add-cell-line-button { - visibility: visible; -} - -.add-cell-line-button .image-button-image { - height: var(--button-size); -} - -.add-cell-line-button .image-button-image svg { - height: var(--button-size); -} - -.add-cell-line-divider { - margin-top: 8px; - margin-left: 2px; - width: calc(100% - 40px); - border-width: 0px; - border-top-color: var(--override-badge-background, var(--vscode-badge-background)); - border-top-width: 1px; - border-style: solid; -} - -.add-cell-line-divider:hover { - cursor: pointer; -} - -.cell-wrapper-selected .add-cell-line { - visibility: visible; -} - -.cell-wrapper-focused .add-cell-line { - visibility: visible; -} - -/* -Cell Row Container layout --------------------------- -collapse-bar controls-div [cell-input, cell-output, markdown-cell-output-container] -(expanded c-bar) celltoolbar-middle -*/ -.cell-row-container { - display: grid; - grid-template-columns: auto auto minmax(0, 1fr); - grid-template-rows: 1fr auto; -} - -.collapse-bar { - grid-column: 1; - grid-row-start: 1; - grid-row-end: 2; - background-color: transparent; - max-width: 8px; - min-width: 8px; -} - -.cell-wrapper:hover .collapse-bar { - background-color: var(--override-widget-background, var(--vscode-notifications-background)); -} - -.collapse-bar-markdown { - margin: 0px 44px 0px 0px; -} - -.collapse-bar-output { - margin: 0px 28px 0px 16px; -} - -.collapse-bar-selected { - background-color: var(--vscode-peekView-border); - grid-row-start: 1; - grid-row-end: 3; -} - -.collapse-bar-focused { - background: repeating-linear-gradient( - -45deg, - transparent, - transparent 3px, - var(--vscode-editorGutter-addedBackground) 3px, - var(--vscode-editorGutter-addedBackground) 6px - ); - grid-row-start: 1; - grid-row-end: 3; -} - -.cell-wrapper:hover .collapse-bar-selected { - background-color: var(--vscode-peekView-border); -} - -.cell-wrapper:hover .collapse-bar-focused { - background: repeating-linear-gradient( - -45deg, - transparent, - transparent 3px, - var(--vscode-editorGutter-addedBackground) 3px, - var(--vscode-editorGutter-addedBackground) 6px - ); -} - -.add-divider { - visibility: hidden; - margin: 0px; - position: absolute; - bottom: 8px; -} - -.cell-wrapper:hover .add-divider { - visibility: hidden; - z-index: -100; - pointer-events: none; -} - -.cell-wrapper-selected .add-divider { - visibility: hidden; - z-index: -100; - pointer-events: none; -} - -.cell-wrapper-focused .add-divider { - visibility: hidden; - z-index: -100; - pointer-events: none; -} - -.cell-wrapper-selected:hover .add-divider { - visibility: hidden; - z-index: -100; - pointer-events: none; -} - -.cell-wrapper-focused:hover .add-divider { - visibility: hidden; - z-index: -100; - pointer-events: none; -} - -.native-editor-celltoolbar-middle .image-button { - margin-right: 3px; - margin-top: 4px; - margin-bottom: 4px; - margin-left: 3px; -} diff --git a/src/datascience-ui/native-editor/nativeEditor.tsx b/src/datascience-ui/native-editor/nativeEditor.tsx deleted file mode 100644 index 96b1bd0fcd4d..000000000000 --- a/src/datascience-ui/native-editor/nativeEditor.tsx +++ /dev/null @@ -1,397 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as React from 'react'; -import { connect } from 'react-redux'; -import { OSType } from '../../client/common/utils/platform'; -import { NativeKeyboardCommandTelemetry, NativeMouseCommandTelemetry } from '../../client/datascience/constants'; -import { buildSettingsCss } from '../interactive-common/buildSettingsCss'; -import { ContentPanel, IContentPanelProps } from '../interactive-common/contentPanel'; -import { handleLinkClick } from '../interactive-common/handlers'; -import { - activeDebugState, - DebugState, - getSelectedAndFocusedInfo, - ICellViewModel, - IMainState -} from '../interactive-common/mainState'; -import { IMainWithVariables, IStore } from '../interactive-common/redux/store'; -import { IVariablePanelProps, VariablePanel } from '../interactive-common/variablePanel'; -import { getOSType } from '../react-common/constants'; -import { ErrorBoundary } from '../react-common/errorBoundary'; -import { getLocString } from '../react-common/locReactSide'; -import { Progress } from '../react-common/progress'; -import { AddCellLine } from './addCellLine'; -import { getConnectedNativeCell } from './nativeCell'; -import './nativeEditor.less'; -import { actionCreators } from './redux/actions'; -import { ToolbarComponent } from './toolbar'; - -type INativeEditorProps = IMainWithVariables & typeof actionCreators; - -function mapStateToProps(state: IStore): IMainWithVariables { - return { ...state.main, variableState: state.variables }; -} - -const ConnectedNativeCell = getConnectedNativeCell(); - -export class NativeEditor extends React.Component { - private renderCount: number = 0; - private waitingForLoadRender = true; - private mainPanelToolbarRef: React.RefObject = React.createRef(); - - constructor(props: INativeEditorProps) { - super(props); - this.insertAboveFirst = this.insertAboveFirst.bind(this); - } - - public componentDidMount() { - this.props.editorLoaded(); - window.addEventListener('keydown', this.mainKeyDown); - window.addEventListener('resize', () => this.forceUpdate(), true); - document.addEventListener('click', this.linkClick, true); - } - - public componentWillUnmount() { - window.removeEventListener('keydown', this.mainKeyDown); - window.removeEventListener('resize', () => this.forceUpdate()); - document.removeEventListener('click', this.linkClick); - this.props.editorUnmounted(); - } - - public componentDidUpdate(prevProps: IMainState) { - if (this.props.loaded && !prevProps.loaded && this.waitingForLoadRender) { - this.waitingForLoadRender = false; - // After this render is complete (see this SO) - // https://stackoverflow.com/questions/26556436/react-after-render-code, - // indicate we are done loading. We want to wait for the render - // so we get accurate timing on first launch. - setTimeout(() => { - window.requestAnimationFrame(() => { - this.props.loadedAllCells(); - }); - }); - } - } - - public render() { - const dynamicFont: React.CSSProperties = { - fontSize: this.props.font.size, - fontFamily: this.props.font.family - }; - - // If in test mode, update our count. Use this to determine how many renders a normal update takes. - if (this.props.testMode) { - this.renderCount = this.renderCount + 1; - } - - // Update the state controller with our new state - const progressBar = (this.props.busy || !this.props.loaded) && !this.props.testMode ? : undefined; - const addCellLine = - this.props.cellVMs.length === 0 ? null : ( - - ); - - return ( -
-
- -
-
- {this.renderToolbarPanel()} - {progressBar} -
-
- {this.renderVariablePanel(this.props.baseTheme)} -
-
- {addCellLine} - {this.renderContentPanel(this.props.baseTheme)} -
-
- ); - } - - private insertAboveFirst() { - setTimeout(() => this.props.insertAboveFirst(), 1); - } - private renderToolbarPanel() { - return ; - } - - private renderVariablePanel(baseTheme: string) { - if (this.props.variableState.visible) { - const variableProps = this.getVariableProps(baseTheme); - return ; - } - - return null; - } - - private renderContentPanel(baseTheme: string) { - // Skip if the tokenizer isn't finished yet. It needs - // to finish loading so our code editors work. - if (!this.props.monacoReady && !this.props.testMode) { - return null; - } - - // Otherwise render our cells. - const contentProps = this.getContentProps(baseTheme); - return ; - } - - private getContentProps = (baseTheme: string): IContentPanelProps => { - return { - baseTheme: baseTheme, - cellVMs: this.props.cellVMs, - testMode: this.props.testMode, - codeTheme: this.props.codeTheme, - submittedText: this.props.submittedText, - skipNextScroll: this.props.skipNextScroll ? true : false, - editable: true, - renderCell: this.renderCell, - scrollToBottom: this.scrollDiv, - scrollBeyondLastLine: this.props.settings - ? this.props.settings.extraSettings.editor.scrollBeyondLastLine - : false - }; - }; - private getVariableProps = (baseTheme: string): IVariablePanelProps => { - let toolbarHeight = 0; - if (this.mainPanelToolbarRef.current) { - toolbarHeight = this.mainPanelToolbarRef.current.offsetHeight; - } - return { - variables: this.props.variableState.variables, - containerHeight: this.props.variableState.containerHeight, - gridHeight: this.props.variableState.gridHeight, - debugging: this.props.debugging, - busy: this.props.busy, - showDataExplorer: this.props.showDataViewer, - skipDefault: this.props.skipDefault, - testMode: this.props.testMode, - closeVariableExplorer: this.props.toggleVariableExplorer, - setVariableExplorerHeight: this.props.setVariableExplorerHeight, - baseTheme: baseTheme, - pageIn: this.pageInVariableData, - fontSize: this.props.font.size, - executionCount: this.props.currentExecutionCount, - refreshCount: this.props.variableState.refreshCount, - offsetHeight: toolbarHeight, - supportsDebugging: - this.props.settings && this.props.settings.variableOptions - ? this.props.settings.variableOptions.enableDuringDebugger - : false - }; - }; - - private pageInVariableData = (startIndex: number, pageSize: number) => { - this.props.getVariableData( - this.props.currentExecutionCount, - this.props.variableState.refreshCount, - startIndex, - pageSize - ); - }; - - private isNotebookTrusted = () => { - return this.props.isNotebookTrusted; - }; - - // tslint:disable-next-line: cyclomatic-complexity - private mainKeyDown = (event: KeyboardEvent) => { - if (!this.isNotebookTrusted()) { - return; // Disable keyboard interaction with untrusted notebooks - } - // Handler for key down presses in the main panel - switch (event.key) { - // tslint:disable-next-line: no-suspicious-comment - // TODO: How to have this work for when the keyboard shortcuts are changed? - case 's': { - if (!this.props.settings?.extraSettings.useCustomEditorApi) { - if ( - (event.ctrlKey && getOSType() !== OSType.OSX) || - (event.metaKey && getOSType() === OSType.OSX) - ) { - // This is save, save our cells - this.props.save(); - this.props.sendCommand(NativeKeyboardCommandTelemetry.Save); - } - } - break; - } - case 'z': - case 'Z': - if ( - !getSelectedAndFocusedInfo(this.props).focusedCellId && - !this.props.settings?.extraSettings.useCustomEditorApi - ) { - if (event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey) { - event.stopPropagation(); - this.props.redo(); - this.props.sendCommand(NativeKeyboardCommandTelemetry.Redo); - } else if (!event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey) { - event.stopPropagation(); - this.props.undo(); - this.props.sendCommand(NativeKeyboardCommandTelemetry.Undo); - } - } - break; - - case 'F10': - if (this.props.debugging) { - // Only allow step if debugging in break mode - const debuggingCell = this.props.cellVMs.find((cvm) => cvm.runningByLine === DebugState.Break); - if (debuggingCell) { - this.props.step(debuggingCell.cell.id); - } - event.stopPropagation(); - } else { - // Otherwise if not debugging, run by line the current focused cell - const focusedCell = getSelectedAndFocusedInfo(this.props).focusedCellId; - if (focusedCell) { - this.props.runByLine(focusedCell); - } - } - break; - case 'F5': - if (this.props.debugging) { - // Only allow continue if debugging in break mode - const debuggingCell = this.props.cellVMs.find((cvm) => cvm.runningByLine === DebugState.Break); - if (debuggingCell) { - this.props.continue(debuggingCell.cell.id); - } - event.stopPropagation(); - } - break; - default: - break; - } - }; - - // private copyToClipboard = (cellId: string) => { - // const cell = this.props.findCell(cellId); - // if (cell) { - // // Need to do this in this process so it copies to the user's clipboard and not - // // the remote clipboard where the extension is running - // const textArea = document.createElement('textarea'); - // textArea.value = concatMultilineString(cell.cell.data.source); - // document.body.appendChild(textArea); - // textArea.select(); - // document.execCommand('Copy'); - // textArea.remove(); - // } - // } - - // private pasteFromClipboard = (cellId: string) => { - // const editedCells = this.props.cellVMs; - // const index = editedCells.findIndex(x => x.cell.id === cellId) + 1; - - // if (index > -1) { - // const textArea = document.createElement('textarea'); - // document.body.appendChild(textArea); - // textArea.select(); - // document.execCommand('Paste'); - // editedCells[index].cell.data.source = textArea.value.split(/\r?\n/); - // textArea.remove(); - // } - - // this.setState({ - // cellVMs: editedCells - // }); - // } - - private renderCell = (cellVM: ICellViewModel, index: number): JSX.Element | null => { - // Don't render until we have settings - if (!this.props.settings || !this.props.editorOptions) { - return null; - } - const addNewCell = () => { - setTimeout(() => this.props.insertBelow(cellVM.cell.id), 1); - this.props.sendCommand(NativeMouseCommandTelemetry.AddToEnd); - }; - const firstLine = index === 0; - const lastLine = - index === this.props.cellVMs.length - 1 ? ( - - ) : null; - - const otherCellRunningByLine = this.props.cellVMs.find( - (cvm) => activeDebugState(cvm.runningByLine) && cvm.cell.id !== cellVM.cell.id - ); - const maxOutputSize = this.props.settings.maxOutputSize; - const outputSizeLimit = 10000; - const maxTextSize = - maxOutputSize && maxOutputSize < outputSizeLimit && maxOutputSize > 0 - ? maxOutputSize - : this.props.settings.enableScrollingForCellOutputs - ? 400 - : undefined; - - return ( -
- - 0} - editorOptions={this.props.editorOptions} - gatherIsInstalled={this.props.settings.gatherIsInstalled} - themeMatplotlibPlots={this.props.settings.themeMatplotlibPlots} - // Focus pending does not apply to native editor. - focusPending={0} - busy={this.props.busy} - useCustomEditorApi={this.props.settings?.extraSettings.useCustomEditorApi} - runningByLine={cellVM.runningByLine} - supportsRunByLine={ - this.props.settings?.variableOptions?.enableDuringDebugger - ? otherCellRunningByLine === undefined - : false - } - language={this.props.kernel.language} - isNotebookTrusted={this.props.isNotebookTrusted} - /> - - {lastLine} -
- ); - }; - - private scrollDiv = (_div: HTMLDivElement) => { - // Doing nothing for now. This should be implemented once redux refactor is done. - }; - - private linkClick = (ev: MouseEvent) => { - handleLinkClick(ev, this.props.linkClick); - }; -} - -// Main export, return a redux connected editor -export function getConnectedNativeEditor() { - return connect(mapStateToProps, actionCreators)(NativeEditor); -} diff --git a/src/datascience-ui/native-editor/redux/actions.ts b/src/datascience-ui/native-editor/redux/actions.ts deleted file mode 100644 index b1c83818a5d4..000000000000 --- a/src/datascience-ui/native-editor/redux/actions.ts +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as uuid from 'uuid/v4'; -import { NativeKeyboardCommandTelemetry, NativeMouseCommandTelemetry } from '../../../client/datascience/constants'; -import { - IInteractiveWindowMapping, - InteractiveWindowMessages -} from '../../../client/datascience/interactive-common/interactiveWindowTypes'; -import { IJupyterVariable, IJupyterVariablesRequest } from '../../../client/datascience/types'; -import { CursorPos } from '../../interactive-common/mainState'; -import { - CommonAction, - CommonActionType, - CommonActionTypeMapping, - ICellAction, - ICellAndCursorAction, - ICodeAction, - ICodeCreatedAction, - IEditCellAction, - ILinkClickAction, - IOpenSettingsAction, - ISendCommandAction, - IShowDataViewerAction, - IVariableExplorerHeight -} from '../../interactive-common/redux/reducers/types'; -import { IMonacoModelContentChangeEvent } from '../../react-common/monacoHelpers'; - -// This function isn't made common and not exported, to ensure it isn't used elsewhere. -function createIncomingActionWithPayload< - M extends IInteractiveWindowMapping & CommonActionTypeMapping, - K extends keyof M ->(type: K, data: M[K]): CommonAction { - // tslint:disable-next-line: no-any - return { type, payload: { data, messageDirection: 'incoming' } as any } as any; -} -// This function isn't made common and not exported, to ensure it isn't used elsewhere. -function createIncomingAction(type: CommonActionType | InteractiveWindowMessages): CommonAction { - return { type, payload: { messageDirection: 'incoming', data: undefined } }; -} - -// See https://react-redux.js.org/using-react-redux/connect-mapdispatch#defining-mapdispatchtoprops-as-an-object -export const actionCreators = { - addCell: () => createIncomingActionWithPayload(CommonActionType.ADD_AND_FOCUS_NEW_CELL, { newCellId: uuid() }), - insertAboveFirst: () => - createIncomingActionWithPayload(CommonActionType.INSERT_ABOVE_FIRST_AND_FOCUS_NEW_CELL, { newCellId: uuid() }), - insertAbove: (cellId: string | undefined) => - createIncomingActionWithPayload(CommonActionType.INSERT_ABOVE_AND_FOCUS_NEW_CELL, { - cellId, - newCellId: uuid() - }), - insertBelow: (cellId: string | undefined) => - createIncomingActionWithPayload(CommonActionType.INSERT_BELOW_AND_FOCUS_NEW_CELL, { - cellId, - newCellId: uuid() - }), - executeCell: (cellId: string, moveOp: 'add' | 'select' | 'none') => - createIncomingActionWithPayload(CommonActionType.EXECUTE_CELL_AND_ADVANCE, { cellId, moveOp }), - focusCell: (cellId: string, cursorPos: CursorPos = CursorPos.Current): CommonAction => - createIncomingActionWithPayload(CommonActionType.FOCUS_CELL, { cellId, cursorPos }), - unfocusCell: (cellId: string, code: string) => - createIncomingActionWithPayload(CommonActionType.UNFOCUS_CELL, { cellId, code }), - selectCell: (cellId: string, cursorPos: CursorPos = CursorPos.Current): CommonAction => - createIncomingActionWithPayload(CommonActionType.SELECT_CELL, { cellId, cursorPos }), - executeAllCells: (): CommonAction => createIncomingAction(CommonActionType.EXECUTE_ALL_CELLS), - executeAbove: (cellId: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.EXECUTE_ABOVE, { cellId }), - executeCellAndBelow: (cellId: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.EXECUTE_CELL_AND_BELOW, { cellId }), - toggleVariableExplorer: (): CommonAction => createIncomingAction(CommonActionType.TOGGLE_VARIABLE_EXPLORER), - setVariableExplorerHeight: (containerHeight: number, gridHeight: number): CommonAction => - createIncomingActionWithPayload(CommonActionType.SET_VARIABLE_EXPLORER_HEIGHT, { containerHeight, gridHeight }), - restartKernel: (): CommonAction => createIncomingAction(CommonActionType.RESTART_KERNEL), - interruptKernel: (): CommonAction => createIncomingAction(CommonActionType.INTERRUPT_KERNEL), - clearAllOutputs: (): CommonAction => createIncomingAction(InteractiveWindowMessages.ClearAllOutputs), - export: (): CommonAction => createIncomingAction(CommonActionType.EXPORT), - exportAs: (): CommonAction => createIncomingAction(CommonActionType.EXPORT_NOTEBOOK_AS), - save: (): CommonAction => createIncomingAction(CommonActionType.SAVE), - showDataViewer: (variable: IJupyterVariable, columnSize: number): CommonAction => - createIncomingActionWithPayload(CommonActionType.SHOW_DATA_VIEWER, { variable, columnSize }), - sendCommand: ( - command: NativeKeyboardCommandTelemetry | NativeMouseCommandTelemetry - ): CommonAction => createIncomingActionWithPayload(CommonActionType.SEND_COMMAND, { command }), - moveCellUp: (cellId: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.MOVE_CELL_UP, { cellId }), - moveCellDown: (cellId: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.MOVE_CELL_DOWN, { cellId }), - changeCellType: (cellId: string) => createIncomingActionWithPayload(CommonActionType.CHANGE_CELL_TYPE, { cellId }), - toggleLineNumbers: (cellId: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.TOGGLE_LINE_NUMBERS, { cellId }), - toggleOutput: (cellId: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.TOGGLE_OUTPUT, { cellId }), - deleteCell: (cellId: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.DELETE_CELL, { cellId }), - undo: (): CommonAction => createIncomingAction(InteractiveWindowMessages.Undo), - redo: (): CommonAction => createIncomingAction(InteractiveWindowMessages.Redo), - arrowUp: (cellId: string, code: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.ARROW_UP, { cellId, code }), - arrowDown: (cellId: string, code: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.ARROW_DOWN, { cellId, code }), - editCell: (cellId: string, e: IMonacoModelContentChangeEvent): CommonAction => - createIncomingActionWithPayload(CommonActionType.EDIT_CELL, { - cellId, - version: e.versionId, - modelId: e.model.id, - forward: e.forward, - reverse: e.reverse, - id: cellId, - code: e.model.getValue() - }), - linkClick: (href: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.LINK_CLICK, { href }), - showPlot: (imageHtml: string) => createIncomingActionWithPayload(InteractiveWindowMessages.ShowPlot, imageHtml), - gatherCell: (cellId: string | undefined): CommonAction => - createIncomingActionWithPayload(CommonActionType.GATHER_CELL, { cellId }), - gatherCellToScript: (cellId: string | undefined): CommonAction => - createIncomingActionWithPayload(CommonActionType.GATHER_CELL_TO_SCRIPT, { cellId }), - editorLoaded: (): CommonAction => createIncomingAction(CommonActionType.EDITOR_LOADED), - codeCreated: (cellId: string | undefined, modelId: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.CODE_CREATED, { cellId, modelId }), - loadedAllCells: (): CommonAction => createIncomingAction(CommonActionType.LOADED_ALL_CELLS), - editorUnmounted: (): CommonAction => createIncomingAction(CommonActionType.UNMOUNT), - selectKernel: (): CommonAction => createIncomingAction(InteractiveWindowMessages.SelectKernel), - selectServer: (): CommonAction => createIncomingAction(CommonActionType.SELECT_SERVER), - launchNotebookTrustPrompt: (): CommonAction => createIncomingAction(CommonActionType.LAUNCH_NOTEBOOK_TRUST_PROMPT), - openSettings: (setting?: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.OPEN_SETTINGS, { setting }), - getVariableData: ( - newExecutionCount: number, - refreshCount: number, - startIndex: number = 0, - pageSize: number = 100 - ): CommonAction => - createIncomingActionWithPayload(CommonActionType.GET_VARIABLE_DATA, { - executionCount: newExecutionCount, - sortColumn: 'name', - sortAscending: true, - startIndex, - pageSize, - refreshCount - }), - widgetFailed: (ex: Error): CommonAction => - createIncomingActionWithPayload(CommonActionType.IPYWIDGET_RENDER_FAILURE, ex), - runByLine: (cellId: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.RUN_BY_LINE, { cellId }), - continue: (cellId: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.CONTINUE, { cellId }), - step: (cellId: string): CommonAction => - createIncomingActionWithPayload(CommonActionType.STEP, { cellId }) -}; diff --git a/src/datascience-ui/native-editor/redux/mapping.ts b/src/datascience-ui/native-editor/redux/mapping.ts deleted file mode 100644 index 354cbe665814..000000000000 --- a/src/datascience-ui/native-editor/redux/mapping.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { - IInteractiveWindowMapping, - InteractiveWindowMessages -} from '../../../client/datascience/interactive-common/interactiveWindowTypes'; -import { BaseReduxActionPayload } from '../../../client/datascience/interactive-common/types'; -import { IMainState } from '../../interactive-common/mainState'; -import { CommonActionType, CommonActionTypeMapping } from '../../interactive-common/redux/reducers/types'; -import { ReducerArg, ReducerFunc } from '../../react-common/reduxUtils'; - -type NativeEditorReducerFunc = ReducerFunc< - IMainState, - CommonActionType | InteractiveWindowMessages, - BaseReduxActionPayload ->; - -export type NativeEditorReducerArg = ReducerArg< - IMainState, - CommonActionType | InteractiveWindowMessages, - BaseReduxActionPayload ->; - -type NativeEditorReducerFunctions = { - [P in keyof T]: T[P] extends never | undefined ? NativeEditorReducerFunc : NativeEditorReducerFunc; -}; - -export type INativeEditorActionMapping = NativeEditorReducerFunctions & - NativeEditorReducerFunctions; diff --git a/src/datascience-ui/native-editor/redux/reducers/creation.ts b/src/datascience-ui/native-editor/redux/reducers/creation.ts deleted file mode 100644 index 4d032e3e5cda..000000000000 --- a/src/datascience-ui/native-editor/redux/reducers/creation.ts +++ /dev/null @@ -1,494 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import { noop } from '../../../../client/common/utils/misc'; -import { - IEditorContentChange, - IFinishCell, - ILoadAllCells, - NotebookModelChange -} from '../../../../client/datascience/interactive-common/interactiveWindowTypes'; -import { ICell, IDataScienceExtraSettings } from '../../../../client/datascience/types'; -import { splitMultilineString } from '../../../common'; -import { - createCellVM, - createEmptyCell, - CursorPos, - DebugState, - extractInputText, - getSelectedAndFocusedInfo, - ICellViewModel, - IMainState -} from '../../../interactive-common/mainState'; -import { queueIncomingActionWithPayload } from '../../../interactive-common/redux/helpers'; -import { Helpers } from '../../../interactive-common/redux/reducers/helpers'; -import { Transfer } from '../../../interactive-common/redux/reducers/transfer'; -import { CommonActionType, IAddCellAction, ICellAction } from '../../../interactive-common/redux/reducers/types'; -import { NativeEditorReducerArg } from '../mapping'; -import { Effects } from './effects'; -import { Execution } from './execution'; -import { Movement } from './movement'; - -export namespace Creation { - function prepareCellVM(cell: ICell, hasBeenRun: boolean, settings?: IDataScienceExtraSettings): ICellViewModel { - const cellVM: ICellViewModel = createCellVM(cell, settings, true, false); - - // Set initial cell visibility and collapse - cellVM.editable = true; - - // Always have the cell input text open - const newText = extractInputText(cellVM, settings); - - cellVM.inputBlockOpen = true; - cell.data.source = splitMultilineString(newText); - cellVM.hasBeenRun = hasBeenRun; - - return cellVM; - } - - export function addAndFocusCell(arg: NativeEditorReducerArg): IMainState { - queueIncomingActionWithPayload(arg, CommonActionType.ADD_NEW_CELL, { newCellId: arg.payload.data.newCellId }); - queueIncomingActionWithPayload(arg, CommonActionType.FOCUS_CELL, { - cellId: arg.payload.data.newCellId, - cursorPos: CursorPos.Current - }); - return arg.prevState; - } - - export function insertAboveAndFocusCell(arg: NativeEditorReducerArg): IMainState { - queueIncomingActionWithPayload(arg, CommonActionType.INSERT_ABOVE, { - cellId: arg.payload.data.cellId, - newCellId: arg.payload.data.newCellId - }); - queueIncomingActionWithPayload(arg, CommonActionType.SELECT_CELL, { - cellId: arg.payload.data.newCellId, - cursorPos: CursorPos.Current - }); - return arg.prevState; - } - - export function insertBelowAndFocusCell(arg: NativeEditorReducerArg): IMainState { - queueIncomingActionWithPayload(arg, CommonActionType.INSERT_BELOW, { - cellId: arg.payload.data.cellId, - newCellId: arg.payload.data.newCellId - }); - queueIncomingActionWithPayload(arg, CommonActionType.SELECT_CELL, { - cellId: arg.payload.data.newCellId, - cursorPos: CursorPos.Current - }); - return arg.prevState; - } - - export function insertAboveFirstAndFocusCell(arg: NativeEditorReducerArg): IMainState { - queueIncomingActionWithPayload(arg, CommonActionType.INSERT_ABOVE_FIRST, { - newCellId: arg.payload.data.newCellId - }); - queueIncomingActionWithPayload(arg, CommonActionType.FOCUS_CELL, { - cellId: arg.payload.data.newCellId, - cursorPos: CursorPos.Current - }); - return arg.prevState; - } - - function insertAbove(arg: NativeEditorReducerArg): IMainState { - const newList = [...arg.prevState.cellVMs]; - const newVM = arg.payload.data.vm; - - // Find the position where we want to insert - let position = arg.prevState.cellVMs.findIndex((c) => c.cell.id === arg.payload.data.cellId); - if (position >= 0) { - newList.splice(position, 0, newVM); - } else { - newList.splice(0, 0, newVM); - position = 0; - } - - const result = { - ...arg.prevState, - undoStack: Helpers.pushStack(arg.prevState.undoStack, arg.prevState.cellVMs), - cellVMs: newList - }; - - // Send a messsage that we inserted a cell - Transfer.postModelInsert(arg, position, newVM.cell, arg.payload.data.cellId); - - return result; - } - - export function insertExistingAbove(arg: NativeEditorReducerArg): IMainState { - const newVM = prepareCellVM(arg.payload.data.cell, false, arg.prevState.settings); - return insertAbove({ - ...arg, - payload: { - ...arg.payload, - data: { - cellId: arg.payload.data.cellId, - vm: newVM - } - } - }); - } - - export function insertNewAbove(arg: NativeEditorReducerArg): IMainState { - const newVM = prepareCellVM(createEmptyCell(arg.payload.data.newCellId, null), false, arg.prevState.settings); - return insertAbove({ - ...arg, - payload: { - ...arg.payload, - data: { - cellId: arg.payload.data.cellId, - vm: newVM - } - } - }); - } - - export function insertBelow(arg: NativeEditorReducerArg): IMainState { - const newVM = prepareCellVM(createEmptyCell(arg.payload.data.newCellId, null), false, arg.prevState.settings); - const newList = [...arg.prevState.cellVMs]; - - // Find the position where we want to insert - let position = arg.prevState.cellVMs.findIndex((c) => c.cell.id === arg.payload.data.cellId); - if (position >= 0) { - position += 1; - newList.splice(position, 0, newVM); - } else { - newList.push(newVM); - position = newList.length; - } - - const result = { - ...arg.prevState, - undoStack: Helpers.pushStack(arg.prevState.undoStack, arg.prevState.cellVMs), - cellVMs: newList - }; - - // Send a messsage that we inserted a cell - Transfer.postModelInsert(arg, position, newVM.cell, arg.payload.data.cellId); - - return result; - } - - export function insertAboveFirst(arg: NativeEditorReducerArg): IMainState { - // Get the first cell id - const firstCellId = arg.prevState.cellVMs.length > 0 ? arg.prevState.cellVMs[0].cell.id : undefined; - - // Do what an insertAbove does - return insertNewAbove({ - ...arg, - payload: { ...arg.payload, data: { cellId: firstCellId, newCellId: arg.payload.data.newCellId } } - }); - } - - export function addNewCell(arg: NativeEditorReducerArg): IMainState { - // Do the same thing that an insertBelow does using the currently selected cell. - return insertBelow({ - ...arg, - payload: { - ...arg.payload, - data: { - cellId: getSelectedAndFocusedInfo(arg.prevState).selectedCellId, - newCellId: arg.payload.data.newCellId - } - } - }); - } - - export function startCell(arg: NativeEditorReducerArg): IMainState { - return Helpers.updateOrAdd(arg, (c: ICell, s: IMainState) => prepareCellVM(c, true, s.settings)); - } - - export function updateCell(arg: NativeEditorReducerArg): IMainState { - return Helpers.updateOrAdd(arg, (c: ICell, s: IMainState) => prepareCellVM(c, true, s.settings)); - } - - export function finishCell(arg: NativeEditorReducerArg): IMainState { - return Helpers.updateOrAdd( - { ...arg, payload: { ...arg.payload, data: arg.payload.data.cell } }, - (c: ICell, s: IMainState) => prepareCellVM(c, true, s.settings) - ); - } - - export function deleteAllCells(arg: NativeEditorReducerArg): IMainState { - // Just leave one single blank empty cell - const newVM: ICellViewModel = { - cell: createEmptyCell(arg.payload.data.newCellId, null), - editable: true, - inputBlockOpen: true, - inputBlockShow: true, - inputBlockText: '', - inputBlockCollapseNeeded: false, - selected: false, - focused: false, - cursorPos: CursorPos.Current, - hasBeenRun: false, - scrollCount: 0, - runningByLine: DebugState.Design, - gathering: false - }; - - Transfer.postModelRemoveAll(arg, newVM.cell.id); - - return { - ...arg.prevState, - cellVMs: [newVM], - undoStack: Helpers.pushStack(arg.prevState.undoStack, arg.prevState.cellVMs) - }; - } - - export function applyCellEdit( - arg: NativeEditorReducerArg<{ id: string; changes: IEditorContentChange[] }> - ): IMainState { - const index = arg.prevState.cellVMs.findIndex((c) => c.cell.id === arg.payload.data.id); - if (index >= 0) { - const newVM = { ...arg.prevState.cellVMs[index] }; - arg.payload.data.changes.forEach((c) => { - const source = newVM.inputBlockText; - const before = source.slice(0, c.rangeOffset); - // tslint:disable-next-line: restrict-plus-operands - const after = source.slice(c.rangeOffset + c.rangeLength); - newVM.inputBlockText = `${before}${c.text}${after}`; - }); - newVM.codeVersion = newVM.codeVersion ? newVM.codeVersion + 1 : 1; - newVM.cell.data.source = splitMultilineString(newVM.inputBlockText); - newVM.cursorPos = arg.payload.data.changes[0].position; - const newVMs = [...arg.prevState.cellVMs]; - newVMs[index] = Helpers.asCellViewModel(newVM); - // When editing, make sure we focus the edited cell (otherwise undo looks weird because it undoes a non focused cell) - return Effects.focusCell({ - ...arg, - prevState: { ...arg.prevState, cellVMs: newVMs }, - payload: { ...arg.payload, data: { cursorPos: CursorPos.Current, cellId: arg.payload.data.id } } - }); - } - return arg.prevState; - } - - export function deleteCell(arg: NativeEditorReducerArg): IMainState { - const cells = arg.prevState.cellVMs; - if (cells.length === 1 && cells[0].cell.id === arg.payload.data.cellId) { - // Special case, if this is the last cell, don't delete it, just clear it's output and input - const newVM: ICellViewModel = { - cell: createEmptyCell(arg.payload.data.cellId, null), - editable: true, - inputBlockOpen: true, - inputBlockShow: true, - inputBlockText: '', - inputBlockCollapseNeeded: false, - selected: cells[0].selected, - focused: cells[0].focused, - cursorPos: CursorPos.Current, - hasBeenRun: false, - scrollCount: 0, - runningByLine: DebugState.Design, - gathering: false - }; - - // Send messages to other side to indicate the new add - Transfer.postModelRemove(arg, 0, cells[0].cell); - Transfer.postModelInsert(arg, 0, newVM.cell); - - return { - ...arg.prevState, - undoStack: Helpers.pushStack(arg.prevState.undoStack, arg.prevState.cellVMs), - cellVMs: [newVM] - }; - } else if (arg.payload.data.cellId) { - // Otherwise just a straight delete - const index = arg.prevState.cellVMs.findIndex((c) => c.cell.id === arg.payload.data.cellId); - if (index >= 0) { - Transfer.postModelRemove(arg, 0, cells[index].cell); - - // Recompute select/focus if this item has either - const previousSelection = getSelectedAndFocusedInfo(arg.prevState); - const newVMs = [...arg.prevState.cellVMs.filter((c) => c.cell.id !== arg.payload.data.cellId)]; - const nextOrPrev = index === arg.prevState.cellVMs.length - 1 ? index - 1 : index; - if ( - previousSelection.selectedCellId === arg.payload.data.cellId || - previousSelection.focusedCellId === arg.payload.data.cellId - ) { - if (nextOrPrev >= 0) { - newVMs[nextOrPrev] = { - ...newVMs[nextOrPrev], - selected: true, - focused: previousSelection.focusedCellId === arg.payload.data.cellId - }; - } - } - - return { - ...arg.prevState, - cellVMs: newVMs, - undoStack: Helpers.pushStack(arg.prevState.undoStack, arg.prevState.cellVMs), - skipNextScroll: true - }; - } - } - - return arg.prevState; - } - - export function loadAllCells(arg: NativeEditorReducerArg): IMainState { - const vms = arg.payload.data.cells.map((c) => prepareCellVM(c, false, arg.prevState.settings)); - return { - ...arg.prevState, - busy: false, - loadTotal: arg.payload.data.cells.length, - undoStack: [], - cellVMs: vms, - loaded: true, - isNotebookTrusted: arg.payload.data.isNotebookTrusted! - }; - } - - export function unmount(arg: NativeEditorReducerArg): IMainState { - return { - ...arg.prevState, - cellVMs: [], - undoStack: [], - redoStack: [] - }; - } - - function handleUndoModel(arg: NativeEditorReducerArg): IMainState { - // Disable the queueAction in the arg so that calling other reducers doesn't cause - // messages to be posted back (as were handling a message from the extension here) - const disabledQueueArg = { ...arg, queueAction: noop }; - switch (arg.payload.data.kind) { - case 'clear': - return loadAllCells({ - ...disabledQueueArg, - payload: { ...arg.payload, data: { cells: arg.payload.data.oldCells } } - }); - case 'edit': - return applyCellEdit({ - ...disabledQueueArg, - payload: { ...arg.payload, data: { id: arg.payload.data.id, changes: arg.payload.data.reverse } } - }); - case 'insert': - return deleteCell({ - ...disabledQueueArg, - payload: { ...arg.payload, data: { cellId: arg.payload.data.cell.id } } - }); - case 'remove': - const cellBelow = - arg.prevState.cellVMs.length > arg.payload.data.index - ? arg.prevState.cellVMs[arg.payload.data.index].cell - : undefined; - return insertExistingAbove({ - ...disabledQueueArg, - payload: { - ...arg.payload, - data: { cell: arg.payload.data.cell, cellId: cellBelow ? cellBelow.id : undefined } - } - }); - case 'remove_all': - return loadAllCells({ - ...disabledQueueArg, - payload: { ...arg.payload, data: { cells: arg.payload.data.oldCells } } - }); - case 'swap': - return Movement.swapCells({ - ...disabledQueueArg, - payload: { - ...arg.payload, - data: { - firstCellId: arg.payload.data.secondCellId, - secondCellId: arg.payload.data.firstCellId - } - } - }); - case 'modify': - // Undo for modify should reapply the outputs. Go through each and apply the update - let result = arg.prevState; - arg.payload.data.oldCells.forEach((c) => { - result = updateCell({ - ...disabledQueueArg, - prevState: result, - payload: { ...arg.payload, data: c } - }); - }); - return result; - - default: - // File, version can be ignored. - break; - } - - return arg.prevState; - } - - function handleRedoModel(arg: NativeEditorReducerArg): IMainState { - // Disable the queueAction in the arg so that calling other reducers doesn't cause - // messages to be posted back (as were handling a message from the extension here) - const disabledQueueArg = { ...arg, queueAction: noop }; - switch (arg.payload.data.kind) { - case 'clear': - // tslint:disable-next-line: no-any - return Execution.clearAllOutputs(disabledQueueArg as any); - case 'edit': - return applyCellEdit({ - ...disabledQueueArg, - payload: { ...arg.payload, data: { id: arg.payload.data.id, changes: arg.payload.data.forward } } - }); - case 'insert': - return insertExistingAbove({ - ...disabledQueueArg, - payload: { - ...arg.payload, - data: { cell: arg.payload.data.cell, cellId: arg.payload.data.codeCellAboveId } - } - }); - case 'remove': - return deleteCell({ - ...disabledQueueArg, - payload: { ...arg.payload, data: { cellId: arg.payload.data.cell.id } } - }); - case 'remove_all': - return deleteAllCells({ - ...disabledQueueArg, - payload: { ...arg.payload, data: { newCellId: arg.payload.data.newCellId } } - }); - case 'swap': - return Movement.swapCells({ - ...disabledQueueArg, - payload: { - ...arg.payload, - data: { - firstCellId: arg.payload.data.secondCellId, - secondCellId: arg.payload.data.firstCellId - } - } - }); - case 'modify': - // Redo for modify should reapply the outputs. Go through each and apply the update - let result = arg.prevState; - arg.payload.data.newCells.forEach((c) => { - result = updateCell({ - ...disabledQueueArg, - prevState: result, - payload: { ...arg.payload, data: c } - }); - }); - return result; - default: - // Modify, file, version can all be ignored. - break; - } - - return arg.prevState; - } - - export function handleUpdate(arg: NativeEditorReducerArg): IMainState { - switch (arg.payload.data.source) { - case 'undo': - return handleUndoModel(arg); - case 'redo': - return handleRedoModel(arg); - default: - break; - } - return arg.prevState; - } -} diff --git a/src/datascience-ui/native-editor/redux/reducers/effects.ts b/src/datascience-ui/native-editor/redux/reducers/effects.ts deleted file mode 100644 index 982e4776d290..000000000000 --- a/src/datascience-ui/native-editor/redux/reducers/effects.ts +++ /dev/null @@ -1,247 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { CssMessages } from '../../../../client/datascience/messages'; -import { IDataScienceExtraSettings } from '../../../../client/datascience/types'; -import { getSelectedAndFocusedInfo, IMainState } from '../../../interactive-common/mainState'; -import { postActionToExtension } from '../../../interactive-common/redux/helpers'; -import { Helpers } from '../../../interactive-common/redux/reducers/helpers'; -import { ICellAction, ICellAndCursorAction } from '../../../interactive-common/redux/reducers/types'; -import { computeEditorOptions } from '../../../react-common/settingsReactSide'; -import { NativeEditorReducerArg } from '../mapping'; - -export namespace Effects { - export function focusCell(arg: NativeEditorReducerArg): IMainState { - // Do nothing if already the focused cell. - let selectionInfo = getSelectedAndFocusedInfo(arg.prevState); - if (selectionInfo.focusedCellId !== arg.payload.data.cellId) { - let prevState = arg.prevState; - - // Ensure we unfocus & unselect all cells. - while (selectionInfo.focusedCellId || selectionInfo.selectedCellId) { - selectionInfo = getSelectedAndFocusedInfo(prevState); - // First find the old focused cell and unfocus it - let removeFocusIndex = selectionInfo.focusedCellIndex; - if (typeof removeFocusIndex !== 'number') { - removeFocusIndex = selectionInfo.selectedCellIndex; - } - - if (typeof removeFocusIndex === 'number') { - prevState = unfocusCell({ - ...arg, - prevState, - payload: { - ...arg.payload, - data: { cellId: prevState.cellVMs[removeFocusIndex].cell.id } - } - }); - prevState = deselectCell({ - ...arg, - prevState, - payload: { ...arg.payload, data: { cellId: prevState.cellVMs[removeFocusIndex].cell.id } } - }); - } - } - - const newVMs = [...prevState.cellVMs]; - - // Add focus on new cell - const addFocusIndex = newVMs.findIndex((c) => c.cell.id === arg.payload.data.cellId); - if (addFocusIndex >= 0) { - newVMs[addFocusIndex] = { - ...newVMs[addFocusIndex], - focused: true, - selected: true, - cursorPos: arg.payload.data.cursorPos - }; - } - - return { - ...prevState, - cellVMs: newVMs - }; - } - - return arg.prevState; - } - - export function unfocusCell(arg: NativeEditorReducerArg): IMainState { - // Unfocus the cell - const index = arg.prevState.cellVMs.findIndex((c) => c.cell.id === arg.payload.data.cellId); - const selectionInfo = getSelectedAndFocusedInfo(arg.prevState); - if (index >= 0 && selectionInfo.focusedCellId === arg.payload.data.cellId) { - const newVMs = [...arg.prevState.cellVMs]; - const current = arg.prevState.cellVMs[index]; - const newCell = { - ...current, - focused: false - }; - - // tslint:disable-next-line: no-any - newVMs[index] = Helpers.asCellViewModel(newCell); // This is because IMessageCell doesn't fit in here - - return { - ...arg.prevState, - cellVMs: newVMs - }; - } else if (index >= 0) { - // Dont change focus state if not the focused cell. Just update the code. - const newVMs = [...arg.prevState.cellVMs]; - const current = arg.prevState.cellVMs[index]; - const newCell = { - ...current - }; - - // tslint:disable-next-line: no-any - newVMs[index] = newCell as any; // This is because IMessageCell doesn't fit in here - - return { - ...arg.prevState, - cellVMs: newVMs - }; - } - - return arg.prevState; - } - - export function deselectCell(arg: NativeEditorReducerArg): IMainState { - const index = arg.prevState.cellVMs.findIndex((c) => c.cell.id === arg.payload.data.cellId); - const selectionInfo = getSelectedAndFocusedInfo(arg.prevState); - if (index >= 0 && selectionInfo.selectedCellId === arg.payload.data.cellId) { - const newVMs = [...arg.prevState.cellVMs]; - const target = arg.prevState.cellVMs[index]; - const newCell = { - ...target, - selected: false - }; - - // tslint:disable-next-line: no-any - newVMs[index] = newCell as any; // This is because IMessageCell doesn't fit in here - - return { - ...arg.prevState, - cellVMs: newVMs - }; - } - - return arg.prevState; - } - - /** - * Select a cell. - * - * @param {boolean} [shouldFocusCell] If provided, then will control the focus behavior of the cell. (defaults to focus state of previously selected cell). - */ - export function selectCell( - arg: NativeEditorReducerArg, - shouldFocusCell?: boolean - ): IMainState { - // Skip doing anything if already selected. - const selectionInfo = getSelectedAndFocusedInfo(arg.prevState); - if (arg.payload.data.cellId !== selectionInfo.selectedCellId) { - let prevState = arg.prevState; - const addIndex = prevState.cellVMs.findIndex((c) => c.cell.id === arg.payload.data.cellId); - const someOtherCellWasFocusedAndSelected = - selectionInfo.focusedCellId === selectionInfo.selectedCellId && !!selectionInfo.focusedCellId; - // First find the old focused cell and unfocus it - let removeFocusIndex = arg.prevState.cellVMs.findIndex((c) => c.cell.id === selectionInfo.focusedCellId); - if (removeFocusIndex < 0) { - removeFocusIndex = arg.prevState.cellVMs.findIndex((c) => c.cell.id === selectionInfo.selectedCellId); - } - - if (removeFocusIndex >= 0) { - prevState = unfocusCell({ - ...arg, - prevState, - payload: { - ...arg.payload, - data: { cellId: prevState.cellVMs[removeFocusIndex].cell.id } - } - }); - prevState = deselectCell({ - ...arg, - prevState, - payload: { ...arg.payload, data: { cellId: prevState.cellVMs[removeFocusIndex].cell.id } } - }); - } - - const newVMs = [...prevState.cellVMs]; - if (addIndex >= 0 && arg.payload.data.cellId !== selectionInfo.selectedCellId) { - newVMs[addIndex] = { - ...newVMs[addIndex], - focused: - typeof shouldFocusCell === 'boolean' ? shouldFocusCell : someOtherCellWasFocusedAndSelected, - selected: true, - cursorPos: arg.payload.data.cursorPos - }; - } - - return { - ...prevState, - cellVMs: newVMs - }; - } - return arg.prevState; - } - - export function toggleLineNumbers(arg: NativeEditorReducerArg): IMainState { - const index = arg.prevState.cellVMs.findIndex((c) => c.cell.id === arg.payload.data.cellId); - if (index >= 0) { - const newVMs = [...arg.prevState.cellVMs]; - newVMs[index] = { ...newVMs[index], showLineNumbers: !newVMs[index].showLineNumbers }; - return { - ...arg.prevState, - cellVMs: newVMs - }; - } - return arg.prevState; - } - - export function toggleOutput(arg: NativeEditorReducerArg): IMainState { - const index = arg.prevState.cellVMs.findIndex((c) => c.cell.id === arg.payload.data.cellId); - if (index >= 0) { - const newVMs = [...arg.prevState.cellVMs]; - newVMs[index] = { ...newVMs[index], hideOutput: !newVMs[index].hideOutput }; - return { - ...arg.prevState, - cellVMs: newVMs - }; - } - return arg.prevState; - } - - export function updateSettings(arg: NativeEditorReducerArg): IMainState { - // String arg should be the IDataScienceExtraSettings - const newSettingsJSON = JSON.parse(arg.payload.data); - const newSettings = newSettingsJSON; - const newEditorOptions = computeEditorOptions(newSettings); - const newFontFamily = newSettings.extraSettings - ? newSettings.extraSettings.editor.fontFamily - : arg.prevState.font.family; - const newFontSize = newSettings.extraSettings - ? newSettings.extraSettings.editor.fontSize - : arg.prevState.font.size; - - // Ask for new theme data if necessary - if ( - newSettings && - newSettings.extraSettings && - newSettings.extraSettings.theme !== arg.prevState.vscodeThemeName - ) { - const knownDark = Helpers.computeKnownDark(newSettings); - // User changed the current theme. Rerender - postActionToExtension(arg, CssMessages.GetCssRequest, { isDark: knownDark }); - postActionToExtension(arg, CssMessages.GetMonacoThemeRequest, { isDark: knownDark }); - } - - return { - ...arg.prevState, - settings: newSettings, - editorOptions: { ...newEditorOptions, lineDecorationsWidth: 5 }, - font: { - size: newFontSize, - family: newFontFamily - } - }; - } -} diff --git a/src/datascience-ui/native-editor/redux/reducers/execution.ts b/src/datascience-ui/native-editor/redux/reducers/execution.ts deleted file mode 100644 index 57ecc2477781..000000000000 --- a/src/datascience-ui/native-editor/redux/reducers/execution.ts +++ /dev/null @@ -1,357 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -// tslint:disable-next-line: no-require-imports no-var-requires -const cloneDeep = require('lodash/cloneDeep'); -import * as uuid from 'uuid/v4'; -import { DebugProtocol } from 'vscode-debugprotocol'; -import { InteractiveWindowMessages } from '../../../../client/datascience/interactive-common/interactiveWindowTypes'; -import { CellState, ICell } from '../../../../client/datascience/types'; -import { concatMultilineString } from '../../../common'; -import { createCellFrom } from '../../../common/cellFactory'; -import { - CursorPos, - DebugState, - getSelectedAndFocusedInfo, - ICellViewModel, - IMainState -} from '../../../interactive-common/mainState'; -import { postActionToExtension, queueIncomingActionWithPayload } from '../../../interactive-common/redux/helpers'; -import { Helpers } from '../../../interactive-common/redux/reducers/helpers'; -import { Transfer } from '../../../interactive-common/redux/reducers/transfer'; -import { - CommonActionType, - ICellAction, - IChangeCellTypeAction, - IExecuteAction -} from '../../../interactive-common/redux/reducers/types'; -import { NativeEditorReducerArg } from '../mapping'; -import { Effects } from './effects'; - -export namespace Execution { - function executeRange( - prevState: IMainState, - cellIds: string[], - // tslint:disable-next-line: no-any - originalArg: NativeEditorReducerArg - ): IMainState { - const newVMs = [...prevState.cellVMs]; - const cellIdsToExecute: string[] = []; - cellIds.forEach((cellId) => { - const index = prevState.cellVMs.findIndex((cell) => cell.cell.id === cellId); - if (index === -1) { - return; - } - const orig = prevState.cellVMs[index]; - // noop if the submitted code is just a cell marker - if (orig.cell.data.cell_type === 'code' && concatMultilineString(orig.cell.data.source)) { - // When cloning cells, preserve the metadata (hence deep clone). - const clonedCell = cloneDeep(orig.cell.data); - // Update our input cell to be in progress again and clear outputs - clonedCell.outputs = []; - newVMs[index] = Helpers.asCellViewModel({ - ...orig, - cell: { ...orig.cell, state: CellState.executing, data: clonedCell } - }); - cellIdsToExecute.push(orig.cell.id); - } - }); - - // If any cells to execute, execute them all - if (cellIdsToExecute.length > 0) { - // Send a message if a code cell - postActionToExtension(originalArg, InteractiveWindowMessages.ReExecuteCells, { - cellIds: cellIdsToExecute - }); - } - - return { - ...prevState, - cellVMs: newVMs - }; - } - - export function executeAbove(arg: NativeEditorReducerArg): IMainState { - const index = arg.prevState.cellVMs.findIndex((c) => c.cell.id === arg.payload.data.cellId); - if (index > 0) { - // Get all cellIds until `index`. - const cellIds = arg.prevState.cellVMs.slice(0, index).map((cellVm) => cellVm.cell.id); - return executeRange(arg.prevState, cellIds, arg); - } - return arg.prevState; - } - - export function executeCellAndAdvance(arg: NativeEditorReducerArg): IMainState { - queueIncomingActionWithPayload(arg, CommonActionType.EXECUTE_CELL, { - cellId: arg.payload.data.cellId, - moveOp: arg.payload.data.moveOp - }); - if (arg.payload.data.moveOp === 'add') { - const newCellId = uuid(); - queueIncomingActionWithPayload(arg, CommonActionType.INSERT_BELOW, { - cellId: arg.payload.data.cellId, - newCellId - }); - queueIncomingActionWithPayload(arg, CommonActionType.FOCUS_CELL, { - cellId: newCellId, - cursorPos: CursorPos.Current - }); - } - return arg.prevState; - } - - export function executeCell(arg: NativeEditorReducerArg): IMainState { - const index = arg.prevState.cellVMs.findIndex((c) => c.cell.id === arg.payload.data.cellId); - if (index >= 0 && arg.payload.data.cellId) { - // Start executing this cell. - const executeResult = executeRange(arg.prevState, [arg.payload.data.cellId], arg); - - // Modify the execute result if moving - if (arg.payload.data.moveOp === 'select') { - // Select the cell below this one, but don't focus it - if (index < arg.prevState.cellVMs.length - 1) { - return Effects.selectCell( - { - ...arg, - prevState: { - ...executeResult - }, - payload: { - ...arg.payload, - data: { - ...arg.payload.data, - cellId: arg.prevState.cellVMs[index + 1].cell.id, - cursorPos: CursorPos.Current - } - } - }, - // Select the next cell, but do not set focus to it. - false - ); - } - return executeResult; - } else { - return executeResult; - } - } - return arg.prevState; - } - - export function executeCellAndBelow(arg: NativeEditorReducerArg): IMainState { - const index = arg.prevState.cellVMs.findIndex((c) => c.cell.id === arg.payload.data.cellId); - if (index >= 0) { - // Get all cellIds starting from `index`. - const cellIds = arg.prevState.cellVMs.slice(index).map((cellVm) => cellVm.cell.id); - return executeRange(arg.prevState, cellIds, arg); - } - return arg.prevState; - } - - export function executeAllCells(arg: NativeEditorReducerArg): IMainState { - if (arg.prevState.cellVMs.length > 0) { - const cellIds = arg.prevState.cellVMs.map((cellVm) => cellVm.cell.id); - return executeRange(arg.prevState, cellIds, arg); - } - return arg.prevState; - } - - export function executeSelectedCell(arg: NativeEditorReducerArg): IMainState { - // This is the same thing as executing the selected cell - const selectionInfo = getSelectedAndFocusedInfo(arg.prevState); - const index = arg.prevState.cellVMs.findIndex((c) => c.cell.id === selectionInfo.selectedCellId); - if (selectionInfo.selectedCellId && index >= 0) { - return executeCell({ - ...arg, - payload: { - ...arg.payload, - data: { - cellId: selectionInfo.selectedCellId, - moveOp: 'none' - } - } - }); - } - - return arg.prevState; - } - - export function clearAllOutputs(arg: NativeEditorReducerArg): IMainState { - const newList = arg.prevState.cellVMs.map((cellVM) => { - return Helpers.asCellViewModel({ - ...cellVM, - cell: { ...cellVM.cell, data: { ...cellVM.cell.data, outputs: [], execution_count: null } } - }); - }); - - Transfer.postModelClearOutputs(arg); - - return { - ...arg.prevState, - cellVMs: newList - }; - } - - export function changeCellType(arg: NativeEditorReducerArg): IMainState { - const index = arg.prevState.cellVMs.findIndex((c) => c.cell.id === arg.payload.data.cellId); - if (index >= 0) { - const cellVMs = [...arg.prevState.cellVMs]; - const current = arg.prevState.cellVMs[index]; - const newType = current.cell.data.cell_type === 'code' ? 'markdown' : 'code'; - const newNotebookCell = createCellFrom(current.cell.data, newType); - const newCell: ICellViewModel = { - ...current, - cell: { - ...current.cell, - data: newNotebookCell - } - }; - cellVMs[index] = newCell; - Transfer.changeCellType(arg, cellVMs[index].cell); - - return { - ...arg.prevState, - cellVMs - }; - } - - return arg.prevState; - } - - export function undo(arg: NativeEditorReducerArg): IMainState { - if (arg.prevState.undoStack.length > 0) { - // Pop one off of our undo stack and update our redo - const cells = arg.prevState.undoStack[arg.prevState.undoStack.length - 1]; - const undoStack = arg.prevState.undoStack.slice(0, arg.prevState.undoStack.length - 1); - const redoStack = Helpers.pushStack(arg.prevState.redoStack, arg.prevState.cellVMs); - postActionToExtension(arg, InteractiveWindowMessages.Undo); - return { - ...arg.prevState, - cellVMs: cells, - undoStack: undoStack, - redoStack: redoStack, - skipNextScroll: true - }; - } - - return arg.prevState; - } - - export function redo(arg: NativeEditorReducerArg): IMainState { - if (arg.prevState.redoStack.length > 0) { - // Pop one off of our redo stack and update our undo - const cells = arg.prevState.redoStack[arg.prevState.redoStack.length - 1]; - const redoStack = arg.prevState.redoStack.slice(0, arg.prevState.redoStack.length - 1); - const undoStack = Helpers.pushStack(arg.prevState.undoStack, arg.prevState.cellVMs); - postActionToExtension(arg, InteractiveWindowMessages.Redo); - return { - ...arg.prevState, - cellVMs: cells, - undoStack: undoStack, - redoStack: redoStack, - skipNextScroll: true - }; - } - - return arg.prevState; - } - - export function continueExec(arg: NativeEditorReducerArg): IMainState { - const index = arg.prevState.cellVMs.findIndex((cv) => cv.cell.id === arg.payload.data.cellId); - if (index >= 0) { - postActionToExtension(arg, InteractiveWindowMessages.Continue); - } - return arg.prevState; - } - - export function step(arg: NativeEditorReducerArg): IMainState { - const index = arg.prevState.cellVMs.findIndex((cv) => cv.cell.id === arg.payload.data.cellId); - if (index >= 0) { - postActionToExtension(arg, InteractiveWindowMessages.Step); - } - return arg.prevState; - } - - export function runByLine(arg: NativeEditorReducerArg): IMainState { - const index = arg.prevState.cellVMs.findIndex((cv) => cv.cell.id === arg.payload.data.cellId); - if (index >= 0) { - postActionToExtension(arg, InteractiveWindowMessages.RunByLine, { - cell: arg.prevState.cellVMs[index].cell, - expectedExecutionCount: arg.prevState.currentExecutionCount + 1 - }); - const newVM = { - ...arg.prevState.cellVMs[index], - runningByLine: DebugState.Run - }; - const newVMs = [...arg.prevState.cellVMs]; - newVMs[index] = newVM; - return { - ...arg.prevState, - cellVMs: newVMs - }; - } - return arg.prevState; - } - - export function handleBreakState( - arg: NativeEditorReducerArg<{ frames: DebugProtocol.StackFrame[]; cell: ICell }> - ): IMainState { - const index = arg.prevState.cellVMs.findIndex((cv) => cv.cell.id === arg.payload.data.cell.id); - if (index >= 0) { - const newVM = { - ...arg.prevState.cellVMs[index], - runningByLine: DebugState.Break, - currentStack: arg.payload.data.frames - }; - const newVMs = [...arg.prevState.cellVMs]; - newVMs[index] = newVM; - return { - ...arg.prevState, - cellVMs: newVMs - }; - } - return arg.prevState; - } - - export function handleContinue(arg: NativeEditorReducerArg): IMainState { - const index = arg.prevState.cellVMs.findIndex((cv) => cv.cell.id === arg.payload.data.id); - if (index >= 0) { - const newVM = { - ...arg.prevState.cellVMs[index], - runningByLine: DebugState.Run, - currentStack: undefined - }; - const newVMs = [...arg.prevState.cellVMs]; - newVMs[index] = newVM; - return { - ...arg.prevState, - cellVMs: newVMs - }; - } - return arg.prevState; - } - - export function startDebugging(arg: NativeEditorReducerArg): IMainState { - return { - ...arg.prevState, - debugging: true - }; - } - - export function stopDebugging(arg: NativeEditorReducerArg): IMainState { - // Clear out any cells that have frames - const index = arg.prevState.cellVMs.findIndex((cvm) => cvm.currentStack); - const newVMs = [...arg.prevState.cellVMs]; - if (index >= 0) { - const newVM = { - ...newVMs[index], - currentStack: undefined - }; - newVMs[index] = newVM; - } - return { - ...arg.prevState, - cellVMs: newVMs, - debugging: false - }; - } -} diff --git a/src/datascience-ui/native-editor/redux/reducers/index.ts b/src/datascience-ui/native-editor/redux/reducers/index.ts deleted file mode 100644 index 0fe8955906f7..000000000000 --- a/src/datascience-ui/native-editor/redux/reducers/index.ts +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { InteractiveWindowMessages } from '../../../../client/datascience/interactive-common/interactiveWindowTypes'; -import { CssMessages, SharedMessages } from '../../../../client/datascience/messages'; -import { CommonEffects } from '../../../interactive-common/redux/reducers/commonEffects'; -import { Kernel } from '../../../interactive-common/redux/reducers/kernel'; -import { Transfer } from '../../../interactive-common/redux/reducers/transfer'; -import { CommonActionType } from '../../../interactive-common/redux/reducers/types'; -import { INativeEditorActionMapping } from '../mapping'; -import { Creation } from './creation'; -import { Effects } from './effects'; -import { Execution } from './execution'; -import { Movement } from './movement'; - -// The list of reducers. 1 per message/action. -export const reducerMap: Partial = { - // State updates - [CommonActionType.INSERT_ABOVE_AND_FOCUS_NEW_CELL]: Creation.insertAboveAndFocusCell, - [CommonActionType.INSERT_ABOVE_FIRST_AND_FOCUS_NEW_CELL]: Creation.insertAboveFirstAndFocusCell, - [CommonActionType.INSERT_BELOW_AND_FOCUS_NEW_CELL]: Creation.insertBelowAndFocusCell, - [CommonActionType.INSERT_ABOVE]: Creation.insertNewAbove, - [CommonActionType.INSERT_ABOVE_FIRST]: Creation.insertAboveFirst, - [CommonActionType.INSERT_BELOW]: Creation.insertBelow, - [CommonActionType.FOCUS_CELL]: Effects.focusCell, - [CommonActionType.UNFOCUS_CELL]: Effects.unfocusCell, - [CommonActionType.ADD_AND_FOCUS_NEW_CELL]: Creation.addAndFocusCell, - [CommonActionType.ADD_NEW_CELL]: Creation.addNewCell, - [CommonActionType.EXECUTE_CELL_AND_ADVANCE]: Execution.executeCellAndAdvance, - [CommonActionType.EXECUTE_CELL]: Execution.executeCell, - [CommonActionType.EXECUTE_ALL_CELLS]: Execution.executeAllCells, - [CommonActionType.EXECUTE_ABOVE]: Execution.executeAbove, - [CommonActionType.EXECUTE_CELL_AND_BELOW]: Execution.executeCellAndBelow, - [CommonActionType.RESTART_KERNEL]: Kernel.restartKernel, - [CommonActionType.INTERRUPT_KERNEL]: Kernel.interruptKernel, - [InteractiveWindowMessages.ClearAllOutputs]: Execution.clearAllOutputs, - [CommonActionType.EXPORT]: Transfer.exportCells, - [CommonActionType.EXPORT_NOTEBOOK_AS]: Transfer.showExportAsMenu, - [CommonActionType.SAVE]: Transfer.save, - [CommonActionType.SHOW_DATA_VIEWER]: Transfer.showDataViewer, - [CommonActionType.SEND_COMMAND]: Transfer.sendCommand, - [CommonActionType.SELECT_CELL]: Effects.selectCell, - [InteractiveWindowMessages.SelectKernel]: Kernel.selectKernel, - [CommonActionType.SELECT_SERVER]: Kernel.selectJupyterURI, - [CommonActionType.MOVE_CELL_UP]: Movement.moveCellUp, - [CommonActionType.MOVE_CELL_DOWN]: Movement.moveCellDown, - [CommonActionType.DELETE_CELL]: Creation.deleteCell, - [CommonActionType.TOGGLE_LINE_NUMBERS]: Effects.toggleLineNumbers, - [CommonActionType.TOGGLE_OUTPUT]: Effects.toggleOutput, - [CommonActionType.CHANGE_CELL_TYPE]: Execution.changeCellType, - [InteractiveWindowMessages.Undo]: Execution.undo, - [InteractiveWindowMessages.Redo]: Execution.redo, - [CommonActionType.ARROW_UP]: Movement.arrowUp, - [CommonActionType.ARROW_DOWN]: Movement.arrowDown, - [CommonActionType.EDIT_CELL]: Transfer.editCell, - [InteractiveWindowMessages.ShowPlot]: Transfer.showPlot, - [CommonActionType.LINK_CLICK]: Transfer.linkClick, - [CommonActionType.GATHER_CELL]: Transfer.gather, - [CommonActionType.GATHER_CELL_TO_SCRIPT]: Transfer.gatherToScript, - [InteractiveWindowMessages.Gathering]: Transfer.gathering, - [CommonActionType.EDITOR_LOADED]: Transfer.started, - [CommonActionType.LOADED_ALL_CELLS]: Transfer.loadedAllCells, - [CommonActionType.LAUNCH_NOTEBOOK_TRUST_PROMPT]: Transfer.launchNotebookTrustPrompt, - [CommonActionType.UNMOUNT]: Creation.unmount, - [CommonActionType.LOAD_IPYWIDGET_CLASS_SUCCESS]: CommonEffects.handleLoadIPyWidgetClassSuccess, - [CommonActionType.LOAD_IPYWIDGET_CLASS_FAILURE]: CommonEffects.handleLoadIPyWidgetClassFailure, - [CommonActionType.IPYWIDGET_WIDGET_VERSION_NOT_SUPPORTED]: CommonEffects.notifyAboutUnsupportedWidgetVersions, - [CommonActionType.CONTINUE]: Execution.continueExec, - [CommonActionType.STEP]: Execution.step, - [CommonActionType.RUN_BY_LINE]: Execution.runByLine, - [CommonActionType.OPEN_SETTINGS]: CommonEffects.openSettings, - - // Messages from the webview (some are ignored) - [InteractiveWindowMessages.StartCell]: Creation.startCell, - [InteractiveWindowMessages.FinishCell]: Creation.finishCell, - [InteractiveWindowMessages.UpdateCellWithExecutionResults]: Creation.updateCell, - [InteractiveWindowMessages.NotebookDirty]: CommonEffects.notebookDirty, - [InteractiveWindowMessages.NotebookClean]: CommonEffects.notebookClean, - [InteractiveWindowMessages.LoadAllCells]: Creation.loadAllCells, - [InteractiveWindowMessages.TrustNotebookComplete]: CommonEffects.trustNotebook, - [InteractiveWindowMessages.NotebookRunAllCells]: Execution.executeAllCells, - [InteractiveWindowMessages.NotebookRunSelectedCell]: Execution.executeSelectedCell, - [InteractiveWindowMessages.NotebookAddCellBelow]: Creation.addAndFocusCell, - [InteractiveWindowMessages.DoSave]: Transfer.save, - [InteractiveWindowMessages.DeleteAllCells]: Creation.deleteAllCells, - [InteractiveWindowMessages.Undo]: Execution.undo, - [InteractiveWindowMessages.Redo]: Execution.redo, - [InteractiveWindowMessages.StartProgress]: CommonEffects.startProgress, - [InteractiveWindowMessages.StopProgress]: CommonEffects.stopProgress, - [SharedMessages.UpdateSettings]: Effects.updateSettings, - [InteractiveWindowMessages.Activate]: CommonEffects.activate, - [InteractiveWindowMessages.RestartKernel]: Kernel.handleRestarted, - [CssMessages.GetCssResponse]: CommonEffects.handleCss, - [InteractiveWindowMessages.MonacoReady]: CommonEffects.monacoReady, - [CssMessages.GetMonacoThemeResponse]: CommonEffects.monacoThemeChange, - [InteractiveWindowMessages.UpdateModel]: Creation.handleUpdate, - [InteractiveWindowMessages.UpdateKernel]: Kernel.updateStatus, - [SharedMessages.LocInit]: CommonEffects.handleLocInit, - [InteractiveWindowMessages.UpdateDisplayData]: CommonEffects.handleUpdateDisplayData, - [InteractiveWindowMessages.ShowBreak]: Execution.handleBreakState, - [InteractiveWindowMessages.ShowContinue]: Execution.handleContinue, - [InteractiveWindowMessages.StartDebugging]: Execution.startDebugging, - [InteractiveWindowMessages.StopDebugging]: Execution.stopDebugging -}; diff --git a/src/datascience-ui/native-editor/redux/reducers/movement.ts b/src/datascience-ui/native-editor/redux/reducers/movement.ts deleted file mode 100644 index bacac77d8398..000000000000 --- a/src/datascience-ui/native-editor/redux/reducers/movement.ts +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { CursorPos, IMainState } from '../../../interactive-common/mainState'; -import { queueIncomingActionWithPayload } from '../../../interactive-common/redux/helpers'; -import { Helpers } from '../../../interactive-common/redux/reducers/helpers'; -import { Transfer } from '../../../interactive-common/redux/reducers/transfer'; -import { CommonActionType, ICellAction, ICodeAction } from '../../../interactive-common/redux/reducers/types'; -import { NativeEditorReducerArg } from '../mapping'; - -export namespace Movement { - export function swapCells(arg: NativeEditorReducerArg<{ firstCellId: string; secondCellId: string }>) { - const newVMs = [...arg.prevState.cellVMs]; - const first = newVMs.findIndex((cvm) => cvm.cell.id === arg.payload.data.firstCellId); - const second = newVMs.findIndex((cvm) => cvm.cell.id === arg.payload.data.secondCellId); - if (first >= 0 && second >= 0 && first !== second) { - const temp = newVMs[first]; - newVMs[first] = newVMs[second]; - newVMs[second] = temp; - Transfer.postModelSwap(arg, arg.payload.data.firstCellId, arg.payload.data.secondCellId); - return { - ...arg.prevState, - cellVMs: newVMs, - undoStack: Helpers.pushStack(arg.prevState.undoStack, arg.prevState.cellVMs) - }; - } - - return arg.prevState; - } - - export function moveCellUp(arg: NativeEditorReducerArg): IMainState { - const index = arg.prevState.cellVMs.findIndex((cvm) => cvm.cell.id === arg.payload.data.cellId); - if (index > 0 && arg.payload.data.cellId) { - return swapCells({ - ...arg, - payload: { - ...arg.payload, - data: { - firstCellId: arg.prevState.cellVMs[index - 1].cell.id, - secondCellId: arg.payload.data.cellId - } - } - }); - } - - return arg.prevState; - } - - export function moveCellDown(arg: NativeEditorReducerArg): IMainState { - const newVMs = [...arg.prevState.cellVMs]; - const index = newVMs.findIndex((cvm) => cvm.cell.id === arg.payload.data.cellId); - if (index < newVMs.length - 1 && arg.payload.data.cellId) { - return swapCells({ - ...arg, - payload: { - ...arg.payload, - data: { - firstCellId: arg.payload.data.cellId, - secondCellId: arg.prevState.cellVMs[index + 1].cell.id - } - } - }); - } - - return arg.prevState; - } - - export function arrowUp(arg: NativeEditorReducerArg): IMainState { - const index = arg.prevState.cellVMs.findIndex((c) => c.cell.id === arg.payload.data.cellId); - if (index > 0) { - queueIncomingActionWithPayload(arg, CommonActionType.SELECT_CELL, { - cellId: arg.prevState.cellVMs[index - 1].cell.id, - cursorPos: CursorPos.Bottom - }); - } - - return arg.prevState; - } - - export function arrowDown(arg: NativeEditorReducerArg): IMainState { - const index = arg.prevState.cellVMs.findIndex((c) => c.cell.id === arg.payload.data.cellId); - if (index < arg.prevState.cellVMs.length - 1) { - queueIncomingActionWithPayload(arg, CommonActionType.SELECT_CELL, { - cellId: arg.prevState.cellVMs[index + 1].cell.id, - cursorPos: CursorPos.Top - }); - } - - return arg.prevState; - } -} diff --git a/src/datascience-ui/native-editor/redux/store.ts b/src/datascience-ui/native-editor/redux/store.ts deleted file mode 100644 index 53f228f59411..000000000000 --- a/src/datascience-ui/native-editor/redux/store.ts +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import * as ReduxCommon from '../../interactive-common/redux/store'; -import { PostOffice } from '../../react-common/postOffice'; -import { reducerMap } from './reducers'; - -// This special version uses the reducer map from the INativeEditorMapping -export function createStore(skipDefault: boolean, baseTheme: string, testMode: boolean, postOffice: PostOffice) { - return ReduxCommon.createStore(skipDefault, baseTheme, testMode, true, true, reducerMap, postOffice); -} diff --git a/src/datascience-ui/native-editor/toolbar.tsx b/src/datascience-ui/native-editor/toolbar.tsx deleted file mode 100644 index ecba9b191e02..000000000000 --- a/src/datascience-ui/native-editor/toolbar.tsx +++ /dev/null @@ -1,279 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as React from 'react'; -import { connect } from 'react-redux'; -import { NativeMouseCommandTelemetry } from '../../client/datascience/constants'; -import { IDataScienceExtraSettings } from '../../client/datascience/types'; -import { JupyterInfo } from '../interactive-common/jupyterInfo'; -import { - getSelectedAndFocusedInfo, - IFont, - IServerState, - SelectionAndFocusedInfo, - ServerStatus -} from '../interactive-common/mainState'; -import { IStore } from '../interactive-common/redux/store'; -import { Image, ImageName } from '../react-common/image'; -import { ImageButton } from '../react-common/imageButton'; -import { getLocString } from '../react-common/locReactSide'; -import './nativeEditor.less'; -import { actionCreators } from './redux/actions'; - -type INativeEditorDataProps = { - busy: boolean; - dirty: boolean; - baseTheme: string; - cellCount: number; - font: IFont; - kernel: IServerState; - selectionFocusedInfo: SelectionAndFocusedInfo; - variablesVisible: boolean; - settings?: IDataScienceExtraSettings; -}; -export type INativeEditorToolbarProps = INativeEditorDataProps & { - sendCommand: typeof actionCreators.sendCommand; - clearAllOutputs: typeof actionCreators.clearAllOutputs; - export: typeof actionCreators.export; - exportAs: typeof actionCreators.exportAs; - addCell: typeof actionCreators.addCell; - save: typeof actionCreators.save; - executeAllCells: typeof actionCreators.executeAllCells; - toggleVariableExplorer: typeof actionCreators.toggleVariableExplorer; - setVariableExplorerHeight: typeof actionCreators.setVariableExplorerHeight; - executeAbove: typeof actionCreators.executeAbove; - executeCellAndBelow: typeof actionCreators.executeCellAndBelow; - restartKernel: typeof actionCreators.restartKernel; - interruptKernel: typeof actionCreators.interruptKernel; - selectKernel: typeof actionCreators.selectKernel; - selectServer: typeof actionCreators.selectServer; - launchNotebookTrustPrompt: typeof actionCreators.launchNotebookTrustPrompt; - isNotebookTrusted: boolean; -}; - -function mapStateToProps(state: IStore): INativeEditorDataProps { - return { - ...state.main, - cellCount: state.main.cellVMs.length, - selectionFocusedInfo: getSelectedAndFocusedInfo(state.main), - variablesVisible: state.variables.visible - }; -} - -export class Toolbar extends React.PureComponent { - constructor(props: INativeEditorToolbarProps) { - super(props); - } - - // tslint:disable: react-this-binding-issue - // tslint:disable-next-line: max-func-body-length - public render() { - const selectedInfo = this.props.selectionFocusedInfo; - - const addCell = () => { - setTimeout(() => this.props.addCell(), 1); - this.props.sendCommand(NativeMouseCommandTelemetry.AddToEnd); - }; - const runAll = () => { - // Run all cells currently available. - this.props.executeAllCells(); - this.props.sendCommand(NativeMouseCommandTelemetry.RunAll); - }; - const save = () => { - this.props.save(); - this.props.sendCommand(NativeMouseCommandTelemetry.Save); - }; - const toggleVariableExplorer = () => { - this.props.toggleVariableExplorer(); - this.props.sendCommand(NativeMouseCommandTelemetry.ToggleVariableExplorer); - }; - const variableExplorerTooltip = this.props.variablesVisible - ? getLocString('DataScience.collapseVariableExplorerTooltip', 'Hide variables active in jupyter kernel') - : getLocString('DataScience.expandVariableExplorerTooltip', 'Show variables active in jupyter kernel'); - const runAbove = () => { - if (selectedInfo.selectedCellId) { - this.props.executeAbove(selectedInfo.selectedCellId); - this.props.sendCommand(NativeMouseCommandTelemetry.RunAbove); - } - }; - const runBelow = () => { - if (selectedInfo.selectedCellId && typeof selectedInfo.selectedCellIndex === 'number') { - // tslint:disable-next-line: no-suspicious-comment - // TODO: Is the source going to be up to date during run below? - this.props.executeCellAndBelow(selectedInfo.selectedCellId); - this.props.sendCommand(NativeMouseCommandTelemetry.RunBelow); - } - }; - const selectKernel = () => { - this.props.selectKernel(); - this.props.sendCommand(NativeMouseCommandTelemetry.SelectKernel); - }; - const selectServer = () => { - this.props.selectServer(); - this.props.sendCommand(NativeMouseCommandTelemetry.SelectServer); - }; - const launchNotebookTrustPrompt = () => { - if (!this.props.isNotebookTrusted) { - this.props.launchNotebookTrustPrompt(); - } - }; - const canRunAbove = (selectedInfo.selectedCellIndex ?? -1) > 0; - const canRunBelow = - (selectedInfo.selectedCellIndex ?? -1) < this.props.cellCount - 1 && - (selectedInfo.selectedCellId || '').length > 0; - - const canRestartAndInterruptKernel = this.props.kernel.jupyterServerStatus !== ServerStatus.NotStarted; - - return ( -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
- ); - } -} - -export const ToolbarComponent = connect(mapStateToProps, actionCreators)(Toolbar); diff --git a/src/datascience-ui/plot/index.html b/src/datascience-ui/plot/index.html deleted file mode 100644 index 9dd3ffa71749..000000000000 --- a/src/datascience-ui/plot/index.html +++ /dev/null @@ -1,356 +0,0 @@ - - - - - - - Python Extension Plot Viewer - - - - - -
- - - diff --git a/src/datascience-ui/plot/index.tsx b/src/datascience-ui/plot/index.tsx deleted file mode 100644 index aa6136295e4e..000000000000 --- a/src/datascience-ui/plot/index.tsx +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -// This must be on top, do not change. Required by webpack. -import '../common/main'; -// This must be on top, do not change. Required by webpack. - -// tslint:disable-next-line: ordered-imports -import '../common/index.css'; - -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; - -import { IVsCodeApi } from '../react-common/postOffice'; -import { detectBaseTheme } from '../react-common/themeDetector'; -import { MainPanel } from './mainPanel'; - -// This special function talks to vscode from a web panel -export declare function acquireVsCodeApi(): IVsCodeApi; - -const baseTheme = detectBaseTheme(); - -// tslint:disable:no-typeof-undefined -ReactDOM.render( - , // Turn this back off when we have real variable explorer data - document.getElementById('root') as HTMLElement -); diff --git a/src/datascience-ui/plot/mainPanel.css b/src/datascience-ui/plot/mainPanel.css deleted file mode 100644 index aa507ba5a6b9..000000000000 --- a/src/datascience-ui/plot/mainPanel.css +++ /dev/null @@ -1,13 +0,0 @@ - -.main-panel { - position: absolute; - bottom: 0; - top: 0; - left: 0; - right: 0; - font-size: var(--code-font-size); - font-family: var(--code-font-family); - background-color: var(--vscode-editor-background); - overflow: hidden; -} - diff --git a/src/datascience-ui/plot/mainPanel.tsx b/src/datascience-ui/plot/mainPanel.tsx deleted file mode 100644 index a706aabe6f61..000000000000 --- a/src/datascience-ui/plot/mainPanel.tsx +++ /dev/null @@ -1,428 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import './mainPanel.css'; - -import * as React from 'react'; -import { Tool, Value } from 'react-svg-pan-zoom'; -import * as uuid from 'uuid/v4'; - -import { createDeferred } from '../../client/common/utils/async'; -import { RegExpValues } from '../../client/datascience/constants'; -import { SharedMessages } from '../../client/datascience/messages'; -import { IPlotViewerMapping, PlotViewerMessages } from '../../client/datascience/plotting/types'; -import { IDataScienceExtraSettings } from '../../client/datascience/types'; -import { storeLocStrings } from '../react-common/locReactSide'; -import { IMessageHandler, PostOffice } from '../react-common/postOffice'; -import { getDefaultSettings } from '../react-common/settingsReactSide'; -import { StyleInjector } from '../react-common/styleInjector'; -import { SvgList } from '../react-common/svgList'; -import { SvgViewer } from '../react-common/svgViewer'; -import { TestSvg } from './testSvg'; -import { Toolbar } from './toolbar'; - -// Our css has to come after in order to override body styles -export interface IMainPanelProps { - skipDefault?: boolean; - baseTheme: string; - testMode?: boolean; -} - -interface ISize { - width: string; - height: string; -} - -//tslint:disable:no-any -interface IMainPanelState { - images: string[]; - thumbnails: string[]; - sizes: ISize[]; - values: (Value | undefined)[]; - ids: string[]; - currentImage: number; - tool: Tool; - forceDark?: boolean; - settings?: IDataScienceExtraSettings; -} - -const PanKeyboardSize = 10; - -export class MainPanel extends React.Component implements IMessageHandler { - private container: React.RefObject = React.createRef(); - private viewer: React.RefObject = React.createRef(); - private postOffice: PostOffice = new PostOffice(); - private currentValue: Value | undefined; - - // tslint:disable-next-line:max-func-body-length - constructor(props: IMainPanelProps, _state: IMainPanelState) { - super(props); - const images = !props.skipDefault ? [TestSvg, TestSvg, TestSvg] : []; - const thumbnails = images.map(this.generateThumbnail); - const sizes = images.map(this.extractSize); - const values = images.map((_i) => undefined); - const ids = images.map((_i) => uuid()); - - this.state = { - images, - thumbnails, - sizes, - values, - ids, - tool: 'pan', - currentImage: images.length > 0 ? 0 : -1, - settings: this.props.testMode ? getDefaultSettings() : undefined - }; - } - - public componentWillMount() { - // Add ourselves as a handler for the post office - this.postOffice.addHandler(this); - - // Tell the plot viewer code we have started. - this.postOffice.sendMessage(PlotViewerMessages.Started); - - // Listen to key events - window.addEventListener('keydown', this.onKeyDown); - } - - public componentWillUnmount() { - this.postOffice.removeHandler(this); - this.postOffice.dispose(); - // Stop listening to key events - window.removeEventListener('keydown', this.onKeyDown); - } - - public render = () => { - if (this.state.settings) { - const baseTheme = this.computeBaseTheme(); - return ( -
- - {this.renderToolbar(baseTheme)} - {this.renderThumbnails(baseTheme)} - {this.renderPlot(baseTheme)} -
- ); - } else { - return null; - } - }; - - // tslint:disable-next-line:no-any - public handleMessage = (msg: string, payload?: any) => { - switch (msg) { - case PlotViewerMessages.SendPlot: - this.addPlot(payload); - break; - - case SharedMessages.UpdateSettings: - this.updateSettings(payload); - break; - - case SharedMessages.LocInit: - this.initializeLoc(payload); - break; - - default: - break; - } - - return false; - }; - - private initializeLoc(content: string) { - const locJSON = JSON.parse(content); - storeLocStrings(locJSON); - } - - private updateSettings(content: string) { - const newSettingsJSON = JSON.parse(content); - const newSettings = newSettingsJSON as IDataScienceExtraSettings; - this.setState({ - settings: newSettings - }); - } - - private darkChanged = (newDark: boolean) => { - // update our base theme if allowed. Don't do this - // during testing as it will mess up the expected render count. - if (!this.props.testMode) { - this.setState({ - forceDark: newDark - }); - } - }; - - private computeBaseTheme(): string { - // If we're ignoring, always light - if (this.state.settings?.ignoreVscodeTheme) { - return 'vscode-light'; - } - - // Otherwise see if the style injector has figured out - // the theme is dark or not - if (this.state.forceDark !== undefined) { - return this.state.forceDark ? 'vscode-dark' : 'vscode-light'; - } - - return this.props.baseTheme; - } - - private onKeyDown = (event: KeyboardEvent) => { - if (!event.ctrlKey) { - switch (event.key) { - case 'ArrowRight': - if (this.state.currentImage < this.state.images.length - 1) { - this.setState({ currentImage: this.state.currentImage + 1 }); - } - break; - - case 'ArrowLeft': - if (this.state.currentImage > 0) { - this.setState({ currentImage: this.state.currentImage - 1 }); - } - break; - - default: - break; - } - } else if (event.ctrlKey && !event.altKey && this.viewer && this.viewer.current) { - switch (event.key) { - case 'ArrowRight': - this.viewer.current.move(PanKeyboardSize, 0); - break; - - case 'ArrowLeft': - this.viewer.current.move(-PanKeyboardSize, 0); - break; - - case 'ArrowUp': - this.viewer.current.move(0, -PanKeyboardSize); - break; - - case 'ArrowDown': - this.viewer.current.move(0, PanKeyboardSize); - break; - - default: - break; - } - } else if (event.ctrlKey && event.altKey && this.viewer && this.viewer.current) { - switch (event.key) { - case '+': - this.viewer.current.zoom(1.5); - break; - - case '-': - this.viewer.current.zoom(0.66666); - break; - - default: - break; - } - } - }; - - private addPlot(payload: any) { - this.setState({ - images: [...this.state.images, payload as string], - thumbnails: [...this.state.thumbnails, this.generateThumbnail(payload)], - sizes: [...this.state.sizes, this.extractSize(payload)], - values: [...this.state.values, undefined], - ids: [...this.state.ids, uuid()], - currentImage: this.state.images.length - }); - } - - private renderThumbnails(_baseTheme: string) { - return ( - - ); - } - - private renderToolbar(baseTheme: string) { - const prev = this.state.currentImage > 0 ? this.prevClicked : undefined; - const next = this.state.currentImage < this.state.images.length - 1 ? this.nextClicked : undefined; - const deleteClickHandler = this.state.currentImage !== -1 ? this.deleteClicked : undefined; - return ( - - ); - } - private renderPlot(baseTheme: string) { - // Render current plot - const currentPlot = this.state.currentImage >= 0 ? this.state.images[this.state.currentImage] : undefined; - const currentSize = this.state.currentImage >= 0 ? this.state.sizes[this.state.currentImage] : undefined; - const currentId = this.state.currentImage >= 0 ? this.state.ids[this.state.currentImage] : undefined; - const value = this.state.currentImage >= 0 ? this.state.values[this.state.currentImage] : undefined; - if (currentPlot && currentSize && currentId) { - return ( - - ); - } - - return null; - } - - private generateThumbnail(image: string): string { - // A 'thumbnail' is really just an svg image with - // the width and height forced to 100% - const h = image.replace(RegExpValues.SvgHeightRegex, '$1100%"'); - return h.replace(RegExpValues.SvgWidthRegex, '$1100%"'); - } - - private changeCurrentValue = (value: Value) => { - this.currentValue = { ...value }; - }; - - private changeTool = (tool: Tool) => { - this.setState({ tool }); - }; - - private extractSize(image: string): ISize { - let height = '100px'; - let width = '100px'; - - // Try the tags that might have been added by the cell formatter - const sizeTagMatch = RegExpValues.SvgSizeTagRegex.exec(image); - if (sizeTagMatch && sizeTagMatch.length > 2) { - width = sizeTagMatch[1]; - height = sizeTagMatch[2]; - } else { - // Otherwise just parse the height/width directly - const heightMatch = RegExpValues.SvgHeightRegex.exec(image); - if (heightMatch && heightMatch.length > 2) { - height = heightMatch[2]; - } - const widthMatch = RegExpValues.SvgHeightRegex.exec(image); - if (widthMatch && widthMatch.length > 2) { - width = widthMatch[2]; - } - } - - return { - height, - width - }; - } - - private changeCurrentImage(index: number) { - // Update our state for our current image and our current value - if (index !== this.state.currentImage) { - const newValues = [...this.state.values]; - newValues[this.state.currentImage] = this.currentValue; - this.setState({ - currentImage: index, - values: newValues - }); - - // Reassign the current value to the new index so we track it. - this.currentValue = newValues[index]; - } - } - - private imageClicked = (index: number) => { - this.changeCurrentImage(index); - }; - - private sendMessage(type: T, payload?: M[T]) { - this.postOffice.sendMessage(type, payload); - } - - private exportCurrent = async () => { - // In order to export, we need the png and the svg. Generate - // a png by drawing to a canvas and then turning the canvas into a dataurl. - if (this.container && this.container.current) { - const doc = this.container.current.ownerDocument; - if (doc) { - const canvas = doc.createElement('canvas'); - if (canvas) { - const ctx = canvas.getContext('2d'); - if (ctx) { - const waitable = createDeferred(); - const svgBlob = new Blob([this.state.images[this.state.currentImage]], { - type: 'image/svg+xml;charset=utf-8' - }); - const img = new Image(); - const url = window.URL.createObjectURL(svgBlob); - img.onload = () => { - canvas.width = img.width; - canvas.height = img.height; - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.drawImage(img, 0, 0); - waitable.resolve(); - }; - img.src = url; - await waitable.promise; - const png = canvas.toDataURL('png'); - canvas.remove(); - - // Send both our image and the png. - this.sendMessage(PlotViewerMessages.ExportPlot, { - svg: this.state.images[this.state.currentImage], - png - }); - } - } - } - } - }; - - private copyCurrent = async () => { - // Not supported at the moment. - }; - - private prevClicked = () => { - this.changeCurrentImage(this.state.currentImage - 1); - }; - - private nextClicked = () => { - this.changeCurrentImage(this.state.currentImage + 1); - }; - - private deleteClicked = () => { - if (this.state.currentImage >= 0) { - const oldCurrent = this.state.currentImage; - const newCurrent = this.state.images.length > 1 ? this.state.currentImage : -1; - - this.setState({ - images: this.state.images.filter((_v, i) => i !== oldCurrent), - sizes: this.state.sizes.filter((_v, i) => i !== oldCurrent), - values: this.state.values.filter((_v, i) => i !== oldCurrent), - thumbnails: this.state.thumbnails.filter((_v, i) => i !== oldCurrent), - currentImage: newCurrent - }); - - // Tell the other side too as we don't want it sending this image again - this.sendMessage(PlotViewerMessages.RemovePlot, oldCurrent); - } - }; -} diff --git a/src/datascience-ui/plot/testSvg.ts b/src/datascience-ui/plot/testSvg.ts deleted file mode 100644 index 64bfd3e54221..000000000000 --- a/src/datascience-ui/plot/testSvg.ts +++ /dev/null @@ -1,571 +0,0 @@ -// tslint:disable: no-multiline-string no-trailing-whitespace -export const TestSvg = ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -`; diff --git a/src/datascience-ui/plot/toolbar.css b/src/datascience-ui/plot/toolbar.css deleted file mode 100644 index 82df52ea1834..000000000000 --- a/src/datascience-ui/plot/toolbar.css +++ /dev/null @@ -1,7 +0,0 @@ -#plot-toolbar-panel { - position: absolute; - top: 0; - left: 0; - background-color: var(--vscode-editor-background); - border: 1px solid black; -} \ No newline at end of file diff --git a/src/datascience-ui/plot/toolbar.tsx b/src/datascience-ui/plot/toolbar.tsx deleted file mode 100644 index 692fe4f68ed6..000000000000 --- a/src/datascience-ui/plot/toolbar.tsx +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as React from 'react'; -import { Tool } from 'react-svg-pan-zoom'; -import { Image, ImageName } from '../react-common/image'; -import { ImageButton } from '../react-common/imageButton'; -import { getLocString } from '../react-common/locReactSide'; - -interface IToolbarProps { - baseTheme: string; - changeTool(tool: Tool): void; - prevButtonClicked?(): void; - nextButtonClicked?(): void; - exportButtonClicked(): void; - copyButtonClicked(): void; - deleteButtonClicked?(): void; -} - -export class Toolbar extends React.Component { - constructor(props: IToolbarProps) { - super(props); - } - - public render() { - return ( -
- - - - - - - - - - - - - - - - {/* This isn't possible until VS Code supports copying images to the clipboard. See https://github.com/microsoft/vscode/issues/217 - - - */} - - - - - - -
- ); - } - - private pan = () => { - this.props.changeTool('pan'); - }; - - private zoomIn = () => { - this.props.changeTool('zoom-in'); - }; - - private zoomOut = () => { - this.props.changeTool('zoom-out'); - }; -} diff --git a/src/datascience-ui/react-common/arePathsSame.ts b/src/datascience-ui/react-common/arePathsSame.ts deleted file mode 100644 index 8e46dc12f41d..000000000000 --- a/src/datascience-ui/react-common/arePathsSame.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; -import * as path from 'path'; -import { getOSType, OSType } from '../../client/common/utils/platform'; - -// Provide functionality of IFileSystem arePathsSame for the React components -export function arePathsSame(path1: string, path2: string): boolean { - path1 = path.normalize(path1); - path2 = path.normalize(path2); - if (getOSType() === OSType.Windows) { - return path1.toUpperCase() === path2.toUpperCase(); - } else { - return path1 === path2; - } -} diff --git a/src/datascience-ui/react-common/button.tsx b/src/datascience-ui/react-common/button.tsx deleted file mode 100644 index e11cb502714a..000000000000 --- a/src/datascience-ui/react-common/button.tsx +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; -import * as React from 'react'; - -interface IButtonProps { - className: string; - tooltip: string; - disabled?: boolean; - hidden?: boolean; - onClick?(event?: React.MouseEvent): void; -} - -export class Button extends React.Component { - constructor(props: IButtonProps) { - super(props); - } - - public render() { - const innerFilter = this.props.disabled ? 'button-inner-disabled-filter' : ''; - const ariaDisabled = this.props.disabled ? 'true' : 'false'; - - return ( - - ); - } -} diff --git a/src/datascience-ui/react-common/codicon/codicon-animations.css b/src/datascience-ui/react-common/codicon/codicon-animations.css deleted file mode 100644 index abfde40dede6..000000000000 --- a/src/datascience-ui/react-common/codicon/codicon-animations.css +++ /dev/null @@ -1,14 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -@keyframes codicon-spin { - 100% { - transform:rotate(360deg); - } -} - -.codicon-animation-spin { - animation: codicon-spin 1.5s linear infinite; -} diff --git a/src/datascience-ui/react-common/codicon/codicon-modifications.css b/src/datascience-ui/react-common/codicon/codicon-modifications.css deleted file mode 100644 index 950493dd6bed..000000000000 --- a/src/datascience-ui/react-common/codicon/codicon-modifications.css +++ /dev/null @@ -1,8 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.codicon-wrench-subaction { - opacity: 0.5; -} diff --git a/src/datascience-ui/react-common/codicon/codicon.css b/src/datascience-ui/react-common/codicon/codicon.css deleted file mode 100644 index 70e60afd42af..000000000000 --- a/src/datascience-ui/react-common/codicon/codicon.css +++ /dev/null @@ -1,429 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -@font-face { - font-family: "codicon"; - src: url("./codicon.ttf?b72c513f65e30cf5c3607d5a7971b6a9") format("truetype"); -} - -.codicon[class*='codicon-'] { - font: normal normal normal 16px/1 codicon; - display: inline-block; - text-decoration: none; - text-rendering: auto; - text-align: center; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - user-select: none; - -webkit-user-select: none; - -ms-user-select: none; -} - - -.codicon-add:before { content: "\ea60" } -.codicon-plus:before { content: "\ea60" } -.codicon-gist-new:before { content: "\ea60" } -.codicon-repo-create:before { content: "\ea60" } -.codicon-lightbulb:before { content: "\ea61" } -.codicon-light-bulb:before { content: "\ea61" } -.codicon-repo:before { content: "\ea62" } -.codicon-repo-delete:before { content: "\ea62" } -.codicon-gist-fork:before { content: "\ea63" } -.codicon-repo-forked:before { content: "\ea63" } -.codicon-git-pull-request:before { content: "\ea64" } -.codicon-git-pull-request-abandoned:before { content: "\ea64" } -.codicon-record-keys:before { content: "\ea65" } -.codicon-keyboard:before { content: "\ea65" } -.codicon-tag:before { content: "\ea66" } -.codicon-tag-add:before { content: "\ea66" } -.codicon-tag-remove:before { content: "\ea66" } -.codicon-person:before { content: "\ea67" } -.codicon-person-add:before { content: "\ea67" } -.codicon-person-follow:before { content: "\ea67" } -.codicon-person-outline:before { content: "\ea67" } -.codicon-person-filled:before { content: "\ea67" } -.codicon-git-branch:before { content: "\ea68" } -.codicon-git-branch-create:before { content: "\ea68" } -.codicon-git-branch-delete:before { content: "\ea68" } -.codicon-source-control:before { content: "\ea68" } -.codicon-mirror:before { content: "\ea69" } -.codicon-mirror-public:before { content: "\ea69" } -.codicon-star:before { content: "\ea6a" } -.codicon-star-add:before { content: "\ea6a" } -.codicon-star-delete:before { content: "\ea6a" } -.codicon-star-empty:before { content: "\ea6a" } -.codicon-comment:before { content: "\ea6b" } -.codicon-comment-add:before { content: "\ea6b" } -.codicon-alert:before { content: "\ea6c" } -.codicon-warning:before { content: "\ea6c" } -.codicon-search:before { content: "\ea6d" } -.codicon-search-save:before { content: "\ea6d" } -.codicon-log-out:before { content: "\ea6e" } -.codicon-sign-out:before { content: "\ea6e" } -.codicon-log-in:before { content: "\ea6f" } -.codicon-sign-in:before { content: "\ea6f" } -.codicon-eye:before { content: "\ea70" } -.codicon-eye-unwatch:before { content: "\ea70" } -.codicon-eye-watch:before { content: "\ea70" } -.codicon-circle-filled:before { content: "\ea71" } -.codicon-primitive-dot:before { content: "\ea71" } -.codicon-close-dirty:before { content: "\ea71" } -.codicon-debug-breakpoint:before { content: "\ea71" } -.codicon-debug-breakpoint-disabled:before { content: "\ea71" } -.codicon-debug-hint:before { content: "\ea71" } -.codicon-primitive-square:before { content: "\ea72" } -.codicon-edit:before { content: "\ea73" } -.codicon-pencil:before { content: "\ea73" } -.codicon-info:before { content: "\ea74" } -.codicon-issue-opened:before { content: "\ea74" } -.codicon-gist-private:before { content: "\ea75" } -.codicon-git-fork-private:before { content: "\ea75" } -.codicon-lock:before { content: "\ea75" } -.codicon-mirror-private:before { content: "\ea75" } -.codicon-close:before { content: "\ea76" } -.codicon-remove-close:before { content: "\ea76" } -.codicon-x:before { content: "\ea76" } -.codicon-repo-sync:before { content: "\ea77" } -.codicon-sync:before { content: "\ea77" } -.codicon-clone:before { content: "\ea78" } -.codicon-desktop-download:before { content: "\ea78" } -.codicon-beaker:before { content: "\ea79" } -.codicon-microscope:before { content: "\ea79" } -.codicon-vm:before { content: "\ea7a" } -.codicon-device-desktop:before { content: "\ea7a" } -.codicon-file:before { content: "\ea7b" } -.codicon-file-text:before { content: "\ea7b" } -.codicon-more:before { content: "\ea7c" } -.codicon-ellipsis:before { content: "\ea7c" } -.codicon-kebab-horizontal:before { content: "\ea7c" } -.codicon-mail-reply:before { content: "\ea7d" } -.codicon-reply:before { content: "\ea7d" } -.codicon-organization:before { content: "\ea7e" } -.codicon-organization-filled:before { content: "\ea7e" } -.codicon-organization-outline:before { content: "\ea7e" } -.codicon-new-file:before { content: "\ea7f" } -.codicon-file-add:before { content: "\ea7f" } -.codicon-new-folder:before { content: "\ea80" } -.codicon-file-directory-create:before { content: "\ea80" } -.codicon-trash:before { content: "\ea81" } -.codicon-trashcan:before { content: "\ea81" } -.codicon-history:before { content: "\ea82" } -.codicon-clock:before { content: "\ea82" } -.codicon-folder:before { content: "\ea83" } -.codicon-file-directory:before { content: "\ea83" } -.codicon-symbol-folder:before { content: "\ea83" } -.codicon-logo-github:before { content: "\ea84" } -.codicon-mark-github:before { content: "\ea84" } -.codicon-github:before { content: "\ea84" } -.codicon-terminal:before { content: "\ea85" } -.codicon-console:before { content: "\ea85" } -.codicon-repl:before { content: "\ea85" } -.codicon-zap:before { content: "\ea86" } -.codicon-symbol-event:before { content: "\ea86" } -.codicon-error:before { content: "\ea87" } -.codicon-stop:before { content: "\ea87" } -.codicon-variable:before { content: "\ea88" } -.codicon-symbol-variable:before { content: "\ea88" } -.codicon-array:before { content: "\ea8a" } -.codicon-symbol-array:before { content: "\ea8a" } -.codicon-symbol-module:before { content: "\ea8b" } -.codicon-symbol-package:before { content: "\ea8b" } -.codicon-symbol-namespace:before { content: "\ea8b" } -.codicon-symbol-object:before { content: "\ea8b" } -.codicon-symbol-method:before { content: "\ea8c" } -.codicon-symbol-function:before { content: "\ea8c" } -.codicon-symbol-constructor:before { content: "\ea8c" } -.codicon-symbol-boolean:before { content: "\ea8f" } -.codicon-symbol-null:before { content: "\ea8f" } -.codicon-symbol-numeric:before { content: "\ea90" } -.codicon-symbol-number:before { content: "\ea90" } -.codicon-symbol-structure:before { content: "\ea91" } -.codicon-symbol-struct:before { content: "\ea91" } -.codicon-symbol-parameter:before { content: "\ea92" } -.codicon-symbol-type-parameter:before { content: "\ea92" } -.codicon-symbol-key:before { content: "\ea93" } -.codicon-symbol-text:before { content: "\ea93" } -.codicon-symbol-reference:before { content: "\ea94" } -.codicon-go-to-file:before { content: "\ea94" } -.codicon-symbol-enum:before { content: "\ea95" } -.codicon-symbol-value:before { content: "\ea95" } -.codicon-symbol-ruler:before { content: "\ea96" } -.codicon-symbol-unit:before { content: "\ea96" } -.codicon-activate-breakpoints:before { content: "\ea97" } -.codicon-archive:before { content: "\ea98" } -.codicon-arrow-both:before { content: "\ea99" } -.codicon-arrow-down:before { content: "\ea9a" } -.codicon-arrow-left:before { content: "\ea9b" } -.codicon-arrow-right:before { content: "\ea9c" } -.codicon-arrow-small-down:before { content: "\ea9d" } -.codicon-arrow-small-left:before { content: "\ea9e" } -.codicon-arrow-small-right:before { content: "\ea9f" } -.codicon-arrow-small-up:before { content: "\eaa0" } -.codicon-arrow-up:before { content: "\eaa1" } -.codicon-bell:before { content: "\eaa2" } -.codicon-bold:before { content: "\eaa3" } -.codicon-book:before { content: "\eaa4" } -.codicon-bookmark:before { content: "\eaa5" } -.codicon-debug-breakpoint-conditional-unverified:before { content: "\eaa6" } -.codicon-debug-breakpoint-conditional:before { content: "\eaa7" } -.codicon-debug-breakpoint-conditional-disabled:before { content: "\eaa7" } -.codicon-debug-breakpoint-data-unverified:before { content: "\eaa8" } -.codicon-debug-breakpoint-data:before { content: "\eaa9" } -.codicon-debug-breakpoint-data-disabled:before { content: "\eaa9" } -.codicon-debug-breakpoint-log-unverified:before { content: "\eaaa" } -.codicon-debug-breakpoint-log:before { content: "\eaab" } -.codicon-debug-breakpoint-log-disabled:before { content: "\eaab" } -.codicon-briefcase:before { content: "\eaac" } -.codicon-broadcast:before { content: "\eaad" } -.codicon-browser:before { content: "\eaae" } -.codicon-bug:before { content: "\eaaf" } -.codicon-calendar:before { content: "\eab0" } -.codicon-case-sensitive:before { content: "\eab1" } -.codicon-check:before { content: "\eab2" } -.codicon-checklist:before { content: "\eab3" } -.codicon-chevron-down:before { content: "\eab4" } -.codicon-chevron-left:before { content: "\eab5" } -.codicon-chevron-right:before { content: "\eab6" } -.codicon-chevron-up:before { content: "\eab7" } -.codicon-chrome-close:before { content: "\eab8" } -.codicon-chrome-maximize:before { content: "\eab9" } -.codicon-chrome-minimize:before { content: "\eaba" } -.codicon-chrome-restore:before { content: "\eabb" } -.codicon-circle-outline:before { content: "\eabc" } -.codicon-debug-breakpoint-unverified:before { content: "\eabc" } -.codicon-circle-slash:before { content: "\eabd" } -.codicon-circuit-board:before { content: "\eabe" } -.codicon-clear-all:before { content: "\eabf" } -.codicon-clippy:before { content: "\eac0" } -.codicon-close-all:before { content: "\eac1" } -.codicon-cloud-download:before { content: "\eac2" } -.codicon-cloud-upload:before { content: "\eac3" } -.codicon-code:before { content: "\eac4" } -.codicon-collapse-all:before { content: "\eac5" } -.codicon-color-mode:before { content: "\eac6" } -.codicon-comment-discussion:before { content: "\eac7" } -.codicon-compare-changes:before { content: "\eac8" } -.codicon-credit-card:before { content: "\eac9" } -.codicon-dash:before { content: "\eacc" } -.codicon-dashboard:before { content: "\eacd" } -.codicon-database:before { content: "\eace" } -.codicon-debug-continue:before { content: "\eacf" } -.codicon-debug-disconnect:before { content: "\ead0" } -.codicon-debug-pause:before { content: "\ead1" } -.codicon-debug-restart:before { content: "\ead2" } -.codicon-debug-start:before { content: "\ead3" } -.codicon-debug-step-into:before { content: "\ead4" } -.codicon-debug-step-out:before { content: "\ead5" } -.codicon-debug-step-over:before { content: "\ead6" } -.codicon-debug-stop:before { content: "\ead7" } -.codicon-debug:before { content: "\ead8" } -.codicon-device-camera-video:before { content: "\ead9" } -.codicon-device-camera:before { content: "\eada" } -.codicon-device-mobile:before { content: "\eadb" } -.codicon-diff-added:before { content: "\eadc" } -.codicon-diff-ignored:before { content: "\eadd" } -.codicon-diff-modified:before { content: "\eade" } -.codicon-diff-removed:before { content: "\eadf" } -.codicon-diff-renamed:before { content: "\eae0" } -.codicon-diff:before { content: "\eae1" } -.codicon-discard:before { content: "\eae2" } -.codicon-editor-layout:before { content: "\eae3" } -.codicon-empty-window:before { content: "\eae4" } -.codicon-exclude:before { content: "\eae5" } -.codicon-extensions:before { content: "\eae6" } -.codicon-eye-closed:before { content: "\eae7" } -.codicon-file-binary:before { content: "\eae8" } -.codicon-file-code:before { content: "\eae9" } -.codicon-file-media:before { content: "\eaea" } -.codicon-file-pdf:before { content: "\eaeb" } -.codicon-file-submodule:before { content: "\eaec" } -.codicon-file-symlink-directory:before { content: "\eaed" } -.codicon-file-symlink-file:before { content: "\eaee" } -.codicon-file-zip:before { content: "\eaef" } -.codicon-files:before { content: "\eaf0" } -.codicon-filter:before { content: "\eaf1" } -.codicon-flame:before { content: "\eaf2" } -.codicon-fold-down:before { content: "\eaf3" } -.codicon-fold-up:before { content: "\eaf4" } -.codicon-fold:before { content: "\eaf5" } -.codicon-folder-active:before { content: "\eaf6" } -.codicon-folder-opened:before { content: "\eaf7" } -.codicon-gear:before { content: "\eaf8" } -.codicon-gift:before { content: "\eaf9" } -.codicon-gist-secret:before { content: "\eafa" } -.codicon-gist:before { content: "\eafb" } -.codicon-git-commit:before { content: "\eafc" } -.codicon-git-compare:before { content: "\eafd" } -.codicon-git-merge:before { content: "\eafe" } -.codicon-github-action:before { content: "\eaff" } -.codicon-github-alt:before { content: "\eb00" } -.codicon-globe:before { content: "\eb01" } -.codicon-grabber:before { content: "\eb02" } -.codicon-graph:before { content: "\eb03" } -.codicon-gripper:before { content: "\eb04" } -.codicon-heart:before { content: "\eb05" } -.codicon-home:before { content: "\eb06" } -.codicon-horizontal-rule:before { content: "\eb07" } -.codicon-hubot:before { content: "\eb08" } -.codicon-inbox:before { content: "\eb09" } -.codicon-issue-closed:before { content: "\eb0a" } -.codicon-issue-reopened:before { content: "\eb0b" } -.codicon-issues:before { content: "\eb0c" } -.codicon-italic:before { content: "\eb0d" } -.codicon-jersey:before { content: "\eb0e" } -.codicon-json:before { content: "\eb0f" } -.codicon-kebab-vertical:before { content: "\eb10" } -.codicon-key:before { content: "\eb11" } -.codicon-law:before { content: "\eb12" } -.codicon-lightbulb-autofix:before { content: "\eb13" } -.codicon-link-external:before { content: "\eb14" } -.codicon-link:before { content: "\eb15" } -.codicon-list-ordered:before { content: "\eb16" } -.codicon-list-unordered:before { content: "\eb17" } -.codicon-live-share:before { content: "\eb18" } -.codicon-loading:before { content: "\eb19" } -.codicon-location:before { content: "\eb1a" } -.codicon-mail-read:before { content: "\eb1b" } -.codicon-mail:before { content: "\eb1c" } -.codicon-markdown:before { content: "\eb1d" } -.codicon-megaphone:before { content: "\eb1e" } -.codicon-mention:before { content: "\eb1f" } -.codicon-milestone:before { content: "\eb20" } -.codicon-mortar-board:before { content: "\eb21" } -.codicon-move:before { content: "\eb22" } -.codicon-multiple-windows:before { content: "\eb23" } -.codicon-mute:before { content: "\eb24" } -.codicon-no-newline:before { content: "\eb25" } -.codicon-note:before { content: "\eb26" } -.codicon-octoface:before { content: "\eb27" } -.codicon-open-preview:before { content: "\eb28" } -.codicon-package:before { content: "\eb29" } -.codicon-paintcan:before { content: "\eb2a" } -.codicon-pin:before { content: "\eb2b" } -.codicon-play:before { content: "\eb2c" } -.codicon-run:before { content: "\eb2c" } -.codicon-plug:before { content: "\eb2d" } -.codicon-preserve-case:before { content: "\eb2e" } -.codicon-preview:before { content: "\eb2f" } -.codicon-project:before { content: "\eb30" } -.codicon-pulse:before { content: "\eb31" } -.codicon-question:before { content: "\eb32" } -.codicon-quote:before { content: "\eb33" } -.codicon-radio-tower:before { content: "\eb34" } -.codicon-reactions:before { content: "\eb35" } -.codicon-references:before { content: "\eb36" } -.codicon-refresh:before { content: "\eb37" } -.codicon-regex:before { content: "\eb38" } -.codicon-remote-explorer:before { content: "\eb39" } -.codicon-remote:before { content: "\eb3a" } -.codicon-remove:before { content: "\eb3b" } -.codicon-replace-all:before { content: "\eb3c" } -.codicon-replace:before { content: "\eb3d" } -.codicon-repo-clone:before { content: "\eb3e" } -.codicon-repo-force-push:before { content: "\eb3f" } -.codicon-repo-pull:before { content: "\eb40" } -.codicon-repo-push:before { content: "\eb41" } -.codicon-report:before { content: "\eb42" } -.codicon-request-changes:before { content: "\eb43" } -.codicon-rocket:before { content: "\eb44" } -.codicon-root-folder-opened:before { content: "\eb45" } -.codicon-root-folder:before { content: "\eb46" } -.codicon-rss:before { content: "\eb47" } -.codicon-ruby:before { content: "\eb48" } -.codicon-save-all:before { content: "\eb49" } -.codicon-save-as:before { content: "\eb4a" } -.codicon-save:before { content: "\eb4b" } -.codicon-screen-full:before { content: "\eb4c" } -.codicon-screen-normal:before { content: "\eb4d" } -.codicon-search-stop:before { content: "\eb4e" } -.codicon-server:before { content: "\eb50" } -.codicon-settings-gear:before { content: "\eb51" } -.codicon-settings:before { content: "\eb52" } -.codicon-shield:before { content: "\eb53" } -.codicon-smiley:before { content: "\eb54" } -.codicon-sort-precedence:before { content: "\eb55" } -.codicon-split-horizontal:before { content: "\eb56" } -.codicon-split-vertical:before { content: "\eb57" } -.codicon-squirrel:before { content: "\eb58" } -.codicon-star-full:before { content: "\eb59" } -.codicon-star-half:before { content: "\eb5a" } -.codicon-symbol-class:before { content: "\eb5b" } -.codicon-symbol-color:before { content: "\eb5c" } -.codicon-symbol-constant:before { content: "\eb5d" } -.codicon-symbol-enum-member:before { content: "\eb5e" } -.codicon-symbol-field:before { content: "\eb5f" } -.codicon-symbol-file:before { content: "\eb60" } -.codicon-symbol-interface:before { content: "\eb61" } -.codicon-symbol-keyword:before { content: "\eb62" } -.codicon-symbol-misc:before { content: "\eb63" } -.codicon-symbol-operator:before { content: "\eb64" } -.codicon-symbol-property:before { content: "\eb65" } -.codicon-wrench:before { content: "\eb65" } -.codicon-wrench-subaction:before { content: "\eb65" } -.codicon-symbol-snippet:before { content: "\eb66" } -.codicon-tasklist:before { content: "\eb67" } -.codicon-telescope:before { content: "\eb68" } -.codicon-text-size:before { content: "\eb69" } -.codicon-three-bars:before { content: "\eb6a" } -.codicon-thumbsdown:before { content: "\eb6b" } -.codicon-thumbsup:before { content: "\eb6c" } -.codicon-tools:before { content: "\eb6d" } -.codicon-triangle-down:before { content: "\eb6e" } -.codicon-triangle-left:before { content: "\eb6f" } -.codicon-triangle-right:before { content: "\eb70" } -.codicon-triangle-up:before { content: "\eb71" } -.codicon-twitter:before { content: "\eb72" } -.codicon-unfold:before { content: "\eb73" } -.codicon-unlock:before { content: "\eb74" } -.codicon-unmute:before { content: "\eb75" } -.codicon-unverified:before { content: "\eb76" } -.codicon-verified:before { content: "\eb77" } -.codicon-versions:before { content: "\eb78" } -.codicon-vm-active:before { content: "\eb79" } -.codicon-vm-outline:before { content: "\eb7a" } -.codicon-vm-running:before { content: "\eb7b" } -.codicon-watch:before { content: "\eb7c" } -.codicon-whitespace:before { content: "\eb7d" } -.codicon-whole-word:before { content: "\eb7e" } -.codicon-window:before { content: "\eb7f" } -.codicon-word-wrap:before { content: "\eb80" } -.codicon-zoom-in:before { content: "\eb81" } -.codicon-zoom-out:before { content: "\eb82" } -.codicon-list-filter:before { content: "\eb83" } -.codicon-list-flat:before { content: "\eb84" } -.codicon-list-selection:before { content: "\eb85" } -.codicon-selection:before { content: "\eb85" } -.codicon-list-tree:before { content: "\eb86" } -.codicon-debug-breakpoint-function-unverified:before { content: "\eb87" } -.codicon-debug-breakpoint-function:before { content: "\eb88" } -.codicon-debug-breakpoint-function-disabled:before { content: "\eb88" } -.codicon-debug-stackframe-active:before { content: "\eb89" } -.codicon-debug-stackframe-dot:before { content: "\eb8a" } -.codicon-debug-stackframe:before { content: "\eb8b" } -.codicon-debug-stackframe-focused:before { content: "\eb8b" } -.codicon-debug-breakpoint-unsupported:before { content: "\eb8c" } -.codicon-symbol-string:before { content: "\eb8d" } -.codicon-debug-reverse-continue:before { content: "\eb8e" } -.codicon-debug-step-back:before { content: "\eb8f" } -.codicon-debug-restart-frame:before { content: "\eb90" } -.codicon-debug-alternate:before { content: "\eb91" } -.codicon-call-incoming:before { content: "\eb92" } -.codicon-call-outgoing:before { content: "\eb93" } -.codicon-menu:before { content: "\eb94" } -.codicon-expand-all:before { content: "\eb95" } -.codicon-feedback:before { content: "\eb96" } -.codicon-group-by-ref-type:before { content: "\eb97" } -.codicon-ungroup-by-ref-type:before { content: "\eb98" } -.codicon-account:before { content: "\eb99" } -.codicon-bell-dot:before { content: "\eb9a" } -.codicon-debug-console:before { content: "\eb9b" } -.codicon-library:before { content: "\eb9c" } -.codicon-output:before { content: "\eb9d" } -.codicon-run-all:before { content: "\eb9e" } -.codicon-sync-ignored:before { content: "\eb9f" } -.codicon-pinned:before { content: "\eba0" } -.codicon-github-inverted:before { content: "\eba1" } -.codicon-debug-alt-2:before { content: "\f101" } -.codicon-debug-alt:before { content: "\f102" } diff --git a/src/datascience-ui/react-common/codicon/codicon.ts b/src/datascience-ui/react-common/codicon/codicon.ts deleted file mode 100644 index 65ca2f1662aa..000000000000 --- a/src/datascience-ui/react-common/codicon/codicon.ts +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// These values come from VS code source. -export enum CodIcon { - RunByLine = '\uead4', // This is actually debug-step-into - Stop = '\uead7' -} diff --git a/src/datascience-ui/react-common/codicon/codicon.ttf b/src/datascience-ui/react-common/codicon/codicon.ttf deleted file mode 100644 index b4066b7d08c4..000000000000 Binary files a/src/datascience-ui/react-common/codicon/codicon.ttf and /dev/null differ diff --git a/src/datascience-ui/react-common/constants.ts b/src/datascience-ui/react-common/constants.ts deleted file mode 100644 index c7bd757316d7..000000000000 --- a/src/datascience-ui/react-common/constants.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { OSType } from '../../client/common/utils/platform'; - -// Javascript keyCodes -export const KeyCodes = { - LeftArrow: 37, - UpArrow: 38, - RightArrow: 39, - DownArrow: 40, - PageUp: 33, - PageDown: 34, - End: 35, - Home: 36 -}; - -export function getOSType() { - if (window.navigator.platform.startsWith('Mac')) { - return OSType.OSX; - } else if (window.navigator.platform.startsWith('Win')) { - return OSType.Windows; - } else if (window.navigator.userAgent.indexOf('Linux') > 0) { - return OSType.Linux; - } else { - return OSType.Unknown; - } -} diff --git a/src/datascience-ui/react-common/errorBoundary.tsx b/src/datascience-ui/react-common/errorBoundary.tsx deleted file mode 100644 index a37bc55cc147..000000000000 --- a/src/datascience-ui/react-common/errorBoundary.tsx +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; -import * as React from 'react'; - -interface IErrorState { - hasError: boolean; - errorMessage: string; -} - -export class ErrorBoundary extends React.Component<{}, IErrorState> { - constructor(props: {}) { - super(props); - this.state = { hasError: false, errorMessage: '' }; - } - - public componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { - const stack = errorInfo.componentStack; - - // Display fallback UI - this.setState({ hasError: true, errorMessage: `${error} at \n ${stack}` }); - } - - public render() { - if (this.state.hasError) { - // Render our error message; - const style: React.CSSProperties = {}; - // tslint:disable-next-line:no-string-literal - style['whiteSpace'] = 'pre'; - - return

{this.state.errorMessage}

; - } - return this.props.children; - } -} diff --git a/src/datascience-ui/react-common/event.ts b/src/datascience-ui/react-common/event.ts deleted file mode 100644 index ab2fed369556..000000000000 --- a/src/datascience-ui/react-common/event.ts +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -// tslint:disable: no-any -export type Event = (listener: (e?: T) => any) => void; - -// Simpler version of the vscode event emitter for passing down through react components. -// Easier to manage than forwarding refs when not sure what the type of the ref should be. -// -// We can't use the vscode version because pulling in vscode apis is not allowed in a webview -export class EventEmitter { - private _event: Event | undefined; - private _listeners: Set<(e?: T) => any> = new Set<(e?: T) => any>(); - - public get event(): Event { - if (!this._event) { - this._event = (listener: (e?: T) => any): void => { - this._listeners.add(listener); - }; - } - return this._event; - } - - public fire(data?: T): void { - this._listeners.forEach((c) => c(data)); - } - - public dispose(): void { - this._listeners.clear(); - } -} - -export interface IKeyboardEvent { - readonly code: string; - readonly target: HTMLElement; - readonly ctrlKey: boolean; - readonly shiftKey: boolean; - readonly altKey: boolean; - readonly metaKey: boolean; - readonly editorInfo?: { - isFirstLine: boolean; - isLastLine: boolean; - isSuggesting: boolean; - isDirty: boolean; - contents: string; - clear(): void; - }; - preventDefault(): void; - stopPropagation(): void; -} diff --git a/src/datascience-ui/react-common/flyout.css b/src/datascience-ui/react-common/flyout.css deleted file mode 100644 index 3f9ad5ce827d..000000000000 --- a/src/datascience-ui/react-common/flyout.css +++ /dev/null @@ -1,20 +0,0 @@ - -.flyout-container { - position: relative; - height: 16px; - width: auto; -} - -.flyout-inner-disabled-filter { - opacity: 0.5; -} - -.flyout-button-hidden { - visibility: hidden; -} - -.flyout-children-hidden { - visibility: hidden; - width: 0px; -} - diff --git a/src/datascience-ui/react-common/flyout.tsx b/src/datascience-ui/react-common/flyout.tsx deleted file mode 100644 index 3e3f634eddfc..000000000000 --- a/src/datascience-ui/react-common/flyout.tsx +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import './flyout.css'; - -import * as React from 'react'; - -interface IFlyoutProps { - buttonClassName: string; - flyoutContainerName: string; - buttonContent: JSX.Element; - buttonTooltip?: string; - disabled?: boolean; - hidden?: boolean; -} - -interface IFlyoutState { - visible: boolean; -} - -export class Flyout extends React.Component { - constructor(props: IFlyoutProps) { - super(props); - this.state = { visible: false }; - } - - public render() { - const innerFilter = this.props.disabled ? 'flyout-inner-disabled-filter' : ''; - const ariaDisabled = this.props.disabled ? 'true' : 'false'; - const buttonClassName = this.props.buttonClassName; - const flyoutClassName = this.state.visible - ? `flyout-children-visible ${this.props.flyoutContainerName}` - : `flyout-children-hidden ${this.props.flyoutContainerName}`; - - return ( -
- -
{this.props.children}
-
- ); - } - - private mouseEnter = () => { - this.setState({ visible: true }); - }; - - private mouseLeave = () => { - this.setState({ visible: false }); - }; -} diff --git a/src/datascience-ui/react-common/image.tsx b/src/datascience-ui/react-common/image.tsx deleted file mode 100644 index 40b09332a062..000000000000 --- a/src/datascience-ui/react-common/image.tsx +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as React from 'react'; -// tslint:disable-next-line:import-name match-default-export-name -import InlineSVG from 'svg-inline-react'; - -// This react component loads our svg files inline so that we can load them in vscode as it no longer -// supports loading svgs from disk. Please put new images in this list as appropriate. -export enum ImageName { - Cancel, - CollapseAll, - ExpandAll, - GoToSourceCode, - Interrupt, - OpenInNewWindow, - PopIn, - PopOut, - Redo, - Restart, - SaveAs, - Undo, - Pan, - Zoom, - ZoomOut, - Next, - Prev, - Copy, - GatherCode, - Up, - Down, - Run, - RunAbove, - RunBelow, - InsertAbove, - InsertBelow, - SwitchToCode, - SwitchToMarkdown, - OpenPlot, - RunAll, - Delete, - VariableExplorer, - ExportToPython, - ClearAllOutput, - JupyterServerConnected, - JupyterServerDisconnected, - Notebook, - Interactive, - Python, - PythonColor, - OpenFolder, - RunByLine, - Sync -} - -// All of the images must be 'require' so that webpack doesn't rewrite the import as requiring a .default. -// tslint:disable:no-require-imports -const images: { [key: string]: { light: string; dark: string } } = { - Cancel: { - light: require('./images/Cancel/Cancel_16xMD_vscode.svg'), - dark: require('./images/Cancel/Cancel_16xMD_vscode_dark.svg') - }, - CollapseAll: { - light: require('./images/CollapseAll/CollapseAll_16x_vscode.svg'), - dark: require('./images/CollapseAll/CollapseAll_16x_vscode_dark.svg') - }, - ExpandAll: { - light: require('./images/ExpandAll/ExpandAll_16x_vscode.svg'), - dark: require('./images/ExpandAll/ExpandAll_16x_vscode_dark.svg') - }, - GatherCode: { - light: require('./images/GatherCode/gather_light.svg'), - dark: require('./images/GatherCode/gather_dark.svg') - }, - GoToSourceCode: { - light: require('./images/GoToSourceCode/GoToSourceCode_16x_vscode.svg'), - dark: require('./images/GoToSourceCode/GoToSourceCode_16x_vscode_dark.svg') - }, - Interrupt: { - light: require('./images/Interrupt/Interrupt_16x_vscode.svg'), - dark: require('./images/Interrupt/Interrupt_16x_vscode_dark.svg') - }, - OpenInNewWindow: { - light: require('./images/OpenInNewWindow/OpenInNewWindow_16x_vscode.svg'), - dark: require('./images/OpenInNewWindow/OpenInNewWindow_16x_vscode_dark.svg') - }, - PopIn: { - light: require('./images/PopIn/PopIn_16x_vscode.svg'), - dark: require('./images/PopIn/PopIn_16x_vscode_dark.svg') - }, - PopOut: { - light: require('./images/PopOut/PopOut_16x_vscode.svg'), - dark: require('./images/PopOut/PopOut_16x_vscode_dark.svg') - }, - Redo: { - light: require('./images/Redo/Redo_16x_vscode.svg'), - dark: require('./images/Redo/Redo_16x_vscode_dark.svg') - }, - Restart: { - light: require('./images/Restart/Restart_grey_16x_vscode.svg'), - dark: require('./images/Restart/Restart_grey_16x_vscode_dark.svg') - }, - SaveAs: { - light: require('./images/SaveAs/SaveAs_16x_vscode.svg'), - dark: require('./images/SaveAs/SaveAs_16x_vscode_dark.svg') - }, - Undo: { - light: require('./images/Undo/Undo_16x_vscode.svg'), - dark: require('./images/Undo/Undo_16x_vscode_dark.svg') - }, - Next: { - light: require('./images/Next/next.svg'), - dark: require('./images/Next/next-inverse.svg') - }, - Prev: { - light: require('./images/Prev/previous.svg'), - dark: require('./images/Prev/previous-inverse.svg') - }, - // tslint:disable-next-line: no-suspicious-comment - // Todo: Get new images from a designer. These are all temporary. - Pan: { - light: require('./images/Pan/pan.svg'), - dark: require('./images/Pan/pan_inverse.svg') - }, - Zoom: { - light: require('./images/Zoom/zoom.svg'), - dark: require('./images/Zoom/zoom_inverse.svg') - }, - ZoomOut: { - light: require('./images/ZoomOut/zoomout.svg'), - dark: require('./images/ZoomOut/zoomout_inverse.svg') - }, - Copy: { - light: require('./images/Copy/copy.svg'), - dark: require('./images/Copy/copy_inverse.svg') - }, - Up: { - light: require('./images/Up/up.svg'), - dark: require('./images/Up/up-inverse.svg') - }, - Down: { - light: require('./images/Down/down.svg'), - dark: require('./images/Down/down-inverse.svg') - }, - Run: { - light: require('./images/Run/run-light.svg'), - dark: require('./images/Run/run-dark.svg') - }, - RunAbove: { - light: require('./images/RunAbove/runabove.svg'), - dark: require('./images/RunAbove/runabove-inverse.svg') - }, - RunBelow: { - light: require('./images/RunBelow/runbelow.svg'), - dark: require('./images/RunBelow/runbelow-inverse.svg') - }, - InsertAbove: { - light: require('./images/InsertAbove/above.svg'), - dark: require('./images/InsertAbove/above-inverse.svg') - }, - InsertBelow: { - light: require('./images/InsertBelow/below.svg'), - dark: require('./images/InsertBelow/below-inverse.svg') - }, - SwitchToCode: { - light: require('./images/SwitchToCode/switchtocode.svg'), - dark: require('./images/SwitchToCode/switchtocode-inverse.svg') - }, - SwitchToMarkdown: { - light: require('./images/SwitchToMarkdown/switchtomarkdown.svg'), - dark: require('./images/SwitchToMarkdown/switchtomarkdown-inverse.svg') - }, - OpenPlot: { - light: require('./images/OpenPlot/plot_light.svg'), - dark: require('./images/OpenPlot/plot_dark.svg') - }, - RunAll: { - light: require('./images/RunAll/run_all_light.svg'), - dark: require('./images/RunAll/run_all_dark.svg') - }, - Delete: { - light: require('./images/Delete/delete_light.svg'), - dark: require('./images/Delete/delete_dark.svg') - }, - VariableExplorer: { - light: require('./images/VariableExplorer/variable_explorer_light.svg'), - dark: require('./images/VariableExplorer/variable_explorer_dark.svg') - }, - ExportToPython: { - light: require('./images/ExportToPython/export_to_python_light.svg'), - dark: require('./images/ExportToPython/export_to_python_dark.svg') - }, - ClearAllOutput: { - light: require('./images/ClearAllOutput/clear_all_output_light.svg'), - dark: require('./images/ClearAllOutput/clear_all_output_dark.svg') - }, - JupyterServerConnected: { - light: require('./images/JupyterServerConnected/connected-light.svg'), - dark: require('./images/JupyterServerConnected/connected-dark.svg') - }, - JupyterServerDisconnected: { - light: require('./images/JupyterServerDisconnected/disconnected-light.svg'), - dark: require('./images/JupyterServerDisconnected/disconnected-dark.svg') - }, - Notebook: { - light: require('./images/StartPage/Notebook.svg'), - dark: require('./images/StartPage/Notebook-inverse.svg') - }, - Interactive: { - light: require('./images/StartPage/Interactive.svg'), - dark: require('./images/StartPage/Interactive-inverse.svg') - }, - Python: { - light: require('./images/StartPage/Python.svg'), - dark: require('./images/StartPage/Python-inverse.svg') - }, - PythonColor: { - light: require('./images/StartPage/Python-color.svg'), - dark: require('./images/StartPage/Python-color.svg') - }, - OpenFolder: { - light: require('./images/StartPage/OpenFolder.svg'), - dark: require('./images/StartPage/OpenFolder-inverse.svg') - }, - RunByLine: { - light: require('./images/RunByLine/runbyline_light.svg'), - dark: require('./images/RunByLine/runbyline_dark.svg') - }, - Sync: { - light: require('./images/Sync/sync.svg'), - dark: require('./images/Sync/sync-inverse.svg') - } -}; - -interface IImageProps { - baseTheme: string; - image: ImageName; - class: string; - title?: string; -} - -export class Image extends React.Component { - constructor(props: IImageProps) { - super(props); - } - - public render() { - const key = ImageName[this.props.image].toString(); - const image = images.hasOwnProperty(key) ? images[key] : images.Cancel; // Default is cancel. - const source = this.props.baseTheme.includes('dark') ? image.dark : image.light; - return ; - } -} diff --git a/src/datascience-ui/react-common/imageButton.css b/src/datascience-ui/react-common/imageButton.css deleted file mode 100644 index 1b47cd8b6d8e..000000000000 --- a/src/datascience-ui/react-common/imageButton.css +++ /dev/null @@ -1,50 +0,0 @@ -:root { - --button-size: 18px; -} - -.image-button { - border-width: 0px; - border-style: solid; - text-align: center; - line-height: 16px; - overflow: hidden; - width: var(--button-size); - height: var(--button-size); - margin-left: 2px; - padding: 1px; - background-color: transparent; - cursor: hand; -} - -.image-button-empty { - visibility: hidden; -} - -.image-button-inner-disabled-filter { - opacity: 0.5; -} - -.image-button-child { - max-width: 100%; - max-height: 100%; -} - -.image-button-child img{ - max-width: 100%; - max-height: 100%; -} - -.image-button-image svg{ - pointer-events: none; -} - -.image-button-vscode-light:disabled { - border-color: gray; - filter: grayscale(100%); -} - -.image-button-vscode-dark:disabled { - border-color: gray; - filter: grayscale(100%); -} - diff --git a/src/datascience-ui/react-common/imageButton.tsx b/src/datascience-ui/react-common/imageButton.tsx deleted file mode 100644 index 6105b49ec693..000000000000 --- a/src/datascience-ui/react-common/imageButton.tsx +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; -import * as React from 'react'; -import './imageButton.css'; - -interface IImageButtonProps { - baseTheme: string; - tooltip: string; - disabled?: boolean; - hidden?: boolean; - className?: string; - onClick?(event?: React.MouseEvent): void; - onMouseDown?(event?: React.MouseEvent): void; -} - -export class ImageButton extends React.Component { - constructor(props: IImageButtonProps) { - super(props); - } - - public render() { - const classNames = `image-button image-button-${this.props.baseTheme} ${this.props.hidden ? 'hide' : ''} ${ - this.props.className ? this.props.className : '' - }`; - const innerFilter = this.props.disabled ? 'image-button-inner-disabled-filter' : ''; - const ariaDisabled = this.props.disabled ? 'true' : 'false'; - - return ( - - ); - } -} diff --git a/src/datascience-ui/react-common/images/Cancel/Cancel_16xMD_vscode.svg b/src/datascience-ui/react-common/images/Cancel/Cancel_16xMD_vscode.svg deleted file mode 100644 index 6cf0d7996f55..000000000000 --- a/src/datascience-ui/react-common/images/Cancel/Cancel_16xMD_vscode.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/Cancel/Cancel_16xMD_vscode_dark.svg b/src/datascience-ui/react-common/images/Cancel/Cancel_16xMD_vscode_dark.svg deleted file mode 100644 index c4df2f91fee2..000000000000 --- a/src/datascience-ui/react-common/images/Cancel/Cancel_16xMD_vscode_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/ClearAllOutput/clear_all_output_dark.svg b/src/datascience-ui/react-common/images/ClearAllOutput/clear_all_output_dark.svg deleted file mode 100644 index f8811f5ea310..000000000000 --- a/src/datascience-ui/react-common/images/ClearAllOutput/clear_all_output_dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/datascience-ui/react-common/images/ClearAllOutput/clear_all_output_light.svg b/src/datascience-ui/react-common/images/ClearAllOutput/clear_all_output_light.svg deleted file mode 100644 index 2c2015b6cb27..000000000000 --- a/src/datascience-ui/react-common/images/ClearAllOutput/clear_all_output_light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/datascience-ui/react-common/images/CollapseAll/CollapseAll_16x_vscode.svg b/src/datascience-ui/react-common/images/CollapseAll/CollapseAll_16x_vscode.svg deleted file mode 100644 index ffddd4cbdb95..000000000000 --- a/src/datascience-ui/react-common/images/CollapseAll/CollapseAll_16x_vscode.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/datascience-ui/react-common/images/CollapseAll/CollapseAll_16x_vscode_dark.svg b/src/datascience-ui/react-common/images/CollapseAll/CollapseAll_16x_vscode_dark.svg deleted file mode 100644 index 11e1f39ad968..000000000000 --- a/src/datascience-ui/react-common/images/CollapseAll/CollapseAll_16x_vscode_dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/datascience-ui/react-common/images/Copy/copy.svg b/src/datascience-ui/react-common/images/Copy/copy.svg deleted file mode 100644 index c8d97a3e23be..000000000000 --- a/src/datascience-ui/react-common/images/Copy/copy.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/Copy/copy_inverse.svg b/src/datascience-ui/react-common/images/Copy/copy_inverse.svg deleted file mode 100644 index b5d2606adb65..000000000000 --- a/src/datascience-ui/react-common/images/Copy/copy_inverse.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/Delete/delete_dark.svg b/src/datascience-ui/react-common/images/Delete/delete_dark.svg deleted file mode 100644 index 909517562dfb..000000000000 --- a/src/datascience-ui/react-common/images/Delete/delete_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/Delete/delete_light.svg b/src/datascience-ui/react-common/images/Delete/delete_light.svg deleted file mode 100644 index 708df4baba32..000000000000 --- a/src/datascience-ui/react-common/images/Delete/delete_light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/Down/down-inverse.svg b/src/datascience-ui/react-common/images/Down/down-inverse.svg deleted file mode 100644 index d3015672bd49..000000000000 --- a/src/datascience-ui/react-common/images/Down/down-inverse.svg +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/src/datascience-ui/react-common/images/Down/down.svg b/src/datascience-ui/react-common/images/Down/down.svg deleted file mode 100644 index 30d5faa11be2..000000000000 --- a/src/datascience-ui/react-common/images/Down/down.svg +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/src/datascience-ui/react-common/images/ExpandAll/ExpandAll_16x_vscode.svg b/src/datascience-ui/react-common/images/ExpandAll/ExpandAll_16x_vscode.svg deleted file mode 100644 index d82b92292bee..000000000000 --- a/src/datascience-ui/react-common/images/ExpandAll/ExpandAll_16x_vscode.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/datascience-ui/react-common/images/ExpandAll/ExpandAll_16x_vscode_dark.svg b/src/datascience-ui/react-common/images/ExpandAll/ExpandAll_16x_vscode_dark.svg deleted file mode 100644 index 8db4b6139c80..000000000000 --- a/src/datascience-ui/react-common/images/ExpandAll/ExpandAll_16x_vscode_dark.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/datascience-ui/react-common/images/ExportToPython/export_to_python_dark.svg b/src/datascience-ui/react-common/images/ExportToPython/export_to_python_dark.svg deleted file mode 100644 index a68ca2942cb7..000000000000 --- a/src/datascience-ui/react-common/images/ExportToPython/export_to_python_dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/datascience-ui/react-common/images/ExportToPython/export_to_python_light.svg b/src/datascience-ui/react-common/images/ExportToPython/export_to_python_light.svg deleted file mode 100644 index 873383aaeb21..000000000000 --- a/src/datascience-ui/react-common/images/ExportToPython/export_to_python_light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/datascience-ui/react-common/images/GatherCode/gather_dark.svg b/src/datascience-ui/react-common/images/GatherCode/gather_dark.svg deleted file mode 100644 index 135fc3c97683..000000000000 --- a/src/datascience-ui/react-common/images/GatherCode/gather_dark.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - GatherIcon_16x - - - - - - - diff --git a/src/datascience-ui/react-common/images/GatherCode/gather_light.svg b/src/datascience-ui/react-common/images/GatherCode/gather_light.svg deleted file mode 100644 index 72ec63e06b65..000000000000 --- a/src/datascience-ui/react-common/images/GatherCode/gather_light.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - GatherIcon_16x - - - - - - - - diff --git a/src/datascience-ui/react-common/images/GoToSourceCode/GoToSourceCode_16x_vscode.svg b/src/datascience-ui/react-common/images/GoToSourceCode/GoToSourceCode_16x_vscode.svg deleted file mode 100644 index d4bca23d4a0e..000000000000 --- a/src/datascience-ui/react-common/images/GoToSourceCode/GoToSourceCode_16x_vscode.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/GoToSourceCode/GoToSourceCode_16x_vscode_dark.svg b/src/datascience-ui/react-common/images/GoToSourceCode/GoToSourceCode_16x_vscode_dark.svg deleted file mode 100644 index b54d042ca46d..000000000000 --- a/src/datascience-ui/react-common/images/GoToSourceCode/GoToSourceCode_16x_vscode_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/InsertAbove/above-inverse.svg b/src/datascience-ui/react-common/images/InsertAbove/above-inverse.svg deleted file mode 100644 index 6b36517f6efa..000000000000 --- a/src/datascience-ui/react-common/images/InsertAbove/above-inverse.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/InsertAbove/above.svg b/src/datascience-ui/react-common/images/InsertAbove/above.svg deleted file mode 100644 index cc0ee1bbd824..000000000000 --- a/src/datascience-ui/react-common/images/InsertAbove/above.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/InsertBelow/below-inverse.svg b/src/datascience-ui/react-common/images/InsertBelow/below-inverse.svg deleted file mode 100644 index 6b36517f6efa..000000000000 --- a/src/datascience-ui/react-common/images/InsertBelow/below-inverse.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/InsertBelow/below.svg b/src/datascience-ui/react-common/images/InsertBelow/below.svg deleted file mode 100644 index cc0ee1bbd824..000000000000 --- a/src/datascience-ui/react-common/images/InsertBelow/below.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/Interrupt/Interrupt_16x_vscode.svg b/src/datascience-ui/react-common/images/Interrupt/Interrupt_16x_vscode.svg deleted file mode 100644 index 391089006ec5..000000000000 --- a/src/datascience-ui/react-common/images/Interrupt/Interrupt_16x_vscode.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/Interrupt/Interrupt_16x_vscode_dark.svg b/src/datascience-ui/react-common/images/Interrupt/Interrupt_16x_vscode_dark.svg deleted file mode 100644 index 391089006ec5..000000000000 --- a/src/datascience-ui/react-common/images/Interrupt/Interrupt_16x_vscode_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/JupyterServerConnected/connected-dark.svg b/src/datascience-ui/react-common/images/JupyterServerConnected/connected-dark.svg deleted file mode 100644 index f44818031c24..000000000000 --- a/src/datascience-ui/react-common/images/JupyterServerConnected/connected-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/JupyterServerConnected/connected-hc.svg b/src/datascience-ui/react-common/images/JupyterServerConnected/connected-hc.svg deleted file mode 100644 index f44818031c24..000000000000 --- a/src/datascience-ui/react-common/images/JupyterServerConnected/connected-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/JupyterServerConnected/connected-light.svg b/src/datascience-ui/react-common/images/JupyterServerConnected/connected-light.svg deleted file mode 100644 index 28987e1c1723..000000000000 --- a/src/datascience-ui/react-common/images/JupyterServerConnected/connected-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/JupyterServerDisconnected/disconnected-dark.svg b/src/datascience-ui/react-common/images/JupyterServerDisconnected/disconnected-dark.svg deleted file mode 100644 index 02a598dc9878..000000000000 --- a/src/datascience-ui/react-common/images/JupyterServerDisconnected/disconnected-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/JupyterServerDisconnected/disconnected-hc.svg b/src/datascience-ui/react-common/images/JupyterServerDisconnected/disconnected-hc.svg deleted file mode 100644 index 02a598dc9878..000000000000 --- a/src/datascience-ui/react-common/images/JupyterServerDisconnected/disconnected-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/JupyterServerDisconnected/disconnected-light.svg b/src/datascience-ui/react-common/images/JupyterServerDisconnected/disconnected-light.svg deleted file mode 100644 index 107b8e22f7c3..000000000000 --- a/src/datascience-ui/react-common/images/JupyterServerDisconnected/disconnected-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/Next/next-inverse.svg b/src/datascience-ui/react-common/images/Next/next-inverse.svg deleted file mode 100644 index d1c3e8dbd956..000000000000 --- a/src/datascience-ui/react-common/images/Next/next-inverse.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/Next/next.svg b/src/datascience-ui/react-common/images/Next/next.svg deleted file mode 100644 index 465903659681..000000000000 --- a/src/datascience-ui/react-common/images/Next/next.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/OpenInNewWindow/OpenInNewWindow_16x_vscode.svg b/src/datascience-ui/react-common/images/OpenInNewWindow/OpenInNewWindow_16x_vscode.svg deleted file mode 100644 index d50b0367205a..000000000000 --- a/src/datascience-ui/react-common/images/OpenInNewWindow/OpenInNewWindow_16x_vscode.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/OpenInNewWindow/OpenInNewWindow_16x_vscode_dark.svg b/src/datascience-ui/react-common/images/OpenInNewWindow/OpenInNewWindow_16x_vscode_dark.svg deleted file mode 100644 index 93a0af00cad6..000000000000 --- a/src/datascience-ui/react-common/images/OpenInNewWindow/OpenInNewWindow_16x_vscode_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/OpenPlot/plot_dark.svg b/src/datascience-ui/react-common/images/OpenPlot/plot_dark.svg deleted file mode 100644 index 8ac26cda0805..000000000000 --- a/src/datascience-ui/react-common/images/OpenPlot/plot_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/OpenPlot/plot_light.svg b/src/datascience-ui/react-common/images/OpenPlot/plot_light.svg deleted file mode 100644 index 67600ef32a6e..000000000000 --- a/src/datascience-ui/react-common/images/OpenPlot/plot_light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/Pan/pan.svg b/src/datascience-ui/react-common/images/Pan/pan.svg deleted file mode 100644 index 25f33c9e5b4b..000000000000 --- a/src/datascience-ui/react-common/images/Pan/pan.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/Pan/pan_inverse.svg b/src/datascience-ui/react-common/images/Pan/pan_inverse.svg deleted file mode 100644 index 75e2f52f6c4f..000000000000 --- a/src/datascience-ui/react-common/images/Pan/pan_inverse.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/PopIn/PopIn_16x_vscode.svg b/src/datascience-ui/react-common/images/PopIn/PopIn_16x_vscode.svg deleted file mode 100644 index 906f8244eb8b..000000000000 --- a/src/datascience-ui/react-common/images/PopIn/PopIn_16x_vscode.svg +++ /dev/null @@ -1 +0,0 @@ -PopIn_16x \ No newline at end of file diff --git a/src/datascience-ui/react-common/images/PopIn/PopIn_16x_vscode_dark.svg b/src/datascience-ui/react-common/images/PopIn/PopIn_16x_vscode_dark.svg deleted file mode 100644 index 041b6e015305..000000000000 --- a/src/datascience-ui/react-common/images/PopIn/PopIn_16x_vscode_dark.svg +++ /dev/null @@ -1 +0,0 @@ -PopIn_16x \ No newline at end of file diff --git a/src/datascience-ui/react-common/images/PopOut/PopOut_16x_vscode.svg b/src/datascience-ui/react-common/images/PopOut/PopOut_16x_vscode.svg deleted file mode 100644 index e979568ea576..000000000000 --- a/src/datascience-ui/react-common/images/PopOut/PopOut_16x_vscode.svg +++ /dev/null @@ -1 +0,0 @@ -PopOut_16x \ No newline at end of file diff --git a/src/datascience-ui/react-common/images/PopOut/PopOut_16x_vscode_dark.svg b/src/datascience-ui/react-common/images/PopOut/PopOut_16x_vscode_dark.svg deleted file mode 100644 index a72e56c2dc70..000000000000 --- a/src/datascience-ui/react-common/images/PopOut/PopOut_16x_vscode_dark.svg +++ /dev/null @@ -1 +0,0 @@ -PopOut_16x \ No newline at end of file diff --git a/src/datascience-ui/react-common/images/Prev/previous-inverse.svg b/src/datascience-ui/react-common/images/Prev/previous-inverse.svg deleted file mode 100644 index d779fba43c47..000000000000 --- a/src/datascience-ui/react-common/images/Prev/previous-inverse.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/Prev/previous.svg b/src/datascience-ui/react-common/images/Prev/previous.svg deleted file mode 100644 index 730afafcfd32..000000000000 --- a/src/datascience-ui/react-common/images/Prev/previous.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/Redo/Redo_16x_vscode.svg b/src/datascience-ui/react-common/images/Redo/Redo_16x_vscode.svg deleted file mode 100644 index 3e537188666c..000000000000 --- a/src/datascience-ui/react-common/images/Redo/Redo_16x_vscode.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/Redo/Redo_16x_vscode_dark.svg b/src/datascience-ui/react-common/images/Redo/Redo_16x_vscode_dark.svg deleted file mode 100644 index 5c1c7c98bc5c..000000000000 --- a/src/datascience-ui/react-common/images/Redo/Redo_16x_vscode_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/Restart/Restart_grey_16x_vscode.svg b/src/datascience-ui/react-common/images/Restart/Restart_grey_16x_vscode.svg deleted file mode 100644 index 4ce884e15bbe..000000000000 --- a/src/datascience-ui/react-common/images/Restart/Restart_grey_16x_vscode.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/Restart/Restart_grey_16x_vscode_dark.svg b/src/datascience-ui/react-common/images/Restart/Restart_grey_16x_vscode_dark.svg deleted file mode 100644 index 720bc53df88c..000000000000 --- a/src/datascience-ui/react-common/images/Restart/Restart_grey_16x_vscode_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/Run/run-dark.svg b/src/datascience-ui/react-common/images/Run/run-dark.svg deleted file mode 100644 index 345289f1e577..000000000000 --- a/src/datascience-ui/react-common/images/Run/run-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/Run/run-light.svg b/src/datascience-ui/react-common/images/Run/run-light.svg deleted file mode 100644 index ef2819326598..000000000000 --- a/src/datascience-ui/react-common/images/Run/run-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/RunAbove/runabove-inverse.svg b/src/datascience-ui/react-common/images/RunAbove/runabove-inverse.svg deleted file mode 100644 index d8f93fb68a32..000000000000 --- a/src/datascience-ui/react-common/images/RunAbove/runabove-inverse.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - diff --git a/src/datascience-ui/react-common/images/RunAbove/runabove.svg b/src/datascience-ui/react-common/images/RunAbove/runabove.svg deleted file mode 100644 index 8b5ccb9ee047..000000000000 --- a/src/datascience-ui/react-common/images/RunAbove/runabove.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - diff --git a/src/datascience-ui/react-common/images/RunAll/run_all_dark.svg b/src/datascience-ui/react-common/images/RunAll/run_all_dark.svg deleted file mode 100644 index e9497b351ed4..000000000000 --- a/src/datascience-ui/react-common/images/RunAll/run_all_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/RunAll/run_all_light.svg b/src/datascience-ui/react-common/images/RunAll/run_all_light.svg deleted file mode 100644 index 36af4345aa03..000000000000 --- a/src/datascience-ui/react-common/images/RunAll/run_all_light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/RunBelow/runbelow-inverse.svg b/src/datascience-ui/react-common/images/RunBelow/runbelow-inverse.svg deleted file mode 100644 index 1b820cd29fb9..000000000000 --- a/src/datascience-ui/react-common/images/RunBelow/runbelow-inverse.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - diff --git a/src/datascience-ui/react-common/images/RunBelow/runbelow.svg b/src/datascience-ui/react-common/images/RunBelow/runbelow.svg deleted file mode 100644 index 38730888f13a..000000000000 --- a/src/datascience-ui/react-common/images/RunBelow/runbelow.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - diff --git a/src/datascience-ui/react-common/images/RunByLine/runbyline_dark.svg b/src/datascience-ui/react-common/images/RunByLine/runbyline_dark.svg deleted file mode 100644 index 5193f5d4902e..000000000000 --- a/src/datascience-ui/react-common/images/RunByLine/runbyline_dark.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - RunByLine_16x - - - - - - - - - - \ No newline at end of file diff --git a/src/datascience-ui/react-common/images/RunByLine/runbyline_light.svg b/src/datascience-ui/react-common/images/RunByLine/runbyline_light.svg deleted file mode 100644 index 2a18ee6ac53a..000000000000 --- a/src/datascience-ui/react-common/images/RunByLine/runbyline_light.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - RunByLine_16x - - - - - - - - - - \ No newline at end of file diff --git a/src/datascience-ui/react-common/images/SaveAs/SaveAs_16x_vscode.svg b/src/datascience-ui/react-common/images/SaveAs/SaveAs_16x_vscode.svg deleted file mode 100644 index 2b461e5c7568..000000000000 --- a/src/datascience-ui/react-common/images/SaveAs/SaveAs_16x_vscode.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/SaveAs/SaveAs_16x_vscode_dark.svg b/src/datascience-ui/react-common/images/SaveAs/SaveAs_16x_vscode_dark.svg deleted file mode 100644 index 865c8fc584cb..000000000000 --- a/src/datascience-ui/react-common/images/SaveAs/SaveAs_16x_vscode_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/StartPage/Interactive-inverse.svg b/src/datascience-ui/react-common/images/StartPage/Interactive-inverse.svg deleted file mode 100644 index 87819244977e..000000000000 --- a/src/datascience-ui/react-common/images/StartPage/Interactive-inverse.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/StartPage/Interactive.svg b/src/datascience-ui/react-common/images/StartPage/Interactive.svg deleted file mode 100644 index 6731d50fee39..000000000000 --- a/src/datascience-ui/react-common/images/StartPage/Interactive.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/StartPage/Notebook-inverse.svg b/src/datascience-ui/react-common/images/StartPage/Notebook-inverse.svg deleted file mode 100644 index 12b217d48fb8..000000000000 --- a/src/datascience-ui/react-common/images/StartPage/Notebook-inverse.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/StartPage/Notebook.svg b/src/datascience-ui/react-common/images/StartPage/Notebook.svg deleted file mode 100644 index 5e058afb30eb..000000000000 --- a/src/datascience-ui/react-common/images/StartPage/Notebook.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/StartPage/OpenFolder-inverse.svg b/src/datascience-ui/react-common/images/StartPage/OpenFolder-inverse.svg deleted file mode 100644 index e3d0f3e933e7..000000000000 --- a/src/datascience-ui/react-common/images/StartPage/OpenFolder-inverse.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/StartPage/OpenFolder.svg b/src/datascience-ui/react-common/images/StartPage/OpenFolder.svg deleted file mode 100644 index 309858c9f469..000000000000 --- a/src/datascience-ui/react-common/images/StartPage/OpenFolder.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/StartPage/Python-color.svg b/src/datascience-ui/react-common/images/StartPage/Python-color.svg deleted file mode 100644 index e9b909eab46b..000000000000 --- a/src/datascience-ui/react-common/images/StartPage/Python-color.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/datascience-ui/react-common/images/StartPage/Python-inverse.svg b/src/datascience-ui/react-common/images/StartPage/Python-inverse.svg deleted file mode 100644 index 73708a57db29..000000000000 --- a/src/datascience-ui/react-common/images/StartPage/Python-inverse.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/datascience-ui/react-common/images/StartPage/Python.svg b/src/datascience-ui/react-common/images/StartPage/Python.svg deleted file mode 100644 index 9a4d621e91c0..000000000000 --- a/src/datascience-ui/react-common/images/StartPage/Python.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/datascience-ui/react-common/images/SwitchToCode/switchtocode-inverse.svg b/src/datascience-ui/react-common/images/SwitchToCode/switchtocode-inverse.svg deleted file mode 100644 index 3b93100ffac3..000000000000 --- a/src/datascience-ui/react-common/images/SwitchToCode/switchtocode-inverse.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/SwitchToCode/switchtocode.svg b/src/datascience-ui/react-common/images/SwitchToCode/switchtocode.svg deleted file mode 100644 index bc21caf9f4b6..000000000000 --- a/src/datascience-ui/react-common/images/SwitchToCode/switchtocode.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/SwitchToMarkdown/switchtomarkdown-inverse.svg b/src/datascience-ui/react-common/images/SwitchToMarkdown/switchtomarkdown-inverse.svg deleted file mode 100644 index 388652f13b4c..000000000000 --- a/src/datascience-ui/react-common/images/SwitchToMarkdown/switchtomarkdown-inverse.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/SwitchToMarkdown/switchtomarkdown.svg b/src/datascience-ui/react-common/images/SwitchToMarkdown/switchtomarkdown.svg deleted file mode 100644 index 34347fe785d1..000000000000 --- a/src/datascience-ui/react-common/images/SwitchToMarkdown/switchtomarkdown.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/Sync/sync-inverse.svg b/src/datascience-ui/react-common/images/Sync/sync-inverse.svg deleted file mode 100644 index 7406dc99d8e9..000000000000 --- a/src/datascience-ui/react-common/images/Sync/sync-inverse.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/Sync/sync.svg b/src/datascience-ui/react-common/images/Sync/sync.svg deleted file mode 100644 index 74a4f483e00c..000000000000 --- a/src/datascience-ui/react-common/images/Sync/sync.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/Undo/Undo_16x_vscode.svg b/src/datascience-ui/react-common/images/Undo/Undo_16x_vscode.svg deleted file mode 100644 index 670fc2eb3404..000000000000 --- a/src/datascience-ui/react-common/images/Undo/Undo_16x_vscode.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/Undo/Undo_16x_vscode_dark.svg b/src/datascience-ui/react-common/images/Undo/Undo_16x_vscode_dark.svg deleted file mode 100644 index 4db7708d1b13..000000000000 --- a/src/datascience-ui/react-common/images/Undo/Undo_16x_vscode_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/Up/up-inverse.svg b/src/datascience-ui/react-common/images/Up/up-inverse.svg deleted file mode 100644 index 72f710713430..000000000000 --- a/src/datascience-ui/react-common/images/Up/up-inverse.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/Up/up.svg b/src/datascience-ui/react-common/images/Up/up.svg deleted file mode 100644 index ff5354d2977a..000000000000 --- a/src/datascience-ui/react-common/images/Up/up.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/VariableExplorer/variable_explorer_dark.svg b/src/datascience-ui/react-common/images/VariableExplorer/variable_explorer_dark.svg deleted file mode 100644 index 4deff429c333..000000000000 --- a/src/datascience-ui/react-common/images/VariableExplorer/variable_explorer_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/VariableExplorer/variable_explorer_light.svg b/src/datascience-ui/react-common/images/VariableExplorer/variable_explorer_light.svg deleted file mode 100644 index 6fdbb1675a37..000000000000 --- a/src/datascience-ui/react-common/images/VariableExplorer/variable_explorer_light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/datascience-ui/react-common/images/Zoom/zoom.svg b/src/datascience-ui/react-common/images/Zoom/zoom.svg deleted file mode 100644 index 213e6224adb6..000000000000 --- a/src/datascience-ui/react-common/images/Zoom/zoom.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/datascience-ui/react-common/images/Zoom/zoom_inverse.svg b/src/datascience-ui/react-common/images/Zoom/zoom_inverse.svg deleted file mode 100644 index 8cecde9b3b9d..000000000000 --- a/src/datascience-ui/react-common/images/Zoom/zoom_inverse.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/datascience-ui/react-common/images/ZoomOut/zoomout.svg b/src/datascience-ui/react-common/images/ZoomOut/zoomout.svg deleted file mode 100644 index 009bb8b89e0e..000000000000 --- a/src/datascience-ui/react-common/images/ZoomOut/zoomout.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/datascience-ui/react-common/images/ZoomOut/zoomout_inverse.svg b/src/datascience-ui/react-common/images/ZoomOut/zoomout_inverse.svg deleted file mode 100644 index a2e887d89c74..000000000000 --- a/src/datascience-ui/react-common/images/ZoomOut/zoomout_inverse.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/datascience-ui/react-common/locReactSide.ts b/src/datascience-ui/react-common/locReactSide.ts deleted file mode 100644 index dec1f5fa6946..000000000000 --- a/src/datascience-ui/react-common/locReactSide.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; -// The react code can't use the localize.ts module because it reads from -// disk. This isn't allowed inside a browser, so we pass the collection -// through the javascript. -let loadedCollection: Record | undefined; - -export function getLocString(key: string, defValue: string): string { - if (loadedCollection && loadedCollection.hasOwnProperty(key)) { - return loadedCollection[key]; - } - - return defValue; -} - -export function storeLocStrings(collection: Record) { - loadedCollection = collection; -} diff --git a/src/datascience-ui/react-common/logger.ts b/src/datascience-ui/react-common/logger.ts deleted file mode 100644 index b062fac823ff..000000000000 --- a/src/datascience-ui/react-common/logger.ts +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { isTestExecution } from '../../client/common/constants'; - -const enableLogger = !isTestExecution() || process.env.VSC_PYTHON_FORCE_LOGGING || process.env.VSC_PYTHON_LOG_FILE; - -// Might want to post this back to the other side too. This was -export function logMessage(message: string) { - // put here to prevent having to disable the console log warning - if (enableLogger) { - // tslint:disable-next-line: no-console - console.log(message); - } -} diff --git a/src/datascience-ui/react-common/monacoEditor.css b/src/datascience-ui/react-common/monacoEditor.css deleted file mode 100644 index eb52202d2c7e..000000000000 --- a/src/datascience-ui/react-common/monacoEditor.css +++ /dev/null @@ -1,123 +0,0 @@ -.measure-width-div { - width: 95vw; - visibility: hidden; - position: absolute; -} - -.monaco-editor-outer-container .mtk1 { - /* For some reason the monaco editor refuses to update this style no matter the theme. It's always black */ - color: var(--override-foreground, var(--vscode-editor-foreground)); -} - -.monaco-editor .mtk1 { - /* For some reason the monaco editor refuses to update this style no matter the theme. It's always black */ - color: var(--override-foreground, var(--vscode-editor-foreground)); -} - -/* Bunch of styles copied from vscode. Handles the hover window */ - - .monaco-editor-hover { - cursor: default; - position: absolute; - overflow: hidden; - z-index: 50; - -webkit-user-select: text; - -ms-user-select: text; - -khtml-user-select: text; - -moz-user-select: text; - -o-user-select: text; - user-select: text; - box-sizing: initial; - animation: fadein 100ms linear; - line-height: 1.5em; -} - -.monaco-editor-hover.hidden { - display: none; -} - -.monaco-editor-hover .hover-contents { - padding: 4px 8px; -} - -.monaco-editor-hover .markdown-hover > .hover-contents:not(.code-hover-contents) { - max-width: 500px; -} - -.monaco-editor-hover p, -.monaco-editor-hover ul { - margin: 8px 0; -} - -.monaco-editor-hover hr { - margin-top: 4px; - margin-bottom: -6px; - margin-left: -10px; - margin-right: -10px; - height: 1px; -} - -.monaco-editor-hover p:first-child, -.monaco-editor-hover ul:first-child { - margin-top: 0; -} - -.monaco-editor-hover p:last-child, -.monaco-editor-hover ul:last-child { - margin-bottom: 0; -} - -.monaco-editor-hover ul { - padding-left: 20px; -} - -.monaco-editor-hover li > p { - margin-bottom: 0; -} - -.monaco-editor-hover li > ul { - margin-top: 0; -} - -.monaco-editor-hover code { - border-radius: 3px; - padding: 0 0.4em; -} - -.monaco-editor-hover .monaco-tokenized-source { - white-space: pre-wrap; - word-break: break-all; -} - -.monaco-editor-hover .hover-row.status-bar { - font-size: 12px; - line-height: 22px; -} - -.monaco-editor-hover .hover-row.status-bar .actions { - display: flex; -} - -.monaco-editor-hover .hover-row.status-bar .actions .action-container { - margin: 0px 8px; - cursor: pointer; -} - -.monaco-editor-hover .hover-row.status-bar .actions .action-container .action .icon { - padding-right: 4px; -} - -.monaco-editor .margin { - background-color: transparent !important; -} - -.monaco-editor .parameter-hints-widget > .wrapper { - overflow: hidden; -} - -.monaco-editor .view-overlays .debug-top-stack-frame-line { background: var(--vscode-editor-stackFrameHighlightBackground); } - -.codicon-debug-stackframe-active:before { content: "\eb89" } -.codicon-debug-stackframe-dot:before { content: "\eb8a" } -.codicon-debug-stackframe:before { content: "\eb8b"; color: var(--vscode-debugIcon-breakpointCurrentStackframeForeground); } -.codicon-debug-stackframe-focused:before { content: "\eb8b" } diff --git a/src/datascience-ui/react-common/monacoEditor.tsx b/src/datascience-ui/react-common/monacoEditor.tsx deleted file mode 100644 index cc1136d77df5..000000000000 --- a/src/datascience-ui/react-common/monacoEditor.tsx +++ /dev/null @@ -1,1049 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import * as fastDeepEqual from 'fast-deep-equal'; -import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; -import * as React from 'react'; -import { isTestExecution } from '../../client/common/constants'; -import { IDisposable } from '../../client/common/types'; -import { logMessage } from './logger'; - -// tslint:disable-next-line:no-require-imports no-var-requires -const debounce = require('lodash/debounce') as typeof import('lodash/debounce'); - -// See the discussion here: https://github.com/Microsoft/tslint-microsoft-contrib/issues/676 -// tslint:disable: react-this-binding-issue -// tslint:disable-next-line:no-require-imports no-var-requires -const throttle = require('lodash/throttle') as typeof import('lodash/throttle'); - -import { noop } from '../../client/common/utils/misc'; -import { CursorPos } from '../interactive-common/mainState'; -import './codicon/codicon.css'; -import './monacoEditor.css'; -import { generateChangeEvent, IMonacoModelContentChangeEvent } from './monacoHelpers'; - -const LINE_HEIGHT = 18; - -const HOVER_DISPOSABLE_EVENT_COUNT = 8; -const HOVER_DISPOSABLE_LEAVE_INDEX = 5; - -enum WidgetCSSSelector { - /** - * CSS Selector for the parameters widget displayed by Monaco. - */ - Parameters = '.parameter-hints-widget', - /** - * CSS Selector for the hover widget displayed by Monaco. - */ - Hover = '.monaco-editor-hover' -} - -export interface IMonacoEditorProps { - language: string; - value: string; - theme?: string; - outermostParentClass: string; - options: monacoEditor.editor.IEditorConstructionOptions; - testMode?: boolean; - forceBackground?: string; - measureWidthClassName?: string; - version: number; - hasFocus: boolean; - cursorPos: CursorPos | monacoEditor.IPosition; - modelChanged(e: IMonacoModelContentChangeEvent): void; - editorMounted(editor: monacoEditor.editor.IStandaloneCodeEditor): void; - openLink(uri: monacoEditor.Uri): void; -} - -export interface IMonacoEditorState { - editor?: monacoEditor.editor.IStandaloneCodeEditor; - model: monacoEditor.editor.ITextModel | null; - widgetsReparented: boolean; // Keeps track of when we reparent the hover widgets so they work inside something with overflow -} - -// Need this to prevent wiping of the current value on a componentUpdate. react-monaco-editor has that problem. - -export class MonacoEditor extends React.Component { - private static lineHeight: number = 0; - private containerRef: React.RefObject; - private measureWidthRef: React.RefObject; - private resizeTimer?: number; - private leaveTimer?: number; - private subscriptions: monacoEditor.IDisposable[] = []; - private widgetParent: HTMLDivElement | undefined; - private outermostParent: HTMLElement | null = null; - private enteredHover: boolean = false; - private lastOffsetLeft: number | undefined; - private lastOffsetTop: number | undefined; - private debouncedUpdateEditorSize: () => void | undefined; - private styleObserver: MutationObserver | undefined; - private watchingMargin: boolean = false; - private throttledUpdateWidgetPosition = throttle(this.updateWidgetPosition.bind(this), 100); - private throttledScrollCurrentPosition = throttle(this.tryToScrollToCurrentPosition.bind(this), 100); - private monacoContainer: HTMLDivElement | undefined; - private lineTops: { top: number; index: number }[] = []; - private debouncedComputeLineTops = debounce(this.computeLineTops.bind(this), 100); - private skipNotifications = false; - private previousModelValue: string = ''; - /** - * Reference to parameter widget (used by monaco to display parameter docs). - */ - private parameterWidget?: Element; - private keyHasBeenPressed = false; - private visibleLineCount: number = -1; - private attached: boolean = false; // Keeps track of when we reparent the editor out of the dummy dom node. - private pendingLayoutScroll = false; - private lastPasteCommandTime = 0; - private lastPasteCommandText = ''; - - constructor(props: IMonacoEditorProps) { - super(props); - this.state = { - editor: undefined, - model: null, - widgetsReparented: false - }; - this.containerRef = React.createRef(); - this.measureWidthRef = React.createRef(); - this.debouncedUpdateEditorSize = debounce(this.updateEditorSize.bind(this), 150); - this.hideAllOtherHoverAndParameterWidgets = debounce(this.hideAllOtherHoverAndParameterWidgets.bind(this), 150); - } - - // tslint:disable-next-line: max-func-body-length - public componentDidMount = () => { - if (window) { - window.addEventListener('resize', this.windowResized); - } - if (this.containerRef.current) { - // Compute our outermost parent - let outerParent = this.containerRef.current.parentElement; - while (outerParent && !outerParent.classList.contains(this.props.outermostParentClass)) { - outerParent = outerParent.parentElement; - } - this.outermostParent = outerParent; - if (this.outermostParent) { - this.outermostParent.addEventListener('mouseleave', this.outermostParentLeave); - } - - // Create a dummy DOM node to attach the editor to so that it skips layout. - this.monacoContainer = document.createElement('div'); - this.monacoContainer.setAttribute('class', 'monaco-editor-container'); - - // Create the editor - const editor = monacoEditor.editor.create(this.monacoContainer, { - value: this.props.value, - language: this.props.language, - ...this.props.options - }); - - // Listen for commands on the editor. This is to work around a problem - // with double pasting - // tslint:disable: no-any - if ((editor as any)._commandService) { - const commandService = (editor as any)._commandService as any; - if (commandService._onWillExecuteCommand) { - this.subscriptions.push( - commandService._onWillExecuteCommand.event(this.onCommandWillExecute.bind(this)) - ); - } - } - - // Force the editor to behave like a unix editor as - // all of our code is assuming that. - const model = editor.getModel(); - if (model) { - model.setEOL(monacoEditor.editor.EndOfLineSequence.LF); - - // Listen to model changes too. - this.subscriptions.push(model?.onDidChangeContent(this.onModelChanged)); - } - - // When testing, eliminate the _assertNotDisposed call. It can break tests if autocomplete - // is still open at the end of a test - // tslint:disable-next-line: no-any - if (isTestExecution() && model && (model as any)._assertNotDisposed) { - // tslint:disable-next-line: no-any - (model as any)._assertNotDisposed = noop; - } - - // Listen for keydown events. We don't do auto scrolling until the user types something - this.subscriptions.push(editor.onKeyDown((_e) => (this.keyHasBeenPressed = true))); - - // Register a link opener so when a user clicks on a link we can navigate to it. - // tslint:disable-next-line: no-any - const openerService = (editor.getContribution('editor.linkDetector') as any).openerService; - if (openerService && openerService.open) { - openerService.open = this.props.openLink; - } - - // Save the editor and the model in our state. - this.setState({ editor, model }); - if (this.props.hasFocus) { - this.giveFocusToEditor(editor, this.props.cursorPos, this.props.options.readOnly); - } - if (this.props.theme) { - monacoEditor.editor.setTheme(this.props.theme); - } - - // do the initial set of the height (wait a bit) - this.windowResized(); - - // on each edit recompute height (wait a bit) - if (model) { - this.subscriptions.push( - model.onDidChangeContent(() => { - this.windowResized(); - if (this.state.editor && this.state.editor.hasWidgetFocus()) { - this.hideAllOtherHoverAndParameterWidgets(); - } - }) - ); - } - - // On layout recompute height - this.subscriptions.push( - editor.onDidLayoutChange(() => { - this.windowResized(); - // Recompute visible line tops - this.debouncedComputeLineTops(); - - // A layout change may be because of a new line - this.throttledScrollCurrentPosition(); - }) - ); - - // Setup our context menu to show up outside. Autocomplete doesn't have this problem so it just works - this.subscriptions.push( - editor.onContextMenu((e) => { - if (this.state.editor) { - const domNode = this.state.editor.getDomNode(); - const contextMenuElement = domNode - ? (domNode.querySelector('.monaco-menu-container') as HTMLElement) - : null; - if (contextMenuElement) { - const posY = - e.event.posy + contextMenuElement.clientHeight > window.outerHeight - ? e.event.posy - contextMenuElement.clientHeight - : e.event.posy; - const posX = - e.event.posx + contextMenuElement.clientWidth > window.outerWidth - ? e.event.posx - contextMenuElement.clientWidth - : e.event.posx; - contextMenuElement.style.position = 'fixed'; - contextMenuElement.style.top = `${Math.max(0, Math.floor(posY))}px`; - contextMenuElement.style.left = `${Math.max(0, Math.floor(posX))}px`; - } - } - }) - ); - - // When editor loses focus, hide parameter widgets (if any currently displayed). - this.subscriptions.push( - editor.onDidBlurEditorWidget(() => { - this.hideParameterWidget(); - }) - ); - - // Track focus changes to make sure we update our widget parent and widget position - this.subscriptions.push( - editor.onDidFocusEditorWidget(() => { - this.throttledUpdateWidgetPosition(); - this.updateWidgetParent(editor); - this.hideAllOtherHoverAndParameterWidgets(); - - // Also update our scroll position, but do that after focus is established. - // This is necessary so that markdown can switch to edit mode before we - // try to scroll to it. - setTimeout(() => this.throttledScrollCurrentPosition(), 0); - }) - ); - - // Track cursor changes and make sure line is on the screen - this.subscriptions.push( - editor.onDidChangeCursorPosition(() => { - this.throttledUpdateWidgetPosition(); - - // Do this after the cursor changes so the text has time to appear - setTimeout(() => this.throttledScrollCurrentPosition(), 0); - }) - ); - - // Update our margin to include the correct line number style - this.updateMargin(editor); - - // If we're readonly, monaco is not putting the aria-readonly property on the textarea - // We should do that - if (this.props.options.readOnly) { - this.setAriaReadOnly(editor); - } - - // Eliminate the find action if possible - // tslint:disable-next-line: no-any - const editorAny = editor as any; - if (editorAny._standaloneKeybindingService) { - editorAny._standaloneKeybindingService.addDynamicKeybinding('-actions.find'); - } - - // Tell our parent the editor is ready to use - this.props.editorMounted(editor); - - if (editor) { - this.subscriptions.push( - editor.onMouseMove(() => { - this.hideAllOtherHoverAndParameterWidgets(); - }) - ); - } - } - }; - - public componentWillUnmount = () => { - if (this.resizeTimer) { - window.clearTimeout(this.resizeTimer); - } - - if (window) { - window.removeEventListener('resize', this.windowResized); - } - if (this.parameterWidget) { - this.parameterWidget.removeEventListener('mouseleave', this.outermostParentLeave); - this.parameterWidget = undefined; - } - if (this.outermostParent) { - this.outermostParent.removeEventListener('mouseleave', this.outermostParentLeave); - this.outermostParent = null; - } - if (this.widgetParent) { - this.widgetParent.remove(); - } - - this.subscriptions.forEach((d) => d.dispose()); - if (this.state.editor) { - this.state.editor.dispose(); - } - - if (this.styleObserver) { - this.styleObserver.disconnect(); - } - }; - - // tslint:disable-next-line: cyclomatic-complexity - public componentDidUpdate(prevProps: IMonacoEditorProps, prevState: IMonacoEditorState) { - if (this.state.editor) { - if (prevProps.language !== this.props.language && this.state.model) { - monacoEditor.editor.setModelLanguage(this.state.model, this.props.language); - } - if (prevProps.theme !== this.props.theme && this.props.theme) { - monacoEditor.editor.setTheme(this.props.theme); - } - if (!fastDeepEqual(prevProps.options, this.props.options)) { - if (prevProps.options.lineNumbers !== this.props.options.lineNumbers) { - this.updateMargin(this.state.editor); - } - this.state.editor.updateOptions(this.props.options); - MonacoEditor.lineHeight = 0; // Font size and family come from theoptions. - } - if ( - prevProps.value !== this.props.value && - this.state.model && - this.state.model.getValue() !== this.props.value - ) { - this.forceValue(this.props.value, this.props.cursorPos); - } else if ( - prevProps.version !== this.props.version && - this.state.model && - this.state.model.getVersionId() < this.props.version - ) { - this.forceValue(this.props.value, this.props.cursorPos); - } - } - - if (this.visibleLineCount === -1) { - this.updateEditorSize(); - } else { - // Debounce the call. This can happen too fast - this.debouncedUpdateEditorSize(); - } - // If this is our first time setting the editor, we might need to dynanically modify the styles - // that the editor generates for the background colors. - if (!prevState.editor && this.state.editor && this.containerRef.current) { - this.updateBackgroundStyle(); - } - if (this.state.editor && !prevProps.hasFocus && this.props.hasFocus) { - this.giveFocusToEditor(this.state.editor, this.props.cursorPos, this.props.options.readOnly); - } - - // Reset key press tracking. - this.keyHasBeenPressed = false; - } - public shouldComponentUpdate( - nextProps: Readonly, - nextState: Readonly, - // tslint:disable-next-line: no-any - _nextContext: any - ): boolean { - if (!fastDeepEqual(nextProps, this.props)) { - return true; - } - if (nextState === this.state) { - return false; - } - if (nextState.model?.id !== this.state.model?.id) { - return true; - } - return false; - } - public render() { - const measureWidthClassName = this.props.measureWidthClassName - ? this.props.measureWidthClassName - : 'measure-width-div'; - return ( -
-
-
- ); - } - - public isSuggesting(): boolean { - // This should mean our widgetParent has some height - if (this.widgetParent && this.widgetParent.firstChild && this.widgetParent.firstChild.childNodes.length >= 2) { - const htmlFirstChild = this.widgetParent.firstChild as HTMLElement; - const suggestWidget = htmlFirstChild.getElementsByClassName('suggest-widget')[0] as HTMLDivElement; - const signatureHelpWidget = htmlFirstChild.getElementsByClassName( - 'parameter-hints-widget' - )[0] as HTMLDivElement; - return this.isElementVisible(suggestWidget) || this.isElementVisible(signatureHelpWidget); - } - return false; - } - - public getCurrentVisibleLine(): number | undefined { - return this.getCurrentVisibleLinePosOrIndex((pos, _i) => pos); - } - - public getVisibleLineCount(): number { - return this.getVisibleLines().length; - } - - public giveFocus(cursorPos: CursorPos | monacoEditor.IPosition) { - if (this.state.editor) { - this.giveFocusToEditor(this.state.editor, cursorPos, this.props.options.readOnly); - } - } - - public getContents(): string { - if (this.state.model) { - // Make sure to remove any carriage returns as they're not expected - // in an ipynb file (and would mess up updates from the file) - return this.state.model.getValue().replace(/\r/g, ''); - } - return ''; - } - - public getVersionId(): number { - return this.state.model ? this.state.model.getVersionId() : 1; - } - - public getPosition(): monacoEditor.Position | null { - return this.state.editor!.getPosition(); - } - - public setValue(text: string, cursorPos: CursorPos) { - if (this.state.model && this.state.editor && this.state.model.getValue() !== text) { - this.forceValue(text, cursorPos, true); - } - } - - /** - * Give focus to the specified editor and clear the property used to track whether to set focus to an editor or not. - */ - private giveFocusToEditor( - editor: monacoEditor.editor.IStandaloneCodeEditor, - cursorPos: CursorPos | monacoEditor.IPosition, - readonly?: boolean - ) { - if (!readonly) { - editor.focus(); - } - if (cursorPos !== CursorPos.Current) { - const current = editor.getPosition(); - const lineNumber = cursorPos === CursorPos.Top ? 1 : editor.getModel()!.getLineCount(); - const column = current && current.lineNumber === lineNumber ? current.column : 1; - editor.setPosition({ lineNumber, column }); - this.scrollToCurrentPosition(); - } - } - - private closeSuggestWidget() { - // tslint:disable-next-line: no-any - const suggest = this.state.editor?.getContribution('editor.contrib.suggestController') as any; - if (suggest && suggest._widget) { - suggest._widget.getValue().hideWidget(); - } - } - - private forceValue(text: string, cursorPos: CursorPos | monacoEditor.IPosition, allowNotifications?: boolean) { - if (this.state.model && this.state.editor) { - // Save current position. May need it to update after setting. - const current = this.state.editor.getPosition(); - - // Disable change notifications if forcing this value should not allow them - this.skipNotifications = allowNotifications ? false : true; - - // Close any suggestions that are open - this.closeSuggestWidget(); - - // Change our text. - this.previousModelValue = text; - this.state.model.setValue(text); - - this.skipNotifications = false; - - // Compute new position - if (typeof cursorPos !== 'object') { - const lineNumber = cursorPos === CursorPos.Top ? 1 : this.state.editor.getModel()!.getLineCount(); - const column = current && current.lineNumber === lineNumber ? current.column : 1; - this.state.editor.setPosition({ lineNumber, column }); - } else { - this.state.editor.setPosition(cursorPos); - } - } - } - - private onModelChanged = (e: monacoEditor.editor.IModelContentChangedEvent) => { - // If not skipping notifications, send an event - if (!this.skipNotifications && this.state.model && this.state.editor) { - this.props.modelChanged(generateChangeEvent(e, this.state.model, this.previousModelValue)); - // Any changes from now onw will be considered previous. - this.previousModelValue = this.getContents(); - } - }; - - private getCurrentVisibleLinePosOrIndex(pickResult: (pos: number, index: number) => number): number | undefined { - // Convert the current cursor into a top and use that to find which visible - // line it is in. - if (this.state.editor) { - const cursor = this.state.editor.getPosition(); - if (cursor) { - const top = this.state.editor.getTopForPosition(cursor.lineNumber, cursor.column); - const count = this.getVisibleLineCount(); - const lineTops = count === this.lineTops.length ? this.lineTops : this.computeLineTops(); - for (let i = 0; i < count; i += 1) { - if (top <= lineTops[i].top) { - return pickResult(i, lineTops[i].index); - } - } - } - } - } - - private getCurrentVisibleLineIndex(): number | undefined { - return this.getCurrentVisibleLinePosOrIndex((_pos, i) => i); - } - - private getVisibleLines(): HTMLDivElement[] { - if (this.state.editor && this.state.model) { - // This is going to use just the dom to compute the visible lines - const editorDom = this.state.editor.getDomNode(); - if (editorDom) { - return Array.from(editorDom.getElementsByClassName('view-line')) as HTMLDivElement[]; - } - } - return []; - } - - private computeLineTops() { - const lines = this.getVisibleLines(); - - // Lines are not sorted by monaco, so we have to sort them by their top value - this.lineTops = lines - .map((l, i) => { - const match = l.style.top ? /(.+)px/.exec(l.style.top) : null; - return { top: match ? parseInt(match[0], 10) : Infinity, index: i }; - }) - .sort((a, b) => a.top - b.top); - return this.lineTops; - } - - private tryToScrollToCurrentPosition() { - // Don't scroll if no key has been pressed - if (!this.keyHasBeenPressed) { - return; - } - this.scrollToCurrentPosition(); - } - - private scrollToCurrentPosition() { - // Unfortunately during functional tests we hack the line count and the like. - if (isTestExecution() || !this.props.hasFocus) { - return; - } - // Scroll to the visible line that has our current line. Note: Visible lines are not sorted by monaco - // so we have to retrieve the current line's index (not its visible position) - const visibleLineDivs = this.getVisibleLines(); - const current = this.getCurrentVisibleLineIndex(); - if (current !== undefined && current >= 0 && visibleLineDivs[current].scrollIntoView) { - visibleLineDivs[current].scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' }); - } - } - - private isElementVisible(element: HTMLElement | undefined): boolean { - if (element && element.clientHeight > 0) { - // See if it has the visibility set on the style - const visibility = element.style.visibility; - return visibility ? visibility !== 'hidden' : true; - } - return false; - } - - private onCommandWillExecute(ev: { commandId: string; args: any[] }) { - if (ev.commandId === 'paste' && ev.args.length > 0 && ev.args[0].text) { - // See if same as last paste and within 100ms. This is the error condition. - const diff = Date.now() - this.lastPasteCommandTime; - if (diff < 100 && ev.args[0].text && ev.args[0].text === this.lastPasteCommandText) { - ev.args[0].text = ''; // Turn into an empty paste to null the operation. - } - this.lastPasteCommandText = ev.args[0].text; - this.lastPasteCommandTime = Date.now(); - } - } - - private setAriaReadOnly(editor: monacoEditor.editor.IStandaloneCodeEditor) { - const editorDomNode = editor.getDomNode(); - if (editorDomNode) { - const textArea = editorDomNode.getElementsByTagName('textarea'); - if (textArea && textArea.length > 0) { - const item = textArea.item(0); - if (item) { - item.setAttribute('aria-readonly', 'true'); - } - } - } - } - - private windowResized = () => { - if (this.resizeTimer) { - clearTimeout(this.resizeTimer); - } - this.resizeTimer = window.setTimeout(this.updateEditorSize.bind(this), 0); - }; - - private startUpdateWidgetPosition = () => { - this.throttledUpdateWidgetPosition(); - }; - - private updateBackgroundStyle = () => { - if (this.state.editor && this.containerRef.current) { - let nodes = this.containerRef.current.getElementsByClassName('monaco-editor-background'); - if (nodes && nodes.length > 0) { - const backgroundNode = nodes[0] as HTMLDivElement; - if (backgroundNode && backgroundNode.style) { - backgroundNode.style.backgroundColor = 'transparent'; - } - } - nodes = this.containerRef.current.getElementsByClassName('monaco-editor'); - if (nodes && nodes.length > 0) { - const editorNode = nodes[0] as HTMLDivElement; - if (editorNode && editorNode.style) { - editorNode.style.backgroundColor = 'transparent'; - } - } - } - }; - - private updateWidgetPosition(width?: number) { - if (this.state.editor && this.widgetParent) { - // Position should be at the top of the editor. - const editorDomNode = this.state.editor.getDomNode(); - if (editorDomNode) { - const rect = editorDomNode.getBoundingClientRect(); - if (rect && (rect.left !== this.lastOffsetLeft || rect.top !== this.lastOffsetTop)) { - this.lastOffsetLeft = rect.left; - this.lastOffsetTop = rect.top; - - this.widgetParent.setAttribute( - 'style', - `position: absolute; left: ${rect.left}px; top: ${rect.top}px; width:${ - width ? width : rect.width - }px` - ); - } - } - } else { - // If widget parent isn't set yet, try again later - this.updateWidgetParent(this.state.editor); - this.throttledUpdateWidgetPosition(width); - } - } - - private computeLineHeight(): number { - if (MonacoEditor.lineHeight <= 0 && this.state.editor) { - const editorDomNode = this.state.editor.getDomNode(); - if (editorDomNode) { - const container = editorDomNode.getElementsByClassName('view-lines')[0] as HTMLElement; - if (container.firstChild && (container.firstChild as HTMLElement).style.height) { - const lineHeightPx = (container.firstChild as HTMLElement).style.height; - MonacoEditor.lineHeight = parseInt(lineHeightPx, 10); - } else { - return LINE_HEIGHT; - } - } - } - return MonacoEditor.lineHeight; - } - - private updateEditorSize() { - if ( - this.measureWidthRef.current && - this.containerRef.current && - this.containerRef.current.parentElement && - this.containerRef.current.parentElement.parentElement && - this.state.editor && - this.state.model - ) { - const editorDomNode = this.state.editor.getDomNode(); - if (!editorDomNode) { - return; - } - const grandParent = this.containerRef.current.parentElement.parentElement; - const container = editorDomNode.getElementsByClassName('view-lines')[0] as HTMLElement; - const currLineCount = Math.max(container.childElementCount, this.state.model.getLineCount()); - const lineHeight = this.computeLineHeight(); - const height = currLineCount * lineHeight + 3; // Fudge factor - const width = this.measureWidthRef.current.clientWidth - grandParent.offsetLeft - 15; // Leave room for the scroll bar in regular cell table - - const layoutInfo = this.state.editor.getLayoutInfo(); - if (layoutInfo.height !== height || layoutInfo.width !== width || currLineCount !== this.visibleLineCount) { - // Make sure to attach to a real dom node. - if (!this.attached && this.state.editor && this.monacoContainer) { - this.containerRef.current.appendChild(this.monacoContainer); - this.monacoContainer.addEventListener('mousemove', this.onContainerMove); - } - this.visibleLineCount = currLineCount; - this.attached = true; - this.state.editor.layout({ width, height }); - - // Once layout completes, scroll to the current position again. It may have disappeared - if (!this.pendingLayoutScroll) { - this.pendingLayoutScroll = true; - setTimeout(() => { - this.scrollToCurrentPosition(); - this.pendingLayoutScroll = false; - }, 0); - } - } - } - } - - private onContainerMove = () => { - if (!this.widgetParent && !this.state.widgetsReparented && this.monacoContainer) { - // Only need to do this once, but update the widget parents and move them. - this.updateWidgetParent(this.state.editor); - this.startUpdateWidgetPosition(); - - // Since only doing this once, remove the listener. - this.monacoContainer.removeEventListener('mousemove', this.onContainerMove); - } - }; - - private onHoverLeave = (e: MouseEvent) => { - // If the hover is active, make sure to hide it. - if (this.state.editor && this.widgetParent) { - this.enteredHover = false; - - // Hide only if not still inside the same editor. Monaco will handle closing otherwise - if (!this.coordsInsideEditor(e.clientX, e.clientY)) { - // tslint:disable-next-line: no-any - const hover = this.state.editor.getContribution('editor.contrib.hover') as any; - if (hover._hideWidgets) { - hover._hideWidgets(); - } - } - } - }; - - private onHoverEnter = () => { - if (this.state.editor && this.widgetParent) { - // If we enter the hover, indicate it so we don't leave - this.enteredHover = true; - } - }; - - // tslint:disable-next-line: no-any - private outermostParentLeave = (e: any) => { - // Have to bounce this because the leave for the cell is the - // enter for the hover - if (this.leaveTimer) { - clearTimeout(this.leaveTimer); - } - this.leaveTimer = window.setTimeout(() => this.outermostParentLeaveBounced(e), 0); - }; - - // tslint:disable-next-line: no-any - private outermostParentLeaveBounced = (e: MouseEvent) => { - if (this.state.editor && !this.enteredHover && !this.coordsInsideEditor(e.clientX, e.clientY)) { - // If we haven't already entered hover, then act like it shuts down - this.onHoverLeave(e); - // Possible user is viewing the parameter hints, wait before user moves the mouse. - // Waiting for 1s is too long to move the mouse and hide the hints (100ms seems like a good fit). - setTimeout(() => this.hideParameterWidget(), 100); - } - }; - - private coordsInsideEditor(x: number, y: number): boolean { - if (this.monacoContainer) { - const clientRect = this.monacoContainer.getBoundingClientRect(); - if (x >= clientRect.left && x <= clientRect.right && y >= clientRect.top && y <= clientRect.bottom) { - return true; - } - } - return false; - } - - /** - * This will hide the parameter widget if the user is not hovering over - * the parameter widget for this monaco editor. - * - * Notes: See issue https://github.com/microsoft/vscode-python/issues/7851 for further info. - * Hide the parameter widget if all of the following conditions have been met: - * - ditor doesn't have focus - * - Mouse is not over the editor - * - Mouse is not over (hovering) the parameter widget - * - * @private - * @returns - * @memberof MonacoEditor - */ - private hideParameterWidget() { - if (!this.state.editor || !this.state.editor.getDomNode() || !this.widgetParent) { - return; - } - // Find all elements that the user is hovering over. - // Its possible the parameter widget is one of them. - const hoverElements: Element[] = Array.prototype.slice.call(document.querySelectorAll(':hover')); - // Find all parameter widgets related to this monaco editor that are currently displayed. - const visibleParameterHintsWidgets: Element[] = Array.prototype.slice.call( - this.widgetParent.querySelectorAll('.parameter-hints-widget.visible') - ); - if (hoverElements.length === 0 && visibleParameterHintsWidgets.length === 0) { - // If user is not hovering over anything and there are no visible parameter widgets, - // then, we have nothing to do but get out of here. - return; - } - - // Find all parameter widgets related to this monaco editor. - const knownParameterHintsWidgets: HTMLDivElement[] = Array.prototype.slice.call( - this.widgetParent.querySelectorAll(WidgetCSSSelector.Parameters) - ); - - // Lets not assume we'll have the exact same DOM for parameter widgets. - // So, just remove the event handler, and add it again later. - if (this.parameterWidget) { - this.parameterWidget.removeEventListener('mouseleave', this.outermostParentLeave); - } - // These are the classes that will appear on a parameter widget when they are visible. - const parameterWidgetClasses = ['editor-widget', 'parameter-hints-widget', 'visible']; - - // Find the parameter widget the user is currently hovering over. - this.parameterWidget = hoverElements.find((item) => { - if (typeof item.className !== 'string') { - return false; - } - // Check if user is hovering over a parameter widget. - const classes = item.className.split(' '); - if (!parameterWidgetClasses.every((cls) => classes.indexOf(cls) >= 0)) { - // Not all classes required in a parameter hint widget are in this element. - // Hence this is not a parameter widget. - return false; - } - - // Ok, this element that the user is hovering over is a parameter widget. - - // Next, check whether this parameter widget belongs to this monaco editor. - // We have a list of parameter widgets that belong to this editor, hence a simple lookup. - return knownParameterHintsWidgets.some((widget) => widget === item); - }); - - if (this.parameterWidget) { - // We know the user is hovering over the parameter widget for this editor. - // Hovering could mean the user is scrolling through a large parameter list. - // We need to add a mouse leave event handler, so as to hide this. - this.parameterWidget.addEventListener('mouseleave', this.outermostParentLeave); - - // In case the event handler doesn't get fired, have a backup of checking within 1s. - setTimeout(() => this.hideParameterWidget(), 1000); - return; - } - if (visibleParameterHintsWidgets.length === 0) { - // There are no parameter widgets displayed for this editor. - // Hence nothing to do. - return; - } - // If the editor has focus, don't hide the parameter widget. - // This is the default behavior. Let the user hit `Escape` or click somewhere - // to forcefully hide the parameter widget. - if (this.state.editor.hasWidgetFocus()) { - return; - } - - // If we got here, then the user is not hovering over the paramter widgets. - // & the editor doesn't have focus. - // However some of the parameter widgets associated with this monaco editor are visible. - // We need to hide them. - - // Solution: Hide the widgets manually. - this.hideWidgets(this.widgetParent, [WidgetCSSSelector.Parameters]); - } - /** - * Hides widgets such as parameters and hover, that belong to a given parent HTML element. - * - * @private - * @param {HTMLDivElement} widgetParent - * @param {string[]} selectors - * @memberof MonacoEditor - */ - private hideWidgets(widgetParent: HTMLDivElement, selectors: string[]) { - for (const selector of selectors) { - for (const widget of Array.from(widgetParent.querySelectorAll(selector))) { - widget.setAttribute( - 'class', - widget.className - .split(' ') - .filter((cls: string) => cls !== 'visible') - .join(' ') - ); - if (widget.style.visibility !== 'hidden') { - widget.style.visibility = 'hidden'; - } - } - } - } - /** - * Hides the hover and parameters widgets related to other monaco editors. - * Use this to ensure we only display hover/parameters widgets for current editor (by hiding others). - * - * @private - * @returns - * @memberof MonacoEditor - */ - private hideAllOtherHoverAndParameterWidgets() { - const root = document.getElementById('root'); - if (!root || !this.widgetParent) { - return; - } - const widgetParents: HTMLDivElement[] = Array.prototype.slice.call( - root.querySelectorAll('div.monaco-editor-pretend-parent') - ); - widgetParents - .filter((widgetParent) => widgetParent !== this.widgetParent) - .forEach((widgetParent) => - this.hideWidgets(widgetParent, [WidgetCSSSelector.Parameters, WidgetCSSSelector.Hover]) - ); - } - private updateMargin(editor: monacoEditor.editor.IStandaloneCodeEditor) { - const editorNode = editor.getDomNode(); - if (editorNode) { - try { - const elements = editorNode.getElementsByClassName('margin-view-overlays'); - if (elements && elements.length) { - const margin = elements[0] as HTMLDivElement; - - // Create special class name based on the line number property - const specialExtra = `margin-view-overlays-border-${this.props.options.lineNumbers}`; - if (margin.className && !margin.className.includes(specialExtra)) { - margin.className = `margin-view-overlays ${specialExtra}`; - } - - // Watch the scrollable element (it's where the code lines up) - const scrollable = editorNode.getElementsByClassName('monaco-scrollable-element'); - if (!this.watchingMargin && scrollable && scrollable.length && this.styleObserver) { - const watching = scrollable[0] as HTMLDivElement; - this.watchingMargin = true; - this.styleObserver.observe(watching, { attributes: true, attributeFilter: ['style'] }); - } - } - } catch { - // Ignore if we can't get modify the margin class - } - } - } - - private updateWidgetParent(editor: monacoEditor.editor.IStandaloneCodeEditor | undefined) { - // Reparent the hover widgets. They cannot be inside anything that has overflow hidden or scrolling or they won't show - // up overtop of anything. Warning, this is a big hack. If the class name changes or the logic - // for figuring out the position of hover widgets changes, this won't work anymore. - // appendChild on a DOM node moves it, but doesn't clone it. - // https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild - const editorNode = editor ? editor.getDomNode() : undefined; - if (editor && editorNode && !this.state.widgetsReparented) { - this.setState({ widgetsReparented: true }); - try { - const elements = editorNode.getElementsByClassName('overflowingContentWidgets'); - if (elements && elements.length) { - const contentWidgets = elements[0] as HTMLDivElement; - if (contentWidgets) { - // Go up to the document. - const document = contentWidgets.getRootNode() as HTMLDocument; - - // His first child with the id 'root' should be where we want to parent our overflow widgets - if (document && document.getElementById) { - const root = document.getElementById('root'); - if (root) { - // We need to create a dummy 'monaco-editor' div so that the content widgets get the same styles. - this.widgetParent = document.createElement('div', {}); - this.widgetParent.setAttribute( - 'class', - `${editorNode.className} monaco-editor-pretend-parent` - ); - - root.appendChild(this.widgetParent); - this.widgetParent.appendChild(contentWidgets); - - // Listen for changes so we can update the position dynamically - editorNode.addEventListener('mouseenter', this.startUpdateWidgetPosition); - - // We also need to trick the editor into thinking mousing over the hover does not - // mean the mouse has left the editor. - // tslint:disable-next-line: no-any - const hover = editor.getContribution('editor.contrib.hover') as any; - if ( - hover._toUnhook && - hover._toUnhook._toDispose && - hover._toUnhook._toDispose && - hover.contentWidget - ) { - // This should mean our 5th element is the event handler for mouse leave. Remove it. - const set = hover._toUnhook._toDispose as Set; - if (set.size === HOVER_DISPOSABLE_EVENT_COUNT) { - // This is horribly flakey, Is set always guaranteed to put stuff in the same order? - const array = Array.from(set); - array[HOVER_DISPOSABLE_LEAVE_INDEX].dispose(); - set.delete(array[HOVER_DISPOSABLE_LEAVE_INDEX]); - - // Instead listen to mouse leave for our hover widget - const hoverWidget = this.widgetParent.getElementsByClassName( - 'monaco-editor-hover' - )[0] as HTMLElement; - if (hoverWidget) { - hoverWidget.addEventListener('mouseenter', this.onHoverEnter); - hoverWidget.addEventListener('mouseleave', this.onHoverLeave); - } - } - } - } - } - } - } - } catch (e) { - // If something fails, then the hover will just work inside the main frame - if (!this.props.testMode) { - logMessage(`Error moving editor widgets: ${e}`); - } - - // Make sure we don't try moving it around. - this.widgetParent = undefined; - } - } - } -} diff --git a/src/datascience-ui/react-common/monacoHelpers.ts b/src/datascience-ui/react-common/monacoHelpers.ts deleted file mode 100644 index 0258d9c86fea..000000000000 --- a/src/datascience-ui/react-common/monacoHelpers.ts +++ /dev/null @@ -1,113 +0,0 @@ -import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; -import { - IEditorContentChange, - IEditorPosition, - IEditorRange -} from '../../client/datascience/interactive-common/interactiveWindowTypes'; - -export interface IMonacoTextModel { - readonly id: string; - getValue(): string; - getValueLength(): number; - getVersionId(): number; - getPositionAt(offset: number): IEditorPosition; -} - -export interface IMonacoModelContentChangeEvent { - // Changes to apply - readonly forward: IEditorContentChange[]; - // Change to undo the apply - readonly reverse: IEditorContentChange[]; - readonly eol: string; - readonly versionId: number; - readonly isUndoing: boolean; - readonly isRedoing: boolean; - readonly isFlush: boolean; - readonly model: IMonacoTextModel; -} - -function getValueInRange(text: string, r: IEditorRange): string { - // Compute start and end offset using line and column data - let startOffset = -1; - let endOffset = -1; - let line = 1; - let col = 1; - - // Go forwards through the text searching for matching lines - for (let pos = 0; pos <= text.length && (startOffset < 0 || endOffset < 0); pos += 1) { - if (line === r.startLineNumber && col === r.startColumn) { - startOffset = pos; - } else if (line === r.endLineNumber && col === r.endColumn) { - endOffset = pos; - } - if (pos < text.length) { - if (text[pos] === '\n') { - line += 1; - col = 1; - } else { - col += 1; - } - } - } - - if (startOffset >= 0 && endOffset >= 0) { - return text.slice(startOffset, endOffset); - } - - return ''; -} - -export function generateReverseChange( - oldModelValue: string, - model: IMonacoTextModel, - c: monacoEditor.editor.IModelContentChange -): monacoEditor.editor.IModelContentChange { - const oldStart = model.getPositionAt(c.rangeOffset); - const oldEnd = model.getPositionAt(c.rangeOffset + c.rangeLength); - const oldText = getValueInRange(oldModelValue, c.range); - const oldRange: monacoEditor.IRange = { - startColumn: oldStart.column, - startLineNumber: oldStart.lineNumber, - endColumn: oldEnd.column, - endLineNumber: oldEnd.lineNumber - }; - return { - rangeLength: c.text.length, - rangeOffset: c.rangeOffset, - text: oldText ? oldText : '', - range: oldRange - }; -} - -export function generateChangeEvent( - ev: monacoEditor.editor.IModelContentChangedEvent, - m: IMonacoTextModel, - oldText: string -): IMonacoModelContentChangeEvent { - // Figure out the end position from the offset plus the length of the text we added - const currentOffset = ev.changes[ev.changes.length - 1].rangeOffset + ev.changes[ev.changes.length - 1].text.length; - const currentPosition = m.getPositionAt(currentOffset); - - // Create the reverse changes - const reverseChanges = ev.changes.map(generateReverseChange.bind(undefined, oldText, m)).reverse(); - - // Figure out the old position by using the first offset - const oldOffset = ev.changes[0].rangeOffset; - const oldPosition = m.getPositionAt(oldOffset); - - // Combine position and change to create result - return { - forward: ev.changes.map((c) => { - return { ...c, position: currentPosition! }; - }), - reverse: reverseChanges.map((r) => { - return { ...r, position: oldPosition! }; - }), - eol: ev.eol, - isFlush: ev.isFlush, - isUndoing: ev.isUndoing, - isRedoing: ev.isRedoing, - versionId: m.getVersionId(), - model: m - }; -} diff --git a/src/datascience-ui/react-common/postOffice.ts b/src/datascience-ui/react-common/postOffice.ts deleted file mode 100644 index 041432356cc1..000000000000 --- a/src/datascience-ui/react-common/postOffice.ts +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import { Observable } from 'rxjs/Observable'; -import { Subject } from 'rxjs/Subject'; -import { WebviewMessage } from '../../client/common/application/types'; -import { IDisposable } from '../../client/common/types'; -import { logMessage } from './logger'; - -export interface IVsCodeApi { - // tslint:disable-next-line:no-any - postMessage(msg: any): void; - // tslint:disable-next-line:no-any - setState(state: any): void; - // tslint:disable-next-line:no-any - getState(): any; -} - -export interface IMessageHandler { - // tslint:disable-next-line:no-any - handleMessage(type: string, payload?: any): boolean; - dispose?(): void; -} - -// This special function talks to vscode from a web panel -export declare function acquireVsCodeApi(): IVsCodeApi; -// tslint:disable-next-line: no-any -export type PostOfficeMessage = { type: string; payload?: any }; -// tslint:disable-next-line: no-unnecessary-class -export class PostOffice implements IDisposable { - private registered: boolean = false; - private vscodeApi: IVsCodeApi | undefined; - private handlers: IMessageHandler[] = []; - private baseHandler = this.handleMessages.bind(this); - private readonly subject = new Subject(); - private readonly observable: Observable; - constructor() { - this.observable = this.subject.asObservable(); - } - public asObservable(): Observable { - return this.observable; - } - public dispose() { - if (this.registered) { - this.registered = false; - window.removeEventListener('message', this.baseHandler); - } - } - - public sendMessage(type: T, payload?: M[T]) { - return this.sendUnsafeMessage(type.toString(), payload); - } - - // tslint:disable-next-line:no-any - public sendUnsafeMessage(type: string, payload?: any) { - const api = this.acquireApi(); - if (api) { - api.postMessage({ type: type, payload }); - } else { - logMessage(`No vscode API to post message ${type}`); - } - } - - public addHandler(handler: IMessageHandler) { - // Acquire here too so that the message handlers are setup during tests. - this.acquireApi(); - this.handlers.push(handler); - } - - public removeHandler(handler: IMessageHandler) { - this.handlers = this.handlers.filter((f) => f !== handler); - } - - private acquireApi(): IVsCodeApi | undefined { - // Only do this once as it crashes if we ask more than once - // tslint:disable-next-line:no-typeof-undefined - if (!this.vscodeApi && typeof acquireVsCodeApi !== 'undefined') { - this.vscodeApi = acquireVsCodeApi(); // NOSONAR - } - if (!this.registered) { - this.registered = true; - window.addEventListener('message', this.baseHandler); - - try { - // For testing, we might use a browser to load the stuff. - // In such instances the `acquireVSCodeApi` will return the event handler to get messages from extension. - // See ./src/datascience-ui/native-editor/index.html - // tslint:disable-next-line: no-any - const api = (this.vscodeApi as any) as { handleMessage?: Function }; - if (api && api.handleMessage) { - api.handleMessage(this.handleMessages.bind(this)); - } - } catch { - // Ignore. - } - } - - return this.vscodeApi; - } - - private async handleMessages(ev: MessageEvent) { - if (this.handlers) { - const msg = ev.data as WebviewMessage; - if (msg) { - this.subject.next({ type: msg.type, payload: msg.payload }); - this.handlers.forEach((h: IMessageHandler | null) => { - if (h) { - h.handleMessage(msg.type, msg.payload); - } - }); - } - } - } -} diff --git a/src/datascience-ui/react-common/progress.css b/src/datascience-ui/react-common/progress.css deleted file mode 100644 index c5802e7ee863..000000000000 --- a/src/datascience-ui/react-common/progress.css +++ /dev/null @@ -1,70 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - - .monaco-progress-container { - width: 100%; - height: 5px; - overflow: hidden; /* keep progress bit in bounds */ - position: fixed; - z-index: 10; -} - -.monaco-progress-container .progress-bit { - width: 2%; - height: 5px; - position: absolute; - left: 0; - display: none; - background-color:var(--vscode-editorSuggestWidget-highlightForeground); -} - -.monaco-progress-container.active .progress-bit { - display: inherit; -} - -.monaco-progress-container.discrete .progress-bit { - left: 0; - transition: width 100ms linear; - -webkit-transition: width 100ms linear; - -o-transition: width 100ms linear; - -moz-transition: width 100ms linear; - -ms-transition: width 100ms linear; -} - -.monaco-progress-container.discrete.done .progress-bit { - width: 100%; -} - -.monaco-progress-container.infinite .progress-bit { - animation-name: progress; - animation-duration: 4s; - animation-iteration-count: infinite; - animation-timing-function: linear; - -ms-animation-name: progress; - -ms-animation-duration: 4s; - -ms-animation-iteration-count: infinite; - -ms-animation-timing-function: linear; - -webkit-animation-name: progress; - -webkit-animation-duration: 4s; - -webkit-animation-iteration-count: infinite; - -webkit-animation-timing-function: linear; - -moz-animation-name: progress; - -moz-animation-duration: 4s; - -moz-animation-iteration-count: infinite; - -moz-animation-timing-function: linear; - will-change: transform; -} - -/** - * The progress bit has a width: 2% (1/50) of the parent container. The animation moves it from 0% to 100% of - * that container. Since translateX is relative to the progress bit size, we have to multiple it with - * its relative size to the parent container: - * 50%: 50 * 50 = 2500% - * 100%: 50 * 100 - 50 (do not overflow): 4950% - */ -@keyframes progress { from { transform: translateX(0%) scaleX(1) } 50% { transform: translateX(2500%) scaleX(3) } to { transform: translateX(4950%) scaleX(1) } } -@-ms-keyframes progress { from { transform: translateX(0%) scaleX(1) } 50% { transform: translateX(2500%) scaleX(3) } to { transform: translateX(4950%) scaleX(1) } } -@-webkit-keyframes progress { from { transform: translateX(0%) scaleX(1) } 50% { transform: translateX(2500%) scaleX(3) } to { transform: translateX(4950%) scaleX(1) } } -@-moz-keyframes progress { from { transform: translateX(0%) scaleX(1) } 50% { transform: translateX(2500%) scaleX(3) } to { transform: translateX(4950%) scaleX(1) } } diff --git a/src/datascience-ui/react-common/progress.tsx b/src/datascience-ui/react-common/progress.tsx deleted file mode 100644 index 4bbf7981f108..000000000000 --- a/src/datascience-ui/react-common/progress.tsx +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import './progress.css'; - -import * as React from 'react'; - -export class Progress extends React.Component { - constructor(props: {}) { - super(props); - } - - public render() { - // Vscode does this with two parts, a progress container and a progress bit - return ( -
-
-
- ); - } -} diff --git a/src/datascience-ui/react-common/reduxUtils.ts b/src/datascience-ui/react-common/reduxUtils.ts deleted file mode 100644 index b651517aff8a..000000000000 --- a/src/datascience-ui/react-common/reduxUtils.ts +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import { Action, AnyAction, Middleware, Reducer } from 'redux'; -import { BaseReduxActionPayload } from '../../client/datascience/interactive-common/types'; - -// tslint:disable-next-line: interface-name -interface TypedAnyAction extends Action { - // Allows any extra properties to be defined in an action. - // tslint:disable-next-line: no-any - [extraProps: string]: any; -} -export type QueueAnotherFunc = (nextAction: Action) => void; -export type QueuableAction = TypedAnyAction & { queueAction: QueueAnotherFunc }; -export type ReducerArg = T extends never | undefined - ? { - prevState: S; - queueAction: QueueAnotherFunc; - payload: BaseReduxActionPayload; - } - : { - prevState: S; - queueAction: QueueAnotherFunc; - payload: T; - }; - -export type ReducerFunc = (args: ReducerArg) => S; -export type ActionWithPayload = TypedAnyAction & { payload: BaseReduxActionPayload }; -export type ActionWithOutPayloadData = TypedAnyAction & { payload: BaseReduxActionPayload }; - -/** - * CombineReducers takes in a map of action.type to func and creates a reducer that will call the appropriate function for - * each action - * @param defaultState - original state to use for the store - * @param postMessage - function passed in to use to post messages back to the extension - * @param map - map of action type to func to call - */ -export function combineReducers(defaultState: S, map: M): Reducer> { - return (currentState: S = defaultState, action: QueuableAction) => { - const func = map[action.type]; - if (typeof func === 'function') { - // Call the reducer, giving it - // - current state - // - function to potentially post stuff to the other side - // - queue function to dispatch again - // - payload containing the data from the action - return func({ prevState: currentState, queueAction: action.queueAction, payload: action.payload }); - } else { - return currentState; - } - }; -} - -// This middleware allows a reducer to dispatch another action after the reducer -// has returned state (it queues up the dispatch). -// -// Got this idea from here: -// https://stackoverflow.com/questions/36730793/can-i-dispatch-an-action-in-reducer -// -// Careful when using the queueAction though. Don't store it past the point of a reducer as -// the local state inside of this middleware function will be wrong. -export function createQueueableActionMiddleware(): Middleware { - return (store) => (next) => (action) => { - let pendingActions: Action[] = []; - let complete = false; - - function flush() { - pendingActions.forEach((a) => store.dispatch(a)); - pendingActions = []; - } - - function queueAction(nextAction: AnyAction) { - pendingActions.push(nextAction); - - // If already done, run the pending actions (this means - // this was pushed async) - if (complete) { - flush(); - } - } - - // Add queue to the action - const modifiedAction = { ...action, queueAction }; - - // Call the next item in the middle ware chain - const res = next(modifiedAction); - - // When done, run all the queued actions - complete = true; - flush(); - - return res; - }; -} diff --git a/src/datascience-ui/react-common/relativeImage.tsx b/src/datascience-ui/react-common/relativeImage.tsx deleted file mode 100644 index 9c67995b7c51..000000000000 --- a/src/datascience-ui/react-common/relativeImage.tsx +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; -import * as path from 'path'; -import * as React from 'react'; - -// This special function finds relative paths when loading inside of vscode. It's not defined -// when loading outside, so the Image component should still work. -export declare function resolvePath(relativePath: string): string; - -interface IRelativeImageProps { - class: string; - path: string; -} - -export class RelativeImage extends React.Component { - constructor(props: IRelativeImageProps) { - super(props); - } - - public render() { - return {path.basename(this.props.path)}; - } - - private getImageSource = () => { - // tslint:disable-next-line:no-typeof-undefined - if (typeof resolvePath === 'undefined') { - return this.props.path; - } else { - return resolvePath(this.props.path); - } - }; -} diff --git a/src/datascience-ui/react-common/settingsReactSide.ts b/src/datascience-ui/react-common/settingsReactSide.ts deleted file mode 100644 index b3f877cd38e2..000000000000 --- a/src/datascience-ui/react-common/settingsReactSide.ts +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; - -import { IDataScienceExtraSettings } from '../../client/datascience/types'; - -export function getDefaultSettings() { - // Default settings for tests - // tslint:disable-next-line: no-unnecessary-local-variable - const result: IDataScienceExtraSettings = { - allowImportFromNotebook: true, - alwaysTrustNotebooks: true, - jupyterLaunchTimeout: 10, - jupyterLaunchRetries: 3, - enabled: true, - jupyterServerURI: 'local', - // tslint:disable-next-line: no-invalid-template-strings - notebookFileRoot: '${fileDirname}', - changeDirOnImportExport: false, - useDefaultConfigForJupyter: true, - jupyterInterruptTimeout: 10000, - searchForJupyter: true, - allowInput: true, - showCellInputCode: true, - collapseCellInputCodeByDefault: true, - maxOutputSize: 400, - enableScrollingForCellOutputs: true, - errorBackgroundColor: '#FFFFFF', - sendSelectionToInteractiveWindow: false, - markdownRegularExpression: '^(#\\s*%%\\s*\\[markdown\\]|#\\s*\\)', - codeRegularExpression: '^(#\\s*%%|#\\s*\\|#\\s*In\\[\\d*?\\]|#\\s*In\\[ \\])', - variableExplorerExclude: 'module;function;builtin_function_or_method', - enablePlotViewer: true, - interactiveWindowMode: 'multiple', - extraSettings: { - editor: { - cursor: 'line', - cursorBlink: 'blink', - autoClosingBrackets: 'languageDefined', - autoClosingQuotes: 'languageDefined', - autoSurround: 'languageDefined', - autoIndent: false, - fontLigatures: false, - scrollBeyondLastLine: true, - // VS Code puts a value for this, but it's 10 (the explorer bar size) not 14 the editor size for vert - verticalScrollbarSize: 14, - horizontalScrollbarSize: 14, - fontSize: 14, - fontFamily: "Consolas, 'Courier New', monospace" - }, - theme: 'Default Dark+', - useCustomEditorApi: false - }, - intellisenseOptions: { - quickSuggestions: { - other: true, - comments: false, - strings: false - }, - acceptSuggestionOnEnter: 'on', - quickSuggestionsDelay: 10, - suggestOnTriggerCharacters: true, - tabCompletion: 'on', - suggestLocalityBonus: true, - suggestSelection: 'recentlyUsed', - wordBasedSuggestions: true, - parameterHintsEnabled: true - }, - variableOptions: { - enableDuringDebugger: false - }, - gatherIsInstalled: false, - runStartupCommands: '', - debugJustMyCode: true, - variableQueries: [], - jupyterCommandLineArguments: [], - widgetScriptSources: [] - }; - - return result; -} - -//tslint:disable:no-any -export function computeEditorOptions(settings: IDataScienceExtraSettings): monacoEditor.editor.IEditorOptions { - const intellisenseOptions = settings.intellisenseOptions; - const extraSettings = settings.extraSettings; - if (intellisenseOptions && extraSettings) { - return { - quickSuggestions: { - other: intellisenseOptions.quickSuggestions.other, - comments: intellisenseOptions.quickSuggestions.comments, - strings: intellisenseOptions.quickSuggestions.strings - }, - acceptSuggestionOnEnter: intellisenseOptions.acceptSuggestionOnEnter, - quickSuggestionsDelay: intellisenseOptions.quickSuggestionsDelay, - suggestOnTriggerCharacters: intellisenseOptions.suggestOnTriggerCharacters, - tabCompletion: intellisenseOptions.tabCompletion, - suggest: { - localityBonus: intellisenseOptions.suggestLocalityBonus - }, - suggestSelection: intellisenseOptions.suggestSelection, - wordBasedSuggestions: intellisenseOptions.wordBasedSuggestions, - parameterHints: { - enabled: intellisenseOptions.parameterHintsEnabled - }, - cursorStyle: extraSettings.editor.cursor, - cursorBlinking: extraSettings.editor.cursorBlink, - autoClosingBrackets: extraSettings.editor.autoClosingBrackets as any, - autoClosingQuotes: extraSettings.editor.autoClosingQuotes as any, - autoIndent: extraSettings.editor.autoIndent as any, - autoSurround: extraSettings.editor.autoSurround as any, - fontLigatures: extraSettings.editor.fontLigatures - }; - } - - return {}; -} diff --git a/src/datascience-ui/react-common/styleInjector.tsx b/src/datascience-ui/react-common/styleInjector.tsx deleted file mode 100644 index 4ff54f1786e5..000000000000 --- a/src/datascience-ui/react-common/styleInjector.tsx +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as React from 'react'; - -import { CssMessages, IGetCssResponse, SharedMessages } from '../../client/datascience/messages'; -import { IDataScienceExtraSettings } from '../../client/datascience/types'; -import { IMessageHandler, PostOffice } from './postOffice'; -import { detectBaseTheme } from './themeDetector'; - -export interface IStyleInjectorProps { - expectingDark: boolean; - postOffice: PostOffice; - settings: IDataScienceExtraSettings; - darkChanged?(newDark: boolean): void; - onReady?(): void; -} - -interface IStyleInjectorState { - rootCss?: string; - theme?: string; - knownDark?: boolean; -} - -export class StyleInjector extends React.Component - implements IMessageHandler { - constructor(props: IStyleInjectorProps) { - super(props); - this.state = { rootCss: undefined, theme: undefined }; - } - - public componentWillMount() { - // Add ourselves as a handler for the post office - this.props.postOffice.addHandler(this); - } - - public componentWillUnmount() { - // Remove ourselves as a handler for the post office - this.props.postOffice.removeHandler(this); - } - - public componentDidMount() { - if (!this.state.rootCss) { - // Set to a temporary value. - this.setState({ rootCss: ' ' }); - this.props.postOffice.sendUnsafeMessage(CssMessages.GetCssRequest, { isDark: this.props.expectingDark }); - } - } - - public render() { - return ( -
- - {this.props.children} -
- ); - } - - // tslint:disable-next-line:no-any - public handleMessage = (msg: string, payload?: any): boolean => { - switch (msg) { - case CssMessages.GetCssResponse: - this.handleCssResponse(payload); - break; - - case SharedMessages.UpdateSettings: - this.updateSettings(payload); - break; - - default: - break; - } - - return true; - }; - - // tslint:disable-next-line:no-any - private handleCssResponse(payload?: any) { - const response = payload as IGetCssResponse; - if (response && response.css) { - // Recompute our known dark value from the class name in the body - // VS code should update this dynamically when the theme changes - const computedKnownDark = this.computeKnownDark(); - - // We also get this in our response, but computing is more reliable - // than searching for it. - - if (this.state.knownDark !== computedKnownDark && this.props.darkChanged) { - this.props.darkChanged(computedKnownDark); - } - - this.setState( - { - rootCss: response.css, - theme: response.theme, - knownDark: computedKnownDark - }, - this.props.onReady - ); - } - } - - // tslint:disable-next-line:no-any - private updateSettings(payload: any) { - if (payload) { - const newSettings = JSON.parse(payload as string); - const dsSettings = newSettings as IDataScienceExtraSettings; - if (dsSettings && dsSettings.extraSettings && dsSettings.extraSettings.theme !== this.state.theme) { - // User changed the current theme. Rerender - this.props.postOffice.sendUnsafeMessage(CssMessages.GetCssRequest, { isDark: this.computeKnownDark() }); - } - } - } - - private computeKnownDark(): boolean { - const ignore = this.props.settings.ignoreVscodeTheme ? true : false; - const baseTheme = ignore ? 'vscode-light' : detectBaseTheme(); - return baseTheme !== 'vscode-light'; - } -} diff --git a/src/datascience-ui/react-common/svgList.css b/src/datascience-ui/react-common/svgList.css deleted file mode 100644 index 6c7d9e34ba39..000000000000 --- a/src/datascience-ui/react-common/svgList.css +++ /dev/null @@ -1,36 +0,0 @@ -.svg-list-container { - width: 100%; - height: 100px; - margin: 10px; -} - -.svg-list { - list-style-type: none; - overflow-x: scroll; - overflow-y: hidden; - white-space: nowrap; - padding-inline-start: 0px; -} - -.svg-list-item { - width: 100px; - height: 100%; - border: 1px solid transparent; - display: inline-block; - overflow: hidden; -} - -.svg-list-item-selected { - border-color: var(--vscode-list-highlightForeground); - border-style: solid; - border-width: 2px; -} - -.svg-list-white-background { - background: white; -} - -.svg-list-item-image { - width: 100px; - height: 100px; -} \ No newline at end of file diff --git a/src/datascience-ui/react-common/svgList.tsx b/src/datascience-ui/react-common/svgList.tsx deleted file mode 100644 index b3e75028d71c..000000000000 --- a/src/datascience-ui/react-common/svgList.tsx +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as React from 'react'; -import { SvgLoader } from 'react-svgmt'; -import { getLocString } from '../react-common/locReactSide'; - -import './svgList.css'; - -interface ISvgListProps { - images: string[]; - currentImage: number; - themeMatplotlibBackground: boolean; - imageClicked(index: number): void; -} - -export class SvgList extends React.Component { - constructor(props: ISvgListProps) { - super(props); - } - - public render() { - return ( -
-
{this.renderImages()}
-
- ); - } - - private renderImages() { - return this.props.images.map((image, index) => { - const className = `svg-list-item${this.props.currentImage === index ? ' svg-list-item-selected' : ''}${ - this.props.themeMatplotlibBackground ? '' : ' svg-list-white-background' - }`; - const ariaLabel = - index === this.props.currentImage - ? getLocString('DataScience.selectedImageListLabel', 'Selected Image') - : getLocString('DataScience.selectedImageLabel', 'Image'); - const ariaPressed = index === this.props.currentImage ? 'true' : 'false'; - const clickHandler = () => this.props.imageClicked(index); - const keyDownHandler = (e: React.KeyboardEvent) => this.onKeyDown(e, index); - return ( -
-
- -
-
- ); - }); - } - - private onKeyDown = (event: React.KeyboardEvent, index: number) => { - // Enter and Space commit an action the same as a click does - if (event.key === 'Enter' || event.key === ' ') { - this.props.imageClicked(index); - } - }; -} diff --git a/src/datascience-ui/react-common/svgViewer.tsx b/src/datascience-ui/react-common/svgViewer.tsx deleted file mode 100644 index e477eca355fa..000000000000 --- a/src/datascience-ui/react-common/svgViewer.tsx +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; -import * as React from 'react'; -import { POSITION_TOP, ReactSVGPanZoom, Tool, Value } from 'react-svg-pan-zoom'; -import { SvgLoader } from 'react-svgmt'; -import { AutoSizer } from 'react-virtualized'; -import './svgViewer.css'; - -interface ISvgViewerProps { - svg: string; - id: string; // Unique identified for this svg (in case they are the same) - baseTheme: string; - themeMatplotlibPlots: boolean; - size: { width: string; height: string }; - defaultValue: Value | undefined; - tool: Tool; - changeValue(value: Value): void; -} - -interface ISvgViewerState { - value: Value; - tool: Tool; -} - -export class SvgViewer extends React.Component { - private svgPanZoomRef: React.RefObject = React.createRef(); - constructor(props: ISvgViewerProps) { - super(props); - // tslint:disable-next-line: no-object-literal-type-assertion - this.state = { value: props.defaultValue ? props.defaultValue : ({} as Value), tool: props.tool }; - } - - public componentDidUpdate(prevProps: ISvgViewerProps) { - // May need to update state if props changed - if (prevProps.defaultValue !== this.props.defaultValue || this.props.id !== prevProps.id) { - this.setState({ - // tslint:disable-next-line: no-object-literal-type-assertion - value: this.props.defaultValue ? this.props.defaultValue : ({} as Value), - tool: this.props.tool - }); - } else if (this.props.tool !== this.state.tool) { - this.setState({ tool: this.props.tool }); - } - } - - public move(offsetX: number, offsetY: number) { - if (this.svgPanZoomRef && this.svgPanZoomRef.current) { - this.svgPanZoomRef.current.pan(offsetX, offsetY); - } - } - - public zoom(amount: number) { - if (this.svgPanZoomRef && this.svgPanZoomRef.current) { - this.svgPanZoomRef.current.zoomOnViewerCenter(amount); - } - } - - public render() { - const plotBackground = this.props.themeMatplotlibPlots - ? 'var(--override-widget-background, var(--vscode-notifications-background))' - : 'white'; - return ( - - {({ height, width }) => - width === 0 || height === 0 ? null : ( - - - - - - ) - } - - ); - } - - private changeTool = (tool: Tool) => { - this.setState({ tool }); - }; - - private changeValue = (value: Value) => { - this.setState({ value }); - this.props.changeValue(value); - }; - - private renderToolbar = () => { - // Hide toolbar too - return
; - }; - - private renderMiniature = () => { - return ( -
// Hide miniature - ); - }; -} diff --git a/src/datascience-ui/react-common/textMeasure.ts b/src/datascience-ui/react-common/textMeasure.ts deleted file mode 100644 index 8e75f75b5da7..000000000000 --- a/src/datascience-ui/react-common/textMeasure.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -let canvas: HTMLCanvasElement | undefined; - -function getCanvas(): HTMLCanvasElement { - if (!canvas) { - canvas = document.createElement('canvas'); - } - return canvas; -} - -export function measureText(text: string, font: string | null): number { - const context = getCanvas().getContext('2d'); - if (context) { - if (font) { - context.font = font; - } - const metrics = context.measureText(text); - return metrics.width; - } - return 0; -} diff --git a/src/datascience-ui/react-common/themeDetector.ts b/src/datascience-ui/react-common/themeDetector.ts deleted file mode 100644 index b217a7334f22..000000000000 --- a/src/datascience-ui/react-common/themeDetector.ts +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -// From here: -// https://stackoverflow.com/questions/37257911/detect-light-dark-theme-programatically-in-visual-studio-code -// Detect vscode-light, vscode-dark, and vscode-high-contrast class name on the body element. -export function detectBaseTheme(): 'vscode-light' | 'vscode-dark' | 'vscode-high-contrast' { - const body = document.body; - if (body) { - switch (body.className) { - default: - case 'vscode-light': - return 'vscode-light'; - case 'vscode-dark': - return 'vscode-dark'; - case 'vscode-high-contrast': - return 'vscode-high-contrast'; - } - } - - return 'vscode-light'; -} diff --git a/src/datascience-ui/renderers/constants.ts b/src/datascience-ui/renderers/constants.ts deleted file mode 100644 index bc14f44742b4..000000000000 --- a/src/datascience-ui/renderers/constants.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -export const JupyterNotebookRenderer = 'jupyter-notebook-renderer'; diff --git a/src/datascience-ui/renderers/index.tsx b/src/datascience-ui/renderers/index.tsx deleted file mode 100644 index 6d574cb1ee77..000000000000 --- a/src/datascience-ui/renderers/index.tsx +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -// This must be on top, do not change. Required by webpack. -declare let __webpack_public_path__: string; -const getPublicPath = () => { - const currentDirname = (document.currentScript as HTMLScriptElement).src.replace(/[^/]+$/, ''); - return new URL(currentDirname).toString(); -}; - -__webpack_public_path__ = getPublicPath(); -// This must be on top, do not change. Required by webpack. - -import type { nbformat } from '@jupyterlab/coreutils'; -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import type { NotebookOutputEventParams } from 'vscode-notebook-renderer'; -import '../../client/common/extensions'; -import { JupyterNotebookRenderer } from './constants'; -import { CellOutput } from './render'; - -const notebookApi = acquireNotebookRendererApi(JupyterNotebookRenderer); - -notebookApi.onDidCreateOutput(renderOutput); - -/** - * Called from renderer to render output. - * This will be exposed as a public method on window for renderer to render output. - */ -function renderOutput(request: NotebookOutputEventParams) { - try { - // tslint:disable-next-line: no-console - console.error('request', request); - const output = convertVSCodeOutputToExecutResultOrDisplayData(request); - // tslint:disable-next-line: no-console - console.log(`Rendering mimeType ${request.mimeType}`, output); - // tslint:disable-next-line: no-console - console.error('request output', output); - - ReactDOM.render(React.createElement(CellOutput, { mimeType: request.mimeType, output }, null), request.element); - } catch (ex) { - // tslint:disable-next-line: no-console - console.error(`Failed to render mime type ${request.mimeType}`, ex); - } -} - -function convertVSCodeOutputToExecutResultOrDisplayData( - request: NotebookOutputEventParams -): nbformat.IExecuteResult | nbformat.IDisplayData { - // tslint:disable-next-line: no-any - const metadata: Record = {}; - // Send metadata only for the mimeType we are interested in. - const customMetadata = request.output.metadata?.custom; - if (customMetadata) { - if (customMetadata[request.mimeType]) { - metadata[request.mimeType] = customMetadata[request.mimeType]; - } - if (customMetadata.needs_background) { - metadata.needs_background = customMetadata.needs_background; - } - if (customMetadata.unconfined) { - metadata.unconfined = customMetadata.unconfined; - } - } - - return { - data: { - [request.mimeType]: request.output.data[request.mimeType] - }, - metadata, - execution_count: null, - output_type: request.output.metadata?.custom?.vscode?.outputType || 'execute_result' - }; -} diff --git a/src/datascience-ui/renderers/render.tsx b/src/datascience-ui/renderers/render.tsx deleted file mode 100644 index e1e4fbdd8f73..000000000000 --- a/src/datascience-ui/renderers/render.tsx +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import type { nbformat } from '@jupyterlab/coreutils'; -import type { JSONObject } from '@phosphor/coreutils'; -import * as React from 'react'; -import { concatMultilineString } from '../common'; -import { fixMarkdown } from '../interactive-common/markdownManipulation'; -import { getTransform } from '../interactive-common/transforms'; - -export interface ICellOutputProps { - output: nbformat.IExecuteResult | nbformat.IDisplayData; - mimeType: string; -} - -export class CellOutput extends React.Component { - constructor(prop: ICellOutputProps) { - super(prop); - } - public render() { - const mimeBundle = this.props.output.data; - const data: nbformat.MultilineString | JSONObject = mimeBundle[this.props.mimeType!]; - - switch (this.props.mimeType) { - case 'text/latex': - return this.renderLatex(data); - case 'image/png': - case 'image/jpeg': - return this.renderImage(mimeBundle, this.props.output.metadata); - - default: - return this.renderOutput(data, this.props.mimeType); - } - } - /** - * Custom rendering of image/png and image/jpeg to handle custom Jupyter metadata. - * Behavior adopted from Jupyter lab. - */ - // tslint:disable-next-line: no-any - private renderImage(mimeBundle: nbformat.IMimeBundle, metadata: Record = {}) { - const mimeType = 'image/png' in mimeBundle ? 'image/png' : 'image/jpeg'; - - const imgStyle: Record = {}; - const divStyle: Record = { overflow: 'scroll' }; // This is the default style used by Jupyter lab. - const imgSrc = `data:${mimeType};base64,${mimeBundle[mimeType]}`; - - if (typeof metadata.needs_background === 'string') { - divStyle.backgroundColor = metadata.needs_background === 'light' ? 'white' : 'black'; - } - // tslint:disable-next-line: no-any - const imageMetadata = metadata[mimeType] as Record | undefined; - if (imageMetadata) { - if (imageMetadata.height) { - imgStyle.height = imageMetadata.height; - } - if (imageMetadata.width) { - imgStyle.width = imageMetadata.width; - } - if (imageMetadata.unconfined === true) { - imgStyle.maxWidth = 'none'; - } - } - - // Hack, use same classes as used in VSCode for images (keep things as similar as possible). - // This is to maintain consistently in displaying images (if we hadn't used HTML). - // See src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts - // tslint:disable: react-a11y-img-has-alt - return ( -
- -
- ); - } - private renderOutput(data: nbformat.MultilineString | JSONObject, mimeType?: string) { - const Transform = getTransform(this.props.mimeType!); - const divStyle: React.CSSProperties = { - backgroundColor: mimeType && isAltairPlot(mimeType) ? 'white' : undefined - }; - return ( -
- -
- ); - } - private renderLatex(data: nbformat.MultilineString | JSONObject) { - // Fixup latex to make sure it has the requisite $$ around it - data = fixMarkdown(concatMultilineString(data as nbformat.MultilineString, true), true); - return this.renderOutput(data); - } -} - -function isAltairPlot(mimeType: string) { - return mimeType.includes('application/vnd.vega'); -} diff --git a/src/datascience-ui/startPage/index.html b/src/datascience-ui/startPage/index.html deleted file mode 100644 index b405e61a8e80..000000000000 --- a/src/datascience-ui/startPage/index.html +++ /dev/null @@ -1,356 +0,0 @@ - - - - - - - Python Extension Plot Viewer - - - - -
- - - diff --git a/src/datascience-ui/startPage/index.tsx b/src/datascience-ui/startPage/index.tsx deleted file mode 100644 index 1b9828708914..000000000000 --- a/src/datascience-ui/startPage/index.tsx +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -// This must be on top, do not change. Required by webpack. -import '../common/main'; -// This must be on top, do not change. Required by webpack. - -// tslint:disable-next-line: ordered-imports -import '../common/index.css'; - -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; - -import { IVsCodeApi } from '../react-common/postOffice'; -import { detectBaseTheme } from '../react-common/themeDetector'; -import { StartPage } from './startPage'; - -// This special function talks to vscode from a web panel -export declare function acquireVsCodeApi(): IVsCodeApi; - -const baseTheme = detectBaseTheme(); -// tslint:disable-next-line: no-any -const testMode = (window as any).inTestMode; -// tslint:disable-next-line: no-typeof-undefined -const skipDefault = testMode ? false : typeof acquireVsCodeApi !== 'undefined'; - -ReactDOM.render( - , - document.getElementById('root') as HTMLElement -); diff --git a/src/datascience-ui/startPage/startPage.css b/src/datascience-ui/startPage/startPage.css deleted file mode 100644 index 035610dcebf8..000000000000 --- a/src/datascience-ui/startPage/startPage.css +++ /dev/null @@ -1,93 +0,0 @@ -.main-page { - margin: 50px; - font-family: var(--font-family); - min-width: 700px; -} - -.title-icon { - display: inline-block; - vertical-align: top; - width: 40px; - margin-right: 10px; -} - -.icon { - display: inline-block; - width: 45px; - padding: 20px 25px 20px 25px; - margin-right: 30px; - background-color: var(--vscode-titleBar-activeBackground); -} - -.icon:hover, -.text:hover { - background-color: var(--vscode-editorIndentGuide-activeBackground); - cursor: pointer; -} - -.title { - display: inline-block; - vertical-align: top; - font-size: xx-large; -} - -.title-row { - display: block; - height: 80px; -} - -.text { - font-weight: 100; - font-size: x-large; - width: fit-content; -} - -.block { - display: inline-block; - vertical-align: top; -} - -.row { - display: block; - min-height: 120px; - white-space: nowrap; -} - -.releaseNotesRow { - display: block; - min-height: 50px; - white-space: nowrap; -} - -.link { - display: inline; - color: var(--vscode-debugIcon-continueForeground); - text-decoration: none; -} - -.link:hover { - cursor: pointer; - color: var(--vscode-button-hoverBackground); -} - -.italics { - display: inline; - font-style: italic; -} - -.checkbox { - margin: 1em 1em 1em 0em; -} - -.paragraph { - display: block; - margin-block-start: 5px; - margin-block-end: 5px; - margin-inline-start: 0px; - margin-inline-end: 0px; -} - -.list { - line-height: 1.5; - white-space: initial; -} diff --git a/src/datascience-ui/startPage/startPage.tsx b/src/datascience-ui/startPage/startPage.tsx deleted file mode 100644 index 3794c22919fe..000000000000 --- a/src/datascience-ui/startPage/startPage.tsx +++ /dev/null @@ -1,293 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import * as React from 'react'; -import '../../client/common/extensions'; -import { ISettingPackage, IStartPageMapping, StartPageMessages } from '../../client/common/startPage/types'; -import { Image, ImageName } from '../react-common/image'; -import { getLocString } from '../react-common/locReactSide'; -import { IMessageHandler, PostOffice } from '../react-common/postOffice'; -import './startPage.css'; - -export interface IStartPageProps { - skipDefault?: boolean; - baseTheme: string; - testMode?: boolean; -} - -// Front end of the Python extension start page. -// In general it consists of its render method and methods that send and receive messages. -export class StartPage extends React.Component implements IMessageHandler { - private releaseNotes: ISettingPackage = { - showAgainSetting: false - }; - private postOffice: PostOffice = new PostOffice(); - - constructor(props: IStartPageProps) { - super(props); - } - - public componentDidMount() { - this.postOffice.sendMessage(StartPageMessages.RequestShowAgainSetting); - } - - // tslint:disable: no-any - public componentWillMount() { - // Add ourselves as a handler for the post office - this.postOffice.addHandler(this); - - // Tell the start page code we have started. - this.postOffice.sendMessage(StartPageMessages.Started); - - // Bind some functions to the window, as we need them to be accessible with clean HTML to use translations - (window as any).openBlankNotebook = this.openBlankNotebook.bind(this); - (window as any).createPythonFile = this.createPythonFile.bind(this); - (window as any).openFileBrowser = this.openFileBrowser.bind(this); - (window as any).openFolder = this.openFolder.bind(this); - (window as any).openWorkspace = this.openWorkspace.bind(this); - (window as any).openCommandPalette = this.openCommandPalette.bind(this); - (window as any).openCommandPaletteWithSelection = this.openCommandPaletteWithSelection.bind(this); - (window as any).openSampleNotebook = this.openSampleNotebook.bind(this); - } - - public render() { - // tslint:disable: react-a11y-anchors - return ( -
-
-
- -
-
{getLocString('StartPage.pythonExtensionTitle', 'Python Extension')}
-
-
-
- -
-
-
- {getLocString('StartPage.CreateJupyterNotebook', 'Create a Jupyter Notebook')} -
- {this.renderNotebookDescription()} -
-
-
-
- -
-
-
- {getLocString('StartPage.createAPythonFile', 'Create a Python File')} -
- {this.renderPythonFileDescription()} -
-
-
-
- -
-
-
- {getLocString('StartPage.openFolder', 'Open a Folder or Workspace')} -
- {this.renderFolderDescription()} -
-
-
-
- -
-
-
- {getLocString( - 'StartPage.openInteractiveWindow', - 'Use the Interactive Window to develop Python Scripts' - )} -
- {this.renderInteractiveWindowDescription()} -
-
-
- {this.renderReleaseNotesLink()} - {this.renderTutorialAndDoc()} -
-
- -
-
-

{getLocString('StartPage.dontShowAgain', "Don't show this page again")}

-
-
- ); - } - - // tslint:disable-next-line: no-any - public handleMessage = (msg: string, payload?: any) => { - if (msg === StartPageMessages.SendSetting) { - this.releaseNotes.showAgainSetting = payload.showAgainSetting; - this.setState({}); - } - - return false; - }; - - public openFileBrowser() { - this.postOffice.sendMessage(StartPageMessages.OpenFileBrowser); - } - - public openFolder = () => { - this.postOffice.sendMessage(StartPageMessages.OpenFolder); - }; - - public openWorkspace() { - this.postOffice.sendMessage(StartPageMessages.OpenWorkspace); - } - - private renderNotebookDescription(): JSX.Element { - // tslint:disable: react-no-dangerous-html - return ( - " in the Command Palette (
Shift + Command + P
)
- Explore our to learn about notebook features' - ).format('openCommandPaletteWithSelection()', 'openSampleNotebook()') - }} - /> - ); - } - - private renderPythonFileDescription(): JSX.Element { - // tslint:disable: react-no-dangerous-html - return ( - with a .py extension' - ).format('createPythonFile()') - }} - /> - ); - } - - private renderInteractiveWindowDescription(): JSX.Element { - // tslint:disable: react-no-dangerous-html - return ( -
- Use "
Shift + Enter
" to run a cell, the output will be shown in the interactive window' - ) - }} - /> - ); - } - - private renderFolderDescription(): JSX.Element { - // tslint:disable: react-no-dangerous-html - return ( -
- Open a ' - ).format('openFolder()', 'openWorkspace()') - }} - /> - ); - } - - private renderReleaseNotesLink(): JSX.Element { - // tslint:disable: react-no-dangerous-html - return ( -